107 lines
2.9 KiB
Python
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
|