mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-01 12:02:05 +00:00
The self-hosted OIDC dashboard provider was public-client + PKCE only, with two `# TODO(confidential-client)` seams. Authentik and Keycloak commonly default a new OIDC client to *confidential*, whose token endpoint rejects an unauthenticated exchange (`invalid_client`) — so a self-hoster who accepts their IDP's default could not complete dashboard login without manually flipping the client to public. Add optional confidential-client support: - New optional `client_secret` (env `HERMES_DASHBOARD_OIDC_CLIENT_SECRET`, or `dashboard.oauth.self_hosted.client_secret`; env-wins-config, empty treated as unset). It is a credential, so docs steer operators to the `.env` file; config.yaml is supported only for precedence symmetry. - `_token_endpoint_auth()` selects `client_secret_basic` (HTTP Basic header) vs `client_secret_post` (form body) from the IDP's advertised `token_endpoint_auth_methods_supported`, defaulting to basic (the OIDC default) when absent. Applied to complete_login, refresh_session, and revoke_session (RFC 7009 §2.1). - PKCE is sent in BOTH modes — the secret is client authentication layered on top, never a replacement (OAuth 2.1 / RFC 9700 keep PKCE mandatory). - Basic header url-encodes client_id/secret before base64 per RFC 6749 §2.3.1, so reserved chars (`:`, `@`, space) round-trip correctly. Non-breaking: with no secret configured the provider is a pure public PKCE client, byte-identical to prior behaviour (no Authorization header, no client_secret in the body). The secret is never logged — register() reports only a `confidential=<bool>` flag. Tests: 16 new cases covering basic/post selection, default-when-absent, public-unchanged contract, PKCE-preserved, reserved-char url-encoding, blank-secret-is-public, refresh + revoke auth, no-secret-in-logs, and env/config register wiring. Full dashboard-auth suite (nous provider, middleware, gate, cookies, WS, 401-reauth, status endpoint) — 396 tests — green, proving no existing auth path regressed. |
||
|---|---|---|
| .. | ||
| test_basic_provider.py | ||
| test_drain_provider.py | ||
| test_nous_provider.py | ||
| test_self_hosted_provider.py | ||