Skip to content

fix(desktop): gate OAuth remote connect on AT-or-RT, not access token alone - #39464

Merged
teknium1 merged 1 commit into
mainfrom
fix/desktop-oauth-rt-refresh-gate
Jun 5, 2026
Merged

fix(desktop): gate OAuth remote connect on AT-or-RT, not access token alone#39464
teknium1 merged 1 commit into
mainfrom
fix/desktop-oauth-rt-refresh-gate

Conversation

@benbarclay

Copy link
Copy Markdown
Collaborator

Problem

The desktop OAuth remote-gateway path gated connectivity on hasOauthSessionCookie(), which checks only the access-token cookie (hermes_session_at, ~15 min TTL). The instant that cookie's Max-Age lapsed, Electron's cookie jar dropped it and both resolveRemoteBackend() and sanitizeDesktopConnectionConfig() reported "not signed in" — forcing a full IDP re-login every ~15 minutes — even though a valid 24h refresh-token cookie (hermes_session_rt) was sitting in the same jar.

Impact case

Hits every desktop user on an OAuth (gated/hosted) remote gateway — the common path for the headline "connect to a hosted gateway" feature, not an exotic config. Symptom: you get bounced to the IDP sign-in screen roughly every 15 minutes of use, despite holding a 24h credential the server is fully prepared to honor.

Root cause

The desktop OAuth code was written against the obsolete "contract v1 issues no refresh token" model — and landed two days after #37247 re-introduced server-side transparent refresh. Since #37247, Nous Portal issues a 24h rotating, reuse-detected refresh token, and the gateway middleware (_attempt_refresh) transparently rotates a fresh AT from the RT on the next authenticated request. So an expired-AT/live-RT session is fully connectable — the desktop just never let the request through; the AT-only gate short-circuited before the ws-ticket mint (which would have triggered the server refresh).

Fix

  • connection-config.cjs: add RT_COOKIE_VARIANTS + cookiesHaveLiveSession() (true when either a live AT or RT cookie is present). cookiesHaveSession() stays AT-only for callers that need that specific signal.
  • main.cjs: add hasLiveOauthSession(). resolveRemoteBackend()'s oauth branch now early-outs only when neither cookie is present; otherwise it uses the ws-ticket mint as the authoritative liveness probe (that POST carries the RT cookie and triggers the server-side AT rotation). A real 401 still surfaces as needsOauthLogin. Settings indicator + oauth-logout report against the same AT-or-RT notion.
  • Remove the stale "contract v1 / NO refresh token" docstrings in hermes_cli/dashboard_auth/cookies.py and the verify_session comments in plugins/dashboard_auth/nous/__init__.py that contradicted feat(dashboard-auth): rotate dashboard sessions via refresh token #37247. (Comment/docstring-only — no behavior change server-side.)

Tests

  • node --test (desktop): 32/32 — +57 lines covering the RT-only "still connectable" case in connection-config.test.cjs.
  • dashboard-auth + nous-provider Python suites: 223/223.

Manual E2E (live, against a real gated gateway)

