diff --git a/src/app/api/users/me/password/route.ts b/src/app/api/users/me/password/route.ts new file mode 100644 index 0000000..dcf8b0d --- /dev/null +++ b/src/app/api/users/me/password/route.ts @@ -0,0 +1,60 @@ +import { NextRequest, NextResponse } from "next/server"; +import { auth } from "@/auth"; +import { prisma } from "@/lib/db"; +import bcrypt from "bcryptjs"; + +export async function PATCH(req: NextRequest) { + const session = await auth(); + if (!session?.user?.id) { + return NextResponse.json({ error: "Non authentifié" }, { status: 401 }); + } + + try { + const { currentPassword, newPassword } = await req.json(); + if (!currentPassword || !newPassword) { + return NextResponse.json( + { error: "Mot de passe actuel et nouveau requis" }, + { status: 400 } + ); + } + if (newPassword.length < 8) { + return NextResponse.json( + { error: "Le nouveau mot de passe doit faire au moins 8 caractères" }, + { status: 400 } + ); + } + + const user = await prisma.user.findUnique({ + where: { id: session.user.id }, + select: { passwordHash: true }, + }); + if (!user?.passwordHash) { + return NextResponse.json( + { error: "Compte sans mot de passe (connexion SSO)" }, + { status: 400 } + ); + } + + const ok = await bcrypt.compare(String(currentPassword), user.passwordHash); + if (!ok) { + return NextResponse.json( + { error: "Mot de passe actuel incorrect" }, + { status: 401 } + ); + } + + const passwordHash = await bcrypt.hash(String(newPassword), 10); + await prisma.user.update({ + where: { id: session.user.id }, + data: { passwordHash }, + }); + + return NextResponse.json({ ok: true }); + } catch (e) { + console.error("Password change error:", e); + return NextResponse.json( + { error: "Erreur lors du changement de mot de passe" }, + { status: 500 } + ); + } +} diff --git a/src/app/settings/page.tsx b/src/app/settings/page.tsx new file mode 100644 index 0000000..27536bb --- /dev/null +++ b/src/app/settings/page.tsx @@ -0,0 +1,129 @@ +"use client"; + +import { useState } from "react"; +import Link from "next/link"; + +export default function SettingsPage() { + const [currentPassword, setCurrentPassword] = useState(""); + const [newPassword, setNewPassword] = useState(""); + const [confirmPassword, setConfirmPassword] = useState(""); + const [error, setError] = useState(""); + const [success, setSuccess] = useState(false); + const [loading, setLoading] = useState(false); + + async function handleSubmit(e: React.FormEvent) { + e.preventDefault(); + setError(""); + setSuccess(false); + if (newPassword !== confirmPassword) { + setError("Les deux nouveaux mots de passe ne correspondent pas"); + return; + } + if (newPassword.length < 8) { + setError("Le mot de passe doit faire au moins 8 caractères"); + return; + } + setLoading(true); + try { + const res = await fetch("/api/users/me/password", { + method: "PATCH", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + currentPassword, + newPassword, + }), + }); + const data = await res.json(); + if (!res.ok) { + setError(data.error ?? "Erreur"); + return; + } + setSuccess(true); + setCurrentPassword(""); + setNewPassword(""); + setConfirmPassword(""); + } catch { + setError("Erreur de connexion"); + } finally { + setLoading(false); + } + } + + return ( +
+ + ← Retour au dashboard + +
+