-- ============================================================================ -- PTA V7.12 — Mot de passe des comptes privilégiés (Service Enfance et DSI) -- ============================================================================ -- Le jeton reste le seul facteur pour les agents et les responsables de lieu. -- Les comptes qui VALIDENT (Service Enfance) et ceux qui DISTRIBUENT LES ACCÈS -- (DSI) en réclament un second : un mot de passe, connu du seul titulaire. -- -- Deux facteurs de nature différente : le jeton est remis par la DSI et peut -- circuler par courriel ; le mot de passe est choisi par le titulaire à sa -- première connexion et n'est jamais affiché nulle part. Un jeton intercepté ne -- suffit donc plus à valider un planning ni à créer un compte. -- -- CONTRAIREMENT AU JETON, le mot de passe est haché avec password_hash() -- (bcrypt) et non SHA-256 : il est choisi par un humain, donc attaquable par -- dictionnaire, et c'est précisément ce que le hachage lent rend coûteux. La -- vérification se fait sur un compte déjà identifié par son jeton, l'absence -- d'index sur le mot de passe n'a donc aucune importance. -- -- POURQUOI « v7_9f » : les migrations sont jouées dans l'ORDRE ALPHABÉTIQUE -- (docker/db/apply-migrations.sh). Le préfixe suit la dernière migration en date -- (v7_9e), pas le numéro de version fonctionnelle. Voir CLAUDE.md. -- -- Prérequis : migration_v7_9e_dsi_comptes_journal.sql. -- ============================================================================ -- ---------------------------------------------------------------------------- -- 1. DEUX COLONNES SUR LE COMPTE -- ---------------------------------------------------------------------------- -- `IF NOT EXISTS` est une extension MariaDB : elle rend l'ALTER idempotent, ce -- qu'exige le rejeu des migrations. -- -- Le hachage est NULLABLE, et cet état porte du sens : « mot de passe pas encore -- défini ». C'est l'état d'un compte fraîchement créé, et c'est aussi ce que -- produit une réinitialisation par la DSI. Un compte privilégié dans cet état ne -- peut pas ouvrir de session : la connexion impose d'abord d'en choisir un. ALTER TABLE compte_acces ADD COLUMN IF NOT EXISTS mot_de_passe_hash VARCHAR(255) NULL COMMENT 'password_hash() du mot de passe ; NULL = à définir à la prochaine connexion', ADD COLUMN IF NOT EXISTS date_mot_de_passe DATETIME NULL COMMENT 'Dernière définition ou modification du mot de passe par le titulaire'; -- ---------------------------------------------------------------------------- -- 2. LA VUE PORTE LA RÈGLE « QUI DOIT AVOIR UN MOT DE PASSE » -- ---------------------------------------------------------------------------- -- Comme pour les droits depuis la V7.8, la règle descend en base : le PHP -- demande à la vue si ce compte réclame un second facteur, il ne rejoue pas la -- liste des rôles concernés de son côté. CREATE OR REPLACE VIEW v_acces_compte AS SELECT c.id_compte, c.role, c.libelle, c.id_agent, c.id_structure, c.jeton_empreinte, c.jeton_prefixe, c.actif, c.commentaire, c.date_creation, c.date_rotation, c.date_derniere_connexion, c.nombre_connexions, c.mot_de_passe_hash, c.date_mot_de_passe, -- Les deux rôles qui engagent l'application : celui qui valide les plannings -- de toute la collectivité, et celui qui distribue les accès. CASE WHEN c.role IN ('SERVICE_ENFANCE', 'DSI') THEN 1 ELSE 0 END AS mot_de_passe_requis, CASE WHEN c.mot_de_passe_hash IS NULL THEN 0 ELSE 1 END AS mot_de_passe_defini, a.matricule, a.nom AS agent_nom, a.prenom AS agent_prenom, s.code AS structure_code, s.nom AS structure_nom, -- Libellé affiché : l'identité VIVE pour un agent ou un lieu (un agent -- renommé doit apparaître sous son nouveau nom), le libellé saisi pour les -- comptes Service Enfance et DSI, qui ne pointent vers rien. CASE c.role WHEN 'AGENT' THEN CONCAT(a.prenom, ' ', a.nom) WHEN 'RESPONSABLE_STRUCTURE' THEN s.nom ELSE c.libelle END AS identite_libelle, -- Un compte reste utilisable tant que l'identité qu'il porte est active : -- désactiver un agent coupe son accès sans avoir à toucher à son compte. CASE c.role WHEN 'AGENT' THEN COALESCE(a.actif, 0) WHEN 'RESPONSABLE_STRUCTURE' THEN COALESCE(s.actif, 0) ELSE 1 END AS identite_active FROM compte_acces c LEFT JOIN agent a ON a.id_agent = c.id_agent LEFT JOIN structure s ON s.id_structure = c.id_structure;