# #226 — HUB-13 : API d'administration des comptes (machine-à-machine) **Carte :** fusion #127 (HUB-13, plan Hub v2 §2.2 et §4 vague A ; décisions D1(a) — magasin des comptes chez ObsiGate — et D2(a) — ObsiGate émettrice). **Version :** 2.84.0. ## Objet Le Hub administre les comptes de la suite depuis son interface d'administration ; ObsiGate reste le magasin des comptes. Cette API est le bras armé de cette interface : administration complète des comptes, en lecture et en écriture, réservée aux appels machine-à-machine. ## Routes — `/api/admin/machine/comptes` Emplacement choisi : l'espace d'administration existant (`/api/admin/…`), segment `machine` explicite. Il distingue ces routes de l'administration par session (`/api/auth/admin/users`, garde `require_admin`) : aucune session utilisateur, même administratrice, ne les ouvre. | Méthode | Chemin | Effet | |---|---|---| | GET | `/api/admin/machine/comptes` | Liste des comptes (profils étendus) | | GET | `/api/admin/machine/comptes/{username}` | Profil étendu d'un compte | | POST | `/api/admin/machine/comptes` | Création (201 ; 409 si identifiant pris) | | PATCH | `/api/admin/machine/comptes/{username}` | Modification (nom affiché, rôle global, langue, vaults) | | POST | `/api/admin/machine/comptes/{username}/desactiver` | Désactivation + révocation des sessions | | POST | `/api/admin/machine/comptes/{username}/reactiver` | Réactivation | | DELETE | `/api/admin/machine/comptes/{username}` | Suppression + révocation des sessions | | POST | `/api/admin/machine/comptes/{username}/reinitialiser-mot-de-passe` | Nouveau mot de passe temporaire, changement forcé | | POST | `/api/admin/machine/comptes/{username}/reinitialiser-mfa` | Effacement de l'enrôlement TOTP | Codes : 401 sans clé machine valide, 404 compte inconnu (ou API non configurée), 409 conflit d'identifiant, 422 corps invalide. ## Protection - Clé machine **dédiée** : `DRACO_ADMIN_MACHINE_KEY`, en-tête `X-Draco-Machine-Key` (même famille et même en-tête que `GET /api/sso/revocations`). Distincte de `DRACO_SSO_MACHINE_KEY` : l'écriture de comptes est plus sensible que la lecture des révocations (plan §3). Comparaison à temps constant. - Sans la variable, l'API est inerte (404 « Non configuré »). - Acteur consigné : en-tête facultatif `X-Draco-Actor` (défaut `service-machine`). ## Profil étendu Liste blanche stricte : `id`, `username`, `display_name`, `language`, `avatar` (valeur du modèle — data-URL ou `null`, comme `GET /api/auth/me`) et `avatar_present`, `role`, `active`, `vaults`, `mfa_enabled` (oui/non), `mfa_method`, `must_change_password`, `created_at`, `last_login`. **Jamais** le haché du mot de passe, le secret TOTP, les codes de récupération ni les identifiants WebAuthn. ## Désactivation et sessions - `active = false` → la connexion est refusée (401 uniforme, BUG-039) et plus aucun jeton n'est émis. - Les `jti` draco émis pour le compte sont révoqués via le mécanisme existant (`draco_session.revoke`, liste publiée par `GET /api/sso/revocations`). Pour les connaître, `draco_session` tient désormais un registre des émissions (`data/draco_issued.json`, `jti → exp` par `sub`, purgé à l'expiration) et `revoke_all_for_user(sub)`. - Le repère `password_changed_at` (BUG-028) est avancé : les jetons natifs déjà émis sont invalidés et ne ressuscitent pas à la réactivation. Les anciennes sessions draco restent révoquées après réactivation : une nouvelle connexion est nécessaire. ## Changement forcé du mot de passe Nouveau champ du modèle utilisateur : `must_change_password` (booléen, défaut `false` — ajouté par `create_user`, aucun fichier à migrer : les comptes existants sans le champ valent `false`). Posé à la création machine et à la réinitialisation du mot de passe ; signalé dans la réponse de connexion et `GET /api/auth/me` ; effacé par `POST /api/auth/change-password`. ## Réinitialisation du MFA Efface `mfa_secret`, `mfa_secret_pending` et `mfa_recovery_codes`. Les clés WebAuthn éventuelles sont conservées et gardent le MFA actif (méthode `webauthn`) ; sans elles, `mfa_enabled` passe à `false`. ## Journalisation Chaque action est consignée dans le journal d'audit existant (`data/audit.log`, JSON lignes) via `backend.audit.log_compte_admin` : horodatage, `acteur` (identité de service), `action` (`compte_cree`, `compte_modifie`, `compte_desactive`, `compte_reactive`, `compte_supprime`, `mot_de_passe_reinitialise`, `mfa_reinitialise`), `cible` (compte visé), IP et détails non sensibles (champs modifiés, nombre de `jti` révoqués — jamais de mot de passe ni de secret). ## Tests `tests/test_comptes_machine.py` : refus sans clé / mauvaise clé / clé SSO / session admin, API inerte non configurée, liste et profil étendu sans secret, création puis lecture et connexion, doublon 409, modification, désactivation (connexion refusée, session native refusée, `jti` draco révoqué et publié) puis réactivation, réinitialisation du mot de passe (ancien refusé, nouveau accepté, changement forcé signalé puis effacé au changement), réinitialisation MFA (connexion directe après), suppression, journalisation.