import { NextResponse } from "next/server"; import { z } from "zod"; import { apiError, parseBody } from "@/lib/api.ts"; import { hashPassword, passwordPolicyError, verifyPassword } from "@/lib/auth/password.ts"; import { assertSameOrigin, destroyAllSessions, logAuthEvent, requireUser, SESSION_COOKIE, createSession } from "@/lib/auth/session.ts"; import { get, run } from "@/lib/db/index.ts"; const schema = z.object({ currentPassword: z.string().max(200), newPassword: z.string().max(200), }); export async function POST(req: Request) { try { await assertSameOrigin(); const user = await requireUser(); const { currentPassword, newPassword } = await parseBody(req, schema); const row = get<{ password_hash: string }>("SELECT password_hash FROM users WHERE id = ?", user.id); if (!row || !verifyPassword(currentPassword, row.password_hash)) { return NextResponse.json({ error: "Mot de passe actuel incorrect." }, { status: 401 }); } const policyErr = passwordPolicyError(newPassword); if (policyErr) return NextResponse.json({ error: policyErr }, { status: 400 }); if (newPassword === currentPassword) { return NextResponse.json({ error: "Le nouveau mot de passe doit être différent de l'actuel." }, { status: 400 }); } run( "UPDATE users SET password_hash = ?, must_change_password = 0 WHERE id = ?", hashPassword(newPassword), user.id ); // Révoque toutes les sessions puis en recrée une propre. destroyAllSessions(user.id); const ip = req.headers.get("x-forwarded-for")?.split(",")[0]?.trim() || "local"; const { token, expiresAt } = createSession(user.id, ip, req.headers.get("user-agent") ?? undefined); logAuthEvent("password-changed", { userId: user.id, username: user.username, ip }); const res = NextResponse.json({ ok: true }); res.cookies.set(SESSION_COOKIE, token, { httpOnly: true, sameSite: "lax", secure: process.env.NODE_ENV === "production" && (process.env.APP_URL ?? "").startsWith("https"), expires: expiresAt, path: "/", }); return res; } catch (e) { return apiError(e); } }