feat(backend): documente le contrat d'erreur dans l'OpenAPI

Le schéma ne déclarait aucun code d'erreur : ni 401, ni 403, ni 404, ni 409,
ni 429. Swagger affirmait que /auth/login ne pouvait répondre que 200 ou 422,
alors que 31-contrat-authentification.md décrit ces codes comme le contrat que
le frontend doit traiter.

Le 422 publié était pire qu'absent : le schéma exposait HTTPValidationError,
le modèle par défaut de FastAPI avec sa clé `loc`, quand
validation_error_handler renvoie {"detail": [{"champ", "type"}]}. Un client
codé sur la documentation lisait une clé qui n'arrive jamais.

Les métadonnées arrivent avec : description, résumé et une description par
tag. `servers`, `license_info` et `contact` restent absents, ils poseraient
des décisions qui ne sont pas prises.

Le cookie de rafraîchissement devient visible par un APIKeyCookie en
auto_error=False, purement documentaire : lit_le_cookie() reste seul maître du
401 de /auth/refresh.

Au passage, health.py posait son tag deux fois, une fois sur son APIRouter et
une fois à l'include_router.
This commit is contained in:
Johan LEROY
2026-09-16 10:12:41 +02:00
parent 44468e85d7
commit 344f82fcdd
8 changed files with 245 additions and 11 deletions
+2 -1
View File
@@ -8,6 +8,7 @@ Environment = Literal["local", "dev", "staging", "prod"]
SameSite = Literal["lax", "strict", "none"]
SECRET_KEY_MIN_LENGTH = 32
REFRESH_COOKIE_DEFAUT = "ev_refresh"
SENTINELLES_INTERDITES = frozenset(
{"change_me", "changeme", "secret", "secret-de-test", "changez-moi", "todo"}
)
@@ -38,7 +39,7 @@ class Settings(BaseSettings):
access_token_ttl_seconds: int = Field(default=900, ge=60, le=3600)
refresh_token_ttl_seconds: int = Field(default=604800, ge=3600, le=2592000)
refresh_cookie_name: str = "ev_refresh"
refresh_cookie_name: str = REFRESH_COOKIE_DEFAUT
cookie_path: str = "/api/v1/auth"
cookie_samesite: SameSite = "strict"
cookie_secure: bool | None = None