80 lines
3 KiB
TypeScript
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.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.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.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.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();
|
|
});
|
|
});
|