Aller au contenu

Missions RBAC — Détail des deux missions d'implémentation

Créé le 2026-06-14
Missions issues de l'analyse 20260614_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

  1. Lire schéma + seed actuels
  2. Modifier schema.prisma (2 nouveaux modèles + champ inverse)
  3. prisma db push
  4. prisma generate
  5. Modifier seed.ts (permissions + profils + liaisons)
  6. Exécuter le seed
  7. Vérifier en base
  8. 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

  1. Lecture des fichiers existants (middleware, types, routes)
  2. Étendre UtilisateurAuthentifie + RequeteAuthentifiee
  3. Trouver le chemin Prisma dans l'infrastructure partagée
  4. Modifier authentificationMiddleware
  5. Créer autoriser.middleware.ts
  6. Créer scopeCommune.middleware.ts
  7. Étendre RouteConfig avec permission?
  8. Modifier GestionnaireModulaire.creerHandler()
  9. Mettre à jour routes Enfants, Accompagnements, Évaluations, Acteurs terrain
  10. Mettre à jour routes Formations, Rapports, Referentiels, Administration, ImportExcel, Synchronisation
  11. Validation TypeScript
  12. 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