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 SECURITYavec policies SELECT/UPDATE/DELETE - Trigger updated_at : fonction
vault_secure.update_timestamp()opérationnelle - Index email :
users_email_idxpour 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_idpour 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 | FAUX — USING(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¶
-
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.
-
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. -
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.
-
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.
-
TypeORM subscribers ont des limites —
afterLoadne 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.