import { and, desc, eq, gt } from "drizzle-orm"; import { NextResponse } from "next/server"; import { z } from "zod"; import { createEmailVerificationCode, EMAIL_VERIFICATION_PURPOSE_REGISTER, hashEmailVerificationCode, resolveResendConfig, sendVerificationCodeByResend, } from "@/lib/auth/email-verification"; import { db } from "@/lib/db"; import { emailVerificationCode, siteSettings, user } from "@/lib/db/schema"; import { getAuthSettings } from "@/lib/db/queries"; import { checkRateLimit, getClientIdentifier, rateLimiters } from "@/lib/security/rate-limiter"; const payloadSchema = z.object({ email: z.string().email(), }); const SEND_CODE_COOLDOWN_SECONDS = 60; const emailCooldownMap = new Map(); function getCooldownSeconds(email: string): number { const expiredAt = emailCooldownMap.get(email.toLowerCase()); if (!expiredAt) { return 0; } const remaining = Math.ceil((expiredAt - Date.now()) / 1000); return remaining > 0 ? remaining : 0; } function setCooldown(email: string): void { emailCooldownMap.set( email.toLowerCase(), Date.now() + SEND_CODE_COOLDOWN_SECONDS * 1000 ); } export async function POST(request: Request) { try { const rateLimit = checkRateLimit(request, rateLimiters.auth); if (!rateLimit.allowed) { return NextResponse.json({ error: "请求过于频繁,请稍后重试" }, { status: 429 }); } const body = await request.json(); const parsed = payloadSchema.safeParse(body); if (!parsed.success) { return NextResponse.json({ error: "邮箱格式不正确" }, { status: 400 }); } const email = parsed.data.email.trim().toLowerCase(); const clientId = getClientIdentifier(request); const localCooldown = getCooldownSeconds(`${clientId}:${email}`); if (localCooldown > 0) { return NextResponse.json( { error: `验证码发送过于频繁,请 ${localCooldown}s 后重试` }, { status: 429 } ); } const authSettings = await getAuthSettings(); if (authSettings.isRegistrationEnabled === false) { return NextResponse.json({ error: "当前系统未开放注册" }, { status: 403 }); } if (authSettings.isEmailVerificationEnabled !== true) { return NextResponse.json({ error: "邮箱验证未启用" }, { status: 400 }); } const resendConfig = resolveResendConfig({ apiKey: authSettings.resendApiKey, fromEmail: authSettings.resendFromEmail, }); if (!resendConfig) { return NextResponse.json( { error: "邮箱服务未配置,请在认证管理中配置 Resend API Key 和发件邮箱" }, { status: 500 } ); } const [existingUser] = await db .select({ id: user.id }) .from(user) .where(eq(user.email, email)) .limit(1); if (existingUser) { return NextResponse.json({ error: "该邮箱已注册" }, { status: 400 }); } const now = new Date(); const [latestCode] = await db .select({ createdAt: emailVerificationCode.createdAt }) .from(emailVerificationCode) .where( and( eq(emailVerificationCode.email, email), eq(emailVerificationCode.purpose, EMAIL_VERIFICATION_PURPOSE_REGISTER), gt(emailVerificationCode.expiresAt, now) ) ) .orderBy(desc(emailVerificationCode.createdAt)) .limit(1); if (latestCode?.createdAt) { const createdAtMs = new Date(latestCode.createdAt).getTime(); const waitSeconds = Math.ceil((createdAtMs + SEND_CODE_COOLDOWN_SECONDS * 1000 - Date.now()) / 1000); if (waitSeconds > 0) { return NextResponse.json( { error: `验证码发送过于频繁,请 ${waitSeconds}s 后重试` }, { status: 429 } ); } } const expiresInMinutes = Math.min( Math.max(Number(authSettings.emailVerificationExpiresMinutes) || 10, 3), 30 ); const [siteSettingRecord] = await db .select({ siteName: siteSettings.siteName }) .from(siteSettings) .limit(1); const code = createEmailVerificationCode(); const codeHash = hashEmailVerificationCode({ email, purpose: EMAIL_VERIFICATION_PURPOSE_REGISTER, code, }); const expiresAt = new Date(Date.now() + expiresInMinutes * 60 * 1000); await db .delete(emailVerificationCode) .where( and( eq(emailVerificationCode.email, email), eq(emailVerificationCode.purpose, EMAIL_VERIFICATION_PURPOSE_REGISTER) ) ); await db.insert(emailVerificationCode).values({ email, purpose: EMAIL_VERIFICATION_PURPOSE_REGISTER, codeHash, expiresAt, }); try { await sendVerificationCodeByResend({ to: email, code, siteName: siteSettingRecord?.siteName || "AI Chatbot", expiresInMinutes, config: resendConfig, }); } catch (sendError) { await db .delete(emailVerificationCode) .where( and( eq(emailVerificationCode.email, email), eq(emailVerificationCode.purpose, EMAIL_VERIFICATION_PURPOSE_REGISTER) ) ); console.error("[Email Verification] Send code failed:", sendError); return NextResponse.json({ error: "验证码发送失败,请稍后重试" }, { status: 500 }); } setCooldown(`${clientId}:${email}`); return NextResponse.json({ success: true, message: "验证码已发送", cooldownSeconds: SEND_CODE_COOLDOWN_SECONDS, expiresInMinutes, }); } catch (error) { console.error("[Email Verification] Send code error:", error); return NextResponse.json({ error: "发送验证码失败" }, { status: 500 }); } }