import { afterEach, describe, expect, it, vi } from "vitest"; import fs from "node:fs"; import http, { createServer } from "node:http"; import { createConnection } from "node:net"; import os from "node:os"; import path from "node:path"; import { DESKTOP_COMPANION_FIELD, MANAGED_COMPANION_HOST_SUFFIXES, NEXT_MANAGED_COMPANION_HOST_SUFFIX, desktopCompanionAccess, desktopCompanionRendererArguments, desktopCompanionProxyTarget, normalizeTailscaleCompanionEndpoint, normalizeDesktopCompanionEndpoint, pairDesktopCompanion, proxyDesktopCompanionApi, proxyDesktopCompanionUpgrade, startDesktopCompanionRelay, withDesktopCompanionAccess, withoutDesktopCompanionAccess, } from "./desktop-companion-client.mjs"; const token = `omb_${"a".repeat(43)}`; // The one opaque label the control plane puts in front of a managed domain. const LABEL = "c-0123456789abcdef0123456789abcdef"; const deviceId = "123e4567-e89b-12d3-a456-426614174000"; const access = { endpoint: "http://host.example-tailnet.ts.net:8810", token, deviceId, serverName: "Office computer", }; const servers = []; const sockets = []; const tempDirs = []; afterEach(async () => { for (const socket of sockets.splice(0)) socket.destroy(); await Promise.all(servers.splice(0).map((server) => new Promise((resolve) => server.close(resolve)))); for (const directory of tempDirs.splice(0)) fs.rmSync(directory, { recursive: true, force: true }); }); describe("desktop companion endpoint", () => { it("preserves the local-origin boundary while marking companion client mode", () => { expect(desktopCompanionRendererArguments("http://127.0.0.1:8799", null)).toEqual([ "--omb-local-origin=http://127.0.0.1:8799", ]); expect(desktopCompanionRendererArguments("http://127.0.0.1:8798", access)).toEqual([ "--omb-local-origin=http://127.0.0.1:8798", "--openmausbot-remote-client", ]); }); it("accepts managed HTTPS and cleartext only for Tailscale MagicDNS", () => { expect(normalizeTailscaleCompanionEndpoint("host.example-tailnet.ts.net")).toBe( "http://host.example-tailnet.ts.net:8810", ); expect(normalizeTailscaleCompanionEndpoint("http://HOST.example-tailnet.ts.net:9910/")).toBe( "http://host.example-tailnet.ts.net:9910", ); expect(normalizeDesktopCompanionEndpoint(`https://${LABEL}.openmausbot.com`)).toBe( `https://${LABEL}.openmausbot.com`, ); expect(normalizeDesktopCompanionEndpoint(`${LABEL}.openmausbot.com`)).toBe( `https://${LABEL}.openmausbot.com`, ); // The control plane's second Cloudflare account gives addresses here. expect(normalizeDesktopCompanionEndpoint(`https://${LABEL}.mausbot.si`)).toBe( `https://${LABEL}.mausbot.si`, ); expect(normalizeDesktopCompanionEndpoint(` ${LABEL.toUpperCase()}.Mausbot.SI/ `)).toBe( `https://${LABEL}.mausbot.si`, ); for (const endpoint of [ "https://unrelated.example.com", `http://${LABEL}.openmausbot.com`, "http://10.0.0.4:8810", "http://host.local:8810", "https://10.0.0.4", "http://host.example-tailnet.ts.net/path", `https://${LABEL}.openmausbot.com/path`, "http://user@host.example-tailnet.ts.net", "http://host.example-tailnet.ts.net.evil.test", `https://${LABEL}.openmausbot.com.evil.test`, `http://${LABEL}.mausbot.si`, `https://${LABEL}.mausbot.si/path`, `https://${LABEL}.mausbot.si.evil.test`, "https://mausbot.si", "mausbot.si", "https://evilmausbot.si", `https://${LABEL}.evilmausbot.si`, "https://www.mausbot.si", "https://app.mausbot.si", "https://accounts.openmausbot.com", "https://c-opaque.openmausbot.com", "https://c-opaque.mausbot.si", `https://${LABEL.slice(0, -1)}.mausbot.si`, `https://${LABEL}0.mausbot.si`, `https://${LABEL.replace("c-", "d-")}.mausbot.si`, `https://${LABEL}.extra.mausbot.si`, `https://www.${LABEL}.mausbot.si`, ]) { expect(normalizeDesktopCompanionEndpoint(endpoint), endpoint).toBe(""); } }); it("accepts one c-<32 hex> label under each listed domain, and only those", () => { // The third domain is a one-line change; until the owner names it, nothing new is accepted. expect(NEXT_MANAGED_COMPANION_HOST_SUFFIX).toBe(""); expect(MANAGED_COMPANION_HOST_SUFFIXES).toEqual(["openmausbot.com", "mausbot.si"]); expect(Object.isFrozen(MANAGED_COMPANION_HOST_SUFFIXES)).toBe(true); for (const suffix of MANAGED_COMPANION_HOST_SUFFIXES) { expect(suffix).toMatch(/^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}$/); expect(normalizeDesktopCompanionEndpoint(`https://${LABEL}.${suffix}`)).toBe(`https://${LABEL}.${suffix}`); expect(normalizeDesktopCompanionEndpoint(`${LABEL.toUpperCase().replace("C-", "c-")}.${suffix}`)) .toBe(`https://${LABEL}.${suffix}`); for (const near of [ `https://${LABEL}.${suffix}.`, `https://${LABEL}.x${suffix}`, `https://${LABEL}.sub.${suffix}`, `https://${LABEL.slice(0, -1)}g.${suffix}`, `https://${LABEL}.${suffix}.example.net`, `https://c-.${suffix}`, ]) { expect(normalizeDesktopCompanionEndpoint(near), near).toBe(""); } } for (const unlisted of ["example.net", "openmausbot.net", "mausbot.com", "si", "com", ""]) { expect(normalizeDesktopCompanionEndpoint(`https://${LABEL}.${unlisted}`), unlisted).toBe(""); } }); it("validates, adds, and removes the encrypted credential document field", () => { const hostedAccess = { ...access, endpoint: `https://${LABEL}.openmausbot.com` }; expect(desktopCompanionAccess({ [DESKTOP_COMPANION_FIELD]: hostedAccess })).toEqual( hostedAccess, ); const secondDomain = { ...access, endpoint: `https://${LABEL}.mausbot.si` }; expect(desktopCompanionAccess({ [DESKTOP_COMPANION_FIELD]: secondDomain })).toEqual( secondDomain, ); expect( desktopCompanionAccess({ [DESKTOP_COMPANION_FIELD]: { ...access, endpoint: "https://app.mausbot.si" } }), ).toBeNull(); expect(desktopCompanionAccess({ [DESKTOP_COMPANION_FIELD]: access })).toEqual(access); expect(desktopCompanionAccess({ [DESKTOP_COMPANION_FIELD]: { ...access, token: "bad" } })).toBeNull(); expect(withDesktopCompanionAccess({ existing: true }, access)).toEqual({ existing: true, [DESKTOP_COMPANION_FIELD]: access, }); expect(withoutDesktopCompanionAccess({ existing: true, [DESKTOP_COMPANION_FIELD]: access })).toEqual({ existing: true, }); }); }); describe("desktop companion pairing", () => { it("sends the six-digit pairing request and accepts a valid device credential", async () => { const fetchImpl = vi.fn(async () => new Response(JSON.stringify({ token, device: { id: deviceId }, serverName: "Office computer" }), { status: 200, headers: { "content-type": "application/json" }, }), ); await expect( pairDesktopCompanion({ endpoint: "host.example-tailnet.ts.net", code: "123456", deviceName: "Laptop", requestId: "request-00000001", fetchImpl, }), ).resolves.toEqual(access); expect(fetchImpl).toHaveBeenCalledOnce(); const [url, options] = fetchImpl.mock.calls[0]; expect(url).toBe("http://host.example-tailnet.ts.net:8810/api/pair"); expect(options.headers).toEqual({ "content-type": "application/json" }); expect(JSON.parse(options.body)).toEqual({ code: "123456", deviceName: "Laptop", pairRequestId: "request-00000001", }); }); it("pairs through a managed HTTPS companion address", async () => { const fetchImpl = vi.fn(async () => new Response(JSON.stringify({ token, device: { id: deviceId }, serverName: "Office computer" }), { status: 201, headers: { "content-type": "application/json" }, }), ); const paired = await pairDesktopCompanion({ endpoint: `https://${LABEL}.openmausbot.com`, code: "654321", deviceName: "Desktop client", requestId: "request-https-01", fetchImpl, }); expect(paired).toEqual({ ...access, endpoint: `https://${LABEL}.openmausbot.com` }); expect(fetchImpl).toHaveBeenCalledWith( `https://${LABEL}.openmausbot.com/api/pair`, expect.objectContaining({ method: "POST" }), ); const secondDomain = await pairDesktopCompanion({ endpoint: `${LABEL}.mausbot.si`, code: "654321", deviceName: "Desktop client", requestId: "request-https-02", fetchImpl, }); expect(secondDomain).toEqual({ ...access, endpoint: `https://${LABEL}.mausbot.si` }); expect(fetchImpl).toHaveBeenLastCalledWith( `https://${LABEL}.mausbot.si/api/pair`, expect.objectContaining({ method: "POST" }), ); await expect( pairDesktopCompanion({ endpoint: "https://app.mausbot.si", code: "654321", deviceName: "Desktop client", requestId: "request-https-03", fetchImpl, }), ).rejects.toThrow("OpenMausBot HTTPS companion address"); expect(fetchImpl).toHaveBeenCalledTimes(2); }); it("rejects bad codes and surfaces a sidecar error without returning secrets", async () => { await expect( pairDesktopCompanion({ endpoint: access.endpoint, code: "123", deviceName: "Laptop" }), ).rejects.toThrow("six-digit"); await expect( pairDesktopCompanion({ endpoint: access.endpoint, code: "123456", deviceName: "Laptop", fetchImpl: async () => new Response(JSON.stringify({ error: "Pairing window closed" }), { status: 403 }), }), ).rejects.toThrow("Pairing window closed"); }); }); describe("desktop companion loopback relay", () => { it("pins absolute-form and network-path requests to the paired origin", () => { expect(desktopCompanionProxyTarget(access.endpoint, "http://evil.test/api/bots?x=1").href).toBe( `${access.endpoint}/api/bots?x=1`, ); expect(desktopCompanionProxyTarget(access.endpoint, "//evil.test/api/events").href).toBe( `${access.endpoint}/api/events`, ); }); it("serves the UI only on loopback and never includes the bearer in the page", async () => { const directory = fs.mkdtempSync(path.join(os.tmpdir(), "omb-desktop-client-")); tempDirs.push(directory); fs.writeFileSync(path.join(directory, "index.html"), "