diff --git a/CHANGELOG.md b/CHANGELOG.md index 765889304..a1ce2c7bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ - Sandbox: add `agent.sandbox.workspaceAccess` (`none`/`ro`/`rw`) to control agent workspace visibility inside the container; `ro` hard-disables `write`/`edit`. - Routing: allow per-agent sandbox overrides (including `workspaceAccess` and `sandbox.tools`) plus per-agent tool policies in multi-agent configs. Thanks @pasogott for PR #380. - Cron: clamp timer delay to avoid TimeoutOverflowWarning. Thanks @emanuelst for PR #412. +- Web UI: allow reconnect + password URL auth for the control UI and always scrub auth params from the URL. Thanks @oswalpalash for PR #414. - ClawdbotKit: fix SwiftPM resource bundling path for `tool-display.json`. Thanks @fcatuhe for PR #398. - Tools: add Telegram/WhatsApp reaction tools (with per-provider gating). Thanks @zats for PR #353. - Tools: flatten literal-union schemas for Claude on Vertex AI. Thanks @carlulsoe for PR #409. diff --git a/ui/src/ui/app.ts b/ui/src/ui/app.ts index 18e109fc7..5a6aa3435 100644 --- a/ui/src/ui/app.ts +++ b/ui/src/ui/app.ts @@ -663,20 +663,29 @@ export class ClawdbotApp extends LitElement { private applySettingsFromUrl() { if (!window.location.search) return; const params = new URLSearchParams(window.location.search); - const token = params.get("token")?.trim(); - const password = params.get("password")?.trim(); + const tokenRaw = params.get("token"); + const passwordRaw = params.get("password"); let changed = false; - if (token && !this.settings.token) { - this.applySettings({ ...this.settings, token }); + + if (tokenRaw != null) { + const token = tokenRaw.trim(); + if (token && !this.settings.token) { + this.applySettings({ ...this.settings, token }); + changed = true; + } params.delete("token"); - changed = true; } - if (password) { - this.password = password; + + if (passwordRaw != null) { + const password = passwordRaw.trim(); + if (password) { + this.password = password; + changed = true; + } params.delete("password"); - changed = true; } - if (!changed) return; + + if (!changed && tokenRaw == null && passwordRaw == null) return; const url = new URL(window.location.href); url.search = params.toString(); window.history.replaceState({}, "", url.toString()); diff --git a/ui/src/ui/navigation.browser.test.ts b/ui/src/ui/navigation.browser.test.ts index 6c3b68b0c..69d9af71e 100644 --- a/ui/src/ui/navigation.browser.test.ts +++ b/ui/src/ui/navigation.browser.test.ts @@ -128,4 +128,26 @@ describe("control UI routing", () => { expect(window.location.pathname).toBe("/ui/overview"); expect(window.location.search).toBe(""); }); + + it("hydrates password from URL params and strips it", async () => { + const app = mountApp("/ui/overview?password=sekret"); + await app.updateComplete; + + expect(app.password).toBe("sekret"); + expect(window.location.pathname).toBe("/ui/overview"); + expect(window.location.search).toBe(""); + }); + + it("strips auth params even when settings already set", async () => { + localStorage.setItem( + "clawdbot.control.settings.v1", + JSON.stringify({ token: "existing-token" }), + ); + const app = mountApp("/ui/overview?token=abc123"); + await app.updateComplete; + + expect(app.settings.token).toBe("existing-token"); + expect(window.location.pathname).toBe("/ui/overview"); + expect(window.location.search).toBe(""); + }); });