ddd628ab6f
IIOS becomes the tenant's integration/credential hub for anything it sends. New IiosProviderCredential (one row per scope+providerType) seals the secret with AES-256-GCM under IIOS_CRED_KEY (secret-crypto.ts); only non-secret displayHints are ever read back. ProviderCredentialService upsert/resolve/status; TwilioSmsProvider resolves the caller's own creds per scope at send time and POSTs to Twilio (fail-closed NOT_CONFIGURED when unset). Registered over the SMS sandbox only when the platform key + store are present. PUT/GET /v1/providers/:type/credentials (scope-fenced, masked reads). 16 new unit tests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
39 lines
1.6 KiB
TypeScript
39 lines
1.6 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import { credKeyFromEnv, sealSecret, openSecret, SECRET_KEY_VERSION } from './secret-crypto';
|
|
|
|
const KEY = Buffer.alloc(32, 7); // deterministic 32-byte key for tests
|
|
|
|
describe('secret-crypto', () => {
|
|
it('round-trips a secret through seal → open', () => {
|
|
const sealed = sealSecret('sk_live_abc123', KEY);
|
|
expect(sealed.keyVersion).toBe(SECRET_KEY_VERSION);
|
|
expect(sealed.cipherText).not.toContain('sk_live_abc123');
|
|
expect(openSecret(sealed, KEY)).toBe('sk_live_abc123');
|
|
});
|
|
|
|
it('produces a distinct ciphertext each time (random IV)', () => {
|
|
const a = sealSecret('same', KEY);
|
|
const b = sealSecret('same', KEY);
|
|
expect(a.cipherText).not.toBe(b.cipherText);
|
|
expect(a.iv).not.toBe(b.iv);
|
|
});
|
|
|
|
it('fails to open with the wrong key', () => {
|
|
const sealed = sealSecret('top-secret', KEY);
|
|
expect(() => openSecret(sealed, Buffer.alloc(32, 9))).toThrow();
|
|
});
|
|
|
|
it('fails to open if the ciphertext is tampered (GCM auth)', () => {
|
|
const sealed = sealSecret('top-secret', KEY);
|
|
const bad = { ...sealed, cipherText: Buffer.from('deadbeef', 'hex').toString('base64') };
|
|
expect(() => openSecret(bad, KEY)).toThrow();
|
|
});
|
|
|
|
it('credKeyFromEnv returns null when unset and rejects a wrong-length key', () => {
|
|
expect(credKeyFromEnv({})).toBeNull();
|
|
expect(() => credKeyFromEnv({ IIOS_CRED_KEY: Buffer.alloc(16).toString('base64') })).toThrow(/32 bytes/);
|
|
const key = credKeyFromEnv({ IIOS_CRED_KEY: KEY.toString('base64') });
|
|
expect(key?.length).toBe(32);
|
|
});
|
|
});
|