mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-18 21:16:47 +00:00
6dda05f672
Eight files in services/ hold no state and touch no database: avatarUrl, conflictResult, demo, passwordPolicy, timezoneService, cookie, and the two crypto helpers. They are imported from 65 places, 62 of them already inside src/nest, so having them live in the legacy layer was pure inertia. They stay plain functions rather than becoming providers. Three importers sit outside the container and would have no way to reach an injectable one: db/migrations.ts runs before the app is built, and middleware/auth.ts and middleware/mfaPolicy.ts run beside it. Wrapping these in DI would buy ceremony and nothing else. mfaCrypto and apiKeyCrypto move together into common/crypto — they read the same ENCRYPTION_KEY, and the directory says so. services/ drops from 44 files to 36, 9,870 lines to 8,493. One trap worth recording: src/app-config and src/nest/app-config both exist. cookie.ts imported '../app-config' from services/, which resolved to the former; after the move the same string silently pointed at the latter, which has no readEnv. It is now explicit about which one it wants. Nothing else changes — no signatures, no behaviour, no tests rewritten beyond their import paths.
80 lines
2.8 KiB
TypeScript
80 lines
2.8 KiB
TypeScript
import { describe, it, expect, vi } from 'vitest';
|
|
|
|
// Inline factory to avoid vi.mock hoisting issue (no imported vars allowed)
|
|
vi.mock('../../../src/config', () => ({
|
|
JWT_SECRET: 'test-jwt-secret-for-trek-testing-only',
|
|
ENCRYPTION_KEY: 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2',
|
|
updateJwtSecret: () => {},
|
|
}));
|
|
|
|
import { encrypt_api_key, decrypt_api_key, maybe_encrypt_api_key } from '../../../src/nest/common/crypto/apiKeyCrypto';
|
|
|
|
describe('apiKeyCrypto', () => {
|
|
const PLAINTEXT_KEY = 'my-secret-api-key-12345';
|
|
const ENC_PREFIX = 'enc:v1:';
|
|
|
|
// SEC-008 — Encrypted API keys not returned in plaintext
|
|
describe('encrypt_api_key', () => {
|
|
it('SEC-008: returns encrypted string with enc:v1: prefix', () => {
|
|
const encrypted = encrypt_api_key(PLAINTEXT_KEY);
|
|
expect(encrypted).toMatch(/^enc:v1:/);
|
|
});
|
|
|
|
it('different calls produce different ciphertext (random IV)', () => {
|
|
const enc1 = encrypt_api_key(PLAINTEXT_KEY);
|
|
const enc2 = encrypt_api_key(PLAINTEXT_KEY);
|
|
expect(enc1).not.toBe(enc2);
|
|
});
|
|
|
|
it('encrypted value does not contain the plaintext', () => {
|
|
const encrypted = encrypt_api_key(PLAINTEXT_KEY);
|
|
expect(encrypted).not.toContain(PLAINTEXT_KEY);
|
|
});
|
|
});
|
|
|
|
describe('decrypt_api_key', () => {
|
|
it('SEC-008: decrypts an encrypted key back to original', () => {
|
|
const encrypted = encrypt_api_key(PLAINTEXT_KEY);
|
|
const decrypted = decrypt_api_key(encrypted);
|
|
expect(decrypted).toBe(PLAINTEXT_KEY);
|
|
});
|
|
|
|
it('returns null for null input', () => {
|
|
expect(decrypt_api_key(null)).toBeNull();
|
|
});
|
|
|
|
it('returns null for empty string', () => {
|
|
expect(decrypt_api_key('')).toBeNull();
|
|
});
|
|
|
|
it('returns plaintext as-is if not prefixed (legacy)', () => {
|
|
expect(decrypt_api_key('plain-legacy-key')).toBe('plain-legacy-key');
|
|
});
|
|
|
|
it('returns null for tampered ciphertext', () => {
|
|
const encrypted = encrypt_api_key(PLAINTEXT_KEY);
|
|
const tampered = encrypted.replace(ENC_PREFIX, ENC_PREFIX) + 'TAMPER';
|
|
expect(decrypt_api_key(tampered)).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('maybe_encrypt_api_key', () => {
|
|
it('encrypts a new plaintext value', () => {
|
|
const result = maybe_encrypt_api_key('my-key');
|
|
expect(result).toMatch(/^enc:v1:/);
|
|
});
|
|
|
|
it('returns null for empty/falsy values', () => {
|
|
expect(maybe_encrypt_api_key('')).toBeNull();
|
|
expect(maybe_encrypt_api_key(null)).toBeNull();
|
|
expect(maybe_encrypt_api_key(undefined)).toBeNull();
|
|
});
|
|
|
|
it('returns already-encrypted value as-is (no double-encryption)', () => {
|
|
const encrypted = encrypt_api_key(PLAINTEXT_KEY);
|
|
const result = maybe_encrypt_api_key(encrypted);
|
|
expect(result).toBe(encrypted);
|
|
});
|
|
});
|
|
});
|