Aller au contenu principal

Règles — Authentification

BR-AUTH-001 — Stratégie JWT, durée 30 jours [APP]

session: {
strategy: "jwt",
maxAge: 30 * 24 * 60 * 60, // 30 jours
}

Source : src/lib/auth.ts:41-44

Les sessions sont gérées via JWT (pas de session DB). La durée de vie est de 30 jours. Après expiration, l'utilisateur est redirigé vers /login.


BR-AUTH-002 — Deux providers coexistants : Authentik OIDC et Credentials [APP]

Source : src/lib/auth.ts:46-80

NextAuth v5 est configuré avec deux providers :

  1. Authentik OIDC (provider principal) — délègue l'authentification à Authentik auto-hébergé. Requiert les variables AUTHENTIK_CLIENT_ID, AUTHENTIK_CLIENT_SECRET, AUTHENTIK_ISSUER.
  2. Credentials (provider secondaire) — formulaire email + mot de passe bcrypt contre la table users.

La page /login présente le bouton Authentik en premier (action principale), suivi d'un séparateur et du formulaire credentials.


BR-AUTH-003 — Account linking automatique si même email [APP]

Source : src/lib/auth.ts:52

allowDangerousEmailAccountLinking: true,

Si un utilisateur se connecte via Authentik avec un email déjà présent en base (ex. compte créé via Credentials), le OAuthAccount OIDC est rattaché au User existant sans créer de doublon. L'historique financier est conservé.


BR-AUTH-004 — Guard credentials : utilisateurs OIDC sans passwordHash [APP]

Source : src/lib/auth.ts:67

// Les users OIDC sans passwordHash ne peuvent pas utiliser credentials
if (!user?.passwordHash) return null;

Un utilisateur créé uniquement via Authentik OIDC n'a pas de passwordHash en base. La fonction authorize() du provider Credentials retourne null pour ces comptes — ils doivent obligatoirement passer par le bouton Authentik. Aucun message d'erreur ne distingue ce cas d'un mot de passe incorrect (réduction de l'information exposée).


BR-AUTH-005 — Hachage bcrypt, coût 10 [APP]

Source : src/app/actions/users.ts:12

const hash = await bcrypt.hash(password, 10);

Les mots de passe sont hachés avec bcrypt au coût 10. Le hash est stocké dans User.passwordHash — jamais le mot de passe en clair.

La création d'utilisateur se fait via src/app/actions/users.ts:createUser().

Coût bcrypt

Le coût était 12 dans la configuration V1. Il est passé à 10 pour la V2. Cette valeur reste conforme aux recommandations OWASP (minimum 10).


BR-AUTH-006 — Longueur minimale du mot de passe : 8 caractères [APP]

Source : src/app/(app)/settings/page.tsx (action updatePassword)

if (data.newPassword.length < 8) {
return { success: false, error: "Le nouveau mot de passe doit faire au moins 8 caractères" };
}

Enforced côté serveur dans updatePassword(). Pas de règle de complexité (majuscule, chiffre…) en V1/V2.


BR-AUTH-007 — Vérification du mot de passe actuel avant changement [APP]

Le changement de mot de passe (updatePassword()) exige la saisie du mot de passe actuel. Il est vérifié par bcrypt avant toute mise à jour. Un utilisateur sans passwordHash (compte OIDC sans credentials) reçoit "Compte sans mot de passe".


BR-AUTH-008 — Architecture multi-utilisateurs [APP]

Source : prisma/schema.prisma:76-96

Chaque entité (compte, transaction, budget…) est liée à un userId. Plusieurs utilisateurs peuvent coexister en base. La création de nouveaux utilisateurs est protégée par un guard isAdmin — il n'existe pas de page d'inscription publique.


BR-AUTH-009 — Champ isAdmin : gate création d'utilisateurs [APP]

Source : src/app/actions/users.ts:9-10

if (!session?.user?.id) throw new Error("Non authentifié");
if (!session.user.isAdmin) throw new Error("Accès refusé — admin requis");

Le champ User.isAdmin (booléen, @default(false)) est requis pour appeler createUser(). Ce champ est propagé dans le JWT via un callback DB (voir BR-AUTH-010). Il n'existe pas d'interface d'administration dédiée en V1 — la promotion admin se fait directement en base ou via script seed.


BR-AUTH-010 — Propagation de id et isAdmin dans la session [APP]

Source : src/lib/auth.ts:83-101

callbacks: {
async jwt({ token, user }) {
if (user?.id) {
token.id = user.id;
const dbUser = await prisma.user.findUnique({
where: { id: user.id },
select: { isAdmin: true },
});
token.isAdmin = dbUser?.isAdmin ?? false;
}
return token;
},
session({ session, token }) {
if (session.user && token.id) {
session.user.id = token.id as string;
session.user.isAdmin = (token.isAdmin as boolean) ?? false;
}
return session;
},
}

isAdmin est récupéré depuis la DB au moment de la génération du JWT (premier login ou refresh) — le provider Authentik ne le connaît pas. session.user.id et session.user.isAdmin sont ainsi disponibles dans tous les server actions via auth().


BR-AUTH-011 — Pages d'erreur et de connexion personnalisées [APP]

Source : src/lib/auth.ts:104-107

pages: {
signIn: "/login",
error: "/login",
}

Toutes les erreurs NextAuth redirigent vers /login (pas de page d'erreur dédiée). L'URL peut contenir ?error=<code> pour afficher un message contextuel.


Voir aussi