feat: revue de code, doc CODE_REVIEW, forfaits 2026, traduction LLM, providers avec modèle
Made-with: Cursor
This commit is contained in:
106
routes/deps.py
Normal file
106
routes/deps.py
Normal file
@@ -0,0 +1,106 @@
|
||||
"""
|
||||
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
|
||||
Reference in New Issue
Block a user