mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-10 22:44:36 +00:00
feat(secrets): expand onboarding secret-ref flows and custom-provider parity
This commit is contained in:
committed by
Peter Steinberger
parent
e8637c79b3
commit
5e3a86fd2f
@@ -48,7 +48,7 @@ type ProviderAuthConfigSnapshot = {
|
||||
{
|
||||
baseUrl?: string;
|
||||
api?: string;
|
||||
apiKey?: string;
|
||||
apiKey?: string | { source?: string; id?: string };
|
||||
models?: Array<{ id?: string }>;
|
||||
}
|
||||
>;
|
||||
@@ -145,6 +145,14 @@ async function runCustomLocalNonInteractive(
|
||||
}
|
||||
|
||||
async function readCustomLocalProviderApiKey(configPath: string): Promise<string | undefined> {
|
||||
const cfg = await readJsonFile<ProviderAuthConfigSnapshot>(configPath);
|
||||
const apiKey = cfg.models?.providers?.[CUSTOM_LOCAL_PROVIDER_ID]?.apiKey;
|
||||
return typeof apiKey === "string" ? apiKey : undefined;
|
||||
}
|
||||
|
||||
async function readCustomLocalProviderApiKeyInput(
|
||||
configPath: string,
|
||||
): Promise<string | { source?: string; id?: string } | undefined> {
|
||||
const cfg = await readJsonFile<ProviderAuthConfigSnapshot>(configPath);
|
||||
return cfg.models?.providers?.[CUSTOM_LOCAL_PROVIDER_ID]?.apiKey;
|
||||
}
|
||||
@@ -349,6 +357,91 @@ describe("onboard (non-interactive): provider auth", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it.each([
|
||||
{
|
||||
name: "anthropic",
|
||||
prefix: "openclaw-onboard-ref-flag-anthropic-",
|
||||
authChoice: "apiKey",
|
||||
optionKey: "anthropicApiKey",
|
||||
flagName: "--anthropic-api-key",
|
||||
envVar: "ANTHROPIC_API_KEY",
|
||||
},
|
||||
{
|
||||
name: "openai",
|
||||
prefix: "openclaw-onboard-ref-flag-openai-",
|
||||
authChoice: "openai-api-key",
|
||||
optionKey: "openaiApiKey",
|
||||
flagName: "--openai-api-key",
|
||||
envVar: "OPENAI_API_KEY",
|
||||
},
|
||||
{
|
||||
name: "openrouter",
|
||||
prefix: "openclaw-onboard-ref-flag-openrouter-",
|
||||
authChoice: "openrouter-api-key",
|
||||
optionKey: "openrouterApiKey",
|
||||
flagName: "--openrouter-api-key",
|
||||
envVar: "OPENROUTER_API_KEY",
|
||||
},
|
||||
{
|
||||
name: "xai",
|
||||
prefix: "openclaw-onboard-ref-flag-xai-",
|
||||
authChoice: "xai-api-key",
|
||||
optionKey: "xaiApiKey",
|
||||
flagName: "--xai-api-key",
|
||||
envVar: "XAI_API_KEY",
|
||||
},
|
||||
{
|
||||
name: "volcengine",
|
||||
prefix: "openclaw-onboard-ref-flag-volcengine-",
|
||||
authChoice: "volcengine-api-key",
|
||||
optionKey: "volcengineApiKey",
|
||||
flagName: "--volcengine-api-key",
|
||||
envVar: "VOLCANO_ENGINE_API_KEY",
|
||||
},
|
||||
{
|
||||
name: "byteplus",
|
||||
prefix: "openclaw-onboard-ref-flag-byteplus-",
|
||||
authChoice: "byteplus-api-key",
|
||||
optionKey: "byteplusApiKey",
|
||||
flagName: "--byteplus-api-key",
|
||||
envVar: "BYTEPLUS_API_KEY",
|
||||
},
|
||||
])(
|
||||
"fails fast for $name when --secret-input-mode ref uses explicit key without env and does not leak the key",
|
||||
async ({ prefix, authChoice, optionKey, flagName, envVar }) => {
|
||||
await withOnboardEnv(prefix, async ({ runtime }) => {
|
||||
const providedSecret = `${envVar.toLowerCase()}-should-not-leak`;
|
||||
const options: Record<string, unknown> = {
|
||||
authChoice,
|
||||
secretInputMode: "ref",
|
||||
[optionKey]: providedSecret,
|
||||
skipSkills: true,
|
||||
};
|
||||
const envOverrides: Record<string, string | undefined> = {
|
||||
[envVar]: undefined,
|
||||
};
|
||||
|
||||
await withEnvAsync(envOverrides, async () => {
|
||||
let thrown: Error | undefined;
|
||||
try {
|
||||
await runNonInteractiveOnboardingWithDefaults(runtime, options);
|
||||
} catch (error) {
|
||||
thrown = error as Error;
|
||||
}
|
||||
expect(thrown).toBeDefined();
|
||||
const message = String(thrown?.message ?? "");
|
||||
expect(message).toContain(
|
||||
`${flagName} cannot be used with --secret-input-mode ref unless ${envVar} is set in env.`,
|
||||
);
|
||||
expect(message).toContain(
|
||||
`Set ${envVar} in env and omit ${flagName}, or use --secret-input-mode plaintext.`,
|
||||
);
|
||||
expect(message).not.toContain(providedSecret);
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
it("rejects vLLM auth choice in non-interactive mode", async () => {
|
||||
await withOnboardEnv("openclaw-onboard-vllm-non-interactive-", async ({ runtime }) => {
|
||||
await expect(
|
||||
@@ -508,6 +601,48 @@ describe("onboard (non-interactive): provider auth", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("stores CUSTOM_API_KEY env ref for non-interactive custom provider auth in ref mode", async () => {
|
||||
await withOnboardEnv(
|
||||
"openclaw-onboard-custom-provider-env-ref-",
|
||||
async ({ configPath, runtime }) => {
|
||||
process.env.CUSTOM_API_KEY = "custom-env-key";
|
||||
await runCustomLocalNonInteractive(runtime, {
|
||||
secretInputMode: "ref",
|
||||
});
|
||||
expect(await readCustomLocalProviderApiKeyInput(configPath)).toEqual({
|
||||
source: "env",
|
||||
id: "CUSTOM_API_KEY",
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it("fails fast for custom provider ref mode when --custom-api-key is set but CUSTOM_API_KEY env is missing", async () => {
|
||||
await withOnboardEnv("openclaw-onboard-custom-provider-ref-flag-", async ({ runtime }) => {
|
||||
const providedSecret = "custom-inline-key-should-not-leak";
|
||||
await withEnvAsync({ CUSTOM_API_KEY: undefined }, async () => {
|
||||
let thrown: Error | undefined;
|
||||
try {
|
||||
await runCustomLocalNonInteractive(runtime, {
|
||||
secretInputMode: "ref",
|
||||
customApiKey: providedSecret,
|
||||
});
|
||||
} catch (error) {
|
||||
thrown = error as Error;
|
||||
}
|
||||
expect(thrown).toBeDefined();
|
||||
const message = String(thrown?.message ?? "");
|
||||
expect(message).toContain(
|
||||
"--custom-api-key cannot be used with --secret-input-mode ref unless CUSTOM_API_KEY is set in env.",
|
||||
);
|
||||
expect(message).toContain(
|
||||
"Set CUSTOM_API_KEY in env and omit --custom-api-key, or use --secret-input-mode plaintext.",
|
||||
);
|
||||
expect(message).not.toContain(providedSecret);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("uses matching profile fallback for non-interactive custom provider auth", async () => {
|
||||
await withOnboardEnv(
|
||||
"openclaw-onboard-custom-provider-profile-fallback-",
|
||||
|
||||
Reference in New Issue
Block a user