Files
Api-DataSentinel/routers/authentification.py
Antho 934e669774 refactor(api): découpe main.py en routeurs par domaine
main.py comptait 829 lignes et regroupait 9 domaines fonctionnels.
Il ne fait plus que l'assemblage de l'application (configuration,
middlewares, montage des routeurs), soit 115 lignes.

- routers/ : un module par domaine, chacun déclarant sa propre
  dépendance d'authentification
- domain.py : enums UserRole et AuditAction, mapping MONITO_TABLES ;
  les rôles étaient jusqu'ici répétés en dur à deux endroits
- helpers.py : conversion des lignes pyodbc, écriture du journal
- rate_limit.py : limiteur partagé, isolé pour éviter un import
  circulaire entre main.py et le routeur d'authentification

Les codes HTTP littéraux (404, 401, 400, 201) passent aux constantes
fastapi.status, comme le faisait déjà auth.py. Les actions du journal
d'audit passent en paramètre SQL au lieu d'être concaténées.

Aucune route modifiée : la comparaison des specs OpenAPI avant/après
confirme que les 26 URL existantes sont identiques.

conftest patchait main.get_cursor ; chaque routeur important désormais
get_cursor dans son propre espace de noms, la fixture remplace le nom
dans tous les modules concernés.
2026-08-15 14:28:45 +02:00

48 lines
1.9 KiB
Python

# ============================================================
# routers/authentification.py — Login JWT et profil courant
#
# Routes publiques (aucune dépendance d'authentification) :
# c'est ici qu'on obtient le jeton exigé par les autres routeurs.
# ============================================================
from fastapi import APIRouter, Depends, HTTPException, Request, status
from fastapi.security import OAuth2PasswordRequestForm
from auth import create_access_token, get_current_user, verify_password
from config import get_cursor
from domain import AuditAction
from rate_limit import limiter
router = APIRouter(tags=["Auth"])
@router.post("/auth/login")
@limiter.limit("5/minute")
def login(request: Request, form: OAuth2PasswordRequestForm = Depends()):
"""Authentifie un utilisateur et retourne un JWT."""
with get_cursor() as cursor:
cursor.execute(
"SELECT id_user, username, password_hash, role, actif "
"FROM [USER] WHERE username = ?", form.username
)
row = cursor.fetchone()
if not row or not row[4] or not verify_password(form.password, row[2]):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED, detail="Identifiants invalides"
)
ip = request.client.host if request.client else None
cursor.execute("UPDATE [USER] SET last_login = SYSDATETIME() WHERE id_user = ?", row[0])
cursor.execute(
"INSERT INTO JOURNAL_AUDIT (id_user, username, action, ip) VALUES (?, ?, ?, ?)",
row[0], row[1], AuditAction.LOGIN.value, ip
)
token = create_access_token({"sub": row[1], "role": row[3], "uid": row[0]})
return {"access_token": token, "token_type": "bearer",
"user": {"username": row[1], "role": row[3]}}
@router.get("/auth/me")
def me(user: dict = Depends(get_current_user)):
"""Profil de l'utilisateur connecté."""
return user