mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 13:40:44 +00:00
fix(dashboard): guide manual token auth fallback
Summary:
- Add a redaction-safe dashboard fallback hint when tokenized URL delivery fails.
- Document the manual auth path and update the changelog.
Verification:
- PR CI exact head 48ccb97c08 green for relevant CI/security checks.
- pnpm test src/commands/dashboard.links.test.ts src/commands/dashboard.test.ts
- pnpm exec oxfmt --check --threads=1 src/commands/dashboard.ts src/commands/dashboard.links.test.ts
- pnpm format:docs:check
- pnpm docs:check-mdx
- pnpm docs:check-i18n-glossary
- targeted markdownlint for docs/cli/dashboard.md and docs/web/dashboard.md
This commit is contained in:
@@ -166,6 +166,27 @@ describe("dashboardCommand", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("guides user to manual auth when delivery channels both fail (CVE-safe)", async () => {
|
||||
const secretToken = "super-secret-bearer-token";
|
||||
mockSnapshot(secretToken);
|
||||
copyToClipboardMock.mockResolvedValue(false);
|
||||
detectBrowserOpenSupportMock.mockResolvedValue({ ok: false, reason: "ssh" });
|
||||
formatControlUiSshHintMock.mockReturnValue("ssh hint without token");
|
||||
|
||||
await dashboardCommand(runtime);
|
||||
|
||||
const allLogs = runtime.log.mock.calls.map((call) => String(call[0])).join("\n");
|
||||
|
||||
// CVE: token value and fragment marker must not appear in logs.
|
||||
expect(allLogs).not.toContain(secretToken);
|
||||
expect(allLogs).not.toContain("#token=");
|
||||
|
||||
// UX: user must be pointed to where their token lives so they can self-recover.
|
||||
expect(allLogs).toMatch(/OPENCLAW_GATEWAY_TOKEN/);
|
||||
// UX: hint must name the URL fragment key so the user knows the syntax.
|
||||
expect(allLogs).toContain("key `token`");
|
||||
});
|
||||
|
||||
it("respects --no-open and tells user token URL is in clipboard", async () => {
|
||||
mockSnapshot("abc");
|
||||
copyToClipboardMock.mockResolvedValue(true);
|
||||
@@ -179,12 +200,25 @@ describe("dashboardCommand", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("respects --no-open with plain URL hint when clipboard fails", async () => {
|
||||
it("respects --no-open and falls through to manual-auth hint when clipboard fails (token configured)", async () => {
|
||||
mockSnapshot("abc");
|
||||
copyToClipboardMock.mockResolvedValue(false);
|
||||
|
||||
await dashboardCommand(runtime, { noOpen: true });
|
||||
|
||||
// Redundant fallback hint is suppressed when the manual-auth hint speaks.
|
||||
expect(runtime.log).not.toHaveBeenCalledWith(
|
||||
"Browser launch disabled (--no-open). Use the URL above.",
|
||||
);
|
||||
expect(runtime.log).toHaveBeenCalledWith(expect.stringContaining("OPENCLAW_GATEWAY_TOKEN"));
|
||||
});
|
||||
|
||||
it("respects --no-open with plain URL hint when clipboard fails and no token is configured", async () => {
|
||||
mockSnapshot("");
|
||||
copyToClipboardMock.mockResolvedValue(false);
|
||||
|
||||
await dashboardCommand(runtime, { noOpen: true });
|
||||
|
||||
expect(runtime.log).toHaveBeenCalledWith(
|
||||
"Browser launch disabled (--no-open). Use the URL above.",
|
||||
);
|
||||
|
||||
@@ -86,9 +86,18 @@ export async function dashboardCommand(
|
||||
: "Browser launch disabled (--no-open). Use the URL above.";
|
||||
}
|
||||
|
||||
const fallbackToManualAuth = !copied && !opened && includeTokenInUrl;
|
||||
const suppressNoOpenHint = options.noOpen === true && fallbackToManualAuth;
|
||||
|
||||
if (opened) {
|
||||
runtime.log("Opened in your browser. Keep that tab to control OpenClaw.");
|
||||
} else if (hint) {
|
||||
} else if (hint && !suppressNoOpenHint) {
|
||||
runtime.log(hint);
|
||||
}
|
||||
|
||||
if (fallbackToManualAuth) {
|
||||
runtime.log(
|
||||
"Token auto-auth not delivered. Append your gateway token (from OPENCLAW_GATEWAY_TOKEN or gateway.auth.token) as a URL fragment with key `token` to authenticate.",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user