/** * Unit tests for src/auth/context.ts -- the auth seam. Verification is * injected (opts.verify) so these tests are pure and network-free. * * Three groups: * 1. the ENV DEFAULT (the fail-closed contract: absent AUTH_ENFORCE means * an unauthenticated request is REJECTED, and production refuses to run * with auth switched off), * 2. enforced-mode decision logic, * 3. the explicit `AUTH_ENFORCE=false` local-development escape hatch. */ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { resolveAuthContext, authEnforced, assertAuthConfigSafe, AuthMisconfigurationError, AUTH_ENFORCE_VAR, type OptRequestContext, } from '@/src/auth/context'; import type { VerifiedPrincipal } from '@/src/auth/zitadel'; const principal: VerifiedPrincipal = { sub: 'user_1', tenantId: 'tenant_x', claims: { sub: 'user_1', iss: 'https://id-customers.siax.io' }, }; const okVerify = vi.fn(async () => principal); const failVerify = vi.fn(async () => { throw new Error('token_verification_failed'); }); function h(init: Record): Headers { return new Headers(init); } describe('auth default -- fail-closed (no AUTH_ENFORCE in the environment)', () => { const saved = { auth: process.env.AUTH_ENFORCE, node: process.env.NODE_ENV }; beforeEach(() => { delete process.env.AUTH_ENFORCE; }); afterEach(() => { if (saved.auth === undefined) delete process.env.AUTH_ENFORCE; else process.env.AUTH_ENFORCE = saved.auth; if (saved.node === undefined) delete process.env.NODE_ENV; else process.env.NODE_ENV = saved.node; }); it('REGRESSION: with the variable unset, an unauthenticated request is REJECTED (401)', async () => { // No `enforce` option -> the decision comes from the environment, which is // exactly the fresh-container / DR-restore / dropped-env-var case. const d = await resolveAuthContext(h({ 'x-tenant-id': 'tenant_attacker' }), { verify: okVerify }); expect(d.ok).toBe(false); if (!d.ok) { expect(d.status).toBe(401); expect(d.code).toBe('OPT_UNAUTHENTICATED'); } }); it('with the variable unset, an invalid token is REJECTED (401) rather than ignored', async () => { const d = await resolveAuthContext(h({ authorization: 'Bearer bad.tok.en' }), { verify: failVerify }); expect(d.ok).toBe(false); if (!d.ok) expect(d.status).toBe(401); }); it('authEnforced() is true when the variable is absent, empty, or misspelled', () => { expect(authEnforced({})).toBe(true); expect(authEnforced({ AUTH_ENFORCE: '' })).toBe(true); expect(authEnforced({ AUTH_ENFORCE: 'flase' })).toBe(true); expect(authEnforced({ AUTH_ENFORCE: '0' })).toBe(true); expect(authEnforced({ AUTH_ENFORCE: 'no' })).toBe(true); expect(authEnforced({ AUTH_ENFORCE: 'true' })).toBe(true); }); it('only the literal string "false" opts out', () => { expect(authEnforced({ AUTH_ENFORCE: 'false' })).toBe(false); expect(authEnforced({ AUTH_ENFORCE: 'FALSE' })).toBe(false); expect(authEnforced({ AUTH_ENFORCE: ' false ' })).toBe(false); }); it('assertAuthConfigSafe throws in production when enforcement is off, naming the variable', () => { expect(() => assertAuthConfigSafe({ NODE_ENV: 'production', AUTH_ENFORCE: 'false' })).toThrow( AuthMisconfigurationError, ); expect(() => assertAuthConfigSafe({ NODE_ENV: 'production', AUTH_ENFORCE: 'false' })).toThrow( new RegExp(AUTH_ENFORCE_VAR), ); }); it('assertAuthConfigSafe is silent when production enforces, or when off outside production', () => { expect(() => assertAuthConfigSafe({ NODE_ENV: 'production' })).not.toThrow(); expect(() => assertAuthConfigSafe({ NODE_ENV: 'production', AUTH_ENFORCE: 'true' })).not.toThrow(); expect(() => assertAuthConfigSafe({ NODE_ENV: 'development', AUTH_ENFORCE: 'false' })).not.toThrow(); }); it('a production process with AUTH_ENFORCE=false cannot serve a request at all (throws)', async () => { process.env.NODE_ENV = 'production'; process.env.AUTH_ENFORCE = 'false'; await expect( resolveAuthContext(h({ 'x-tenant-id': 'tenant_attacker' }), { verify: okVerify }), ).rejects.toBeInstanceOf(AuthMisconfigurationError); }); }); describe('resolveAuthContext -- enforced mode (the default)', () => { it('401s when no bearer token is present', async () => { const d = await resolveAuthContext(h({ 'x-tenant-id': 'tenant_x' }), { enforce: true, verify: okVerify }); expect(d.ok).toBe(false); if (!d.ok) { expect(d.status).toBe(401); expect(d.code).toBe('OPT_UNAUTHENTICATED'); } }); it('401s when the token fails verification', async () => { const d = await resolveAuthContext(h({ authorization: 'Bearer bad.tok.en' }), { enforce: true, verify: failVerify, }); expect(d.ok).toBe(false); if (!d.ok) expect(d.status).toBe(401); }); it('attaches the verified principal and derives tenant from the TOKEN', async () => { const d = await resolveAuthContext(h({ authorization: 'Bearer good.tok.en' }), { enforce: true, verify: okVerify, }); expect(d.ok).toBe(true); if (d.ok) { expect(d.ctx.principal).toEqual(principal); expect(d.ctx.tenantId).toBe('tenant_x'); expect(d.ctx.actorId).toBe('user_1'); } }); it('403s when X-Tenant-Id disagrees with the token tenant', async () => { const d = await resolveAuthContext( h({ authorization: 'Bearer good.tok.en', 'x-tenant-id': 'tenant_evil' }), { enforce: true, verify: okVerify }, ); expect(d.ok).toBe(false); if (!d.ok) { expect(d.status).toBe(403); expect(d.code).toBe('OPT_ACCESS_DENIED'); expect(d.details).toMatchObject({ header_tenant: 'tenant_evil', token_tenant: 'tenant_x' }); } }); it('rejects a forged actor header and accepts only the verified subject', async () => { const forged = await resolveAuthContext( h({ authorization: 'Bearer good.tok.en', 'x-actor-id': 'user_other' }), { enforce: true, verify: okVerify }, ); expect(forged).toMatchObject({ ok: false, status: 403, code: 'OPT_ACCESS_DENIED' }); const matching = await resolveAuthContext( h({ authorization: 'Bearer good.tok.en', 'x-actor-id': 'user_1' }), { enforce: true, verify: okVerify }, ); expect(matching.ok).toBe(true); if (matching.ok) expect(matching.ctx.actorId).toBe('user_1'); }); it('allows a matching X-Tenant-Id', async () => { const d = await resolveAuthContext( h({ authorization: 'Bearer good.tok.en', 'x-tenant-id': 'tenant_x' }), { enforce: true, verify: okVerify }, ); expect(d.ok).toBe(true); }); }); describe('resolveAuthContext -- explicit AUTH_ENFORCE=false escape hatch (local dev only)', () => { it('never rejects when no token is present', async () => { const d = await resolveAuthContext(h({ 'x-tenant-id': 'tenant_header' }), { enforce: false, verify: okVerify, }); expect(d.ok).toBe(true); if (d.ok) { expect((d.ctx as OptRequestContext).principal).toBeUndefined(); expect(d.ctx.tenantId).toBe('tenant_header'); } }); it('never rejects even when a present token fails verification (logs a warning)', async () => { const logger = { warn: vi.fn() }; const d = await resolveAuthContext(h({ authorization: 'Bearer bad.tok.en' }), { enforce: false, verify: failVerify, logger, }); expect(d.ok).toBe(true); if (d.ok) expect(d.ctx.principal).toBeUndefined(); expect(logger.warn).toHaveBeenCalledOnce(); }); it('attaches the principal when a present token verifies, without overriding a supplied tenant', async () => { const d = await resolveAuthContext( h({ authorization: 'Bearer good.tok.en', 'x-tenant-id': 'tenant_header' }), { enforce: false, verify: okVerify }, ); expect(d.ok).toBe(true); if (d.ok) { expect(d.ctx.principal).toEqual(principal); // the caller-supplied header tenant is preserved in this mode expect(d.ctx.tenantId).toBe('tenant_header'); } }); });