Journal en direct — audit RBAC backend vs frontend → matrice des permissions¶
Contexte¶
Bernard demande une matrice des permissions couvrant toute l'application. Méthode imposée :
1. Analyser d'abord le mécanisme RBAC réel du backend (source de vérité technique).
2. Comparer avec ce qui est implémenté au frontend.
3. S'il y a des écarts, les expliquer et en discuter avec Bernard avant toute correction —
pas de correction silencieuse.
4. Produire ensuite le document final (matrice des permissions, .md horodaté dans
Documents/).
Ce journal est tenu en direct, section par section, horodatage réel à chaque étape (voir règle générale confirmée cette session — non limitée aux tests E2E).
Prior art identifié avant de commencer (à vérifier, ne pas prendre pour argent comptant —
daté du 2026-06-23/24/26, soit ~2-3 semaines avant aujourd'hui, et plusieurs correctifs de scope
commune ont eu lieu depuis, cf. commits récents fix(accompagnements), fix(insertion,formations)) :
- Documents/20260623_1808_matrice-permissions-profils.md — matrice 8 profils × 32 permissions,
source déclarée = requête DB directe, seed.ts explicitement noté comme désynchronisé.
- Documents/20260623_1808_liste-permissions.md — liste des 32 permissions par module.
- debug/tests-rbac-conformite.md — 3 bugs réels trouvés par tests empiriques : (v2) PSYCHOLOGUE/
ASSISTANT_SOCIAL manquaient à tort INDICATEUR_LIRE/SAISIR ; (v3) bug de scope de données
(filtrage par idUtilisateur au lieu du scope propre au rôle) sur les rapports — distinct du RBAC
permissions lui-même. Point de vigilance non résolu : BAILLEUR a RAPPORT_EXPORTER mais scope
personnel seulement (agrégats nationaux attendus ou non ? jamais tranché).
Egalement en mémoire : project_rbac_validation_meal.md (MEAL = contrôle qualité, jamais
création) et les 5 derniers commits du repo montrent des corrections récentes de scope commune sur
accompagnements/insertion/formations — donc le périmètre communal a bougé depuis le 2026-06-24.
2026-07-11 11:30 — Démarrage¶
Tâches créées (TaskCreate #1 à #5). Début de l'analyse backend : schema Prisma (modèles Role/ Permission), middleware d'autorisation, seed.ts, routes par module.
2026-07-11 11:37 — Backend et frontend explorés (2 agents Explore en parallèle)¶
Backend — faits extraits¶
- 16 modules (
src/Modules/), pas 18.Permission/ProfilPermissionen base, pas d'enum en dur. seed.tsactuel = 37 permissions, 8 profils (dernier commit03638be, 2026-07-11, "MEAL n'a plus de permissions de création"). Fiable et à jour, contrairement aux notes du 2026-06-23.- Middleware
autoriser(...codes): AND strict surreq.utilisateur.permissionCodes(chargés en DB à chaque requête). Câblage automatique viaGestionnaireModulaire— champpermissiondéclaré directement dans chaque*.module.routes.ts, pas de câblage manuel. - Aucun
req.communeFiltern'existe (contrairement à l'architecture documentée danssysteme-rbac-permissions.md). Le scope réel passe parUtilisateur.communeCodebrut, répliqué manuellement dans chaque controller (getCommuneCode: () => utilisateur?.communeCode). ACCES_TOUTES_COMMUNES(permission) n'est lu nulle part dans le code — seul un nom de rôle identique existe en dur dansServiceAutorisationDonnees.ROLES_ACCES_TOUTES_COMMUNES, utilisé seulement dans 4 fichiers du module Pilotage. Partout ailleurs (Enfants, Accompagnements, Mobilisation, Insertion, Formations, MaitresCoraniques, Tuteurs, Parents, Rapports), un profil MEAL_NATIONAL/CHARGE_PROJET/BAILLEUR/ADMIN ne voit "toutes les communes" que si son propreUtilisateur.communeCodeestnullen base — pas garanti par le code, dépend de la donnée.- Permissions du seed jamais vérifiées par aucune route (dead) :
ENFANT_SUPPRIMER,CAS_ORIENTE_CREER,INSERTION_LIRE,INSERTION_CREER,INSERTION_MODIFIER. - Le module Insertion (CRUD) est en réalité gouverné par
ACTEUR_LIRE/CREER/MODIFIER, pas par les permissionsINSERTION_*— pattern partagé aussi par MaitresCoraniques/Tuteurs/Parents (4 entités distinctes sous la même permissionACTEUR_*). - Fichiers de routes Auth "main" (7 fichiers) et
auditReferentiel.routes.ts: orphelins, jamais montés (dead code, sans impact car unauth.module.routes.tsactif à 36 routes existe déjà).
Frontend — faits extraits¶
- Aucun
permissionCodes[], aucun hookusePermission, aucun composant de garde par permission n'existe — contredit l'architecture documentée ("frontend miroir viausePermission('X')"). Le frontend ne connaît QUEuser.role(string libre). ProtectedRoutene vérifie querequiredRole(liste de strings, égalité stricte).- Constantes
ROLES_*codées en dur et dupliquées sans source commune dans ≥9 fichiers (Router.tsx,Sidebar.tsx,EnfantsPage.tsx,FormationsPage.tsx,MobilisationPage.tsx,PilotagePage.tsx,MaitresCoraniqueePage.tsx,MonEquipePage.tsx,ValidationActions.tsx). ROLES_AVEC_PILOTAGEetROLES_GLOBAL(mentionnées dans des notes anciennes) n'existent plus dans le code actuel.
Écarts concrets identifiés (à discuter avec Bernard avant correction)¶
- BAILLEUR absent de tous les tableaux
ROLES_*du frontend (ROLES_COORD_ET_ADMIN,ROLES_TERRAIN_ET_COORD) alors que son paquet de permissions backend est identique à CHARGE_PROJET (ENFANT_LIRE, ACCOMPAGNEMENT_LIRE, ACTEUR_LIRE, FORMATION_LIRE, INSERTION_LIRE, MOBILISATION_LIRE, INDICATEUR_LIRE, ETUDE_LIRE, RAPPORT_EXPORTER, ACCES_TOUTES_COMMUNES). Résultat : un utilisateur BAILLEUR est aujourd'hui redirigé hors de Pilotage & Indicateurs et de Sensibilisation/Mobilisation (section menu masquée + route qui renvoie vers le tableau de bord), alors que CHARGE_PROJET (mêmes permissions) y accède normalement. ACCES_TOUTES_COMMUNESscope-dépendant, pas code-garanti hors du module Pilotage — risque silencieux si un compte MEAL_NATIONAL/CHARGE_PROJET/BAILLEUR/ADMIN a uncommuneCodenon-null en base.- Rôles fantômes côté frontend (
COORDINATEUR_LOCAL,FOCAL_POINT_DATA,DIRECTION) dansROLES_COORD_ET_ADMIN/ROLES_TERRAIN_ET_COORD— n'existent dans aucun des 8 profils seedés backend. Sans effet actuel (personne n'a ce rôle) mais signale du code non nettoyé. - Module Formations absent du menu latéral (
Sidebar.tsx) — route accessible par URL directe pour tout rôle, mais aucune entrée de navigation ne permet d'y accéder normalement. - Permissions mortes déjà listées côté backend (
ENFANT_SUPPRIMER,CAS_ORIENTE_CREER,INSERTION_LIRE/CREER/MODIFIER) — dette de nettoyage, sans impact fonctionnel actuel.
Point positif confirmé : les gates de boutons déjà en place (création Enfants/Formations/ Mobilisation, validation MEAL, calcul indicateurs, création étude) sont bien cohérents avec les permissions backend actuelles — ce sont les correctifs des sessions du 2026-07-10/11.
Prochaine étape : présenter ces écarts à Bernard avant toute correction/document final.
2026-07-11 12:10 — Décision de Bernard : chantier A (alignement frontend permission-based)¶
Bernard choisit d'aligner le frontend sur le design d'origine plutôt que d'officialiser le
role-based. Question posée en retour : le chantier donnera-t-il un alignement réel ? Réponse :
oui à condition (1) de câbler chaque gate frontend sur la permission réellement vérifiée par la
route backend correspondante (pas déduite du nom du module — ex. Insertion est gouverné par
ACTEUR_CREER, pas INSERTION_CREER) et (2) de corriger ACCES_TOUTES_COMMUNES côté backend en
même temps, sinon le frontend ne ferait que refléter fidèlement un mécanisme déjà incohérent.
Bernard accepte d'inclure le scope commune dans le chantier A. Document pédagogique
Documents/20260711_1210_explication-rbac-vs-permission-based.md écrit à sa demande (RBAC à 3
niveaux NIST, tableau comparatif, complété ensuite d'un paragraphe sur le NIST lui-même).
2026-07-11 12:32 — Chantier A, partie backend terminée¶
Scope commune canonicalisé (tâche #7) :
- IServiceAutorisationDonnees.UtilisateurAutorisation : ajout de permissionCodes?: readonly
string[] (optionnel, pour ne pas casser les ~25 fichiers qui n'utilisent que peutModifier/
peutValider, lesquels ne dépendent pas du scope commune).
- ServiceAutorisationDonnees.construireScopeCommune() : remplace la liste de rôles codée en dur
ROLES_ACCES_TOUTES_COMMUNES par une vérification réelle de la permission
ACCES_TOUTES_COMMUNES dans permissionCodes.
- IHttpContext : ajout de getPermissionCodes(): string[].
- 17 controllers de module patchés par script Python (pattern uniforme) : getCommuneCode
utilise désormais serviceAutorisationDonnees.construireScopeCommune(...) au lieu du
communeCode brut ; hasPermission (qui lisait un champ .permissions inexistant — bug mort
depuis toujours, jamais appelé nulle part) corrigé pour lire le vrai permissionCodes.
- 4 fichiers Pilotage (2 handlers HTTP + 2 use cases avec DTO role/communeCode) : ajout du
paramètre permissionCodes de bout en bout (DTO + appel).
- ExpressHttpContext.ts (adapter de la stack Auth orpheline identifiée dans l'audit) : même
correctif appliqué pour rester compilable — toujours dead code (aucun routeur ne l'utilise),
mais découvert car tsc --noEmit vérifie tout le projet.
Exposition permissionCodes[] au frontend (tâche #8) — découverte en cours de route : le
champ RoleInfo.permissions de l'entité domaine Utilisateur était codé en dur à [] dans
UtilisateurRepositoryPrisma.convertirVersEntite() depuis toujours (vestige d'un ancien modèle de
permissions {ressource, action, slug} jamais branché sur la vraie table Permission). Corrigé à
la racine : INCLUDE_ROLE inclut maintenant profil.permissions.permission, et la conversion
mappe permission.code → slug. Ce fix centralisé profite aux 13 endroits du repository qui
chargent un Utilisateur. permissionCodes ajouté ensuite à :
- ResultatConnexion.utilisateur (login direct) + ConnexionHandler.
- ResultatVerifier2FA.utilisateur (connexion via 2FA) + Verifier2FAHandler — au passage,
communeCode était lui aussi absent de ce chemin (bug préexistant, non lié à ce chantier
mais bloquant pour lui) : un utilisateur avec 2FA activé perdait son scope commune après
vérification. Corrigé en même temps.
npx tsc --noEmit backend : 0 erreur après ces changements.
Prochaine étape : partie frontend (authStore + usePermission + ProtectedRoute + câblage
routes/menu/boutons sur les permissions exactes).
2026-07-11 13:16 — Chantier A, partie frontend terminée¶
Mécanisme générique (tâche #9) :
- dtos.ts : permissionCodes ajouté à LoginResult.utilisateur, TwoFactorResult.utilisateur,
BackupCodeVerificationResult.utilisateur (les 3 chemins de connexion réels — login direct,
2FA, code de secours) et à UserProfile.
- User.ts (entité domaine) : permissionCodes propagé partout (constructeur, fromProfile,
fromLoginResult, toProfile), + aPermission()/auMoinsUnePermission().
- useAuth.ts : usePermission(codes, mode) (hook, sémantique 'any'/'all' miroir de autoriser
backend) + possedePermission() (fonction pure, pour filtrer une liste sans violer les Rules of
Hooks) + usePermissionCodes(). Exportés depuis Modules/auth/index.ts.
- ProtectedRoute.tsx : nouvelle prop requiredPermission (garde requiredRole en parallèle,
migration progressive).
Câblage routes/menu/boutons (tâche #10) — remplacement des tableaux ROLES_* dupliqués par
la permission réellement vérifiée par la route backend correspondante :
- Router.tsx : Sensibilisation→SUPPORT_GERER, Mobilisation→MOBILISATION_LIRE,
Pilotage→INDICATEUR_LIRE, Mon équipe→EQUIPE_GERER, Référencements reçus→ACCOMPAGNEMENT_LIRE,
Admin→[UTILISATEUR_GERER, REFERENTIEL_MODIFIER, AUDIT_LIRE] (any). Corrige le bug BAILLEUR
(mêmes permissions que CHARGE_PROJET, désormais accès identique à Pilotage/Mobilisation/
Référencements reçus).
- Sidebar.tsx : mêmes permissions appliquées aux sections/items du menu, + /educateurs-pairs
gaté sur SUPPORT_GERER (oubli constaté lors de l'audit — visible pour tous alors que la route
backend l'exige).
- Boutons : EnfantsPage (créer→ENFANT_CREER), FormationsPage (créer→FORMATION_CREER),
MobilisationPage (gérer, 10 sous-composants→[COMITE_GERER, CONCERTATION_GERER] any),
PilotagePage (créer étude→ETUDE_CREER, calculer→INDICATEUR_CALCULER),
ValidationActions.tsx (composant transverse→union des 7 permissions *_VALIDER, faute de
connaître ici l'entité validée).
Effet de bord corrigé en cascade : le filtre d'onglets interne de PilotagePage
(ROLES_MEAL_ARRAY, sans BAILLEUR) aurait laissé BAILLEUR atteindre une page Pilotage à 0 onglet
visible une fois la route elle-même corrigée — pire que le bug initial (section invisible vs
section visible mais vide). Supprimé : les 5 onglets sont désormais toujours visibles une fois la
section atteinte, cohérent avec le fait que INDICATEUR_LIRE/ETUDE_LIRE sont universels côté
backend. Effet de bord assumé, à noter pour Bernard : PSYCHOLOGUE/ASSISTANT_SOCIAL peuvent
désormais aussi atteindre la section Pilotage (ils ont INDICATEUR_LIRE en base, comme corrigé dans
ProjetESM/debug/tests-rbac-conformite v2) — auparavant bloqués par l'ancien rôle-gate
ROLES_COORD_ET_ADMIN qui les excluait à tort. Pas un ajout arbitraire : conforme aux permissions
déjà en base, mais c'est un changement de périmètre visible qui mérite d'être signalé.
Exceptions volontairement laissées en l'état (pas de permission backend correspondante
clairement identifiable) : MaitresCoraniqueePage.tsx et MonEquipePage.tsx gardent leur toggle
ROLES_NATIONAUX (affichage d'agrégats nationaux, pas un droit d'action CRUD) — non migré faute
de permission dédiée équivalente côté backend.
npx tsc --noEmit frontend : 0 erreur après tous ces changements.
Prochaine étape : déploiement backend + vérification réelle (au minimum BAILLEUR sur Pilotage/ Mobilisation), puis discussion des 3 écarts restants (rôles fantômes, permissions mortes, menu Formations manquant) avant le document final.
2026-07-11 13:20 — Chantier A déployé et vérifié en réel¶
Backend + frontend déployés via deploy.sh --config projetesm.env --target backend|frontend
(scripts standards, pas de build/run manuel). Vérification par appels HTTP réels (login + Bearer
token, méthode déjà utilisée avec succès le 2026-07-11 matin pour le scope commune) :
POST /api/auth/connexionavectest.bailleur@projetesm.test→ réponse contient bienpermissionCodes: [ENFANT_LIRE, ACCOMPAGNEMENT_LIRE, ACTEUR_LIRE, FORMATION_LIRE, INSERTION_LIRE, MOBILISATION_LIRE, INDICATEUR_LIRE, ETUDE_LIRE, RAPPORT_EXPORTER, ACCES_TOUTES_COMMUNES]etcommuneCode: null.GET /api/v1/pilotage/indicateursavec le token BAILLEUR → 200 OK (45 indicateurs).GET /api/v1/mobilisation/comitesavec le token BAILLEUR → 200 OK.- Ces deux endpoints étaient déjà accessibles côté API avant ce chantier (le blocage était
uniquement frontend) — le test confirme que le nouveau scope commune basé sur
ACCES_TOUTES_COMMUNES(permission) fonctionne et n'a rien cassé, pas qu'il change ce cas précis (communeCodeétait déjànullpour ce compte).
Anomalie annexe repérée, hors périmètre du chantier A : le compte de test
psychologue.test@esm.local (profil PSYCHOLOGUE) a RAPPORT_EXPORTER dans ses
permissionCodes, alors que l'audit initial (lecture de seed.ts) indiquait que seuls
MEAL_COMMUNAL/MEAL_NATIONAL/CHARGE_PROJET/BAILLEUR/ADMIN/SUPER_ADMIN devraient l'avoir. Signale
soit un seed.ts non réappliqué en base depuis un précédent ajustement manuel, soit une
imprécision de l'audit initial — à vérifier séparément, sans lien avec le mécanisme
d'alignement construit ici (qui reflète fidèlement, quoi qu'il arrive, ce qui est réellement en
base).
Chantier A clos. Reste à trancher avec Bernard, avant le document final :
1. Rôles fantômes frontend (disparus avec la suppression des tableaux ROLES_*, non réintroduits
— déjà réglé de fait par ce chantier).
2. Permissions mortes côté backend (ENFANT_SUPPRIMER, CAS_ORIENTE_CREER,
INSERTION_LIRE/CREER/MODIFIER).
3. Module Formations absent du menu latéral (route fonctionnelle, jamais liée dans Sidebar.tsx).
4. Nouveau : vérifier si psychologue.test reflète un seed désynchronisé (point ci-dessus).
2026-07-11 13:31 — Les 3 écarts traités¶
Bernard tranche les 3 points (choix recommandés retenus dans les 3 cas) :
-
ENFANT_SUPPRIMERretirée du seed — aucune route DELETE enfant n'existe ni n'est prévue (désactivation via statut, jamais suppression). Retirée de la liste des 37 permissions + nettoyage en base (même pattern que la suppression historique deINDICATEUR_SAISIR). -
Câblage Insertion/CAS_ORIENTE_CREER corrigé. Vérification d'impact faite avant modification (comme annoncé) : basculer les routes Insertion de
ACTEUR_LIRE/CREER/MODIFIERversINSERTION_LIRE/CREER/MODIFIERaurait été neutre pour LIRE/CREER (mêmes détenteurs dans le seed actuel), sauf pourINSERTION_MODIFIER, absente d'agentTerrainalors qu'ils ont bienINSERTION_CREER— un vrai trou du seed qui aurait cassé la capacité des agents de terrain à modifier leurs propres dossiers d'insertion. Corrigé en même temps :INSERTION_MODIFIERajoutée àagentTerrain, puis les 27 routes deinsertion.module.routes.tsrecâblées (ACTEUR_*→INSERTION_*), et les 3 routes cas-orientés deaccompagnements.module.routes.tsrecâblées (ACCOMPAGNEMENT_CREER→CAS_ORIENTE_CREER, neutre pour tout le monde). -
Module Formations ajouté au menu latéral (
Sidebar.tsx), nouvelle section autonome gatée surFORMATION_LIRE(permission universelle), entre Insertion et Acteurs de terrain.
Blocage inattendu rencontré en appliquant le seed : npx tsx prisma/seeds/seed.ts plantait
dès le démarrage (prisma.typeAccompagnement undefined) — le modèle Prisma TypeAccompagnement
n'existe plus depuis le commit ec7447f (2026-07-10, retrait du module Accompagnements
générique), mais le bloc de seed correspondant n'avait jamais été retiré. Conséquence
découverte en creusant : ce bloc mort bloquait seed.ts entièrement depuis le 2026-07-10 — la
section RBAC plus bas (dont le fix MEAL du 2026-07-11 matin, commit 03638be) n'a donc
probablement jamais été appliquée via ce fichier ; les permissions RBAC actuellement en base
proviennent forcément d'un autre chemin (script direct, ou modifications manuelles). Ceci explique
aussi l'anomalie psychologue.test avec RAPPORT_EXPORTER repérée à 13:20 : un ajustement
antérieur jamais nettoyé par un vrai passage de seed déclaratif. Bloc obsolète retiré (fix évident,
aucune ambiguïté possible : le modèle n'existe plus, le bloc ne pouvait plus jamais fonctionner) ;
seed.ts s'exécute maintenant de bout en bout avec succès.
Vérifications réelles post-déploiement (login + appels HTTP avec Bearer token) :
- psychologue.test : RAPPORT_EXPORTER disparu (23 permissions au lieu de 24), INSERTION_MODIFIER
présent, ENFANT_SUPPRIMER absent de tout profil.
- GET /api/v1/esm/insertion/ avec PSYCHOLOGUE → 200 (aucune régression).
- GET /api/v1/esm/insertion/ avec BAILLEUR → 200 (permission universelle, cohérent).
Backend + frontend redéployés (deploy.sh). Les 3 écarts + l'anomalie annexe + le seed cassé sont
clos. Reste : produire le document final (matrice des permissions), tâche #5.
2026-07-11 13:53 — CONCERTATION_GERER tranchée après coup¶
Bernard, en relisant la synthèse de fin de session, demande des précisions sur le point resté
ouvert CONCERTATION_GERER (signalé dans la matrice mais non discuté explicitement). Explication
donnée, puis décision : oubli confirmé, corrigé — CONCERTATION_GERER ajoutée à agentTerrain
dans seed.ts (même profil que INSERTION_MODIFIER). Seed réappliqué (nécessitait un
npx prisma generate au préalable, le client local avait de nouveau divergé). Aucun
redéploiement backend nécessaire : les permissions sont lues dynamiquement en base à chaque
requête, pas embarquées dans le code. Vérifié par appel HTTP réel : psychologue.test passe de 23
à 24 permissions, CONCERTATION_GERER présente. Matrice finale mise à jour (24/39 pour
PSYCHOLOGUE/ASSISTANT_SOCIAL).
Plus aucun point ouvert. Chantier RBAC entièrement clos : audit, chantier A, 3+1 écarts
traités, seed réparé, document final à jour, code ProjetESM commité et poussé (269a5b1).