fix(gateway): honor local auth password for CLI (PR #301, thanks @jeffersonwarrior)
This commit is contained in:
@@ -21,6 +21,7 @@
|
|||||||
- macOS: harden Voice Wake tester/runtime (pause trigger, mic persistence, local-only tester) and keep transcript logs private. Thanks @xadenryan for PR #438.
|
- macOS: harden Voice Wake tester/runtime (pause trigger, mic persistence, local-only tester) and keep transcript logs private. Thanks @xadenryan for PR #438.
|
||||||
- Doctor/Daemon: surface gateway runtime state + port collision diagnostics; warn on legacy workspace dirs.
|
- Doctor/Daemon: surface gateway runtime state + port collision diagnostics; warn on legacy workspace dirs.
|
||||||
- Gateway/CLI: include gateway target/source details in close/timeout errors and verbose health/status output.
|
- Gateway/CLI: include gateway target/source details in close/timeout errors and verbose health/status output.
|
||||||
|
- Gateway/CLI: honor `gateway.auth.password` for local CLI calls when env is unset. Thanks @jeffersonwarrior for PR #301.
|
||||||
- Discord: format slow listener logs in seconds to match shared duration style.
|
- Discord: format slow listener logs in seconds to match shared duration style.
|
||||||
- CLI: show colored table output for `clawdbot cron list` (JSON behind `--json`).
|
- CLI: show colored table output for `clawdbot cron list` (JSON behind `--json`).
|
||||||
- CLI: add cron `create`/`remove`/`delete` aliases for job management.
|
- CLI: add cron `create`/`remove`/`delete` aliases for job management.
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ const pickPrimaryTailnetIPv4 = vi.fn();
|
|||||||
|
|
||||||
let lastClientOptions: {
|
let lastClientOptions: {
|
||||||
url?: string;
|
url?: string;
|
||||||
|
token?: string;
|
||||||
|
password?: string;
|
||||||
onHelloOk?: () => void | Promise<void>;
|
onHelloOk?: () => void | Promise<void>;
|
||||||
onClose?: (code: number, reason: string) => void;
|
onClose?: (code: number, reason: string) => void;
|
||||||
} | null = null;
|
} | null = null;
|
||||||
@@ -36,6 +38,8 @@ vi.mock("./client.js", () => ({
|
|||||||
GatewayClient: class {
|
GatewayClient: class {
|
||||||
constructor(opts: {
|
constructor(opts: {
|
||||||
url?: string;
|
url?: string;
|
||||||
|
token?: string;
|
||||||
|
password?: string;
|
||||||
onHelloOk?: () => void | Promise<void>;
|
onHelloOk?: () => void | Promise<void>;
|
||||||
onClose?: (code: number, reason: string) => void;
|
onClose?: (code: number, reason: string) => void;
|
||||||
}) {
|
}) {
|
||||||
@@ -162,3 +166,86 @@ describe("callGateway error details", () => {
|
|||||||
expect(err?.message).toContain("Bind: loopback");
|
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");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ export async function callGateway<T = unknown>(
|
|||||||
const isRemoteMode = config.gateway?.mode === "remote";
|
const isRemoteMode = config.gateway?.mode === "remote";
|
||||||
const remote = isRemoteMode ? config.gateway?.remote : undefined;
|
const remote = isRemoteMode ? config.gateway?.remote : undefined;
|
||||||
const authToken = config.gateway?.auth?.token;
|
const authToken = config.gateway?.auth?.token;
|
||||||
|
const authPassword = config.gateway?.auth?.password;
|
||||||
const localPort = resolveGatewayPort(config);
|
const localPort = resolveGatewayPort(config);
|
||||||
const tailnetIPv4 = pickPrimaryTailnetIPv4();
|
const tailnetIPv4 = pickPrimaryTailnetIPv4();
|
||||||
const bindMode = config.gateway?.bind ?? "loopback";
|
const bindMode = config.gateway?.bind ?? "loopback";
|
||||||
@@ -64,9 +65,13 @@ export async function callGateway<T = unknown>(
|
|||||||
? opts.password.trim()
|
? opts.password.trim()
|
||||||
: undefined) ||
|
: undefined) ||
|
||||||
process.env.CLAWDBOT_GATEWAY_PASSWORD?.trim() ||
|
process.env.CLAWDBOT_GATEWAY_PASSWORD?.trim() ||
|
||||||
(typeof remote?.password === "string" && remote.password.trim().length > 0
|
(isRemoteMode
|
||||||
? remote.password.trim()
|
? typeof remote?.password === "string" && remote.password.trim().length > 0
|
||||||
: undefined);
|
? remote.password.trim()
|
||||||
|
: undefined
|
||||||
|
: typeof authPassword === "string" && authPassword.trim().length > 0
|
||||||
|
? authPassword.trim()
|
||||||
|
: undefined);
|
||||||
const urlSource = urlOverride
|
const urlSource = urlOverride
|
||||||
? "cli --url"
|
? "cli --url"
|
||||||
: remoteUrl
|
: remoteUrl
|
||||||
|
|||||||
Reference in New Issue
Block a user