mirror of
https://github.com/openclaw/openclaw.git
synced 2026-02-19 18:39:20 -05:00
fix(doctor,configure): skip gateway auth for loopback-only setups
This commit is contained in:
@@ -82,30 +82,33 @@ async function runTrustedProxyPrompt(params: {
|
||||
tailscaleMode?: "off" | "serve";
|
||||
}) {
|
||||
return runGatewayPrompt({
|
||||
selectQueue: ["loopback", "trusted-proxy", params.tailscaleMode ?? "off"],
|
||||
selectQueue: ["lan", params.tailscaleMode ?? "off", "trusted-proxy"],
|
||||
textQueue: params.textQueue,
|
||||
authConfigFactory: ({ mode, trustedProxy }) => ({ mode, trustedProxy }),
|
||||
});
|
||||
}
|
||||
|
||||
describe("promptGatewayConfig", () => {
|
||||
it("generates a token when the prompt returns undefined", async () => {
|
||||
it("skips gateway auth setup for loopback-only gateways", async () => {
|
||||
const { result } = await runGatewayPrompt({
|
||||
selectQueue: ["loopback", "token", "off"],
|
||||
textQueue: ["18789", undefined],
|
||||
selectQueue: ["loopback", "off"],
|
||||
textQueue: ["18789"],
|
||||
randomToken: "generated-token",
|
||||
authConfigFactory: ({ mode, token, password }) => ({ mode, token, password }),
|
||||
});
|
||||
expect(result.token).toBe("generated-token");
|
||||
expect(result.token).toBeUndefined();
|
||||
expect(result.config.gateway?.auth).toBeUndefined();
|
||||
expect(mocks.buildGatewayAuthConfig).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not set password to literal 'undefined' when prompt returns undefined", async () => {
|
||||
it("configures password auth when gateway is exposed", async () => {
|
||||
const { call } = await runGatewayPrompt({
|
||||
selectQueue: ["loopback", "password", "off"],
|
||||
selectQueue: ["lan", "off", "password"],
|
||||
textQueue: ["18789", undefined],
|
||||
randomToken: "unused",
|
||||
authConfigFactory: ({ mode, token, password }) => ({ mode, token, password }),
|
||||
});
|
||||
expect(call?.mode).toBe("password");
|
||||
expect(call?.password).not.toBe("undefined");
|
||||
expect(call?.password).toBe("");
|
||||
});
|
||||
|
||||
@@ -85,22 +85,7 @@ export async function promptGatewayConfig(
|
||||
customBindHost = typeof input === "string" ? input : undefined;
|
||||
}
|
||||
|
||||
let authMode = guardCancel(
|
||||
await select({
|
||||
message: "Gateway auth",
|
||||
options: [
|
||||
{ value: "token", label: "Token", hint: "Recommended default" },
|
||||
{ value: "password", label: "Password" },
|
||||
{
|
||||
value: "trusted-proxy",
|
||||
label: "Trusted Proxy",
|
||||
hint: "Behind reverse proxy (Pomerium, Caddy, Traefik, etc.)",
|
||||
},
|
||||
],
|
||||
initialValue: "token",
|
||||
}),
|
||||
runtime,
|
||||
) as GatewayAuthChoice;
|
||||
let authMode: GatewayAuthChoice = "token";
|
||||
|
||||
let tailscaleMode = guardCancel(
|
||||
await select({
|
||||
@@ -137,22 +122,44 @@ export async function promptGatewayConfig(
|
||||
bind = "loopback";
|
||||
}
|
||||
|
||||
if (tailscaleMode === "funnel" && authMode !== "password") {
|
||||
note("Tailscale funnel requires password auth.", "Note");
|
||||
authMode = "password";
|
||||
}
|
||||
const loopbackOnlyGateway = bind === "loopback" && tailscaleMode === "off";
|
||||
if (loopbackOnlyGateway) {
|
||||
note("Loopback-only gateway does not require gateway.auth. Keeping auth disabled.", "Note");
|
||||
} else {
|
||||
authMode = guardCancel(
|
||||
await select({
|
||||
message: "Gateway auth",
|
||||
options: [
|
||||
{ value: "token", label: "Token", hint: "Recommended default" },
|
||||
{ value: "password", label: "Password" },
|
||||
{
|
||||
value: "trusted-proxy",
|
||||
label: "Trusted Proxy",
|
||||
hint: "Behind reverse proxy (Pomerium, Caddy, Traefik, etc.)",
|
||||
},
|
||||
],
|
||||
initialValue: tailscaleMode === "funnel" ? "password" : "token",
|
||||
}),
|
||||
runtime,
|
||||
) as GatewayAuthChoice;
|
||||
|
||||
if (authMode === "trusted-proxy" && bind === "loopback") {
|
||||
note("Trusted proxy auth requires network bind. Adjusting bind to lan.", "Note");
|
||||
bind = "lan";
|
||||
}
|
||||
if (authMode === "trusted-proxy" && tailscaleMode !== "off") {
|
||||
note(
|
||||
"Trusted proxy auth is incompatible with Tailscale serve/funnel. Disabling Tailscale.",
|
||||
"Note",
|
||||
);
|
||||
tailscaleMode = "off";
|
||||
tailscaleResetOnExit = false;
|
||||
if (tailscaleMode === "funnel" && authMode !== "password") {
|
||||
note("Tailscale funnel requires password auth.", "Note");
|
||||
authMode = "password";
|
||||
}
|
||||
|
||||
if (authMode === "trusted-proxy" && bind === "loopback") {
|
||||
note("Trusted proxy auth requires network bind. Adjusting bind to lan.", "Note");
|
||||
bind = "lan";
|
||||
}
|
||||
if (authMode === "trusted-proxy" && tailscaleMode !== "off") {
|
||||
note(
|
||||
"Trusted proxy auth is incompatible with Tailscale serve/funnel. Disabling Tailscale.",
|
||||
"Note",
|
||||
);
|
||||
tailscaleMode = "off";
|
||||
tailscaleResetOnExit = false;
|
||||
}
|
||||
}
|
||||
|
||||
let gatewayToken: string | undefined;
|
||||
@@ -163,7 +170,7 @@ export async function promptGatewayConfig(
|
||||
let trustedProxies: string[] | undefined;
|
||||
let next = cfg;
|
||||
|
||||
if (authMode === "token") {
|
||||
if (!loopbackOnlyGateway && authMode === "token") {
|
||||
const tokenInput = guardCancel(
|
||||
await text({
|
||||
message: "Gateway token (blank to generate)",
|
||||
@@ -174,7 +181,7 @@ export async function promptGatewayConfig(
|
||||
gatewayToken = normalizeGatewayTokenInput(tokenInput) || randomToken();
|
||||
}
|
||||
|
||||
if (authMode === "password") {
|
||||
if (!loopbackOnlyGateway && authMode === "password") {
|
||||
const password = guardCancel(
|
||||
await text({
|
||||
message: "Gateway password",
|
||||
@@ -185,7 +192,7 @@ export async function promptGatewayConfig(
|
||||
gatewayPassword = String(password ?? "").trim();
|
||||
}
|
||||
|
||||
if (authMode === "trusted-proxy") {
|
||||
if (!loopbackOnlyGateway && authMode === "trusted-proxy") {
|
||||
note(
|
||||
[
|
||||
"Trusted proxy mode: OpenClaw trusts user identity from a reverse proxy.",
|
||||
@@ -261,22 +268,26 @@ export async function promptGatewayConfig(
|
||||
};
|
||||
}
|
||||
|
||||
const authConfig = buildGatewayAuthConfig({
|
||||
existing: next.gateway?.auth,
|
||||
mode: authMode,
|
||||
token: gatewayToken,
|
||||
password: gatewayPassword,
|
||||
trustedProxy: trustedProxyConfig,
|
||||
});
|
||||
const authConfig = loopbackOnlyGateway
|
||||
? undefined
|
||||
: buildGatewayAuthConfig({
|
||||
existing: next.gateway?.auth,
|
||||
mode: authMode,
|
||||
token: gatewayToken,
|
||||
password: gatewayPassword,
|
||||
trustedProxy: trustedProxyConfig,
|
||||
});
|
||||
|
||||
const gatewayWithoutAuth = { ...next.gateway };
|
||||
delete gatewayWithoutAuth.auth;
|
||||
next = {
|
||||
...next,
|
||||
gateway: {
|
||||
...next.gateway,
|
||||
...gatewayWithoutAuth,
|
||||
mode: "local",
|
||||
port,
|
||||
bind,
|
||||
auth: authConfig,
|
||||
...(authConfig ? { auth: authConfig } : {}),
|
||||
...(customBindHost && { customBindHost }),
|
||||
...(trustedProxies && { trustedProxies }),
|
||||
tailscale: {
|
||||
|
||||
@@ -123,14 +123,18 @@ export async function doctorCommand(
|
||||
note(gatewayDetails.remoteFallbackNote, "Gateway");
|
||||
}
|
||||
if (resolveMode(cfg) === "local") {
|
||||
const gatewayBind = cfg.gateway?.bind ?? "loopback";
|
||||
const tailscaleMode = cfg.gateway?.tailscale?.mode ?? "off";
|
||||
const requireGatewayAuth = gatewayBind !== "loopback" || tailscaleMode !== "off";
|
||||
const auth = resolveGatewayAuth({
|
||||
authConfig: cfg.gateway?.auth,
|
||||
tailscaleMode: cfg.gateway?.tailscale?.mode ?? "off",
|
||||
tailscaleMode,
|
||||
});
|
||||
const needsToken = auth.mode !== "password" && (auth.mode !== "token" || !auth.token);
|
||||
const needsToken =
|
||||
requireGatewayAuth && auth.mode !== "password" && (auth.mode !== "token" || !auth.token);
|
||||
if (needsToken) {
|
||||
note(
|
||||
"Gateway auth is off or missing a token. Token auth is now the recommended default (including loopback).",
|
||||
"Gateway auth is off or missing a token. Token auth is recommended when the gateway is exposed beyond local loopback.",
|
||||
"Gateway auth",
|
||||
);
|
||||
const shouldSetToken =
|
||||
|
||||
Reference in New Issue
Block a user