mirror of
https://github.com/R0m1k3/PleinR.git
synced 2026-10-21 22:28:04 +02:00
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
59 lines
1.9 KiB
TypeScript
59 lines
1.9 KiB
TypeScript
import NextAuth from "next-auth";
|
|
import Credentials from "next-auth/providers/credentials";
|
|
import bcrypt from "bcryptjs";
|
|
import { eq } from "drizzle-orm";
|
|
import { db } from "@/db";
|
|
import { users } from "@/db/schema";
|
|
import { authConfig } from "@/auth.config";
|
|
import { clearLoginFailures, isLoginBlocked, recordLoginFailure } from "@/lib/login-throttle";
|
|
|
|
// Empreinte bcrypt d'une valeur quelconque : sert de comparaison factice quand
|
|
// l'adresse n'existe pas, pour que le temps de réponse reste identique.
|
|
const ABSENT_USER_HASH = "$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy";
|
|
|
|
export const { handlers, auth, signIn, signOut } = NextAuth({
|
|
...authConfig,
|
|
providers: [
|
|
Credentials({
|
|
credentials: {
|
|
email: { label: "E-mail", type: "email" },
|
|
password: { label: "Mot de passe", type: "password" },
|
|
},
|
|
async authorize(credentials) {
|
|
const email = String(credentials?.email ?? "")
|
|
.toLowerCase()
|
|
.trim();
|
|
const password = String(credentials?.password ?? "");
|
|
if (!email || !password) return null;
|
|
|
|
if (isLoginBlocked(email)) return null;
|
|
|
|
const rows = await db.select().from(users).where(eq(users.email, email));
|
|
const user = rows[0];
|
|
|
|
// Comparaison menée même sans compte correspondant : sans cela, le
|
|
// temps de réponse révélerait quelles adresses existent.
|
|
const hash = user?.passwordHash ?? ABSENT_USER_HASH;
|
|
const valid = await bcrypt.compare(password, hash);
|
|
|
|
if (!user || !valid) {
|
|
recordLoginFailure(email);
|
|
return null;
|
|
}
|
|
|
|
clearLoginFailures(email);
|
|
|
|
return {
|
|
id: String(user.id),
|
|
name: user.name,
|
|
email: user.email,
|
|
role: user.role,
|
|
memberId: user.memberId,
|
|
mustChangePassword: user.mustChangePassword,
|
|
sessionVersion: user.sessionVersion ?? 0,
|
|
};
|
|
},
|
|
}),
|
|
],
|
|
});
|