randomayzer/tests/token-vault.test.ts

80 lines
3 KiB
TypeScript

import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { AesGcmTokenVault } from '../src/lib/auth/token-vault';
describe('Phase 2.2.1 Token Vault Security & Fail-Fast Policies', () => {
const originalEnv = process.env;
beforeEach(() => {
process.env = { ...originalEnv };
});
afterEach(() => {
process.env = originalEnv;
});
it('fails fast with fatal configuration error in production when TOKEN_ENCRYPTION_KEY is missing', () => {
(process.env as any).NODE_ENV = 'production';
delete process.env.TOKEN_ENCRYPTION_KEY;
expect(() => new AesGcmTokenVault()).toThrow(/TOKEN_ENCRYPTION_KEY environment variable is strictly required in production/i);
});
it('fails fast in production when TOKEN_ENCRYPTION_KEY is shorter than 32 characters', () => {
(process.env as any).NODE_ENV = 'production';
process.env.TOKEN_ENCRYPTION_KEY = 'too-short-key';
expect(() => new AesGcmTokenVault()).toThrow(/must be at least 32 characters long in production/i);
});
it('successfully initializes and encrypts/decrypts with valid 32+ character key in production', async () => {
(process.env as any).NODE_ENV = 'production';
process.env.TOKEN_ENCRYPTION_KEY = 'a-super-secret-production-encryption-key-32chars!';
const vault = new AesGcmTokenVault();
const token = 'vk1.a.prod_access_token_1234567890abcdef';
const encrypted = await vault.encrypt(token);
expect(encrypted).not.toBe(token);
expect(encrypted).not.toContain(token);
const decrypted = await vault.decrypt(encrypted);
expect(decrypted).toBe(token);
});
it('allows explicit dev/test key when in development or test environment', async () => {
(process.env as any).NODE_ENV = 'test';
delete process.env.TOKEN_ENCRYPTION_KEY;
const vault = new AesGcmTokenVault();
const token = 'vk1.a.dev_token_sample';
const encrypted = await vault.encrypt(token);
const decrypted = await vault.decrypt(encrypted);
expect(decrypted).toBe(token);
});
it('fails decryption with wrong key', async () => {
const vault1 = new AesGcmTokenVault('key-number-one-with-sufficient-entropy-32b!');
const vault2 = new AesGcmTokenVault('key-number-two-with-sufficient-entropy-32b!');
const token = 'vk1.a.confidential_user_token_123';
const encrypted = await vault1.encrypt(token);
await expect(vault2.decrypt(encrypted)).rejects.toThrow();
});
it('fails authentication on tampered ciphertext or tag', async () => {
const vault = new AesGcmTokenVault('valid-key-for-gcm-integrity-testing-32b!');
const token = 'vk1.a.token_to_tamper_with';
const encrypted = await vault.encrypt(token);
const [iv, tag, cipher] = encrypted.split(':');
// Tamper tag
const tamperedTag = tag.slice(0, -2) + 'ff';
await expect(vault.decrypt(`${iv}:${tamperedTag}:${cipher}`)).rejects.toThrow();
// Tamper ciphertext
const tamperedCipher = cipher.slice(0, -2) + '00';
await expect(vault.decrypt(`${iv}:${tag}:${tamperedCipher}`)).rejects.toThrow();
});
});