mirror of
https://github.com/MaksTinyWorkshop/_Assistant_Lead_Tech
synced 2026-04-27 14:58:16 +02:00
capitalisation: intégration 28 entrées knowledge + 2 CLAUDE.md RL799_V2 (triage branche mcp_v1)
28 nouvelles sections intégrées dans 12 fichiers knowledge (backend risques/patterns, frontend risques/patterns, workflow risques). Couvre rate limiting, RGPD, CSP Next.js, refresh token TOCTOU, catch-all Prisma, distinction 401/403, tests E2E Playwright, etc. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -239,3 +239,60 @@ try {
|
||||
- [ ] `P2025` absorbé silencieusement sur les suppressions de session
|
||||
- [ ] Effets de bord hors transaction documentés (cookie, email)
|
||||
- [ ] Tests couvrant le cas d'une session déjà expirée
|
||||
|
||||
---
|
||||
|
||||
<a id="pattern-scope-minimal-cookie-refresh"></a>
|
||||
## Pattern : Scope minimal du cookie refresh token
|
||||
|
||||
- Objectif : limiter l'exposition du cookie refresh token au strict minimum.
|
||||
- Contexte : migration d'un stockage localStorage vers cookies httpOnly pour les tokens d'authentification.
|
||||
- Quand l'utiliser : dès qu'un refresh token est transmis via cookie.
|
||||
- Quand l'éviter : jamais.
|
||||
- Avantage :
|
||||
- réduit la surface d'attaque — le cookie ne voyage qu'avec les requêtes de refresh
|
||||
- évite l'envoi inutile du refresh token sur les endpoints auth (login, password, invitations)
|
||||
- Limites / vigilance :
|
||||
- vérifier que le path est compatible avec le routing réel de l'endpoint de refresh
|
||||
- Validé le : 08-04-2026
|
||||
- Contexte technique : auth / cookies httpOnly — RL799_V2
|
||||
|
||||
### Règle
|
||||
|
||||
- Le cookie `refresh_token` doit avoir `Path=/api/auth/refresh` (pas `/api/auth`). Seul l'endpoint de refresh a besoin de recevoir ce cookie.
|
||||
- Plus le path est large, plus la surface d'attaque est grande (le cookie voyage avec chaque requête matchant le path).
|
||||
|
||||
### Checklist
|
||||
|
||||
- [ ] `Path=/api/auth/refresh` sur le cookie refresh token
|
||||
- [ ] `Path=/` uniquement pour l'access token (nécessaire sur toutes les routes API)
|
||||
- [ ] Vérifier que l'endpoint de refresh reçoit bien le cookie après changement de path
|
||||
|
||||
---
|
||||
|
||||
<a id="pattern-distinction-401-403"></a>
|
||||
## Pattern : Distinction stricte 401 vs 403
|
||||
|
||||
- Objectif : permettre au frontend de réagir correctement aux erreurs d'authentification et d'autorisation.
|
||||
- Contexte : helper centralisé `requireRoleAccess` ou équivalent.
|
||||
- Quand l'utiliser : systématiquement sur tout helper d'authentification/autorisation.
|
||||
- Quand l'éviter : jamais.
|
||||
- Avantage :
|
||||
- le frontend peut distinguer "session expirée → redirection login" de "permission manquante → message accès refusé"
|
||||
- debug plus rapide en production
|
||||
- Limites / vigilance :
|
||||
- la distinction doit être cohérente sur tous les endpoints — ne pas retourner 403 pour un token absent sur certains services
|
||||
- Validé le : 08-04-2026
|
||||
- Contexte technique : auth / RBAC — RL799_V2
|
||||
|
||||
### Règle
|
||||
|
||||
Le helper `requireRoleAccess` doit retourner :
|
||||
- **401 UNAUTHORIZED** : token absent, expiré, malformé, ou email manquant dans le payload
|
||||
- **403 FORBIDDEN** : token valide mais rôle non autorisé pour la ressource
|
||||
|
||||
### Checklist
|
||||
|
||||
- [ ] 401 pour tout problème de token (absent, expiré, malformé)
|
||||
- [ ] 403 uniquement quand le token est valide mais le rôle insuffisant
|
||||
- [ ] Frontend redirige vers `/login` sur 401, affiche "accès refusé" sur 403
|
||||
|
||||
@@ -167,3 +167,23 @@ Quand un repository utilise le pattern `{ ok: true; data } | { ok: false }` pour
|
||||
### Signal review
|
||||
|
||||
- `catch { return null }` dans un repository qui utilise `{ ok: false }` ailleurs
|
||||
|
||||
---
|
||||
|
||||
<a id="pattern-api-crypto-variantes-binaire-base64"></a>
|
||||
## Pattern : API crypto — variantes binaire et base64
|
||||
|
||||
- Objectif : éviter un round-trip base64 coûteux en mémoire quand les données sont déjà en binaire.
|
||||
- Contexte : fonction crypto qui travaille en base64 pour la sérialisation (stockage DB, transport JSON) mais appelée aussi depuis des lectures fichier (binaire natif).
|
||||
- Quand l'utiliser : dès qu'une fonction crypto accepte uniquement du base64 mais est appelée avec des données binaires.
|
||||
- Quand l'éviter : si toutes les sources de données sont déjà en base64.
|
||||
- Validé le : 08-04-2026
|
||||
- Contexte technique : Node.js crypto / fichiers — RL799_V2 story 13-7
|
||||
|
||||
### Règle
|
||||
|
||||
Quand une fonction crypto travaille en base64 pour la sérialisation, prévoir une variante `*Buffer` qui accepte un `Buffer` natif pour les cas où les données sont déjà en binaire (lecture fichier, stream). Cela évite un double encodage (fichier → base64 → Buffer → déchiffrement).
|
||||
|
||||
### Signal review
|
||||
|
||||
- `buffer.toString('base64')` suivi immédiatement de `decrypt(base64String)` qui fait `Buffer.from(str, 'base64')` → round-trip inutile
|
||||
|
||||
@@ -33,3 +33,29 @@ source_projects: [RL799_V2]
|
||||
### Signal review
|
||||
|
||||
- Tout service qui importe `verifyToken` directement ET fait son propre check RBAC est suspect de duplication.
|
||||
|
||||
---
|
||||
|
||||
<a id="pattern-extension-service-par-type-rbac"></a>
|
||||
## Pattern : Extension d'un service existant par type avec RBAC granulaire
|
||||
|
||||
- Objectif : éviter la duplication de service quand un nouveau besoin est fonctionnellement identique à un service existant mais avec des restrictions d'accès différentes.
|
||||
- Contexte : ajout d'un nouveau type de communication (ex: `vm`) avec restrictions RBAC spécifiques dans un service communications existant.
|
||||
- Quand l'utiliser : quand le nouveau besoin utilise le même modèle de données et le même CRUD que le service existant, seules les restrictions d'accès changent.
|
||||
- Quand l'éviter : quand le modèle de données diverge (champs spécifiques au nouveau type) — dans ce cas, un service dédié est préférable.
|
||||
- Validé le : 08-04-2026
|
||||
- Contexte technique : backend / architecture — RL799_V2 story 18-7
|
||||
|
||||
### Règle
|
||||
|
||||
Préférer étendre le service avec un nouveau type (enum/Set) et ajuster les restrictions RBAC par type, plutôt que dupliquer le service.
|
||||
|
||||
### Avantages
|
||||
|
||||
- Un seul repository, un seul endpoint, même format de réponse
|
||||
- Tests existants couvrent la non-régression
|
||||
- Moins de code à maintenir
|
||||
|
||||
### Signal review
|
||||
|
||||
- Nouveau service qui réplique le CRUD d'un service existant avec un filtre additionnel → candidat à la fusion par type
|
||||
|
||||
@@ -283,3 +283,50 @@ it('retourne 403 si subscription inactive', async () => {
|
||||
- **Signal review** : `email: user.email` dans le mapping d'un endpoint annuaire
|
||||
|
||||
- Contexte technique : auth / annuaire — RL799_V2 02-04-2026
|
||||
|
||||
---
|
||||
|
||||
<a id="risque-toctou-rotation-refresh-token"></a>
|
||||
## TOCTOU sur rotation de refresh token
|
||||
|
||||
### Risques
|
||||
|
||||
- Un pattern `findUnique` + `update` sé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 `updateMany` atomique avec condition `WHERE revokedAt IS NULL AND expiresAt > NOW()` et vérifier `count === 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** : `findUnique` suivi de `update` séparés dans un flux de rotation de refresh token
|
||||
|
||||
- Contexte technique : auth / refresh token — RL799_V2 08-04-2026
|
||||
|
||||
---
|
||||
|
||||
<a id="risque-drift-auth-copier-coller"></a>
|
||||
## 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` + `verifyToken` directement dans un service métier
|
||||
- Si le helper ne couvre pas un besoin (ex: besoin de `userId` en plus de `email`), étendre le helper plutôt que contourner
|
||||
- **Signal review** : import de `verifyToken` dans un fichier service (hors `authHelpers.ts`)
|
||||
|
||||
- Contexte technique : auth / architecture — RL799_V2 08-04-2026
|
||||
|
||||
@@ -438,3 +438,313 @@ try {
|
||||
- Permet la surcharge en test et le rechargement dynamique
|
||||
|
||||
- Contexte technique : Node.js / tests — RL799_V2 07-04-2026
|
||||
|
||||
---
|
||||
|
||||
<a id="risque-rate-limiting-couverture-test"></a>
|
||||
## Rate limiting — couverture de test insuffisante
|
||||
|
||||
### Risques
|
||||
|
||||
- Tester uniquement l'objet `RateLimiter` en isolation (`.check()`, `.reset()`) donne une fausse confiance. Les bugs de câblage (limiter non appelé, mauvais limiter sur le mauvais endpoint, format de réponse 429 incorrect) passent au travers.
|
||||
|
||||
### Symptômes
|
||||
|
||||
- Tests unitaires verts mais réponse 429 absente ou mal formatée en intégration
|
||||
- Header `retry-after` manquant dans la réponse HTTP
|
||||
|
||||
### Bonnes pratiques / mitigations
|
||||
|
||||
- Pour chaque rate limiter intégré dans un endpoint, ajouter au minimum :
|
||||
1. Un test d'intégration qui dépasse le seuil via le handler HTTP et vérifie status 429 + body + header `retry-after`
|
||||
2. Un test d'expiration de fenêtre (mock `Date.now` et avancer le temps)
|
||||
3. Vérifier que le logging sécurité est déclenché (au minimum visible dans la sortie test)
|
||||
|
||||
- Contexte technique : backend / rate limiting — RL799_V2 07-04-2026
|
||||
|
||||
---
|
||||
|
||||
<a id="risque-nommage-metriques-agregees-dto"></a>
|
||||
## Nommage des métriques agrégées dans les DTO
|
||||
|
||||
### Risques
|
||||
|
||||
- Nommer un champ `xxxCount` ou `xxxThisYear` sans que le nom reflète exactement ce qui est compté
|
||||
- Confusion en maintenance, bugs d'affichage, labels UI incohérents
|
||||
|
||||
### Symptômes
|
||||
|
||||
- `tenuesThisYear` qui compte en réalité les *présences* du membre, pas le nombre total de tenues
|
||||
- Labels frontend incohérents avec le comportement réel du champ
|
||||
|
||||
### Bonnes pratiques / mitigations
|
||||
|
||||
- Le nom du champ DTO doit refléter exactement l'entité comptée ET le scope du comptage
|
||||
- Préférer `presencesThisYear` à `tenuesThisYear` si on compte les attendances d'un user, réserver `tenuesThisYear` pour le COUNT de tenues elles-mêmes
|
||||
- **Signal review** : tout champ `xxxCount` ou `xxxThisYear` dont le nom ne correspond pas à la requête sous-jacente
|
||||
|
||||
- Contexte technique : backend / DTO — RL799_V2 07-04-2026
|
||||
|
||||
---
|
||||
|
||||
<a id="risque-gitignore-env-wildcard"></a>
|
||||
## `.gitignore` — `.env*` wildcard capture `.env.example`
|
||||
|
||||
### Risques
|
||||
|
||||
- Utiliser `.env*` dans `.gitignore` sans exception exclut aussi `.env.example`, qui est le fichier standard de documentation des variables d'environnement et qui DOIT être versionné
|
||||
|
||||
### Symptômes
|
||||
|
||||
- Le fichier `.env.example` existe sur le disque mais n'apparaît pas dans `git status`
|
||||
- Les développeurs ne savent pas quelles variables configurer
|
||||
|
||||
### Bonnes pratiques / mitigations
|
||||
|
||||
- Toujours ajouter `!.env.example` après le wildcard `.env*` dans chaque `.gitignore` (racine ET sous-projets du monorepo)
|
||||
- Vérifier avec `git check-ignore <path>` que l'exception fonctionne
|
||||
- **Signal review** : `.env*` dans `.gitignore` sans `!.env.example`
|
||||
|
||||
- Contexte technique : git / configuration — RL799_V2 08-04-2026
|
||||
|
||||
---
|
||||
|
||||
<a id="risque-strip-html-regex-single-pass"></a>
|
||||
## Strip HTML regex — single-pass insuffisant
|
||||
|
||||
### Risques
|
||||
|
||||
- `input.replace(/<[^>]*>/g, '')` en un seul passage laisse des fragments exploitables sur des inputs malformés type `<scr<script>ipt>alert(1)</script>`
|
||||
|
||||
### Symptômes
|
||||
|
||||
- Après strip, le résultat contient encore des fragments `>` ou des reconstitutions partielles de tags
|
||||
|
||||
### Bonnes pratiques / mitigations
|
||||
|
||||
- Toujours (1) boucler le strip jusqu'à stabilisation (`while (prev !== result)`) ET (2) supprimer les chevrons orphelins `<>` après la boucle
|
||||
- Pour du plain text (pas de rich text), c'est suffisant. Pour du rich text, utiliser une lib dédiée (DOMPurify, sanitize-html)
|
||||
- **Signal review** : `replace(/<[^>]*>/g, '')` sans boucle dans un code qui traite de l'input utilisateur
|
||||
|
||||
- Contexte technique : sécurité / sanitisation — RL799_V2 08-04-2026
|
||||
|
||||
---
|
||||
|
||||
<a id="risque-incoherence-source-verite-filtrage-affichage"></a>
|
||||
## Incohérence source de vérité — filtrage vs affichage sur des tables différentes
|
||||
|
||||
### Risques
|
||||
|
||||
- Filtrer des entités sur un champ d'une table relationnelle (`profile.grade`) tout en affichant le résultat depuis une autre table (`directory.grade`). Si les deux tables ne sont pas synchronisées en permanence, les résultats de filtrage et d'affichage divergent silencieusement.
|
||||
|
||||
### Symptômes
|
||||
|
||||
- Un membre apparaît éligible mais avec un grade affiché incohérent, ou inversement un membre éligible est invisible parce que seule la table d'affichage a été mise à jour
|
||||
|
||||
### Bonnes pratiques / mitigations
|
||||
|
||||
- Toujours utiliser la même table comme source de vérité pour le filtrage ET l'affichage d'un même attribut
|
||||
- Si deux tables portent la même information, choisir celle qui fait autorité et aligner le code dessus
|
||||
- **Signal review** : `where` sur `tableA.field` avec `select` sur `tableB.field` pour le même attribut
|
||||
|
||||
- Contexte technique : Prisma / relations — RL799_V2 08-04-2026
|
||||
|
||||
---
|
||||
|
||||
<a id="risque-check-then-create-p2002"></a>
|
||||
## Check-then-create non atomique — catcher P2002
|
||||
|
||||
### Risques
|
||||
|
||||
- Vérifier l'existence d'un enregistrement (`findFirst/findUnique`) puis créer dans un second appel est une race condition classique
|
||||
- Deux requêtes concurrentes passent le check, l'une échoue sur la contrainte unique
|
||||
|
||||
### Symptômes
|
||||
|
||||
- Erreur 500 générique au lieu d'un conflit 409, intermittent et difficile à reproduire
|
||||
|
||||
### Bonnes pratiques / mitigations
|
||||
|
||||
- Toujours catcher l'erreur Prisma `P2002` (unique constraint violation) dans le bloc catch de la création et la transformer en réponse métier explicite (`USER_ALREADY_EXISTS`, `CONFLICT`, etc.)
|
||||
- Le check préalable reste utile pour le cas nominal mais ne doit pas être la seule protection
|
||||
- **Règle** : tout `prisma.create` sur une entité à contrainte unique doit avoir un catch `P2002`
|
||||
|
||||
- Contexte technique : Prisma / concurrence — RL799_V2 08-04-2026
|
||||
|
||||
---
|
||||
|
||||
<a id="risque-double-update-non-transactionnel"></a>
|
||||
## Double update non transactionnel sur la même entité
|
||||
|
||||
### Risques
|
||||
|
||||
- Enchaîner deux `prisma.update` séparés sur la même entité (ex: update status puis update metadata) sans transaction laisse l'entité dans un état partiel si le second échoue
|
||||
|
||||
### Symptômes
|
||||
|
||||
- L'entité est dans un état qui ne correspond à aucune transition définie
|
||||
- Les flux aval (workflows, UI) se retrouvent bloqués
|
||||
|
||||
### Bonnes pratiques / mitigations
|
||||
|
||||
- Quand plusieurs champs d'une même entité doivent être modifiés ensemble pour représenter une transition d'état cohérente, les regrouper dans un seul appel `prisma.update` ou les envelopper dans une `$transaction`
|
||||
- **Signal review** : deux `prisma.update` séquentiels sur le même `where: { id }` sans `$transaction`
|
||||
|
||||
- Contexte technique : Prisma / atomicité — RL799_V2 08-04-2026
|
||||
|
||||
---
|
||||
|
||||
<a id="risque-fallback-legacy-bypass-workflow"></a>
|
||||
## Fallback "legacy" qui bypass un nouveau workflow
|
||||
|
||||
### Risques
|
||||
|
||||
- Laisser un fallback de compatibilité dans une transition d'état (ex: accepter `'draft'` en plus de `'pending_vm_approval'` comme état source de la publication) crée un chemin de contournement du nouveau workflow
|
||||
- Un appel direct à la fonction interne bypass le contrôle métier
|
||||
|
||||
### Symptômes
|
||||
|
||||
- Transition d'état possible depuis un état qui ne devrait plus être accepté
|
||||
- `{ in: ['ancien', 'nouveau'] }` dans un filtre Prisma sur une transition d'état
|
||||
|
||||
### Bonnes pratiques / mitigations
|
||||
|
||||
- Quand un nouveau workflow remplace un flux direct, retirer le fallback vers l'ancien état dans la transition atomique
|
||||
- Si la rétro-compatibilité est nécessaire, l'encapsuler dans un flag explicite ou une route dédiée, pas dans un `{ in: ['ancien', 'nouveau'] }` silencieux
|
||||
|
||||
- Contexte technique : workflow / transitions d'état — RL799_V2 08-04-2026
|
||||
|
||||
---
|
||||
|
||||
<a id="risque-couverture-chemins-ecriture-securite"></a>
|
||||
## Couverture incomplète des chemins d'écriture lors d'ajout de sécurité transverse
|
||||
|
||||
### Risques
|
||||
|
||||
- Quand on ajoute une mesure de sécurité (chiffrement, sanitisation, validation, audit) sur un chemin d'écriture (ex: upload), les chemins alternatifs vers la même ressource (ex: create-from-text, import CSV, seed) sont oubliés, laissant une faille
|
||||
|
||||
### Symptômes
|
||||
|
||||
- Un chemin d'écriture protégé, un autre non protégé, pour la même ressource
|
||||
- Données non chiffrées ou non sanitisées créées par un chemin secondaire
|
||||
|
||||
### Bonnes pratiques / mitigations
|
||||
|
||||
- Avant de marquer une tâche sécurité comme terminée, lister TOUS les chemins d'écriture vers la ressource ciblée (grep `createDocument`, `writeFile`, `prisma.model.create`) et vérifier que chacun est couvert
|
||||
- Documenter la liste dans les Dev Notes de la story
|
||||
|
||||
- Contexte technique : sécurité / transverse — RL799_V2 08-04-2026
|
||||
|
||||
---
|
||||
|
||||
<a id="risque-anonymisation-rgpd-pieges"></a>
|
||||
## Anonymisation RGPD — pièges courants
|
||||
|
||||
### Risques
|
||||
|
||||
- **Données personnelles dans les audit logs** : stocker email ou nom dans les métadonnées d'un audit log d'anonymisation annule le droit à l'oubli (art. 17)
|
||||
- **Password en clair après anonymisation** : remplacer le password par une string comme `'ANONYMIZED'` est un signal exploitable en base
|
||||
- **Dernier admin anonymisable** : le système peut se retrouver sans administrateur
|
||||
- **Sessions non révoquées** : le JWT existant reste valide jusqu'à expiration
|
||||
|
||||
### Symptômes
|
||||
|
||||
- Email en clair dans les métadonnées d'audit après anonymisation
|
||||
- Password `'ANONYMIZED'` visible en base au lieu d'un hash bcrypt structurellement invalide
|
||||
- Aucun admin actif restant après anonymisation
|
||||
- Utilisateur anonymisé qui peut encore accéder à l'application
|
||||
|
||||
### Bonnes pratiques / mitigations
|
||||
|
||||
- Utiliser un hash tronqué (ex: `sha256(email).slice(0,12)`) pour la corrélation dans les audit logs, pas l'email brut
|
||||
- Remplacer le password par un hash bcrypt structurellement invalide (ex: `$2b$10$INVALID...`)
|
||||
- Vérifier qu'au moins un admin actif reste après anonymisation
|
||||
- Révoquer tous les refresh tokens du user dans la même transaction
|
||||
|
||||
- Contexte technique : RGPD / sécurité — RL799_V2 08-04-2026
|
||||
|
||||
---
|
||||
|
||||
<a id="risque-assertions-body-erreur-migration-codes"></a>
|
||||
## Assertions de body d'erreur non mises à jour après migration de codes
|
||||
|
||||
### Risques
|
||||
|
||||
- Lors d'une migration de codes d'erreur (ex: distinguer `UNAUTHORIZED` de `FORBIDDEN`), les assertions de status HTTP sont mises à jour mais pas les assertions sur le body JSON
|
||||
|
||||
### Symptômes
|
||||
|
||||
- `assert.equal(response.status, 401)` passe, mais `assert.equal(body.error.code, 'FORBIDDEN')` échoue — le code réel est `UNAUTHORIZED`
|
||||
- Détecté seulement quand on relance la suite complète
|
||||
|
||||
### Bonnes pratiques / mitigations
|
||||
|
||||
- Lors de toute modification d'un code d'erreur dans un helper centralisé, rechercher TOUTES les assertions qui testent l'ancien code (`grep -rn 'FORBIDDEN' __tests__/`) et les mettre à jour en cohérence
|
||||
- Ne pas se fier au fait que "les tests passent" sans les exécuter réellement
|
||||
|
||||
- Contexte technique : tests / migration de codes — RL799_V2 08-04-2026
|
||||
|
||||
---
|
||||
|
||||
<a id="risque-audit-conditionnel-lookup-secondaire"></a>
|
||||
## Audit conditionnel sur un lookup DB secondaire
|
||||
|
||||
### Risques
|
||||
|
||||
- Le handler utilise `requireRoleAccess` qui ne retourne que `{ email, role }` mais pas `userId`. Pour logger l'audit, un second lookup (`getUserByEmail`) est nécessaire et peut échouer silencieusement
|
||||
|
||||
### Symptômes
|
||||
|
||||
- Une action sensible (création, promotion, suppression) réussit mais aucun log d'audit n'est écrit
|
||||
- Le caller est pourtant authentifié
|
||||
|
||||
### Bonnes pratiques / mitigations
|
||||
|
||||
- Sur tout endpoint qui doit journaliser un audit, utiliser un helper qui retourne `{ userId, email, role }` directement depuis le JWT `sub` claim, plutôt qu'un helper + lookup DB
|
||||
- Le userId est déjà dans le token — pas besoin d'aller le chercher en base
|
||||
|
||||
- Contexte technique : audit / auth — RL799_V2 08-04-2026
|
||||
|
||||
---
|
||||
|
||||
<a id="risque-derive-format-erreur-api"></a>
|
||||
## Dérive du format d'erreur API entre services
|
||||
|
||||
### Risques
|
||||
|
||||
- Chaque service recrée sa propre fonction `errorResponse` locale au lieu de réutiliser un helper centralisé
|
||||
- Le format standard `{ error: { code, message, requestId } }` n'est pas imposé par le typage
|
||||
|
||||
### Symptômes
|
||||
|
||||
- Certaines routes API retournent `{ error: { code, message } }` sans `requestId`, d'autres incluent le `requestId`
|
||||
- Les erreurs sans requestId sont impossibles à tracer en production
|
||||
|
||||
### Bonnes pratiques / mitigations
|
||||
|
||||
- Tout nouveau service doit inclure `requestId: crypto.randomUUID()` dans ses réponses d'erreur
|
||||
- Factoriser un helper `createApiErrorResponse` partagé dans `lib/` pour éviter la divergence
|
||||
- **Signal review** : réponse d'erreur sans `requestId` dans un nouveau service
|
||||
|
||||
- Contexte technique : observabilité / API — RL799_V2 08-04-2026
|
||||
|
||||
---
|
||||
|
||||
<a id="risque-unicite-applicative-sans-contrainte-db"></a>
|
||||
## Unicité applicative sans contrainte DB
|
||||
|
||||
### Risques
|
||||
|
||||
- Un contrôle d'unicité uniquement applicatif sur des affectations "actives" reste vulnérable aux races concurrentes et peut créer deux titulaires actifs sur un même rôle
|
||||
|
||||
### Symptômes
|
||||
|
||||
- Deux enregistrements actifs pour un slot censé être unique (ex: deux titulaires pour un rôle d'officier)
|
||||
- Bug intermittent sous charge concurrente, invisible en dev
|
||||
|
||||
### Bonnes pratiques / mitigations
|
||||
|
||||
- Pour tout agrégat avec invariant "un seul actif" (ex: mandat d'officier par rôle), imposer une contrainte d'unicité au niveau base (index unique partiel ou stratégie équivalente) en plus des checks service
|
||||
- Les checks applicatifs seuls ne suffisent pas sous concurrence
|
||||
|
||||
- Contexte technique : Prisma / contraintes — RL799_V2 08-04-2026
|
||||
|
||||
@@ -120,3 +120,47 @@ return buildLocalizedPath(locale, "home");
|
||||
- **Règle** : `os.tmpdir()` est interdit pour les fichiers qui seront renommés vers un volume Docker bind-mounté.
|
||||
|
||||
- Contexte technique : Node.js / Docker / fichiers media — app-template-resto 31-03-2026
|
||||
|
||||
---
|
||||
|
||||
<a id="risque-csp-strict-routes-html-api"></a>
|
||||
## CSP strict casse les routes HTML dans une API Next.js
|
||||
|
||||
### Risques
|
||||
|
||||
- Appliquer un CSP strict (`default-src 'self'`) via `headers()` dans `next.config.ts` sur toutes les routes `/api/*` casse les routes qui servent du HTML avec des dépendances externes (Swagger UI, pages de documentation, etc.)
|
||||
|
||||
### Symptômes
|
||||
|
||||
- La page HTML se charge mais reste blanche — les scripts et styles externes sont bloqués silencieusement par le navigateur
|
||||
|
||||
### Bonnes pratiques / mitigations
|
||||
|
||||
- Quand on ajoute des headers de sécurité via un pattern global, vérifier s'il existe des routes qui servent du HTML (pas uniquement du JSON)
|
||||
- Si oui, ajouter une règle dédiée plus spécifique **en premier** dans le tableau `headers()` (Next.js matche dans l'ordre de déclaration) avec un CSP adapté
|
||||
- Factoriser les headers communs dans une constante partagée
|
||||
|
||||
- Contexte technique : Next.js / CSP — RL799_V2 08-04-2026
|
||||
|
||||
---
|
||||
|
||||
<a id="risque-middleware-faux-argument-edge"></a>
|
||||
## Middleware Next.js — faux argument "Edge Runtime incompatible" pour éviter les imports
|
||||
|
||||
### Risques
|
||||
|
||||
- Dupliquer la logique métier dans `middleware.ts` au lieu d'importer depuis `src/lib/` sous prétexte que "Edge Runtime ne supporte pas les imports cross-boundary"
|
||||
- C'est faux pour les modules purs (pas de `fs`, `child_process`, `crypto` Node-only, etc.)
|
||||
|
||||
### Symptômes
|
||||
|
||||
- Logique dupliquée entre middleware et utilitaires, dérive garantie à la prochaine modification
|
||||
- Commentaire dans le code justifiant la duplication par une incompatibilité Edge non vérifiée
|
||||
|
||||
### Bonnes pratiques / mitigations
|
||||
|
||||
- Le middleware Next.js peut importer n'importe quel module local tant que celui-ci n'utilise que des API compatibles Edge (Web APIs, `process.env`, opérations JS pures)
|
||||
- Factoriser la logique partagée dans un module commun et l'importer depuis le middleware
|
||||
- **Signal review** : logique dupliquée dans `middleware.ts` avec un commentaire "Edge incompatible"
|
||||
|
||||
- Contexte technique : Next.js / middleware — RL799_V2 08-04-2026
|
||||
|
||||
@@ -384,3 +384,26 @@ Checklist minimale après `prisma migrate resolve --applied` :
|
||||
- **Signal review** : si le code suppose l'unicité, la base doit l'imposer explicitement
|
||||
|
||||
- Contexte technique : Prisma / contraintes DB — RL799_V2 06-04-2026
|
||||
|
||||
---
|
||||
|
||||
<a id="risque-catch-all-silencieux-repository"></a>
|
||||
## Catch-all silencieux dans les repositories Prisma
|
||||
|
||||
### Risques
|
||||
|
||||
- Un `try { prisma.update(...); return true } catch { return false }` dans un repository masque TOUTES les erreurs Prisma (connexion perdue, timeout, contrainte violée) derrière une réponse "not found"
|
||||
- Le service appelant ne peut pas distinguer un échec technique d'une absence de données
|
||||
|
||||
### Symptômes
|
||||
|
||||
- Le service retourne 404 alors que la DB est down, ou 404 alors qu'une contrainte FK est violée
|
||||
- Le monitoring ne voit aucune erreur 500
|
||||
|
||||
### Bonnes pratiques / mitigations
|
||||
|
||||
- Dans les repositories Prisma, catcher uniquement `Prisma.PrismaClientKnownRequestError` avec le code spécifique attendu (`P2025` pour record not found, `P2002` pour unique constraint)
|
||||
- Re-throw toute autre erreur pour qu'elle remonte en 500 dans le handler
|
||||
- **Signal review** : `catch {` ou `catch (e) {` sans vérification de `e.code` dans un repository Prisma
|
||||
|
||||
- Contexte technique : Prisma / error handling — RL799_V2 08-04-2026
|
||||
|
||||
@@ -90,3 +90,45 @@ Si un test vérifie un *comportement* (ex: "le menu se ferme après clic"), il d
|
||||
|
||||
- Validé le : 03-04-2026
|
||||
- Contexte technique : Vue 3 / node:test — RL799_V2 story 6A.8
|
||||
|
||||
---
|
||||
|
||||
<a id="pattern-ordre-dom-comparedocumentposition"></a>
|
||||
## Pattern : Vérifier l'ordre DOM avec `compareDocumentPosition`, pas `boundingBox`
|
||||
|
||||
### Synthèse
|
||||
|
||||
- **Objectif** : valider l'ordre réel des éléments dans le DOM, indépendamment du rendu CSS.
|
||||
- **Contexte** : tests E2E Playwright qui doivent vérifier l'ordre d'affichage de sections ou d'éléments.
|
||||
- **Quand l'utiliser** : toute assertion d'ordre dans un test E2E.
|
||||
- **Quand l'éviter** : si on veut explicitement tester la position visuelle CSS (rare).
|
||||
|
||||
### Analyse
|
||||
|
||||
- **Avantages** :
|
||||
- vérifie l'ordre DOM réel, insensible aux propriétés CSS (`flex-order`, `position`, `transform`)
|
||||
- pas de `null` en retour (contrairement à `boundingBox()` hors viewport)
|
||||
- déterministe
|
||||
- **Limites / vigilance** :
|
||||
- ne vérifie pas la position visuelle — si le test doit valider un rendu CSS spécifique, `boundingBox` reste pertinent
|
||||
|
||||
### Validation
|
||||
|
||||
- Validé le : 08-04-2026
|
||||
- Contexte technique : Playwright / E2E — RL799_V2 story 17-5
|
||||
|
||||
### Implémentation
|
||||
|
||||
```ts
|
||||
const aBeforeB = await page.evaluate(() => {
|
||||
const a = document.querySelector('[data-testid="section-a"]');
|
||||
const b = document.querySelector('[data-testid="section-b"]');
|
||||
if (!a || !b) return false;
|
||||
return (a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_FOLLOWING) !== 0;
|
||||
});
|
||||
expect(aBeforeB).toBe(true);
|
||||
```
|
||||
|
||||
### Risque associé
|
||||
|
||||
`boundingBox().y` vérifie la position visuelle rendue par CSS, pas l'ordre dans le DOM. De plus `boundingBox()` retourne `null` pour les éléments hors viewport → crash non déterministe.
|
||||
|
||||
@@ -112,3 +112,26 @@ if (user === null || user.role !== 'ADMIN') return <View />;
|
||||
- **Règle** : tout guard de rôle dans un composant React Native doit utiliser `useEffect` + redirect + rendu vide, jamais un return conditionnel direct
|
||||
|
||||
- Contexte technique : React Native / Expo Router / Zustand auth — app-alexandrie 24-03-2026
|
||||
|
||||
---
|
||||
|
||||
<a id="risque-tests-structurels-obsoletes-migration-auth"></a>
|
||||
## Tests structurels obsolètes après migration du mécanisme d'auth
|
||||
|
||||
### Risques
|
||||
|
||||
- Lors de la migration d'un pattern `Authorization: Bearer` vers des cookies httpOnly, les tests structurels (qui mocquent `getSession()` et assertent sur les headers) deviennent tous faux sans que TypeScript ne détecte rien
|
||||
- Les mocks ne matchent plus le code réel, les tests passent ou échouent silencieusement
|
||||
|
||||
### Symptômes
|
||||
|
||||
- Tests frontend qui mocquent `getSession()` et assertent `Authorization: Bearer` sur les appels `fetch`, alors que le code utilise désormais `apiFetch` avec cookies (`credentials: 'include'`)
|
||||
- Faux positifs : les tests ne testent plus rien de réel
|
||||
|
||||
### Bonnes pratiques / mitigations
|
||||
|
||||
- Après toute migration d'un mécanisme d'auth frontend, auditer systématiquement les tests de services pour vérifier que les assertions portent sur le bon mécanisme (cookies vs headers)
|
||||
- Adapter les mocks pour patcher `apiFetch` au lieu de `getSession` + `fetch` brut
|
||||
- **Signal review** : `Authorization: Bearer` dans les tests alors que le code production utilise `credentials: 'include'`
|
||||
|
||||
- Contexte technique : Vue 3 / auth migration — RL799_V2 08-04-2026
|
||||
|
||||
@@ -254,3 +254,26 @@ const routes = [
|
||||
- Ajouter un test qui valide la synchro sur changement de query param (même composant réutilisé, navigation intra-page)
|
||||
|
||||
- Contexte technique : Vue 3 / Vue Router 4 — RL799_V2 02-04-2026
|
||||
|
||||
---
|
||||
|
||||
<a id="risque-vue-router-tests-textuels-guards"></a>
|
||||
## Vue Router — faux sentiment de sécurité avec tests textuels sur guards
|
||||
|
||||
### Risques
|
||||
|
||||
- Des assertions de type `content.includes("requiresRoles: ['admin']")` valident la présence de configuration mais pas le comportement réel du `beforeEach`
|
||||
- Un changement dans le guard redirige mal (`login` vs `home`) ou ignore un cas RBAC sans casser les tests
|
||||
|
||||
### Symptômes
|
||||
|
||||
- Les tests passent alors qu'un changement dans le guard redirige vers la mauvaise page
|
||||
- Régressions de guard runtime invisibles aux tests
|
||||
|
||||
### Bonnes pratiques / mitigations
|
||||
|
||||
- Ajouter au moins un test runtime qui exécute effectivement la logique du guard (session admin/non-admin) et vérifie la valeur retournée par le guard (`true` ou `{ name: 'home' }`)
|
||||
- Les tests textuels (`includes`) sont acceptables comme smoke structurel mais ne doivent pas être la seule couverture des guards de navigation
|
||||
- **Signal review** : guards de navigation couverts uniquement par des tests `includes()` sans test comportemental
|
||||
|
||||
- Contexte technique : Vue 3 / Vue Router 4 / node:test — RL799_V2 08-04-2026
|
||||
|
||||
@@ -148,3 +148,26 @@ source_projects: [app-alexandrie, app-template-resto, RL799_V2]
|
||||
- **Règle** : si un test vérifie un *comportement* (ex: "le menu se ferme après clic"), il doit monter le composant, pas chercher une string dans le source
|
||||
|
||||
- Contexte technique : Vue 3 / node:test — RL799_V2 02-04-2026
|
||||
|
||||
---
|
||||
|
||||
<a id="risque-catch-false-test-skip-e2e"></a>
|
||||
## Anti-pattern : `.catch(() => false)` + `test.skip` dans les tests E2E
|
||||
|
||||
### Risques
|
||||
|
||||
- Le pattern `await locator.isVisible().catch(() => false)` suivi de `test.skip(true, ...)` masque les erreurs réelles (sélecteur cassé, timeout, changement de structure) derrière un skip silencieux
|
||||
- Un AC peut rester perpétuellement non testé sans qu'aucun rapport ne le signale comme problème
|
||||
|
||||
### Symptômes
|
||||
|
||||
- Tests skippés en permanence dans les rapports CI
|
||||
- AC marqués `[x]` dans la story mais jamais réellement validés
|
||||
|
||||
### Bonnes pratiques / mitigations
|
||||
|
||||
- Pour vérifier si un élément optionnel est présent (données dépendantes du seed), utiliser `await locator.count()` qui retourne 0 sans lancer d'exception, puis `test.skip` uniquement si count === 0
|
||||
- Réserver `.catch()` aux cas où une exception est réellement attendue et documentée
|
||||
- **Signal review** : `.catch(() => false)` suivi de `test.skip` dans un test E2E
|
||||
|
||||
- Contexte technique : Playwright / E2E — RL799_V2 08-04-2026
|
||||
|
||||
@@ -227,3 +227,27 @@ source_projects: [app-alexandrie, app-template-resto, RL799_V2]
|
||||
- **Règle review** : tout type défini localement qui correspond à un DTO existant dans shared est une régression
|
||||
|
||||
- Contexte technique : BMAD / agents autonomes — RL799_V2 07-04-2026
|
||||
|
||||
---
|
||||
|
||||
<a id="risque-completion-notes-incoherentes"></a>
|
||||
## Incohérence entre Completion Notes et réalité du code
|
||||
|
||||
### Risques
|
||||
|
||||
- Les dev agents documentent des métriques dans les Completion Notes (nombre de tests, nombre de routes) sans les vérifier en fin d'implémentation
|
||||
- Le reviewer passe du temps à réconcilier des chiffres qui ne matchent pas
|
||||
|
||||
### Symptômes
|
||||
|
||||
- "20 tests RBAC" revendiqués dans les Completion Notes alors que le fichier test n'en contient que 11
|
||||
- Route `DELETE /api/favorites` listée dans le rapport d'audit mais inexistante dans le code
|
||||
|
||||
### Bonnes pratiques / mitigations
|
||||
|
||||
- Avant de passer une story en `review`, le dev agent doit vérifier que les chiffres dans les Completion Notes correspondent exactement au code
|
||||
- `grep -c 'test(' fichier.test.ts` pour le nombre de tests
|
||||
- `grep -c 'export' fichier.routes.ts` pour le nombre de routes
|
||||
- **Règle** : toute métrique dans les Completion Notes doit être vérifiable par une commande simple
|
||||
|
||||
- Contexte technique : BMAD / workflow agent — RL799_V2 08-04-2026
|
||||
|
||||
Reference in New Issue
Block a user