Files
probably-rendering-one-more…/apps/dashboard/src/lib/account-handler.ts
T

56 lines
4.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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);}
}