diff --git a/apps/api/src/training/permissions-regression.spec.ts b/apps/api/src/training/permissions-regression.spec.ts index 82f1010d4c..d578bcef05 100644 --- a/apps/api/src/training/permissions-regression.spec.ts +++ b/apps/api/src/training/permissions-regression.spec.ts @@ -195,8 +195,10 @@ describe('Built-in role permissions — regression', () => { ); }); - it('should NOT have portal permissions', () => { - expect(perms.portal).toBeUndefined(); + it('should have portal read/update', () => { + expect(perms.portal).toEqual( + expect.arrayContaining(['read', 'update']), + ); }); it('should have pentest create/read/delete', () => { diff --git a/apps/api/src/training/portal-access.spec.ts b/apps/api/src/training/portal-access.spec.ts index 90c64ded13..f1930ce000 100644 --- a/apps/api/src/training/portal-access.spec.ts +++ b/apps/api/src/training/portal-access.spec.ts @@ -93,7 +93,10 @@ describe('Portal access matrix', () => { ['employee'], ['contractor'], ['owner'], + ['admin'], ['admin,employee'], + ['admin,member'], + ['admin,auditor'], ['owner,employee'], ['employee,contractor'], ])('%s → ALLOW', (roleString) => { @@ -102,26 +105,26 @@ describe('Portal access matrix', () => { }); describe('roles that should NOT have portal access', () => { - it.each([ - ['admin'], - ['auditor'], - ['member'], - ['admin,member'], - ['admin,auditor'], - [''], - ])('%s → DENY', (roleString) => { - expect(hasPortalAccessForBuiltInRoles(roleString)).toBe(false); - }); + it.each([['auditor'], ['member'], ['']])( + '%s → DENY', + (roleString) => { + expect(hasPortalAccessForBuiltInRoles(roleString)).toBe(false); + }, + ); }); describe('portal access relies on RBAC, not role names', () => { - it('admin gets portal access only through the employee role', () => { - expect(BUILT_IN_ROLE_PERMISSIONS.admin?.portal).toBeUndefined(); - expect(BUILT_IN_ROLE_PERMISSIONS.employee?.portal).toEqual( + it('admin has portal access through its own permissions', () => { + expect(BUILT_IN_ROLE_PERMISSIONS.admin?.portal).toEqual( expect.arrayContaining(['read', 'update']), ); }); + it('auditor is denied portal access by RBAC, not by role name', () => { + expect(BUILT_IN_ROLE_PERMISSIONS.auditor?.portal).toBeUndefined(); + expect(BUILT_IN_ROLE_OBLIGATIONS.auditor?.compliance).toBeFalsy(); + }); + it('admin does not have compliance obligation', () => { expect(BUILT_IN_ROLE_OBLIGATIONS.admin?.compliance).toBeFalsy(); }); diff --git a/packages/auth/src/permissions.ts b/packages/auth/src/permissions.ts index 3d9bc2e076..58d5da483e 100644 --- a/packages/auth/src/permissions.ts +++ b/packages/auth/src/permissions.ts @@ -125,6 +125,9 @@ export const admin = ac.newRole({ pentest: ['create', 'read', 'update', 'delete'], // Training management training: ['read', 'update'], + // Portal self-service — admins manage GRC evidence, so they need to submit + // evidence forms (portal:update) just like owners do. + portal: ['read', 'update'], // Secrets manager — admin can fully manage decrypted credentials secret: ['create', 'read', 'update', 'delete'], });