mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 12:21:24 +00:00
fix(auth): strip line breaks from pasted keys
This commit is contained in:
93
src/commands/onboard-non-interactive.token.test.ts
Normal file
93
src/commands/onboard-non-interactive.token.test.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
describe("onboard (non-interactive): token auth", () => {
|
||||
it("writes token profile config and stores the token", async () => {
|
||||
const prev = {
|
||||
home: process.env.HOME,
|
||||
stateDir: process.env.OPENCLAW_STATE_DIR,
|
||||
configPath: process.env.OPENCLAW_CONFIG_PATH,
|
||||
skipChannels: process.env.OPENCLAW_SKIP_CHANNELS,
|
||||
skipGmail: process.env.OPENCLAW_SKIP_GMAIL_WATCHER,
|
||||
skipCron: process.env.OPENCLAW_SKIP_CRON,
|
||||
skipCanvas: process.env.OPENCLAW_SKIP_CANVAS_HOST,
|
||||
token: process.env.OPENCLAW_GATEWAY_TOKEN,
|
||||
password: process.env.OPENCLAW_GATEWAY_PASSWORD,
|
||||
};
|
||||
|
||||
process.env.OPENCLAW_SKIP_CHANNELS = "1";
|
||||
process.env.OPENCLAW_SKIP_GMAIL_WATCHER = "1";
|
||||
process.env.OPENCLAW_SKIP_CRON = "1";
|
||||
process.env.OPENCLAW_SKIP_CANVAS_HOST = "1";
|
||||
delete process.env.OPENCLAW_GATEWAY_TOKEN;
|
||||
delete process.env.OPENCLAW_GATEWAY_PASSWORD;
|
||||
|
||||
const tempHome = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-onboard-token-"));
|
||||
process.env.HOME = tempHome;
|
||||
process.env.OPENCLAW_STATE_DIR = tempHome;
|
||||
process.env.OPENCLAW_CONFIG_PATH = path.join(tempHome, "openclaw.json");
|
||||
vi.resetModules();
|
||||
|
||||
const cleanToken = `sk-ant-oat01-${"a".repeat(80)}`;
|
||||
const token = `${cleanToken.slice(0, 30)}\r${cleanToken.slice(30)}`;
|
||||
|
||||
const runtime = {
|
||||
log: () => {},
|
||||
error: (msg: string) => {
|
||||
throw new Error(msg);
|
||||
},
|
||||
exit: (code: number) => {
|
||||
throw new Error(`exit:${code}`);
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
const { runNonInteractiveOnboarding } = await import("./onboard-non-interactive.js");
|
||||
await runNonInteractiveOnboarding(
|
||||
{
|
||||
nonInteractive: true,
|
||||
authChoice: "token",
|
||||
tokenProvider: "anthropic",
|
||||
token,
|
||||
tokenProfileId: "anthropic:default",
|
||||
skipHealth: true,
|
||||
skipChannels: true,
|
||||
json: true,
|
||||
},
|
||||
runtime,
|
||||
);
|
||||
|
||||
const { CONFIG_PATH } = await import("../config/config.js");
|
||||
const cfg = JSON.parse(await fs.readFile(CONFIG_PATH, "utf8")) as {
|
||||
auth?: {
|
||||
profiles?: Record<string, { provider?: string; mode?: string }>;
|
||||
};
|
||||
};
|
||||
|
||||
expect(cfg.auth?.profiles?.["anthropic:default"]?.provider).toBe("anthropic");
|
||||
expect(cfg.auth?.profiles?.["anthropic:default"]?.mode).toBe("token");
|
||||
|
||||
const { ensureAuthProfileStore } = await import("../agents/auth-profiles.js");
|
||||
const store = ensureAuthProfileStore();
|
||||
const profile = store.profiles["anthropic:default"];
|
||||
expect(profile?.type).toBe("token");
|
||||
if (profile?.type === "token") {
|
||||
expect(profile.provider).toBe("anthropic");
|
||||
expect(profile.token).toBe(cleanToken);
|
||||
}
|
||||
} finally {
|
||||
await fs.rm(tempHome, { recursive: true, force: true });
|
||||
process.env.HOME = prev.home;
|
||||
process.env.OPENCLAW_STATE_DIR = prev.stateDir;
|
||||
process.env.OPENCLAW_CONFIG_PATH = prev.configPath;
|
||||
process.env.OPENCLAW_SKIP_CHANNELS = prev.skipChannels;
|
||||
process.env.OPENCLAW_SKIP_GMAIL_WATCHER = prev.skipGmail;
|
||||
process.env.OPENCLAW_SKIP_CRON = prev.skipCron;
|
||||
process.env.OPENCLAW_SKIP_CANVAS_HOST = prev.skipCanvas;
|
||||
process.env.OPENCLAW_GATEWAY_TOKEN = prev.token;
|
||||
process.env.OPENCLAW_GATEWAY_PASSWORD = prev.password;
|
||||
}
|
||||
}, 60_000);
|
||||
});
|
||||
91
src/commands/onboard-non-interactive.xai.test.ts
Normal file
91
src/commands/onboard-non-interactive.xai.test.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
describe("onboard (non-interactive): xAI", () => {
|
||||
it("stores the API key and configures the default model", async () => {
|
||||
const prev = {
|
||||
home: process.env.HOME,
|
||||
stateDir: process.env.OPENCLAW_STATE_DIR,
|
||||
configPath: process.env.OPENCLAW_CONFIG_PATH,
|
||||
skipChannels: process.env.OPENCLAW_SKIP_CHANNELS,
|
||||
skipGmail: process.env.OPENCLAW_SKIP_GMAIL_WATCHER,
|
||||
skipCron: process.env.OPENCLAW_SKIP_CRON,
|
||||
skipCanvas: process.env.OPENCLAW_SKIP_CANVAS_HOST,
|
||||
token: process.env.OPENCLAW_GATEWAY_TOKEN,
|
||||
password: process.env.OPENCLAW_GATEWAY_PASSWORD,
|
||||
};
|
||||
|
||||
process.env.OPENCLAW_SKIP_CHANNELS = "1";
|
||||
process.env.OPENCLAW_SKIP_GMAIL_WATCHER = "1";
|
||||
process.env.OPENCLAW_SKIP_CRON = "1";
|
||||
process.env.OPENCLAW_SKIP_CANVAS_HOST = "1";
|
||||
delete process.env.OPENCLAW_GATEWAY_TOKEN;
|
||||
delete process.env.OPENCLAW_GATEWAY_PASSWORD;
|
||||
|
||||
const tempHome = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-onboard-xai-"));
|
||||
process.env.HOME = tempHome;
|
||||
process.env.OPENCLAW_STATE_DIR = tempHome;
|
||||
process.env.OPENCLAW_CONFIG_PATH = path.join(tempHome, "openclaw.json");
|
||||
vi.resetModules();
|
||||
|
||||
const runtime = {
|
||||
log: () => {},
|
||||
error: (msg: string) => {
|
||||
throw new Error(msg);
|
||||
},
|
||||
exit: (code: number) => {
|
||||
throw new Error(`exit:${code}`);
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
const { runNonInteractiveOnboarding } = await import("./onboard-non-interactive.js");
|
||||
await runNonInteractiveOnboarding(
|
||||
{
|
||||
nonInteractive: true,
|
||||
authChoice: "xai-api-key",
|
||||
xaiApiKey: "xai-test-\r\nkey",
|
||||
skipHealth: true,
|
||||
skipChannels: true,
|
||||
skipSkills: true,
|
||||
json: true,
|
||||
},
|
||||
runtime,
|
||||
);
|
||||
|
||||
const { CONFIG_PATH } = await import("../config/config.js");
|
||||
const cfg = JSON.parse(await fs.readFile(CONFIG_PATH, "utf8")) as {
|
||||
auth?: {
|
||||
profiles?: Record<string, { provider?: string; mode?: string }>;
|
||||
};
|
||||
agents?: { defaults?: { model?: { primary?: string } } };
|
||||
};
|
||||
|
||||
expect(cfg.auth?.profiles?.["xai:default"]?.provider).toBe("xai");
|
||||
expect(cfg.auth?.profiles?.["xai:default"]?.mode).toBe("api_key");
|
||||
expect(cfg.agents?.defaults?.model?.primary).toBe("xai/grok-4");
|
||||
|
||||
const { ensureAuthProfileStore } = await import("../agents/auth-profiles.js");
|
||||
const store = ensureAuthProfileStore();
|
||||
const profile = store.profiles["xai:default"];
|
||||
expect(profile?.type).toBe("api_key");
|
||||
if (profile?.type === "api_key") {
|
||||
expect(profile.provider).toBe("xai");
|
||||
expect(profile.key).toBe("xai-test-key");
|
||||
}
|
||||
} finally {
|
||||
await fs.rm(tempHome, { recursive: true, force: true });
|
||||
process.env.HOME = prev.home;
|
||||
process.env.OPENCLAW_STATE_DIR = prev.stateDir;
|
||||
process.env.OPENCLAW_CONFIG_PATH = prev.configPath;
|
||||
process.env.OPENCLAW_SKIP_CHANNELS = prev.skipChannels;
|
||||
process.env.OPENCLAW_SKIP_GMAIL_WATCHER = prev.skipGmail;
|
||||
process.env.OPENCLAW_SKIP_CRON = prev.skipCron;
|
||||
process.env.OPENCLAW_SKIP_CANVAS_HOST = prev.skipCanvas;
|
||||
process.env.OPENCLAW_GATEWAY_TOKEN = prev.token;
|
||||
process.env.OPENCLAW_GATEWAY_PASSWORD = prev.password;
|
||||
}
|
||||
}, 60_000);
|
||||
});
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
resolveAuthProfileOrder,
|
||||
} from "../../agents/auth-profiles.js";
|
||||
import { resolveEnvApiKey } from "../../agents/model-auth.js";
|
||||
import { normalizeOptionalSecretInput } from "../../utils/normalize-secret-input.js";
|
||||
|
||||
export type NonInteractiveApiKeySource = "flag" | "env" | "profile";
|
||||
|
||||
@@ -48,7 +49,7 @@ export async function resolveNonInteractiveApiKey(params: {
|
||||
agentDir?: string;
|
||||
allowProfile?: boolean;
|
||||
}): Promise<{ key: string; source: NonInteractiveApiKeySource } | null> {
|
||||
const flagKey = params.flagValue?.trim();
|
||||
const flagKey = normalizeOptionalSecretInput(params.flagValue);
|
||||
if (flagKey) {
|
||||
return { key: flagKey, source: "flag" };
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { normalizeProviderId } from "../../../agents/model-selection.js";
|
||||
import { parseDurationMs } from "../../../cli/parse-duration.js";
|
||||
import { upsertSharedEnvVar } from "../../../infra/env-file.js";
|
||||
import { shortenHomePath } from "../../../utils.js";
|
||||
import { normalizeSecretInput } from "../../../utils/normalize-secret-input.js";
|
||||
import { buildTokenProfileId, validateAnthropicSetupToken } from "../../auth-token.js";
|
||||
import { applyGoogleGeminiModelDefault } from "../../google-gemini-model-default.js";
|
||||
import {
|
||||
@@ -111,7 +112,7 @@ export async function applyNonInteractiveAuthChoice(params: {
|
||||
runtime.exit(1);
|
||||
return null;
|
||||
}
|
||||
const tokenRaw = opts.token?.trim();
|
||||
const tokenRaw = normalizeSecretInput(opts.token);
|
||||
if (!tokenRaw) {
|
||||
runtime.error("Missing --token for --auth-choice token.");
|
||||
runtime.exit(1);
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { WizardPrompter } from "../wizard/prompts.js";
|
||||
import { installSkill } from "../agents/skills-install.js";
|
||||
import { buildWorkspaceSkillStatus } from "../agents/skills-status.js";
|
||||
import { formatCliCommand } from "../cli/command-format.js";
|
||||
import { normalizeSecretInput } from "../utils/normalize-secret-input.js";
|
||||
import { detectBinary, resolveNodeManagerOptions } from "./onboard-helpers.js";
|
||||
|
||||
function summarizeInstallFailure(message: string): string | undefined {
|
||||
@@ -198,7 +199,7 @@ export async function setupSkills(
|
||||
validate: (value) => (value?.trim() ? undefined : "Required"),
|
||||
}),
|
||||
);
|
||||
next = upsertSkillEntry(next, skill.skillKey, { apiKey: apiKey.trim() });
|
||||
next = upsertSkillEntry(next, skill.skillKey, { apiKey: normalizeSecretInput(apiKey) });
|
||||
}
|
||||
|
||||
return next;
|
||||
|
||||
Reference in New Issue
Block a user