Spaces:
Running
Running
| from __future__ import annotations | |
| import os | |
| import time | |
| from typing import Any | |
| from fastapi import HTTPException, Request | |
| _SUPABASE_TOKEN_CACHE: dict[str, tuple[float, dict[str, Any]]] = {} | |
| async def verify_supabase_access_token(access_token: str) -> dict[str, Any]: | |
| """ | |
| Verifies a Supabase access token by calling Supabase Auth `GET /auth/v1/user`. | |
| Uses a small in-memory TTL cache to avoid calling Supabase on every request. | |
| """ | |
| access_token = (access_token or "").strip() | |
| if not access_token: | |
| raise HTTPException(status_code=401, detail={"code": "missing_token", "message": "Missing access token"}) | |
| now = time.time() | |
| cached = _SUPABASE_TOKEN_CACHE.get(access_token) | |
| if cached and (now - cached[0]) < 30: | |
| return cached[1] | |
| supabase_url = os.environ.get("SUPABASE_URL") | |
| supabase_key = os.environ.get("SUPABASE_KEY") or os.environ.get("SUPABASE_ANON_KEY") | |
| if not supabase_url or not supabase_key: | |
| raise HTTPException( | |
| status_code=503, | |
| detail={"code": "supabase_not_configured", "message": "Supabase is not configured"}, | |
| ) | |
| import httpx | |
| headers = {"Authorization": f"Bearer {access_token}", "apikey": supabase_key} | |
| url = f"{supabase_url.rstrip('/')}/auth/v1/user" | |
| async with httpx.AsyncClient(timeout=10.0) as client: | |
| resp = await client.get(url, headers=headers) | |
| if resp.status_code != 200: | |
| raise HTTPException(status_code=401, detail={"code": "invalid_session", "message": "Invalid or expired session"}) | |
| user = resp.json() | |
| _SUPABASE_TOKEN_CACHE[access_token] = (now, user) | |
| # Best-effort cache bound. | |
| if len(_SUPABASE_TOKEN_CACHE) > 500: | |
| for k in list(_SUPABASE_TOKEN_CACHE.keys())[:200]: | |
| _SUPABASE_TOKEN_CACHE.pop(k, None) | |
| return user | |
| async def require_user_from_request(request: Request) -> dict[str, Any]: | |
| auth = (request.headers.get("authorization") or "").strip() | |
| if not auth.lower().startswith("bearer "): | |
| raise HTTPException( | |
| status_code=401, | |
| detail={"code": "missing_bearer_token", "message": "Missing Authorization bearer token"}, | |
| ) | |
| token = auth.split(None, 1)[1].strip() | |
| return await verify_supabase_access_token(token) | |