Let a rotated cred.txt key win over the stale stored one
The credential file only seeded the database when it was empty, so once a key was stored, rotating it in cred.txt changed nothing and Composio kept rejecting the dead stored key with 401 - while the file held a freshly generated, verified-working key. The file is now the source of truth whenever it exists: a differing key replaces the stored one on resolution, and packaged installs without the file keep managing the key in Settings. Four resolution contract tests pin seed, rotation, settings-only, and absent cases; the Connectors error line truncates instead of wrapping the raw API response across the page.
This commit is contained in:
1 parent
62e663ed90
commit
8fef89a22e
4 files changed
+100
-171
No files matched your search
@@ -3,30 +3,35 @@ import { join } from "node:path";
|
||||
import type { DatabaseSync } from "node:sqlite";
|
||||
|
||||
/**
|
||||
* The org's Composio API key. When the database has none, a developer
|
||||
* credential file next to the app (cred.txt, first line "Composio apikey:
|
||||
* ak_...", gitignored) seeds it once for this org — the product owner's
|
||||
* local path. Packaged installs configure the key in Settings instead; a
|
||||
* missing file simply yields null.
|
||||
* The org's Composio API key. A developer credential file next to the app
|
||||
* (cred.txt, first line "Composio apikey: ak_...", gitignored) is the source
|
||||
* of truth whenever it exists: rotating the key in the file takes effect on
|
||||
* the next resolution, replacing whatever the database held. Packaged
|
||||
* installs have no credential file and manage the key in Settings instead.
|
||||
*/
|
||||
export function resolveComposioApiKey(db: DatabaseSync, orgId: string, credentialFileDir: string = process.cwd()): string | null {
|
||||
const stored = db.prepare(
|
||||
"select value from composio_settings where org_id = ? and key = 'api_key' limit 1",
|
||||
).get(orgId) as { value: string } | undefined;
|
||||
if (stored?.value) return stored.value;
|
||||
|
||||
const path = join(credentialFileDir, "cred.txt");
|
||||
if (!existsSync(path)) return null;
|
||||
const firstLine = readFileSync(path, "utf8").split("\n")[0] ?? "";
|
||||
const key = firstLine.split(":").slice(1).join(":").trim();
|
||||
if (!key) return null;
|
||||
const fileKey = readCredentialFile(credentialFileDir);
|
||||
if (!fileKey) return stored?.value ?? null;
|
||||
if (stored?.value === fileKey) return fileKey;
|
||||
|
||||
const now = new Date().toISOString();
|
||||
const upsert = db.prepare(`
|
||||
insert into composio_settings(org_id, key, value, updated_at) values (?, ?, ?, ?)
|
||||
on conflict(org_id, key) do update set value = excluded.value, updated_at = excluded.updated_at
|
||||
`);
|
||||
upsert.run(orgId, "api_key", key, now);
|
||||
upsert.run(orgId, "api_key", fileKey, now);
|
||||
upsert.run(orgId, "entity_id", orgId, now);
|
||||
return key;
|
||||
return fileKey;
|
||||
}
|
||||
|
||||
function readCredentialFile(dir: string): string | null {
|
||||
const path = join(dir, "cred.txt");
|
||||
if (!existsSync(path)) return null;
|
||||
const firstLine = readFileSync(path, "utf8").split("\n")[0] ?? "";
|
||||
const key = firstLine.split(":").slice(1).join(":").trim();
|
||||
return key || null;
|
||||
}
|
||||
Reference in new issue
Block a user