hermes-agent/web/src
Ben Barclay 7857d8737c feat(dashboard-auth): RFC 8252 native desktop sign-in (system browser + PKCE, no webview/cookies)
The Desktop app can now sign in to a gated gateway using the user's SYSTEM
browser and OAuth 2.0 for Native Apps (RFC 8252) instead of an embedded
Electron BrowserWindow, and authenticates with bearer tokens it holds itself
instead of relying on HttpOnly browser session cookies.

Why brokered: the upstream IDP (Nous Portal) binds client_id to the gateway
instance and only permits redirect_uris on the gateway's own origin, so a
desktop loopback redirect can't be a direct Portal client. The gateway
therefore acts as the authorization server TO the desktop and an OAuth client
TO the Portal, reusing the existing PKCE start_login/complete_login provider
path unchanged.

Server (Ben's dashboard-auth lane):
- native_flow.py: in-memory broker — binds the desktop's PKCE challenge to a
  completed Session, mints a single-use, short-TTL, PKCE-verified gateway
  authorization code. Constant-time compare, single-use (consumed before the
  PKCE check so a wrong verifier can't be retried), capacity-bounded.
- routes.py: GET /auth/native/authorize (starts the brokered PKCE login,
  loopback-only redirect_uri, S256-only), POST /auth/native/token (loopback
  code + verifier -> tokens in the JSON body, never Set-Cookie), POST
  /auth/native/refresh (desktop-held RT rotation). /auth/callback branches to
  mint a loopback code + 302 to 127.0.0.1 when a broker_state rides the PKCE
  cookie; the cookie/SPA path is untouched.
- middleware.py: the gate accepts Authorization: Bearer <access_token>,
  verified via the same verify_session provider stack (no cookie set/read),
  with the same "provider unreachable -> 503, not logout" semantics.
- web_server.py /api/status: advertise auth_flows (["cookie","native_pkce"])
  so clients can detect the capability; native_pkce only when a brokerable
  OAuth provider is registered.

Desktop (Ben's lane):
- native-oauth.ts: pure PKCE/capability/URL/callback/token helpers.
- native-oauth-login.ts: loopback-listener orchestration (system browser via
  openExternal, ephemeral 127.0.0.1 listener, state/PKCE verification), all
  I/O injected for testability.
- main.ts: capability-gated oauth-login IPC — native flow when advertised,
  automatic fallback to the existing embedded-webview cookie flow otherwise;
  tokens stored encrypted (safeStorage/OS keychain), REST + ws-ticket
  authenticated by bearer, transparent refresh, logout clears both shapes.

Tests: 18 server pytest (broker unit + full authorize->callback->token E2E +
cookieless bearer auth of a gated route + ws-ticket mint + capability
advertisement + refresh); desktop node --test/vitest for both pure modules
(PKCE, capability detection, callback CSRF, loopback round trip, timeout,
browser-open failure). Electron project typechecks clean.

Docs: website/docs/guides/desktop-native-signin.md.
2026-07-22 06:50:50 -07:00
..
components fix(dashboard): don't let a provider-name query hide the selected provider's models (#65374) (#65413) 2026-07-19 19:05:44 -04:00
contexts fix(web): remove marketing backdrop stack for lighter dashboard shell 2026-06-29 12:30:24 -07:00
hooks Merge remote-tracking branch 'origin/main' into refactor/use-ds-primitives 2026-05-28 14:20:49 -04:00
i18n feat(kanban): modal create-task dialog, editable board project directory, comment workflow hint (#66333) 2026-07-17 07:23:54 -07:00
lib feat(dashboard-auth): RFC 8252 native desktop sign-in (system browser + PKCE, no webview/cookies) 2026-07-22 06:50:50 -07:00
pages fix(dashboard): opaque MoA presets modal (stop page bleed-through) (#67410) 2026-07-19 19:09:12 -04:00
plugins fix(web): theme terminal foreground and restore backdrop plugin slot 2026-06-29 12:30:24 -07:00
themes fix(web): theme terminal foreground and restore backdrop plugin slot 2026-06-29 12:30:24 -07:00
App.tsx fix(web): confirm sidebar Update Hermes before running 2026-06-29 12:30:24 -07:00
index.css fix(web): remove marketing backdrop stack for lighter dashboard shell 2026-06-29 12:30:24 -07:00
main.tsx fix(dashboard): remove country flags from language picker (#29997) 2026-05-21 13:10:52 -07:00