Files
AgentHub/server/__tests__/websocket-pty-token.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

83 lines
3.1 KiB
JavaScript

/**
* @file websocket-pty-token.test.js
* @description With DASHBOARD_TOKEN set, a `/ws-pty/:runId` upgrade is let
* through only with the token (`?token=`, appended after the size query the
* terminal sends), and a refused one gets a real HTTP status and is counted
* by reason — it used to be a silent `socket.destroy()`, so a terminal stuck
* "reconnecting" behind a token gave no clue why. Real http.Server, raw
* upgrade requests; the run itself does not exist, so a let-through upgrade
* ends in the attach failing, never in a 401.
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
*/
const { describe, it, before, after } = require("node:test");
const assert = require("node:assert/strict");
const http = require("node:http");
const net = require("node:net");
process.env.DASHBOARD_TOKEN = "s3cret-token";
const ws = require("../websocket");
function rawUpgrade(port, path) {
return new Promise((resolve) => {
const socket = net.connect(port, "127.0.0.1", () => {
socket.write(
`GET ${path} HTTP/1.1\r\nHost: 127.0.0.1:${port}\r\n` +
`Upgrade: websocket\r\nConnection: Upgrade\r\n` +
`Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\nSec-WebSocket-Version: 13\r\n\r\n`
);
});
let body = "";
socket.on("data", (d) => {
body += d.toString("utf8");
// A 101 means the upgrade was accepted: that is all this test needs.
if (body.startsWith("HTTP/1.1 101")) socket.destroy();
});
socket.on("close", () => resolve(body));
socket.on("error", () => resolve(body));
});
}
describe("/ws-pty behind DASHBOARD_TOKEN", () => {
let server;
let port;
before(async () => {
server = http.createServer((req, res) => res.end("ok"));
ws.initWebSocket(server);
ws.initPtyWebSocket(server);
await new Promise((r) => server.listen(0, "127.0.0.1", r));
port = server.address().port;
});
after(async () => {
ws.closeWebSocket();
await new Promise((r) => server.close(r));
delete process.env.DASHBOARD_TOKEN;
});
it("refuses an attach without the token with a 401, and counts why", async () => {
const before = ws.getUpgradeRefusals().token;
const reply = await rawUpgrade(port, "/ws-pty/agenthub-lane-1?cols=120&rows=30");
assert.match(reply, /^HTTP\/1\.1 401/);
assert.equal(ws.getUpgradeRefusals().token, before + 1);
});
it("refuses a wrong token", async () => {
const reply = await rawUpgrade(port, "/ws-pty/agenthub-lane-1?cols=120&rows=30&token=nope");
assert.match(reply, /^HTTP\/1\.1 401/);
});
it("lets the attach through with the token after the size query", async () => {
const before = ws.getUpgradeRefusals().token;
const reply = await rawUpgrade(
port,
"/ws-pty/agenthub-lane-1?cols=120&rows=30&token=s3cret-token"
);
assert.match(reply, /^HTTP\/1\.1 101/);
assert.equal(ws.getUpgradeRefusals().token, before);
});
it("lets a sizeless attach through with ?token=", async () => {
const reply = await rawUpgrade(port, "/ws-pty/agenthub-lane-1?token=s3cret-token");
assert.match(reply, /^HTTP\/1\.1 101/);
});
});