@@ -34,11 +34,18 @@ Both Admin (NextAuth) and API (Express middleware) validate these claims for acc
3434| Role | Description | Access Level |
3535| ------| -------------| --------------|
3636| ` SuperAdmin ` | Full platform access | All pages, all API endpoints |
37+ | ` SuperAdminPlus ` | SuperAdmin with extended privileges | All SuperAdmin access + organisation deletion |
3738| ` CityAdmin ` | Location-specific administrator | Pages/APIs for assigned locations |
3839| ` VolunteerAdmin ` | Volunteer management | Organisation management, content creation |
3940| ` OrgAdmin ` | Organisation-specific administrator | Own organisation only |
4041| ` SwepAdmin ` | SWEP banner management | SWEP banners for assigned locations |
4142
43+ ### Special Notes on SuperAdminPlus
44+
45+ - ** Cannot be created through UI** : This role must be manually assigned in MongoDB and Auth0
46+ - ** Organisation Deletion** : Only SuperAdminPlus can delete organisations and their related data
47+ - ** Cannot be removed through UI** : The role removal is disabled in the Edit User modal
48+
4249### Role Prefixes (Specific Claims)
4350
4451| Prefix | Format | Example |
@@ -50,6 +57,8 @@ Both Admin (NextAuth) and API (Express middleware) validate these claims for acc
5057### Role Hierarchy
5158
5259```
60+ SuperAdminPlus (SuperAdmin + organisation deletion)
61+ ↓
5362SuperAdmin
5463 ↓ (Full access)
5564VolunteerAdmin
@@ -64,16 +73,27 @@ OrgAdmin + AdminFor:*
6473
6574### Page Access by Role
6675
67- | Page | SuperAdmin | CityAdmin | VolunteerAdmin | OrgAdmin | SwepAdmin |
68- | ------| ------------| -----------| ----------------| ----------| -----------|
69- | ` /cities ` | ✅ | ✅ | ✅ | ❌ | ❌ |
70- | ` /organisations ` | ✅ | ✅ | ✅ | ✅ | ❌ |
71- | ` /users ` | ✅ | ✅ | ❌ | ❌ | ❌ |
72- | ` /banners ` | ✅ | ✅ | ✅ | ❌ | ❌ |
73- | ` /swep-banners ` | ✅ | ✅ | ✅ | ❌ | ✅ |
74- | ` /advice ` | ✅ | ✅ | ✅ | ❌ | ❌ |
75- | ` /location-logos ` | ✅ | ✅ | ✅ | ❌ | ❌ |
76- | ` /resources ` | ✅ | ❌ | ✅ | ❌ | ❌ |
76+ | Page | SuperAdmin | SuperAdminPlus | CityAdmin | VolunteerAdmin | OrgAdmin | SwepAdmin |
77+ | ------| ------------| ----------------| -----------| ----------------| ----------| -----------|
78+ | ` /cities ` | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
79+ | ` /organisations ` | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ |
80+ | ` /users ` | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ |
81+ | ` /banners ` | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
82+ | ` /swep-banners ` | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ |
83+ | ` /advice ` | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
84+ | ` /location-logos ` | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
85+ | ` /resources ` | ✅ | ✅ | ❌ | ✅ | ❌ | ❌ |
86+
87+ ### Organisation Actions by Role
88+
89+ | Action | SuperAdmin | SuperAdminPlus | CityAdmin | VolunteerAdmin | OrgAdmin |
90+ | --------| ------------| ----------------| -----------| ----------------| ----------|
91+ | View | ✅ | ✅ | ✅ | ✅ | ✅ |
92+ | Create | ✅ | ✅ | ✅ | ✅ | ❌ |
93+ | Edit | ✅ | ✅ | ✅ | ✅ | ✅ |
94+ | Publish/Disable | ✅ | ✅ | ✅ | ✅ | ❌ |
95+ | Verify | ✅ | ✅ | ✅ | ✅ | ❌ |
96+ | ** Delete** | ❌ | ✅ | ❌ | ❌ | ❌ |
7797
7898---
7999
@@ -156,14 +176,15 @@ The API validates role assignments based on the creator's permissions:
156176``` typescript
157177// authMiddleware.ts - requireUserCreationAccess
158178// SuperAdmin can assign any role
159- if (userAuthClaims .includes (ROLES .SUPER_ADMIN )) {
179+ if (userAuthClaims .includes (ROLES .SUPER_ADMIN ) || userAuthClaims . includes ( ROLES . SUPER_ADMIN_PLUS ) ) {
160180 return next ();
161181}
162182
163183// CityAdmin cannot assign SuperAdmin or VolunteerAdmin
164184if (userAuthClaims .includes (ROLES .CITY_ADMIN )) {
165185 if (newUserClaims .includes (ROLES .SUPER_ADMIN ) ||
166- newUserClaims .includes (ROLES .VOLUNTEER_ADMIN )) {
186+ newUserClaims .includes (ROLES .VOLUNTEER_ADMIN ) ||
187+ newUserClaims .includes (ROLES .VOLUNTEER_ADMIN_PLUS )) {
167188 return sendForbidden (res , ' CityAdmin cannot assign SuperAdmin or VolunteerAdmin roles' );
168189 }
169190}
@@ -292,7 +313,7 @@ Using the `useAuthorization` hook:
292313// Example: Banners page
293314export default function BannersPage() {
294315 const { isChecking, isAuthorized } = useAuthorization ({
295- allowedRoles: [ROLES .SUPER_ADMIN , ROLES .CITY_ADMIN , ROLES .VOLUNTEER_ADMIN ],
316+ allowedRoles: [ROLES .SUPER_ADMIN , ROLES .SUPER_ADMIN_PLUS , ROLES . CITY_ADMIN , ROLES .VOLUNTEER_ADMIN ],
296317 requiredPage: ' /banners' ,
297318 autoRedirect: true
298319 });
@@ -321,7 +342,7 @@ export function hasApiAccess(
321342 method : HttpMethod
322343): boolean {
323344 // SuperAdmin has access to everything
324- if (userAuthClaims .roles .includes (ROLES .SUPER_ADMIN )) {
345+ if (userAuthClaims .roles .includes (ROLES .SUPER_ADMIN ) || userAuthClaims . roles . includes ( ROLES . SUPER_ADMIN_PLUS ) ) {
325346 return true ;
326347 }
327348
@@ -346,10 +367,26 @@ export function hasApiAccess(
346367``` typescript
347368// src/types/auth.ts
348369export const ROLE_PERMISSIONS: Record <UserRole , RolePermissions > = {
349- [ROLES .SUPER_ADMIN ]: {
370+ [ROLES .SUPER_ADMIN_PLUS ]: {
350371 pages: [' *' ],
351372 apiEndpoints: [{ path: ' *' , methods: [' *' ] }]
352373 },
374+ [ROLES .SUPER_ADMIN ]: {
375+ pages: [' *' ],
376+ apiEndpoints: [
377+ { path: ' /api/cities' , methods: [' *' ] },
378+ { path: ' /api/organisations' , methods: [HTTP_METHODS .GET , HTTP_METHODS .POST , HTTP_METHODS .PUT , HTTP_METHODS .PATCH ] },
379+ { path: ' /api/services' , methods: [' *' ] },
380+ { path: ' /api/accommodations' , methods: [' *' ] },
381+ { path: ' /api/faqs' , methods: [' *' ] },
382+ { path: ' /api/banners' , methods: [' *' ] },
383+ { path: ' /api/location-logos' , methods: [' *' ] },
384+ { path: ' /api/swep-banners' , methods: [' *' ] },
385+ { path: ' /api/resources' , methods: [' *' ] },
386+ { path: ' /api/users' , methods: [' *' ] },
387+ { path: ' /api/service-categories' , methods: [' *' ] },
388+ ]
389+ },
353390 [ROLES .CITY_ADMIN ]: {
354391 pages: [' /cities' , ' /organisations' , ' /advice' , ' /banners' , ' /location-logos' , ' /swep-banners' , ' /users' ],
355392 apiEndpoints: [
0 commit comments