orgs-authz.test.ts43 lines · main
1// apps/api/src/routes/orgs-authz.test.ts
2//
3// Zone-2 regression test for the org-member-mutation privilege-escalation fix.
4//
5// Bug: PATCH /v1/orgs/:id/members/:userId (role change), DELETE …/members/:userId
6// (member removal) and POST …/invitations gated on `isOrgMember` ONLY — so any
7// member (including a viewer) could change roles, remove the owner, or invite.
8//
9// Fix: those three routes now gate on the caller's org role via
10// `getOrgRole(...)` + `hasRoleAtLeast(role, 'admin')` — a viewer/developer is
11// rejected, admin/owner allowed. This test pins that exact predicate (the
12// route's authorisation decision) without needing a DB.
13
14import { describe, expect, it } from 'bun:test';
15
16import { hasRoleAtLeast } from '../services/access.js';
17import { orgRole } from '../db/schema.js';
18
19describe('org-member-mutation admin gate', () => {
20 // Mirrors the route guard: `!actorRole || !hasRoleAtLeast(actorRole, 'admin')`
21 const allowed = (role: (typeof orgRole)[number] | null): boolean =>
22 Boolean(role) && hasRoleAtLeast(role as (typeof orgRole)[number], 'admin');
23
24 it('denies a viewer', () => {
25 expect(allowed('viewer')).toBe(false);
26 });
27
28 it('denies a developer', () => {
29 expect(allowed('developer')).toBe(false);
30 });
31
32 it('allows an admin', () => {
33 expect(allowed('admin')).toBe(true);
34 });
35
36 it('allows an owner', () => {
37 expect(allowed('owner')).toBe(true);
38 });
39
40 it('denies a non-member (null role)', () => {
41 expect(allowed(null)).toBe(false);
42 });
43});