import { strict as assert } from 'node:assert'; import { describe, it } from 'node:test'; import { AuthError, effectivePermissions, requireAnyTeamCapability, requireCapability, requireReadCapability, } from '../src/lib/auth'; import { onTeam, principal } from './helpers/principal'; describe('capability enforcement', () => { it('rejects a role grant from the wrong team', () => { assert.throws( () => requireCapability(principal(), 'deal:write', 'supply'), (error: unknown) => error instanceof AuthError && error.code === 'insufficient_permission', ); }); it('removes write grants from a read-only API key but keeps its reads', () => { const readOnly = principal({ via: 'api_key', scopes: ['read'] }); // The point of a read-only key. Before read capabilities existed this // resolved to nothing at all, which was right then and would now tell the // front end that a reader may not read. assert.deepEqual( effectivePermissions(readOnly).map((grant) => grant.capability), ['book:read', 'economics:read', 'team:read'], ); assert.throws( () => requireCapability(readOnly, 'deal:write', 'demand'), (error: unknown) => error instanceof AuthError && error.code === 'insufficient_scope', ); }); it('grants no reads to a write-only credential', () => { const writeOnly = principal({ via: 'api_key', scopes: ['write'] }); assert.throws( () => requireReadCapability(writeOnly, 'economics:read'), (error: unknown) => error instanceof AuthError && error.code === 'insufficient_scope', ); }); it('keeps supplier economics away from research, whatever their rank', () => { const researchAdmin = principal(onTeam('research', 'admin')); requireReadCapability(researchAdmin, 'book:read'); assert.throws( () => requireReadCapability(researchAdmin, 'economics:read'), (error: unknown) => error instanceof AuthError && error.code === 'insufficient_permission', ); }); it('gives a viewer the book and nothing that writes to it', () => { const viewer = principal(onTeam('demand', 'viewer')); requireReadCapability(viewer, 'book:read'); requireReadCapability(viewer, 'team:read'); for (const capability of ['deal:write', 'activity:write'] as const) { assert.throws( () => requireCapability(viewer, capability, 'demand'), (error: unknown) => error instanceof AuthError && error.code === 'insufficient_permission', `viewer should not hold ${capability}`, ); } }); /** * The bug this pins: `requireCapability(p, 'data:import')` with no team * passed if the principal held it anywhere, so a research-team admin could * rewrite the demand pipeline. The overload no longer accepts a team-scoped * capability without a team; the any-team question has to be asked by name. */ it('separates "holds it here" from "holds it somewhere"', () => { const researchAdmin = principal(onTeam('research', 'admin')); requireAnyTeamCapability(researchAdmin, 'data:import'); assert.throws( () => requireCapability(researchAdmin, 'data:import', 'demand'), (error: unknown) => error instanceof AuthError && error.code === 'insufficient_permission', ); }); it('will not let fact review borrow bulk-import authority', () => { const demandAdmin = principal(onTeam('demand', 'admin')); requireCapability(demandAdmin, 'data:import', 'demand'); assert.throws( () => requireAnyTeamCapability(demandAdmin, 'fact:review'), (error: unknown) => error instanceof AuthError && error.code === 'insufficient_permission', ); }); });