From 00ddd1e4ea71814d6ca1f7a4ca4c292b6bff7577 Mon Sep 17 00:00:00 2001 From: Till JS Date: Thu, 23 Apr 2026 23:16:43 +0200 Subject: [PATCH] fix(api/profile): send credentials cross-origin so Better-Auth updateUser works MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fetchWithAuth called mana-auth's /api/v1/auth/profile without credentials: 'include'. In production both hosts sit under *.mana.how with the shared auth cookie, so the session rode along regardless — but in dev (5173 → 3001) the cookie was dropped, and the server's auth.api.updateUser threw because it couldn't identify the user. serviceErrorHandler then masked it as a generic 500. The failure was silent at the call site because syncAvatarToAuth() wraps the POST in try/catch — but every face-ref primary claim logged "[profile] syncing avatar to Better Auth failed" and left auth.users.image out of sync. Surfaced now because wardrobe's new inline face upload claims face-ref reliably. Matches credentials: 'include' used everywhere in packages/shared-auth/src/core/authService.ts. Co-Authored-By: Claude Opus 4.7 (1M context) --- apps/mana/apps/web/src/lib/api/profile.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/apps/mana/apps/web/src/lib/api/profile.ts b/apps/mana/apps/web/src/lib/api/profile.ts index e7d0142dd..918ecc1fa 100644 --- a/apps/mana/apps/web/src/lib/api/profile.ts +++ b/apps/mana/apps/web/src/lib/api/profile.ts @@ -42,6 +42,13 @@ async function fetchWithAuth(endpoint: string, options: RequestInit = {}): Pr const response = await fetch(`${getManaAuthUrl()}${endpoint}`, { ...options, + // Better-Auth's /profile handler calls auth.api.updateUser, which + // identifies the user via the session cookie (not the JWT bearer). + // In dev the request is cross-origin (5173 → 3001); without + // `credentials: 'include'` the browser drops the cookie and the + // server throws "Internal server error" instead of updating. + // Matches the pattern used throughout packages/shared-auth. + credentials: 'include', headers: { 'Content-Type': 'application/json', ...(token ? { Authorization: `Bearer ${token}` } : {}),