From ec608d7091b8fc6bbe2550e8a8d5d3a5d9a8a742 Mon Sep 17 00:00:00 2001 From: Sergey Kozyrenko Date: Thu, 25 Jun 2026 06:41:36 +0700 Subject: [PATCH] fix(webui): redirect to login when the GraphQL subscription WS auth fails MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The subscription WebSocket retries forever on a 403 handshake (expired/invalid cookie), but its error handler only escalated to auth:refresh when the error *message* contained "403"/"auth required" — and browsers never expose the handshake HTTP status on a WebSocket error event, so that detection never fired. An idle page with active subscriptions therefore looped on 403 with no redirect to login (the redirect only fired once a REST/axios call hit a 403). Dispatch auth:refresh unconditionally on any WS error and let /info decide: it redirects on a real 401/403 and is a no-op while the session holds. Live-verified both ways: an invalid cookie now redirects to login from an idle page, and a transient WS drop on a valid session keeps the user logged in. Pre-existing (the WS handler is unchanged from main); surfaced by a tester. Co-Authored-By: Claude Opus 4.8 --- frontend/src/lib/apollo.ts | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/frontend/src/lib/apollo.ts b/frontend/src/lib/apollo.ts index 088c0390..36f353b3 100644 --- a/frontend/src/lib/apollo.ts +++ b/frontend/src/lib/apollo.ts @@ -390,21 +390,9 @@ const createApolloClient = () => { error: (error) => { Log.error('GraphQL WebSocket error:', error); - if (error && typeof error === 'object') { - const errorMessage = 'message' in error ? String(error.message) : ''; - const errorString = errorMessage.toLowerCase(); - - if ( - errorString.includes('403') || - errorString.includes('401') || - errorString.includes('unauthorized') || - errorString.includes('auth required') || - errorString.includes('forbidden') - ) { - Log.warn('WebSocket authorization error detected, refreshing auth info'); - window.dispatchEvent(new Event('auth:refresh')); - } - } + // A WebSocket error event doesn't expose the handshake HTTP status, so a + // 403 can't be detected here — let /info classify it via auth:refresh. + window.dispatchEvent(new Event('auth:refresh')); }, ping: () => Log.debug('GraphQL WebSocket ping'), pong: () => Log.debug('GraphQL WebSocket pong'),