mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-04 15:20:21 +00:00
201 lines
6.4 KiB
TypeScript
201 lines
6.4 KiB
TypeScript
import { formatCliCommand } from "../cli/command-format.js";
|
|
import {
|
|
readConfigFileSnapshot,
|
|
replaceConfigFile,
|
|
type OpenClawConfig,
|
|
} from "../config/config.js";
|
|
import { resolveSecretInputRef } from "../config/types.secrets.js";
|
|
import { shouldRequireGatewayTokenForInstall } from "../gateway/auth-install-policy.js";
|
|
import { hasAmbiguousGatewayAuthModeConfig } from "../gateway/auth-mode-policy.js";
|
|
import { resolveGatewayAuth } from "../gateway/auth.js";
|
|
import { readGatewayTokenEnv } from "../gateway/credentials.js";
|
|
import { secretRefKey } from "../secrets/ref-contract.js";
|
|
import { resolveSecretRefValues } from "../secrets/resolve.js";
|
|
import { randomToken } from "./onboard-helpers.js";
|
|
|
|
type GatewayInstallTokenOptions = {
|
|
config: OpenClawConfig;
|
|
env: NodeJS.ProcessEnv;
|
|
explicitToken?: string;
|
|
autoGenerateWhenMissing?: boolean;
|
|
persistGeneratedToken?: boolean;
|
|
};
|
|
|
|
export type GatewayInstallTokenResolution = {
|
|
token?: string;
|
|
tokenRefConfigured: boolean;
|
|
unavailableReason?: string;
|
|
warnings: string[];
|
|
};
|
|
|
|
function resolveConfiguredGatewayInstallToken(params: {
|
|
config: OpenClawConfig;
|
|
env: NodeJS.ProcessEnv;
|
|
explicitToken?: string;
|
|
tokenRef: unknown;
|
|
}): string | undefined {
|
|
const configToken =
|
|
params.tokenRef || typeof params.config.gateway?.auth?.token !== "string"
|
|
? undefined
|
|
: params.config.gateway.auth.token.trim() || undefined;
|
|
const explicitToken = params.explicitToken?.trim() || undefined;
|
|
const envToken = readGatewayTokenEnv(params.env);
|
|
return explicitToken || configToken || (params.tokenRef ? undefined : envToken);
|
|
}
|
|
|
|
async function validateGatewayInstallTokenSecretRef(params: {
|
|
tokenRef: NonNullable<ReturnType<typeof resolveSecretInputRef>["ref"]>;
|
|
config: OpenClawConfig;
|
|
env: NodeJS.ProcessEnv;
|
|
}): Promise<string | undefined> {
|
|
try {
|
|
const resolved = await resolveSecretRefValues([params.tokenRef], {
|
|
config: params.config,
|
|
env: params.env,
|
|
});
|
|
const value = resolved.get(secretRefKey(params.tokenRef));
|
|
if (typeof value !== "string" || value.trim().length === 0) {
|
|
throw new Error("gateway.auth.token resolved to an empty or non-string value.");
|
|
}
|
|
return undefined;
|
|
} catch (err) {
|
|
return `gateway.auth.token SecretRef is configured but unresolved (${String(err)}).`;
|
|
}
|
|
}
|
|
|
|
async function maybePersistAutoGeneratedGatewayInstallToken(params: {
|
|
token: string;
|
|
config: OpenClawConfig;
|
|
warnings: string[];
|
|
}): Promise<string | undefined> {
|
|
try {
|
|
const snapshot = await readConfigFileSnapshot();
|
|
if (snapshot.exists && !snapshot.valid) {
|
|
params.warnings.push(
|
|
"Warning: config file exists but is invalid; skipping token persistence.",
|
|
);
|
|
return params.token;
|
|
}
|
|
|
|
const baseConfig = snapshot.exists ? (snapshot.sourceConfig ?? snapshot.config) : {};
|
|
const existingTokenRef = resolveSecretInputRef({
|
|
value: baseConfig.gateway?.auth?.token,
|
|
defaults: baseConfig.secrets?.defaults,
|
|
}).ref;
|
|
const baseConfigToken =
|
|
existingTokenRef || typeof baseConfig.gateway?.auth?.token !== "string"
|
|
? undefined
|
|
: baseConfig.gateway.auth.token.trim() || undefined;
|
|
if (!existingTokenRef && !baseConfigToken) {
|
|
await replaceConfigFile({
|
|
baseHash: snapshot.hash,
|
|
nextConfig: {
|
|
...baseConfig,
|
|
gateway: {
|
|
...baseConfig.gateway,
|
|
auth: {
|
|
...baseConfig.gateway?.auth,
|
|
mode: baseConfig.gateway?.auth?.mode ?? "token",
|
|
token: params.token,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
return params.token;
|
|
}
|
|
if (baseConfigToken) {
|
|
return baseConfigToken;
|
|
}
|
|
params.warnings.push(
|
|
"Warning: gateway.auth.token is SecretRef-managed; skipping plaintext token persistence.",
|
|
);
|
|
return undefined;
|
|
} catch (err) {
|
|
params.warnings.push(`Warning: could not persist token to config: ${String(err)}`);
|
|
return params.token;
|
|
}
|
|
}
|
|
|
|
function formatAmbiguousGatewayAuthModeReason(): string {
|
|
return [
|
|
"gateway.auth.token and gateway.auth.password are both configured while gateway.auth.mode is unset.",
|
|
`Set ${formatCliCommand("openclaw config set gateway.auth.mode token")} or ${formatCliCommand("openclaw config set gateway.auth.mode password")}.`,
|
|
].join(" ");
|
|
}
|
|
|
|
export async function resolveGatewayInstallToken(
|
|
options: GatewayInstallTokenOptions,
|
|
): Promise<GatewayInstallTokenResolution> {
|
|
const cfg = options.config;
|
|
const warnings: string[] = [];
|
|
const tokenRef = resolveSecretInputRef({
|
|
value: cfg.gateway?.auth?.token,
|
|
defaults: cfg.secrets?.defaults,
|
|
}).ref;
|
|
const tokenRefConfigured = Boolean(tokenRef);
|
|
|
|
if (hasAmbiguousGatewayAuthModeConfig(cfg)) {
|
|
return {
|
|
token: undefined,
|
|
tokenRefConfigured,
|
|
unavailableReason: formatAmbiguousGatewayAuthModeReason(),
|
|
warnings,
|
|
};
|
|
}
|
|
|
|
const resolvedAuth = resolveGatewayAuth({
|
|
authConfig: cfg.gateway?.auth,
|
|
env: options.env,
|
|
tailscaleMode: cfg.gateway?.tailscale?.mode ?? "off",
|
|
});
|
|
const needsToken =
|
|
shouldRequireGatewayTokenForInstall(cfg, options.env) && !resolvedAuth.allowTailscale;
|
|
|
|
let token = resolveConfiguredGatewayInstallToken({
|
|
config: cfg,
|
|
env: options.env,
|
|
explicitToken: options.explicitToken,
|
|
tokenRef,
|
|
});
|
|
let unavailableReason: string | undefined;
|
|
|
|
if (tokenRef && !token && needsToken) {
|
|
unavailableReason = await validateGatewayInstallTokenSecretRef({
|
|
tokenRef,
|
|
config: cfg,
|
|
env: options.env,
|
|
});
|
|
if (!unavailableReason) {
|
|
warnings.push(
|
|
"gateway.auth.token is SecretRef-managed; install will not persist a resolved token in service environment. Ensure the SecretRef is resolvable in the daemon runtime context.",
|
|
);
|
|
}
|
|
}
|
|
|
|
const allowAutoGenerate = options.autoGenerateWhenMissing ?? false;
|
|
const persistGeneratedToken = options.persistGeneratedToken ?? false;
|
|
if (!token && needsToken && !tokenRef && allowAutoGenerate) {
|
|
token = randomToken();
|
|
warnings.push(
|
|
persistGeneratedToken
|
|
? "No gateway token found. Auto-generated one and saving to config."
|
|
: "No gateway token found. Auto-generated one for this run without saving to config.",
|
|
);
|
|
|
|
if (persistGeneratedToken) {
|
|
token = await maybePersistAutoGeneratedGatewayInstallToken({
|
|
token,
|
|
config: cfg,
|
|
warnings,
|
|
});
|
|
}
|
|
}
|
|
|
|
return {
|
|
token,
|
|
tokenRefConfigured,
|
|
unavailableReason,
|
|
warnings,
|
|
};
|
|
}
|