feat(dashboard-auth): add BasicAuthProvider username/password plugin
A bundled, zero-infrastructure 'just put a password on my dashboard'
provider that uses the supports_password extension point. No external IDP,
no database: sessions are stateless HMAC-signed tokens the provider mints
and verifies itself, and passwords are hashed with stdlib scrypt (no
third-party dependency — deliberately avoids bcrypt to keep the dep
surface unchanged).
- plugins/dashboard_auth/basic: BasicAuthProvider (scrypt verify with a
constant-time dummy-hash path for unknown users so the endpoint is not
a username-timing oracle; access/refresh tokens carry a 'kind' claim
that verify/refresh enforce; cross-secret tokens are rejected). The
register() entry point mirrors the Nous plugin's config/env precedence
(env wins; empty treated as unset) and LAST_SKIP_REASON channel.
- config.py: document the canonical dashboard.basic_auth.* surface
(username / password_hash / password / secret / session_ttl_seconds).
Activates only when username + (password or password_hash) are set, so
OAuth users and loopback/--insecure operators are unaffected. Without an
explicit secret a random per-process key is generated (logged): fine for a
single process, but sessions then don't survive restart or span workers.
This commit is contained in:
@@ -1482,6 +1482,34 @@ DEFAULT_CONFIG = {
|
||||
"client_id": "", # agent:{instance_id} — Portal provisions this
|
||||
"portal_url": "", # blank → use plugin default (production Portal)
|
||||
},
|
||||
# Username/password gate configuration — read by the bundled
|
||||
# ``dashboard_auth/basic`` plugin (a self-hosted "just put a
|
||||
# password on my dashboard" provider that needs no OAuth IDP).
|
||||
# The plugin registers a password provider when ``username`` plus
|
||||
# either ``password_hash`` (preferred — no plaintext at rest) or
|
||||
# ``password`` (plaintext, hashed in-memory at load) are set. Each
|
||||
# key is overridable by an env var
|
||||
# (``HERMES_DASHBOARD_BASIC_AUTH_USERNAME`` /
|
||||
# ``_PASSWORD_HASH`` / ``_PASSWORD`` / ``_SECRET`` /
|
||||
# ``_TTL_SECONDS``), env winning when non-empty. Leave ``username``
|
||||
# empty (the default) to keep the plugin a no-op — loopback /
|
||||
# ``--insecure`` operators and OAuth users are unaffected.
|
||||
#
|
||||
# ``secret`` is the HMAC key used to sign the stateless session
|
||||
# tokens this provider mints. When empty, a random per-process key
|
||||
# is generated — fine for a single process, but sessions then
|
||||
# don't survive a restart or span multiple workers. Set an
|
||||
# explicit ``secret`` (32+ random bytes, base64/hex/raw) for
|
||||
# stable multi-worker / restart-surviving sessions. Compute a
|
||||
# ``password_hash`` with
|
||||
# ``python -c "from plugins.dashboard_auth.basic import hash_password; print(hash_password('PW'))"``.
|
||||
"basic_auth": {
|
||||
"username": "", # blank → plugin no-op (no password provider)
|
||||
"password_hash": "", # scrypt$... (preferred — no plaintext at rest)
|
||||
"password": "", # plaintext fallback (hashed in-memory at load)
|
||||
"secret": "", # token-signing key; blank → random per-process
|
||||
"session_ttl_seconds": 0, # 0 → plugin default (12h)
|
||||
},
|
||||
# Public URL override (env: ``HERMES_DASHBOARD_PUBLIC_URL``).
|
||||
# When set, this is the complete authority — scheme + host +
|
||||
# optional path prefix (e.g. ``https://example.com/hermes``) —
|
||||
|
||||
Reference in New Issue
Block a user