"""Admin API: managing collector API keys. Only for users in BACKUP_DASH_ADMINS.""" import logging import sqlite3 from typing import Annotated from fastapi import APIRouter, Depends, HTTPException from pydantic import BaseModel, StringConstraints from . import db, keys from .auth import require_admin, require_page_request log = logging.getLogger("backup_dashboard") router = APIRouter(prefix="/api/v1/admin", dependencies=[Depends(require_admin)]) class NewKey(BaseModel): name: Annotated[str, StringConstraints(strip_whitespace=True, pattern=f"^{keys.NAME_PATTERN}$")] @router.get("/keys") def list_keys(conn: sqlite3.Connection = Depends(db.get_db)): return {"keys": keys.list_keys(conn)} @router.post("/keys", status_code=201, dependencies=[Depends(require_page_request)]) def create_key(body: NewKey, admin: str = Depends(require_admin), conn: sqlite3.Connection = Depends(db.get_db)): try: record, secret = keys.create_key(conn, body.name, created_by=admin) except keys.KeyNameTaken: raise HTTPException(409, f"A key named {body.name!r} already exists (revoked keys keep their names)") from None log.info("admin %s created API key %r", admin, body.name) # The key itself is returned this once; only its hash is stored return {"key": record, "secret": secret} @router.post("/keys/{key_id}/revoke", dependencies=[Depends(require_page_request)]) def revoke_key(key_id: int, admin: str = Depends(require_admin), conn: sqlite3.Connection = Depends(db.get_db)): record = keys.revoke_key(conn, key_id, revoked_by=admin) if record is None: raise HTTPException(404, "Key not found") log.info("admin %s revoked API key %r", admin, record["name"]) return {"key": record}