From 1b53cdce8064aa8b0f0947906a7c9f9d6182487a Mon Sep 17 00:00:00 2001 From: Dotta Date: Sat, 10 Oct 2026 15:05:31 -0500 Subject: [PATCH] fix(computers): refresh desktop credentials on explicit connect Co-Authored-By: Paperclip --- server/src/modules/computers/adapters/boat.test.ts | 14 ++++++++++---- server/src/modules/computers/adapters/boat.ts | 8 +++++++- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/server/src/modules/computers/adapters/boat.test.ts b/server/src/modules/computers/adapters/boat.test.ts index e1ec76f173d..725bf128b76 100644 --- a/server/src/modules/computers/adapters/boat.test.ts +++ b/server/src/modules/computers/adapters/boat.test.ts @@ -774,22 +774,28 @@ os.execve(command[0],command,env) expect(readFileSync(f.calls, "utf8")).toBe(expected.repeat(2)); }); - it("checks input on explicit Connect but reuses credentials without daemon work during presence", async () => { + it("requests a fresh stream on every Connect but reuses credentials during presence", async () => { const id = randomUUID(); const scoped = { ...record, id, providerId: `bx_${id}` }; const execute = vi.fn(async () => ({ exitCode: 0, stdout: "{}", stderr: "", timedOut: false, signal: null, pid: null, startedAt: "" })); sshFactory.mockReturnValue({ execute }); + let streamNumber = 0; const fetcher = vi.fn(async (url: string | URL | Request) => String(url).endsWith("/sshkey") ? json({ hostKey: "ssh-ed25519 AAAA", sshEndpoint: "fixture.invalid:2222" }) - : json({ desktopUrl: "https://fixture.on.boat.dev/" })); + : json({ desktopUrl: `https://fixture.on.boat.dev/stream.html#token=fixture-${++streamNumber}` })); const backend = boatBackend(async () => "fixture-key", fetcher); const viewer = await backend.desktop(scoped); expect(execute).toHaveBeenCalledOnce(); expect(await backend.desktop(scoped, { checkInput: false })).toEqual(viewer); expect(execute).toHaveBeenCalledOnce(); - expect(await backend.desktop(scoped)).toEqual(viewer); + const reconnected = await backend.desktop(scoped); + expect(reconnected.viewerUrl).not.toEqual(viewer.viewerUrl); + expect(new URL(reconnected.viewerUrl).hash).toBe("#token=fixture-2"); + expect(new URL(reconnected.viewerUrl).searchParams.get("overlay")).toBe("0"); expect(execute).toHaveBeenCalledTimes(2); - expect(fetcher.mock.calls.filter(([url]) => String(url).endsWith("/desktop"))).toHaveLength(1); + expect(await backend.desktop(scoped, { checkInput: false })).toEqual(reconnected); + expect(execute).toHaveBeenCalledTimes(2); + expect(fetcher.mock.calls.filter(([url]) => String(url).endsWith("/desktop"))).toHaveLength(2); sshFactory.mockReset(); }); it("rejects a symlink input directory without replacing the daemon", () => { diff --git a/server/src/modules/computers/adapters/boat.ts b/server/src/modules/computers/adapters/boat.ts index 9f35158b06d..1c2f077fd88 100644 --- a/server/src/modules/computers/adapters/boat.ts +++ b/server/src/modules/computers/adapters/boat.ts @@ -558,7 +558,13 @@ print('{}') if (options?.checkInput !== false) await execute(record, `${desktopReadinessProgram}\nprint('{}')`, {}); const cached = desktopCache.get(record.id); - if (cached && Date.parse(cached.expiresAt) > Date.now() + 60_000) + // Explicit Connect must obtain a new stream session, including after a + // failed connection. Only presence renewals reuse existing credentials. + if ( + options?.checkInput === false && + cached && + Date.parse(cached.expiresAt) > Date.now() + 60_000 + ) return cached; const value = await api(record, "POST", "/desktop", {}); const url = new URL(value.desktopUrl);