4a665da79e
- /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.
55 lines
2.1 KiB
JavaScript
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(), {});
|
|
});
|
|
});
|