Missions RBAC — Détail des deux missions d'implémentation¶
Créé le 2026-06-14
Missions issues de l'analyse20260614_1624_analyse-modules-rbac.md
Vue d'ensemble¶
Le RBAC est implémenté en 2 missions séquentielles (lancer 1 avant 2).
Les missions DDD use cases / repositories (0641-0646) sont indépendantes et peuvent tourner en parallèle.
20260614_1627_rbac_schema_seed → Phase données (schéma + seed)
↓ (dépendance : Prisma client régénéré)
20260614_1628_rbac_middlewares_routes → Phase applicative (middlewares + routes)
Mission 1 — 20260614_1627_rbac_schema_seed¶
Titre : RBAC Phase 1 — Schéma Prisma + Seed (Permission + ProfilPermission + 5 profils)
Dossier : .claude/missions/20260614_1627_rbac_schema_seed/
Ce que cette mission fait¶
1. Schéma Prisma — 2 nouveaux modèles (ajout pur)¶
model Permission {
id String @id @default(cuid())
code String @unique // ex: "ENFANT_CREER"
libelle String
description String?
module String // ex: "ENFANTS", "PILOTAGE", "ADMIN"
profils ProfilPermission[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("permissions")
}
model ProfilPermission {
profilId String
permissionId String
profil ProfilUtilisateur @relation(fields: [profilId], references: [id])
permission Permission @relation(fields: [permissionId], references: [id])
@@id([profilId, permissionId])
@@map("profils_permissions")
}
ProfilUtilisateur reçoit le champ inverse : permissions ProfilPermission[]
Aucune table existante n'est modifiée. Exécution via prisma db push.
2. Seed — 27 permissions créées¶
| Module | Codes |
|---|---|
| ENFANTS | ENFANT_LIRE, ENFANT_CREER, ENFANT_MODIFIER, ENFANT_SUPPRIMER, ENFANT_VALIDER |
| ACCOMPAGNEMENT | ACCOMPAGNEMENT_LIRE, ACCOMPAGNEMENT_CREER, SUIVI_CREER, CAS_ORIENTE_CREER |
| ACTEURS | ACTEUR_LIRE, ACTEUR_CREER, ACTEUR_MODIFIER |
| FORMATIONS | FORMATION_LIRE, FORMATION_CREER, SUPPORT_GERER |
| INSERTION | INSERTION_LIRE, INSERTION_CREER, INSERTION_MODIFIER |
| MOBILISATION | MOBILISATION_LIRE, COMITE_GERER, CONCERTATION_GERER |
| PILOTAGE | INDICATEUR_LIRE, INDICATEUR_SAISIR, ETUDE_LIRE, ETUDE_CREER, RAPPORT_EXPORTER |
| ADMIN | UTILISATEUR_GERER, REFERENTIEL_MODIFIER |
| AUDIT | AUDIT_LIRE |
| SYSTEM | ACCES_TOUTES_COMMUNES |
3. Seed — 5 profils officiels + liaisons¶
| Profil | Permissions clés |
|---|---|
AGENT_TERRAIN |
ENFANT_LIRE/CREER/MODIFIER, ACCOMPAGNEMENT_CREER, SUIVI_CREER, CAS_ORIENTE_CREER, ACTEUR_*, FORMATION_LIRE/CREER, INSERTION_LIRE/CREER |
MEAL_COMMUNAL |
Tout AGENT_TERRAIN + ENFANT_VALIDER, INDICATEUR_LIRE/SAISIR, ETUDE_LIRE/CREER, MOBILISATION_LIRE, RAPPORT_EXPORTER, SUPPORT_GERER |
MEAL_NATIONAL |
ENFANT_LIRE/VALIDER, ACCOMPAGNEMENT_LIRE, ACTEUR_LIRE, FORMATION_LIRE, INSERTION_LIRE/MODIFIER, MOBILISATION_LIRE, COMITE_GERER, CONCERTATION_GERER, INDICATEUR_, ETUDE_LIRE, RAPPORT_EXPORTER, ACCES_TOUTES_COMMUNES* |
CHARGE_PROJET |
ENFANT_LIRE, ACCOMPAGNEMENT_LIRE, ACTEUR_LIRE, FORMATION_LIRE, INSERTION_LIRE, MOBILISATION_LIRE, INDICATEUR_LIRE, ETUDE_LIRE, RAPPORT_EXPORTER, ACCES_TOUTES_COMMUNES |
ADMIN |
Toutes les permissions |
Note : les anciens profils du seed (SUPER_ADMIN, COORDINATEUR_LOCAL, FOCAL_POINT_DATA) sont conservés via upsert. Les 5 nouveaux s'ajoutent sans suppression.
Étapes de la mission¶
- Lire schéma + seed actuels
- Modifier
schema.prisma(2 nouveaux modèles + champ inverse) prisma db pushprisma generate- Modifier
seed.ts(permissions + profils + liaisons) - Exécuter le seed
- Vérifier en base
- Validation TypeScript
Mission 2 — 20260614_1628_rbac_middlewares_routes¶
Titre : RBAC Phase 2 — Middlewares authorize + scopeCommune + branchement routes
Dossier : .claude/missions/20260614_1628_rbac_middlewares_routes/
Prérequis : Mission 1 terminée (Prisma client régénéré avec Permission + ProfilPermission)
Ce que cette mission fait¶
1. Types étendus¶
UtilisateurAuthentifie (RequeteAuthentifiee.ts) reçoit 2 nouveaux champs :
export interface UtilisateurAuthentifie {
id: string;
email: string;
role: string;
sessionId?: string;
has2FAEnabled?: boolean;
permissionCodes: string[]; // NOUVEAU — chargé depuis DB
communeCode: string | null; // NOUVEAU — commune d'affectation
}
RequeteAuthentifiee reçoit :
communeFilter?: Record<string, unknown>; // posé par scopeCommune
RouteConfig reçoit :
permission?: string; // code permission requis, ex: 'ENFANT_CREER'
2. authentificationMiddleware modifié¶
Après la validation JWT (étape existante), chargement des permissions depuis la DB :
const utilisateurDB = await prisma.utilisateur.findUnique({
where: { id: utilisateur.id },
include: {
profil: {
include: { permissions: { include: { permission: true } } }
}
}
});
req.utilisateur!.permissionCodes =
utilisateurDB?.profil?.permissions?.map(pp => pp.permission.code) ?? [];
req.utilisateur!.communeCode = utilisateurDB?.communeCode ?? null;
La vérification rolesAutorises existante est conservée (backward-compatible).
3. Nouveaux middlewares¶
autoriser.middleware.ts :
export const autoriser = (...permissionsRequises: string[]) =>
(req: RequeteAuthentifiee, res: Response, next: NextFunction): void => {
const codes = req.utilisateur?.permissionCodes ?? [];
const ok = permissionsRequises.every(p => codes.includes(p));
if (!ok) {
res.status(403).json({
succes: false,
code: 'ACCES_REFUSE',
message: 'Droits insuffisants pour cette action',
timestamp: new Date().toISOString()
});
return;
}
next();
};
scopeCommune.middleware.ts :
export const scopeCommune = (req: RequeteAuthentifiee, res: Response, next: NextFunction): void => {
const codes = req.utilisateur?.permissionCodes ?? [];
const aAccesGlobal = codes.includes('ACCES_TOUTES_COMMUNES');
req.communeFilter = aAccesGlobal
? {}
: { communeCode: req.utilisateur?.communeCode };
next();
};
4. GestionnaireModulaire — branchement automatique¶
Dans creerHandler(), après l'application de authMiddleware :
if (route.permission) {
handlers.push(autoriser(route.permission));
}
5. Mapping routes → permissions¶
| Module | Action | Permission |
|---|---|---|
| Enfants | lister, obtenir | ENFANT_LIRE |
| Enfants | creer | ENFANT_CREER |
| Enfants | modifier | ENFANT_MODIFIER |
| Enfants | supprimer | ENFANT_SUPPRIMER |
| Accompagnements | lister, obtenir | ACCOMPAGNEMENT_LIRE |
| Accompagnements | creer, ouvrir | ACCOMPAGNEMENT_CREER |
| Accompagnements | suivi | SUIVI_CREER |
| MaitresCoraniques, Tuteurs, Parents | lister, obtenir | ACTEUR_LIRE |
| MaitresCoraniques, Tuteurs, Parents | creer | ACTEUR_CREER |
| MaitresCoraniques, Tuteurs, Parents | modifier | ACTEUR_MODIFIER |
| Formations | lister, obtenir | FORMATION_LIRE |
| Formations | creer, modifier | FORMATION_CREER |
| Evaluations | lire | ENFANT_LIRE |
| Evaluations | saisir | ENFANT_MODIFIER |
| Rapports | tableaux de bord | INDICATEUR_LIRE |
| Rapports | exports | RAPPORT_EXPORTER |
| Referentiels | lecture | ENFANT_LIRE |
| Referentiels | écriture | REFERENTIEL_MODIFIER |
| Administration | gestion | UTILISATEUR_GERER |
| Administration | audit | AUDIT_LIRE |
| ImportExcel | import | REFERENTIEL_MODIFIER |
| Synchronisation | sync | REFERENTIEL_MODIFIER |
| Auth | connexion, inscription | isPublic: true (inchangé) |
Étapes de la mission¶
- Lecture des fichiers existants (middleware, types, routes)
- Étendre
UtilisateurAuthentifie+RequeteAuthentifiee - Trouver le chemin Prisma dans l'infrastructure partagée
- Modifier
authentificationMiddleware - Créer
autoriser.middleware.ts - Créer
scopeCommune.middleware.ts - Étendre
RouteConfigavecpermission? - Modifier
GestionnaireModulaire.creerHandler() - Mettre à jour routes Enfants, Accompagnements, Évaluations, Acteurs terrain
- Mettre à jour routes Formations, Rapports, Referentiels, Administration, ImportExcel, Synchronisation
- Validation TypeScript
- Corrections résiduelles
Architecture résultante¶
Requête HTTP
↓
authentificationMiddleware
→ vérifie JWT (existant)
→ vérifie blacklist Redis (existant)
→ charge permissionCodes[] depuis DB (NOUVEAU)
→ charge communeCode depuis DB (NOUVEAU)
↓
autoriser('ENFANT_CREER') ← automatique via RouteConfig.permission
→ vérifie permissionCodes.includes('ENFANT_CREER')
→ 403 si absent
↓
Controller / Handler
→ scopeCommune (optionnel, appelé manuellement si besoin)
→ req.communeFilter = {} | { communeCode: '...' }
↓
Repository Prisma
→ prisma.enfant.findMany({ where: { ...req.communeFilter } })
Principe de défense en profondeur¶
| Niveau | Mécanisme | Rôle |
|---|---|---|
| Backend — Auth | authentificationMiddleware |
Vérifie JWT + charge permissions |
| Backend — Perm | autoriser(permission) |
Bloque si permission manquante (403) |
| Backend — Data | scopeCommune + Prisma where |
Isole les données par commune |
| Frontend | usePermission() hook |
UX — masque les sections non accessibles |