feat(auth): JWT + RBAC, RGPD, sécurité (P0)
Build & Deploy / build (push) Successful in 17s

- auth.py: JWT (python-jose) + bcrypt (passlib), get_current_user, require_admin
- endpoints protégés via APIRouter(dependencies=[get_current_user]); /health public
- /auth/login (rate-limit 5/min) + /auth/me
- /admin/users CRUD + reset-password + /admin/journal (require_admin)
- RGPD: /me/data-export (portabilité) + DELETE /me (oubli/anonymisation)
- en-têtes de sécurité, CORS tous verbes + credentials
- requirements: python-jose, passlib[bcrypt], bcrypt 4.0.1, python-multipart, slowapi
This commit is contained in:
neckfire
2026-06-20 12:49:01 +02:00
parent 51d041c0da
commit 8563e6daac
3 changed files with 286 additions and 20 deletions
+54
View File
@@ -0,0 +1,54 @@
# ============================================================
# auth.py — Authentification JWT + hachage bcrypt
# Data Sentinel
# ============================================================
from datetime import datetime, timedelta, timezone
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from jose import jwt, JWTError
from passlib.context import CryptContext
from config import Config
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="auth/login")
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
def verify_password(plain: str, hashed: str) -> bool:
return pwd_context.verify(plain, hashed)
def hash_password(plain: str) -> str:
return pwd_context.hash(plain)
def create_access_token(data: dict) -> str:
payload = data.copy()
payload["exp"] = datetime.now(timezone.utc) + timedelta(minutes=Config.TOKEN_EXPIRE_MINUTES)
return jwt.encode(payload, Config.SECRET_KEY, algorithm=Config.ALGORITHM)
def get_current_user(token: str = Depends(oauth2_scheme)) -> dict:
"""Décode le JWT et retourne l'utilisateur courant, sinon 401."""
creds_exc = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Identifiants invalides",
headers={"WWW-Authenticate": "Bearer"},
)
try:
payload = jwt.decode(token, Config.SECRET_KEY, algorithms=[Config.ALGORITHM])
username = payload.get("sub")
if not username:
raise creds_exc
return {"username": username, "role": payload.get("role"), "id_user": payload.get("uid")}
except JWTError:
raise creds_exc
def require_admin(user: dict = Depends(get_current_user)) -> dict:
"""Réserve l'accès aux administrateurs (403 sinon)."""
if user.get("role") != "Admin":
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Accès réservé aux administrateurs")
return user