Spaces:
Runtime error
Runtime error
| """Auth: dev-mode bypass or Clerk JWT verification via JWKS.""" | |
| import time | |
| from dataclasses import dataclass | |
| import httpx | |
| import jwt | |
| from fastapi import Depends, HTTPException, Request | |
| from jwt import PyJWKClient | |
| from app.core.config import Settings, get_settings | |
| DEV_TENANT = "dev-tenant" | |
| DEV_USER = "dev-user" | |
| class Principal: | |
| user_id: str | |
| tenant_id: str # Clerk org id when present, else user id (personal tenant) | |
| _jwks_client: PyJWKClient | None = None | |
| def _get_jwks_client(settings: Settings) -> PyJWKClient: | |
| global _jwks_client | |
| if _jwks_client is None: | |
| url = settings.clerk_jwks_url | |
| if not url: | |
| raise HTTPException(status_code=500, detail="CLERK_JWKS_URL not configured") | |
| _jwks_client = PyJWKClient(url, cache_keys=True) | |
| return _jwks_client | |
| def _verify_clerk_token(token: str, settings: Settings) -> Principal: | |
| try: | |
| signing_key = _get_jwks_client(settings).get_signing_key_from_jwt(token) | |
| claims = jwt.decode( | |
| token, | |
| signing_key.key, | |
| algorithms=["RS256"], | |
| options={"verify_aud": False}, | |
| ) | |
| except jwt.PyJWTError as exc: | |
| raise HTTPException(status_code=401, detail=f"Invalid token: {exc}") from exc | |
| if claims.get("exp", 0) < time.time(): | |
| raise HTTPException(status_code=401, detail="Token expired") | |
| user_id = claims["sub"] | |
| tenant_id = claims.get("org_id") or user_id | |
| return Principal(user_id=user_id, tenant_id=tenant_id) | |
| async def get_principal( | |
| request: Request, settings: Settings = Depends(get_settings) | |
| ) -> Principal: | |
| if settings.auth_mode == "dev": | |
| return Principal(user_id=DEV_USER, tenant_id=DEV_TENANT) | |
| auth = request.headers.get("Authorization", "") | |
| if not auth.startswith("Bearer "): | |
| raise HTTPException(status_code=401, detail="Missing bearer token") | |
| return _verify_clerk_token(auth.removeprefix("Bearer "), settings) | |
| async def clerk_healthcheck(settings: Settings) -> bool: | |
| if settings.auth_mode == "dev": | |
| return True | |
| async with httpx.AsyncClient(timeout=5) as client: | |
| resp = await client.get(settings.clerk_jwks_url) | |
| return resp.status_code == 200 | |