fix(dashboard): honor injected HERMES_DASHBOARD_SESSION_TOKEN
The desktop shell mints a session token and signs its /api + /api/ws calls with it via HERMES_DASHBOARD_SESSION_TOKEN, but the main-merge restored a web_server.py that ignored the env var and minted its own random _SESSION_TOKEN -- so every desktop request 401'd and the UI reported "gateway offline". Read the injected token (fall back to a fresh random one) so loopback HTTP + WS auth line up. Adds a regression test so a future merge can't silently drop the read.
This commit is contained in:
@@ -85,10 +85,13 @@ app = FastAPI(title="Hermes Agent", version=__version__)
|
|||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Session token for protecting sensitive endpoints (reveal).
|
# Session token for protecting sensitive endpoints (reveal).
|
||||||
# Generated fresh on every server start — dies when the process exits.
|
# The desktop shell mints the token and injects it via
|
||||||
# Injected into the SPA HTML so only the legitimate web UI can use it.
|
# HERMES_DASHBOARD_SESSION_TOKEN so its main process can authenticate the
|
||||||
|
# /api calls it makes on the user's behalf; otherwise we generate one fresh
|
||||||
|
# on every server start. Either way it dies when the process exits and is
|
||||||
|
# injected into the SPA HTML so only the legitimate web UI can use it.
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
_SESSION_TOKEN = secrets.token_urlsafe(32)
|
_SESSION_TOKEN = os.environ.get("HERMES_DASHBOARD_SESSION_TOKEN") or secrets.token_urlsafe(32)
|
||||||
_SESSION_HEADER_NAME = "X-Hermes-Session-Token"
|
_SESSION_HEADER_NAME = "X-Hermes-Session-Token"
|
||||||
|
|
||||||
# In-browser Chat tab (/chat, /api/pty, …). Off unless ``hermes dashboard --tui``
|
# In-browser Chat tab (/chat, /api/pty, …). Off unless ``hermes dashboard --tui``
|
||||||
|
|||||||
@@ -89,6 +89,35 @@ class TestRedactKey:
|
|||||||
assert "not set" in result.lower() or result == "***" or "\x1b" in result
|
assert "not set" in result.lower() or result == "***" or "\x1b" in result
|
||||||
|
|
||||||
|
|
||||||
|
class TestSessionTokenInjection:
|
||||||
|
"""The desktop shell mints HERMES_DASHBOARD_SESSION_TOKEN and signs its
|
||||||
|
/api + /api/ws calls with it. The backend must adopt that token, else every
|
||||||
|
desktop request 401s ("gateway is offline"). A main-merge once silently
|
||||||
|
dropped this read — this guards the contract, not a literal value.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def test_honors_injected_token(self, monkeypatch):
|
||||||
|
import importlib
|
||||||
|
import hermes_cli.web_server as ws
|
||||||
|
|
||||||
|
monkeypatch.setenv("HERMES_DASHBOARD_SESSION_TOKEN", "desktop-seeded-token")
|
||||||
|
try:
|
||||||
|
importlib.reload(ws)
|
||||||
|
assert ws._SESSION_TOKEN == "desktop-seeded-token"
|
||||||
|
finally:
|
||||||
|
monkeypatch.delenv("HERMES_DASHBOARD_SESSION_TOKEN", raising=False)
|
||||||
|
importlib.reload(ws)
|
||||||
|
|
||||||
|
def test_falls_back_to_random_token(self, monkeypatch):
|
||||||
|
import importlib
|
||||||
|
import hermes_cli.web_server as ws
|
||||||
|
|
||||||
|
monkeypatch.delenv("HERMES_DASHBOARD_SESSION_TOKEN", raising=False)
|
||||||
|
importlib.reload(ws)
|
||||||
|
|
||||||
|
assert ws._SESSION_TOKEN and len(ws._SESSION_TOKEN) >= 32
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# web_server tests (FastAPI endpoints)
|
# web_server tests (FastAPI endpoints)
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user