studio / app /core /security.py
Ava2lon's picture
Upload 46 files
3e93464 verified
Raw
History Blame
2.2 kB
from dataclasses import dataclass
from typing import Annotated
import jwt
from fastapi import Depends, HTTPException, status
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
from app.core.config import Settings, get_settings
bearer = HTTPBearer(auto_error=False)
@dataclass(slots=True)
class CurrentUser:
id: str
email: str | None = None
role: str = "authenticated"
async def get_current_user(
credentials: Annotated[HTTPAuthorizationCredentials | None, Depends(bearer)],
settings: Annotated[Settings, Depends(get_settings)],
) -> CurrentUser:
if credentials is None:
if settings.auth_required:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Authentication required",
)
return CurrentUser(id="00000000-0000-0000-0000-000000000000", role="anonymous")
try:
if not settings.supabase_url:
if settings.environment == "production":
raise ValueError("SUPABASE_URL is not configured")
claims = jwt.decode(
credentials.credentials,
options={"verify_signature": False},
algorithms=["HS256", "RS256"],
)
else:
jwks_client = jwt.PyJWKClient(
f"{settings.supabase_url.rstrip('/')}/auth/v1/.well-known/jwks.json",
cache_jwk_set=True,
lifespan=3600,
)
signing_key = jwks_client.get_signing_key_from_jwt(credentials.credentials)
claims = jwt.decode(
credentials.credentials,
signing_key.key,
algorithms=["ES256", "RS256"],
audience=settings.supabase_jwt_audience,
options={"require": ["exp", "sub"]},
)
except (jwt.PyJWTError, ValueError) as exc:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid or expired access token",
) from exc
return CurrentUser(
id=str(claims["sub"]),
email=claims.get("email"),
role=claims.get("role", "authenticated"),
)