# Backend — Risques & vigilance : Auth > Extrait de la base de connaissance Lead_tech. Voir `knowledge/backend/risques/README.md` pour 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.user` en erreur (ou contournements involontaires) - Découvert tard (souvent uniquement en code review ou en prod) ### Symptômes - `request.user` vaut `undefined` dans 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 lit `request.user`) - Ajouter au minimum 1 test d'intégration/e2e qui prouve que `request.user` est 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.userId` sans null-check lève une `TypeError` (500) si `request.user` est 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.user` mocké globalement, mais pas le guard isolé ### Bonnes pratiques / mitigations ```typescript 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`, `getThreads` ou équivalent accessible sans vérification d'entitlements - Endpoint write protégé par `assertForumAccess` mais 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 `assertForumAccess` ou é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 metadata `REQUIRE_ADMIN_ROLE_KEY` posée par `@RequireAdminRole()` - Si le décorateur est absent, `requiresAdmin = false/undefined` → le guard retourne `true` et 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 ```typescript // ✅ 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.findFirst` omet de filtrer `expiresAt` → 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` : ```typescript // ✅ 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 initialiser `expiresAt` à +30j par défaut. Ajouter un helper `seedExpiredSession()` 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 `describe` e2e avec `buildApp` partagé en `beforeAll` (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 un `it` intermé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 : ```typescript 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 un `buildApp` isolé avec `app.close()` en fin de test - Contexte technique : NestJS / Jest e2e — app-alexandrie 24-03-2026