diff --git a/apps/pwa/src/components/CalendarShell.tsx b/apps/pwa/src/components/CalendarShell.tsx index 3f4ef37..8e1d6d1 100644 --- a/apps/pwa/src/components/CalendarShell.tsx +++ b/apps/pwa/src/components/CalendarShell.tsx @@ -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, })