Files
office_translator/routes/deps.py
2026-03-07 11:42:58 +01:00

107 lines
2.9 KiB
Python

"""
Shared authentication dependencies for routes.
Story 3.9: Extracted from api_key_routes.py and glossary_routes.py
"""
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é",
},
)
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