"""CSRF token per-sesiune + validare. Contract pentru rutele POST web: - Formulare HTML includ: - Handler-ul POST apeleaza: verify_csrf(request, form.get("csrf_token")) - La nepotrivire/lipsa: CsrfError -> @app.exception_handler(CsrfError) -> 403 Token e per-sesiune (stabil pana la logout), generat lazy la primul acces. """ from __future__ import annotations import hmac import secrets from starlette.requests import Request from ..config import get_settings class CsrfError(Exception): """Token CSRF lipsa sau invalid. Prins de exception_handler in main.py -> 403.""" def get_csrf_token(request: Request) -> str: """Intoarce tokenul CSRF al sesiunii, generandu-l daca lipseste.""" token = request.session.get("csrf_token") if not token: token = secrets.token_urlsafe(32) request.session["csrf_token"] = token return token def verify_csrf(request: Request, submitted: str | None) -> None: """Verifica tokenul CSRF trimis in formular. Gateaza pe MOD, nu pe account_id: - prod (web_auth_required=True): enforce pe TOATE rutele POST, inclusiv /login si /signup unde atacatorul ar putea forta victima sa se logheze in contul sau (login CSRF). GET-urile de formular genereaza token in sesiune via get_csrf_token. - dev/test (web_auth_required=False, fara account_id): skip transparent, testele existente raman verzi fara sa fie nevoie de token. - sesiune autentificata (account_id in sesiune): enforce indiferent de mod. """ settings = get_settings() enforce = settings.web_auth_required or request.session.get("account_id") is not None if not enforce: return # dev fara auth: CSRF neaplicabil expected = request.session.get("csrf_token") if not expected or not submitted or not hmac.compare_digest(expected.encode(), submitted.encode()): raise CsrfError("token CSRF invalid")