fix(pairing): align mobile setup with secure endpoints

This commit is contained in:
Ayaan Zaidi
2026-04-03 12:55:11 +05:30
parent c6f95a0c37
commit acd5734aa9
18 changed files with 412 additions and 73 deletions

View File

@@ -201,7 +201,7 @@ const entries: SubCliEntry[] = [
},
{
name: "qr",
description: "Generate iOS pairing QR/setup code",
description: "Generate mobile pairing QR/setup code",
hasSubcommands: false,
register: async (program) => {
const mod = await import("../qr-cli.js");

View File

@@ -80,7 +80,7 @@ export const SUB_CLI_DESCRIPTORS = [
},
{
name: "qr",
description: "Generate iOS pairing QR/setup code",
description: "Generate mobile pairing QR/setup code",
hasSubcommands: false,
},
{

View File

@@ -87,7 +87,7 @@ function createLocalGatewayConfigWithAuth(auth: Record<string, unknown>) {
secrets: createDefaultSecretProvider(),
gateway: {
bind: "custom",
customBindHost: "gateway.local",
customBindHost: "127.0.0.1",
auth,
},
};
@@ -149,7 +149,7 @@ describe("registerQrCli", () => {
}
function expectLoggedLocalSetupCode() {
expectLoggedSetupCode("ws://gateway.local:18789");
expectLoggedSetupCode("ws://127.0.0.1:18789");
}
function mockTailscaleStatusLookup() {
@@ -178,7 +178,7 @@ describe("registerQrCli", () => {
loadConfig.mockReturnValue({
gateway: {
bind: "custom",
customBindHost: "gateway.local",
customBindHost: "127.0.0.1",
auth: { mode: "token", token: "tok" },
},
});
@@ -186,7 +186,7 @@ describe("registerQrCli", () => {
await runQr(["--setup-code-only"]);
const expected = encodePairingSetupCode({
url: "ws://gateway.local:18789",
url: "ws://127.0.0.1:18789",
bootstrapToken: "bootstrap-123",
});
expect(runtime.log).toHaveBeenCalledWith(expected);
@@ -198,7 +198,7 @@ describe("registerQrCli", () => {
loadConfig.mockReturnValue({
gateway: {
bind: "custom",
customBindHost: "gateway.local",
customBindHost: "127.0.0.1",
auth: { mode: "token", token: "tok" },
},
});
@@ -213,11 +213,27 @@ describe("registerQrCli", () => {
expect(output).toContain("openclaw devices approve <requestId>");
});
it("accepts --token override when config has no auth", async () => {
it("fails fast for insecure remote mobile pairing setup urls", async () => {
loadConfig.mockReturnValue({
gateway: {
bind: "custom",
customBindHost: "gateway.local",
auth: { mode: "token", token: "tok" },
},
});
await expectQrExit(["--setup-code-only"]);
const output = runtime.error.mock.calls.map((call) => readRuntimeCallText(call)).join("\n");
expect(output).toContain("Mobile pairing requires a secure remote gateway URL");
expect(output).toContain("gateway.tailscale.mode=serve");
});
it("accepts --token override when config has no auth", async () => {
loadConfig.mockReturnValue({
gateway: {
bind: "custom",
customBindHost: "127.0.0.1",
},
});

View File

@@ -98,7 +98,7 @@ function emitQrSecretResolveDiagnostics(diagnostics: string[], opts: QrCliOption
export function registerQrCli(program: Command) {
program
.command("qr")
.description("Generate an iOS pairing QR code and setup code")
.description("Generate a mobile pairing QR code and setup code")
.addHelpText(
"after",
() => `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/qr", "docs.openclaw.ai/cli/qr")}\n`,
@@ -236,7 +236,7 @@ export function registerQrCli(program: Command) {
const lines: string[] = [
theme.heading("Pairing QR"),
"Scan this with the OpenClaw iOS app (Onboarding -> Scan QR).",
"Scan this with the OpenClaw mobile app (Onboarding -> Scan QR).",
"",
];

View File

@@ -50,7 +50,7 @@ function createGatewayTokenRefFixture() {
},
gateway: {
bind: "custom",
customBindHost: "gateway.local",
customBindHost: "127.0.0.1",
port: 18789,
auth: {
mode: "token",
@@ -157,7 +157,7 @@ describe("cli integration: qr + dashboard token SecretRef", () => {
const setupCode = runtimeLogs.at(-1);
expect(setupCode).toBeTruthy();
const payload = decodeSetupCode(setupCode ?? "");
expect(payload.url).toBe("ws://gateway.local:18789");
expect(payload.url).toBe("ws://127.0.0.1:18789");
expect(payload.bootstrapToken).toBeTruthy();
expect(runtimeErrors).toEqual([]);