PD-23 — Retour d'expérience (REX)¶
📚 Navigation User Story
| Document | | | ---------- | -- | | 📋 [Spécification](PD-23-specification.md) | | | 🛠️ [Plan d'implémentation](PD-23-plan.md) | | | ✅ [Critères d'acceptation](PD-23-acceptability.md) | | | 📝 **Retour d'expérience** | *(ce document)* | [← Retour à auth-identity](../PD-182-epic.md) · [↑ Index User Story](index.md)1. Résumé synthèse¶
| Critère | Valeur |
|---|---|
| User Story | PD-23 — Inscription utilisateur Zero-Knowledge (SRP-6a) |
| EPIC parent | PD-182 — AUTH |
| Date début | 2025-12-27 |
| Date acceptation | 2025-12-27 |
| Verdict final | ✅ ACCEPTÉ |
| Nb écarts identifiés | 7 (E-01 à E-07) |
| Nb écarts résolus | 7/7 |
L'implémentation de l'inscription Zero-Knowledge SRP-6a a nécessité trois passes de revue avant acceptation. Les écarts majeurs concernaient le flux d'envoi d'email (E-01), l'anti-automatisation (E-02) et la sécurisation de l'endpoint de purge (E-05). Tous les invariants de la spécification sont désormais respectés.
2. Ce qui a bien fonctionné¶
2.1 Pattern Outbox pour l'atomicité transactionnelle¶
L'adoption du pattern Outbox dès la conception a permis de garantir l'invariant 6 ("succès complet ou aucun effet") : - L'insertion dans email_outbox est transactionnelle avec la création de l'utilisateur - L'envoi d'email est décorrelé de la transaction HTTP - En cas d'échec SMTP, le retry est possible sans perte de données
Recommandation : Ce pattern doit être systématisé pour tout flux asynchrone critique.
2.2 Anti-énumération multi-couches¶
La protection contre l'énumération des emails (invariant 8) a été correctement implémentée via : - Réponse HTTP 200 identique quel que soit l'état de l'email - Timing mitigation (délai aléatoire 50-150ms) - Gestion des violations UNIQUE PostgreSQL (code 23505) - Audit différencié mais transparent côté client
2.3 Architecture SRP-6a Zero-Knowledge¶
La séparation claire entre : - Client : génération locale du salt et verifier (Argon2id + SHA3-256) - Serveur : stockage uniquement des artefacts vérifiables (srpSalt, srpVerifier, srpVersion)
Le mot de passe n'est jamais transmis, conformément à l'invariant 1.
2.4 Structure documentaire spec/plan/accept/rex¶
Le cycle documentaire en 4 phases a permis : - Une spécification contractuelle claire avant implémentation - Un plan d'implémentation avec mapping explicite invariants → mécanismes - Une revue d'acceptabilité systématique identifiant les écarts - Ce REX pour capitaliser les apprentissages
3. Points de vigilance¶
3.1 E-01 — Flux email incomplet (BLOQUANT)¶
Constat initial : Le plan mentionnait l'envoi d'email (§2.1 étape 4), mais l'implémentation contenait un // TODO: Email envoi sans consumer de la table outbox.
Cause racine : Dépendance inter-specs mal gérée. L'US "Validation email" était marquée "à créer" sans blocage formel.
Résolution : - Création du module EmailModule avec worker EmailSenderService - Transport SMTP TLS via Infomaniak (nodemailer) - Credentials via VaultService ou variables d'environnement - Template validation_email générant le lien /validate-email?token=...
Impact : +1 module, +3 services, configuration SMTP ajoutée.
3.2 E-02 — Rate limiting mémoire (BLOQUANT)¶
Constat initial : RateLimitGuard utilisait un Map en mémoire, incompatible avec un déploiement multi-instances.
Cause racine : Raccourci d'implémentation pour un POC, non signalé comme dette technique.
Résolution : - Création de RateLimitService avec stockage Redis - Pattern identique à SrpSessionStoreService (INCR + EXPIRE atomiques) - Ajout de CaptchaService (Cloudflare Turnstile) pour les seuils soft/hard - Configuration via rate-limit.config.ts et captcha.config.ts
Impact : Dépendance Redis obligatoire, intégration Turnstile.
3.3 E-05 — Endpoint purge non sécurisé (MAJEUR)¶
Constat initial : POST /auth/purge/expired était exposé sans authentification, avec un // TODO: AdminAuthGuard.
Cause racine : Guard prévu dans le plan (§9.1) mais non implémenté dans la phase initiale.
Résolution : - Création de InternalApiGuard (clé API X-Internal-Api-Key + réseaux autorisés) - Application au AccountPurgeController - Audit des accès refusés
Impact : Configuration clé API requise en production.
3.4 E-06 — Race condition UNIQUE non gérée (MINEUR)¶
Constat initial : Une inscription concurrente sur le même email déclenchait une erreur 500 (violation UNIQUE) au lieu d'une réponse anti-énumération.
Cause racine : Le cas de course n'était pas explicitement spécifié (timing très serré entre deux requêtes).
Résolution : - Rattrapage du code PostgreSQL 23505 dans le catch - Même traitement que l'email existant (timing mitigation + audit + HTTP 200)
Impact : Robustesse accrue, comportement déterministe.
4. Leçons apprises¶
4.1 Spécification des dépendances inter-specs¶
| Avant | Après |
|---|---|
| "US Validation email (à créer)" dans le plan | Dépendance bloquante explicite avec critères de démarrage |
Action : Toute dépendance "à créer" doit être soit : - Implémentée avant (blocage formel) - Stub minimal acceptable pour la revue d'acceptabilité
4.2 TODO = dette technique = ticket¶
Les commentaires // TODO sans ticket associé ont causé les écarts E-01 et E-05.
Action : Tout // TODO doit référencer un ticket (ex: // TODO(PD-XXX): ...) et être traqué dans le backlog.
4.3 Stockage distribué par défaut¶
Le rate limiting en mémoire a fonctionné en développement mais était incompatible avec la production.
Action : Pour tout stockage d'état serveur, préférer Redis/PostgreSQL dès le départ. Le fallback mémoire n'est acceptable que s'il est explicitement documenté comme limitation de développement.
4.4 Edge cases dans la matrice d'erreurs¶
La race condition UNIQUE n'était pas dans la matrice erreurs → réponses du plan (§6.1).
Action : Ajouter systématiquement les cas de concurrence dans l'analyse des erreurs : - Violation UNIQUE/FK - Deadlocks transactionnels - Timeouts réseau
4.5 Tests alignés sur la spécification¶
Les tests auth.controller.spec.ts utilisaient l'ancienne API (password au lieu de SRP), causant l'écart E-07.
Action : Les tests doivent être écrits/mis à jour en même temps que la spécification, pas après l'implémentation.
5. Impact sur le processus ProbatioVault¶
5.1 Checklist pré-implémentation (ajout)¶
- Toutes les US dépendantes sont implémentées ou ont un stub acceptable
- Aucun
// TODOsans ticket associé - Matrice erreurs inclut les cas de concurrence
- Stockage d'état utilise Redis/PostgreSQL (pas de Map mémoire)
5.2 Checklist revue d'acceptabilité (ajout)¶
- Recherche de
// TODOdans les fichiers modifiés - Vérification des endpoints protégés (guards appliqués, pas de TODO)
- Tests unitaires alignés sur le DTO de la spécification
5.3 Invariants transverses confirmés¶
Cette implémentation a validé l'applicabilité des invariants suivants sur un cas concret :
| Invariant | Mécanisme vérifié |
|---|---|
| INV-1 (mot de passe jamais transmis) | SRP-6a, rejet payload password |
| INV-6 (atomicité) | Transaction PostgreSQL + pattern Outbox |
| INV-7 (traçabilité sans PII) | SHA256(email) dans audit, ValidationErrorFilter |
| INV-8 (anti-énumération) | Réponse identique + timing + gestion UNIQUE |
| INV-11 (anti-automatisation) | Rate limiting Redis + CAPTCHA Turnstile |
| INV-12 (RGPD) | Purge comptes + traces, hash PII |
6. Métriques¶
| Métrique | Valeur |
|---|---|
| Nombre de passes de revue | 3 |
| Écarts BLOQUANTS | 2 (E-01, E-02) |
| Écarts MAJEURS | 3 (E-03, E-04, E-05) |
| Écarts MINEURS | 2 (E-06, E-07) |
| Fichiers créés | ~15 (module email, guards, services, configs, tests) |
| Fichiers modifiés | ~10 (auth.service, auth.controller, app.module, etc.) |
| Couverture de branches finale | 80.06% (seuil: 80%) |
7. Références¶
- PD-23-specification.md — Spécification canonique
- PD-23-plan.md — Plan d'implémentation
- PD-23-acceptability.md — Revue d'acceptabilité
- PD-182-epic.md — EPIC AUTH parent
Fin du retour d'expérience PD-23.