corpora / app /core /security.py
MiKaustubh007's picture
Upload folder using huggingface_hub
524e6d5 verified
Raw
History Blame Contribute Delete
2.23 kB
"""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"
@dataclass
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