import { NextResponse } from "next/server"; import { z } from "zod"; import { createPasswordResetToken } from "@/lib/password-reset"; import { prisma } from "@/lib/prisma"; import { sendPasswordReset } from "@/lib/email"; import { recordAudit } from "@/lib/admin/audit"; import { rateLimitRequest } from "@/lib/rate-limit"; export const runtime = "nodejs"; const schema = z.object({ email: z.string().trim().toLowerCase().email(), }); const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL ?? "https://karbe.cosmolan.fr"; export async function POST(req: Request) { const rl = rateLimitRequest(req, "password-reset", 60 * 60 * 1000, 3); if (!rl.ok) { return NextResponse.json( { error: `Trop de tentatives. Réessayez dans ${rl.retryAfter}s.` }, { status: 429, headers: { "Retry-After": String(rl.retryAfter) } }, ); } let body: unknown; try { body = await req.json(); } catch { return NextResponse.json({ error: "Corps JSON invalide." }, { status: 400 }); } const parsed = schema.safeParse(body); if (!parsed.success) { // Réponse générique pour ne pas leak la validité du format à un attaquant. return NextResponse.json({ ok: true }); } const user = await prisma.user.findUnique({ where: { email: parsed.data.email }, select: { id: true, email: true, firstName: true, isActive: true }, }); if (user && user.isActive) { const token = await createPasswordResetToken(user.id); const resetUrl = `${SITE_URL}/mot-de-passe-oublie/${token}`; sendPasswordReset(user.email, resetUrl).catch(() => {}); await recordAudit({ scope: "public.password", event: "reset.request", target: user.id, actorEmail: user.email, details: {}, }); } // Réponse identique que l'email existe ou non (énumération-safe). return NextResponse.json({ ok: true }); }