HUB-13 — API d'administration des comptes d'ObsiGate (fusion #127, #226)
Bras armé de l'interface d'administration des utilisateurs du Hub (HUB-14), côté magasin des comptes : décisions D1(a) — ObsiGate reste maître des comptes — et D2(a) — ObsiGate reste l'émettrice du jeton draco (plan Hub v2, §2.2 et §4 vague A).
Périmètre — routes /api/admin/machine/comptes
Emplacement justifié : l'espace d'administration existant (/api/admin/…), avec un segment machine explicite qui distingue ces routes de l'administration par session (/api/auth/admin/users, garde require_admin).
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)
Codes propres : 401 sans clé machine valide, 404 compte inconnu (ou API non configurée), 409 conflit d'identifiant, 422 corps invalide.
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.
Protection par clé machine
Clé dédiéeDRACO_ADMIN_MACHINE_KEY, en-tête X-Draco-Machine-Key — même famille et même en-tête que GET /api/sso/revocations, mais distincte de DRACO_SSO_MACHINE_KEY : l'écriture de comptes est plus sensible que la lecture des révocations (plan §3), et la clé SSO ne suffit donc pas (testé). Comparaison à temps constant. Aucune session utilisateur — même administratrice — n'ouvre ces routes (testé). Sans la variable, l'API est inerte (404 « Non configuré », patron des révocations). À provisionner dans Infisical (/obsigate + /hub) avant activation côté Hub.
Désactivation et sessions
active=false refuse toute nouvelle connexion (401 uniforme). Les jti draco actifs sont révoqués via le mécanisme existant : backend/draco_session.py tient désormais un registre des émissions (data/draco_issued.json, jti → exp par sub, purgé à l'expiration) et expose revoke_all_for_user(sub), qui alimente la liste publiée par GET /api/sso/revocations. Le repère password_changed_at (BUG-028) est aussi avancé : les jetons natifs émis ne ressuscitent pas à la réactivation ; les anciens jti draco restent révoqués après réactivation.
Changement de schéma (minimum)
Nouveau champ utilisateur must_change_password (booléen, défaut false, posé par create_user) — les comptes existants sans le champ valent false, aucune migration de fichier requise (data/users.json reste en version 1). 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. Plus le nouveau fichier de registre data/draco_issued.json (généré, jamais versionné).
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
Support existant : data/audit.log (JSON lignes) via la nouvelle fonction backend.audit.log_compte_admin — horodatage, acteur (identité de service de l'en-tête facultatif X-Draco-Actor, défaut service-machine), action (compte_cree, compte_modifie, compte_desactive, compte_reactive, compte_supprime, mot_de_passe_reinitialise, mfa_reinitialise), cible, IP, détails non sensibles (champs modifiés, nombre de jti révoqués — jamais de mot de passe ni de secret). Visible via GET /api/admin/audit existant.
Tests (comptes lus dans les sorties)
Ciblés : tests/test_comptes_machine.py — 18/18 réussis (refus sans clé / mauvaise clé / clé SSO / session admin, API inerte non configurée, liste et profil 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é —, réinitialisation MFA, suppression, journalisation). Suites voisines (draco, auth, admin, MFA, version, OpenAPI) : 247/247, puis 64/64 avec version après bump.
ruff check backend/ et mypy backend/ --ignore-missing-imports : 0 erreur.
Version
2.84.0 par scripts/bump_version.py --set 2.84.0 (VERSION + fichiers dérivés + changelog synchronisés). Aucune release : elle suivra la fusion ordonnée par Bruno. Après fusion et redéploiement Docker, poser DRACO_ADMIN_MACHINE_KEY dans l'environnement d'ObsiGate (et du Hub, HUB-14).
## HUB-13 — API d'administration des comptes d'ObsiGate (fusion #127, #226)
Bras armé de l'interface d'administration des utilisateurs du Hub (HUB-14), côté magasin des comptes : décisions D1(a) — ObsiGate reste maître des comptes — et D2(a) — ObsiGate reste l'émettrice du jeton draco (plan Hub v2, §2.2 et §4 vague A).
### Périmètre — routes `/api/admin/machine/comptes`
Emplacement justifié : l'espace d'administration existant (`/api/admin/…`), avec un segment `machine` explicite qui distingue ces routes de l'administration par session (`/api/auth/admin/users`, garde `require_admin`).
| 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` | Mot de passe temporaire, changement forcé |
| POST | `/api/admin/machine/comptes/{username}/reinitialiser-mfa` | Effacement de l'enrôlement TOTP |
Codes propres : **401** sans clé machine valide, **404** compte inconnu (ou API non configurée), **409** conflit d'identifiant, **422** corps invalide.
**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.
### Protection par clé machine
Clé **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`, mais **distincte** de `DRACO_SSO_MACHINE_KEY` : l'écriture de comptes est plus sensible que la lecture des révocations (plan §3), et la clé SSO ne suffit donc pas (testé). Comparaison à temps constant. Aucune session utilisateur — même administratrice — n'ouvre ces routes (testé). Sans la variable, l'API est inerte (404 « Non configuré », patron des révocations). À provisionner dans Infisical (`/obsigate` + `/hub`) avant activation côté Hub.
### Désactivation et sessions
`active=false` refuse toute nouvelle connexion (401 uniforme). Les `jti` draco actifs sont révoqués via le mécanisme existant : `backend/draco_session.py` tient désormais un **registre des émissions** (`data/draco_issued.json`, `jti → exp` par `sub`, purgé à l'expiration) et expose `revoke_all_for_user(sub)`, qui alimente la liste publiée par `GET /api/sso/revocations`. Le repère `password_changed_at` (BUG-028) est aussi avancé : les jetons natifs émis ne ressuscitent pas à la réactivation ; les anciens `jti` draco restent révoqués après réactivation.
### Changement de schéma (minimum)
Nouveau champ utilisateur `must_change_password` (booléen, défaut `false`, posé par `create_user`) — les comptes existants sans le champ valent `false`, **aucune migration de fichier requise** (`data/users.json` reste en version 1). 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`. Plus le nouveau fichier de registre `data/draco_issued.json` (généré, jamais versionné).
### 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
Support existant : `data/audit.log` (JSON lignes) via la nouvelle fonction `backend.audit.log_compte_admin` — horodatage, `acteur` (identité de service de l'en-tête facultatif `X-Draco-Actor`, défaut `service-machine`), `action` (`compte_cree`, `compte_modifie`, `compte_desactive`, `compte_reactive`, `compte_supprime`, `mot_de_passe_reinitialise`, `mfa_reinitialise`), `cible`, IP, détails non sensibles (champs modifiés, nombre de `jti` révoqués — jamais de mot de passe ni de secret). Visible via `GET /api/admin/audit` existant.
### Tests (comptes lus dans les sorties)
- Ciblés : `tests/test_comptes_machine.py` — **18/18 réussis** (refus sans clé / mauvaise clé / clé SSO / session admin, API inerte non configurée, liste et profil 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é —, réinitialisation MFA, suppression, journalisation). Suites voisines (draco, auth, admin, MFA, version, OpenAPI) : **247/247**, puis **64/64** avec version après bump.
- Suite complète, **listes d'échecs comparées** : arbre vierge (HEAD `b83de53`, worktree séparé) = **42 échecs / 1878 réussites / 1 ignoré** ; branche = **42 échecs / 1896 réussites / 1 ignoré** — listes d'identifiants d'échecs **strictement identiques** (agent_loop, ai_chat, ai_e2e, collab, pdf, perf, watcher : le fonds préexistant connu de 42), +18 réussites = les nouveaux tests. **Aucun échec nouveau.**
- `ruff check backend/` et `mypy backend/ --ignore-missing-imports` : **0 erreur**.
### Version
**2.84.0** par `scripts/bump_version.py --set 2.84.0` (VERSION + fichiers dérivés + changelog synchronisés). **Aucune release** : elle suivra la fusion ordonnée par Bruno. Après fusion et redéploiement Docker, poser `DRACO_ADMIN_MACHINE_KEY` dans l'environnement d'ObsiGate (et du Hub, HUB-14).
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
HUB-13 — API d'administration des comptes d'ObsiGate (fusion #127, #226)
Bras armé de l'interface d'administration des utilisateurs du Hub (HUB-14), côté magasin des comptes : décisions D1(a) — ObsiGate reste maître des comptes — et D2(a) — ObsiGate reste l'émettrice du jeton draco (plan Hub v2, §2.2 et §4 vague A).
Périmètre — routes
/api/admin/machine/comptesEmplacement justifié : l'espace d'administration existant (
/api/admin/…), avec un segmentmachineexplicite qui distingue ces routes de l'administration par session (/api/auth/admin/users, garderequire_admin)./api/admin/machine/comptes/api/admin/machine/comptes/{username}/api/admin/machine/comptes/api/admin/machine/comptes/{username}/api/admin/machine/comptes/{username}/desactiver/api/admin/machine/comptes/{username}/reactiver/api/admin/machine/comptes/{username}/api/admin/machine/comptes/{username}/reinitialiser-mot-de-passe/api/admin/machine/comptes/{username}/reinitialiser-mfaCodes propres : 401 sans clé machine valide, 404 compte inconnu (ou API non configurée), 409 conflit d'identifiant, 422 corps invalide.
Profil étendu (liste blanche stricte) :
id,username,display_name,language,avatar(valeur du modèle — data-URL ounull, commeGET /api/auth/me) etavatar_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.Protection par clé machine
Clé dédiée
DRACO_ADMIN_MACHINE_KEY, en-têteX-Draco-Machine-Key— même famille et même en-tête queGET /api/sso/revocations, mais distincte deDRACO_SSO_MACHINE_KEY: l'écriture de comptes est plus sensible que la lecture des révocations (plan §3), et la clé SSO ne suffit donc pas (testé). Comparaison à temps constant. Aucune session utilisateur — même administratrice — n'ouvre ces routes (testé). Sans la variable, l'API est inerte (404 « Non configuré », patron des révocations). À provisionner dans Infisical (/obsigate+/hub) avant activation côté Hub.Désactivation et sessions
active=falserefuse toute nouvelle connexion (401 uniforme). Lesjtidraco actifs sont révoqués via le mécanisme existant :backend/draco_session.pytient désormais un registre des émissions (data/draco_issued.json,jti → expparsub, purgé à l'expiration) et exposerevoke_all_for_user(sub), qui alimente la liste publiée parGET /api/sso/revocations. Le repèrepassword_changed_at(BUG-028) est aussi avancé : les jetons natifs émis ne ressuscitent pas à la réactivation ; les anciensjtidraco restent révoqués après réactivation.Changement de schéma (minimum)
Nouveau champ utilisateur
must_change_password(booléen, défautfalse, posé parcreate_user) — les comptes existants sans le champ valentfalse, aucune migration de fichier requise (data/users.jsonreste en version 1). Posé à la création machine et à la réinitialisation du mot de passe ; signalé dans la réponse de connexion etGET /api/auth/me; effacé parPOST /api/auth/change-password. Plus le nouveau fichier de registredata/draco_issued.json(généré, jamais versionné).Réinitialisation du MFA
Efface
mfa_secret,mfa_secret_pendingetmfa_recovery_codes. Les clés WebAuthn éventuelles sont conservées et gardent le MFA actif (méthodewebauthn) ; sans elles,mfa_enabledpasse àfalse.Journalisation
Support existant :
data/audit.log(JSON lignes) via la nouvelle fonctionbackend.audit.log_compte_admin— horodatage,acteur(identité de service de l'en-tête facultatifX-Draco-Actor, défautservice-machine),action(compte_cree,compte_modifie,compte_desactive,compte_reactive,compte_supprime,mot_de_passe_reinitialise,mfa_reinitialise),cible, IP, détails non sensibles (champs modifiés, nombre dejtirévoqués — jamais de mot de passe ni de secret). Visible viaGET /api/admin/auditexistant.Tests (comptes lus dans les sorties)
tests/test_comptes_machine.py— 18/18 réussis (refus sans clé / mauvaise clé / clé SSO / session admin, API inerte non configurée, liste et profil sans secret, création puis lecture et connexion, doublon 409, modification, désactivation → connexion refusée + session native refusée +jtidraco révoqué et publié, puis réactivation, réinitialisation du mot de passe — ancien refusé, nouveau accepté, changement forcé signalé puis effacé —, réinitialisation MFA, suppression, journalisation). Suites voisines (draco, auth, admin, MFA, version, OpenAPI) : 247/247, puis 64/64 avec version après bump.b83de53, worktree séparé) = 42 échecs / 1878 réussites / 1 ignoré ; branche = 42 échecs / 1896 réussites / 1 ignoré — listes d'identifiants d'échecs strictement identiques (agent_loop, ai_chat, ai_e2e, collab, pdf, perf, watcher : le fonds préexistant connu de 42), +18 réussites = les nouveaux tests. Aucun échec nouveau.ruff check backend/etmypy backend/ --ignore-missing-imports: 0 erreur.Version
2.84.0 par
scripts/bump_version.py --set 2.84.0(VERSION + fichiers dérivés + changelog synchronisés). Aucune release : elle suivra la fusion ordonnée par Bruno. Après fusion et redéploiement Docker, poserDRACO_ADMIN_MACHINE_KEYdans l'environnement d'ObsiGate (et du Hub, HUB-14).