Move Composio, logo.dev and Antigravity keys into Settings
The three third-party keys were hardcoded or read from the environment. Operators now enter them in Settings > Integrations; they are stored encrypted in the app-wide integration_keys table (migration 058). An agent holding connector grants fails loudly when no Composio key is saved. The goal-review test no longer depends on absolute dates. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
1ea040c0f0
commit
56b0594f91
36 files changed
+430
-100
No files matched your search
@@ -0,0 +1,56 @@
|
||||
import type { DatabaseSync } from "node:sqlite";
|
||||
import {
|
||||
INTEGRATION_KEY_NAMES,
|
||||
type IntegrationKeyName,
|
||||
type IntegrationKeyStatus,
|
||||
} from "../../shared/commands";
|
||||
import type { ProviderSecretVault } from "./auth-broker-service";
|
||||
import { SecretVault } from "./secret-vault";
|
||||
|
||||
/** Operator-entered third-party keys, encrypted. */
|
||||
export class IntegrationKeyStore {
|
||||
constructor(
|
||||
private readonly db: DatabaseSync,
|
||||
private readonly vault: ProviderSecretVault = new SecretVault(),
|
||||
) {}
|
||||
|
||||
read(name: IntegrationKeyName): string | null {
|
||||
const row = this.db.prepare("select ciphertext from integration_keys where name = ?")
|
||||
.get(name) as { ciphertext: Uint8Array } | undefined;
|
||||
return row ? this.vault.decryptSecret(Buffer.from(row.ciphertext)) : null;
|
||||
}
|
||||
|
||||
write(name: IntegrationKeyName, value: string): void {
|
||||
requireKnownName(name);
|
||||
const key = value.trim();
|
||||
if (!key) throw new Error(`The ${name} key cannot be blank.`);
|
||||
this.db.prepare(`
|
||||
insert into integration_keys(name, ciphertext, updated_at) values (?, ?, ?)
|
||||
on conflict(name) do update set ciphertext = excluded.ciphertext, updated_at = excluded.updated_at
|
||||
`).run(name, this.vault.encryptSecret(key), new Date().toISOString());
|
||||
console.info(`[integration-keys] ${name} saved`);
|
||||
}
|
||||
|
||||
clear(name: IntegrationKeyName): void {
|
||||
requireKnownName(name);
|
||||
this.db.prepare("delete from integration_keys where name = ?").run(name);
|
||||
console.info(`[integration-keys] ${name} cleared`);
|
||||
}
|
||||
|
||||
statuses(): IntegrationKeyStatus[] {
|
||||
const rows = this.db.prepare("select name, updated_at from integration_keys")
|
||||
.all() as Array<{ name: string; updated_at: string }>;
|
||||
const saved = new Map(rows.map((row) => [row.name, row.updated_at]));
|
||||
return INTEGRATION_KEY_NAMES.map((name) => ({
|
||||
name,
|
||||
configured: saved.has(name),
|
||||
updatedAt: saved.get(name) ?? null,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
function requireKnownName(name: string): asserts name is IntegrationKeyName {
|
||||
if (!(INTEGRATION_KEY_NAMES as readonly string[]).includes(name)) {
|
||||
throw new Error(`Unknown integration key "${name}".`);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user