Spaces:
Sleeping
Sleeping
| """ | |
| FastAPI dependencies for authentication. | |
| """ | |
| from datetime import datetime, timedelta, timezone | |
| from typing import Optional | |
| from fastapi import Depends, HTTPException, status | |
| from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials | |
| from fastapi.concurrency import run_in_threadpool | |
| from jose import JWTError, jwt | |
| import database | |
| from config import JWT_SECRET | |
| _bearer = HTTPBearer(auto_error=False) | |
| _ALGORITHM = "HS256" | |
| _TOKEN_EXPIRE_DAYS = 30 | |
| def create_access_token(data: dict) -> str: | |
| payload = data.copy() | |
| payload["exp"] = datetime.now(timezone.utc) + timedelta(days=_TOKEN_EXPIRE_DAYS) | |
| return jwt.encode(payload, JWT_SECRET, algorithm=_ALGORITHM) | |
| async def _decode_token(credentials: Optional[HTTPAuthorizationCredentials]) -> Optional[dict]: | |
| if credentials is None: | |
| return None | |
| try: | |
| payload = jwt.decode(credentials.credentials, JWT_SECRET, algorithms=[_ALGORITHM]) | |
| user_id: str = payload.get("sub") | |
| if not user_id: | |
| return None | |
| return await run_in_threadpool(database.get_user_by_id, user_id) | |
| except JWTError: | |
| return None | |
| async def get_current_user( | |
| credentials: Optional[HTTPAuthorizationCredentials] = Depends(_bearer), | |
| ) -> dict: | |
| """Require a valid JWT; raise 401 otherwise.""" | |
| user = await _decode_token(credentials) | |
| if user is None: | |
| raise HTTPException( | |
| status_code=status.HTTP_401_UNAUTHORIZED, | |
| detail="Not authenticated.", | |
| headers={"WWW-Authenticate": "Bearer"}, | |
| ) | |
| return user | |
| async def get_optional_user( | |
| credentials: Optional[HTTPAuthorizationCredentials] = Depends(_bearer), | |
| ) -> Optional[dict]: | |
| """Return the authenticated user or None (for routes that work with or without auth).""" | |
| return await _decode_token(credentials) | |