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,
|
||||
})
|
||||
|
||||
// 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({
|
||||
queryKey: ['events', start, end],
|
||||
queryFn: () => fetchEvents(start, end),
|
||||
enabled: meQuery.isSuccess,
|
||||
retry: 2,
|
||||
staleTime: 5 * 60 * 1000,
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user