Aller au contenu

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.

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

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é

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
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.

  • ADMINISTRATEUR et DIRIGEANT obtiennent toutes les permissions via un CROSS JOIN sur admin.permission.
  • RESIDENT.VOIR est 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) ; seuls CAISSIER, RH et RESPONSABLE_* peuvent les modifier.

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.

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.

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
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>

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).

Les tables de jetons ne sont pas auditées :

  • admin.refresh_token
  • admin.password_reset_token

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).

  • Les hachages de mots de passe dev/démo sont générés par scripts/hash-seeds.mjs et commités dans seeds/generated/10_auth_passwords.sql.
  • Le mot de passe admin de production est haché au déploiement par scripts/hash-prod-admin.mjs depuis .env.prod et pipé vers psql ; il n’est jamais persisté hors de la base.

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ôle
INSERT 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 pas
INSERT 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ôle
INSERT INTO admin.role_permission (id_role, id_permission)
SELECT r.id_role, p.id_permission
FROM admin.role r
CROSS JOIN admin.permission p
WHERE r.code = 'RESPONSABLE_MARKET'
AND p.code IN ('MARKET.VOIR', 'MARKET.CREER', 'MARKET.MODIFIER', 'MARKET.SUPPRIMER')
ON CONFLICT (id_role, id_permission) DO NOTHING;
-- 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.code
FROM admin.utilisateur u
JOIN admin.role r ON r.id_role = u.id_role
JOIN admin.role_permission rp ON rp.id_role = r.id_role
JOIN admin.permission p ON p.id_permission = rp.id_permission
WHERE 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.apres
FROM audit.journal_audit ja
LEFT JOIN admin.utilisateur u ON u.id_utilisateur = ja.id_utilisateur
WHERE ja.entite = 'contrat_location'
AND ja.entite_id = 1
ORDER 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 apres
FROM audit.journal_audit
WHERE 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
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