mirror of https://github.com/openclaw/openclaw.git
refactor(gateway): use auth mode provenance for bootstrap persistence
This commit is contained in:
parent
5006b67829
commit
0275281e8d
|
|
@ -239,14 +239,17 @@ async function runGatewayCommand(opts: GatewayRunOpts) {
|
|||
}
|
||||
|
||||
const miskeys = extractGatewayMiskeys(snapshot?.parsed);
|
||||
const authConfig = {
|
||||
...cfg.gateway?.auth,
|
||||
...(authMode ? { mode: authMode } : {}),
|
||||
...(passwordRaw ? { password: passwordRaw } : {}),
|
||||
...(tokenRaw ? { token: tokenRaw } : {}),
|
||||
};
|
||||
const authOverride =
|
||||
authMode || passwordRaw || tokenRaw || authModeRaw
|
||||
? {
|
||||
...(authMode ? { mode: authMode } : {}),
|
||||
...(tokenRaw ? { token: tokenRaw } : {}),
|
||||
...(passwordRaw ? { password: passwordRaw } : {}),
|
||||
}
|
||||
: undefined;
|
||||
const resolvedAuth = resolveGatewayAuth({
|
||||
authConfig,
|
||||
authConfig: cfg.gateway?.auth,
|
||||
authOverride,
|
||||
env: process.env,
|
||||
tailscaleMode: tailscaleMode ?? cfg.gateway?.tailscale?.mode ?? "off",
|
||||
});
|
||||
|
|
@ -303,14 +306,6 @@ async function runGatewayCommand(opts: GatewayRunOpts) {
|
|||
defaultRuntime.exit(1);
|
||||
return;
|
||||
}
|
||||
const authOverride =
|
||||
authMode || passwordRaw || tokenRaw || authModeRaw
|
||||
? {
|
||||
...(authMode ? { mode: authMode } : {}),
|
||||
...(tokenRaw ? { token: tokenRaw } : {}),
|
||||
...(passwordRaw ? { password: passwordRaw } : {}),
|
||||
}
|
||||
: undefined;
|
||||
const tailscaleOverride =
|
||||
tailscaleMode || opts.tailscaleResetOnExit
|
||||
? {
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ describe("gateway auth", () => {
|
|||
}),
|
||||
).toMatchObject({
|
||||
mode: "password",
|
||||
modeSource: "password",
|
||||
token: "env-token",
|
||||
password: "env-password",
|
||||
});
|
||||
|
|
@ -50,6 +51,7 @@ describe("gateway auth", () => {
|
|||
}),
|
||||
).toMatchObject({
|
||||
mode: "token",
|
||||
modeSource: "default",
|
||||
token: undefined,
|
||||
password: undefined,
|
||||
});
|
||||
|
|
@ -63,11 +65,27 @@ describe("gateway auth", () => {
|
|||
}),
|
||||
).toMatchObject({
|
||||
mode: "none",
|
||||
modeSource: "config",
|
||||
token: undefined,
|
||||
password: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it("marks mode source as override when runtime mode override is provided", () => {
|
||||
expect(
|
||||
resolveGatewayAuth({
|
||||
authConfig: { mode: "password", password: "config-password" },
|
||||
authOverride: { mode: "token" },
|
||||
env: {} as NodeJS.ProcessEnv,
|
||||
}),
|
||||
).toMatchObject({
|
||||
mode: "token",
|
||||
modeSource: "override",
|
||||
token: undefined,
|
||||
password: "config-password",
|
||||
});
|
||||
});
|
||||
|
||||
it("does not throw when req is missing socket", async () => {
|
||||
const res = await authorizeGatewayConnect({
|
||||
auth: { mode: "token", token: "secret", allowTailscale: false },
|
||||
|
|
|
|||
|
|
@ -20,9 +20,16 @@ import {
|
|||
} from "./net.js";
|
||||
|
||||
export type ResolvedGatewayAuthMode = "none" | "token" | "password" | "trusted-proxy";
|
||||
export type ResolvedGatewayAuthModeSource =
|
||||
| "override"
|
||||
| "config"
|
||||
| "password"
|
||||
| "token"
|
||||
| "default";
|
||||
|
||||
export type ResolvedGatewayAuth = {
|
||||
mode: ResolvedGatewayAuthMode;
|
||||
modeSource?: ResolvedGatewayAuthModeSource;
|
||||
token?: string;
|
||||
password?: string;
|
||||
allowTailscale: boolean;
|
||||
|
|
@ -178,24 +185,55 @@ async function resolveVerifiedTailscaleUser(params: {
|
|||
|
||||
export function resolveGatewayAuth(params: {
|
||||
authConfig?: GatewayAuthConfig | null;
|
||||
authOverride?: GatewayAuthConfig | null;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
tailscaleMode?: GatewayTailscaleMode;
|
||||
}): ResolvedGatewayAuth {
|
||||
const authConfig = params.authConfig ?? {};
|
||||
const baseAuthConfig = params.authConfig ?? {};
|
||||
const authOverride = params.authOverride ?? undefined;
|
||||
const authConfig: GatewayAuthConfig = { ...baseAuthConfig };
|
||||
if (authOverride) {
|
||||
if (authOverride.mode !== undefined) {
|
||||
authConfig.mode = authOverride.mode;
|
||||
}
|
||||
if (authOverride.token !== undefined) {
|
||||
authConfig.token = authOverride.token;
|
||||
}
|
||||
if (authOverride.password !== undefined) {
|
||||
authConfig.password = authOverride.password;
|
||||
}
|
||||
if (authOverride.allowTailscale !== undefined) {
|
||||
authConfig.allowTailscale = authOverride.allowTailscale;
|
||||
}
|
||||
if (authOverride.rateLimit !== undefined) {
|
||||
authConfig.rateLimit = authOverride.rateLimit;
|
||||
}
|
||||
if (authOverride.trustedProxy !== undefined) {
|
||||
authConfig.trustedProxy = authOverride.trustedProxy;
|
||||
}
|
||||
}
|
||||
const env = params.env ?? process.env;
|
||||
const token = authConfig.token ?? env.OPENCLAW_GATEWAY_TOKEN ?? undefined;
|
||||
const password = authConfig.password ?? env.OPENCLAW_GATEWAY_PASSWORD ?? undefined;
|
||||
const trustedProxy = authConfig.trustedProxy;
|
||||
|
||||
let mode: ResolvedGatewayAuth["mode"];
|
||||
if (authConfig.mode) {
|
||||
let modeSource: ResolvedGatewayAuth["modeSource"];
|
||||
if (authOverride?.mode !== undefined) {
|
||||
mode = authOverride.mode;
|
||||
modeSource = "override";
|
||||
} else if (authConfig.mode) {
|
||||
mode = authConfig.mode;
|
||||
modeSource = "config";
|
||||
} else if (password) {
|
||||
mode = "password";
|
||||
modeSource = "password";
|
||||
} else if (token) {
|
||||
mode = "token";
|
||||
modeSource = "token";
|
||||
} else {
|
||||
mode = "token";
|
||||
modeSource = "default";
|
||||
}
|
||||
|
||||
const allowTailscale =
|
||||
|
|
@ -204,6 +242,7 @@ export function resolveGatewayAuth(params: {
|
|||
|
||||
return {
|
||||
mode,
|
||||
modeSource,
|
||||
token,
|
||||
password,
|
||||
allowTailscale,
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import {
|
|||
import { normalizeControlUiBasePath } from "./control-ui-shared.js";
|
||||
import { resolveHooksConfig } from "./hooks.js";
|
||||
import { isLoopbackHost, resolveGatewayBindHost } from "./net.js";
|
||||
import { mergeGatewayAuthConfig, mergeGatewayTailscaleConfig } from "./startup-auth.js";
|
||||
import { mergeGatewayTailscaleConfig } from "./startup-auth.js";
|
||||
|
||||
export type GatewayRuntimeConfig = {
|
||||
bindHost: string;
|
||||
|
|
@ -58,15 +58,13 @@ export async function resolveGatewayRuntimeConfig(params: {
|
|||
typeof controlUiRootRaw === "string" && controlUiRootRaw.trim().length > 0
|
||||
? controlUiRootRaw.trim()
|
||||
: undefined;
|
||||
const authBase = params.cfg.gateway?.auth ?? {};
|
||||
const authOverrides = params.auth ?? {};
|
||||
const authConfig = mergeGatewayAuthConfig(authBase, authOverrides);
|
||||
const tailscaleBase = params.cfg.gateway?.tailscale ?? {};
|
||||
const tailscaleOverrides = params.tailscale ?? {};
|
||||
const tailscaleConfig = mergeGatewayTailscaleConfig(tailscaleBase, tailscaleOverrides);
|
||||
const tailscaleMode = tailscaleConfig.mode ?? "off";
|
||||
const resolvedAuth = resolveGatewayAuth({
|
||||
authConfig,
|
||||
authConfig: params.cfg.gateway?.auth,
|
||||
authOverride: params.auth,
|
||||
env: process.env,
|
||||
tailscaleMode,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -243,8 +243,8 @@ export async function startGatewayServer(
|
|||
"Gateway auth token was missing. Generated a new token and saved it to config (gateway.auth.token).",
|
||||
);
|
||||
} else {
|
||||
log.info(
|
||||
"Gateway auth token was missing. Generated a runtime token for this startup without changing config.",
|
||||
log.warn(
|
||||
"Gateway auth token was missing. Generated a runtime token for this startup without changing config; restart will generate a different token. Persist one with `openclaw config set gateway.auth.mode token` and `openclaw config set gateway.auth.token <token>`.",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
import crypto from "node:crypto";
|
||||
import type {
|
||||
GatewayAuthConfig,
|
||||
GatewayAuthMode,
|
||||
GatewayTailscaleConfig,
|
||||
OpenClawConfig,
|
||||
} from "../config/config.js";
|
||||
import { writeConfigFile } from "../config/config.js";
|
||||
import { resolveGatewayAuth } from "./auth.js";
|
||||
import { resolveGatewayAuth, type ResolvedGatewayAuth } from "./auth.js";
|
||||
|
||||
export function mergeGatewayAuthConfig(
|
||||
base?: GatewayAuthConfig,
|
||||
|
|
@ -60,13 +59,13 @@ function resolveGatewayAuthFromConfig(params: {
|
|||
authOverride?: GatewayAuthConfig;
|
||||
tailscaleOverride?: GatewayTailscaleConfig;
|
||||
}) {
|
||||
const authConfig = mergeGatewayAuthConfig(params.cfg.gateway?.auth, params.authOverride);
|
||||
const tailscaleConfig = mergeGatewayTailscaleConfig(
|
||||
params.cfg.gateway?.tailscale,
|
||||
params.tailscaleOverride,
|
||||
);
|
||||
return resolveGatewayAuth({
|
||||
authConfig,
|
||||
authConfig: params.cfg.gateway?.auth,
|
||||
authOverride: params.authOverride,
|
||||
env: params.env,
|
||||
tailscaleMode: tailscaleConfig.mode ?? "off",
|
||||
});
|
||||
|
|
@ -74,16 +73,15 @@ function resolveGatewayAuthFromConfig(params: {
|
|||
|
||||
function shouldPersistGeneratedToken(params: {
|
||||
persistRequested: boolean;
|
||||
baselineMode: GatewayAuthMode;
|
||||
overrideMode?: GatewayAuthMode;
|
||||
resolvedAuth: ResolvedGatewayAuth;
|
||||
}): boolean {
|
||||
if (!params.persistRequested) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Keep CLI/runtime auth mode overrides ephemeral when they switch from an
|
||||
// explicit non-token config mode to token mode.
|
||||
if (params.overrideMode === "token" && params.baselineMode !== "token") {
|
||||
// Keep CLI/runtime mode overrides ephemeral: startup should not silently
|
||||
// mutate durable auth policy when mode was chosen by an override flag.
|
||||
if (params.resolvedAuth.modeSource === "override") {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -104,11 +102,6 @@ export async function ensureGatewayStartupAuth(params: {
|
|||
}> {
|
||||
const env = params.env ?? process.env;
|
||||
const persistRequested = params.persist === true;
|
||||
const baselineResolved = resolveGatewayAuthFromConfig({
|
||||
cfg: params.cfg,
|
||||
env,
|
||||
tailscaleOverride: params.tailscaleOverride,
|
||||
});
|
||||
const resolved = resolveGatewayAuthFromConfig({
|
||||
cfg: params.cfg,
|
||||
env,
|
||||
|
|
@ -133,8 +126,7 @@ export async function ensureGatewayStartupAuth(params: {
|
|||
};
|
||||
const persist = shouldPersistGeneratedToken({
|
||||
persistRequested,
|
||||
baselineMode: baselineResolved.mode,
|
||||
overrideMode: params.authOverride?.mode,
|
||||
resolvedAuth: resolved,
|
||||
});
|
||||
if (persist) {
|
||||
await writeConfigFile(nextCfg);
|
||||
|
|
|
|||
Loading…
Reference in New Issue