"""Authentification JWT + hachage PIN.""" from datetime import datetime, timedelta, timezone from jose import jwt, JWTError from fastapi import Depends, HTTPException, status from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select import bcrypt from app.core.config import settings from app.core.database import get_db security = HTTPBearer() def hash_pin(pin: str) -> str: return bcrypt.hashpw(pin.encode("utf-8"), bcrypt.gensalt(rounds=12)).decode("utf-8") def verify_pin(pin: str, hashed: str) -> bool: return bcrypt.checkpw(pin.encode("utf-8"), hashed.encode("utf-8")) def create_token(membre_id: str, groupe_id: str) -> str: expire = datetime.now(timezone.utc) + timedelta(days=settings.JWT_EXPIRE_DAYS) payload = {"sub": str(membre_id), "grp": str(groupe_id), "exp": expire} return jwt.encode(payload, settings.JWT_SECRET, algorithm=settings.JWT_ALGORITHM) def decode_token(token: str) -> dict: try: return jwt.decode(token, settings.JWT_SECRET, algorithms=[settings.JWT_ALGORITHM]) except JWTError: raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Token invalide ou expiré") async def get_current_membre( credentials: HTTPAuthorizationCredentials = Depends(security), db: AsyncSession = Depends(get_db), ): from app.models.membre import Membre payload = decode_token(credentials.credentials) membre_id = payload.get("sub") if not membre_id: raise HTTPException(status_code=401, detail="Token invalide") result = await db.execute(select(Membre).where(Membre.id == membre_id, Membre.actif == True)) membre = result.scalar_one_or_none() if not membre: raise HTTPException(status_code=401, detail="Membre introuvable ou inactif") return membre async def require_executif( membre=Depends(get_current_membre), db: AsyncSession = Depends(get_db), ): from app.models.affectation import Affectation from app.models.poste import Poste result = await db.execute( select(Affectation).join(Poste).where( Affectation.membre_id == membre.id, Affectation.statut == "actif", Poste.categorie == "executif", ) ) if not result.scalar_one_or_none(): raise HTTPException(status_code=403, detail="Réservé à l'exécutif") return membre