From f42c76894d8905f859b66256cdb4b14bc3d0f6e9 Mon Sep 17 00:00:00 2001 From: tbeaumont79 Date: Wed, 10 Jun 2026 19:25:10 +0200 Subject: [PATCH] =?UTF-8?q?fix(auth):=20=C3=A9critures=20profil=20qui=20pe?= =?UTF-8?q?ndent=20+=20middleware=20qui=20ne=20prot=C3=A8ge=20pas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Deux bugs qui cassaient tout le parcours profil : 1. getAuthSession utilisait toWebRequest(event), qui touche au flux du corps de la requête : le readBody(event) des handlers PUT/POST attendait ensuite un corps déjà verrouillé → toutes les écritures /api/profile* pendaient indéfiniment en build de prod (les GET, sans corps, passaient). On passe event.headers directement à auth.api.getSession, comme le fait déjà le middleware serveur. 2. Le middleware de navigation `auth` testait la truthiness de l'atom nanostores de useAuth() — toujours vrai → aucune redirection, et les visiteurs non connectés voyaient « Impossible de charger ton profil » (401) sur /profil. On vérifie désormais la session via GET /api/auth/get-session (cookies transférés en SSR). Vérifié sur build de prod + Postgres : PUT/POST/DELETE profil en ~10 ms, /profil anonyme → 302 /connexion, /profil connecté → 200. Co-Authored-By: Claude Fable 5 --- apps/app/middleware/auth.ts | 19 +++++++++++++++---- apps/app/server/utils/session.ts | 15 +++++++++++---- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/apps/app/middleware/auth.ts b/apps/app/middleware/auth.ts index f625904..0cdea7b 100644 --- a/apps/app/middleware/auth.ts +++ b/apps/app/middleware/auth.ts @@ -2,10 +2,21 @@ * Middleware de navigation — protège les pages authentifiées. * Usage : definePageMeta({ middleware: 'auth' }) dans la page. * Redirige vers /connexion si la session est absente. + * + * La session est vérifiée auprès du serveur Better Auth (GET /api/auth/get-session), + * fiable en SSR (cookies transférés) comme en navigation client. L'atom nanostores + * de useAuth() n'est PAS utilisable ici : l'objet est toujours truthy et la session + * n'est hydratée qu'après le premier fetch côté client — l'ancien check laissait + * passer les visiteurs non connectés (écran d'erreur 401 sur /profil au lieu d'une + * redirection). */ export default defineNuxtRouteMiddleware(async () => { - const { session } = useAuth() - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const s = (session as any)?.value ?? session - if (!s) return navigateTo('/connexion') + // En SSR, $fetch interne ne transmet pas les cookies du navigateur tout seul. + const headers = import.meta.server ? useRequestHeaders(['cookie']) : undefined + try { + const session = await $fetch('/api/auth/get-session', { headers }) + if (!session) return navigateTo('/connexion') + } catch { + return navigateTo('/connexion') + } }) diff --git a/apps/app/server/utils/session.ts b/apps/app/server/utils/session.ts index 0a0b35f..200cd40 100644 --- a/apps/app/server/utils/session.ts +++ b/apps/app/server/utils/session.ts @@ -4,7 +4,7 @@ * - `requireUserId` (THI-126) : lit `event.context.userId` posé par le middleware * d'auth (THI-134) et lève une 401 si absent. Découplé de l'instance Better Auth. * - `getAuthSession` (THI-132) : récupère la session Better Auth complète depuis - * l'événement (headers Web API via `toWebRequest`). Retourne null si non authentifié. + * les headers de l'événement. Retourne null si non authentifié. */ import { createError, type H3Event } from 'h3' import { auth } from './auth' @@ -26,8 +26,15 @@ export function requireUserId(event: H3Event): string { return userId } -/** Récupère la session Better Auth complète (ou null si non authentifié). */ +/** + * Récupère la session Better Auth complète (ou null si non authentifié). + * + * ⚠️ On passe `event.headers` directement — surtout pas `toWebRequest(event)` : + * convertir l'événement en Request touche au flux du corps, et le `readBody(event)` + * qui suit dans les handlers PUT/POST attend alors un corps déjà verrouillé → + * requête qui pend indéfiniment (toutes les écritures /api/profile* étaient HS + * en build de prod ; les GET, sans corps, passaient). + */ export async function getAuthSession(event: H3Event) { - const req = toWebRequest(event) - return auth.api.getSession({ headers: req.headers }) + return auth.api.getSession({ headers: event.headers }) }