Support username/password auth; test against containerised Hermes on hermternal-dev #1

Open
opened 2026-08-21 08:23:39 +00:00 by kayg · 0 comments
Owner

Problem

The client only supports SSO today (native_pkce). Username/password sign-in
is unimplemented and, more importantly, cannot be tested against the live
instance at all
.

GET https://hermes-dashboard.kayg.org/api/auth/providers returns:

{"providers":[{"name":"self-hosted","display_name":"Self-Hosted OIDC","supports_password":false}]}

POST /auth/password-login checks getattr(p, "supports_password", False) and
returns 404 Unknown provider when it is false
(hermes_cli/dashboard_auth/routes.py:680-690). So there is no way to exercise
the password path against the production gateway.

Plan

1. Stand up a password-capable Hermes

Do not rebuild from the official container docs. A launcher already exists:

~/Developer/hermternal/.claude/skills/deploy-hermes-agent/SKILL.md

It runs the official immutable image under rootless Podman as
hermternal-test@hermternal-dev, preserves the upstream entrypoint, runs
gateway run, and enables a synthetic Basic provider — precisely the
password path we need. Expose it over Netbird so the macOS client can reach it.

2. Implement the client side

  • AuthClient: add POST /auth/password-login with body
    {provider, username, password}.
  • SignInView: add a credentials form, shown only when
    /api/auth/providers advertises a provider with supports_password: true.
    Keep the SSO button as the only option otherwise.

The real work: cookies vs bearer

/auth/password-login does not return bearer tokens. It calls
set_session_cookies(...) and returns {"ok": true, "next": "/"}
(routes.py:729-739), whereas /auth/native/token returns
{access_token, refresh_token, expires_at, ...} in the JSON body.

That asymmetry is the bulk of the effort, not the form:

  • CredentialStore currently persists a bearer pair. It needs a cookie-session
    branch, or a common abstraction over both.
  • POST /api/auth/ws-ticket is authenticated by the gate; it accepts the
    session cookie as well as a bearer, but the client must actually send the
    cookie jar (URLSession cookie storage) instead of an Authorization
    header.
  • Token refresh differs: /auth/native/refresh takes a refresh token, while
    cookie sessions are rotated transparently by the gate middleware. The
    refresh path must not run for cookie sessions.

Acceptance

  • Container reachable over Netbird, /api/auth/providers shows a provider
    with supports_password: true.
  • Password sign-in succeeds from the macOS client against that instance.
  • The cookie session mints a ws-ticket, /api/ws reaches gateway.ready,
    and a chat turn streams end to end.
  • SSO against hermes-dashboard.kayg.org still works unchanged.
## Problem The client only supports SSO today (`native_pkce`). Username/password sign-in is unimplemented and, more importantly, **cannot be tested against the live instance at all**. `GET https://hermes-dashboard.kayg.org/api/auth/providers` returns: ```json {"providers":[{"name":"self-hosted","display_name":"Self-Hosted OIDC","supports_password":false}]} ``` `POST /auth/password-login` checks `getattr(p, "supports_password", False)` and returns **404 Unknown provider** when it is false (`hermes_cli/dashboard_auth/routes.py:680-690`). So there is no way to exercise the password path against the production gateway. ## Plan ### 1. Stand up a password-capable Hermes Do **not** rebuild from the official container docs. A launcher already exists: `~/Developer/hermternal/.claude/skills/deploy-hermes-agent/SKILL.md` It runs the official immutable image under rootless Podman as `hermternal-test@hermternal-dev`, preserves the upstream entrypoint, runs `gateway run`, and **enables a synthetic Basic provider** — precisely the password path we need. Expose it over Netbird so the macOS client can reach it. ### 2. Implement the client side - `AuthClient`: add `POST /auth/password-login` with body `{provider, username, password}`. - `SignInView`: add a credentials form, shown only when `/api/auth/providers` advertises a provider with `supports_password: true`. Keep the SSO button as the only option otherwise. ## The real work: cookies vs bearer `/auth/password-login` does **not** return bearer tokens. It calls `set_session_cookies(...)` and returns `{"ok": true, "next": "/"}` (`routes.py:729-739`), whereas `/auth/native/token` returns `{access_token, refresh_token, expires_at, ...}` in the JSON body. That asymmetry is the bulk of the effort, not the form: - `CredentialStore` currently persists a bearer pair. It needs a cookie-session branch, or a common abstraction over both. - `POST /api/auth/ws-ticket` is authenticated by the gate; it accepts the session cookie as well as a bearer, but the client must actually send the cookie jar (`URLSession` cookie storage) instead of an `Authorization` header. - Token refresh differs: `/auth/native/refresh` takes a refresh token, while cookie sessions are rotated transparently by the gate middleware. The refresh path must not run for cookie sessions. ## Acceptance - Container reachable over Netbird, `/api/auth/providers` shows a provider with `supports_password: true`. - Password sign-in succeeds from the macOS client against that instance. - The cookie session mints a ws-ticket, `/api/ws` reaches `gateway.ready`, and a chat turn streams end to end. - SSO against `hermes-dashboard.kayg.org` still works unchanged.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
kayg/hermternal-apple#1
No description provided.