56 lines
4.3 KiB
TypeScript
56 lines
4.3 KiB
TypeScript
import { NextResponse } from 'next/server';
|
||
import { normalizeEmail,validPassword, trustedOrigin,accountCookie } from './account-crypto';
|
||
import { requestPasswordReset,resetPassword,rateLimit,registerAccount,resendVerification,verifyAccount,loginAccount,currentAccount,logoutAccount } from './account';
|
||
export const runtime = 'nodejs';
|
||
const headers = {'Cache-Control':'private, no-store','Referrer-Policy':'no-referrer'};
|
||
const json = (body: object,status=200) => NextResponse.json(body,{status,headers});
|
||
type Context = {params: Promise<{action:string}>};
|
||
export async function GET(request: Request,context: Context) {
|
||
if ((await context.params).action !== 'session') return json({error:'不存在'},404);
|
||
try { return json({user:await currentAccount(request)}); } catch { return json({error:'账户服务暂不可用'},503); }
|
||
}
|
||
export async function POST(request: Request,context: Context) {
|
||
if (!trustedOrigin(request)) return json({error:'请求来源不受信任'},403);
|
||
const {action} = await context.params;
|
||
if (!['register','resend','verify','login','logout','forgot','reset'].includes(action)) return json({error:'不存在'},404);
|
||
try {
|
||
// Revocation remains available even when login or mail limits are exhausted.
|
||
if (action === 'logout') { await logoutAccount(request); const r=json({ok:true});r.headers.set('Set-Cookie',accountCookie('',true));return r; }
|
||
// Use a global cap by default: forwarding headers are untrusted unless the deployment explicitly enables a trusted ingress.
|
||
const ip = process.env.ACCOUNT_TRUST_PROXY === 'true' ? request.headers.get('x-forwarded-for')?.split(',')[0].trim() || 'unknown' : 'global';
|
||
if (!await rateLimit('request:'+ip,process.env.ACCOUNT_TRUST_PROXY === 'true' ? 60 : 300)) return json({error:'请求过于频繁,请稍后再试'},429);
|
||
if (Number(request.headers.get('content-length') || 0)>4096) return json({error:'请求过大'},413);
|
||
const text=await request.text(); if(text.length>4096)return json({error:'请求过大'},413);
|
||
let body;try{body=JSON.parse(text);}catch{return json({error:'请求格式错误'},400);}
|
||
if (!body || typeof body!=='object') return json({error:'请求格式错误'},400);
|
||
if (action === 'verify') {
|
||
if(typeof body.token!=='string'||! /^[a-f0-9]{64}$/.test(body.token))return json({error:'验证链接无效或已过期'},400);
|
||
return await verifyAccount(body.token) ? json({ok:true,message:'邮箱已验证,请登录'}) : json({error:'验证链接无效或已过期'},400);
|
||
}
|
||
if(action==='reset') {
|
||
if(typeof body.token!=='string'||!/^[a-f0-9]{64}$/.test(body.token)||!validPassword(body.password))return json({error:'重设链接无效,或密码不符合要求'},400);
|
||
if(!await rateLimit('reset:'+body.token,5))return json({error:'请求过于频繁,请稍后再试'},429);
|
||
if(!await resetPassword(body.token,body.password))return json({error:'重设链接无效或已过期,请重新申请'},400);
|
||
const r=json({ok:true,message:'密码已更新,请使用新密码登录'});r.headers.set('Set-Cookie',accountCookie('',true));return r;
|
||
}
|
||
if(action==='forgot') {
|
||
const email=normalizeEmail(body.email);
|
||
if(!email)return json({error:'请输入有效邮箱'},400);
|
||
if(!await rateLimit('forgot:'+email,3))return json({error:'请求过于频繁,请 15 分钟后再试'},429);
|
||
await requestPasswordReset(email);
|
||
return json({ok:true,message:'若该邮箱对应已验证账户,重设链接将发送至邮箱,有效期 30 分钟。'});
|
||
}
|
||
const email=normalizeEmail(body.email);
|
||
if(!email||!validPassword(body.password))return json({error:'请输入有效邮箱及 12–256 字节密码'},400);
|
||
if(!await rateLimit(action+':'+email,action==='login'?10:3))return json({error:'请求过于频繁,请 15 分钟后再试'},429);
|
||
if(action==='register')await registerAccount(email,body.password);
|
||
if(action==='resend')await resendVerification(email,body.password);
|
||
if(action==='login') {
|
||
const token=await loginAccount(email,body.password);
|
||
if(!token)return json({error:'邮箱或密码错误,或邮箱尚未验证'},401);
|
||
const r=json({ok:true});r.headers.set('Set-Cookie',accountCookie(token));return r;
|
||
}
|
||
return json({ok:true,message:'若账户符合条件,验证邮件已发送。请检查收件箱和垃圾邮件。'});
|
||
}catch{return json({error:'账户服务暂不可用,请稍后重试'},503);}
|
||
}
|