From 3bfda066ab8e141fd8c6f4a32086de30aafeb11e Mon Sep 17 00:00:00 2001 From: Spark Date: Fri, 6 Nov 2026 11:07:31 -0500 Subject: [PATCH] =?UTF-8?q?feat:=20HUB-22=20=E2=80=94=20API=20machine=20de?= =?UTF-8?q?s=20sessions=20draco=20:=20liste=20et=20r=C3=A9vocation=20des?= =?UTF-8?q?=20sessions=20(2.85.0,=20#227)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/features/hub-sessions-machine-227.md | 85 +++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 docs/features/hub-sessions-machine-227.md diff --git a/docs/features/hub-sessions-machine-227.md b/docs/features/hub-sessions-machine-227.md new file mode 100644 index 0000000..df7c293 --- /dev/null +++ b/docs/features/hub-sessions-machine-227.md @@ -0,0 +1,85 @@ +# #227 — HUB-22 : API machine des sessions draco (liste et révocation) + +**Carte :** vague C du plan Hub v2.1 (HUB-22) — le Hub consomme ces +routes en 0.17.0 pour l'écran des sessions d'un utilisateur. +**Version :** 2.85.0. **Prérequis :** HUB-13 (#226) — registre des +émissions `jti` et garde machine. + +## Objet + +Le Hub doit lister les sessions actives d'un utilisateur et les +révoquer une à une ou toutes, via le canal `jti` livré en HUB-13. +Aucune route machine « sessions » n'existait : cette livraison la crée, +sous la même garde (`require_machine_admin`, clé +`DRACO_ADMIN_MACHINE_KEY`, en-têtes `X-Draco-Machine-Key` / +`X-Draco-Actor`) et avec la même journalisation d'audit que les routes +comptes existantes. Sans la clé configurée, l'API reste inerte (404) ; +sans clé valide, 401. + +## Routes — `/api/admin/machine/comptes/{username}/sessions` + +| Méthode | Chemin | Effet | +|---|---|---| +| GET | `/api/admin/machine/comptes/{username}/sessions` | Sessions actives du compte | +| POST | `/api/admin/machine/comptes/{username}/sessions/{jti}/revoquer` | Révocation d'une session | +| POST | `/api/admin/machine/comptes/{username}/sessions/revoquer-toutes` | Révocation de toutes les sessions | + +Codes : 401 sans clé machine valide, 404 compte inconnu (ou API non +configurée), 404 `jti` absent de l'ensemble actif du compte. + +### Liste — `GET …/sessions` + +`200 {"username": …, "sessions": [{"jti", "exp", "expire_le", +"revoquee"}]}` — une entrée par `jti` émis et non expiré, triée par +`exp` croissant ; `expire_le` est l'échéance en ISO 8601 UTC et +`revoquee` signale un `jti` déjà présent dans la liste des +révocations. + +### Révocation unitaire — `POST …/sessions/{jti}/revoquer` + +`200 {"jti", "revoquee": true}`. Le `jti` n'est révoqué que s'il +appartient à l'ensemble émis du compte visé et n'est pas expiré : +**404 sinon, jamais de révocation cross-utilisateur**. + +### Tout révoquer — `POST …/sessions/revoquer-toutes` + +`200 {"revoquees": n}` — réutilise `revoke_all_for_user` de HUB-13. +C'est la « déconnexion globale », **distincte de la désactivation** : +le compte reste actif, une nouvelle connexion émet une nouvelle +session. + +## Backend — `backend/draco_session.py` + +- `issued_sessions_for_user(sub)` : sessions actives + (`exp > maintenant`) du `sub`, `[{jti, exp, revoquee}]` triées par + `exp` croissant ; les entrées expirées du `sub` sont purgées du + registre au passage. +- `revoke_one_for_user(sub, jti)` : révoque via le `revoke()` + existant uniquement si le `jti` appartient à l'ensemble émis de ce + `sub` et n'est pas expiré ; retourne un booléen. + +La clé du registre est le même `sub` que celui de HUB-13 +(`_identifiant_sessions` dans `comptes_machine.py` : `id` du compte, +à défaut `username`) — factorisée depuis `_revoquer_sessions`, qui +l'utilise aussi désormais. + +## Journalisation + +Les deux écritures sont consignées dans `data/audit.log` via +`log_compte_admin` : actions `session_revoquee` (détail : le `jti`) et +`sessions_revoquees` (détail : nombre de `jti` révoqués), acteur = +identité de service `X-Draco-Actor`. La lecture suit le patron des +lectures existantes (non journalisée). + +## Tests + +- `tests/test_draco_session.py` : liste triée et marquage `revoquee`, + purge des expirés, révocation unitaire, refus cross-utilisateur, + refus d'un `jti` expiré ; le registre des émissions est isolé en + fichier temporaire dans la fixture. +- `tests/test_comptes_machine.py` (`TestSessions`) : liste (contenu, + `expire_le` ISO 8601 UTC), liste vide, compte inconnu 404, + révocation unitaire (succès + effet sur `verify_token`), + cross-utilisateur 404 sans effet, tout-révoquer (compte toujours + actif, autres comptes intacts, seconde passe à 0), garde (401 sans + clé, 404 API non configurée), journalisation.