Aller au contenu

PD-15 — Retour d'expérience


📚 Navigation User Story | Document | | | ---------- | -- | | 📋 [Spécification](PD-15-specification.md) | | | 🛠️ [Plan d'implémentation](PD-15-plan.md) | | | ✅ [Critères d'acceptation](PD-15-acceptability.md) | | | 📝 **Retour d'expérience** | *(ce document)* | [← Retour à backend-core](../PD-186-epic.md) · [↑ Index User Story](index.md)

1. Résumé exécutif

La User Story PD-15 visait à créer la table vault_secure.users avec RLS strict, compatibilité SRP-6a (salt + verifier), et isolation par utilisateur via app.current_user_id. L'implémentation fournit une migration complète avec policies RLS, trigger updated_at et index email. Cependant, 2 écarts MAJEURS sont identifiés : RLS incohérent pour les lectures (SELECT non couvert par le subscriber) et suppression impossible même pour admin (DELETE = USING(false) sans exception). Verdict : ACCEPTÉ AVEC RÉSERVES.


2. Points fluides

  • Migration structurée : pattern PRE-CHECK / MIGRATION / POST-CHECK documenté
  • Schéma vault_secure : séparation logique stricte respectée
  • Table complète : id UUID, email UNIQUE, srp_salt BYTEA, password_hash BYTEA, plan CHECK, timestamps
  • RLS activé : ENABLE ROW LEVEL SECURITY avec policies SELECT/UPDATE/DELETE
  • Trigger updated_at : fonction vault_secure.update_timestamp() opérationnelle
  • Index email : users_email_idx pour lookup SRP rapide
  • Commentaires SQL : documentation inline sur table et colonnes
  • Compatibilité SRP-6a : password_hash = verifier, jamais hash classique
  • RlsSubscriber : injection app.current_user_id pour INSERT/UPDATE/DELETE
  • Validation DTO : class-validator avec @IsEmail, @IsHexadecimal, @Length

3. Points difficiles

Difficulté Contexte
RLS SELECT non couvert TypeORM afterLoad ne peut pas modifier la query, le contexte doit être défini AVANT le SELECT
DELETE strictement bloqué USING(false) ne permet aucune exception, même pour rôle admin
Normalisation email @IsEmail valide le format mais ne lowercase/trim pas, dépend du client
down() vide Migration non réversible, choix délibéré mais limite les rollbacks

4. Hypothèses révélées tardivement

Hypothèse initiale Réalité découverte
RlsSubscriber couvre tous les événements FAUX — SELECT n'est pas interceptable avant la query dans TypeORM
DELETE policy avec exception admin FAUXUSING(false) bloque tout, nécessite rôle bypass ou policy conditionnelle
@IsEmail normalise l'email FAUX — Validation seulement, pas de transformation lowercase/trim
Login SRP fonctionne avec RLS FAUX — findOne sans contexte bloqué par policy SELECT

5. Invariants complexes à implémenter

Invariant Complexité
RLS strict par utilisateur id = current_setting('app.current_user_id')::UUID sur toutes les opérations
Contexte AVANT query SET LOCAL doit être exécuté dans la même transaction/connexion
Email unique case-insensitive Nécessite normalisation applicative ou UNIQUE sur LOWER(email)
Verifier jamais loggué Revue manuelle de tous les logs, erreurs incluses
INSERT sans RLS user Le user n'existe pas encore à l'inscription, rôle serveur requis

6. Dette technique

Dette Impact Priorité
RLS SELECT non fonctionnel Login SRP bloqué ou bypass RLS requis HAUTE
DELETE impossible pour admin Pas de suppression utilisateur possible HAUTE
Normalisation email absente Doublons fonctionnels possibles (User@x vs user@x) MOYENNE
Documentation manquante /docs/db/vault_secure/users.md non créé BASSE
down() vide Migration non réversible BASSE

7. Risques résiduels

Risque Probabilité Impact Mitigation suggérée
Login impossible avec RLS Élevée ÉLEVÉ Ajouter beforeLoad ou rôle bypass pour auth
Utilisateurs non supprimables Élevée MOYEN Policy DELETE conditionnelle avec rôle admin
Doublons email casse Moyenne MOYEN Normaliser en service ou UNIQUE LOWER(email)
Bypass RLS non audité Faible ÉLEVÉ Logger les accès via rôle bypass

8. Améliorations processus

Amélioration Bénéfice attendu
Tester RLS avec vraies queries Détecter incompatibilité SELECT avant merge
Prévoir rôle admin dans spec Clarifier suppression utilisateur dès la spec
Ajouter @Transform lowercase Normalisation email automatique dans DTO
Créer docs/ avec la migration Ne pas séparer code et documentation
Implémenter down() pour dev Permettre rollbacks en développement

9. Enseignements clés

  1. RLS SELECT est le plus complexe — Les policies SELECT s'appliquent à TOUTES les lectures, y compris le login SRP. Le subscriber TypeORM ne peut pas intercepter avant un SELECT, nécessitant soit un rôle bypass soit un middleware qui exécute SET LOCAL explicitement.

  2. USING(false) bloque tout le monde — Une policy DELETE avec USING(false) ne permet aucune exception. Pour permettre la suppression admin, il faut soit une policy conditionnelle (USING(current_setting('app.is_admin')::boolean)), soit un rôle avec BYPASSRLS.

  3. La normalisation email est applicative — class-validator @IsEmail valide le format mais ne transforme pas. Sans @Transform ou normalisation dans le service, "User@X.com" et "user@x.com" sont deux utilisateurs différents.

  4. L'inscription échappe au RLS user — L'utilisateur n'existe pas encore lors de l'INSERT. Le modèle recommandé (spec) est un rôle serveur dédié, mais cela complexifie l'architecture.

  5. TypeORM subscribers ont des limitesafterLoad ne peut pas modifier la query déjà exécutée. Pour RLS sur SELECT, il faut soit utiliser un middleware qui exécute SET LOCAL avant chaque query, soit un rôle avec permissions spécifiques.