HUB-22 (partie ObsiGate, vague C du plan Hub v2.1) — #227. Le Hub (0.17.0) doit lister les sessions actives d'un utilisateur et les révoquer une à une ou toutes, via le canal « jti » livré en HUB-13 (#226). Aucune route machine « sessions » n'existait : cette PR la crée. Aucune fusion — branche + PR seulement.
GET /api/admin/machine/comptes/{username}/sessions → 200 {"username", "sessions": [{"jti", "exp", "expire_le" (ISO 8601 UTC), "revoquee"}]} ; 404 compte inconnu ou API non configurée.
POST /api/admin/machine/comptes/{username}/sessions/{jti}/revoquer → 200 {"jti", "revoquee": true} ; 404 si le jti n'est pas dans l'ensemble actif de cet utilisateur (jamais de révocation cross-utilisateur).
POST /api/admin/machine/comptes/{username}/sessions/revoquer-toutes → 200 {"revoquees": n} — réutilise revoke_all_for_user, sans désactiver le compte (déconnexion globale distincte de la désactivation).
Backend
backend/draco_session.py : issued_sessions_for_user(sub) (sessions actives triées par exp croissant, purge des expirés au passage) et revoke_one_for_user(sub, jti) (révoque via revoke() seulement si le jti appartient à l'ensemble émis du sub et n'est pas expiré ; booléen).
backend/routers/comptes_machine.py : les trois routes + clé d'identité factorisée _identifiant_sessions (le même sub que _revoquer_sessions : id, à défaut username) ; écritures journalisées (session_revoquee, sessions_revoquees) via log_compte_admin.
Version et docs
2.85.0 via scripts/bump_version.py --set 2.85.0 (dérivés synchronisés) ; base = main distant 2.84.1 (HUB-18) — le diff ne contient que cette livraison (vérifié fichier par fichier par hachages).
Suite complète : non obtenue — l'environnement d'exécution a gelé les écritures disque par intermittence pendant la fenêtre (premier run bloqué ~19 min, relances mortes sur tempfile) ; le périmètre ciblé ci-dessus couvre l'intégralité du code touché. À confirmer par le CI de la PR.
Note de livraison (transparence)
Le git push local étant bloqué par ce même gel d'écritures, la branche a été poussée via l'API Contents de Gitea (14 commits, un par fichier, même message). Un premier PUT a brièvement commité backend/draco_session.py sur main (champ new_branch_name ignoré par l'API) : revert immédiat (commit 51275105), contenu de main vérifié revenu à l'état 2.84.1 ; il en reste deux commits (erreur + revert) dans l'historique de main, sans aucun écart de contenu. Redéploiement Docker requis après fusion.
**HUB-22 (partie ObsiGate, vague C du plan Hub v2.1) — #227.** Le Hub (0.17.0) doit lister les sessions actives d'un utilisateur et les révoquer une à une ou toutes, via le canal « jti » livré en HUB-13 (#226). Aucune route machine « sessions » n'existait : cette PR la crée. **Aucune fusion** — branche + PR seulement.
## Routes livrées (garde require_machine_admin, en-têtes X-Draco-Machine-Key / X-Draco-Actor, erreurs detail FastAPI comme l'existant)
- `GET /api/admin/machine/comptes/{username}/sessions` → 200 `{"username", "sessions": [{"jti", "exp", "expire_le" (ISO 8601 UTC), "revoquee"}]}` ; 404 compte inconnu ou API non configurée.
- `POST /api/admin/machine/comptes/{username}/sessions/{jti}/revoquer` → 200 `{"jti", "revoquee": true}` ; **404** si le jti n'est pas dans l'ensemble actif de cet utilisateur (jamais de révocation cross-utilisateur).
- `POST /api/admin/machine/comptes/{username}/sessions/revoquer-toutes` → 200 `{"revoquees": n}` — réutilise `revoke_all_for_user`, **sans désactiver le compte** (déconnexion globale distincte de la désactivation).
## Backend
- `backend/draco_session.py` : `issued_sessions_for_user(sub)` (sessions actives triées par exp croissant, purge des expirés au passage) et `revoke_one_for_user(sub, jti)` (révoque via `revoke()` seulement si le jti appartient à l'ensemble émis du sub et n'est pas expiré ; booléen).
- `backend/routers/comptes_machine.py` : les trois routes + clé d'identité factorisée `_identifiant_sessions` (le même sub que `_revoquer_sessions` : id, à défaut username) ; écritures journalisées (`session_revoquee`, `sessions_revoquees`) via `log_compte_admin`.
## Version et docs
- **2.85.0** via `scripts/bump_version.py --set 2.85.0` (dérivés synchronisés) ; base = main distant **2.84.1** (HUB-18) — le diff ne contient que cette livraison (vérifié fichier par fichier par hachages).
- `CHANGELOG.md` [2.85.0], Roadmap **#227** (nouvel ID stable), fiche `docs/features/hub-sessions-machine-227.md`. Aucun impact frontend (pas d'i18n).
## Tests (lus)
- Ciblés : `tests/test_draco_session.py` + `tests/test_comptes_machine.py` + `tests/test_version.py` = **102/102** (dont `TestSessions` : liste, révocation unitaire succès + 404 cross-utilisateur, tout-révoquer sans désactivation, garde 401/404, journalisation ; et les unitaires du registre : tri, purge des expirés, refus cross-utilisateur/expiré).
- `ruff check backend/` : **0 erreur**. `mypy backend/ --ignore-missing-imports` : **0 erreur** (127 fichiers).
- Suite complète : **non obtenue** — l'environnement d'exécution a gelé les écritures disque par intermittence pendant la fenêtre (premier run bloqué ~19 min, relances mortes sur tempfile) ; le périmètre ciblé ci-dessus couvre l'intégralité du code touché. À confirmer par le CI de la PR.
## Note de livraison (transparence)
Le `git push` local étant bloqué par ce même gel d'écritures, la branche a été poussée via l'API Contents de Gitea (14 commits, un par fichier, même message). Un premier PUT a brièvement commité `backend/draco_session.py` sur `main` (champ `new_branch_name` ignoré par l'API) : **revert immédiat** (commit `51275105`), contenu de `main` vérifié revenu à l'état 2.84.1 ; il en reste deux commits (erreur + revert) dans l'historique de `main`, sans aucun écart de contenu. Redéploiement Docker requis après fusion.
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-22 (partie ObsiGate, vague C du plan Hub v2.1) — #227. Le Hub (0.17.0) doit lister les sessions actives d'un utilisateur et les révoquer une à une ou toutes, via le canal « jti » livré en HUB-13 (#226). Aucune route machine « sessions » n'existait : cette PR la crée. Aucune fusion — branche + PR seulement.
Routes livrées (garde require_machine_admin, en-têtes X-Draco-Machine-Key / X-Draco-Actor, erreurs detail FastAPI comme l'existant)
GET /api/admin/machine/comptes/{username}/sessions→ 200{"username", "sessions": [{"jti", "exp", "expire_le" (ISO 8601 UTC), "revoquee"}]}; 404 compte inconnu ou API non configurée.POST /api/admin/machine/comptes/{username}/sessions/{jti}/revoquer→ 200{"jti", "revoquee": true}; 404 si le jti n'est pas dans l'ensemble actif de cet utilisateur (jamais de révocation cross-utilisateur).POST /api/admin/machine/comptes/{username}/sessions/revoquer-toutes→ 200{"revoquees": n}— réutiliserevoke_all_for_user, sans désactiver le compte (déconnexion globale distincte de la désactivation).Backend
backend/draco_session.py:issued_sessions_for_user(sub)(sessions actives triées par exp croissant, purge des expirés au passage) etrevoke_one_for_user(sub, jti)(révoque viarevoke()seulement si le jti appartient à l'ensemble émis du sub et n'est pas expiré ; booléen).backend/routers/comptes_machine.py: les trois routes + clé d'identité factorisée_identifiant_sessions(le même sub que_revoquer_sessions: id, à défaut username) ; écritures journalisées (session_revoquee,sessions_revoquees) vialog_compte_admin.Version et docs
scripts/bump_version.py --set 2.85.0(dérivés synchronisés) ; base = main distant 2.84.1 (HUB-18) — le diff ne contient que cette livraison (vérifié fichier par fichier par hachages).CHANGELOG.md[2.85.0], Roadmap #227 (nouvel ID stable), fichedocs/features/hub-sessions-machine-227.md. Aucun impact frontend (pas d'i18n).Tests (lus)
tests/test_draco_session.py+tests/test_comptes_machine.py+tests/test_version.py= 102/102 (dontTestSessions: liste, révocation unitaire succès + 404 cross-utilisateur, tout-révoquer sans désactivation, garde 401/404, journalisation ; et les unitaires du registre : tri, purge des expirés, refus cross-utilisateur/expiré).ruff check backend/: 0 erreur.mypy backend/ --ignore-missing-imports: 0 erreur (127 fichiers).Note de livraison (transparence)
Le
git pushlocal étant bloqué par ce même gel d'écritures, la branche a été poussée via l'API Contents de Gitea (14 commits, un par fichier, même message). Un premier PUT a brièvement commitébackend/draco_session.pysurmain(champnew_branch_nameignoré par l'API) : revert immédiat (commit51275105), contenu demainvérifié revenu à l'état 2.84.1 ; il en reste deux commits (erreur + revert) dans l'historique demain, sans aucun écart de contenu. Redéploiement Docker requis après fusion.