fix(pwa): fetchMe uses redirect:manual so unauthenticated /api/me can't hang

With the default redirect:follow, the browser follows the OIDC guard's 302 to
Authelia (cross-origin, credentialed) and the fetch HANGS — meQuery stays
'loading' so the SPA spins forever and the isError-driven login redirect never
fires. redirect:manual surfaces the 302 as an opaqueredirect (status 0) that we
detect as auth-required and throw, letting CalendarShell navigate to /api/login.
+4 fetchMe tests.
This commit is contained in:
Lucas Berger
2026-06-06 21:57:42 -04:00
parent 874f23de2f
commit 1adb460412
2 changed files with 83 additions and 3 deletions
+15 -3
View File
@@ -26,14 +26,26 @@ export interface MeResponse {
}
export async function fetchMe(): Promise<MeResponse> {
// redirect: 'manual' is critical. The OIDC guard answers an unauthenticated
// request with a 302 to Authelia (cross-origin). With the default
// redirect: 'follow', the browser follows that credentialed cross-origin
// redirect and the fetch HANGS (never resolves, never rejects) — leaving the
// query stuck "loading" so the SPA spins forever and the auth-redirect below
// never fires. With 'manual', the 302 comes back as an opaqueredirect
// (res.type === 'opaqueredirect', res.status === 0) that we detect immediately.
const res = await fetch('/api/me', {
credentials: 'include',
redirect: 'manual',
})
if (res.type === 'opaqueredirect' || res.status === 401) {
// Session missing/expired → the guard wants us at Authelia. Signal the caller
// (CalendarShell) to perform a TOP-LEVEL navigation to /api/login via
// maybeRedirectToLogin() — a document navigation is not CORS-restricted.
throw new Error('GET /api/me: authentication required')
}
if (!res.ok) {
// Throw on any non-ok response. The OIDC guard's 302 to Authelia cannot be
// followed by fetch (CORS-blocked for XHR). Callers that handle auth errors
// should use maybeRedirectToLogin() for a top-level navigation to /api/login.
throw new Error(`GET /api/me failed: ${res.status}`)
}