Farhan Beg commited on
Commit ·
8da84d4
1
Parent(s): 23ba9c5
feat(router): add /hmd passthrough for off-Space dashboard access
Browse filesForwards /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.
- 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`, "") || "/";
|