From 7444c6197877a8c36713e2a2bf167d53987e28e8 Mon Sep 17 00:00:00 2001 From: Jeremy Ward <91213920+nrmjeremy@users.noreply.github.com> Date: Thu, 23 Jul 2026 22:36:32 -0700 Subject: [PATCH] fix(desktop): authenticate primary-profile session slices in OAuth mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In OAuth remote mode the primary backend connection carries token: null — REST is authenticated either by a native RFC 8252 bearer token or through the cookie-bound Electron session partition. The two remote-splice helpers that fetch the primary profile's session-list slices (fetchProfilesSessionSlice's local-primary branch and mergeRemoteProfileSessions' base aggregate) called plain fetchJson(url, primary.token), so every sidebar refresh for the primary profile got a 401 no_cookie that the .catch(() => ({ sessions: [] })) swallowed silently. Result: the default/primary profile's session list rendered empty in the desktop sidebar while per-profile-override tabs (which route through the auth-aware requestJsonForProfile) worked. Route both helpers through a new fetchPrimaryGetJson() that mirrors requestJsonForProfile: for OAuth primaries prefer the native access token (bearer) when held, else fall back to fetchJsonViaOauthSession; non-OAuth primaries keep plain fetchJson(url, token). Supersedes #68169 (stale after the RFC 8252 native-login refactor; the original branch was deleted upstream). Verified: typecheck clean (all three tsconfigs); 697 electron vitest tests pass; helper confirmed wired into the packaged bundle (app.asar.unpacked/dist/electron-main.mjs). The pre-refactor version of this fix was verified live on a remote OAuth setup on 2026-07-21 (800+ primary-profile sessions populating). --- apps/desktop/electron/main.ts | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/apps/desktop/electron/main.ts b/apps/desktop/electron/main.ts index c448e3ac0318f..ab0ea6fa07ac4 100644 --- a/apps/desktop/electron/main.ts +++ b/apps/desktop/electron/main.ts @@ -9211,6 +9211,23 @@ async function remoteSessionList(profile, searchParams) { return { ...(data as any), sessions: rowsOf(data) } } +// Primary-connection GET that stays authenticated for OAuth primaries, whose +// token is null by design. Prefer the native RFC 8252 bearer when held, else +// ride the cookie-bound session partition — plain fetchJson(url, primary.token) +// 401s and callers' .catch silently blanks the primary profile's session rows +// (mirrors the requestJsonForProfile pattern). +async function fetchPrimaryGetJson(primary, url) { + const opts = { method: 'GET', timeoutMs: DEFAULT_FETCH_TIMEOUT_MS } + if (primary.authMode === 'oauth') { + const nativeAt = await ensureNativeAccessToken(primary.baseUrl).catch(() => null) + if (nativeAt) { + return fetchJson(url, null, { ...opts, bearer: nativeAt }) + } + return fetchJsonViaOauthSession(url, opts) + } + return fetchJson(url, primary.token, opts) +} + // Resolve one /api/profiles/sessions slice with remote profiles spliced in — // the same branch logic as the GET /api/profiles/sessions intercept, but always // returns data (never `undefined`) so a batched caller can compose slices. A @@ -9226,10 +9243,9 @@ async function fetchProfilesSessionSlice(searchParams, remoteProfiles) { const primary = await ensureBackend(null) - return fetchJson(`${primary.baseUrl}/api/profiles/sessions?${searchParams}`, primary.token, { - method: 'GET', - timeoutMs: DEFAULT_FETCH_TIMEOUT_MS - }).catch(() => ({ sessions: [], total: 0, profile_totals: {} })) + // OAuth-aware: token is null for OAuth primaries — see fetchPrimaryGetJson. + return fetchPrimaryGetJson(primary, `${primary.baseUrl}/api/profiles/sessions?${searchParams}`) + .catch(() => ({ sessions: [], total: 0, profile_totals: {} })) } return mergeRemoteProfileSessions(searchParams, remoteProfiles) @@ -9246,10 +9262,11 @@ async function mergeRemoteProfileSessions(searchParams, remoteProfiles) { const primary = await ensureBackend(null) - const base = (await fetchJson(`${primary.baseUrl}/api/profiles/sessions?${searchParams}`, primary.token, { - method: 'GET', - timeoutMs: DEFAULT_FETCH_TIMEOUT_MS - }).catch(() => ({ sessions: [], total: 0, profile_totals: {} }))) as any + // Same OAuth caveat as fetchProfilesSessionSlice: token is null for OAuth + // primaries, so without the auth-aware fetch the base aggregate 401s and + // silently contributes zero rows. + const base = (await fetchPrimaryGetJson(primary, `${primary.baseUrl}/api/profiles/sessions?${searchParams}`) + .catch(() => ({ sessions: [], total: 0, profile_totals: {} }))) as any // Over-fetch each remote from offset 0 (limit+offset rows) so the merged window // is correct for this page — mirrors the primary's per-profile over-fetch.