fix(pwa): gate events query on auth to stop OIDC state-cookie churn
Internal Server Error after returning from Authelia: processOAuthCallback threw
OAUTH_INVALID_RESPONSE ("unexpected state parameter") because the OIDC state
cookie no longer matched the state returned to /callback.
Root cause: eventsQuery (fetchEvents, redirect:'follow', retry:2) ran
concurrently with fetchMe on load. While unauthenticated, every /api/* request
hits the OIDC guard, which 302-redirects to Authelia AND sets a fresh state
cookie. fetchEvents could not follow the cross-origin redirect, so React Query
retried it up to 3x over ~3s — each retry overwriting the state cookie mid-login,
racing the single /api/login navigation that owns the real flow.
Fix: enabled: meQuery.isSuccess. Only fetchMe (redirect:'manual', retry:false)
touches a guarded endpoint while unauthenticated, so the top-level /api/login
navigation owns the state cookie uncontested. Realizes the documented design
intent that only fetchMe drives the login redirect.
This commit is contained in:
@@ -93,10 +93,22 @@ export function CalendarShell() {
|
|||||||
staleTime: 5 * 60 * 1000,
|
staleTime: 5 * 60 * 1000,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Fetch windowed occurrences — key includes start/end so navigation refetches
|
// Fetch windowed occurrences — key includes start/end so navigation refetches.
|
||||||
|
//
|
||||||
|
// enabled: meQuery.isSuccess is load-bearing for the OIDC login flow, not just
|
||||||
|
// an optimization. When unauthenticated, every /api/* request hits the OIDC
|
||||||
|
// guard, which 302-redirects to Authelia AND sets a fresh state cookie. If this
|
||||||
|
// query ran concurrently with fetchMe (and retried), each /api/events redirect
|
||||||
|
// would overwrite the OIDC state cookie mid-login — so the state returned to
|
||||||
|
// /callback no longer matched the cookie, producing OAUTH_INVALID_RESPONSE
|
||||||
|
// ("unexpected state parameter") and an Internal Server Error after Authelia.
|
||||||
|
// Gating on a successful /api/me means only fetchMe (redirect:'manual',
|
||||||
|
// retry:false) touches a guarded endpoint while unauthenticated, so the single
|
||||||
|
// top-level /api/login navigation owns the state cookie uncontested.
|
||||||
const eventsQuery = useQuery({
|
const eventsQuery = useQuery({
|
||||||
queryKey: ['events', start, end],
|
queryKey: ['events', start, end],
|
||||||
queryFn: () => fetchEvents(start, end),
|
queryFn: () => fetchEvents(start, end),
|
||||||
|
enabled: meQuery.isSuccess,
|
||||||
retry: 2,
|
retry: 2,
|
||||||
staleTime: 5 * 60 * 1000,
|
staleTime: 5 * 60 * 1000,
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user