Session 10: Internal auth refactor, prefetch cascade keys, Sentry, welcome email (1286 tests)

This commit is contained in:
Kev
2026-06-10 20:45:05 -04:00
parent b55dcbd614
commit e5c45ecc8e
22 changed files with 3837 additions and 94 deletions
+160
View File
@@ -0,0 +1,160 @@
// internalAuth middleware (Session 10) — tests both the new short-form
// header (x-internal-key) and the legacy long form
// (X-VYNDR-Internal-Key), the timing-safe compare, and the optional
// loopback restriction.
const { requireInternalAuth, __internals } = require('../../src/middleware/internalAuth');
function fakeReqRes({ headers = {}, ip = '127.0.0.1' } = {}) {
const req = {
headers: Object.fromEntries(Object.entries(headers).map(([k, v]) => [k.toLowerCase(), v])),
ip,
socket: { remoteAddress: ip },
get(name) {
return this.headers[name.toLowerCase()];
},
};
const res = {
statusCode: null,
body: null,
status(code) { this.statusCode = code; return this; },
json(body) { this.body = body; return this; },
};
return { req, res };
}
function run(mw, req, res) {
return new Promise((resolve) => {
mw(req, res, () => resolve('next'));
if (res.statusCode != null) resolve('blocked');
});
}
beforeEach(() => {
delete process.env.VYNDR_INTERNAL_KEY;
});
describe('requireInternalAuth', () => {
describe('configuration guard', () => {
test('returns 503 when VYNDR_INTERNAL_KEY env var is unset', async () => {
const mw = requireInternalAuth();
const { req, res } = fakeReqRes({ headers: { 'x-internal-key': 'anything' } });
const out = await run(mw, req, res);
expect(out).toBe('blocked');
expect(res.statusCode).toBe(503);
expect(res.body.error).toMatch(/not configured/);
});
});
describe('header compatibility', () => {
beforeEach(() => { process.env.VYNDR_INTERNAL_KEY = 'real-key-12345'; });
test('accepts the new short-form header (x-internal-key)', async () => {
const mw = requireInternalAuth();
const { req, res } = fakeReqRes({ headers: { 'x-internal-key': 'real-key-12345' } });
const out = await run(mw, req, res);
expect(out).toBe('next');
});
test('accepts the legacy long-form header (X-VYNDR-Internal-Key)', async () => {
const mw = requireInternalAuth();
const { req, res } = fakeReqRes({ headers: { 'x-vyndr-internal-key': 'real-key-12345' } });
const out = await run(mw, req, res);
expect(out).toBe('next');
});
test('returns 401 on missing header', async () => {
const mw = requireInternalAuth();
const { req, res } = fakeReqRes({});
const out = await run(mw, req, res);
expect(out).toBe('blocked');
expect(res.statusCode).toBe(401);
});
test('returns 401 on wrong key (timing-safe)', async () => {
const mw = requireInternalAuth();
const { req, res } = fakeReqRes({ headers: { 'x-internal-key': 'wrong-key' } });
const out = await run(mw, req, res);
expect(out).toBe('blocked');
expect(res.statusCode).toBe(401);
});
test('returns 401 even when prefix matches but suffix differs', async () => {
// Guards against early-exit string compare leaking timing info.
const mw = requireInternalAuth();
const { req, res } = fakeReqRes({ headers: { 'x-internal-key': 'real-key-1234X' } });
const out = await run(mw, req, res);
expect(out).toBe('blocked');
expect(res.statusCode).toBe(401);
});
test('returns 401 on length mismatch (avoids any timingSafeEqual throw)', async () => {
const mw = requireInternalAuth();
const { req, res } = fakeReqRes({ headers: { 'x-internal-key': 'short' } });
const out = await run(mw, req, res);
expect(out).toBe('blocked');
expect(res.statusCode).toBe(401);
});
});
describe('loopbackOnly option', () => {
beforeEach(() => { process.env.VYNDR_INTERNAL_KEY = 'real-key-12345'; });
test('passes from 127.0.0.1', async () => {
const mw = requireInternalAuth({ loopbackOnly: true });
const { req, res } = fakeReqRes({ headers: { 'x-internal-key': 'real-key-12345' }, ip: '127.0.0.1' });
const out = await run(mw, req, res);
expect(out).toBe('next');
});
test('passes from IPv6 loopback', async () => {
const mw = requireInternalAuth({ loopbackOnly: true });
const { req, res } = fakeReqRes({ headers: { 'x-internal-key': 'real-key-12345' }, ip: '::1' });
const out = await run(mw, req, res);
expect(out).toBe('next');
});
test('passes from IPv4-mapped IPv6 loopback', async () => {
const mw = requireInternalAuth({ loopbackOnly: true });
const { req, res } = fakeReqRes({ headers: { 'x-internal-key': 'real-key-12345' }, ip: '::ffff:127.0.0.1' });
const out = await run(mw, req, res);
expect(out).toBe('next');
});
test('returns 403 from off-host IP', async () => {
const mw = requireInternalAuth({ loopbackOnly: true });
const { req, res } = fakeReqRes({ headers: { 'x-internal-key': 'real-key-12345' }, ip: '10.0.0.42' });
const out = await run(mw, req, res);
expect(out).toBe('blocked');
expect(res.statusCode).toBe(403);
expect(res.body.error).toMatch(/Origin not permitted/);
});
test('WITHOUT loopbackOnly, off-host IPs are accepted (n8n case)', async () => {
const mw = requireInternalAuth({ loopbackOnly: false });
const { req, res } = fakeReqRes({ headers: { 'x-internal-key': 'real-key-12345' }, ip: '172.18.0.3' });
const out = await run(mw, req, res);
expect(out).toBe('next');
});
});
describe('timingSafeStringEqual', () => {
const { timingSafeStringEqual } = __internals;
test('equal strings → true', () => {
expect(timingSafeStringEqual('abc123', 'abc123')).toBe(true);
});
test('different strings same length → false', () => {
expect(timingSafeStringEqual('abc123', 'abc124')).toBe(false);
});
test('different lengths → false (does not throw)', () => {
expect(() => timingSafeStringEqual('a', 'abc')).not.toThrow();
expect(timingSafeStringEqual('a', 'abc')).toBe(false);
});
test('non-string inputs → false', () => {
expect(timingSafeStringEqual(null, 'a')).toBe(false);
expect(timingSafeStringEqual('a', undefined)).toBe(false);
expect(timingSafeStringEqual(123, '123')).toBe(false);
});
});
});