Kanbante 0.47.0 — HUB-23 : métadonnées des jetons kbt_ pour le registre du Hub (GET /api/jetons/metadonnees, cookie draco relayé, métadonnées seulement)
This commit is contained in:
+54
-1
@@ -439,7 +439,7 @@ class AuthMiddleware(BaseHTTPMiddleware):
|
||||
# lieu à la première connexion par le pont /auth/draco.
|
||||
# Les preflights OPTIONS ne portent jamais de cookies : ils
|
||||
# sont répondus par RechercheCorsMiddleware avant d'arriver ici.
|
||||
if path == "/api/search":
|
||||
if path in ("/api/search", "/api/jetons/metadonnees"):
|
||||
try:
|
||||
from app import accounts as accounts_service
|
||||
from app import draco
|
||||
@@ -7160,6 +7160,59 @@ async def api_projects():
|
||||
return {"active": active, "inactive": inactive, "archived": archived}
|
||||
|
||||
|
||||
@app.get("/api/jetons/metadonnees")
|
||||
async def api_jetons_metadonnees(request: Request):
|
||||
"""Métadonnées des jetons `kbt_` pour le registre du Hub (HUB-23).
|
||||
|
||||
Le Hub interroge cette route côté serveur en relayant le cookie
|
||||
`draco_session` de l'administrateur connecté (même patron que
|
||||
/api/search : correspondance par utilisateur exigée, aucun JIT —
|
||||
l'amorçage de l'unique compte admin reste possible, comme sur
|
||||
/api/search). Un administrateur Kanbante reçoit les métadonnées
|
||||
de tous les jetons ; tout autre compte, seulement les siennes.
|
||||
|
||||
Métadonnées seulement : nom, propriétaire, préfixe `kbt_`,
|
||||
création, dernier usage, état (actif / révoqué). La valeur du
|
||||
jeton n'existe plus après sa création et l'empreinte SHA-256 ne
|
||||
quitte jamais la base : aucun des deux ne figure ici.
|
||||
"""
|
||||
from app import accounts as accounts_service
|
||||
from app import draco
|
||||
|
||||
claims = draco.claims_for(request)
|
||||
user = (
|
||||
accounts_service.resolve_draco_user(claims)
|
||||
if claims is not None
|
||||
else None
|
||||
)
|
||||
if user is None:
|
||||
return JSONResponse(
|
||||
{"ok": False, "error": "Authentification draco requise"},
|
||||
status_code=401,
|
||||
)
|
||||
if user["role"] == "admin":
|
||||
lignes = models.list_all_api_tokens()
|
||||
portee = "tous"
|
||||
else:
|
||||
lignes = [
|
||||
{**t, "proprietaire": user["username"]}
|
||||
for t in models.list_api_tokens(user["id"])
|
||||
]
|
||||
portee = "propres"
|
||||
jetons = [
|
||||
{
|
||||
"nom": t["name"],
|
||||
"proprietaire": t["proprietaire"],
|
||||
"prefixe": models.API_TOKEN_PREFIX,
|
||||
"cree_le": t["created_at"],
|
||||
"dernier_usage": t["last_used_at"],
|
||||
"etat": "revoque" if t["revoked_at"] else "actif",
|
||||
}
|
||||
for t in lignes
|
||||
]
|
||||
return {"jetons": jetons, "portee": portee}
|
||||
|
||||
|
||||
@app.get("/api/health")
|
||||
async def health():
|
||||
"""Liveness probe: the process is up. Deliberately dependency-free."""
|
||||
|
||||
@@ -969,6 +969,25 @@ def list_api_tokens(user_id: int) -> list[dict]:
|
||||
]
|
||||
|
||||
|
||||
def list_all_api_tokens() -> list[dict]:
|
||||
"""Métadonnées de TOUS les jetons (registre du Hub, HUB-23).
|
||||
|
||||
Jointure avec le compte propriétaire. La sélection est explicite,
|
||||
colonne par colonne : l'empreinte (`token_hash`) n'en fait jamais
|
||||
partie — cette fonction ne doit alimenter que des écrans de
|
||||
métadonnées, jamais une surface d'authentification."""
|
||||
with get_db() as conn:
|
||||
return [
|
||||
dict(r)
|
||||
for r in conn.execute(
|
||||
"SELECT t.id, t.name, t.created_at, t.last_used_at,"
|
||||
" t.revoked_at, u.username AS proprietaire"
|
||||
" FROM api_tokens t JOIN users u ON u.id = t.user_id"
|
||||
" ORDER BY u.username, t.id"
|
||||
).fetchall()
|
||||
]
|
||||
|
||||
|
||||
def revoke_api_token(user_id: int, token_id: int) -> bool:
|
||||
with get_db() as conn:
|
||||
cur = conn.execute(
|
||||
|
||||
Reference in New Issue
Block a user