marvin/62-API-REST-brique-authentification #63

Merged
marvin merged 24 commits from marvin/62-API-REST-brique-authentification into develop 2026-09-03 07:15:21 +00:00
Member

Ce que ça change

Toute la brique d'authentification de l'API : création et gestion des comptes (par un administrateur), connexion par mot de passe, session maintenue en cookies httpOnly, rôles, et le durcissement associé. Les jetons sont émis par l'API - JWT d'accès court, jeton de rafraîchissement opaque à rotation. Seul point d'entrée d'authentification du tableau de bord.

Closes #

Preuve

image
image
image
image


Relecture

  • Un pair a relu et laissé un commentaire, même court
  • Ses remarques sont traitées, ou une réponse explique pourquoi elles ne le sont pas

Ce qui suit le code

  • docs/runbooks/ mis à jour, un geste d'exploitation a changé
  • docs/adr/ complété, une décision structurante a été prise
  • docs/journal.md complété, un incident a été rencontré
  • Une nouvelle variable d'environnement est apparue, elle est dans .env du projet back de plus un .env.exemple a été créer
## Ce que ça change Toute la brique d'authentification de l'API : création et gestion des comptes (par un administrateur), connexion par mot de passe, session maintenue en cookies httpOnly, rôles, et le durcissement associé. Les jetons sont émis par l'API - JWT d'accès court, jeton de rafraîchissement opaque à rotation. Seul point d'entrée d'authentification du tableau de bord. Closes # ## Preuve ![image](/attachments/5a03c22f-a001-4b22-a252-571e89586c40) ![image](/attachments/ca810ea1-6d8a-41f6-80a2-b8942645b0f3) ![image](/attachments/abf61d67-7e5c-4f8b-8a47-85f6fb943ebc) ![image](/attachments/4b6cfa26-167d-4495-9c12-bfc483f60375) ``` ``` ## Relecture - [ ] Un pair a relu et laissé un commentaire, même court - [ ] Ses remarques sont traitées, ou une réponse explique pourquoi elles ne le sont pas ## Ce qui suit le code <!-- Ne cocher que ce qui s'applique, supprimer le reste. --> - [ ] `docs/runbooks/` mis à jour, un geste d'exploitation a changé - [ ] `docs/adr/` complété, une décision structurante a été prise - [ ] `docs/journal.md` complété, un incident a été rencontré - [ ] Une nouvelle variable d'environnement est apparue, elle est dans `.env` du projet back de plus un .env.exemple a été créer
marvin self-assigned this 2026-09-02 08:23:17 +00:00
- pool psycopg (enervision_api.db), migrations appliquées au démarrage
  avec verrou consultatif ; CLI python db/migrate.py
- db/migrations/0001_auth.sql : schéma ops (users, user_sites, auth_sessions)
- auth/service : login, refresh (rotation + détection de rejeu), logout
- auth/repository et auth/models
- tests d'intégration du service, skippés sans PostgreSQL
- venv unique à la racine ; .env résolu depuis services/api/

Refs #38

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- POST /auth/login, /auth/refresh, /auth/logout, GET /auth/me
- les jetons ne sortent qu'en cookies httpOnly : ev_access (Lax, /),
  ev_refresh (Strict, /auth) ; le SPA ne les voit jamais
- get_current_user : cookie ev_access, repli Authorization: Bearer
- PublicUser en réponse, jamais le password_hash
- tests/integration/test_auth_flow.py : login, me, refresh + rejeu, logout

Refs #38

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- rôles réduits à user et admin (défaut user), contrainte et Literal alignés
- require_role(*roles) + alias AdminOnly ; POST /auth/users réservé admin
- claim role dans l'access token
- retrait du cloisonnement par site : table user_sites, require_site,
  routes /sites (cf. réunion §2.3, question laissée ouverte)
- tests d'intégration conservés mais non lancés sans PostgreSQL joignable

Refs #38

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- 0002_auth_events.sql : journal d'audit ops.auth_events (EC04)
- record_event sur login ok/ko, refresh ok, rejeu, logout, création de compte
- limite de débit /auth/login : 5 échecs par IP en 15 min -> 429
- middleware Origin : 403 sur méthode mutante si ENERVISION_API_ALLOWED_ORIGIN
  est défini et ne correspond pas (les cookies SameSite restent la 1re barrière)
