docs: capitaliser les patterns valides du 16 mars

This commit is contained in:
MaksTinyWorkshop
2026-03-16 15:36:17 +01:00
parent 019a6d2787
commit 0fe41269e8
4 changed files with 413 additions and 7 deletions

View File

@@ -8,7 +8,7 @@ Ce fichier contient **uniquement** des patterns back-end :
Objectif : éviter de réinventer la roue et réduire le temps de debug. Objectif : éviter de réinventer la roue et réduire le temps de debug.
Dernière mise à jour : 12-03-2026 Dernière mise à jour : 16-03-2026
--- ---
@@ -32,6 +32,11 @@ Dernière mise à jour : 12-03-2026
- [Restauration dachats Stripe en 3 étapes](#pattern-restauration-achats-stripe) - [Restauration dachats Stripe en 3 étapes](#pattern-restauration-achats-stripe)
- [Mapping explicite de `P2002` Prisma sur update de champ unique](#pattern-prisma-p2002-update-unique) - [Mapping explicite de `P2002` Prisma sur update de champ unique](#pattern-prisma-p2002-update-unique)
- [Autorisation interne minimale sans RBAC complet](#pattern-autorisation-interne-minimale) - [Autorisation interne minimale sans RBAC complet](#pattern-autorisation-interne-minimale)
- [Anti-énumération sur endpoints auth liés à un email](#pattern-anti-enumeration-auth-email)
- [Token à usage unique — génération, hash et invalidation atomique](#pattern-token-usage-unique)
- [Guardrails multi-tenant — 403 vs 404 selon la sémantique](#pattern-guardrails-multi-tenant-403-404)
- [Repository tenant-aware — `tenantId` obligatoire dans la signature](#pattern-repository-tenant-aware)
- [Défense en profondeur — inclure `tenantId` dans les updates](#pattern-tenantid-dans-updates)
--- ---
@@ -747,3 +752,176 @@ handlePackWebhookEvent(event): PackWebhookResult | null
- On préfère 5 patterns solides à 50 “bons conseils”. - On préfère 5 patterns solides à 50 “bons conseils”.
- Un pattern = une idée actionnable + son cadre dutilisation. - Un pattern = une idée actionnable + son cadre dutilisation.
---
<a id="pattern-anti-enumeration-auth-email"></a>
## Pattern : Anti-énumération sur endpoints auth liés à un email
- Objectif : empêcher quun endpoint auth révèle si un compte existe, nexiste pas ou nest pas éligible.
- Contexte : reset de mot de passe, invitation, vérification de compte, login ou tout flux qui part dun email utilisateur.
- Quand lutiliser : dès quune requête auth touche un identifiant de type email.
- Quand léviter : jamais sur une surface exposée.
- Avantage :
- réduit la fuite dinformation sur les comptes existants
- homogénéise les réponses côté client
- se combine bien avec les garde-fous anti-abus
- Limites / vigilance :
- ne protège pas seul contre le brute-force, à combiner avec du rate-limiting
- les logs internes doivent conserver la vraie cause sans lexposer au client
- Validé le : 16-03-2026
- Contexte technique : Node.js / auth applicative / API HTTP
### Implémentation (exemple minimal)
```txt
- retourner la même réponse HTTP 200 quun compte existe ou non
- ne jamais distinguer "email inconnu", "email connu" ou "compte OAuth-only" dans la réponse
- journaliser la cause réelle côté serveur
- ajouter un rate-limiting basé sur email + IP
```
### Checklist
- Réponse client uniforme pour les cas compte connu/inconnu/non éligible
- Aucune fuite dexistence dans le message ou le code derreur
- Rate-limiting présent sur les endpoints exposés
- Logs internes exploitables
---
<a id="pattern-token-usage-unique"></a>
## Pattern : Token à usage unique — génération, hash et invalidation atomique
- Objectif : standardiser la création et la consommation de tokens sensibles sans stocker de secret brut en base.
- Contexte : invitation, reset de mot de passe, vérification demail, lien magique ou tout token one-shot.
- Quand lutiliser : pour tout token à usage unique transmis à lutilisateur.
- Quand léviter : sessions longues ou secrets devant être relus en clair côté serveur.
- Avantage :
- réduit limpact dune fuite de base
- garde des tokens URL-safe
- favorise une consommation atomique et réutilisable
- Limites / vigilance :
- la consommation doit rester atomique
- la politique dexpiration doit être explicite
- Validé le : 16-03-2026
- Contexte technique : Node.js `crypto` / Prisma / email ou URL signée
### Implémentation (exemple minimal)
```txt
- générer le token avec `crypto.randomBytes(32).toString("base64url")`
- stocker uniquement le hash SHA-256 du token en base
- transmettre le token brut uniquement via URL ou email
- recalculer le hash côté serveur lors de la consommation
- invalider le token dans une transaction atomique après usage
```
### Checklist
- Token brut jamais persisté en base
- Hash recalculé côté serveur pour la vérification
- Expiration explicite
- Invalidation atomique après consommation
---
<a id="pattern-guardrails-multi-tenant-403-404"></a>
## Pattern : Guardrails multi-tenant — 403 vs 404 selon la sémantique
- Objectif : éviter les fuites dinformation inter-tenant tout en gardant une sémantique derreur claire.
- Contexte : API multi-tenant avec ressources métier isolées et surfaces internes ou opérateur.
- Quand lutiliser : dès quune vérification dappartenance tenant peut soit refuser explicitement laccès, soit masquer lexistence dune ressource.
- Quand léviter : contexte mono-tenant ou endpoints purement internes sans enjeu de fuite.
- Avantage :
- clarifie la convention de sécurité
- évite les réponses incohérentes selon les modules
- facilite les tests disolation tenant
- Limites / vigilance :
- la convention doit être documentée et appliquée partout
- un mauvais choix entre 403 et 404 peut révéler une information sensible
- Validé le : 16-03-2026
- Contexte technique : API multi-tenant / HTTP / services métier
### Implémentation (exemple minimal)
```txt
- `assertTenantMatch(actor, expectedTenantId)` -> 403 quand la ressource est connue mais laccès refusé
- `assertResourceBelongsToTenant(actor, resourceTenantId)` -> 404 quand il faut masquer lexistence dune ressource dun autre tenant
- documenter la convention dans le module
- couvrir les deux sémantiques par des tests dédiés
```
### Checklist
- Convention 403 vs 404 documentée
- Helpers distincts selon la sémantique métier
- Aucune fuite dexistence cross-tenant sur les ressources métier
- Tests dédiés sur les deux comportements
---
<a id="pattern-repository-tenant-aware"></a>
## Pattern : Repository tenant-aware — `tenantId` obligatoire dans la signature
- Objectif : rendre impossible par construction une query non scopée sur un domaine multi-tenant.
- Contexte : repositories ou services daccès aux données sur ressources tenant-scoped.
- Quand lutiliser : dès quun domaine métier est massivement filtré par tenant.
- Quand léviter : domaines réellement globaux ou méthodes volontairement cross-tenant.
- Avantage :
- force le scoping dès la signature TypeScript
- réduit les oublis de filtre tenant dans les call sites
- rend les exceptions cross-tenant visibles
- Limites / vigilance :
- les exceptions cross-tenant doivent être rares et documentées explicitement
- ne dispense pas dun second garde-fou dans les mutations sensibles
- Validé le : 16-03-2026
- Contexte technique : TypeScript / Prisma / architecture repository
### Implémentation (exemple minimal)
```txt
- chaque méthode métier tenant-scoped prend `tenantId` en paramètre obligatoire
- les méthodes réellement cross-tenant sont nommées et documentées comme exception
- les call sites Prisma directs sur ces domaines sont interdits ou supprimés
```
### Checklist
- `tenantId` obligatoire sur les méthodes tenant-scoped
- Exceptions cross-tenant documentées
- Appels directs concurrents à Prisma supprimés
- Tests sur scoping tenant au niveau repository
---
<a id="pattern-tenantid-dans-updates"></a>
## Pattern : Défense en profondeur — inclure `tenantId` dans les updates
- Objectif : éviter une mutation cross-tenant même si un identifiant a été mal résolu en amont.
- Contexte : `update` ou `updateMany` sur une ressource tenant-scoped.
- Quand lutiliser : dès quune mutation dépend dun `id` reçu ou résolu dans un flux multi-tenant.
- Quand léviter : ressources globales non liées à un tenant.
- Avantage :
- ajoute une seconde barrière côté base
- réduit limpact dun call site mal scopé
- rend la mutation plus sûre sans complexité forte
- Limites / vigilance :
- ne remplace pas le scoping en lecture ni la vérification dautorisation
- suppose que `tenantId` soit disponible au moment de la mutation
- Validé le : 16-03-2026
- Contexte technique : Prisma / multi-tenant / mutations métier
### Implémentation (exemple minimal)
```txt
- préférer `where: { id, tenantId }` à `where: { id }` sur les updates tenant-scoped
- appliquer la même règle sur `updateMany` et opérations de révocation
- conserver les vérifications métier amont, mais ne pas leur déléguer toute la sécurité
```
### Checklist
- `tenantId` présent dans les clauses `where` des updates sensibles
- Pas de mutation tenant-scoped basée sur `id` seul
- Revue explicite des exceptions documentées

View File

@@ -8,7 +8,7 @@ Ce fichier recense des risques back-end susceptibles de provoquer :
- régressions coûteuses, - régressions coûteuses,
- incohérences de données. - incohérences de données.
Dernière mise à jour : 10-03-2026 Dernière mise à jour : 16-03-2026
--- ---
@@ -44,6 +44,8 @@ Dernière mise à jour : 10-03-2026
- [Stripe `list()` sans gestion de `has_more`](#risque-stripe-list-has-more) - [Stripe `list()` sans gestion de `has_more`](#risque-stripe-list-has-more)
- [Concurrence entre activation locale et webhook sur transition trial → payant](#risque-trial-payant-concurrence) - [Concurrence entre activation locale et webhook sur transition trial → payant](#risque-trial-payant-concurrence)
- [`jest.clearAllMocks()` dans des `beforeEach` imbriqués avec mocks Prisma](#risque-jest-clearallmocks-imbrique) - [`jest.clearAllMocks()` dans des `beforeEach` imbriqués avec mocks Prisma](#risque-jest-clearallmocks-imbrique)
- [Suppression du cookie après révocation DB sur logout](#risque-cookie-apres-revocation-db)
- [Repository layer non branché (dead layer)](#risque-repository-dead-layer)
--- ---
@@ -503,3 +505,51 @@ if (!user?.userId) {
- Éviter les `clearAllMocks()` concurrents à plusieurs niveaux de nesting - Éviter les `clearAllMocks()` concurrents à plusieurs niveaux de nesting
- Préférer un setup explicite et local par scénario quand les mocks Prisma sont structurants - Préférer un setup explicite et local par scénario quand les mocks Prisma sont structurants
- Contexte technique : Jest / Prisma / tests NestJS — 10-03-2026 - Contexte technique : Jest / Prisma / tests NestJS — 10-03-2026
---
<a id="risque-cookie-apres-revocation-db"></a>
## Suppression du cookie après révocation DB sur logout
### Risques
- Si la révocation DB échoue avant la suppression du cookie, lutilisateur garde un cookie local devenu incohérent
- Lutilisateur 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 didempotence adaptée
- Vérifier en test quun échec DB ne laisse pas laccès browser actif
- Contexte technique : Next.js / auth par cookie / session persistée — 16-03-2026
---
<a id="risque-repository-dead-layer"></a>
## Repository layer non branché (dead layer)
### Risques
- Donner une impression de sécurité alors que le code métier continue dappeler lORM directement
- Multiplier les chemins daccès aux données avec des règles différentes
- Payer le coût dune abstraction qui na aucun effet réel
### Symptômes
- Un repository est créé mais les anciens call sites Prisma restent en place
- Les nouvelles règles de scoping ou de sécurité ne sappliquent pas partout
- La review montre des fichiers de repository peu ou jamais importés
### Bonnes pratiques / mitigations
- Vérifier quune nouvelle couche dabstraction est réellement branchée dans les call sites existants
- Rechercher explicitement les appels directs restants lors de la review
- Refuser lintroduction dune couche repository tant que la migration effective nest pas faite
- Contexte technique : TypeScript / Prisma / refactor daccès aux données — 16-03-2026

View File

@@ -12,7 +12,7 @@ Il sert de **mémoire durable** pour éviter :
- de redélibérer éternellement sur des sujets déjà tranchés, - de redélibérer éternellement sur des sujets déjà tranchés,
- de propager des “bonnes pratiques” théoriques non éprouvées. - de propager des “bonnes pratiques” théoriques non éprouvées.
Dernière mise à jour : 12-03-2026 Dernière mise à jour : 16-03-2026
--- ---
@@ -23,6 +23,7 @@ Dernière mise à jour : 12-03-2026
- [Formulaire robuste avec validation et erreurs explicites](#pattern-formulaire-robuste) - [Formulaire robuste avec validation et erreurs explicites](#pattern-formulaire-robuste)
- [Navigation réactive post-action async (React / Expo Router)](#pattern-navigation-reactive-post-action-async) - [Navigation réactive post-action async (React / Expo Router)](#pattern-navigation-reactive-post-action-async)
- [Refresh idempotent sur store de liste paginée](#pattern-refresh-idempotent-liste-paginee) - [Refresh idempotent sur store de liste paginée](#pattern-refresh-idempotent-liste-paginee)
- [UI admin légère sur domaine existant](#pattern-ui-admin-legere-domaine-existant)
--- ---
@@ -336,6 +337,52 @@ const handleOAuth = async () => {
--- ---
<a id="pattern-ui-admin-legere-domaine-existant"></a>
## Pattern : UI admin légère sur domaine existant
### Synthèse
- **Objectif** : ajouter une capacité interne simple sans ouvrir trop tôt un back-office séparé ni dupliquer la logique métier.
- **Contexte** : app mobile ou SPA avec un domaine métier déjà structuré et quelques actions internes ponctuelles.
- **Quand lutiliser** : publication, activation, modération légère, bascule de statut, action opérateur simple.
- **Quand léviter** : permissions complexes, workflows multiples, audit riche ou volume dactions qui justifie un vrai espace dadministration.
### Analyse
- **Avantages** :
- réutilise le service et le store métier existants
- limite le coût de structure pour une capacité admin mince
- garde les mutations testables et lisibles
- **Limites / vigilance** :
- ne pas laisser cette approche dériver vers un pseudo back-office implicite
- le refresh après mutation doit être explicite sur les vues impactées
### Validation
- Validé le : 10-03-2026
- Contexte technique : React Native / Expo Router / store de domaine
### Implémentation (exemple minimal)
```txt
- ajouter une route dédiée minimale pour laction interne
- réutiliser le service/store métier existant au lieu de créer une couche parallèle
- afficher le statut courant avant action
- bloquer les actions concurrentes avec un flag explicite (`isUpdating*`)
- déclencher un refresh explicite des vues impactées après succès
- éviter les mutations en fire-and-forget
```
### Checklist
- [ ] Route dédiée minimale, pas de mini back-office générique
- [ ] Réutilisation du domaine métier existant
- [ ] Garde-fou explicite contre les doubles actions
- [ ] Refresh explicite après mutation réussie
- [ ] Tests sur succès, erreur et action concurrente
---
### Principes transverses ### Principes transverses
- Un pattern = une responsabilité claire - Un pattern = une responsabilité claire

View File

@@ -75,6 +75,53 @@ Description courte, factuelle, orientée réutilisation.
4. Une fois intégrée, la proposition doit être **supprimée de ce fichier**. 4. Une fois intégrée, la proposition doit être **supprimée de ce fichier**.
5. La structure de ce fichier est **restaurée à son état initial** (voir `70_templates/template_a_capitaliser.md`). 5. La structure de ce fichier est **restaurée à son état initial** (voir `70_templates/template_a_capitaliser.md`).
---
# Revue de tri — 2026-03-16
Cette section sert à qualifier les propositions en attente avant intégration manuelle.
## Rappel
L'exemple plus haut est conservé volontairement pour montrer aux agents le format attendu.
Il ne fait pas partie des propositions à intégrer.
## À intégrer
Intégré le 16-03-2026 dans les fichiers cibles.
## À fusionner avant intégration
- `2026-03-16 — app-template-resto``10_backend_patterns_valides.md`
`Isolation des guards purs des modules server-only`
- `2026-03-16 — app-template-resto``10_backend_patterns_valides.md`
``server-only` réservé aux modules avec APIs Next.js exclusivement serveur`
- `2026-03-16 — app-template-resto` → `10_backend_patterns_valides.md`
`Server Action Next.js — isoler la logique pure dans un module injectable`
Fusion recommandée :
`Next.js server-only / Server Actions : garder l'orchestration runtime-only fine et extraire la logique pure testable`
- `2026-03-16 — app-template-resto` → `10_backend_patterns_valides.md`
`Transaction obligatoire pour les opérations auth multi-étapes`
- `2026-03-16 — app-template-resto` → `10_backend_patterns_valides.md`
`Suppression de session idempotente (P2025)`
Fusion recommandée :
`Opérations auth sensibles : atomiques, idempotentes et cohérentes en cas d'erreur`
## À laisser en tampon
- `2026-03-10 — app-alexandrie` → `10_backend_patterns_valides.md`
`Progression V1 calculée sans persistance dédiée`
Motif : intéressant mais encore trop lié à un choix produit / modélisation métier.
- `2026-03-16 — app-template-resto` → `10_backend_risques_et_vigilance.md`
`Divergence schéma / spec story`
Motif : utile en review, mais trop lié au process de story pour la mémoire durable.
- `2026-03-16 — app-template-resto / code-review story 1.11` → `10_backend_risques_et_vigilance.md`
`server-only dans les repositories bloque les tests unitaires`
Motif : le problème est réel, mais la solution proposée (`stub` local) ne doit pas devenir un standard.
2026-03-10 — app-alexandrie 2026-03-10 — app-alexandrie
FILE_UPDATE_PROPOSAL FILE_UPDATE_PROPOSAL
@@ -95,13 +142,97 @@ Pour une feature de progression minimum viable :
Évite le scope creep vers un module `achievements` prématuré et garde un contrat stable. Évite le scope creep vers un module `achievements` prématuré et garde un contrat stable.
2026-03-10 — app-alexandrie 2026-03-16 — app-template-resto
FILE_UPDATE_PROPOSAL FILE_UPDATE_PROPOSAL
Fichier cible : 10_frontend_patterns_valides.md Fichier cible : 10_backend_patterns_valides.md
Pourquoi : Pourquoi :
Pour une capacité admin mince sur mobile, une route dédiée légère branchée sur le domaine existant et un refresh explicite du store après mutation permettent de rester testable et robuste sans lancer un back-office séparé. Code review story 1.9 a révélé un pattern récurrent : les services avec opérations multi-étapes (hash + update + delete) doivent systématiquement utiliser $transaction pour éviter les race conditions.
Proposition : Proposition :
Pattern "UI admin légère sur domaine existant" : pour une action interne simple (publication, activation, modération légère), ajouter une route dédiée minimale qui réutilise le service/store métier existant, afficher le statut courant, bloquer les actions concurrentes avec un flag `isUpdating*`, et déclencher un refresh explicite des vues impactées après succès au lieu dun `fire-and-forget`. **Pattern : Transaction obligatoire pour les opérations auth multi-étapes**
Toute opération qui combine hashing de mot de passe + update user + invalidation de tokens doit être enveloppée dans `prisma.$transaction()`. Sans transaction, une interruption entre les étapes laisse l'état incohérent (ex: token valide après reset du mot de passe).
Exemple : consumePasswordReset — marquer consumedAt + update passwordHash + deleteMany autres tokens dans une seule transaction.
---
2026-03-16 — app-template-resto
FILE_UPDATE_PROPOSAL
Fichier cible : 10_backend_risques_et_vigilance.md
Pourquoi :
Code review story 1.9 a révélé un anti-pattern : les tâches de story peuvent déclarer [x] consumedAt sans que le champ existe réellement dans le schéma Prisma.
Proposition :
**Anti-pattern : Divergence schéma / spec story**
Lors d'une implémentation, valider que chaque champ mentionné dans les tâches (consumedAt, tokenHash, etc.) existe réellement dans le schéma Prisma avant de marquer la tâche [x]. Une story peut décrire consumedAt comme stratégie de conception sans que le champ soit présent — toujours croiser avec schema.prisma.
---
2026-03-16 — app-template-resto
FILE_UPDATE_PROPOSAL
Fichier cible : 10_backend_patterns_valides.md
Pourquoi :
Le module sendPasswordResetEmail utilise `server-only` ce qui le rend non-importable dans le runner de tests Node. Résolution : tester la logique pure (safeHttpUrl) dans un fichier séparé sans dépendances Next.js.
Proposition :
**Pattern : Isolation des guards purs des modules server-only**
Extraire la logique pure (validation URL, sanitisation) dans des fonctions utilitaires sans import `server-only` ou `nodemailer`. Le module email orchestre uniquement. Cela permet de tester les guards en isolation sans les contraintes du runtime Next.js.
---
2026-03-16 — app-template-resto
FILE_UPDATE_PROPOSAL
Fichier cible : 10_backend_patterns_valides.md
Pourquoi :
Pattern validé sur story 1.10 — la règle `server-only` vs testabilité est implicite dans le projet mais mérite d'être explicitée pour les agents futurs.
Proposition :
**Pattern : `server-only` réservé aux modules avec APIs Next.js exclusivement serveur**
Ne pas mettre `import "server-only"` sur les modules de logique pure injectés via dépendances (ex: `deleteSession({ prisma, sessionToken })`). Réserver `server-only` aux modules qui appellent des APIs Next.js runtime-only (`cookies()`, `headers()`, `redirect()`). Les modules purs sans ces imports peuvent être importés par le test runner Node et testés unitairement sans friction.
---
2026-03-16 — app-template-resto
FILE_UPDATE_PROPOSAL
Fichier cible : 10_backend_patterns_valides.md
Pourquoi :
Pattern validé sur story 1.10 — suppression de session avec gestion idempotente, réutilisable pour toute opération de révocation.
Proposition :
**Pattern : Suppression de session idempotente (P2025)**
Lors d'une déconnexion ou révocation de session, entourer le `prisma.session.delete()` d'un try/catch qui absorbe silencieusement le code Prisma `P2025` (record not found). Une session peut déjà avoir été supprimée (expiration, logout concurrent) — ce n'est pas une erreur, ne pas la propager.
---
2026-03-16 — app-template-resto
FILE_UPDATE_PROPOSAL
Fichier cible : 10_backend_patterns_valides.md
Pourquoi :
Pattern validé sur story 1.10 — Server Action Next.js qui orchestre des dépendances Next.js runtime non-testables : isoler la logique pure dans un module injectable.
Proposition :
**Pattern : Server Action Next.js — isoler la logique pure dans un module injectable**
Une Server Action qui appelle `cookies()`, `headers()` ou `redirect()` ne peut pas être testée unitairement (imports runtime-only). Pattern : extraire la logique pure (suppression DB, validation) dans une fonction avec injection de dépendances (`performSignOut({ prismaClient, sessionToken, redirectFn })`). La Server Action reste fine et orchestre uniquement les dépendances Next.js. Le module extrait est testable sans friction avec le runner Node natif.
---
2026-03-16 — app-template-resto / code-review story 1.11
FILE_UPDATE_PROPOSAL
Fichier cible : 10_backend_risques_et_vigilance.md
Pourquoi :
`import "server-only"` dans les repositories casse les tests Node.js hors Next.js — rencontré lors de cette review.
Proposition :
## Risque : `server-only` dans les repositories bloque les tests unitaires
`import "server-only"` empêche l'exécution des fichiers hors runtime Next.js.
Solution : créer un stub `node_modules/server-only/index.js` (no-op) pour les tests.
Alternativement, ne mettre `server-only` que dans les fichiers qui utilisent des APIs
Next.js (`cookies()`, `headers()`), pas dans les repositories purs.