""" 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)