Spaces:
Running
Running
| from __future__ import annotations | |
| from fastapi import APIRouter, HTTPException, Query, Request, Response, status | |
| from app.security.errors import APIKeyConflictError, APIKeyNotFoundError | |
| from app.security.schemas import ( | |
| APIKeyCreate, | |
| APIKeyCreated, | |
| APIKeyList, | |
| APIKeyPatch, | |
| APIKeyRotate, | |
| APIKeyView, | |
| AuthContextView, | |
| AuditLogView, | |
| ) | |
| from app.security.scopes import ALL_SCOPES | |
| router = APIRouter(prefix="/v1", tags=["authentication"]) | |
| def _view(record: object) -> APIKeyView: | |
| return APIKeyView.model_validate(record) | |
| def _not_found() -> HTTPException: | |
| return HTTPException(status_code=404, detail="API key was not found") | |
| async def current_auth_context(request: Request) -> AuthContextView: | |
| """Validate a key and return only its safe, non-secret authorization context.""" | |
| context = request.state.auth | |
| return AuthContextView( | |
| id=context.api_key_id, | |
| name=context.key_name, | |
| key_prefix=context.key_prefix, | |
| environment=context.environment, | |
| role=context.role, | |
| scopes=sorted(context.scopes), | |
| expires_at=context.expires_at, | |
| workspace_id=context.workspace_id, | |
| user_id=context.user_id, | |
| membership_role=context.membership_role, | |
| ) | |
| async def api_key_capabilities(request: Request) -> dict[str, object]: | |
| container = request.app.state.container | |
| return { | |
| "scopes": sorted(ALL_SCOPES), | |
| "roles": { | |
| role: sorted(scopes) | |
| for role, scopes in container.api_keys.roles.items() | |
| }, | |
| "defaults": { | |
| "requests_per_minute": container.settings.auth_default_requests_per_minute, | |
| "concurrent_jobs": container.settings.auth_default_concurrent_jobs, | |
| "uploads_per_hour": container.settings.auth_default_uploads_per_hour, | |
| "processing_bytes_per_day": ( | |
| container.settings.auth_default_processing_bytes_per_day | |
| ), | |
| }, | |
| } | |
| async def list_api_keys( | |
| request: Request, | |
| offset: int = Query(default=0, ge=0), | |
| limit: int = Query(default=100, ge=1, le=500), | |
| ) -> APIKeyList: | |
| records, total = await request.app.state.container.api_keys.list( | |
| offset=offset, limit=limit | |
| ) | |
| return APIKeyList(items=[_view(record) for record in records], total=total) | |
| async def create_api_key(request: Request, payload: APIKeyCreate) -> APIKeyCreated: | |
| context = request.state.auth | |
| try: | |
| record, secret = await request.app.state.container.api_keys.create( | |
| payload, | |
| created_by=context.api_key_id, | |
| workspace_id=context.workspace_id, | |
| user_id=context.user_id, | |
| ) | |
| except APIKeyConflictError as exc: | |
| raise HTTPException(status_code=409, detail=str(exc)) from exc | |
| return APIKeyCreated(**_view(record).model_dump(), api_key=secret) | |
| async def get_api_key(request: Request, key_id: str) -> APIKeyView: | |
| try: | |
| return _view(await request.app.state.container.api_keys.get(key_id)) | |
| except APIKeyNotFoundError as exc: | |
| raise _not_found() from exc | |
| async def patch_api_key( | |
| request: Request, key_id: str, payload: APIKeyPatch | |
| ) -> APIKeyView: | |
| try: | |
| return _view(await request.app.state.container.api_keys.patch(key_id, payload)) | |
| except APIKeyNotFoundError as exc: | |
| raise _not_found() from exc | |
| except APIKeyConflictError as exc: | |
| raise HTTPException(status_code=409, detail=str(exc)) from exc | |
| async def revoke_api_key(request: Request, key_id: str) -> Response: | |
| try: | |
| await request.app.state.container.api_keys.set_status(key_id, "revoked") | |
| except APIKeyNotFoundError as exc: | |
| raise _not_found() from exc | |
| return Response(status_code=status.HTTP_204_NO_CONTENT) | |
| async def rotate_api_key( | |
| request: Request, key_id: str, payload: APIKeyRotate | |
| ) -> APIKeyCreated: | |
| try: | |
| record, secret = await request.app.state.container.api_keys.rotate( | |
| key_id, | |
| payload.grace_period_seconds, | |
| created_by=request.state.auth.api_key_id, | |
| ) | |
| except APIKeyNotFoundError as exc: | |
| raise _not_found() from exc | |
| except APIKeyConflictError as exc: | |
| raise HTTPException(status_code=409, detail=str(exc)) from exc | |
| return APIKeyCreated(**_view(record).model_dump(), api_key=secret) | |
| async def disable_api_key(request: Request, key_id: str) -> APIKeyView: | |
| try: | |
| return _view( | |
| await request.app.state.container.api_keys.set_status(key_id, "disabled") | |
| ) | |
| except APIKeyNotFoundError as exc: | |
| raise _not_found() from exc | |
| except APIKeyConflictError as exc: | |
| raise HTTPException(status_code=409, detail=str(exc)) from exc | |
| async def enable_api_key(request: Request, key_id: str) -> APIKeyView: | |
| try: | |
| return _view( | |
| await request.app.state.container.api_keys.set_status(key_id, "active") | |
| ) | |
| except APIKeyNotFoundError as exc: | |
| raise _not_found() from exc | |
| except APIKeyConflictError as exc: | |
| raise HTTPException(status_code=409, detail=str(exc)) from exc | |
| async def list_audit_logs( | |
| request: Request, | |
| offset: int = Query(default=0, ge=0), | |
| limit: int = Query(default=100, ge=1, le=500), | |
| ) -> list[AuditLogView]: | |
| records = await request.app.state.container.audit.list(offset=offset, limit=limit) | |
| return [AuditLogView.model_validate(record) for record in records] | |