fix(auth): déconnexion forcée quand une requête 401 en cours de session (#108) #116

Merged
kyuno merged 1 commit from fix/client-force-logout-on-401 into main 2026-09-08 04:14:49 +02:00
Owner

Contexte

Closes #108.

La session expire déjà (JWT expiresIn: JWT_EXPIRES_IN, défaut "7d"), mais le client web n'y réagissait pas : seul le GET /auth/me du montage de AuthProvider attrapait un 401 ; un 401 de n'importe quel autre endpoint ne faisait que remonter une erreur locale de la feature, l'UI connectée restant affichée jusqu'au prochain rechargement complet. Sur l'app native (rarement redémarrée, token persisté dans @capacitor/preferences), la session paraissait ne jamais finir.

Changements

  • apps/web/src/api/client.ts : sur un 401 NOT_AUTHENTICATED pour toute requête autre que le probe GET /auth/me, ApiClient._request purge le token store et appelle un handler onSessionExpired (enregistré via setSessionExpiredHandler). L'ApiError continue d'être levée comme avant — le traitement local des features est inchangé.
  • apps/web/src/features/auth/AuthContext.tsx : AuthProvider enregistre ce handler au montage — setUser(null) + navigate("/login", { replace: true }), gardé par un userRef pour être un no-op si déjà déconnecté. ApiClient restant agnostique du framework (pas de useNavigate), c'est bien lui qui délègue la navigation/état, comme suggéré dans l'issue.

Pas de changement API : le back renvoie déjà 401 sur JWT expiré (require-auth.ts), couvert par sa suite Mocha.

Tests

  • apps/web/cypress/e2e/session-expiry.cy.ts (nouveau) — un 401 en cours de session redirige vers /login et vide l'état (un retour sur une route gardée re-redirige) ; le 401 du /auth/me initial d'un visiteur non connecté ne déclenche pas ce chemin (la landing reste affichée).
  • Local : pnpm lint + pnpm --filter web build OK ; session-expiry.cy.ts 2/2, auth.feature 6/6, régression layout/account/recipes/onboarding/preferences sans casse.

🤖 Generated with Claude Code

## Contexte `Closes #108`. La session expire déjà (JWT `expiresIn: JWT_EXPIRES_IN`, défaut `"7d"`), mais le **client web n'y réagissait pas** : seul le `GET /auth/me` du montage de `AuthProvider` attrapait un 401 ; un 401 de n'importe quel autre endpoint ne faisait que remonter une erreur locale de la feature, l'UI connectée restant affichée jusqu'au prochain rechargement complet. Sur l'app native (rarement redémarrée, token persisté dans `@capacitor/preferences`), la session paraissait ne jamais finir. ## Changements - `apps/web/src/api/client.ts` : sur un `401 NOT_AUTHENTICATED` pour toute requête **autre que** le probe `GET /auth/me`, `ApiClient._request` purge le token store et appelle un handler `onSessionExpired` (enregistré via `setSessionExpiredHandler`). L'`ApiError` continue d'être levée comme avant — le traitement local des features est inchangé. - `apps/web/src/features/auth/AuthContext.tsx` : `AuthProvider` enregistre ce handler au montage — `setUser(null)` + `navigate("/login", { replace: true })`, gardé par un `userRef` pour être un no-op si déjà déconnecté. `ApiClient` restant agnostique du framework (pas de `useNavigate`), c'est bien lui qui délègue la navigation/état, comme suggéré dans l'issue. Pas de changement API : le back renvoie déjà 401 sur JWT expiré (`require-auth.ts`), couvert par sa suite Mocha. ## Tests - `apps/web/cypress/e2e/session-expiry.cy.ts` (nouveau) — un 401 en cours de session redirige vers `/login` et vide l'état (un retour sur une route gardée re-redirige) ; le 401 du `/auth/me` initial d'un visiteur non connecté ne déclenche pas ce chemin (la landing reste affichée). - Local : `pnpm lint` + `pnpm --filter web build` OK ; `session-expiry.cy.ts` 2/2, `auth.feature` 6/6, régression `layout`/`account`/`recipes`/`onboarding`/`preferences` sans casse. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
kyuno added the
Medium
bug
planned
labels 2026-09-08 03:43:36 +02:00
kyuno added 1 commit 2026-09-08 03:43:37 +02:00
fix(auth): déconnexion forcée quand une requête 401 en cours de session (#108)
All checks were successful
CI / lint (push) Successful in 2m37s
CI / build (push) Successful in 3m37s
CI / intent-service-test (push) Successful in 5m20s
CI / e2e (push) Successful in 9m52s
CI / test (push) Successful in 28m39s
CI / build-android (push) Has been skipped
a54cb85027
La session expire déjà (JWT 7 j), mais le client web n'y réagissait pas :
seul le `GET /auth/me` du montage de `AuthProvider` attrapait un 401 ; un
401 de n'importe quel autre endpoint ne faisait que remonter une erreur
locale, l'UI connectée restant affichée jusqu'au prochain rechargement
complet. Sur l'app native (rarement redémarrée, token persisté) la session
paraissait ne jamais finir.

- `client.ts` : sur un `401 NOT_AUTHENTICATED` pour toute requête autre que
  le probe `GET /auth/me`, `ApiClient._request` purge le token store et
  appelle un handler `onSessionExpired` (enregistré via
  `setSessionExpiredHandler`). L'`ApiError` continue d'être levée comme
  avant.
- `AuthContext.tsx` : `AuthProvider` enregistre ce handler au montage —
  `setUser(null)` + `navigate(/login, { replace: true })`, gardé par un
  `userRef` pour être un no-op si déjà déconnecté. `ApiClient` restant
  agnostique du framework, c'est bien lui qui délègue la navigation/état.

Pas de changement API : le back renvoie déjà 401 sur JWT expiré
(`require-auth.ts`), couvert par sa suite Mocha.

Tests : `apps/web/cypress/e2e/session-expiry.cy.ts` — un 401 en cours de
session redirige vers `/login` et vide l'état (un retour sur une route
gardée re-redirige) ; le 401 du `/auth/me` initial d'un visiteur non
connecté ne déclenche pas ce chemin (la landing reste affichée). Suite e2e
existante (auth / layout / account / recipes / onboarding / preferences)
revérifiée sans régression.

Closes #108

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
kyuno merged commit 3d9f93e204 into main 2026-09-08 04:14:49 +02:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
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: kyuno/batchCooking#116
No description provided.