/** * ═══════════════════════════════════════════════════════════════════════════ * AAMOS Ledger — Auth & RBAC Tester * ═══════════════════════════════════════════════════════════════════════════ */ import { describe, it, before } from 'node:test'; import assert from 'node:assert'; import jwt from 'jsonwebtoken'; // Mocka miljövariabler före import process.env.NODE_ENV = 'development'; process.env.AMOS_JWT_SECRET = 'test-secret-minst-32-tecken-lång-12345'; process.env.LEDGER_INTERNAL_TOKEN = 'test-internal-token-12345'; const auth = await import('../auth.mjs'); describe('Auth-konfiguration', () => { it('ska validera korrekt med giltiga miljövariabler', () => { assert.doesNotThrow(() => auth.validateAuthConfig()); }); }); describe('JWT-verifiering', () => { const secret = process.env.AMOS_JWT_SECRET; it('ska acceptera giltig HS256-token', async () => { const token = jwt.sign({ sub: 'user-1', roles: ['viewer'] }, secret, { algorithm: 'HS256' }); const req = { headers: { authorization: `Bearer ${token}` } }; const res = { status: () => ({ json: () => {} }) }; let nextCalled = false; auth.authenticate(req, res, () => { nextCalled = true; }); assert.strictEqual(nextCalled, true); assert.strictEqual(req.auth.user_id, 'user-1'); assert.deepStrictEqual(req.auth.roles, ['viewer']); }); it('ska neka expired token', async () => { const token = jwt.sign({ sub: 'user-1', roles: ['viewer'] }, secret, { algorithm: 'HS256', expiresIn: '-1s' }); const req = { headers: { authorization: `Bearer ${token}` } }; let statusCode = null; const res = { status: (code) => { statusCode = code; return { json: () => {} }; } }; auth.authenticate(req, res, () => {}); assert.strictEqual(statusCode, 401); }); it('ska neka token utan roller i production', async () => { // Simulera production-beteende genom att testa logiken direkt // I production krävs RS256 + publik nyckel, så vi testar att // auth-modulen kastar fel vid startup om konfig saknas // Testa att validateAuthConfig kastar fel utan publik nyckel i production const originalEnv = process.env.NODE_ENV; const originalPath = process.env.JWT_PUBLIC_KEY_PATH; process.env.NODE_ENV = 'production'; delete process.env.JWT_PUBLIC_KEY_PATH; assert.throws(() => auth.validateAuthConfig(), /JWT_PUBLIC_KEY_PATH/); process.env.NODE_ENV = originalEnv; if (originalPath) process.env.JWT_PUBLIC_KEY_PATH = originalPath; }); }); describe('Service-to-service token', () => { it('ska acceptera giltigt intern token', () => { const req = { headers: { authorization: `Bearer ${process.env.LEDGER_INTERNAL_TOKEN}` } }; let nextCalled = false; const res = { status: () => ({ json: () => {} }) }; auth.authenticate(req, res, () => { nextCalled = true; }); assert.strictEqual(nextCalled, true); assert.strictEqual(req.auth.type, 'service'); assert.deepStrictEqual(req.auth.roles, ['admin']); }); it('ska neka ogiltigt intern token', () => { const req = { headers: { authorization: 'Bearer wrong-token' } }; let statusCode = null; const res = { status: (code) => { statusCode = code; return { json: () => {} }; } }; auth.authenticate(req, res, () => {}); assert.strictEqual(statusCode, 401); }); }); describe('RBAC — requireRole', () => { it('ska tillåta admin att komma åt admin-resurser', () => { const req = { auth: { roles: ['admin'] } }; let nextCalled = false; const res = { status: () => ({ json: () => {} }) }; auth.requireRole('admin')(req, res, () => { nextCalled = true; }); assert.strictEqual(nextCalled, true); }); it('ska tillåta accountant att komma åt viewer-resurser', () => { const req = { auth: { roles: ['accountant'] } }; let nextCalled = false; const res = { status: () => ({ json: () => {} }) }; auth.requireRole('viewer')(req, res, () => { nextCalled = true; }); assert.strictEqual(nextCalled, true); }); it('ska neka viewer att komma åt accountant-resurser', () => { const req = { auth: { roles: ['viewer'] } }; let statusCode = null; const res = { status: (code) => { statusCode = code; return { json: () => {} }; } }; auth.requireRole('accountant')(req, res, () => {}); assert.strictEqual(statusCode, 403); }); }); describe('Tenant-isolering', () => { it('ska tillåta användare att komma åt sin egen tenant', () => { const req = { auth: { type: 'user', tenant_id: 'landvex' }, headers: { 'x-tenant-id': 'landvex' }, body: {} }; let nextCalled = false; const res = { status: () => ({ json: () => {} }) }; auth.requireTenantMatch(req, res, () => { nextCalled = true; }); assert.strictEqual(nextCalled, true); assert.strictEqual(req.tenant_id, 'landvex'); }); it('ska neka användare att komma åt annan tenant', () => { const req = { auth: { type: 'user', tenant_id: 'landvex' }, headers: { 'x-tenant-id': 'annan-tenant' }, body: {} }; let statusCode = null; const res = { status: (code) => { statusCode = code; return { json: () => {} }; } }; auth.requireTenantMatch(req, res, () => {}); assert.strictEqual(statusCode, 403); }); it('ska tillåta service-konto att komma åt alla tenants', () => { const req = { auth: { type: 'service', tenant_id: 'wavult-group' }, headers: { 'x-tenant-id': 'annan-tenant' }, body: {} }; let nextCalled = false; const res = { status: () => ({ json: () => {} }) }; auth.requireTenantMatch(req, res, () => { nextCalled = true; }); assert.strictEqual(nextCalled, true); assert.strictEqual(req.tenant_id, 'annan-tenant'); }); }); console.log('✅ Auth-tester klara');