- run_purge au démarrage : sessions expirées depuis plus de 7 jours
- get_settings via model_validate (robuste au retrait du type: ignore)

Non rejoué contre PostgreSQL (pas de conteneur). pytest tests/integration à passer.

Refs #38

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- CORSMiddleware dans main.py, activé si ENERVISION_API_ALLOWED_ORIGIN est défini
  (allow_credentials pour les cookies) ; même réglage que le contrôle Origin
- suppression de tests/integration (nécessitaient PostgreSQL)
- tests/unit/test_auth_service.py : login / refresh / rotation / rejeu / logout
  avec un dépôt en mémoire, aucune infra
- retrait de httpx des dépendances de dev

Suite : 21 tests unitaires, pytest tourne sans serveur ni conteneur.

Refs #38

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- /refresh : fenêtre de grâce (refresh_grace_seconds, 10 s) — un double appel
  concurrent ne révoque plus la famille ; corrige la déconnexion multi-onglets
- PATCH /auth/users/{id} (admin) : désactiver, changer le rôle ou le mot de passe ;
  révoque les sessions de l'utilisateur ; refuse l'auto-rétrogradation d'un admin
- limite de débit du login par (ip, email) au lieu de (ip)
- _client_ip ne lit X-Forwarded-For que si trust_proxy, et prend le dernier élément
- 0003_auth_indexes.sql : index family_id, index login_ko (ip, email)
- longueurs bornées sur email et mot de passe (Credentials, NewUser, UserPatch)

25 tests unitaires, toujours sans base.

Refs #38

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
docs : documentation de conception de l'API, commentaires et OpenAPI
Some checks failed
Intégration / Qualité du code Python (pull_request) Failing after 2s
Intégration / Tests unitaires (pull_request) Failing after 2s
Intégration / Aucun secret commité (pull_request) Failing after 2s
dca2786a4f
- docs/api/ : 6 fiches (architecture, authentification, sécurité, configuration,
  exploitation, décisions et limites)
- README du service réécrit : variables de config, routes, section tests
- commentaires techniques sur les points non évidents (temps constant, rejeu et
  fenêtre de grâce, verrou de migration, X-Forwarded-For, path=/auth)
- summary et description sur chaque route → /docs lisible

Refs #38

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
marvin added this to the EnerVision project 2026-09-02 08:23:18 +00:00
marvin requested review from florian 2026-09-02 08:24:14 +00:00
marvin changed target branch from main to develop 2026-09-02 08:28:51 +00:00
gabriel removed this from the EnerVision project 2026-09-02 08:57:19 +00:00
gabriel left a comment

