import Database from 'better-sqlite3'; import { mkdtemp, rm } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { afterEach, describe, expect, it } from 'vitest'; import { openDatabase } from '../../infrastructure/database/database.js'; import { migrateDatabase } from '../../infrastructure/database/migrations.js'; import type { SecretKey, SecretStore } from '../../infrastructure/secrets/secret-store.js'; import { ActivationError, activatePendingSecret } from './activate-pending-secret.js'; const secret = 'activation-secret-never-in-sqlite'; const databases: Database.Database[] = []; afterEach(() => { for (const db of databases.splice(0)) db.close(); }); function pendingDatabase(): Database.Database { const db = new Database(':memory:'); db.pragma('foreign_keys = ON'); migrateDatabase(db); const now = '2026-07-16T00:00:00.000Z'; db.prepare( 'INSERT INTO instances (id,name,base_url,auth_mode,enabled,config_revision,created_at,updated_at) VALUES (?,?,?,?,?,?,?,?)', ).run('alpha', 'Alpha', 'https://203.0.113.8', 'none', 1, 3, now, now); db.prepare('INSERT INTO app_settings VALUES (?,?,?,?)').run( 'legacy-import.instance.alpha', JSON.stringify({ source: 'legacy-config', secretPending: true, requestedAuthMode: 'password', secretPurpose: 'instance-password', }), now, now, ); databases.push(db); return db; } class FakeStore implements SecretStore { readonly provider = 'macos-keychain'; readonly sets: { key: SecretKey; value: string }[] = []; readonly deletes: string[] = []; setError?: Error; deleteError?: Error; reference = 'keychain://multi-simadmin/b3BhcXVl'; async set(key: SecretKey, value: string): Promise { this.sets.push({ key, value }); if (this.setError) throw this.setError; return key.slot ? `${this.reference}-${key.slot}` : this.reference; } async get(): Promise { return undefined; } async delete(reference: string): Promise { this.deletes.push(reference); if (this.deleteError) throw this.deleteError; return true; } } function allText(db: Database.Database): string { const tables = db .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'") .all() as { name: string }[]; const rows: unknown[] = []; for (const { name } of tables) { const columns = ( db.prepare(`PRAGMA table_info("${name}")`).all() as { name: string; type: string }[] ) .filter(({ type }) => type.toUpperCase().includes('TEXT')) .map(({ name: column }) => `"${column}"`); if (columns.length) rows.push(...db.prepare(`SELECT ${columns.join(',')} FROM "${name}"`).all()); } return JSON.stringify(rows); } function state(db: Database.Database): Uint8Array { return db.serialize(); } describe('activatePendingSecret', () => { it('writes keychain first then atomically activates password auth and clears pending metadata', async () => { const db = pendingDatabase(); const store = new FakeStore(); const result = await activatePendingSecret({ db, store, instanceId: 'alpha', secret }); expect(store.sets).toHaveLength(1); expect(store.sets[0]).toEqual({ key: { instanceId: 'alpha', purpose: 'instance-password', slot: expect.stringMatching(/^[A-Za-z0-9_-]+$/), }, value: secret, }); const storedReference = `${store.reference}-${store.sets[0]?.key.slot}`; expect(result).toEqual({ instanceId: 'alpha', referenceId: 'legacy-secret:alpha:instance-password', externalReference: storedReference, configRevision: 4, }); expect( db.prepare('SELECT auth_mode,config_revision FROM instances WHERE id=?').get('alpha'), ).toEqual({ auth_mode: 'password', config_revision: 4, }); expect( db .prepare('SELECT id,instance_id,purpose,provider,external_reference FROM secret_references') .get(), ).toEqual({ id: 'legacy-secret:alpha:instance-password', instance_id: 'alpha', purpose: 'instance-password', provider: 'macos-keychain', external_reference: storedReference, }); const metadata = JSON.parse( ( db .prepare('SELECT value_json FROM app_settings WHERE key=?') .get('legacy-import.instance.alpha') as { value_json: string } ).value_json, ); expect(metadata).toMatchObject({ secretPending: false, requestedAuthMode: 'password' }); expect(JSON.stringify(metadata)).not.toContain(secret); expect(allText(db)).not.toContain(secret); }); it('rolls back every DB write and compensates keychain when the transaction fails', async () => { const db = pendingDatabase(); const store = new FakeStore(); const before = state(db); db.exec( "CREATE TRIGGER fail_activation BEFORE INSERT ON secret_references BEGIN SELECT RAISE(ABORT, 'forced'); END", ); const withTrigger = state(db); await expect( activatePendingSecret({ db, store, instanceId: 'alpha', secret }), ).rejects.toMatchObject({ code: 'DATABASE_FAILED', }); expect(store.deletes).toHaveLength(1); expect(store.deletes[0]).toBe(`${store.reference}-${store.sets[0]?.key.slot}`); expect(state(db)).toEqual(withTrigger); expect(withTrigger).not.toEqual(before); }); it('returns a stable compensation error without leaking secret when cleanup fails', async () => { const db = pendingDatabase(); db.exec( "CREATE TRIGGER fail_activation BEFORE INSERT ON secret_references BEGIN SELECT RAISE(ABORT, 'forced'); END", ); const store = new FakeStore(); store.deleteError = new Error(`malicious ${secret}`); const error = await activatePendingSecret({ db, store, instanceId: 'alpha', secret }).catch( (reason: unknown) => reason, ); expect(error).toBeInstanceOf(ActivationError); expect(error).toMatchObject({ code: 'COMPENSATION_FAILED' }); expect( JSON.stringify({ name: (error as Error).name, message: (error as Error).message }), ).not.toContain(secret); }); it('does not touch DB when store set fails', async () => { const db = pendingDatabase(); const before = state(db); const store = new FakeStore(); store.setError = new Error(`malicious ${secret}`); const error = await activatePendingSecret({ db, store, instanceId: 'alpha', secret }).catch( (reason: unknown) => reason, ); expect(error).toMatchObject({ code: 'SECRET_STORE_FAILED' }); expect(state(db)).toEqual(before); expect( JSON.stringify({ name: (error as Error).name, message: (error as Error).message }), ).not.toContain(secret); }); it('rejects duplicate activation before store access and leaves DB unchanged', async () => { const db = pendingDatabase(); const firstStore = new FakeStore(); await activatePendingSecret({ db, store: firstStore, instanceId: 'alpha', secret }); const before = state(db); const secondStore = new FakeStore(); await expect( activatePendingSecret({ db, store: secondStore, instanceId: 'alpha', secret }), ).rejects.toMatchObject({ code: 'ALREADY_ACTIVE' }); expect(secondStore.sets).toHaveLength(0); expect(state(db)).toEqual(before); }); it('serializes concurrent activation so only one unique keychain item is created', async () => { const db = pendingDatabase(); const store = new FakeStore(); const results = await Promise.allSettled([ activatePendingSecret({ db, store, instanceId: 'alpha', secret: `${secret}-one` }), activatePendingSecret({ db, store, instanceId: 'alpha', secret: `${secret}-two` }), ]); expect(results.filter(({ status }) => status === 'fulfilled')).toHaveLength(1); expect(results.filter(({ status }) => status === 'rejected')).toHaveLength(1); expect(store.sets).toHaveLength(1); expect(store.sets[0]?.key.slot).toMatch(/^[A-Za-z0-9_-]+$/); expect(store.deletes).toHaveLength(0); const saved = db.prepare('SELECT external_reference FROM secret_references').get() as { external_reference: string; }; expect(saved.external_reference).toContain(store.sets[0]?.key.slot); }); it('serializes activation across two managed connections to the same physical database', async () => { const directory = await mkdtemp(join(tmpdir(), 'secret-activation-')); const path = join(directory, 'state.sqlite'); const first = openDatabase(path); const second = openDatabase(path); migrateDatabase(first); const now = '2026-07-16T00:00:00.000Z'; first .prepare( 'INSERT INTO instances (id,name,base_url,auth_mode,enabled,config_revision,created_at,updated_at) VALUES (?,?,?,?,?,?,?,?)', ) .run('alpha', 'Alpha', 'https://203.0.113.8', 'none', 1, 3, now, now); first .prepare('INSERT INTO app_settings VALUES (?,?,?,?)') .run( 'legacy-import.instance.alpha', JSON.stringify({ secretPending: true, requestedAuthMode: 'password' }), now, now, ); const store = new FakeStore(); try { const results = await Promise.allSettled([ activatePendingSecret({ db: first, store, instanceId: 'alpha', secret: `${secret}-one` }), activatePendingSecret({ db: second, store, instanceId: 'alpha', secret: `${secret}-two` }), ]); expect(results.filter(({ status }) => status === 'fulfilled')).toHaveLength(1); expect(results.filter(({ status }) => status === 'rejected')).toHaveLength(1); expect(store.sets).toHaveLength(1); expect(store.deletes).toHaveLength(0); } finally { second.close(); first.close(); await rm(directory, { recursive: true, force: true }); } }); it('rejects empty secrets and invalid pending metadata before store access', async () => { const db = pendingDatabase(); const store = new FakeStore(); await expect( activatePendingSecret({ db, store, instanceId: 'alpha', secret: '' }), ).rejects.toMatchObject({ code: 'EMPTY_SECRET' }); db.prepare('UPDATE app_settings SET value_json=?').run( JSON.stringify({ secretPending: false, requestedAuthMode: 'password' }), ); await expect( activatePendingSecret({ db, store, instanceId: 'alpha', secret }), ).rejects.toMatchObject({ code: 'NOT_PENDING' }); expect(store.sets).toHaveLength(0); }); });