openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
116 lines (115 loc) • 4.71 kB
JavaScript
import { c as isRecord } from "./record-coerce-DItp3I4t.js";
import { a as getNodeSqliteKysely, i as executeSqliteQueryTakeFirstSync, r as executeSqliteQuerySync } from "./kysely-sync-COmh4HWh.js";
import { s as runOpenClawStateWriteTransaction } from "./openclaw-state-db-BRTnL-D8.js";
import { n as normalizeDeviceAuthScopes, t as normalizeDeviceAuthRole } from "./device-auth-C-STNejO.js";
import { c as resetLegacyDeviceAuthPresenceCache } from "./device-auth-store-Bu3kSvQA.js";
import { t as withLegacyMigrationStateLock } from "./state-migrations.lock-C0ywqz7v.js";
import fs from "node:fs";
import path from "node:path";
import { root } from "@openclaw/fs-safe";
//#region src/infra/state-migrations.device-auth.ts
const LEGACY_PATH = "identity/device-auth.json";
/** Detect the retired device-auth store only when an explicit Doctor flow opts in. */
function detectLegacyDeviceAuth(params) {
const sourcePath = path.join(params.stateDir, LEGACY_PATH);
const sourcePresent = fs.existsSync(sourcePath);
return {
sourcePath,
sourcePresent,
hasLegacy: params.doctorOnlyStateMigrations === true && sourcePresent
};
}
function parseStore(value) {
if (!isRecord(value) || value.version !== 1 || typeof value.deviceId !== "string" || !value.deviceId.trim() || !isRecord(value.tokens)) throw new Error("legacy device-auth store is invalid or unsupported");
const entries = Object.entries(value.tokens).flatMap(([rawRole, tokenValue]) => {
const role = normalizeDeviceAuthRole(rawRole);
if (!role || !isRecord(tokenValue) || typeof tokenValue.token !== "string") return [];
return [{
token: tokenValue.token,
role,
scopes: normalizeDeviceAuthScopes(Array.isArray(tokenValue.scopes) ? tokenValue.scopes : void 0),
updatedAtMs: typeof tokenValue.updatedAtMs === "number" && Number.isSafeInteger(tokenValue.updatedAtMs) ? tokenValue.updatedAtMs : 0
}];
});
return {
deviceId: value.deviceId,
entries: [...new Map(entries.map((entry) => [entry.role, entry])).values()]
};
}
function rowIsCanonical(row) {
try {
return Array.isArray(JSON.parse(row.scopes_json)) && Number.isSafeInteger(row.updated_at_ms);
} catch {
return false;
}
}
async function importLegacyStore(params) {
const stateRoot = await root(params.stateDir, {
hardlinks: "reject",
maxBytes: 262144,
symlinks: "reject"
});
const source = await stateRoot.read(LEGACY_PATH, {
hardlinks: "reject",
maxBytes: 262144,
symlinks: "reject"
});
const store = parseStore(JSON.parse(source.buffer.toString("utf8")));
const counts = runOpenClawStateWriteTransaction(({ db }) => {
const stateDb = getNodeSqliteKysely(db);
let imported = 0;
let preserved = 0;
for (const entry of store.entries) {
const query = stateDb.selectFrom("device_auth_tokens").select(["scopes_json", "updated_at_ms"]).where("device_id", "=", store.deviceId).where("role", "=", entry.role);
const existing = executeSqliteQueryTakeFirstSync(db, query);
if (existing && rowIsCanonical(existing)) {
preserved += 1;
continue;
}
executeSqliteQuerySync(db, stateDb.insertInto("device_auth_tokens").values({
device_id: store.deviceId,
role: entry.role,
token: entry.token,
scopes_json: JSON.stringify(entry.scopes),
updated_at_ms: entry.updatedAtMs
}).onConflict((conflict) => conflict.columns(["device_id", "role"]).doUpdateSet({
token: entry.token,
scopes_json: JSON.stringify(entry.scopes),
updated_at_ms: entry.updatedAtMs
})));
if (!executeSqliteQueryTakeFirstSync(db, query)) throw new Error("SQLite verification failed for a device-auth token");
imported += 1;
}
return {
imported,
preserved
};
}, { env: params.env });
await stateRoot.remove(LEGACY_PATH);
resetLegacyDeviceAuthPresenceCache(params.env);
return {
changes: [`Migrated ${counts.imported} device-auth token${counts.imported === 1 ? "" : "s"} to SQLite.`],
warnings: [],
notices: [...counts.preserved > 0 ? [`Preserved ${counts.preserved} canonical SQLite device-auth token${counts.preserved === 1 ? "" : "s"}.`] : [], "Removed retired device-auth JSON after verified SQLite import."]
};
}
/** Import retired device-auth JSON while excluding Gateways that can rewrite it. */
async function migrateLegacyDeviceAuth(params) {
if (!params.detected.hasLegacy) return {
changes: [],
warnings: []
};
return await withLegacyMigrationStateLock({
stateDir: params.stateDir,
env: params.env,
label: "legacy device auth",
releaseLabel: "Device-auth",
errorLabel: "Failed migrating legacy device auth",
run: async (env) => await importLegacyStore({
...params,
env
})
});
}
//#endregion
export { migrateLegacyDeviceAuth as n, detectLegacyDeviceAuth as t };