Files
openclaw/scripts/e2e/lib/auth-profile-store-assertions.mjs
Vincent Koc 1c80cf86cf fix(release): reuse validation evidence after tooling updates (#130850)
* fix(release): trust ancestor validation evidence

* fix(release): read candidate auth schema
2026-08-27 17:32:58 +08:00

161 lines
5.0 KiB
JavaScript

// Shared auth profile store assertions for install/onboard E2E proof.
import fs from "node:fs";
import path from "node:path";
import { DatabaseSync } from "node:sqlite";
import { isRecord } from "../../lib/record-shared.mjs";
const AUTH_PROFILE_MACHINE_STATE_SCHEMA_VERSION = 13;
export function readSharedAuthProfileStoreText(stateDir) {
const dbPath = path.join(stateDir, "state", "openclaw.sqlite");
if (!fs.existsSync(dbPath)) {
return "";
}
let db;
try {
db = new DatabaseSync(dbPath, { readOnly: true });
const schemaVersion = db.prepare("PRAGMA user_version").get()?.user_version;
if (!Number.isInteger(schemaVersion)) {
throw new Error(`invalid state schema version ${String(schemaVersion)}`);
}
// Release candidates own their persisted schema. Never let a retired row
// mask a missing canonical row once the v13 fold has occurred.
const storage =
schemaVersion >= AUTH_PROFILE_MACHINE_STATE_SCHEMA_VERSION
? {
column: "value_json",
key: "authProfiles.store",
query: "SELECT value_json FROM config_machine_state WHERE state_key = ?",
table: "config_machine_state",
}
: {
column: "store_json",
key: "shared",
query: "SELECT store_json FROM auth_profile_stores WHERE store_key = ?",
table: "auth_profile_stores",
};
const schema = db
.prepare("SELECT type FROM sqlite_schema WHERE name = ? LIMIT 1")
.get(storage.table);
if (!schema) {
return "";
}
if (schema.type !== "table") {
throw new Error(`${storage.table} is ${String(schema.type)}, not a table`);
}
const row = db.prepare(storage.query).get(storage.key);
const value = row?.[storage.column];
return typeof value === "string" ? value : "";
} catch (error) {
const detail = error instanceof Error ? error.message : String(error);
throw new Error(`could not read the shared auth profile store: ${detail}`, {
cause: error,
});
} finally {
db?.close();
}
}
export function assertNoLegacyPrimaryAuthRows(stateDir) {
const dbPath = path.join(stateDir, "agents", "main", "agent", "openclaw-agent.sqlite");
if (!fs.existsSync(dbPath)) {
return;
}
let db;
try {
db = new DatabaseSync(dbPath, { readOnly: true });
const legacyRows = [
{
table: "auth_profile_store",
query: "SELECT 1 AS present FROM auth_profile_store WHERE store_key = ? LIMIT 1",
},
{
table: "auth_profile_state",
query: "SELECT 1 AS present FROM auth_profile_state WHERE state_key = ? LIMIT 1",
},
];
for (const entry of legacyRows) {
const schema = db
.prepare("SELECT type FROM sqlite_schema WHERE name = ? LIMIT 1")
.get(entry.table);
if (!schema) {
continue;
}
if (schema.type !== "table") {
throw new Error(`${entry.table} is ${String(schema.type)}, not a table`);
}
if (db.prepare(entry.query).get("primary")) {
throw new Error(`onboard preserved a retired primary row in ${entry.table}`);
}
}
} catch (error) {
if (error instanceof Error && error.message.startsWith("onboard preserved a retired")) {
throw error;
}
const detail = error instanceof Error ? error.message : String(error);
throw new Error(`could not validate the main-agent auth database: ${detail}`, {
cause: error,
});
} finally {
db?.close();
}
}
function hasExpectedOpenAiEnvRef(profile) {
if (!isRecord(profile)) {
return false;
}
const keyRef = profile.keyRef;
return (
profile.type === "api_key" &&
profile.provider === "openai" &&
!Object.hasOwn(profile, "key") &&
isRecord(keyRef) &&
keyRef.source === "env" &&
keyRef.provider === "default" &&
keyRef.id === "OPENAI_API_KEY"
);
}
function hasInlineOpenAiKey(profile) {
return (
isRecord(profile) &&
profile.type === "api_key" &&
profile.provider === "openai" &&
Object.hasOwn(profile, "key")
);
}
export function assertOpenAiEnvAuthProfileStore(storeJson, options = {}) {
const missingMessage = options.missingMessage ?? "auth profile store was not persisted";
const envRefMessage =
options.envRefMessage ?? "auth profile did not persist OPENAI_API_KEY env ref";
const rawKeyMessage = options.rawKeyMessage ?? "auth profile persisted an inline OpenAI key";
const rawKeyNeedle = options.rawKeyNeedle;
if (!storeJson) {
throw new Error(missingMessage);
}
if (rawKeyNeedle && storeJson.includes(rawKeyNeedle)) {
throw new Error(rawKeyMessage);
}
let store;
try {
store = JSON.parse(storeJson);
} catch {
throw new Error(envRefMessage);
}
const profiles = isRecord(store) && isRecord(store.profiles) ? store.profiles : null;
if (!profiles) {
throw new Error(envRefMessage);
}
const profileValues = Object.values(profiles);
if (profileValues.some(hasInlineOpenAiKey)) {
throw new Error(rawKeyMessage);
}
if (!profileValues.some(hasExpectedOpenAiEnvRef)) {
throw new Error(envRefMessage);
}
}