Files
openclaw/src/commands/configure.gateway-auth.ts
Omair Afzal 59733a02c8 fix(configure): reject literal "undefined" and "null" gateway auth tokens (#13767)
* fix(configure): reject literal "undefined" and "null" gateway auth tokens

* fix(configure): reject literal "undefined" and "null" gateway auth tokens

* fix(configure): validate gateway password prompt and harden token coercion (#13767) (thanks @omair445)

* test: remove unused vitest imports in baseline lint fixtures (#13767)

---------

Co-authored-by: Luna AI <luna@coredirection.ai>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-02-13 17:04:41 +01:00

120 lines
3.8 KiB
TypeScript

import type { OpenClawConfig, GatewayAuthConfig } from "../config/config.js";
import type { RuntimeEnv } from "../runtime.js";
import type { WizardPrompter } from "../wizard/prompts.js";
import { ensureAuthProfileStore } from "../agents/auth-profiles.js";
import { promptAuthChoiceGrouped } from "./auth-choice-prompt.js";
import { applyAuthChoice, resolvePreferredProviderForAuthChoice } from "./auth-choice.js";
import {
applyModelAllowlist,
applyModelFallbacksFromSelection,
applyPrimaryModel,
promptDefaultModel,
promptModelAllowlist,
} from "./model-picker.js";
import { promptCustomApiConfig } from "./onboard-custom.js";
import { randomToken } from "./onboard-helpers.js";
type GatewayAuthChoice = "token" | "password";
/** Reject undefined, empty, and common JS string-coercion artifacts for token auth. */
function sanitizeTokenValue(value: string | undefined): string | undefined {
if (typeof value !== "string") {
return undefined;
}
const trimmed = value.trim();
if (!trimmed || trimmed === "undefined" || trimmed === "null") {
return undefined;
}
return trimmed;
}
const ANTHROPIC_OAUTH_MODEL_KEYS = [
"anthropic/claude-opus-4-6",
"anthropic/claude-opus-4-5",
"anthropic/claude-sonnet-4-5",
"anthropic/claude-haiku-4-5",
];
export function buildGatewayAuthConfig(params: {
existing?: GatewayAuthConfig;
mode: GatewayAuthChoice;
token?: string;
password?: string;
}): GatewayAuthConfig | undefined {
const allowTailscale = params.existing?.allowTailscale;
const base: GatewayAuthConfig = {};
if (typeof allowTailscale === "boolean") {
base.allowTailscale = allowTailscale;
}
if (params.mode === "token") {
// Keep token mode always valid: treat empty/undefined/"undefined"/"null" as missing and generate a token.
const token = sanitizeTokenValue(params.token) ?? randomToken();
return { ...base, mode: "token", token };
}
const password = params.password?.trim();
return { ...base, mode: "password", ...(password && { password }) };
}
export async function promptAuthConfig(
cfg: OpenClawConfig,
runtime: RuntimeEnv,
prompter: WizardPrompter,
): Promise<OpenClawConfig> {
const authChoice = await promptAuthChoiceGrouped({
prompter,
store: ensureAuthProfileStore(undefined, {
allowKeychainPrompt: false,
}),
includeSkip: true,
});
let next = cfg;
if (authChoice === "custom-api-key") {
const customResult = await promptCustomApiConfig({ prompter, runtime, config: next });
next = customResult.config;
} else if (authChoice !== "skip") {
const applied = await applyAuthChoice({
authChoice,
config: next,
prompter,
runtime,
setDefaultModel: true,
});
next = applied.config;
} else {
const modelSelection = await promptDefaultModel({
config: next,
prompter,
allowKeep: true,
ignoreAllowlist: true,
preferredProvider: resolvePreferredProviderForAuthChoice(authChoice),
});
if (modelSelection.config) {
next = modelSelection.config;
}
if (modelSelection.model) {
next = applyPrimaryModel(next, modelSelection.model);
}
}
const anthropicOAuth =
authChoice === "setup-token" || authChoice === "token" || authChoice === "oauth";
if (authChoice !== "custom-api-key") {
const allowlistSelection = await promptModelAllowlist({
config: next,
prompter,
allowedKeys: anthropicOAuth ? ANTHROPIC_OAUTH_MODEL_KEYS : undefined,
initialSelections: anthropicOAuth ? ["anthropic/claude-opus-4-6"] : undefined,
message: anthropicOAuth ? "Anthropic OAuth models" : undefined,
});
if (allowlistSelection.models) {
next = applyModelAllowlist(next, allowlistSelection.models);
next = applyModelFallbacksFromSelection(next, allowlistSelection.models);
}
}
return next;
}