Farhan Beg commited on
Commit
8da84d4
·
1 Parent(s): 23ba9c5

feat(router): add /hmd passthrough for off-Space dashboard access

Browse files

Forwards /hmd/* directly to the internal dashboard (DASHBOARD_PORT)
including /api/*, assets, root HTML and WebSocket upgrades.

Lets remote workspaces (e.g. hermes-workspace) point
HERMES_DASHBOARD_URL=https://<space>/hmd and use the dashboard's own
ephemeral session token without going through HuggingMes auth.

No existing route changes. /hm and /hm/app/* still cookie-gated.

Files changed (1) hide show
  1. health-server.js +38 -0
health-server.js CHANGED
@@ -7,6 +7,9 @@
7
  * /login -> HuggingMes login (password = GATEWAY_TOKEN)
8
  * /health /status -> JSON health (unauthenticated — for HF probes + keepalive)
9
  * /hm /hm/* -> HuggingMes status page + app (auth-gated)
 
 
 
10
  * /dashboard -> redirect to /hm
11
  * /v1 /v1/* -> Hermes gateway (bearer auth; HTML => login redirect)
12
  * /telegram /telegram/*-> Telegram webhook (unauthenticated; Telegram needs to reach it)
@@ -33,6 +36,17 @@ const GATEWAY_HOST = "127.0.0.1";
33
  const startTime = Date.now();
34
  const API_SERVER_KEY = process.env.API_SERVER_KEY || "";
35
  const HM_PREFIX = "/hm";
 
 
 
 
 
 
 
 
 
 
 
36
  const LOGIN_PATH = "/hm/login";
37
  const SESSION_COOKIE = "huggingmes_session";
38
  const PRIMARY_UI = (process.env.PRIMARY_UI || "webui").toLowerCase();
@@ -746,6 +760,25 @@ const server = http.createServer(async (req, res) => {
746
  return;
747
  }
748
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
749
  // /hm/app/* -> Hermes dashboard (SPA with HTML rewriting for base path)
750
  if (path === `${HM_PREFIX}/app` || path.startsWith(`${HM_PREFIX}/app/`)) {
751
  if (!requireAuth(req, res)) return;
@@ -884,6 +917,11 @@ server.on("upgrade", (req, clientSocket, head) => {
884
 
885
  if (path === "/v1" || path.startsWith("/v1/")) {
886
  targetPort = GATEWAY_PORT;
 
 
 
 
 
887
  } else if (path === `${HM_PREFIX}/app` || path.startsWith(`${HM_PREFIX}/app/`)) {
888
  targetPort = DASHBOARD_PORT;
889
  targetPath = path.replace(`${HM_PREFIX}/app`, "") || "/";
 
7
  * /login -> HuggingMes login (password = GATEWAY_TOKEN)
8
  * /health /status -> JSON health (unauthenticated — for HF probes + keepalive)
9
  * /hm /hm/* -> HuggingMes status page + app (auth-gated)
10
+ * /hmd /hmd/* -> Hermes dashboard passthrough for off-Space
11
+ * workspaces (no router auth — dashboard's own
12
+ * session token gates writes; opt-in by URL)
13
  * /dashboard -> redirect to /hm
14
  * /v1 /v1/* -> Hermes gateway (bearer auth; HTML => login redirect)
15
  * /telegram /telegram/*-> Telegram webhook (unauthenticated; Telegram needs to reach it)
 
36
  const startTime = Date.now();
37
  const API_SERVER_KEY = process.env.API_SERVER_KEY || "";
38
  const HM_PREFIX = "/hm";
39
+ // Dashboard passthrough for off-Space workspaces (e.g. hermes-workspace
40
+ // running on a laptop). Anything under /hmd/* is forwarded directly to the
41
+ // internal dashboard with no router-level auth — the dashboard's own
42
+ // ephemeral session token is the only gate. This is intentional: the
43
+ // workspace scrapes that token from /hmd/ and then sends it as the bearer
44
+ // on /hmd/api/* requests, exactly mirroring the dashboard's normal flow.
45
+ //
46
+ // Implication: anyone who can reach this Space's URL can call the dashboard
47
+ // API (sessions, skills, config). If you don't need remote workspace access,
48
+ // don't share the Space URL or set up an upstream auth layer.
49
+ const HMD_PREFIX = "/hmd";
50
  const LOGIN_PATH = "/hm/login";
51
  const SESSION_COOKIE = "huggingmes_session";
52
  const PRIMARY_UI = (process.env.PRIMARY_UI || "webui").toLowerCase();
 
760
  return;
761
  }
762
 
763
+ // /hmd/* — Off-Space dashboard passthrough.
764
+ //
765
+ // Forwards verbatim to the internal Hermes dashboard on DASHBOARD_PORT,
766
+ // including its /api/* endpoints, /assets/*, root HTML (which carries the
767
+ // ephemeral session token), and WebSocket upgrades. Workspace clients
768
+ // (e.g. hermes-workspace) point HERMES_DASHBOARD_URL at
769
+ // https://<space>/hmd
770
+ // and the workspace's own scrape-the-token-from-root-HTML logic just
771
+ // works because /hmd/ returns the unmodified dashboard index.
772
+ //
773
+ // SECURITY: this prefix has no router-level auth on purpose — the
774
+ // dashboard's own session token gates writes. If you need an extra layer,
775
+ // wrap your Space behind a Cloudflare Access policy or remove this
776
+ // handler.
777
+ if (path === HMD_PREFIX || path.startsWith(`${HMD_PREFIX}/`)) {
778
+ proxyRequest(req, res, DASHBOARD_PORT, (p) => p.replace(HMD_PREFIX, "") || "/");
779
+ return;
780
+ }
781
+
782
  // /hm/app/* -> Hermes dashboard (SPA with HTML rewriting for base path)
783
  if (path === `${HM_PREFIX}/app` || path.startsWith(`${HM_PREFIX}/app/`)) {
784
  if (!requireAuth(req, res)) return;
 
917
 
918
  if (path === "/v1" || path.startsWith("/v1/")) {
919
  targetPort = GATEWAY_PORT;
920
+ } else if (path === HMD_PREFIX || path.startsWith(`${HMD_PREFIX}/`)) {
921
+ // Off-Space dashboard passthrough (mirrors the HTTP /hmd handler).
922
+ targetPort = DASHBOARD_PORT;
923
+ targetPath = path.replace(HMD_PREFIX, "") || "/";
924
+ if (parsed.search) targetPath += parsed.search;
925
  } else if (path === `${HM_PREFIX}/app` || path.startsWith(`${HM_PREFIX}/app/`)) {
926
  targetPort = DASHBOARD_PORT;
927
  targetPath = path.replace(`${HM_PREFIX}/app`, "") || "/";