File size: 3,026 Bytes
2276d59
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1f34571
2276d59
 
 
 
 
1f34571
2276d59
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
import { describe, it, expect, afterEach, vi } from 'vitest';
import request from 'supertest';
import app from '../../src/app.js';
import { assertProdConfig } from '../../src/utils/network/auth.util.js';

// non-local test client
const REMOTE = '203.0.113.5';

describe('inbound API key auth', () => {
  afterEach(() => {
    vi.unstubAllEnvs();
  });

  it('is disabled when API_KEY is unset', async () => {
    vi.stubEnv('API_KEY', '');
    const res = await request(app).get('/proxy').set('X-Forwarded-For', REMOTE);
    expect(res.status).toBe(403);
  });

  it('returns 401 for a remote request with no key', async () => {
    vi.stubEnv('API_KEY', 'secret');
    const res = await request(app).get('/proxy').set('X-Forwarded-For', REMOTE);
    expect(res.status).toBe(401);
  });

  it('returns 401 for a wrong key', async () => {
    vi.stubEnv('API_KEY', 'secret');
    const res = await request(app)
      .get('/proxy')
      .set('X-Forwarded-For', REMOTE)
      .set('x-api-key', 'nope');
    expect(res.status).toBe(401);
  });

  it('accepts a correct key header (then hits the 403 sig gate)', async () => {
    vi.stubEnv('API_KEY', 'secret');
    const res = await request(app)
      .get('/proxy')
      .set('X-Forwarded-For', REMOTE)
      .set('x-api-key', 'secret');
    expect(res.status).toBe(403);
  });

  it('accepts a correct ?key= query', async () => {
    vi.stubEnv('API_KEY', 'secret');
    const res = await request(app)
      .get('/proxy?key=secret')
      .set('X-Forwarded-For', REMOTE);
    expect(res.status).toBe(403);
  });

  it('exempts localhost even with no key', async () => {
    vi.stubEnv('API_KEY', 'secret');
    const res = await request(app).get('/proxy');
    expect(res.status).toBe(403);
  });
});

describe('localhost bypass cannot be spoofed via header', () => {
  afterEach(() => vi.unstubAllEnvs());

  it('rejects a spoofed X-Forwarded-For: 127.0.0.1', async () => {
    vi.stubEnv('API_KEY', 'secret');
    const res = await request(app)
      .get('/proxy')
      .set('X-Forwarded-For', '127.0.0.1');
    expect(res.status).toBe(401);
  });
});

describe('assertProdConfig', () => {
  it('passes in production without API_KEY (auth disabled)', () => {
    expect(() =>
      assertProdConfig({
        NODE_ENV: 'production',
        PROXY_SIGNING_SECRET: 's',
      } as NodeJS.ProcessEnv)
    ).not.toThrow();
  });

  it('throws in production without PROXY_SIGNING_SECRET', () => {
    expect(() =>
      assertProdConfig({
        NODE_ENV: 'production',
        API_KEY: 'x',
      } as NodeJS.ProcessEnv)
    ).toThrow();
  });

  it('passes in production with API_KEY and PROXY_SIGNING_SECRET', () => {
    expect(() =>
      assertProdConfig({
        NODE_ENV: 'production',
        API_KEY: 'x',
        PROXY_SIGNING_SECRET: 's',
      } as NodeJS.ProcessEnv)
    ).not.toThrow();
  });

  it('passes outside production', () => {
    expect(() =>
      assertProdConfig({ NODE_ENV: 'development' } as NodeJS.ProcessEnv)
    ).not.toThrow();
  });
});