File size: 2,821 Bytes
1f5ea39
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
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/services/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);
    });
  });
});