/** * @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ĩ */ 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/); }); });