"""API Token routes — create, list, revoke Bearer tokens for programmatic access.""" from __future__ import annotations import uuid from datetime import datetime, timedelta, timezone from typing import Any from fastapi import APIRouter, Depends, HTTPException, status from pydantic import BaseModel from sqlalchemy.ext.asyncio import AsyncSession from app.core.api_token import create_api_token, list_api_tokens, revoke_api_token from app.core.db import get_db from app.deps import get_current_user, require_permission router = APIRouter(prefix="/api/v1/tokens", tags=["api-tokens"]) class TokenCreateRequest(BaseModel): name: str scopes: list[str] = [] expires_in_days: int | None = None class TokenRevokeRequest(BaseModel): token_id: str @router.post("", status_code=status.HTTP_201_CREATED) async def create_token( body: TokenCreateRequest, db: AsyncSession = Depends(get_db), current_user: dict = Depends(require_permission("mcp:write")), ): """Create a new API token. The plaintext token is returned ONCE.""" tenant_id = uuid.UUID(current_user["tenant_id"]) user_id = uuid.UUID(current_user["user_id"]) expires_at = None if body.expires_in_days is not None: expires_at = datetime.now(timezone.utc) + timedelta(days=body.expires_in_days) result = await create_api_token( db, tenant_id, user_id, body.name, body.scopes, expires_at, ) await db.commit() return result @router.get("") async def list_tokens( db: AsyncSession = Depends(get_db), current_user: dict = Depends(require_permission("mcp:read")), ): """List all API tokens for the current user (without token hashes).""" tenant_id = uuid.UUID(current_user["tenant_id"]) user_id = uuid.UUID(current_user["user_id"]) tokens = await list_api_tokens(db, tenant_id, user_id) return {"items": tokens, "total": len(tokens)} @router.delete("/{token_id}", status_code=status.HTTP_204_NO_CONTENT) async def revoke_token( token_id: str, db: AsyncSession = Depends(get_db), current_user: dict = Depends(require_permission("mcp:write")), ): """Revoke an API token.""" tenant_id = uuid.UUID(current_user["tenant_id"]) try: tid = uuid.UUID(token_id) except ValueError: raise HTTPException(400, detail={"detail": "Invalid token_id", "code": "invalid_id"}) revoked = await revoke_api_token(db, tenant_id, tid) if not revoked: raise HTTPException(404, detail={"detail": "Token not found or already revoked", "code": "not_found"}) await db.commit()