Sécurité, RBAC et audit
La SIM Database implémente un modèle de contrôle d’accès basé sur les rôles (RBAC) dans le schéma admin, stocke les jetons de rafraîchissement et de réinitialisation de mot de passe de façon sécurisée, et enregistre chaque opération métier dans le schéma audit.
Modèle RBAC
Section intitulée « Modèle RBAC »Tables principales
Section intitulée « Tables principales »| Table | Rôle | Colonnes clés |
|---|---|---|
admin.role |
Catalogue des rôles | id_role, code, libelle |
admin.permission |
Catalogue des permissions atomiques | id_permission, code, libelle |
admin.role_permission |
Attributions plusieurs-à-plusieurs | id_role, id_permission |
admin.utilisateur |
Comptes utilisateurs | id_utilisateur, login, mot_de_passe, id_role, id_activite_scope, id_employe, id_client, email, actif |
admin.utilisateur
Section intitulée « admin.utilisateur »Un utilisateur est un compte unique. Il est lié à :
- un
admin.role(obligatoire) - optionnellement un
rh.employe(compte employé) - optionnellement un
client.client(compte portail résident/client) - optionnellement une
finances.activite(périmètre caisse/activité) - optionnellement une
finances.caisse(affectation de tiroir-caisse)
Des index garantissent l’unicité :
ux_utilisateur_id_employe— un compte par employéux_utilisateur_id_client— un compte par client (portail résident)ux_utilisateur_email— l’email optionnel est unique quand renseigné
Nommage des permissions
Section intitulée « Nommage des permissions »Les permissions suivent le patron :
<MODULE>.<ACTION>- Actions :
VOIR,CREER,MODIFIER,SUPPRIMER - Modules :
CORE,CLIENT,RESIDENCE,MARCHANDISE,PRESSING,RESTAURANT,SALLE_FETE,FACTURATION,FINANCES,RH,ADMIN,AUDIT
Des permissions spéciales étendent le CRUD avec des actions de cycle de vie :
| Permission | Rôle |
|---|---|
RESIDENT.VOIR |
Accès en lecture au portail résident |
RAPPORTS.VOIR |
Reporting à périmètre d’activité |
DEPENSE.* |
Gestion des dépenses |
ABONNEMENT.* |
Catalogue des catégories d’abonnement |
PRESSING.TRAITER / MARQUER_PRET / RETIRER / ANNULER / VALIDER |
Cycle de vie pressing |
RESTAURANT.VALIDER |
Valider une commande restaurant |
RESIDENCE.VALIDER / ENCAISSER |
Valider un contrat / encaisser un loyer |
FACTURATION.VALIDER |
Valider une facture |
RH.VALIDER |
Valider la paie |
SALLE_FETE.VALIDER |
Valider une réservation de salle |
FINANCES.ENCAISSER |
Encaissement espèces/paiement |
*.SUPERVISER |
Accès au tableau de bord superviseur par module |
SIGNALEMENT.VOIR / CREER / MODIFIER |
Signalement d’incidents |
Rôles seedés
Section intitulée « Rôles seedés »| Code | Libellé | Notes |
|---|---|---|
DIRIGEANT |
Dirigeant | Toutes les permissions sauf RESIDENT.VOIR |
ADMINISTRATEUR |
Administrateur | Toutes les permissions sauf RESIDENT.VOIR |
CAISSIER |
Caissier | Caisse, paiements, factures, clients |
RESPONSABLE_RESIDENCE |
Responsable résidence | Logement et locataires |
RESPONSABLE_MAGASIN |
Responsable magasin | Boutique |
RESPONSABLE_PRESSING |
Responsable pressing | Pressing |
RESPONSABLE_RESTAURANT |
Responsable restaurant | Restaurant |
RESPONSABLE_SALLE_FETE |
Responsable salle de fête | Salle de réception |
RH |
Ressources humaines | Paie |
EMPLOYE |
Employé | Lecture minimale |
RESIDENT |
Résident | Portail résident |
CLIENT |
Client | Compte public auto-inscrit |
Les rôles fonctionnels pour le personnel de terrain (ex. RESTAURANT_SERVEUR, PRESSING_RECEPTIONNISTE, RESIDENCE_CAISSIER, MARCHANDISE_VENDEUR) ne reçoivent que les permissions nécessaires à une étape précise du workflow.
Règles spéciales des rôles
Section intitulée « Règles spéciales des rôles »ADMINISTRATEURetDIRIGEANTobtiennent toutes les permissions via unCROSS JOINsuradmin.permission.RESIDENT.VOIRest volontairement révoquée des rôles admin afin que le périmètre du portail résident ne puisse pas être contourné.- Tous les rôles peuvent créer et voir leurs propres
SIGNALEMENT(rapport d’incident) ; seulsCAISSIER,RHetRESPONSABLE_*peuvent les modifier.
Jetons d’authentification
Section intitulée « Jetons d’authentification »admin.refresh_token
Section intitulée « admin.refresh_token »Stocke les hachages SHA-256 des refresh tokens opaques pour la rotation de session.
| Colonne | Notes |
|---|---|
id_refresh_token |
PK |
id_utilisateur |
FK → admin.utilisateur (ON DELETE CASCADE) |
token_hash |
Hex SHA-256, unique |
expires_at |
Horodatage d’expiration |
revoked_at |
Null tant que valide |
replaced_by_hash |
Hachage du jeton suivant dans la chaîne de rotation |
user_agent |
User agent du client |
adresse_ip |
IPv4 ou IPv6, 45 caractères max |
Les refresh tokens sont exclus des triggers d’audit métier.
admin.password_reset_token
Section intitulée « admin.password_reset_token »Jetons de réinitialisation à usage unique et courte durée.
| Colonne | Notes |
|---|---|
id_reset_token |
PK |
id_utilisateur |
FK → admin.utilisateur (ON DELETE CASCADE) |
token_hash |
Hex SHA-256, unique |
expires_at |
TTL courte |
created_at |
Date de création |
used_at |
Null jusqu’à consommation |
Les jetons de réinitialisation sont également exclus des triggers d’audit.
audit.journal_audit
Section intitulée « audit.journal_audit »Table en ajout seul. Une ligne par opération métier.
| Colonne | Notes |
|---|---|
id_trace |
PK |
date_heure |
Horodatage de l’opération |
id_utilisateur |
Acteur, FK → admin.utilisateur |
module |
Nom du schéma |
operation |
INSERT, UPDATE ou DELETE |
entite |
Nom de la table |
entite_id |
PK de la ligne affectée |
description |
Résumé lisible |
montant |
Montant monétaire extrait automatiquement quand présent |
avant |
État de la ligne avant update/delete |
apres |
État de la ligne après insert/update |
Fonctions d’audit
Section intitulée « Fonctions d’audit »| Fonction | Rôle |
|---|---|
audit.masquer_donnees(jsonb) |
Rédige le champ mot_de_passe en *** avant écriture |
audit.enregistrer_trace() |
Fonction trigger qui capture le DML sur chaque table métier |
audit.installer_triggers() |
Découvre chaque table des 11 schémas métier et crée trg_audit_<table> |
Résolution de l’acteur
Section intitulée « Résolution de l’acteur »audit.enregistrer_trace lit la variable de session sim.id_utilisateur. Si elle n’est pas définie, la trace est attribuée à l’utilisateur system (créé automatiquement pendant la migration).
Tables exclues
Section intitulée « Tables exclues »Les tables de jetons ne sont pas auditées :
admin.refresh_tokenadmin.password_reset_token
Métadonnées de sauvegarde et de mot de passe
Section intitulée « Métadonnées de sauvegarde et de mot de passe »admin.sauvegarde et admin.sauvegarde_planification
Section intitulée « admin.sauvegarde et admin.sauvegarde_planification »Ces tables suivent les sauvegardes logiques créées par make backup. Elles stockent l’heure de début/fin, le type (AUTOMATIQUE / MANUELLE), le statut, la taille et le chemin. La table de planification est un singleton (une ligne) garanti par un index unique sur l’expression constante (1).
Hachages de mot de passe
Section intitulée « Hachages de mot de passe »- Les hachages de mots de passe dev/démo sont générés par
scripts/hash-seeds.mjset commités dansseeds/generated/10_auth_passwords.sql. - Le mot de passe admin de production est haché au déploiement par
scripts/hash-prod-admin.mjsdepuis.env.prodet pipé verspsql; il n’est jamais persisté hors de la base.
Recettes et exemples de sécurité
Section intitulée « Recettes et exemples de sécurité »Créer un nouveau rôle et accorder des permissions
Section intitulée « Créer un nouveau rôle et accorder des permissions »-- 1. Insérer le rôleINSERT INTO admin.role (code, libelle, description)VALUES ('RESPONSABLE_MARKET', 'Responsable Market', 'Gestion complète du market')ON CONFLICT (code) DO NOTHING;
-- 2. Créer les permissions si elles n'existent pasINSERT INTO admin.permission (code, libelle) VALUES ('MARKET.VOIR', 'Voir le market'), ('MARKET.CREER', 'Créer dans le market'), ('MARKET.MODIFIER', 'Modifier dans le market'), ('MARKET.SUPPRIMER','Supprimer dans le market')ON CONFLICT (code) DO NOTHING;
-- 3. Lier les permissions au rôleINSERT INTO admin.role_permission (id_role, id_permission)SELECT r.id_role, p.id_permissionFROM admin.role rCROSS JOIN admin.permission pWHERE r.code = 'RESPONSABLE_MARKET' AND p.code IN ('MARKET.VOIR', 'MARKET.CREER', 'MARKET.MODIFIER', 'MARKET.SUPPRIMER')ON CONFLICT (id_role, id_permission) DO NOTHING;Créer un utilisateur et lui assigner un rôle
Section intitulée « Créer un utilisateur et lui assigner un rôle »-- Le login doit être unique ; le mot de passe est un hachage bcrypt/argon2 produit par le backend.INSERT INTO admin.utilisateur ( nom, prenom, login, mot_de_passe, id_role, actif, date_creation)VALUES ( 'Diallo', 'Fatou', 'fdiallo', '$2b$10$...', -- haché par le backend (SELECT id_role FROM admin.role WHERE code = 'RESPONSABLE_MARKET'), TRUE, now())ON CONFLICT (login) DO NOTHING;Vérifier les permissions effectives d’un utilisateur
Section intitulée « Vérifier les permissions effectives d’un utilisateur »SELECT p.codeFROM admin.utilisateur uJOIN admin.role r ON r.id_role = u.id_roleJOIN admin.role_permission rp ON rp.id_role = r.id_roleJOIN admin.permission p ON p.id_permission = rp.id_permissionWHERE u.login = 'fdiallo'ORDER BY p.code;Définir l’acteur d’audit avant une transaction
Section intitulée « Définir l’acteur d’audit avant une transaction »Votre application doit exécuter ceci avant tout DML :
SELECT set_config('sim.id_utilisateur', ( SELECT id_utilisateur::text FROM admin.utilisateur WHERE login = 'fdiallo'), true);Le INSERT/UPDATE/DELETE suivant sur une table métier sera alors attribué à cet utilisateur dans audit.journal_audit.
Inspecter la piste d’audit d’un enregistrement
Section intitulée « Inspecter la piste d’audit d’un enregistrement »SELECT ja.date_heure, u.login, ja.operation, ja.entite, ja.entite_id, ja.montant, ja.avant, ja.apresFROM audit.journal_audit jaLEFT JOIN admin.utilisateur u ON u.id_utilisateur = ja.id_utilisateurWHERE ja.entite = 'contrat_location' AND ja.entite_id = 1ORDER BY ja.date_heure DESC;Vérifier que les champs de mot de passe sont masqués dans la piste d’audit
Section intitulée « Vérifier que les champs de mot de passe sont masqués dans la piste d’audit »SELECT apresFROM audit.journal_auditWHERE entite = 'utilisateur' AND operation = 'UPDATE' AND avant LIKE '%mot_de_passe%'LIMIT 1;
-- Attendu : la colonne apres contient 'mot_de_passe': '***'À faire, à ne pas faire
Section intitulée « À faire, à ne pas faire »| À faire | À ne pas faire |
|---|---|
Générer les hachages dev avec make password-hashes |
Écrire des mots de passe en clair dans les fichiers de seed |
Définir sim.id_utilisateur sur chaque connexion applicative |
Laisser non défini et attribuer chaque trace à system |
Utiliser ON CONFLICT lors de l’insertion de rôles/permissions |
Supposer que le seed a déjà tourné et omettre ON CONFLICT |
Hacher l’admin de production avec scripts/hash-prod-admin.mjs |
Commiter .env.prod ou tout vrai mot de passe dans Git |
Interroger admin.role_permission pour les décisions d’accès |
Coder en dur les noms de rôles dans l’application au lieu des permissions |