Validated against a local OAuth gateway (localhost:9119) by waiting out a natural AT expiry:

  • After the AT cookie expired, Settings → Gateway still showed "Signed in" and the System page showed the gateway running (fix Terminal tool #1).
  • Cookie-DB capture at expiry showed the old AT (lapsed) replaced within seconds by a fresh AT (new ~15-min window) AND a rotated RT, both persisted back into the Electron OAuth partition — confirming the server's transparent refresh fired and the rotated Set-Cookie was captured (closing the reuse-detection risk: a dropped rotated cookie would have replayed a stale RT and revoked the session).
  • Sent a chat message → got a response, verified in the web dashboard that it reached the gateway, with no sign-in prompt at any point (fix Support passing morph snapshot id #2).

Review note

Touches hermes_cli/dashboard_auth/ and plugins/dashboard_auth/ (comment/docstring-only) in addition to apps/desktop/, so this is outside the pure-Docker lane and needs @teknium1 review.

… alone

The desktop OAuth remote-gateway path gated connectivity on
hasOauthSessionCookie(), which checks only the access-token cookie
(hermes_session_at, ~15 min TTL). The moment that cookie's Max-Age
lapsed, Electron's cookie jar dropped it and both resolveRemoteBackend()
and sanitizeDesktopConnectionConfig() reported "not signed in" — forcing
a full IDP re-login every ~15 min — even though a valid 24h refresh-token
cookie (hermes_session_rt) was sitting in the same jar.

The desktop OAuth code (2026-06-04) was written against the obsolete
"contract v1 issues no refresh token" model, two days after #37247
re-introduced server-side transparent refresh: Portal now issues a 24h
rotating, reuse-detected refresh token, and the gateway middleware
(_attempt_refresh) rotates a fresh AT from the RT on the next
authenticated request. So an expired-AT/live-RT session is fully
connectable — the desktop just never let the request through.

Fix:
- connection-config.cjs: add RT_COOKIE_VARIANTS + cookiesHaveLiveSession()
  (true when EITHER a live AT or RT cookie is present). Keep
  cookiesHaveSession() AT-only for callers that need that specific signal.
- main.cjs: add hasLiveOauthSession(); resolveRemoteBackend()'s oauth
  branch now early-outs only when NEITHER cookie is present, otherwise
  uses the ws-ticket mint as the authoritative liveness probe (that POST
  carries the RT cookie and triggers the server-side AT rotation). A real
  401 still surfaces as needsOauthLogin. Settings indicator + oauth-logout
  report against the same AT-or-RT notion.
- Remove the stale "contract v1 / NO refresh token" docstrings in
  cookies.py and the verify_session comments in the Nous provider that
  contradicted #37247.

Tests: +57 lines in connection-config.test.cjs covering the RT-only
"still connectable" case. node --test: 32/32. dashboard-auth +
nous-provider Python suites: 223/223.

Note: server-side files (hermes_cli/dashboard_auth/, plugins/dashboard_auth/)
are comment/docstring-only here, but this touches outside apps/desktop/ so
it needs Teknium review.
@github-actions

github-actions Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

🔎 Lint report: fix/desktop-oauth-rt-refresh-gate vs origin/main

ruff

Total: 0 on HEAD, 0 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 0 pre-existing issues carried over.

ty (type checker)

Total: 9841 on HEAD, 9841 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 5103 pre-existing issues carried over.

Diagnostics are surfaced as warnings — this check never fails the build.

@alt-glitch alt-glitch added type/bug Something isn't working P3 Low — cosmetic, nice to have area/auth Authentication, OAuth, credential pools labels Jun 5, 2026

@tonydwb tonydwb left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review Summary

Verdict: Approved

Bug fix: desktop app OAuth connectivity checks now look for EITHER a live access-token cookie OR a (longer-lived) refresh-token cookie. Previously gating only on the AT cookie caused spurious "not signed in" states every ~15 min even when a valid 24h RT was present. The fix adds cookiesHaveLiveSession, hasLiveOauthSession, and updates resolveRemoteBackend accordingly.

Design

  • The two-function distinction is clear: cookiesHaveSession (AT-only) answers "is there an unexpired AT right now?"; cookiesHaveLiveSession (AT-or-RT) answers "is the user signed in at all?" for display/early-out purposes.
  • The authoritative liveness check remains the ws-ticket mint at connect time (which triggers server-side AT rotation). This is the right architecture.
  • Comment in connection-config.cjs documents the Portal NAS #293 / hermes #37247 contract change clearly.
  • Dashboard auth cookies.py updated to reflect the 24h rotating RT (updating the old "30 days + deprecated" comments).

Tests

  • Extensive new tests for cookiesHaveLiveSession covering AT-only, RT-only, both, empty values, unrelated cookies.

No concerns


Reviewed by Hermes Agent

@teknium1
teknium1 merged commit 439f53c into main Jun 5, 2026
30 of 31 checks passed
@teknium1
teknium1 deleted the fix/desktop-oauth-rt-refresh-gate branch June 5, 2026 05:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/auth Authentication, OAuth, credential pools P3 Low — cosmetic, nice to have type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants