Files
AgentHub/server/__tests__/dashboard-token-publish.test.js
T
nntrivi2001 4a665da79e fix: a token-gated or LAN-bound dashboard works end to end (WSL mirrored)
- /ws-pty carries the dashboard token like /ws (the terminal never attached
  with DASHBOARD_TOKEN set), and so does everything that cannot send the
  header: images, downloads, exports, drops (withToken/tokenHeader). A
  refused upgrade answers 401/403 and is logged and counted by reason.
- The browser asks for a missing or wrong token (401 EUNAUTHORIZED) and takes
  ?token= from the link agenthub start prints, dropping it from the URL.
- The CLI sends the token (DASHBOARD_TOKEN, else the one the running server
  publishes 0600 in <data dir>/runtime/dashboard-token); MCP and statusline
  read the same file.
- DASHBOARD_HOST takes a list, and 127.0.0.1 is always listened on too, so a
  LAN-only bind keeps hooks and the CLI working.
- agenthub start --host/--allowed-hosts/--token are saved in
  <data dir>/network.json for every later start; a wide bind gets a token;
  every reachable URL is printed.
- tmux < 3.7 is an amber, once-per-version notice that says only the pane
  scrollbar is missing, with a ~/.local build instead of sudo make install.
- INSTALL: PATH belongs in ~/.profile for sudo -iu/ssh; WSL mirrored notes.
2026-11-06 14:59:48 +07:00

55 lines
2.1 KiB
JavaScript

/**
* @file dashboard-token-publish.test.js
* @description A token-gated dashboard publishes its token (0600) for local
* tools, and `dashboard-url.dashboardToken()` — what the `agenthub` CLI, the
* MCP server and the statusline send — takes DASHBOARD_TOKEN first, else that
* file, else nothing. Without it `agenthub remote login/status` answered 401
* from any shell that had not exported the token.
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
*/
const { describe, it, before, after, afterEach } = require("node:test");
const assert = require("node:assert/strict");
const fs = require("node:fs");
const os = require("node:os");
const path = require("node:path");
const TMP = fs.mkdtempSync(path.join(os.tmpdir(), "dash-token-"));
process.env.DASHBOARD_DATA_DIR = TMP;
const security = require("../lib/security");
const { dashboardToken, tokenHeaders } = require("../lib/dashboard-url");
describe("the published dashboard token", () => {
let saved;
before(() => {
saved = process.env.DASHBOARD_TOKEN;
});
afterEach(() => {
if (saved === undefined) delete process.env.DASHBOARD_TOKEN;
else process.env.DASHBOARD_TOKEN = saved;
});
after(() => fs.rmSync(TMP, { recursive: true, force: true }));
it("is written 0600 when the server has one, and read back without the env", () => {
process.env.DASHBOARD_TOKEN = "server-tok";
security.publishDashboardToken();
const file = security.tokenFilePath();
assert.equal(fs.statSync(file).mode & 0o777, 0o600);
delete process.env.DASHBOARD_TOKEN; // a shell that never exported it
assert.equal(dashboardToken(), "server-tok");
assert.deepEqual(tokenHeaders(), { "x-dashboard-token": "server-tok" });
});
it("prefers the caller's own DASHBOARD_TOKEN", () => {
process.env.DASHBOARD_TOKEN = "mine";
assert.equal(dashboardToken(), "mine");
});
it("is removed when the server runs without a token", () => {
delete process.env.DASHBOARD_TOKEN;
security.publishDashboardToken();
assert.equal(fs.existsSync(security.tokenFilePath()), false);
assert.equal(dashboardToken(), null);
assert.deepEqual(tokenHeaders(), {});
});
});