Files
0pt/tests/auth/context.test.ts
admin bd1c20edb2
CI (SIAX Cloud) / integration (push) Skipped
CI (SIAX Cloud) / sonar (push) Skipped
masterplan-lock / check (push) Successful in 5s
Security Scan (SIAX Cloud) / security (push) Successful in 15s
CI (SIAX Cloud) / security (push) Successful in 16s
CI (SIAX Cloud) / sonar (pull_request) Skipped
masterplan-lock / check (pull_request) Successful in 7s
Security Scan (SIAX Cloud) / security (pull_request) Successful in 21s
CI (SIAX Cloud) / security (pull_request) Successful in 25s
CI (SIAX Cloud) / quality (push) Successful in 1m17s
CI (SIAX Cloud) / integration (pull_request) Successful in 54s
CI (SIAX Cloud) / quality (pull_request) Successful in 1m7s
feat: add guarded document workbench foundations
2026-11-02 01:26:36 +01:00

215 lines
8.0 KiB
TypeScript

/**
* 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<string, string>): 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');
}
});
});