106 lines
5.2 KiB
Markdown
106 lines
5.2 KiB
Markdown
# #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.
|