"use server"; import { eq } from "drizzle-orm"; import { sendVerification } from "@/lib/auth/email-verification"; import { db, User } from "@/lib/db"; import { clientIp, rateLimit } from "@/lib/rate-limit"; export interface ResendVerificationState { ok: boolean; error: string | null; } const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; /** * Re-send a verification e-mail for an address the visitor typed on /verify. * * Deliberately reports success even when no matching unverified account exists: * a distinct failure would let anyone probe which addresses are registered. The * identical-privacy behaviour also applies to the e-mail templates, which are * only sent for real accounts. Rate limiting is the spam defence. */ export async function resendVerification( _prevState: ResendVerificationState, formData: FormData, ): Promise { const email = String(formData.get("email") ?? "") .normalize("NFC") .trim() .toLowerCase(); if (!EMAIL_RE.test(email)) { return { ok: false, error: "invalid" }; } const ip = await clientIp(); if (!(await rateLimit(`verify:resend:${ip}`, 3, 10 * 60_000)).ok) { return { ok: false, error: "rateLimited" }; } try { const [user] = await db .select({ id: User.id, mailVerified: User.mailVerified }) .from(User) .where(eq(User.mail, email)) .limit(1); if (user && user.mailVerified !== "1") { await sendVerification(email); } } catch { return { ok: false, error: "unavailable" }; } return { ok: true, error: null }; }