166 lines
5.5 KiB
Python
166 lines
5.5 KiB
Python
from fastapi import Depends, HTTPException, status, Request
|
|
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
from sqlalchemy import select
|
|
from database import get_session, User
|
|
from security.auth import decode_token
|
|
from typing import Optional
|
|
from datetime import datetime
|
|
import logging
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
security = HTTPBearer(auto_error=False)
|
|
|
|
|
|
async def get_current_user_hybrid(
|
|
request: Request,
|
|
credentials: Optional[HTTPAuthorizationCredentials] = Depends(security),
|
|
session: AsyncSession = Depends(get_session),
|
|
) -> User:
|
|
if credentials and credentials.credentials:
|
|
token = credentials.credentials
|
|
|
|
payload = decode_token(token)
|
|
if not payload:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail="Token invalide ou expiré",
|
|
headers={"WWW-Authenticate": "Bearer"},
|
|
)
|
|
|
|
if payload.get("type") != "access":
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail="Type de token incorrect",
|
|
headers={"WWW-Authenticate": "Bearer"},
|
|
)
|
|
|
|
user_id: str = payload.get("sub")
|
|
if not user_id:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail="Token malformé",
|
|
headers={"WWW-Authenticate": "Bearer"},
|
|
)
|
|
|
|
result = await session.execute(select(User).where(User.id == user_id))
|
|
user = result.scalar_one_or_none()
|
|
|
|
if not user:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail="Utilisateur introuvable",
|
|
headers={"WWW-Authenticate": "Bearer"},
|
|
)
|
|
|
|
if not user.is_active:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_403_FORBIDDEN, detail="Compte désactivé"
|
|
)
|
|
|
|
if not user.is_verified:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_403_FORBIDDEN,
|
|
detail="Email non vérifié. Consultez votre boîte de réception.",
|
|
)
|
|
|
|
if user.locked_until and user.locked_until > datetime.now():
|
|
raise HTTPException(
|
|
status_code=status.HTTP_403_FORBIDDEN,
|
|
detail="Compte temporairement verrouillé suite à trop de tentatives échouées",
|
|
)
|
|
|
|
logger.debug(f" Authentifié via JWT: {user.email}")
|
|
return user
|
|
|
|
api_key_obj = getattr(request.state, "api_key", None)
|
|
|
|
if api_key_obj:
|
|
if api_key_obj.user_id:
|
|
result = await session.execute(
|
|
select(User).where(User.id == api_key_obj.user_id)
|
|
)
|
|
user = result.scalar_one_or_none()
|
|
|
|
if user:
|
|
logger.debug(
|
|
f" Authentifié via API Key ({api_key_obj.name}) → User: {user.email}"
|
|
)
|
|
return user
|
|
|
|
from database import User as UserModel
|
|
|
|
virtual_user = UserModel(
|
|
id=f"api_key_{api_key_obj.id}",
|
|
email=f"{api_key_obj.name.lower().replace(' ', '_')}@api-key.local",
|
|
nom="API Key",
|
|
prenom=api_key_obj.name,
|
|
role="api_client",
|
|
is_verified=True,
|
|
is_active=True,
|
|
)
|
|
|
|
virtual_user._is_api_key_user = True
|
|
virtual_user._api_key_obj = api_key_obj
|
|
|
|
logger.debug(f" Authentifié via API Key: {api_key_obj.name} (user virtuel)")
|
|
return virtual_user
|
|
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail="Authentification requise (JWT ou API Key)",
|
|
headers={"WWW-Authenticate": "Bearer"},
|
|
)
|
|
|
|
|
|
async def get_current_user_optional_hybrid(
|
|
request: Request,
|
|
credentials: Optional[HTTPAuthorizationCredentials] = Depends(security),
|
|
session: AsyncSession = Depends(get_session),
|
|
) -> Optional[User]:
|
|
"""Version optionnelle de get_current_user_hybrid (ne lève pas d'erreur)"""
|
|
try:
|
|
return await get_current_user_hybrid(request, credentials, session)
|
|
except HTTPException:
|
|
return None
|
|
|
|
|
|
def require_role(*allowed_roles: str):
|
|
async def role_checker(
|
|
request: Request, user: User = Depends(get_current_user_hybrid)
|
|
) -> User:
|
|
is_api_key_user = getattr(user, "_is_api_key_user", False)
|
|
|
|
if is_api_key_user:
|
|
if "api_client" not in allowed_roles and "*" not in allowed_roles:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_403_FORBIDDEN,
|
|
detail=f"Accès refusé. Rôles requis: {', '.join(allowed_roles)}. API Keys non autorisées.",
|
|
)
|
|
logger.debug(" API Key autorisée pour cette route")
|
|
return user
|
|
|
|
if user.role not in allowed_roles and "*" not in allowed_roles:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_403_FORBIDDEN,
|
|
detail=f"Accès refusé. Rôles requis: {', '.join(allowed_roles)}",
|
|
)
|
|
|
|
return user
|
|
|
|
return role_checker
|
|
|
|
|
|
def is_api_key_user(user: User) -> bool:
|
|
"""Vérifie si l'utilisateur est authentifié via API Key"""
|
|
return getattr(user, "_is_api_key_user", False)
|
|
|
|
|
|
def get_api_key_from_user(user: User):
|
|
"""Récupère l'objet API Key depuis un user virtuel"""
|
|
return getattr(user, "_api_key_obj", None)
|
|
|
|
|
|
get_current_user = get_current_user_hybrid
|
|
get_current_user_optional = get_current_user_optional_hybrid
|