Spaces:
Running
Running
| import base64 | |
| import hashlib | |
| import json | |
| import logging | |
| from fastapi import Depends, HTTPException, Request | |
| from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer | |
| logger = logging.getLogger(__name__) | |
| security = HTTPBearer(auto_error=False) | |
| async def get_user_id( | |
| credentials: HTTPAuthorizationCredentials | None = Depends(security), | |
| ) -> str | None: | |
| """Extract Supabase user_id from the Bearer JWT, or None for anonymous users.""" | |
| if credentials is None: | |
| return None | |
| try: | |
| parts = credentials.credentials.split(".") | |
| if len(parts) != 3: | |
| return None | |
| payload = parts[1] | |
| padding = 4 - len(payload) % 4 | |
| if padding != 4: | |
| payload += "=" * padding | |
| decoded = base64.urlsafe_b64decode(payload) | |
| claims = json.loads(decoded) | |
| return claims.get("sub") | |
| except Exception: | |
| logger.debug("Failed to decode JWT", exc_info=True) | |
| return None | |
| async def require_user_id( | |
| credentials: HTTPAuthorizationCredentials | None = Depends(security), | |
| ) -> str: | |
| """Like get_user_id but raises 401 if no valid JWT is present.""" | |
| uid = await get_user_id(credentials) | |
| if not uid: | |
| raise HTTPException(status_code=401, detail="Authentication required") | |
| return uid | |
| async def get_user_id_from_api_key(request: Request) -> str | None: | |
| """Authenticate via X-API-Key header. Returns user_id or None.""" | |
| api_key = request.headers.get("X-API-Key") | |
| if not api_key: | |
| return None | |
| key_hash = hashlib.sha256(api_key.encode()).hexdigest() | |
| try: | |
| from app.services.supabase import get_supabase | |
| supabase = get_supabase() | |
| result = supabase.table("api_keys").select("user_id").eq("key_hash", key_hash).execute() | |
| if result.data: | |
| uid = result.data[0]["user_id"] | |
| supabase.table("api_keys").update({"last_used_at": "now()"}).eq("key_hash", key_hash).execute() | |
| return uid | |
| except Exception: | |
| pass | |
| return None | |
| async def require_user_or_api_key( | |
| credentials: HTTPAuthorizationCredentials | None = Depends(security), | |
| request: Request = None, | |
| ) -> str: | |
| """Accepts either a Bearer JWT or X-API-Key header.""" | |
| uid = await get_user_id(credentials) | |
| if uid: | |
| return uid | |
| uid = await get_user_id_from_api_key(request) | |
| if uid: | |
| return uid | |
| raise HTTPException(status_code=401, detail="Authentication required") | |