Triage du 95_a_capitaliser.md (~75 propositions) : - 60 entrées intégrées dans knowledge/ (backend, frontend, workflow) - 4 nouveaux fichiers : backend/patterns/tests.md, backend/risques/tests.md, frontend/patterns/general.md, workflow/patterns/general.md - 6 doublons rejetés - Mise à jour des READMEs index pour refléter les nouvelles entrées - 95_a_capitaliser.md restauré à sa structure initiale - 40_decisions_et_archi.md : décision mono-tenant déployable vs SaaS multi-tenant - 90_debug_et_postmortem.md : sub-agents Write indisponible, effet iceberg CI, prisma migrate diffs cosmétiques Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
19 KiB
Backend — Risques & vigilance : Auth
Extrait de la base de connaissance Lead_tech. Voir
knowledge/backend/risques/README.mdpour l'index complet.
AuthN/AuthZ dispersée (contrôles d'accès au fil de l'eau)
Risques
- Règles de permissions incohérentes selon endpoints
- Failles "oubliées" sur un endpoint secondaire
- Audit impossible
Symptômes
- Utilisateurs qui accèdent à des ressources non prévues
- Correctifs en urgence "on ajoute un if ici"
- Bugs qui réapparaissent après refactor
Bonnes pratiques / mitigations
- Centraliser authn/authz (middleware/policies)
- Tests sur règles critiques
- Logs/audit des décisions d'accès
Guard global manquant (request.user jamais peuplé)
Risques
- Chaîne auth bâtie sur une fondation inopérante (tout "a l'air OK" en dev/tests, mais casse en prod)
- Guards aval qui dépendent de
request.useren erreur (ou contournements involontaires) - Découvert tard (souvent uniquement en code review ou en prod)
Symptômes
request.uservautundefineddans un guard supposé "après auth"- Endpoints qui passent alors qu'ils devraient être refusés (si les guards aval se désactivent/retournent true par défaut)
- Tests "verts" car trop mockés (pas de test e2e qui valide le pipeline complet)
Bonnes pratiques / mitigations
- Poser explicitement le guard global dès les foundations (au moins
AuthGuard) - Vérifier l'ordre des
APP_GUARD(AuthGuard avant tout guard qui litrequest.user) - Ajouter au minimum 1 test d'intégration/e2e qui prouve que
request.userest bien peuplé sur un endpoint protégé
Guard NestJS route-level — null-check manquant sur request.user
Risques
- Un guard route-level qui lit
request.user.userIdsans null-check lève uneTypeError(500) sirequest.userest absent - Mauvaise registration de module, test d'intégration mal configuré, ou middleware custom peuvent produire cet état
Symptômes
TypeError: Cannot read properties of undefined (reading 'userId')en prod- Tests "verts" car
request.usermocké globalement, mais pas le guard isolé
Bonnes pratiques / mitigations
const user = (request as any).user as { userId: string } | undefined;
if (!user?.userId) {
throw new UnauthorizedException({ error: { code: 'UNAUTHENTICATED', message: '...' } });
}
- Règle : les guards route-level ne font pas confiance aux guards globaux pour leurs invariants — ils se défendent eux-mêmes.
- Contexte technique : NestJS v10+ — 09-03-2026
Suppression du cookie après révocation DB sur logout
Risques
- Si la révocation DB échoue avant la suppression du cookie, l'utilisateur garde un cookie local devenu incohérent
- L'utilisateur peut rester bloqué dans un état où il ne peut plus se déconnecter proprement
- Le comportement diffère selon la disponibilité de la base
Symptômes
- Logout qui échoue par intermittence quand la DB est instable
- Cookie de session toujours présent côté navigateur après erreur serveur
- Réessais de logout qui produisent des états difficiles à diagnostiquer
Bonnes pratiques / mitigations
- Toujours supprimer le cookie en premier, même si la révocation DB échoue ensuite
- Traiter la suppression côté DB en best-effort ou avec gestion d'idempotence adaptée
- Vérifier en test qu'un échec DB ne laisse pas l'accès browser actif
- Contexte technique : Next.js / auth par cookie / session persistée — 16-03-2026
Endpoints GET sans contrôle d'accès sur ressource protégée
Risques
- Un endpoint de lecture expose des données premium/protégées à tout utilisateur authentifié
- La règle "seuls les writes vérifient les droits" est un anti-pattern qui cause des fuites silencieuses
Symptômes
getCategories,getThreadsou équivalent accessible sans vérification d'entitlements- Endpoint write protégé par
assertForumAccessmais GET correspondant non protégé
Bonnes pratiques / mitigations
-
Tout endpoint retournant des données liées à une ressource protégée (forum pack, contenu premium) doit appeler
assertForumAccessou équivalent, même pour les GET -
Checklist review : pour chaque nouveau GET, vérifier qu'il passe par le guard/helper d'accès si la ressource appartient à un scope protégé
-
Contexte technique : NestJS / app-alexandrie — 23-03-2026
NestJS @UseGuards(AdminRoleGuard) sans @RequireAdminRole() — silencieusement ouvert
Risques
AdminRoleGuard.canActivate()lit la metadataREQUIRE_ADMIN_ROLE_KEYposée par@RequireAdminRole()- Si le décorateur est absent,
requiresAdmin = false/undefined→ le guard retournetrueet laisse passer sans vérification
Symptômes
- Endpoint admin accessible à tout utilisateur authentifié
- Zéro erreur de compilation ou de démarrage — le bug est silencieux
Bonnes pratiques / mitigations
// ✅ Correct — les deux décorateurs ensemble
@Post('admin/ressource')
@UseGuards(AdminRoleGuard)
@RequireAdminRole()
async createRessource(...) {}
// ❌ Silencieusement non protégé — @RequireAdminRole() manquant
@Post('admin/ressource')
@UseGuards(AdminRoleGuard)
async createRessource(...) {}
-
Règle : s'applique à tout guard NestJS qui délègue la décision à une metadata de décorateur
-
Checklist review : vérifier systématiquement les endpoints admin que
@RequireAdminRole()est présent -
Contexte technique : NestJS / guards metadata — app-alexandrie 23-03-2026
Mock Prisma session sans filtre expiresAt — divergence test/prod
Risques
- Le mock
session.findFirstomet de filtrerexpiresAt→ des sessions expirées passent en test alors qu'elles seraient rejetées en prod - Masque des régressions sur la logique d'expiration de session
Symptômes
- Tests e2e verts avec un token de session expiré
- Bug découvert uniquement en prod quand la TTL est dépassée
Bonnes pratiques / mitigations
Le mock doit répliquer tous les critères de getUserByToken() en prod : revokedAt === null ET expiresAt > now :
// ✅ Mock complet fidèle à la prod
findFirst: jest.fn().mockImplementation(({ where }) => {
const session = store[where.accessToken];
if (!session) return null;
if (where.revokedAt === null && session.revokedAt !== null) return null;
if (where.expiresAt?.gt && session.expiresAt <= where.expiresAt.gt) return null;
return session;
})
-
Règle :
seedSession()doit initialiserexpiresAtà +30j par défaut. Ajouter un helperseedExpiredSession()si des tests de session expirée sont nécessaires. -
Contexte technique : NestJS / Prisma mock / e2e — app-alexandrie 24-03-2026
Tests e2e autorisation : scénarios non-abonné avec buildApp partagé
Risques
- Un
describee2e avecbuildApppartagé enbeforeAll(entitlements actifs) rend impossible le test de scénarios non-abonné sans pollution entre tests - Tenter de surcharger le mock partagé (
jest.fn().mockResolvedValueOnce(...)) dans unitintermédiaire est fragile et crée des effets de bord
Symptômes
- Scénario "non-abonné → 403" n'est jamais testé, ou pollue les autres tests si le mock est modifié en cours de describe
Bonnes pratiques / mitigations
Créer une instance buildApp isolée pour les scénarios d'autorisation alternatifs :
it('retourne 403 si subscription inactive', async () => {
const isolatedApp = await buildApp({
getEntitlementsForUser: jest.fn().mockResolvedValue({
subscription: { isActive: false, plan: 'free' }
})
});
// ... tests
await isolatedApp.close();
});
-
Règle : ne jamais tenter de surcharger un mock partagé dans un
it— créer unbuildAppisolé avecapp.close()en fin de test -
Contexte technique : NestJS / Jest e2e — app-alexandrie 24-03-2026
Champ métier absent du JWT — découplage silencieux frontend/backend
Risques
- Le frontend lit un champ dans
decodeJwtPayload(token)qui n'est jamais émis par le service d'authentification - Le comportement est silencieux —
undefinedest traité comme'', aucune erreur visible, l'UI se dégrade sans signal d'alerte
Symptômes
decodeJwtPayload(token).fieldretourneundefinedpour tous les utilisateurs réels- Filtres de grade/rôle côté UI entièrement inopérants (rank=0, aucune tab affichée)
Bonnes pratiques / mitigations
-
Toute donnée lue via
decodeJwtPayloadcôté frontend doit être explicitement émise dans le payload JWT côté backend -
Lors de l'ajout d'un champ à
JwtPayload(type TypeScript), vérifier immédiatement que le service d'authentification inclut ce champ à l'émission -
Ajouter un test d'intégration login → decode qui vérifie la présence des champs critiques dans le token retourné
-
Signal review : un champ apparaît dans le type
JwtPayloadcôté frontend sans modification correspondante dansauthService.ts -
Contexte technique : JWT / auth — RL799_V2 02-04-2026
Confusion email login / email de contact dans un endpoint annuaire
Risques
- Le mapping de l'endpoint annuaire utilise
email: user.email(email de login, toujours présent) alors que l'intention est d'exposer un email de contact optionnel - Même un utilisateur à bas privilège peut récupérer les emails de login de tous les membres
Symptômes
email: user.emaildans le mapping d'un endpoint de type "annuaire" ou "liste membres"- Emails de connexion exposés à tous les utilisateurs authentifiés
Bonnes pratiques / mitigations
-
Dans tout endpoint annuaire ou profil public, distinguer explicitement :
- email de login : identifiant de compte, JAMAIS exposé à un tiers dans un endpoint annuaire
- email de contact : champ optionnel dans le profil ou la table directory, exposé uniquement s'il est renseigné
-
Si le modèle ne dispose pas encore d'un champ email de contact distinct, renvoyer
undefinedpour le champ email dans l'annuaire -
Signal review :
email: user.emaildans le mapping d'un endpoint annuaire -
Contexte technique : auth / annuaire — RL799_V2 02-04-2026
TOCTOU sur rotation de refresh token
Risques
- Un pattern
findUnique+updateséparés sur la rotation de refresh token crée une fenêtre TOCTOU - Deux requêtes concurrentes avec le même refresh token passent toutes les deux la vérification avant que l'une ne révoque → deux sessions valides émises, le vol de token passe inaperçu
Symptômes
- Deux sessions actives issues du même refresh token
- Détection de vol impossible car les deux tokens sont valides
Bonnes pratiques / mitigations
-
Toujours utiliser un
updateManyatomique avec conditionWHERE revokedAt IS NULL AND expiresAt > NOW()et vérifiercount === 1 -
Si
count === 0, le token a déjà été utilisé → révoquer tous les tokens du user (token family detection, RFC 6819) -
Signal review :
findUniquesuivi deupdateséparés dans un flux de rotation de refresh token -
Contexte technique : auth / refresh token — RL799_V2 08-04-2026
Drift d'authentification par copier-coller de pattern auth
Risques
- Quand un helper d'auth centralisé existe (
requireRoleAccess), mais que de nouveaux services réimplémentent le même pattern manuellement (extractAccessToken+verifyToken+ vérification locale), chaque service développe ses propres variantes (codes d'erreur différents, 401 vs 403, requestId ou non) - La surface d'auth devient incohérente et indéfendable en audit
Symptômes
- Un audit RBAC révèle qu'une part significative des routes ont un pattern d'auth "fait maison" au lieu du helper standard
- Codes d'erreur divergents entre services pour la même situation (token absent)
Bonnes pratiques / mitigations
-
Tout nouveau handler HTTP DOIT utiliser le helper centralisé pour l'authentification et l'autorisation
-
Ne JAMAIS importer
extractAccessToken+verifyTokendirectement dans un service métier -
Si le helper ne couvre pas un besoin (ex: besoin de
userIden plus deemail), étendre le helper plutôt que contourner -
Signal review : import de
verifyTokendans un fichier service (horsauthHelpers.ts) -
Contexte technique : auth / architecture — RL799_V2 08-04-2026
ACL unique pour ressource globale et sous-champ sensible
Risques
- Champs sensibles exposés à des rôles qui ne devraient accéder qu'à la vue agrégée.
Symptômes
- Endpoint fonctionnellement "autorisé" mais fuite de notes/valeurs sensibles en clair.
Bonnes pratiques / mitigations
-
Séparer explicitement les règles d'accès : liste globale vs détails sensibles.
-
Appliquer des guards dédiés au niveau du champ ou du sous-endpoint.
-
Contexte technique : auth / ACL granulaire — RL799_V2 13-04-2026
JWT valide mais utilisateur introuvable en base
Risques
- Retour
403trompeur (authz) au lieu d'un401(auth invalide côté sujet).
Symptômes
- Frontend affiche "accès refusé" au lieu de forcer une ré-authentification.
Bonnes pratiques / mitigations
-
Si le sujet JWT ne résout plus un user actif : répondre
401. -
Déclencher invalidation de session côté client.
-
Contexte technique : auth / cycle de vie compte — RL799_V2 17-04-2026
Helpers "X actif" qui dérivent silencieusement
Risques
- Plusieurs helpers répondent à la même question — "l'entité X est-elle active / opérante ?" — avec des filtres légèrement différents
- Un user passe la guard A mais pas la guard B sur la même ressource (ou inversement). Bugs silencieux, pas d'erreur, juste une asymétrie de comportement
Symptômes
- Délégation
secretaireDeSeance"active" filtrée surstatus: 'published', closedAt: null, cancelledAt: nulldans un helper, justecancelledAt: nulldans l'autre - Un ex-délégué d'une soirée clôturée garde l'autorité cross-soirée indéfiniment
Bonnes pratiques / mitigations
- Un seul helper canonique par notion d'activité (ex :
isDelegationActive,isSoireeOpenForRappel). Les autres l'appellent - Si la centralisation n'est pas faisable immédiatement (ex : helper appelé en N+1 query, perf), au moins un test qui compare leur output sur des fixtures partagées et casse à la moindre divergence
- Au minimum : un commentaire en tête du helper "secondaire" qui pointe vers le canonique et liste explicitement les filtres à maintenir synchronisés
- Contexte technique : auth / RBAC — RL799_V2 27-04-2026
Guard d'autorisation qui charge des objets riches
Risques
- Une guard d'autorisation s'exécute à CHAQUE requête sur une route protégée
- Si la guard a besoin de "trouver une candidate" (ex : "cette tenue est-elle dans les 'dernières rappelables' du grade pour une de mes délégations ?"), le repo helper utilisé doit avoir un select minimal, PAS le select complet utilisé par les services métier
- Pour un user avec N délégations actives, on charge N agrégats volumineux à chaque requête
Symptômes
- Même fonction repo appelée par (1) un service qui a besoin de toutes les relations (rendu UI) et (2) une guard qui n'a besoin que de l'id
- La guard paie le coût du fetch riche inutilement
- Latence guard qui croît avec le nombre de relations chargées
Bonnes pratiques / mitigations
Exposer deux variantes du repo helper :
findX(...)— select riche, utilisé par les services métierfindXIdOnly(...)— select{ id: true }, utilisé par les guards
// Guard
export const requireXAccess = async (request, id, { roleSet }) => {
// utilise findXIdOnly (select minimal) — pas findX
const candidate = await repo.findXIdOnly({ ... });
if (!candidate || candidate.id !== id) return forbidden();
};
// Service métier
export const getXFullDetails = async (id) => {
return repo.findX({ ... }); // include riche
};
Coût : duplication de la clause where (acceptable, factorisable en constante). Bénéfice : la guard reste O(1) en payload même quand les relations grossissent.
- Contexte technique : auth / performance — RL799_V2 27-04-2026
Suppression d'un flag auth global (DB + DTO + tests) — cleanup atomique obligatoire
Risques
- Un flag profondément câblé dans Prisma (ex :
mustChangePassword,isVerified) ne peut pas être supprimé incrémentalement : chaque cleanup partiel produit un état non-compilable - Les fixtures de tests qui posent
mustChangePassword: falsecassent à la compilation TS au moment du drop — bloque tout commit séparé - Les helpers
helpers/db.tset les DTO partagés (packages/shared) sont prioritaires, sinon les imports cross-package échouent en cascade
Symptômes
Property 'mustChangePassword' does not exist on type 'User'après un drop partiel- Tentative de découpage en sous-lots qui échoue au typecheck
Bonnes pratiques / mitigations
Quand on prévoit de supprimer un flag auth profondément câblé :
- Le cleanup ne peut pas être incrémental — soit on supprime tout dans un chantier, soit on garde le flag avec un nullable de transition
- Les fixtures de tests doivent être nettoyées dans le même PR — grep systématique avant de démarrer (
grep -rn "mustChangePassword" apps/) pour estimer l'ampleur - Les helpers
helpers/db.tssont prioritaires — un seul fichier touché casse tous les tests qui l'importent - Les DTO partagés (
packages/shared) doivent être alignés en premier - Considérer un sous-lot dédié au cleanup si le flag est transverse — éviter de l'inclure dans un sous-lot fonctionnel
Anti-pattern : déprécier en douceur en gardant le flag avec un commentaire // @deprecated sans supprimer les usages. Le code mort s'accumule, les futurs devs hésitent à le nettoyer ("pourquoi c'est encore là ?"), la dépréciation ne se finit jamais.
- Contexte technique : auth / refactor schema — RL799_V2 28-04-2026