Aller au contenu

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 // TODO sans 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 // TODO dans 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


Fin du retour d'expérience PD-23.