From fecca6fd8d97c4a3b46a261d0a46fbf6ff8d66a2 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 7 Mar 2026 21:32:42 +0000 Subject: [PATCH] refactor: unify gateway SecretRef auth resolution paths --- src/cli/daemon-cli/status.gather.ts | 40 ++--- src/cli/qr-cli.ts | 24 +-- src/commands/dashboard.ts | 63 +++----- src/commands/doctor-gateway-auth-token.ts | 53 +++---- src/gateway/auth-config-utils.ts | 14 +- ...lve-configured-secret-input-string.test.ts | 137 ++++++++++++++++++ .../resolve-configured-secret-input-string.ts | 96 ++++++++++++ src/gateway/startup-auth.ts | 39 +---- src/pairing/setup-code.ts | 41 ++---- 9 files changed, 318 insertions(+), 189 deletions(-) create mode 100644 src/gateway/resolve-configured-secret-input-string.test.ts diff --git a/src/cli/daemon-cli/status.gather.ts b/src/cli/daemon-cli/status.gather.ts index 71201c6b4..6834f379b 100644 --- a/src/cli/daemon-cli/status.gather.ts +++ b/src/cli/daemon-cli/status.gather.ts @@ -12,7 +12,6 @@ import type { import { hasConfiguredSecretInput, normalizeSecretInputString, - resolveSecretInputRef, } from "../../config/types.secrets.js"; import { readLastGatewayErrorLine } from "../../daemon/diagnostics.js"; import type { FindExtraGatewayServicesOptions } from "../../daemon/inspect.js"; @@ -27,6 +26,7 @@ import { trimToUndefined, } from "../../gateway/credentials.js"; import { resolveGatewayBindHost } from "../../gateway/net.js"; +import { resolveRequiredConfiguredSecretRefInputString } from "../../gateway/resolve-configured-secret-input-string.js"; import { formatPortDiagnostics, inspectPortUsage, @@ -35,8 +35,6 @@ import { } from "../../infra/ports.js"; import { pickPrimaryTailnetIPv4 } from "../../infra/tailnet.js"; import { loadGatewayTlsRuntime } from "../../infra/tls/gateway.js"; -import { secretRefKey } from "../../secrets/ref-contract.js"; -import { resolveSecretRefValues } from "../../secrets/resolve.js"; import { probeGatewayStatus } from "./probe.js"; import { normalizeListenerAddress, parsePortFromArgs, pickProbeHostForBind } from "./shared.js"; import type { GatewayRpcOpts } from "./types.js"; @@ -127,13 +125,6 @@ async function resolveDaemonProbeToken(params: { } const defaults = params.daemonCfg.secrets?.defaults; const configured = params.daemonCfg.gateway?.auth?.token; - const { ref } = resolveSecretInputRef({ - value: configured, - defaults, - }); - if (!ref) { - return normalizeSecretInputString(configured); - } const authMode = params.daemonCfg.gateway?.auth?.mode; if (authMode === "password" || authMode === "none" || authMode === "trusted-proxy") { return undefined; @@ -149,15 +140,16 @@ async function resolveDaemonProbeToken(params: { return undefined; } } - const resolved = await resolveSecretRefValues([ref], { + const resolvedToken = await resolveRequiredConfiguredSecretRefInputString({ config: params.daemonCfg, env: params.mergedDaemonEnv as NodeJS.ProcessEnv, + value: configured, + path: "gateway.auth.token", }); - const token = trimToUndefined(resolved.get(secretRefKey(ref))); - if (!token) { - throw new Error("gateway.auth.token resolved to an empty or non-string value."); + if (resolvedToken) { + return resolvedToken; } - return token; + return normalizeSecretInputString(configured); } async function resolveDaemonProbePassword(params: { @@ -176,13 +168,6 @@ async function resolveDaemonProbePassword(params: { } const defaults = params.daemonCfg.secrets?.defaults; const configured = params.daemonCfg.gateway?.auth?.password; - const { ref } = resolveSecretInputRef({ - value: configured, - defaults, - }); - if (!ref) { - return normalizeSecretInputString(configured); - } const authMode = params.daemonCfg.gateway?.auth?.mode; if (authMode === "token" || authMode === "none" || authMode === "trusted-proxy") { return undefined; @@ -198,15 +183,16 @@ async function resolveDaemonProbePassword(params: { return undefined; } } - const resolved = await resolveSecretRefValues([ref], { + const resolvedPassword = await resolveRequiredConfiguredSecretRefInputString({ config: params.daemonCfg, env: params.mergedDaemonEnv as NodeJS.ProcessEnv, + value: configured, + path: "gateway.auth.password", }); - const password = trimToUndefined(resolved.get(secretRefKey(ref))); - if (!password) { - throw new Error("gateway.auth.password resolved to an empty or non-string value."); + if (resolvedPassword) { + return resolvedPassword; } - return password; + return normalizeSecretInputString(configured); } export async function gatherDaemonStatus( diff --git a/src/cli/qr-cli.ts b/src/cli/qr-cli.ts index 45115a506..b7ff0345c 100644 --- a/src/cli/qr-cli.ts +++ b/src/cli/qr-cli.ts @@ -1,13 +1,12 @@ import type { Command } from "commander"; import qrcode from "qrcode-terminal"; import { loadConfig } from "../config/config.js"; -import { hasConfiguredSecretInput, resolveSecretInputRef } from "../config/types.secrets.js"; +import { hasConfiguredSecretInput } from "../config/types.secrets.js"; import { readGatewayPasswordEnv, readGatewayTokenEnv } from "../gateway/credentials.js"; +import { resolveRequiredConfiguredSecretRefInputString } from "../gateway/resolve-configured-secret-input-string.js"; import { resolvePairingSetupFromConfig, encodePairingSetupCode } from "../pairing/setup-code.js"; import { runCommandWithTimeout } from "../process/exec.js"; import { defaultRuntime } from "../runtime.js"; -import { secretRefKey } from "../secrets/ref-contract.js"; -import { resolveSecretRefValues } from "../secrets/resolve.js"; import { formatDocsLink } from "../terminal/links.js"; import { theme } from "../terminal/theme.js"; import { resolveCommandSecretRefsViaGateway } from "./command-secret-gateway.js"; @@ -66,26 +65,19 @@ function shouldResolveLocalGatewayPasswordSecret( async function resolveLocalGatewayPasswordSecretIfNeeded( cfg: ReturnType, ): Promise { - const authPassword = cfg.gateway?.auth?.password; - const { ref } = resolveSecretInputRef({ - value: authPassword, - defaults: cfg.secrets?.defaults, - }); - if (!ref) { - return; - } - const resolved = await resolveSecretRefValues([ref], { + const resolvedPassword = await resolveRequiredConfiguredSecretRefInputString({ config: cfg, env: process.env, + value: cfg.gateway?.auth?.password, + path: "gateway.auth.password", }); - const value = resolved.get(secretRefKey(ref)); - if (typeof value !== "string" || value.trim().length === 0) { - throw new Error("gateway.auth.password resolved to an empty or non-string value."); + if (!resolvedPassword) { + return; } if (!cfg.gateway?.auth) { return; } - cfg.gateway.auth.password = value.trim(); + cfg.gateway.auth.password = resolvedPassword; } function emitQrSecretResolveDiagnostics(diagnostics: string[], opts: QrCliOptions): void { diff --git a/src/commands/dashboard.ts b/src/commands/dashboard.ts index a25acf757..3ca69fbc3 100644 --- a/src/commands/dashboard.ts +++ b/src/commands/dashboard.ts @@ -1,12 +1,10 @@ import { readConfigFileSnapshot, resolveGatewayPort } from "../config/config.js"; import type { OpenClawConfig } from "../config/types.js"; -import { resolveSecretInputRef } from "../config/types.secrets.js"; import { readGatewayTokenEnv } from "../gateway/credentials.js"; +import { resolveConfiguredSecretInputWithFallback } from "../gateway/resolve-configured-secret-input-string.js"; import { copyToClipboard } from "../infra/clipboard.js"; import type { RuntimeEnv } from "../runtime.js"; import { defaultRuntime } from "../runtime.js"; -import { secretRefKey } from "../secrets/ref-contract.js"; -import { resolveSecretRefValues } from "../secrets/resolve.js"; import { detectBrowserOpenSupport, formatControlUiSshHint, @@ -27,49 +25,26 @@ async function resolveDashboardToken( unresolvedRefReason?: string; tokenSecretRefConfigured: boolean; }> { - const { ref } = resolveSecretInputRef({ + const resolved = await resolveConfiguredSecretInputWithFallback({ + config: cfg, + env, value: cfg.gateway?.auth?.token, - defaults: cfg.secrets?.defaults, + path: "gateway.auth.token", + readFallback: () => readGatewayTokenEnv(env), }); - const configToken = - ref || typeof cfg.gateway?.auth?.token !== "string" - ? undefined - : cfg.gateway.auth.token.trim() || undefined; - if (configToken) { - return { token: configToken, source: "config", tokenSecretRefConfigured: false }; - } - if (!ref) { - const envToken = readGatewayTokenEnv(env); - return envToken - ? { token: envToken, source: "env", tokenSecretRefConfigured: false } - : { tokenSecretRefConfigured: false }; - } - const refLabel = `${ref.source}:${ref.provider}:${ref.id}`; - try { - const resolved = await resolveSecretRefValues([ref], { - config: cfg, - env, - }); - const value = resolved.get(secretRefKey(ref)); - if (typeof value === "string" && value.trim().length > 0) { - return { token: value.trim(), source: "secretRef", tokenSecretRefConfigured: true }; - } - const envToken = readGatewayTokenEnv(env); - return envToken - ? { token: envToken, source: "env", tokenSecretRefConfigured: true } - : { - unresolvedRefReason: `gateway.auth.token SecretRef is unresolved (${refLabel}).`, - tokenSecretRefConfigured: true, - }; - } catch { - const envToken = readGatewayTokenEnv(env); - return envToken - ? { token: envToken, source: "env", tokenSecretRefConfigured: true } - : { - unresolvedRefReason: `gateway.auth.token SecretRef is unresolved (${refLabel}).`, - tokenSecretRefConfigured: true, - }; - } + return { + token: resolved.value, + source: + resolved.source === "config" + ? "config" + : resolved.source === "secretRef" + ? "secretRef" + : resolved.source === "fallback" + ? "env" + : undefined, + unresolvedRefReason: resolved.unresolvedRefReason, + tokenSecretRefConfigured: resolved.secretRefConfigured, + }; } export async function dashboardCommand( diff --git a/src/commands/doctor-gateway-auth-token.ts b/src/commands/doctor-gateway-auth-token.ts index 782ad26d1..8bbac6722 100644 --- a/src/commands/doctor-gateway-auth-token.ts +++ b/src/commands/doctor-gateway-auth-token.ts @@ -1,49 +1,30 @@ import type { OpenClawConfig } from "../config/config.js"; -import { resolveSecretInputRef } from "../config/types.secrets.js"; export { shouldRequireGatewayTokenForInstall } from "../gateway/auth-install-policy.js"; import { readGatewayTokenEnv } from "../gateway/credentials.js"; -import { secretRefKey } from "../secrets/ref-contract.js"; -import { resolveSecretRefValues } from "../secrets/resolve.js"; +import { resolveConfiguredSecretInputWithFallback } from "../gateway/resolve-configured-secret-input-string.js"; export async function resolveGatewayAuthTokenForService( cfg: OpenClawConfig, env: NodeJS.ProcessEnv, ): Promise<{ token?: string; unavailableReason?: string }> { - const { ref } = resolveSecretInputRef({ + const resolved = await resolveConfiguredSecretInputWithFallback({ + config: cfg, + env, value: cfg.gateway?.auth?.token, - defaults: cfg.secrets?.defaults, + path: "gateway.auth.token", + unresolvedReasonStyle: "detailed", + readFallback: () => readGatewayTokenEnv(env), }); - const configToken = - ref || typeof cfg.gateway?.auth?.token !== "string" - ? undefined - : cfg.gateway.auth.token.trim() || undefined; - if (configToken) { - return { token: configToken }; + if (resolved.value) { + return { token: resolved.value }; } - if (ref) { - try { - const resolved = await resolveSecretRefValues([ref], { - config: cfg, - env, - }); - const value = resolved.get(secretRefKey(ref)); - if (typeof value === "string" && value.trim().length > 0) { - return { token: value.trim() }; - } - const envToken = readGatewayTokenEnv(env); - if (envToken) { - return { token: envToken }; - } - return { unavailableReason: "gateway.auth.token SecretRef resolved to an empty value." }; - } catch (err) { - const envToken = readGatewayTokenEnv(env); - if (envToken) { - return { token: envToken }; - } - return { - unavailableReason: `gateway.auth.token SecretRef is configured but unresolved (${String(err)}).`, - }; - } + if (!resolved.secretRefConfigured) { + return {}; } - return { token: readGatewayTokenEnv(env) }; + if (resolved.unresolvedRefReason?.includes("resolved to an empty value")) { + return { unavailableReason: resolved.unresolvedRefReason }; + } + return { + unavailableReason: `gateway.auth.token SecretRef is configured but unresolved (${resolved.unresolvedRefReason ?? "unknown reason"}).`, + }; } diff --git a/src/gateway/auth-config-utils.ts b/src/gateway/auth-config-utils.ts index f62e60f85..7f1ca9fd0 100644 --- a/src/gateway/auth-config-utils.ts +++ b/src/gateway/auth-config-utils.ts @@ -1,7 +1,6 @@ import type { GatewayAuthConfig, OpenClawConfig } from "../config/config.js"; import { resolveSecretInputRef } from "../config/types.secrets.js"; -import { secretRefKey } from "../secrets/ref-contract.js"; -import { resolveSecretRefValues } from "../secrets/resolve.js"; +import { resolveRequiredConfiguredSecretRefInputString } from "./resolve-configured-secret-input-string.js"; export function withGatewayAuthPassword(cfg: OpenClawConfig, password: string): OpenClawConfig { return { @@ -57,13 +56,14 @@ export async function resolveGatewayPasswordSecretRef(params: { ) { return params.cfg; } - const resolved = await resolveSecretRefValues([ref], { + const value = await resolveRequiredConfiguredSecretRefInputString({ config: params.cfg, env: params.env, + value: authPassword, + path: "gateway.auth.password", }); - const value = resolved.get(secretRefKey(ref)); - if (typeof value !== "string" || value.trim().length === 0) { - throw new Error("gateway.auth.password resolved to an empty or non-string value."); + if (!value) { + return params.cfg; } - return withGatewayAuthPassword(params.cfg, value.trim()); + return withGatewayAuthPassword(params.cfg, value); } diff --git a/src/gateway/resolve-configured-secret-input-string.test.ts b/src/gateway/resolve-configured-secret-input-string.test.ts new file mode 100644 index 000000000..b99e15c4e --- /dev/null +++ b/src/gateway/resolve-configured-secret-input-string.test.ts @@ -0,0 +1,137 @@ +import { describe, expect, it } from "vitest"; +import type { OpenClawConfig } from "../config/types.js"; +import { + resolveConfiguredSecretInputWithFallback, + resolveRequiredConfiguredSecretRefInputString, +} from "./resolve-configured-secret-input-string.js"; + +function createConfig(value: unknown): OpenClawConfig { + return { + gateway: { + auth: { + token: value, + }, + }, + secrets: { + providers: { + default: { source: "env" }, + }, + }, + } as OpenClawConfig; +} + +describe("resolveConfiguredSecretInputWithFallback", () => { + it("returns plaintext config value when present", async () => { + const resolved = await resolveConfiguredSecretInputWithFallback({ + config: createConfig("config-token"), + env: {} as NodeJS.ProcessEnv, + value: "config-token", + path: "gateway.auth.token", + readFallback: () => "env-token", + }); + + expect(resolved).toEqual({ + value: "config-token", + source: "config", + secretRefConfigured: false, + }); + }); + + it("returns fallback value when config is empty and no SecretRef is configured", async () => { + const resolved = await resolveConfiguredSecretInputWithFallback({ + config: createConfig(""), + env: {} as NodeJS.ProcessEnv, + value: "", + path: "gateway.auth.token", + readFallback: () => "env-token", + }); + + expect(resolved).toEqual({ + value: "env-token", + source: "fallback", + secretRefConfigured: false, + }); + }); + + it("returns resolved SecretRef value", async () => { + const resolved = await resolveConfiguredSecretInputWithFallback({ + config: createConfig("${CUSTOM_GATEWAY_TOKEN}"), + env: { CUSTOM_GATEWAY_TOKEN: "resolved-token" } as NodeJS.ProcessEnv, + value: "${CUSTOM_GATEWAY_TOKEN}", + path: "gateway.auth.token", + readFallback: () => undefined, + }); + + expect(resolved).toEqual({ + value: "resolved-token", + source: "secretRef", + secretRefConfigured: true, + }); + }); + + it("falls back when SecretRef cannot be resolved", async () => { + const resolved = await resolveConfiguredSecretInputWithFallback({ + config: createConfig("${MISSING_GATEWAY_TOKEN}"), + env: {} as NodeJS.ProcessEnv, + value: "${MISSING_GATEWAY_TOKEN}", + path: "gateway.auth.token", + readFallback: () => "env-fallback-token", + }); + + expect(resolved).toEqual({ + value: "env-fallback-token", + source: "fallback", + secretRefConfigured: true, + }); + }); + + it("returns unresolved reason when SecretRef cannot be resolved and no fallback exists", async () => { + const resolved = await resolveConfiguredSecretInputWithFallback({ + config: createConfig("${MISSING_GATEWAY_TOKEN}"), + env: {} as NodeJS.ProcessEnv, + value: "${MISSING_GATEWAY_TOKEN}", + path: "gateway.auth.token", + }); + + expect(resolved.value).toBeUndefined(); + expect(resolved.source).toBeUndefined(); + expect(resolved.secretRefConfigured).toBe(true); + expect(resolved.unresolvedRefReason).toContain("gateway.auth.token SecretRef is unresolved"); + expect(resolved.unresolvedRefReason).toContain("MISSING_GATEWAY_TOKEN"); + }); +}); + +describe("resolveRequiredConfiguredSecretRefInputString", () => { + it("returns undefined when no SecretRef is configured", async () => { + const value = await resolveRequiredConfiguredSecretRefInputString({ + config: createConfig("plain-token"), + env: {} as NodeJS.ProcessEnv, + value: "plain-token", + path: "gateway.auth.token", + }); + + expect(value).toBeUndefined(); + }); + + it("returns resolved SecretRef value", async () => { + const value = await resolveRequiredConfiguredSecretRefInputString({ + config: createConfig("${CUSTOM_GATEWAY_TOKEN}"), + env: { CUSTOM_GATEWAY_TOKEN: "resolved-token" } as NodeJS.ProcessEnv, + value: "${CUSTOM_GATEWAY_TOKEN}", + path: "gateway.auth.token", + }); + + expect(value).toBe("resolved-token"); + }); + + it("throws when SecretRef cannot be resolved", async () => { + await expect( + resolveRequiredConfiguredSecretRefInputString({ + config: createConfig("${MISSING_GATEWAY_TOKEN}"), + env: {} as NodeJS.ProcessEnv, + value: "${MISSING_GATEWAY_TOKEN}", + path: "gateway.auth.token", + }), + ).rejects.toThrow(/MISSING_GATEWAY_TOKEN/i); + }); +}); diff --git a/src/gateway/resolve-configured-secret-input-string.ts b/src/gateway/resolve-configured-secret-input-string.ts index e698b0991..b16b89e83 100644 --- a/src/gateway/resolve-configured-secret-input-string.ts +++ b/src/gateway/resolve-configured-secret-input-string.ts @@ -4,6 +4,7 @@ import { secretRefKey } from "../secrets/ref-contract.js"; import { resolveSecretRefValues } from "../secrets/resolve.js"; export type SecretInputUnresolvedReasonStyle = "generic" | "detailed"; // pragma: allowlist secret +export type ConfiguredSecretInputSource = "config" | "secretRef" | "fallback"; function trimToUndefined(value: unknown): string | undefined { if (typeof value !== "string") { @@ -87,3 +88,98 @@ export async function resolveConfiguredSecretInputString(params: { }; } } + +export async function resolveConfiguredSecretInputWithFallback(params: { + config: OpenClawConfig; + env: NodeJS.ProcessEnv; + value: unknown; + path: string; + unresolvedReasonStyle?: SecretInputUnresolvedReasonStyle; + readFallback?: () => string | undefined; +}): Promise<{ + value?: string; + source?: ConfiguredSecretInputSource; + unresolvedRefReason?: string; + secretRefConfigured: boolean; +}> { + const { ref } = resolveSecretInputRef({ + value: params.value, + defaults: params.config.secrets?.defaults, + }); + const configValue = !ref ? trimToUndefined(params.value) : undefined; + if (configValue) { + return { + value: configValue, + source: "config", + secretRefConfigured: false, + }; + } + if (!ref) { + const fallback = params.readFallback?.(); + if (fallback) { + return { + value: fallback, + source: "fallback", + secretRefConfigured: false, + }; + } + return { secretRefConfigured: false }; + } + + const resolved = await resolveConfiguredSecretInputString({ + config: params.config, + env: params.env, + value: params.value, + path: params.path, + unresolvedReasonStyle: params.unresolvedReasonStyle, + }); + if (resolved.value) { + return { + value: resolved.value, + source: "secretRef", + secretRefConfigured: true, + }; + } + + const fallback = params.readFallback?.(); + if (fallback) { + return { + value: fallback, + source: "fallback", + secretRefConfigured: true, + }; + } + + return { + unresolvedRefReason: resolved.unresolvedRefReason, + secretRefConfigured: true, + }; +} + +export async function resolveRequiredConfiguredSecretRefInputString(params: { + config: OpenClawConfig; + env: NodeJS.ProcessEnv; + value: unknown; + path: string; + unresolvedReasonStyle?: SecretInputUnresolvedReasonStyle; +}): Promise { + const { ref } = resolveSecretInputRef({ + value: params.value, + defaults: params.config.secrets?.defaults, + }); + if (!ref) { + return undefined; + } + + const resolved = await resolveConfiguredSecretInputString({ + config: params.config, + env: params.env, + value: params.value, + path: params.path, + unresolvedReasonStyle: params.unresolvedReasonStyle, + }); + if (resolved.value) { + return resolved.value; + } + throw new Error(resolved.unresolvedRefReason ?? `${params.path} resolved to an empty value.`); +} diff --git a/src/gateway/startup-auth.ts b/src/gateway/startup-auth.ts index 8132d902b..c3995ed2d 100644 --- a/src/gateway/startup-auth.ts +++ b/src/gateway/startup-auth.ts @@ -5,9 +5,7 @@ import type { OpenClawConfig, } from "../config/config.js"; import { writeConfigFile } from "../config/config.js"; -import { hasConfiguredSecretInput, resolveSecretInputRef } from "../config/types.secrets.js"; -import { secretRefKey } from "../secrets/ref-contract.js"; -import { resolveSecretRefValues } from "../secrets/resolve.js"; +import { hasConfiguredSecretInput } from "../config/types.secrets.js"; import { assertExplicitGatewayAuthModeWhenBothConfigured } from "./auth-mode-policy.js"; import { resolveGatewayAuth, type ResolvedGatewayAuth } from "./auth.js"; import { @@ -15,6 +13,7 @@ import { hasGatewayTokenEnvCandidate, readGatewayTokenEnv, } from "./credentials.js"; +import { resolveRequiredConfiguredSecretRefInputString } from "./resolve-configured-secret-input-string.js"; export function mergeGatewayAuthConfig( base?: GatewayAuthConfig, @@ -167,26 +166,15 @@ async function resolveGatewayTokenSecretRef( env: NodeJS.ProcessEnv, authOverride?: GatewayAuthConfig, ): Promise { - const authToken = cfg.gateway?.auth?.token; - const { ref } = resolveSecretInputRef({ - value: authToken, - defaults: cfg.secrets?.defaults, - }); - if (!ref) { - return undefined; - } if (!shouldResolveGatewayTokenSecretRef({ cfg, env, authOverride })) { return undefined; } - const resolved = await resolveSecretRefValues([ref], { + return await resolveRequiredConfiguredSecretRefInputString({ config: cfg, env, + value: cfg.gateway?.auth?.token, + path: "gateway.auth.token", }); - const value = resolved.get(secretRefKey(ref)); - if (typeof value !== "string" || value.trim().length === 0) { - throw new Error("gateway.auth.token resolved to an empty or non-string value."); - } - return value.trim(); } function shouldResolveGatewayPasswordSecretRef(params: { @@ -216,26 +204,15 @@ async function resolveGatewayPasswordSecretRef( env: NodeJS.ProcessEnv, authOverride?: GatewayAuthConfig, ): Promise { - const authPassword = cfg.gateway?.auth?.password; - const { ref } = resolveSecretInputRef({ - value: authPassword, - defaults: cfg.secrets?.defaults, - }); - if (!ref) { - return undefined; - } if (!shouldResolveGatewayPasswordSecretRef({ cfg, env, authOverride })) { return undefined; } - const resolved = await resolveSecretRefValues([ref], { + return await resolveRequiredConfiguredSecretRefInputString({ config: cfg, env, + value: cfg.gateway?.auth?.password, + path: "gateway.auth.password", }); - const value = resolved.get(secretRefKey(ref)); - if (typeof value !== "string" || value.trim().length === 0) { - throw new Error("gateway.auth.password resolved to an empty or non-string value."); - } - return value.trim(); } export async function ensureGatewayStartupAuth(params: { diff --git a/src/pairing/setup-code.ts b/src/pairing/setup-code.ts index ef9dda897..2e4246b19 100644 --- a/src/pairing/setup-code.ts +++ b/src/pairing/setup-code.ts @@ -7,8 +7,7 @@ import { resolveSecretInputRef, } from "../config/types.secrets.js"; import { assertExplicitGatewayAuthModeWhenBothConfigured } from "../gateway/auth-mode-policy.js"; -import { secretRefKey } from "../secrets/ref-contract.js"; -import { resolveSecretRefValues } from "../secrets/resolve.js"; +import { resolveRequiredConfiguredSecretRefInputString } from "../gateway/resolve-configured-secret-input-string.js"; import { resolveGatewayBindUrl } from "../shared/gateway-bind-url.js"; import { isCarrierGradeNatIpv4Address, isRfc1918Ipv4Address } from "../shared/net/ip.js"; import { resolveTailnetHostWithRunner } from "../shared/tailscale-status.js"; @@ -209,14 +208,6 @@ async function resolveGatewayTokenSecretRef( cfg: OpenClawConfig, env: NodeJS.ProcessEnv, ): Promise { - const authToken = cfg.gateway?.auth?.token; - const { ref } = resolveSecretInputRef({ - value: authToken, - defaults: cfg.secrets?.defaults, - }); - if (!ref) { - return cfg; - } const hasTokenEnvCandidate = Boolean(resolveGatewayTokenFromEnv(env)); if (hasTokenEnvCandidate) { return cfg; @@ -233,13 +224,14 @@ async function resolveGatewayTokenSecretRef( return cfg; } } - const resolved = await resolveSecretRefValues([ref], { + const token = await resolveRequiredConfiguredSecretRefInputString({ config: cfg, env, + value: cfg.gateway?.auth?.token, + path: "gateway.auth.token", }); - const value = resolved.get(secretRefKey(ref)); - if (typeof value !== "string" || value.trim().length === 0) { - throw new Error("gateway.auth.token resolved to an empty or non-string value."); + if (!token) { + return cfg; } return { ...cfg, @@ -247,7 +239,7 @@ async function resolveGatewayTokenSecretRef( ...cfg.gateway, auth: { ...cfg.gateway?.auth, - token: value.trim(), + token, }, }, }; @@ -257,14 +249,6 @@ async function resolveGatewayPasswordSecretRef( cfg: OpenClawConfig, env: NodeJS.ProcessEnv, ): Promise { - const authPassword = cfg.gateway?.auth?.password; - const { ref } = resolveSecretInputRef({ - value: authPassword, - defaults: cfg.secrets?.defaults, - }); - if (!ref) { - return cfg; - } const hasPasswordEnvCandidate = Boolean(resolveGatewayPasswordFromEnv(env)); if (hasPasswordEnvCandidate) { return cfg; @@ -281,13 +265,14 @@ async function resolveGatewayPasswordSecretRef( return cfg; } } - const resolved = await resolveSecretRefValues([ref], { + const password = await resolveRequiredConfiguredSecretRefInputString({ config: cfg, env, + value: cfg.gateway?.auth?.password, + path: "gateway.auth.password", }); - const value = resolved.get(secretRefKey(ref)); - if (typeof value !== "string" || value.trim().length === 0) { - throw new Error("gateway.auth.password resolved to an empty or non-string value."); + if (!password) { + return cfg; } return { ...cfg, @@ -295,7 +280,7 @@ async function resolveGatewayPasswordSecretRef( ...cfg.gateway, auth: { ...cfg.gateway?.auth, - password: value.trim(), + password, }, }, };