Relecture critique de la brique auth (ticket #62). Le code est soigné : archi en 4 couches nette, rotation + detection de rejeu correcte, argon2id, login en temps constant, doc de conception complete. ruff, ruff format, mypy --strict et les 25 tests passent en local.

Trois bloquants avant merge, puis des points importants et des nits. Detail ligne a ligne en commentaires.

Bloquants

1. Branche en retard sur develop (mergeable: false). Conflit reel : .gitignore seulement. A merger develop.

2. Le job CI tests-unitaires echoue a la collecte. Apres merge de develop, ce job n'installe que requirements-dev.txt, pas les deps runtime des services -> import jwt / psycopg / argon2 echouent avant le premier test (reproduit en venv propre). Defaut de la chaine (#40) que cette PR est la premiere a exposer : le job qualite installe les deps de service via deps-services.py, pas tests-unitaires. A corriger cote CI, en coordination avec Gabriel.

3. Les deux paliers de couverture passent au rouge. Cette PR apporte les premiers tests unitaires -> les mesures s'activent :

  • global services+packages : 40 % vs 70 % exige (--cov-fail-under)
  • zone sensible .../auth/* : 47 % vs 85 % exige

router.py (116 stmts) et dependencies.py (37) sont a 0 %. Le plan de test du ticket lui-meme n'inclut pas la couche HTTP -> contradiction avec la barriere CI. Soit des tests TestClient (cookies, 401/403/409/429, rotation), soit une decision d'equipe au point du matin (le runbook ci.md interdit de baisser le seuil en douce).

Important

  • Closes #62 manquant dans la description.
  • Audit EC04 : user_created / user_updated n'enregistrent pas l'admin auteur, et user_updated ne dit pas ce qui a change. Cf. commentaire l.231.
  • CSRF : _origin_guard inoperant si ALLOWED_ORIGIN vide (deploiement meme-origine). Cf. main.py l.39.
  • DoS verrouillage de compte : rate-limit (ip, email) -> (email) seul si TRUST_PROXY=false derriere Caddy. Cf. router.py l.98.
  • Aucun plancher de robustesse du mot de passe (min_length=1). Cf. router.py l.26.

Nits

  • email: str -> EmailStr (validation de format).
  • decode_access : options={"require": ["exp"]}. Cf. tokens.py l.33.
  • refresh : pas de SELECT ... FOR UPDATE, detection de rejeu TOCTOU (compromis assume, merite un commentaire dans le code).
  • Index cree en 0002 puis remplace en 0003 dans la meme PR. Cf. 0003.
  • char(64) / role text : char(n) est un anti-idiome PostgreSQL, prefer text.
  • Squelette Vue par defaut commite tel quel (HelloWorld, TheWelcome, icones, counter store) : code mort, a elaguer ou sortir dans un ticket "init dashboard".

Rien a redire sur : rotation family_id, plafond dur absolute_exp, condense SHA-256 seul stocke, role verifie en base a chaque requete (JWT non fiable pour l'authz), is_active recontrole dans get_current_user, fenetre de grace du double-refresh, verrou consultatif sur les migrations.

Relecture critique de la brique auth (ticket #62). Le code est soigné : archi en 4 couches nette, rotation + detection de rejeu correcte, argon2id, login en temps constant, doc de conception complete. `ruff`, `ruff format`, `mypy --strict` et les 25 tests passent en local. Trois bloquants avant merge, puis des points importants et des nits. Detail ligne a ligne en commentaires. ## Bloquants **1. Branche en retard sur `develop`** (`mergeable: false`). Conflit reel : `.gitignore` seulement. A merger `develop`. **2. Le job CI `tests-unitaires` echoue a la collecte.** Apres merge de `develop`, ce job n'installe que `requirements-dev.txt`, pas les deps runtime des services -> `import jwt` / `psycopg` / `argon2` echouent avant le premier test (reproduit en venv propre). Defaut de la chaine (#40) que cette PR est la premiere a exposer : le job `qualite` installe les deps de service via `deps-services.py`, pas `tests-unitaires`. A corriger cote CI, en coordination avec Gabriel. **3. Les deux paliers de couverture passent au rouge.** Cette PR apporte les premiers tests unitaires -> les mesures s'activent : - global `services`+`packages` : **40 %** vs 70 % exige (`--cov-fail-under`) - zone sensible `.../auth/*` : **47 %** vs 85 % exige `router.py` (116 stmts) et `dependencies.py` (37) sont a 0 %. Le plan de test du ticket lui-meme n'inclut pas la couche HTTP -> contradiction avec la barriere CI. Soit des tests `TestClient` (cookies, 401/403/409/429, rotation), soit une decision d'equipe au point du matin (le runbook `ci.md` interdit de baisser le seuil en douce). ## Important - `Closes #62` manquant dans la description. - Audit EC04 : `user_created` / `user_updated` n'enregistrent pas l'admin auteur, et `user_updated` ne dit pas ce qui a change. Cf. commentaire l.231. - CSRF : `_origin_guard` inoperant si `ALLOWED_ORIGIN` vide (deploiement meme-origine). Cf. `main.py` l.39. - DoS verrouillage de compte : rate-limit `(ip, email)` -> `(email)` seul si `TRUST_PROXY=false` derriere Caddy. Cf. `router.py` l.98. - Aucun plancher de robustesse du mot de passe (`min_length=1`). Cf. `router.py` l.26. ## Nits - `email: str` -> `EmailStr` (validation de format). - `decode_access` : `options={"require": ["exp"]}`. Cf. `tokens.py` l.33. - `refresh` : pas de `SELECT ... FOR UPDATE`, detection de rejeu TOCTOU (compromis assume, merite un commentaire dans le code). - Index cree en `0002` puis remplace en `0003` dans la meme PR. Cf. `0003`. - `char(64)` / `role text` : `char(n)` est un anti-idiome PostgreSQL, prefer `text`. - Squelette Vue par defaut commite tel quel (HelloWorld, TheWelcome, icones, counter store) : code mort, a elaguer ou sortir dans un ticket "init dashboard". Rien a redire sur : rotation `family_id`, plafond dur `absolute_exp`, condense SHA-256 seul stocke, role verifie en base a chaque requete (JWT non fiable pour l'authz), `is_active` recontrole dans `get_current_user`, fenetre de grace du double-refresh, verrou consultatif sur les migrations.
@ -0,0 +4,4 @@
create index if not exists auth_sessions_famille on ops.auth_sessions (family_id);
-- limite de débit du login par (ip, email)
drop index if exists ops.auth_events_echecs_recents;
Member

0002 cree auth_events_echecs_recents sur (ip, at), et ce fichier le supprime aussitot pour le recreer sur (ip, email, at) -- dans la meme PR. Autant definir directement le bon index dans 0002 et supprimer ce drop/create. (Sur une base deja migree en preprod on garderait la migration ; ici tout est neuf.)

`0002` cree `auth_events_echecs_recents` sur `(ip, at)`, et ce fichier le supprime aussitot pour le recreer sur `(ip, email, at)` -- dans la meme PR. Autant definir directement le bon index dans `0002` et supprimer ce `drop`/`create`. (Sur une base deja migree en preprod on garderait la migration ; ici tout est neuf.)
@ -0,0 +23,4 @@
class Credentials(BaseModel):
email: str = Field(min_length=3, max_length=320)
password: str = Field(min_length=1, max_length=1024)
Member

min_length=1 : un admin peut creer un compte avec un mot de passe d'un caractere. Pour un ticket "durcissement", poser un plancher (8-12 car. min). Idem Credentials (l.31) et UserPatch (l.40).

`min_length=1` : un admin peut creer un compte avec un mot de passe d'un caractere. Pour un ticket "durcissement", poser un plancher (8-12 car. min). Idem `Credentials` (l.31) et `UserPatch` (l.40).
@ -0,0 +95,4 @@
agent = request.headers.get("user-agent")
if (
ip
and repo.count_recent_login_failures(conn, ip, email, s.login_failure_window_seconds)
Member

Le compteur est indexe (ip, email). Avec TRUST_PROXY=false (le defaut, y compris dans .env.example) derriere Caddy, toutes les requetes portent l'IP du proxy -> 5 echecs sur victime@x.com depuis n'importe ou verrouillent ce compte 15 min pour tout le monde (DoS par verrouillage). Rendre le defaut prod sur / assert de config au demarrage, ou verrouiller sur l'IP plutot que sur le compte.

Le compteur est indexe `(ip, email)`. Avec `TRUST_PROXY=false` (le defaut, y compris dans `.env.example`) derriere Caddy, toutes les requetes portent l'IP du proxy -> 5 echecs sur `victime@x.com` depuis n'importe ou verrouillent ce compte 15 min pour tout le monde (DoS par verrouillage). Rendre le defaut prod sur / `assert` de config au demarrage, ou verrouiller sur l'IP plutot que sur le compte.
@ -0,0 +228,4 @@
) from None
repo.record_event(
conn,
"user_updated",
Member

user_updated ne dit pas ce qui a change : reset de mot de passe, changement de role et desactivation produisent le meme evenement. Et l'user_id enregistre est la cible, pas l'admin auteur -> impossible de dire quel admin a fait l'action. Pour EC04 (journal d'audit), ajouter un champ "action" et l'id de l'acteur. Idem user_created l.188.

`user_updated` ne dit pas ce qui a change : reset de mot de passe, changement de role et desactivation produisent le meme evenement. Et l'`user_id` enregistre est la cible, pas l'admin auteur -> impossible de dire quel admin a fait l'action. Pour EC04 (journal d'audit), ajouter un champ "action" et l'id de l'acteur. Idem `user_created` l.188.
@ -0,0 +30,4 @@
def decode_access(token: str, *, secret: str) -> int:
"""Renvoie l'id utilisateur. Lève ``jwt.InvalidTokenError`` sur tout écart."""
payload = jwt.decode(token, secret, algorithms=[_ALGO])
Member

jwt.decode sans options={"require": ["exp"]} : un jeton sans exp serait accepte comme non-expirant. Ton encodeur met toujours exp, donc c'est de la defense en profondeur -- mais peu couteux.

`jwt.decode` sans `options={"require": ["exp"]}` : un jeton sans `exp` serait accepte comme non-expirant. Ton encodeur met toujours `exp`, donc c'est de la defense en profondeur -- mais peu couteux.
@ -0,0 +36,4 @@
# et qu'un en-tête Origin est présent (absent = client non-navigateur).
allowed = get_settings().allowed_origin
origin = request.headers.get("origin")
if request.method in _UNSAFE_METHODS and allowed and origin and origin != allowed:
Member

_origin_guard ne fait rien quand allowed_origin est vide -- ce qui sera le cas en prod si Caddy sert le SPA et l'API sur la meme origine. Le ticket #62 exige un controle d'origine sur les requetes mutantes "en plus des cookies SameSite". Ici, en meme-origine, il ne reste que SameSite=Lax sur ev_access. Le navigateur envoie pourtant Origin sur les requetes non-sures meme en meme-origine : on pourrait comparer a l'hote de la requete quand Origin est present, ou au minimum documenter que SameSite est la seule barriere dans ce mode et le faire valider.

`_origin_guard` ne fait rien quand `allowed_origin` est vide -- ce qui sera le cas en prod si Caddy sert le SPA et l'API sur la meme origine. Le ticket #62 exige un controle d'origine sur les requetes mutantes "en plus des cookies SameSite". Ici, en meme-origine, il ne reste que `SameSite=Lax` sur `ev_access`. Le navigateur envoie pourtant `Origin` sur les requetes non-sures meme en meme-origine : on pourrait comparer a l'hote de la requete quand `Origin` est present, ou au minimum documenter que SameSite est la seule barriere dans ce mode et le faire valider.
Merge remote-tracking branch 'origin/develop' into marvin/62-API-REST-brique-authentification
Some checks failed
Intégration / Tests unitaires et couverture (pull_request) Failing after 33s
Intégration / Qualité du code Python (pull_request) Successful in 45s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 5m23s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Aucun secret commité (pull_request) Failing after 3s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 7m7s
d6b96aac72
# Conflicts:
#	.gitignore
tests : couvre la couche HTTP de l'auth sans base
Some checks failed
Intégration / Qualité du code Python (pull_request) Successful in 49s
Intégration / Tests unitaires et couverture (pull_request) Successful in 50s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 5m12s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Aucun secret commité (pull_request) Failing after 3s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 7m9s
33e55986a2
- tests/unit/api/ : les tests d'auth regroupés + _fakes.py (dépôt en mémoire)
- test_auth_http.py : routeur, dépendances, cookies, limite de débit, Origin,
  CORS, gestion des comptes -- via TestClient, dépôt monkeypatché, zéro PostgreSQL
- couverture : auth/* 90 % (>= 85 exigé), global services+packages 84 % (>= 70)
- ci : le job tests-unitaires installe aussi les deps des services (jwt, psycopg…)
- requirements-dev : httpx, requis par starlette.testclient

Répond aux bloquants #2 (deps CI) et #3 (couverture) de la relecture de #63.

Refs #62

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EJ8iHaDDQJa8jGxQaCu8xS
tests : évite le faux positif du scan de secrets
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 55s
Intégration / Tests unitaires et couverture (pull_request) Successful in 52s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 5m23s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Aucun secret commité (pull_request) Successful in 3s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 7m11s
f7d40a0619
`password="..."` littéral dans un appel de test déclenchait la règle
`password\s*=\s*"..."` du job « Aucun secret commité ». Passé par une constante.

Refs #62

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EJ8iHaDDQJa8jGxQaCu8xS
This reverts commit 3deb2a864d8acb75f2c6707cb8e715284ddb5bfe.
api : corrige les points de sécurité de la relecture
Some checks failed
Intégration / Qualité du code Python (pull_request) Failing after 30s
Intégration / Tests unitaires et couverture (pull_request) Successful in 51s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 4s
Intégration / Images épinglées par version (pull_request) Successful in 2s
Intégration / Aucun secret commité (pull_request) Successful in 2s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 7m7s
ffed46bb38
- limite de débit du login comptée par IP (plus par compte) : un attaquant
  ne verrouille plus un compte cible depuis n'importe où. Avertissement au
  démarrage si trust_proxy=false hors debug.
- 0004 : aligne la contrainte de rôle sur user/admin (bases migrées avant le
  changement) et ajoute actor_id + detail au journal d'audit.
- user_created / user_updated enregistrent l'admin auteur et le champ modifié.
- plancher de mot de passe à 8 caractères (NewUser, UserPatch).
- decode_access exige le claim exp.
- 0003 réduit à l'index family_id (l'index login_ko reste celui de 0002).

Points 1 à 4 de la relecture de #63.

Refs #62

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EJ8iHaDDQJa8jGxQaCu8xS
api : validation d'email et contrôle Origin en même-origine
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 49s
Intégration / Tests unitaires et couverture (pull_request) Successful in 59s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 5s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Aucun secret commité (pull_request) Successful in 3s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 6m55s
a75bef90ca
- Origin : quand ALLOWED_ORIGIN est vide (déploiement même-origine derrière
  Caddy), l'hôte de l'en-tête Origin est comparé au Host de la requête. Une
  requête montée depuis un autre site est refusée en 403, alors qu'avant le
  contrôle ne faisait rien dans ce mode.
- email : type Email = EmailStr + normalisation (strip/lower) avant validation.
  Un email mal formé rend 422 ; « " Jean@Site.fr " » reste accepté.
- email-validator ajouté aux dépendances du service.

Points (c) et nit « EmailStr » de la relecture de #63.

Refs #62

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EJ8iHaDDQJa8jGxQaCu8xS
db : token_digest en text plutôt que char(64)
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 44s
Intégration / Tests unitaires et couverture (pull_request) Successful in 54s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 5s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Aucun secret commité (pull_request) Successful in 3s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 6m54s
1ce2be8dd8
char(n) remplit par des espaces et compare en ignorant les espaces finaux,
sans gain sur PostgreSQL. Le condensé SHA-256 fait toujours 64 caractères.

- 0001 corrigé pour les bases neuves
- 0005 rattrape celles qui ont déjà tourné
- README des migrations remis à jour (0003 avait changé, 0004 et 0005 manquaient)

Nit « char(n) » de la relecture de #63.

Refs #62

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EJ8iHaDDQJa8jGxQaCu8xS
api : le service renvoie le profil, plus de relecture en base
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 47s
Intégration / Tests unitaires et couverture (pull_request) Successful in 52s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 4s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Aucun secret commité (pull_request) Successful in 2s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 6m30s
6063eeb64e
IssuedTokens porte un PublicUser au lieu du seul user_id : le routeur n'a plus
à relire l'utilisateur que le service venait de charger.

- login : 1 requête au lieu de 2
- refresh : 2 au lieu de 3
- POST /auth/users : construit le profil depuis l'insert, 1 au lieu de 2

_public reste pour patch_user, qui doit relire l'état après écriture.

Refs #62

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EJ8iHaDDQJa8jGxQaCu8xS
api : un seul endroit définit les rôles
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 46s
Intégration / Tests unitaires et couverture (pull_request) Successful in 50s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 4s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Aucun secret commité (pull_request) Successful in 2s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 6m54s
a8a20ba161
Role, un StrEnum dans auth/models.py, remplace les chaînes recopiées dans le
Literal Pydantic, dans require_role et dans le service. Le dépôt convertit la
valeur lue en base, une valeur inconnue lève au lieu de passer.

La contrainte SQL reste une définition séparée (une migration ne lit pas
l'enum) : un commentaire dans 0001 la rattache.

OpenAPI expose désormais un schéma Role nommé plutôt qu'un Literal recopié.

Nit « rôle défini à trois endroits » de la relecture de #63.

Refs #62

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EJ8iHaDDQJa8jGxQaCu8xS
marvin referenced this pull request from a commit 2026-09-02 12:30:08 +00:00
db : l'email est garanti en minuscules
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 47s
Intégration / Tests unitaires et couverture (pull_request) Successful in 54s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 5s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Aucun secret commité (pull_request) Successful in 3s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 6m51s
d23cb47a69
La contrainte unique sur ops.users.email est sensible à la casse. Une insertion
SQL directe de « Admin@Site.fr » — le premier admin se crée à la main — donnait
un compte introuvable au login, et laissait passer « admin@site.fr » en doublon.

0006 normalise l'existant puis pose la contrainte. L'invariant que le code
suppose partout est désormais tenu par la base. Pas de citext : l'extension
demande des droits que le rôle de la préprod n'a pas.

Docs : « Limites connues » de docs/api/decisions.md remise à jour, quatre points
étaient corrigés par les étapes précédentes.

Nit « casse de l'email » de la relecture de #63.

Refs #62

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EJ8iHaDDQJa8jGxQaCu8xS
api : purge aussi le journal d'audit
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 49s
Intégration / Tests unitaires et couverture (pull_request) Successful in 58s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 4s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Aucun secret commité (pull_request) Successful in 2s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 6m49s
e6413831d1
run_purge ne nettoyait que les sessions ; ops.auth_events grossissait sans fin.
Rétention séparée : 90 jours pour l'audit (réponse à incident) contre 7 pour
les sessions (fonctionnement).

Docs : configuration.md était périmée sur les variables, les colonnes d'audit
et la liste des migrations ; securite.md décrivait encore la limite de débit
par (ip, email) et le contrôle Origin comme inactif en même-origine.

Refs #62

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EJ8iHaDDQJa8jGxQaCu8xS
Merge branch 'develop' into marvin/62-API-REST-brique-authentification
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 43s
Intégration / Tests unitaires et couverture (pull_request) Successful in 54s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 4s
Intégration / Images épinglées par version (pull_request) Successful in 2s
Intégration / Aucun secret commité (pull_request) Successful in 2s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 6m37s
a4b6be672f
@ -0,0 +11,4 @@
## Limites connues
- Pas d'auto-service : un utilisateur ne peut pas changer son propre mot de passe
Member

A modifier. Ça rentre en conflit avec l'issue #21 https://10.105.200.41/g2/enervision/issues/21

A modifier. Ça rentre en conflit avec l'issue #21 https://10.105.200.41/g2/enervision/issues/21
Author
Member

évolution possible vers un system de ticking a faire plus tard

évolution possible vers un system de ticking a faire plus tard
florian marked this conversation as resolved
Merge branch 'develop' into marvin/62-API-REST-brique-authentification
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 48s
Intégration / Tests unitaires et couverture (pull_request) Successful in 52s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 8s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Aucun secret commité (pull_request) Successful in 3s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 1m16s
6e085f39f7
florian approved these changes 2026-09-02 14:54:05 +00:00
Dismissed
Member

LGTM

LGTM
Merge branch 'develop' into marvin/62-API-REST-brique-authentification
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 1m17s
Intégration / Tests unitaires et couverture (pull_request) Successful in 1m25s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 14s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Aucun secret commité (pull_request) Successful in 3s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 1m37s
f73c66ab85
marvin dismissed florian's review 2026-09-03 07:11:14 +00:00
Reason:

New commits pushed, approval review dismissed automatically according to repository settings

marvin merged commit 8c98a3e6de into develop 2026-09-03 07:15:21 +00:00
marvin deleted branch marvin/62-API-REST-brique-authentification 2026-09-03 07:15:21 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
3 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
g2/enervision!63
No description provided.