Spaces:
Running
Running
Download app/api/api_keys.py from basyx/MediaRouter: direct link, hf CLI and curl.
- Browser
- Download file 9.46 kB
-
https://huggingface.co/spaces/basyx/MediaRouter/resolve/main/app/api/api_keys.py
- Command line
-
hf download hf://spaces/basyx/MediaRouter/app/api/api_keys.py
-
curl -L -o api_keys.py https://huggingface.co/spaces/basyx/MediaRouter/resolve/main/app/api/api_keys.py
9.46 kB
| from __future__ import annotations | |
| from fastapi import APIRouter, HTTPException, Query, Request, Response, status | |
| from sqlalchemy import func, select | |
| from app.security.models import APIKey, APIKeyPrincipal, AuditLog | |
| 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") | |
| def _require_workspace_key_admin(request: Request): | |
| """Require a resolved user membership with workspace owner/admin authority.""" | |
| context = getattr(request.state, "auth", None) | |
| if ( | |
| context is None | |
| or not context.workspace_id | |
| or not context.user_id | |
| or (context.membership_role or "").strip().lower() not in {"owner", "admin"} | |
| or ( | |
| getattr(request.state, "studio_identity_verified", False) is not True | |
| and "admin" not in context.scopes | |
| ) | |
| ): | |
| raise HTTPException(status_code=403, detail="Workspace owner or admin membership is required") | |
| return context | |
| async def _require_key_in_workspace(request: Request, key_id: str, context) -> None: | |
| session_factory = request.app.state.container.security_database.session | |
| async with session_factory() as session: | |
| principal = await session.scalar( | |
| select(APIKeyPrincipal).where( | |
| APIKeyPrincipal.api_key_id == key_id, | |
| APIKeyPrincipal.workspace_id == context.workspace_id, | |
| APIKeyPrincipal.status == "active", | |
| ) | |
| ) | |
| if principal is None: | |
| # Do not disclose whether another workspace owns the identifier. | |
| raise _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]: | |
| _require_workspace_key_admin(request) | |
| 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: | |
| context = _require_workspace_key_admin(request) | |
| await request.app.state.container.api_keys._finalize_expired_rotations() | |
| async with request.app.state.container.security_database.session() as session: | |
| base = ( | |
| select(APIKey) | |
| .join(APIKeyPrincipal, APIKeyPrincipal.api_key_id == APIKey.id) | |
| .where( | |
| APIKeyPrincipal.workspace_id == context.workspace_id, | |
| APIKeyPrincipal.status == "active", | |
| ) | |
| ) | |
| total = int(await session.scalar(select(func.count()).select_from(base.subquery())) or 0) | |
| records = list((await session.scalars( | |
| base.order_by(APIKey.created_at.desc()).offset(offset).limit(limit) | |
| )).all()) | |
| return APIKeyList(items=[_view(record) for record in records], total=total) | |
| async def create_api_key(request: Request, payload: APIKeyCreate) -> APIKeyCreated: | |
| context = _require_workspace_key_admin(request) | |
| 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: | |
| context = _require_workspace_key_admin(request) | |
| await _require_key_in_workspace(request, key_id, context) | |
| 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: | |
| context = _require_workspace_key_admin(request) | |
| await _require_key_in_workspace(request, key_id, context) | |
| 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: | |
| context = _require_workspace_key_admin(request) | |
| await _require_key_in_workspace(request, key_id, context) | |
| 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: | |
| context = _require_workspace_key_admin(request) | |
| await _require_key_in_workspace(request, key_id, context) | |
| 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: | |
| context = _require_workspace_key_admin(request) | |
| await _require_key_in_workspace(request, key_id, context) | |
| 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: | |
| context = _require_workspace_key_admin(request) | |
| await _require_key_in_workspace(request, key_id, context) | |
| 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]: | |
| context = _require_workspace_key_admin(request) | |
| async with request.app.state.container.security_database.session() as session: | |
| records = list((await session.scalars( | |
| select(AuditLog) | |
| .join(APIKeyPrincipal, APIKeyPrincipal.api_key_id == AuditLog.api_key_id) | |
| .where( | |
| APIKeyPrincipal.workspace_id == context.workspace_id, | |
| APIKeyPrincipal.status == "active", | |
| ) | |
| .order_by(AuditLog.created_at.desc()) | |
| .offset(offset) | |
| .limit(limit) | |
| )).all()) | |
| return [AuditLogView.model_validate(record) for record in records] | |