import { createHmac, createSign, generateKeyPairSync } from 'node:crypto'; import { test } from 'node:test'; import assert from 'node:assert/strict'; import { JwtAuthProvider, validateAuthConfig, type ControlPlaneAuthConfig, } from '../src/common/auth-provider.js'; const secret = 'test-only-secret-with-sufficient-length'; const baseConfig: ControlPlaneAuthConfig = { mode: 'jwt', profile: 'test', bind: '0.0.0.0', issuer: 'https://issuer.test', audience: 'casan-control-plane', algorithm: 'HS256', hmacSecret: secret, clockSkewSeconds: 30, roleClaim: 'groups', tenantClaim: 'casan_tenant', projectClaim: 'casan_project', }; function token(payload: Record, signingSecret = secret): string { const header = Buffer.from(JSON.stringify({ alg: 'HS256', typ: 'JWT' })).toString('base64url'); const body = Buffer.from(JSON.stringify(payload)).toString('base64url'); const signature = createHmac('sha256', signingSecret).update(`${header}.${body}`).digest('base64url'); return `${header}.${body}.${signature}`; } function rsToken(payload: Record, privateKey: string): string { const header = Buffer.from(JSON.stringify({ alg: 'RS256', typ: 'JWT' })).toString('base64url'); const body = Buffer.from(JSON.stringify(payload)).toString('base64url'); const signer = createSign('RSA-SHA256'); signer.update(`${header}.${body}`); signer.end(); return `${header}.${body}.${signer.sign(privateKey).toString('base64url')}`; } function claims(now: number, overrides: Record = {}): Record { return { iss: baseConfig.issuer, aud: baseConfig.audience, sub: 'verified-user', exp: now + 300, iat: now, groups: ['project-admin'], casan_tenant: 'tenant-a', casan_project: 'project-a', ...overrides, }; } test('JWT provider verifies claims and ignores spoofed forwarded identity headers', () => { const now = 1_800_000_000; const provider = new JwtAuthProvider(baseConfig); const decision = provider.authenticate({ authorization: `Bearer ${token(claims(now))}`, 'x-forwarded-user': 'attacker', 'x-casan-role': 'org-admin', 'x-casan-tenant': 'victim', }, now); assert.equal(decision.allowed, true); assert.equal(decision.claims?.subject, 'verified-user'); assert.equal(decision.claims?.tenant, 'tenant-a'); assert.deepEqual(decision.claims?.roles, ['project-admin']); }); test('JWT provider rejects missing, invalid, expired, wrong-audience and wrong-issuer tokens', () => { const now = 1_800_000_000; const provider = new JwtAuthProvider(baseConfig); assert.equal(provider.authenticate({}, now).reasonCode, 'auth_bearer_token_required'); assert.equal(provider.authenticate({ authorization: `Bearer ${token(claims(now), 'wrong-secret')}` }, now).reasonCode, 'auth_signature_invalid'); assert.equal(provider.authenticate({ authorization: `Bearer ${token(claims(now, { exp: now - 31 }))}` }, now).reasonCode, 'auth_token_expired'); assert.equal(provider.authenticate({ authorization: `Bearer ${token(claims(now, { aud: 'wrong' }))}` }, now).reasonCode, 'auth_audience_invalid'); assert.equal(provider.authenticate({ authorization: `Bearer ${token(claims(now, { iss: 'https://wrong.test' }))}` }, now).reasonCode, 'auth_issuer_invalid'); }); test('production-compatible RS256 verification accepts a valid asymmetric token', () => { const now = 1_800_000_000; const keys = generateKeyPairSync('rsa', { modulusLength: 2048, publicKeyEncoding: { type: 'spki', format: 'pem' }, privateKeyEncoding: { type: 'pkcs8', format: 'pem' }, }); const provider = new JwtAuthProvider({ ...baseConfig, profile: 'production', algorithm: 'RS256', publicKey: keys.publicKey, hmacSecret: undefined, }); const decision = provider.authenticate({ authorization: `Bearer ${rsToken(claims(now), keys.privateKey)}` }, now); assert.equal(decision.allowed, true); assert.equal(decision.claims?.subject, 'verified-user'); }); test('production and non-loopback startup refuse local or symmetric identity modes', () => { assert.deepEqual( validateAuthConfig({ ...baseConfig, mode: 'local', profile: 'production' }), ['verified_identity_required'], ); assert.ok(validateAuthConfig({ ...baseConfig, profile: 'production' }).includes('auth_asymmetric_key_required_in_production')); assert.ok(validateAuthConfig({ ...baseConfig, mode: 'local', profile: 'development' }).includes('verified_identity_required')); assert.ok(validateAuthConfig({ ...baseConfig, mode: 'local', profile: 'strict', bind: '127.0.0.1' }).includes('verified_identity_required')); assert.ok(validateAuthConfig({ ...baseConfig, profile: 'production', algorithm: 'RS256', publicKey: 'not-a-public-key', hmacSecret: undefined, }).includes('auth_rsa_public_key_invalid')); });