Aller au contenu

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/ProfilPermission en base, pas d'enum en dur.
  • seed.ts actuel = 37 permissions, 8 profils (dernier commit 03638be, 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 sur req.utilisateur.permissionCodes (chargés en DB à chaque requête). Câblage automatique via GestionnaireModulaire — champ permission déclaré directement dans chaque *.module.routes.ts, pas de câblage manuel.
  • Aucun req.communeFilter n'existe (contrairement à l'architecture documentée dans systeme-rbac-permissions.md). Le scope réel passe par Utilisateur.communeCode brut, 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 dans ServiceAutorisationDonnees.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 propre Utilisateur.communeCode est null en 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 permissions INSERTION_* — pattern partagé aussi par MaitresCoraniques/Tuteurs/Parents (4 entités distinctes sous la même permission ACTEUR_*).
  • Fichiers de routes Auth "main" (7 fichiers) et auditReferentiel.routes.ts : orphelins, jamais montés (dead code, sans impact car un auth.module.routes.ts actif à 36 routes existe déjà).

Frontend — faits extraits

  • Aucun permissionCodes[], aucun hook usePermission, aucun composant de garde par permission n'existe — contredit l'architecture documentée ("frontend miroir via usePermission('X')"). Le frontend ne connaît QUE user.role (string libre).
  • ProtectedRoute ne vérifie que requiredRole (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_PILOTAGE et ROLES_GLOBAL (mentionnées dans des notes anciennes) n'existent plus dans le code actuel.

Écarts concrets identifiés (à discuter avec Bernard avant correction)

  1. 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.
  2. ACCES_TOUTES_COMMUNES scope-dépendant, pas code-garanti hors du module Pilotage — risque silencieux si un compte MEAL_NATIONAL/CHARGE_PROJET/BAILLEUR/ADMIN a un communeCode non-null en base.
  3. Rôles fantômes côté frontend (COORDINATEUR_LOCAL, FOCAL_POINT_DATA, DIRECTION) dans ROLES_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é.
  4. 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.
  5. 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.codeslug. 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/connexion avec test.bailleur@projetesm.test → réponse contient bien permissionCodes: [ENFANT_LIRE, ACCOMPAGNEMENT_LIRE, ACTEUR_LIRE, FORMATION_LIRE, INSERTION_LIRE, MOBILISATION_LIRE, INDICATEUR_LIRE, ETUDE_LIRE, RAPPORT_EXPORTER, ACCES_TOUTES_COMMUNES] et communeCode: null.
  • GET /api/v1/pilotage/indicateurs avec le token BAILLEUR → 200 OK (45 indicateurs).
  • GET /api/v1/mobilisation/comites avec 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à null pour 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) :

  1. ENFANT_SUPPRIMER retiré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 de INDICATEUR_SAISIR).

  2. Câblage Insertion/CAS_ORIENTE_CREER corrigé. Vérification d'impact faite avant modification (comme annoncé) : basculer les routes Insertion de ACTEUR_LIRE/CREER/MODIFIER vers INSERTION_LIRE/CREER/MODIFIER aurait été neutre pour LIRE/CREER (mêmes détenteurs dans le seed actuel), sauf pour INSERTION_MODIFIER, absente d'agentTerrain alors qu'ils ont bien INSERTION_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_MODIFIER ajoutée à agentTerrain, puis les 27 routes de insertion.module.routes.ts recâblées (ACTEUR_*INSERTION_*), et les 3 routes cas-orientés de accompagnements.module.routes.ts recâblées (ACCOMPAGNEMENT_CREERCAS_ORIENTE_CREER, neutre pour tout le monde).

  3. Module Formations ajouté au menu latéral (Sidebar.tsx), nouvelle section autonome gatée sur FORMATION_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).