""" Shared authentication dependencies for routes. Story 3.9: Extracted from api_key_routes.py and glossary_routes.py Story 6.4: Bind user_id to structlog context when user is resolved. """ import logging from typing import Optional from fastapi import Depends, HTTPException from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials from services.auth_service import verify_token, get_user_by_id logger = logging.getLogger(__name__) security = HTTPBearer(auto_error=False) class ProUser: """Wrapper for authenticated user with tier info.""" def __init__(self, user): self._user = user self.id = user.id self.email = getattr(user, "email", None) self._tier = None @property def tier(self) -> str: if self._tier is None: user_tier = getattr(self._user, "tier", None) if user_tier: self._tier = user_tier else: plan_value = getattr(self._user, "plan", None) if plan_value and hasattr(plan_value, "value"): if plan_value.value in ("pro", "business", "enterprise"): self._tier = "pro" else: self._tier = "free" else: self._tier = "free" return self._tier def require_auth( credentials: Optional[HTTPAuthorizationCredentials] = Depends(security), ): """Dependency that requires a valid JWT token.""" if not credentials: raise HTTPException( status_code=401, detail={ "error": "UNAUTHORIZED", "message": "Authentification requise", }, ) payload = verify_token(credentials.credentials) if not payload: raise HTTPException( status_code=401, detail={ "error": "UNAUTHORIZED", "message": "Token invalide ou expiré", }, ) sub = payload.get("sub") if not sub or not isinstance(sub, str): raise HTTPException( status_code=401, detail={ "error": "UNAUTHORIZED", "message": "Token invalide", }, ) user = get_user_by_id(sub) if not user: raise HTTPException( status_code=401, detail={ "error": "UNAUTHORIZED", "message": "Utilisateur non trouvé", }, ) try: from core.logging import bind_request_context bind_request_context(user_id=str(getattr(user, "id", user))) except Exception: pass return user def require_pro_user(user=Depends(require_auth)) -> ProUser: """Dependency that requires a valid Pro user JWT token.""" pro_user = ProUser(user) if pro_user.tier != "pro": raise HTTPException( status_code=403, detail={ "error": "PRO_FEATURE_REQUIRED", "message": "Cette fonctionnalité nécessite un abonnement Pro.", "details": {"current_tier": pro_user.tier}, }, ) return pro_user