From 42d3f9e816df520337f845be464fdbe2acf88420 Mon Sep 17 00:00:00 2001 From: KernelDeimos <7225168+KernelDeimos@users.noreply.github.com> Date: Wed, 18 Feb 2026 16:58:31 -0500 Subject: [PATCH] fix(oidc): http-only cookie sync for switch user --- src/backend/src/services/auth/AuthService.js | 28 ++++++++++++++++++++ src/gui/src/helpers.js | 21 +++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/src/backend/src/services/auth/AuthService.js b/src/backend/src/services/auth/AuthService.js index 1881c3ed1..68eab4518 100644 --- a/src/backend/src/services/auth/AuthService.js +++ b/src/backend/src/services/auth/AuthService.js @@ -714,6 +714,34 @@ class AuthService extends BaseService { return res.json({ token: gui_token }); }, }).attach(app); + + // Sync HTTP-only session cookie to the user implied by the request's auth token. + // Used when switching users in the UI: client sends Authorization with the new user's + // GUI token; we set the session cookie so cookie-based (e.g. user-protected) requests match. + Endpoint({ + route: '/session/sync-cookie', + methods: ['GET'], + mw: [configurable_auth()], + handler: async (req, res) => { + if ( ! req.user ) { + return res.status(401).end(); + } + const actor = Context.get('actor'); + if ( !(actor.type instanceof UserActorType) || !actor.type.session ) { + return res.status(400).end(); + } + const session_token = svc_auth.create_session_token_for_session( + actor.type.user, + actor.type.session, + ); + res.cookie(config.cookie_name, session_token, { + sameSite: 'none', + secure: true, + httpOnly: true, + }); + return res.status(204).end(); + }, + }).attach(app); } } diff --git a/src/gui/src/helpers.js b/src/gui/src/helpers.js index 62fcdeac6..539582e4e 100644 --- a/src/gui/src/helpers.js +++ b/src/gui/src/helpers.js @@ -461,6 +461,27 @@ window.update_auth_data = async (auth_token, user, api_origin) => { window.auth_token = auth_token; localStorage.setItem('auth_token', auth_token); + // Set http-only session cookie when user is changing. + // This ensures user-protected endpoints, which only refer to the http-only cookie, + // act on the intended user. + // Only the server can set this cookie, so we call the `/session/sync-cookie` endpoint. + const userChanging = !window.user || window.user.uuid !== user.uuid; + if ( userChanging && auth_token && (window.gui_origin || window.location?.origin) ) { + try { + const origin = window.gui_origin || window.location.origin; + await fetch(`${origin}/session/sync-cookie`, { + method: 'GET', + credentials: 'include', + headers: { Authorization: `Bearer ${auth_token}` }, + }); + } catch (e) { + console.error('Failed to sync session cookie:', e); + await UIAlert({ + message: `Failed to sync session cookie: ${ e.message}`, + }); + } + } + if ( api_origin ) { window.api_origin = api_origin; localStorage.setItem('api_origin', api_origin);