import { NextRequest, NextResponse } from "next/server"; import { z } from "zod"; import { cookies } from "next/headers"; import { verifyCode } from "@/lib/auth/otp"; import { assertSameOrigin, clientIp, createSession, cookieOptions, COOKIE, userAgent } from "@/lib/auth/session"; import { handle, parseBody } from "@/lib/api"; import { hit } from "@/lib/auth/ratelimit"; import { ipHash } from "@/lib/auth/crypto"; export const dynamic = "force-dynamic"; const Body = z.object({ challengeId: z.string().uuid(), code: z.string().regex(/^\d{6}$/), acceptTerms: z.boolean().optional().default(false) }); const MSG: Record = { invalid: "Code invalide.", expired: "Ce code a expiré. Demandez-en un nouveau.", too_many_attempts: "Trop d'essais. Demandez un nouveau code.", not_found: "Demande introuvable. Recommencez.", terms_required: "Nouveau compte : acceptez les conditions puis confirmez le code." }; export async function POST(req: NextRequest) { try { await assertSameOrigin(); const { challengeId, code, acceptTerms } = await parseBody(req, Body); const ip = await clientIp(); const rl = await hit(`otp:verify:${ipHash(ip)}`, 30, 600); if (!rl.ok) return NextResponse.json({ error: { code: "rate_limited", message: "Trop de tentatives. Réessayez plus tard." } }, { status: 429 }); const r = await verifyCode(challengeId, code, ip, acceptTerms); if (!r.ok) return NextResponse.json({ error: { code: r.reason, message: MSG[r.reason] } }, { status: r.reason === "terms_required" ? 409 : 400 }); const s = await createSession(r.userId, { ip, device: await userAgent() }); (await cookies()).set(COOKIE, s.token, cookieOptions(s.expiresAt)); return NextResponse.json({ ok: true, created: r.created, user: { id: r.userId, email: r.email } }); } catch (e) { return handle(e); } }