fix(gateway): honor local auth password for CLI (PR #301, thanks @jeffersonwarrior)

This commit is contained in:
Peter Steinberger
2026-01-08 02:57:52 +01:00
parent cda050d050
commit 440a5b82cf
3 changed files with 96 additions and 3 deletions

View File

@@ -6,6 +6,8 @@ const pickPrimaryTailnetIPv4 = vi.fn();
let lastClientOptions: {
url?: string;
token?: string;
password?: string;
onHelloOk?: () => void | Promise<void>;
onClose?: (code: number, reason: string) => void;
} | null = null;
@@ -36,6 +38,8 @@ vi.mock("./client.js", () => ({
GatewayClient: class {
constructor(opts: {
url?: string;
token?: string;
password?: string;
onHelloOk?: () => void | Promise<void>;
onClose?: (code: number, reason: string) => void;
}) {
@@ -162,3 +166,86 @@ describe("callGateway error details", () => {
expect(err?.message).toContain("Bind: loopback");
});
});
describe("callGateway password resolution", () => {
const originalEnvPassword = process.env.CLAWDBOT_GATEWAY_PASSWORD;
beforeEach(() => {
loadConfig.mockReset();
resolveGatewayPort.mockReset();
pickPrimaryTailnetIPv4.mockReset();
lastClientOptions = null;
startMode = "hello";
closeCode = 1006;
closeReason = "";
delete process.env.CLAWDBOT_GATEWAY_PASSWORD;
resolveGatewayPort.mockReturnValue(18789);
pickPrimaryTailnetIPv4.mockReturnValue(undefined);
});
afterEach(() => {
if (originalEnvPassword == null) {
delete process.env.CLAWDBOT_GATEWAY_PASSWORD;
} else {
process.env.CLAWDBOT_GATEWAY_PASSWORD = originalEnvPassword;
}
});
it("uses local config password when env is unset", async () => {
loadConfig.mockReturnValue({
gateway: {
mode: "local",
bind: "loopback",
auth: { password: "secret" },
},
});
await callGateway({ method: "health" });
expect(lastClientOptions?.password).toBe("secret");
});
it("prefers env password over local config password", async () => {
process.env.CLAWDBOT_GATEWAY_PASSWORD = "from-env";
loadConfig.mockReturnValue({
gateway: {
mode: "local",
bind: "loopback",
auth: { password: "from-config" },
},
});
await callGateway({ method: "health" });
expect(lastClientOptions?.password).toBe("from-env");
});
it("uses remote password in remote mode when env is unset", async () => {
loadConfig.mockReturnValue({
gateway: {
mode: "remote",
remote: { url: "ws://remote.example:18789", password: "remote-secret" },
auth: { password: "from-config" },
},
});
await callGateway({ method: "health" });
expect(lastClientOptions?.password).toBe("remote-secret");
});
it("prefers env password over remote password in remote mode", async () => {
process.env.CLAWDBOT_GATEWAY_PASSWORD = "from-env";
loadConfig.mockReturnValue({
gateway: {
mode: "remote",
remote: { url: "ws://remote.example:18789", password: "remote-secret" },
auth: { password: "from-config" },
},
});
await callGateway({ method: "health" });
expect(lastClientOptions?.password).toBe("from-env");
});
});

View File

@@ -29,6 +29,7 @@ export async function callGateway<T = unknown>(
const isRemoteMode = config.gateway?.mode === "remote";
const remote = isRemoteMode ? config.gateway?.remote : undefined;
const authToken = config.gateway?.auth?.token;
const authPassword = config.gateway?.auth?.password;
const localPort = resolveGatewayPort(config);
const tailnetIPv4 = pickPrimaryTailnetIPv4();
const bindMode = config.gateway?.bind ?? "loopback";
@@ -64,9 +65,13 @@ export async function callGateway<T = unknown>(
? opts.password.trim()
: undefined) ||
process.env.CLAWDBOT_GATEWAY_PASSWORD?.trim() ||
(typeof remote?.password === "string" && remote.password.trim().length > 0
? remote.password.trim()
: undefined);
(isRemoteMode
? typeof remote?.password === "string" && remote.password.trim().length > 0
? remote.password.trim()
: undefined
: typeof authPassword === "string" && authPassword.trim().length > 0
? authPassword.trim()
: undefined);
const urlSource = urlOverride
? "cli --url"
: remoteUrl