pairing: keep setup codes bootstrap-token only (#51259)
This commit is contained in:
parent
5a5e84ca1d
commit
11d71ca352
@ -135,24 +135,16 @@ describe("registerQrCli", () => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function expectLoggedSetupCode(
|
function expectLoggedSetupCode(url: string) {
|
||||||
url: string,
|
|
||||||
auth?: {
|
|
||||||
token?: string;
|
|
||||||
password?: string;
|
|
||||||
},
|
|
||||||
) {
|
|
||||||
const expected = encodePairingSetupCode({
|
const expected = encodePairingSetupCode({
|
||||||
url,
|
url,
|
||||||
bootstrapToken: "bootstrap-123",
|
bootstrapToken: "bootstrap-123",
|
||||||
...(auth?.token ? { token: auth.token } : {}),
|
|
||||||
...(auth?.password ? { password: auth.password } : {}),
|
|
||||||
});
|
});
|
||||||
expect(runtime.log).toHaveBeenCalledWith(expected);
|
expect(runtime.log).toHaveBeenCalledWith(expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
function expectLoggedLocalSetupCode(auth?: { token?: string; password?: string }) {
|
function expectLoggedLocalSetupCode() {
|
||||||
expectLoggedSetupCode("ws://gateway.local:18789", auth);
|
expectLoggedSetupCode("ws://gateway.local:18789");
|
||||||
}
|
}
|
||||||
|
|
||||||
function mockTailscaleStatusLookup() {
|
function mockTailscaleStatusLookup() {
|
||||||
@ -189,7 +181,6 @@ describe("registerQrCli", () => {
|
|||||||
const expected = encodePairingSetupCode({
|
const expected = encodePairingSetupCode({
|
||||||
url: "ws://gateway.local:18789",
|
url: "ws://gateway.local:18789",
|
||||||
bootstrapToken: "bootstrap-123",
|
bootstrapToken: "bootstrap-123",
|
||||||
token: "tok",
|
|
||||||
});
|
});
|
||||||
expect(runtime.log).toHaveBeenCalledWith(expected);
|
expect(runtime.log).toHaveBeenCalledWith(expected);
|
||||||
expect(qrGenerate).not.toHaveBeenCalled();
|
expect(qrGenerate).not.toHaveBeenCalled();
|
||||||
@ -225,7 +216,7 @@ describe("registerQrCli", () => {
|
|||||||
|
|
||||||
await runQr(["--setup-code-only", "--token", "override-token"]);
|
await runQr(["--setup-code-only", "--token", "override-token"]);
|
||||||
|
|
||||||
expectLoggedLocalSetupCode({ token: "override-token" });
|
expectLoggedLocalSetupCode();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("skips local password SecretRef resolution when --token override is provided", async () => {
|
it("skips local password SecretRef resolution when --token override is provided", async () => {
|
||||||
@ -237,7 +228,7 @@ describe("registerQrCli", () => {
|
|||||||
|
|
||||||
await runQr(["--setup-code-only", "--token", "override-token"]);
|
await runQr(["--setup-code-only", "--token", "override-token"]);
|
||||||
|
|
||||||
expectLoggedLocalSetupCode({ token: "override-token" });
|
expectLoggedLocalSetupCode();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("resolves local gateway auth password SecretRefs before setup code generation", async () => {
|
it("resolves local gateway auth password SecretRefs before setup code generation", async () => {
|
||||||
@ -250,7 +241,7 @@ describe("registerQrCli", () => {
|
|||||||
|
|
||||||
await runQr(["--setup-code-only"]);
|
await runQr(["--setup-code-only"]);
|
||||||
|
|
||||||
expectLoggedLocalSetupCode({ password: "local-password-secret" });
|
expectLoggedLocalSetupCode();
|
||||||
expect(resolveCommandSecretRefsViaGateway).not.toHaveBeenCalled();
|
expect(resolveCommandSecretRefsViaGateway).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -264,7 +255,7 @@ describe("registerQrCli", () => {
|
|||||||
|
|
||||||
await runQr(["--setup-code-only"]);
|
await runQr(["--setup-code-only"]);
|
||||||
|
|
||||||
expectLoggedLocalSetupCode({ password: "password-from-env" });
|
expectLoggedLocalSetupCode();
|
||||||
expect(resolveCommandSecretRefsViaGateway).not.toHaveBeenCalled();
|
expect(resolveCommandSecretRefsViaGateway).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -279,7 +270,7 @@ describe("registerQrCli", () => {
|
|||||||
|
|
||||||
await runQr(["--setup-code-only"]);
|
await runQr(["--setup-code-only"]);
|
||||||
|
|
||||||
expectLoggedLocalSetupCode({ token: "token-123" });
|
expectLoggedLocalSetupCode();
|
||||||
expect(resolveCommandSecretRefsViaGateway).not.toHaveBeenCalled();
|
expect(resolveCommandSecretRefsViaGateway).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -293,7 +284,7 @@ describe("registerQrCli", () => {
|
|||||||
|
|
||||||
await runQr(["--setup-code-only"]);
|
await runQr(["--setup-code-only"]);
|
||||||
|
|
||||||
expectLoggedLocalSetupCode({ password: "inferred-password" });
|
expectLoggedLocalSetupCode();
|
||||||
expect(resolveCommandSecretRefsViaGateway).not.toHaveBeenCalled();
|
expect(resolveCommandSecretRefsViaGateway).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -342,7 +333,6 @@ describe("registerQrCli", () => {
|
|||||||
const expected = encodePairingSetupCode({
|
const expected = encodePairingSetupCode({
|
||||||
url: "wss://remote.example.com:444",
|
url: "wss://remote.example.com:444",
|
||||||
bootstrapToken: "bootstrap-123",
|
bootstrapToken: "bootstrap-123",
|
||||||
token: "remote-tok",
|
|
||||||
});
|
});
|
||||||
expect(runtime.log).toHaveBeenCalledWith(expected);
|
expect(runtime.log).toHaveBeenCalledWith(expected);
|
||||||
expect(resolveCommandSecretRefsViaGateway).toHaveBeenCalledWith(
|
expect(resolveCommandSecretRefsViaGateway).toHaveBeenCalledWith(
|
||||||
@ -386,7 +376,6 @@ describe("registerQrCli", () => {
|
|||||||
const expected = encodePairingSetupCode({
|
const expected = encodePairingSetupCode({
|
||||||
url: "wss://remote.example.com:444",
|
url: "wss://remote.example.com:444",
|
||||||
bootstrapToken: "bootstrap-123",
|
bootstrapToken: "bootstrap-123",
|
||||||
token: "remote-tok",
|
|
||||||
});
|
});
|
||||||
expect(runtime.log).toHaveBeenCalledWith(expected);
|
expect(runtime.log).toHaveBeenCalledWith(expected);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -69,8 +69,6 @@ function createGatewayTokenRefFixture() {
|
|||||||
function decodeSetupCode(setupCode: string): {
|
function decodeSetupCode(setupCode: string): {
|
||||||
url?: string;
|
url?: string;
|
||||||
bootstrapToken?: string;
|
bootstrapToken?: string;
|
||||||
token?: string;
|
|
||||||
password?: string;
|
|
||||||
} {
|
} {
|
||||||
const padded = setupCode.replace(/-/g, "+").replace(/_/g, "/");
|
const padded = setupCode.replace(/-/g, "+").replace(/_/g, "/");
|
||||||
const padLength = (4 - (padded.length % 4)) % 4;
|
const padLength = (4 - (padded.length % 4)) % 4;
|
||||||
@ -79,8 +77,6 @@ function decodeSetupCode(setupCode: string): {
|
|||||||
return JSON.parse(json) as {
|
return JSON.parse(json) as {
|
||||||
url?: string;
|
url?: string;
|
||||||
bootstrapToken?: string;
|
bootstrapToken?: string;
|
||||||
token?: string;
|
|
||||||
password?: string;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -119,7 +115,7 @@ describe("cli integration: qr + dashboard token SecretRef", () => {
|
|||||||
delete process.env.SHARED_GATEWAY_TOKEN;
|
delete process.env.SHARED_GATEWAY_TOKEN;
|
||||||
});
|
});
|
||||||
|
|
||||||
it("uses the same resolved token SecretRef for both qr and dashboard commands", async () => {
|
it("uses the same resolved token SecretRef for qr auth validation and dashboard commands", async () => {
|
||||||
const fixture = createGatewayTokenRefFixture();
|
const fixture = createGatewayTokenRefFixture();
|
||||||
process.env.SHARED_GATEWAY_TOKEN = "shared-token-123";
|
process.env.SHARED_GATEWAY_TOKEN = "shared-token-123";
|
||||||
loadConfigMock.mockReturnValue(fixture);
|
loadConfigMock.mockReturnValue(fixture);
|
||||||
@ -137,7 +133,6 @@ describe("cli integration: qr + dashboard token SecretRef", () => {
|
|||||||
const payload = decodeSetupCode(setupCode ?? "");
|
const payload = decodeSetupCode(setupCode ?? "");
|
||||||
expect(payload.url).toBe("ws://gateway.local:18789");
|
expect(payload.url).toBe("ws://gateway.local:18789");
|
||||||
expect(payload.bootstrapToken).toBeTruthy();
|
expect(payload.bootstrapToken).toBeTruthy();
|
||||||
expect(payload.token).toBe("shared-token-123");
|
|
||||||
expect(runtimeErrors).toEqual([]);
|
expect(runtimeErrors).toEqual([]);
|
||||||
|
|
||||||
runtimeLogs.length = 0;
|
runtimeLogs.length = 0;
|
||||||
|
|||||||
@ -45,8 +45,6 @@ describe("pairing setup code", () => {
|
|||||||
authLabel: string;
|
authLabel: string;
|
||||||
url?: string;
|
url?: string;
|
||||||
urlSource?: string;
|
urlSource?: string;
|
||||||
token?: string;
|
|
||||||
password?: string;
|
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
expect(resolved.ok).toBe(true);
|
expect(resolved.ok).toBe(true);
|
||||||
@ -55,8 +53,6 @@ describe("pairing setup code", () => {
|
|||||||
}
|
}
|
||||||
expect(resolved.authLabel).toBe(params.authLabel);
|
expect(resolved.authLabel).toBe(params.authLabel);
|
||||||
expect(resolved.payload.bootstrapToken).toBe("bootstrap-123");
|
expect(resolved.payload.bootstrapToken).toBe("bootstrap-123");
|
||||||
expect(resolved.payload.token).toBe(params.token);
|
|
||||||
expect(resolved.payload.password).toBe(params.password);
|
|
||||||
if (params.url) {
|
if (params.url) {
|
||||||
expect(resolved.payload.url).toBe(params.url);
|
expect(resolved.payload.url).toBe(params.url);
|
||||||
}
|
}
|
||||||
@ -117,7 +113,6 @@ describe("pairing setup code", () => {
|
|||||||
payload: {
|
payload: {
|
||||||
url: "ws://gateway.local:19001",
|
url: "ws://gateway.local:19001",
|
||||||
bootstrapToken: "bootstrap-123",
|
bootstrapToken: "bootstrap-123",
|
||||||
token: "tok_123",
|
|
||||||
},
|
},
|
||||||
authLabel: "token",
|
authLabel: "token",
|
||||||
urlSource: "gateway.bind=custom",
|
urlSource: "gateway.bind=custom",
|
||||||
@ -144,7 +139,7 @@ describe("pairing setup code", () => {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
expectResolvedSetupOk(resolved, { authLabel: "password", password: "resolved-password" });
|
expectResolvedSetupOk(resolved, { authLabel: "password" });
|
||||||
});
|
});
|
||||||
|
|
||||||
it("uses OPENCLAW_GATEWAY_PASSWORD without resolving configured password SecretRef", async () => {
|
it("uses OPENCLAW_GATEWAY_PASSWORD without resolving configured password SecretRef", async () => {
|
||||||
@ -167,7 +162,7 @@ describe("pairing setup code", () => {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
expectResolvedSetupOk(resolved, { authLabel: "password", password: "password-from-env" });
|
expectResolvedSetupOk(resolved, { authLabel: "password" });
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does not resolve gateway.auth.password SecretRef in token mode", async () => {
|
it("does not resolve gateway.auth.password SecretRef in token mode", async () => {
|
||||||
@ -189,7 +184,7 @@ describe("pairing setup code", () => {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
expectResolvedSetupOk(resolved, { authLabel: "token", token: "tok_123" });
|
expectResolvedSetupOk(resolved, { authLabel: "token" });
|
||||||
});
|
});
|
||||||
|
|
||||||
it("resolves gateway.auth.token SecretRef for pairing payload", async () => {
|
it("resolves gateway.auth.token SecretRef for pairing payload", async () => {
|
||||||
@ -212,7 +207,7 @@ describe("pairing setup code", () => {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
expectResolvedSetupOk(resolved, { authLabel: "token", token: "resolved-token" });
|
expectResolvedSetupOk(resolved, { authLabel: "token" });
|
||||||
});
|
});
|
||||||
|
|
||||||
it("errors when gateway.auth.token SecretRef is unresolved in token mode", async () => {
|
it("errors when gateway.auth.token SecretRef is unresolved in token mode", async () => {
|
||||||
@ -261,13 +256,13 @@ describe("pairing setup code", () => {
|
|||||||
id: "MISSING_GW_TOKEN",
|
id: "MISSING_GW_TOKEN",
|
||||||
});
|
});
|
||||||
|
|
||||||
expectResolvedSetupOk(resolved, { authLabel: "password", password: "password-from-env" });
|
expectResolvedSetupOk(resolved, { authLabel: "password" });
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does not treat env-template token as plaintext in inferred mode", async () => {
|
it("does not treat env-template token as plaintext in inferred mode", async () => {
|
||||||
const resolved = await resolveInferredModeWithPasswordEnv("${MISSING_GW_TOKEN}");
|
const resolved = await resolveInferredModeWithPasswordEnv("${MISSING_GW_TOKEN}");
|
||||||
|
|
||||||
expectResolvedSetupOk(resolved, { authLabel: "password", password: "password-from-env" });
|
expectResolvedSetupOk(resolved, { authLabel: "password" });
|
||||||
});
|
});
|
||||||
|
|
||||||
it("requires explicit auth mode when token and password are both configured", async () => {
|
it("requires explicit auth mode when token and password are both configured", async () => {
|
||||||
@ -333,7 +328,7 @@ describe("pairing setup code", () => {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
expectResolvedSetupOk(resolved, { authLabel: "token", token: "new-token" });
|
expectResolvedSetupOk(resolved, { authLabel: "token" });
|
||||||
});
|
});
|
||||||
|
|
||||||
it("errors when gateway is loopback only", async () => {
|
it("errors when gateway is loopback only", async () => {
|
||||||
@ -367,7 +362,6 @@ describe("pairing setup code", () => {
|
|||||||
payload: {
|
payload: {
|
||||||
url: "wss://mb-server.tailnet.ts.net",
|
url: "wss://mb-server.tailnet.ts.net",
|
||||||
bootstrapToken: "bootstrap-123",
|
bootstrapToken: "bootstrap-123",
|
||||||
password: "secret",
|
|
||||||
},
|
},
|
||||||
authLabel: "password",
|
authLabel: "password",
|
||||||
urlSource: "gateway.tailscale.mode=serve",
|
urlSource: "gateway.tailscale.mode=serve",
|
||||||
@ -396,7 +390,6 @@ describe("pairing setup code", () => {
|
|||||||
payload: {
|
payload: {
|
||||||
url: "wss://remote.example.com:444",
|
url: "wss://remote.example.com:444",
|
||||||
bootstrapToken: "bootstrap-123",
|
bootstrapToken: "bootstrap-123",
|
||||||
token: "tok_123",
|
|
||||||
},
|
},
|
||||||
authLabel: "token",
|
authLabel: "token",
|
||||||
urlSource: "gateway.remote.url",
|
urlSource: "gateway.remote.url",
|
||||||
|
|||||||
@ -16,8 +16,6 @@ import { resolveTailnetHostWithRunner } from "../shared/tailscale-status.js";
|
|||||||
export type PairingSetupPayload = {
|
export type PairingSetupPayload = {
|
||||||
url: string;
|
url: string;
|
||||||
bootstrapToken: string;
|
bootstrapToken: string;
|
||||||
token?: string;
|
|
||||||
password?: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PairingSetupCommandResult = {
|
export type PairingSetupCommandResult = {
|
||||||
@ -64,11 +62,6 @@ type ResolveAuthLabelResult = {
|
|||||||
error?: string;
|
error?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ResolveSharedAuthResult = {
|
|
||||||
token?: string;
|
|
||||||
password?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
function normalizeUrl(raw: string, schemeFallback: "ws" | "wss"): string | null {
|
function normalizeUrl(raw: string, schemeFallback: "ws" | "wss"): string | null {
|
||||||
const trimmed = raw.trim();
|
const trimmed = raw.trim();
|
||||||
if (!trimmed) {
|
if (!trimmed) {
|
||||||
@ -213,41 +206,6 @@ function resolvePairingSetupAuthLabel(
|
|||||||
return { error: "Gateway auth is not configured (no token or password)." };
|
return { error: "Gateway auth is not configured (no token or password)." };
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolvePairingSetupSharedAuth(
|
|
||||||
cfg: OpenClawConfig,
|
|
||||||
env: NodeJS.ProcessEnv,
|
|
||||||
): ResolveSharedAuthResult {
|
|
||||||
const defaults = cfg.secrets?.defaults;
|
|
||||||
const tokenRef = resolveSecretInputRef({
|
|
||||||
value: cfg.gateway?.auth?.token,
|
|
||||||
defaults,
|
|
||||||
}).ref;
|
|
||||||
const passwordRef = resolveSecretInputRef({
|
|
||||||
value: cfg.gateway?.auth?.password,
|
|
||||||
defaults,
|
|
||||||
}).ref;
|
|
||||||
const token =
|
|
||||||
resolveGatewayTokenFromEnv(env) ||
|
|
||||||
(tokenRef ? undefined : normalizeSecretInputString(cfg.gateway?.auth?.token));
|
|
||||||
const password =
|
|
||||||
resolveGatewayPasswordFromEnv(env) ||
|
|
||||||
(passwordRef ? undefined : normalizeSecretInputString(cfg.gateway?.auth?.password));
|
|
||||||
const mode = cfg.gateway?.auth?.mode;
|
|
||||||
if (mode === "token") {
|
|
||||||
return { token };
|
|
||||||
}
|
|
||||||
if (mode === "password") {
|
|
||||||
return { password };
|
|
||||||
}
|
|
||||||
if (token) {
|
|
||||||
return { token };
|
|
||||||
}
|
|
||||||
if (password) {
|
|
||||||
return { password };
|
|
||||||
}
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
async function resolveGatewayTokenSecretRef(
|
async function resolveGatewayTokenSecretRef(
|
||||||
cfg: OpenClawConfig,
|
cfg: OpenClawConfig,
|
||||||
env: NodeJS.ProcessEnv,
|
env: NodeJS.ProcessEnv,
|
||||||
@ -417,8 +375,6 @@ export async function resolvePairingSetupFromConfig(
|
|||||||
if (authLabel.error) {
|
if (authLabel.error) {
|
||||||
return { ok: false, error: authLabel.error };
|
return { ok: false, error: authLabel.error };
|
||||||
}
|
}
|
||||||
const sharedAuth = resolvePairingSetupSharedAuth(cfgForAuth, env);
|
|
||||||
|
|
||||||
const urlResult = await resolveGatewayUrl(cfgForAuth, {
|
const urlResult = await resolveGatewayUrl(cfgForAuth, {
|
||||||
env,
|
env,
|
||||||
publicUrl: options.publicUrl,
|
publicUrl: options.publicUrl,
|
||||||
@ -445,8 +401,6 @@ export async function resolvePairingSetupFromConfig(
|
|||||||
baseDir: options.pairingBaseDir,
|
baseDir: options.pairingBaseDir,
|
||||||
})
|
})
|
||||||
).token,
|
).token,
|
||||||
...(sharedAuth.token ? { token: sharedAuth.token } : {}),
|
|
||||||
...(sharedAuth.password ? { password: sharedAuth.password } : {}),
|
|
||||||
},
|
},
|
||||||
authLabel: authLabel.label,
|
authLabel: authLabel.label,
|
||||||
urlSource: urlResult.source ?? "unknown",
|
urlSource: urlResult.source ?? "unknown",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user