Files
PleinR/src/app/backend/changer-mot-de-passe/page.tsx
T
Claude b0788d3f20 Durcissement : mot de passe actuel, sessions révocables, dépendances, CSP, tests
Suite de l'audit : les cinq points laissés en suspens sont traités.

Mot de passe
- `changeOwnPassword` exige désormais le mot de passe actuel. Un poste laissé
  ouvert ne suffit plus à s'approprier un compte. Les erreurs reviennent sur
  l'écran avec un message au lieu d'une page d'erreur brute.

Sessions révocables
- Colonne `users.session_version`, portée dans le jeton et comparée à la base.
- `getSession()` (src/lib/session.ts) remplace `auth()` sur les 20 pages et
  actions : rôle, rattachement adhérent et existence du compte sont relus à
  chaque requête. Supprimer un compte ou réinitialiser un mot de passe coupe
  immédiatement les sessions ouvertes, sans attendre l'expiration du jeton.

Dépendances — de 8 vulnérabilités (2 critiques) à zéro
- next 15.5.19 → 15.5.21, next-auth beta.25 → beta.32, drizzle-orm 0.38 → 0.45.
- postcss et sharp forcés par `overrides` sur leurs versions corrigées, Next ne
  les ayant pas encore reprises ; drizzle-kit et esbuild montés côté outillage.
- `npm audit fix --force` a été écarté : il proposait de RÉTROGRADER Next en
  9.3.3 et eslint-config-next en 12, ce qui aurait cassé l'application.
- `eslint-config-next` traînait dans node_modules sans être déclaré : retiré.

CSP complète
- Politique à nonce posée par le middleware, nonce régénéré à chaque requête,
  `script-src` sans 'unsafe-inline'. `style-src` garde 'unsafe-inline' : tout le
  design repose sur des attributs style, et une injection de style n'a pas la
  portée d'une injection de script.
- Conséquence assumée : rendu dynamique pour toutes les pages, un HTML
  pré-généré ne pouvant pas porter de nonce.

Tests
- `npm test` (runner natif node:test via tsx), 18 tests sur le filtre XSS, la
  limitation des tentatives de connexion et le chiffrement des jetons.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QXNXRC4j5VLfKvpyyisrnb
2026-07-25 10:18:04 +00:00

68 lines
2.6 KiB
TypeScript

import { redirect } from "next/navigation";
import { getSession } from "@/lib/session";
import { changeOwnPassword } from "../actions";
export const dynamic = "force-dynamic";
export default async function ChangePasswordPage({
searchParams,
}: {
searchParams: Promise<{ error?: string }>;
}) {
const session = await getSession();
if (!session?.user) redirect("/login");
const { error } = await searchParams;
const forced = session.user.mustChangePassword;
return (
<div style={{ maxWidth: 460 }}>
<div style={{ background: "#fff", border: "1px solid #e6dcc6", borderRadius: 16, padding: 26 }}>
<h3 className="font-display" style={{ fontWeight: 800, fontSize: 20, margin: "0 0 8px", color: "#26201a" }}>
Changer mon mot de passe
</h3>
{forced && (
<div style={{ background: "#fbeede", border: "1px solid #ecd8b8", color: "#9a6638", borderRadius: 10, padding: "11px 14px", fontSize: 13.5, marginBottom: 16 }}>
Première connexion : pour des raisons de sécurité, choisissez un nouveau mot de passe
avant de continuer.
</div>
)}
{error && (
<div style={{ background: "#fbe9e6", border: "1px solid #f2d5cf", color: "#a8503c", borderRadius: 10, padding: "11px 14px", fontSize: 13.5, marginBottom: 16 }}>
{error}
</div>
)}
<form action={changeOwnPassword}>
<label className="field-label">
{forced ? "Mot de passe temporaire reçu" : "Mot de passe actuel"}
</label>
<input
name="currentPassword"
type="password"
required
className="field"
style={{ marginBottom: 14 }}
autoComplete="current-password"
/>
<label className="field-label">Nouveau mot de passe (8 caractères min.)</label>
<input name="password" type="password" required minLength={8} className="field" style={{ marginBottom: 14 }} autoComplete="new-password" />
<label className="field-label">Confirmer le mot de passe</label>
<input name="confirm" type="password" required minLength={8} className="field" style={{ marginBottom: 18 }} autoComplete="new-password" />
<button
type="submit"
className="font-display"
style={{ width: "100%", border: "none", background: "#13324F", color: "#fff", fontWeight: 700, fontSize: 14.5, padding: 13, borderRadius: 11, cursor: "pointer" }}
>
Enregistrer le nouveau mot de passe
</button>
</form>
</div>
</div>
);
}