Embed the Composio key in the app bundle instead of shipping cred.txt

Root cause of the recurring 401: a revoked key was hardcoded in
startup-recovery.ts and onboarding-service.ts and reseeded into
composio_settings on every launch, and the interim fix shipped cred.txt
as a visible file under resources/.

- electron.vite.config.ts reads the gitignored cred.txt at build time
  and injects the key into the main-process bundle as
  __COMPOSIO_API_KEY__; no credential file is packaged and the key is
  not user-visible or configurable. The renderer bundle never sees it.
- Delete both hardcoded-key seeders; migration 046 purges stored
  api_key and never-read toolkits rows while keeping entity_id and
  auth_config state.
- Stop shipping composio_settings in the renderer snapshot.
This commit is contained in:
ZacharyZhang-NY committed 2026-07-07 01:40:19 -04:00
1 parent 2d43950123
commit a2de132083
16 files changed
+105 -181

No files matched your search

+10 -69
View File
@@ -1,78 +1,19 @@
import { randomUUID } from "node:crypto";
import { mkdirSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { describe, expect, test } from "vitest";
import { DatabaseConnection } from "../../src/main/database/connection";
import { OrganizationService } from "../../src/main/services/organization-service";
import { resolveComposioApiKey } from "../../src/main/services/composio/composio-credentials";
interface Harness {
connection: DatabaseConnection;
orgId: string;
credDir: string;
}
function harness(): Harness {
const connection = new DatabaseConnection(join(tmpdir(), `ac-cred-${randomUUID()}.db`));
const organization = new OrganizationService(connection.db);
let orgId = "";
connection.transaction(() => {
orgId = organization.createOrganization({ organizationName: "OrbitOps", employeeName: "Yang", mission: "m", goal: "g" });
});
const credDir = join(tmpdir(), `ac-cred-dir-${randomUUID()}`);
mkdirSync(credDir, { recursive: true });
return { connection, orgId, credDir };
}
function storedKey(h: Harness): string | null {
const row = h.connection.db.prepare(
"select value from composio_settings where org_id = ? and key = 'api_key'",
).get(h.orgId) as { value: string } | undefined;
return row?.value ?? null;
}
function writeCred(h: Harness, key: string): void {
writeFileSync(join(h.credDir, "cred.txt"), `Composio apikey: ${key}\nProject id: pr_x\n`, "utf8");
}
import { composioApiKey, normalizeComposioApiKey } from "../../src/main/services/composio/composio-credentials";
describe("composio credential resolution", () => {
test("seeds the database from the credential file", () => {
const h = harness();
writeCred(h, "ak_first");
expect(resolveComposioApiKey(h.connection.db, h.orgId, [h.credDir])).toBe("ak_first");
expect(storedKey(h)).toBe("ak_first");
test("trims the embedded key", () => {
expect(normalizeComposioApiKey(" ak_live ")).toBe("ak_live");
});
test("a rotated credential file replaces the stale stored key", () => {
const h = harness();
writeCred(h, "ak_old");
resolveComposioApiKey(h.connection.db, h.orgId, [h.credDir]);
writeCred(h, "ak_rotated");
expect(resolveComposioApiKey(h.connection.db, h.orgId, [h.credDir])).toBe("ak_rotated");
expect(storedKey(h)).toBe("ak_rotated");
test("a blank key resolves to null so callers report the integration as unconfigured", () => {
expect(normalizeComposioApiKey("")).toBeNull();
expect(normalizeComposioApiKey(" ")).toBeNull();
});
test("finds the credential file in a later directory when the first has none", () => {
const h = harness();
writeCred(h, "ak_resources");
const emptyDir = join(tmpdir(), `ac-cred-empty-${randomUUID()}`);
mkdirSync(emptyDir, { recursive: true });
expect(resolveComposioApiKey(h.connection.db, h.orgId, [emptyDir, h.credDir])).toBe("ak_resources");
expect(storedKey(h)).toBe("ak_resources");
});
test("without a credential file the stored key stands", () => {
const h = harness();
const now = new Date().toISOString();
h.connection.db.prepare(
"insert into composio_settings(org_id, key, value, updated_at) values (?, 'api_key', 'ak_settings', ?)",
).run(h.orgId, now);
expect(resolveComposioApiKey(h.connection.db, h.orgId, [h.credDir])).toBe("ak_settings");
});
test("no file and no stored key yields null", () => {
const h = harness();
expect(resolveComposioApiKey(h.connection.db, h.orgId, [h.credDir])).toBeNull();
test("without the build-time injection the key is null", () => {
// Vitest runs without the electron-vite define — the same shape as a
// build from a checkout that has no cred.txt.
expect(composioApiKey()).toBeNull();
});
});