import { NextResponse } from "next/server"; import { getPayload } from "payload"; import config from "@payload-config"; import { checkRateLimit, getClientIp } from "@/lib/auth/rateLimit"; import { EMAIL_VERIFICATION_TTL_MS, generateVerificationToken, hashVerificationToken, verificationExpiryISO } from "@/lib/auth/verification"; import { verificationEmail } from "@/lib/email/authTemplates"; import { sendEmail } from "@/lib/email/sendBookingEmails"; import type { Customer } from "@/payload-types"; export const dynamic = "force-dynamic"; // Deliberately identical whether or not an account exists / is already // verified — this endpoint must never let a caller find out either of // those things about an email address that isn't their own session. const GENERIC_RESPONSE = { ok: true, message: "Falls ein Konto mit dieser E-Mail-Adresse existiert und noch nicht bestätigt ist, wurde eine neue Bestätigungs-E-Mail gesendet.", }; async function issueAndSend(payload: Awaited>, customer: Customer) { const token = generateVerificationToken(); await payload.update({ collection: "customers", id: customer.id, data: { emailVerificationTokenHash: hashVerificationToken(token), emailVerificationExpires: verificationExpiryISO(), }, }); const serverUrl = process.env.NEXT_PUBLIC_SERVER_URL || "http://localhost:3000"; await sendEmail( customer.email, verificationEmail({ name: customer.name, verifyUrl: `${serverUrl}/auth/verify-email/${token}`, expiresHours: Math.round(EMAIL_VERIFICATION_TTL_MS / (60 * 60 * 1000)), }), ); } export async function POST(request: Request) { const ip = getClientIp(request); if (!checkRateLimit(`resend-verification:ip:${ip}`, { max: 10, windowMs: 60 * 60 * 1000 })) { return NextResponse.json({ error: "Zu viele Anfragen. Bitte versuche es später erneut." }, { status: 429 }); } try { const payload = await getPayload({ config }); const { user } = await payload.auth({ headers: request.headers }); let customer: Customer | null = null; if (user && user.collection === "customers") { customer = user as Customer; } else { let body: { email?: unknown } = {}; try { body = await request.json(); } catch { // no body — handled below as a missing email } const email = typeof body.email === "string" ? body.email.trim().toLowerCase() : ""; if (!email) { return NextResponse.json({ error: "E-Mail-Adresse erforderlich." }, { status: 400 }); } if (!checkRateLimit(`resend-verification:email:${email}`, { max: 3, windowMs: 60 * 60 * 1000 })) { return NextResponse.json({ error: "Zu viele Anfragen. Bitte versuche es später erneut." }, { status: 429 }); } const { docs } = await payload.find({ collection: "customers", where: { email: { equals: email } }, limit: 1, }); customer = (docs[0] as Customer) ?? null; } if (customer && !customer.emailVerified) { if (!checkRateLimit(`resend-verification:customer:${customer.id}`, { max: 3, windowMs: 60 * 60 * 1000 })) { return NextResponse.json({ error: "Zu viele Anfragen. Bitte versuche es später erneut." }, { status: 429 }); } await issueAndSend(payload, customer); } return NextResponse.json(GENERIC_RESPONSE); } catch (error) { console.error("resend verification failed", error); return NextResponse.json({ error: "Die Bestätigungs-E-Mail konnte nicht gesendet werden." }, { status: 500 }); } }