@jmruthers/pace-core 0.5.108 → 0.5.110
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +93 -173
- package/dist/{AuthService-1D2ifNfa.d.ts → AuthService-DrHrvXNZ.d.ts} +8 -1
- package/dist/{DataTable-WFCHVWTY.js → DataTable-D3BK2FCN.js} +7 -7
- package/dist/{UnifiedAuthProvider-XU4BHFXZ.js → UnifiedAuthProvider-A7I23UCN.js} +3 -3
- package/dist/{api-KG4A2X7P.js → api-PIE4JRFS.js} +2 -2
- package/dist/{chunk-DMNMZKWS.js → chunk-2W4WKJVF.js} +4 -4
- package/dist/{chunk-B3QX32P5.js → chunk-3J5N2T2N.js} +85 -28
- package/dist/chunk-3J5N2T2N.js.map +1 -0
- package/dist/{chunk-MOMYOQMC.js → chunk-7GBEBJLR.js} +29 -37
- package/dist/chunk-7GBEBJLR.js.map +1 -0
- package/dist/{chunk-X4FRXJV6.js → chunk-AUXS7XSO.js} +57 -6
- package/dist/{chunk-X4FRXJV6.js.map → chunk-AUXS7XSO.js.map} +1 -1
- package/dist/{chunk-VJ7MPS2K.js → chunk-AWK2FAUN.js} +6 -6
- package/dist/{chunk-LT6RKRA7.js → chunk-D6MEKC27.js} +2 -2
- package/dist/{chunk-KBG34SVL.js → chunk-EYSXQ756.js} +2 -2
- package/dist/{chunk-ZXY5NTJB.js → chunk-EZ64QG2I.js} +2 -2
- package/dist/chunk-GZRXOUBE.js +176 -0
- package/dist/chunk-GZRXOUBE.js.map +1 -0
- package/dist/{chunk-QDDUU625.js → chunk-HADXAZT3.js} +4 -4
- package/dist/{chunk-IMZGJ2X7.js → chunk-HGZSO43Y.js} +4 -4
- package/dist/{chunk-S63MFSY6.js → chunk-XRSP3H52.js} +15 -8
- package/dist/chunk-XRSP3H52.js.map +1 -0
- package/dist/{chunk-GVRSXXAA.js → chunk-YFMENCR4.js} +3 -3
- package/dist/components.js +9 -9
- package/dist/{database-BXAfr2Y_.d.ts → database-C6jy7EOu.d.ts} +21 -9
- package/dist/{formatting-BiEv5oEk.d.ts → formatting-B1jSqgl-.d.ts} +16 -1
- package/dist/hooks.d.ts +2 -2
- package/dist/hooks.js +7 -7
- package/dist/index.d.ts +6 -6
- package/dist/index.js +16 -14
- package/dist/index.js.map +1 -1
- package/dist/providers.d.ts +4 -3
- package/dist/providers.js +2 -2
- package/dist/rbac/index.d.ts +35 -23
- package/dist/rbac/index.js +8 -8
- package/dist/types.d.ts +2 -2
- package/dist/{usePublicRouteParams-CnM-IK2I.d.ts → usePublicRouteParams-BdF8bZgs.d.ts} +1 -1
- package/dist/utils.d.ts +2 -15
- package/dist/utils.js +4 -145
- package/dist/utils.js.map +1 -1
- package/dist/validation.d.ts +1 -1
- package/docs/api/classes/ColumnFactory.md +1 -1
- package/docs/api/classes/ErrorBoundary.md +1 -1
- package/docs/api/classes/InvalidScopeError.md +1 -1
- package/docs/api/classes/MissingUserContextError.md +1 -1
- package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
- package/docs/api/classes/PermissionDeniedError.md +1 -1
- package/docs/api/classes/PublicErrorBoundary.md +1 -1
- package/docs/api/classes/RBACAuditManager.md +1 -1
- package/docs/api/classes/RBACCache.md +1 -1
- package/docs/api/classes/RBACEngine.md +9 -8
- package/docs/api/classes/RBACError.md +1 -1
- package/docs/api/classes/RBACNotInitializedError.md +1 -1
- package/docs/api/classes/SecureSupabaseClient.md +1 -1
- package/docs/api/classes/StorageUtils.md +1 -1
- package/docs/api/enums/FileCategory.md +1 -1
- package/docs/api/interfaces/AggregateConfig.md +1 -1
- package/docs/api/interfaces/ButtonProps.md +1 -1
- package/docs/api/interfaces/CardProps.md +1 -1
- package/docs/api/interfaces/ColorPalette.md +1 -1
- package/docs/api/interfaces/ColorShade.md +1 -1
- package/docs/api/interfaces/DataAccessRecord.md +1 -1
- package/docs/api/interfaces/DataRecord.md +1 -1
- package/docs/api/interfaces/DataTableAction.md +1 -1
- package/docs/api/interfaces/DataTableColumn.md +3 -3
- package/docs/api/interfaces/DataTableProps.md +1 -1
- package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
- package/docs/api/interfaces/EmptyStateConfig.md +1 -1
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
- package/docs/api/interfaces/FileDisplayProps.md +1 -1
- package/docs/api/interfaces/FileMetadata.md +1 -1
- package/docs/api/interfaces/FileReference.md +1 -1
- package/docs/api/interfaces/FileSizeLimits.md +1 -1
- package/docs/api/interfaces/FileUploadOptions.md +1 -1
- package/docs/api/interfaces/FileUploadProps.md +1 -1
- package/docs/api/interfaces/FooterProps.md +1 -1
- package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
- package/docs/api/interfaces/InputProps.md +1 -1
- package/docs/api/interfaces/LabelProps.md +1 -1
- package/docs/api/interfaces/LoginFormProps.md +1 -1
- package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
- package/docs/api/interfaces/NavigationContextType.md +1 -1
- package/docs/api/interfaces/NavigationGuardProps.md +1 -1
- package/docs/api/interfaces/NavigationItem.md +1 -1
- package/docs/api/interfaces/NavigationMenuProps.md +1 -1
- package/docs/api/interfaces/NavigationProviderProps.md +1 -1
- package/docs/api/interfaces/Organisation.md +1 -1
- package/docs/api/interfaces/OrganisationContextType.md +1 -1
- package/docs/api/interfaces/OrganisationMembership.md +1 -1
- package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
- package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
- package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
- package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
- package/docs/api/interfaces/PageAccessRecord.md +1 -1
- package/docs/api/interfaces/PagePermissionContextType.md +1 -1
- package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
- package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
- package/docs/api/interfaces/PaletteData.md +1 -1
- package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
- package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
- package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
- package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
- package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
- package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
- package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
- package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
- package/docs/api/interfaces/RBACConfig.md +19 -8
- package/docs/api/interfaces/RBACLogger.md +5 -5
- package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
- package/docs/api/interfaces/RouteAccessRecord.md +1 -1
- package/docs/api/interfaces/RouteConfig.md +1 -1
- package/docs/api/interfaces/SecureDataContextType.md +1 -1
- package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
- package/docs/api/interfaces/StorageConfig.md +1 -1
- package/docs/api/interfaces/StorageFileInfo.md +1 -1
- package/docs/api/interfaces/StorageFileMetadata.md +1 -1
- package/docs/api/interfaces/StorageListOptions.md +1 -1
- package/docs/api/interfaces/StorageListResult.md +1 -1
- package/docs/api/interfaces/StorageUploadOptions.md +1 -1
- package/docs/api/interfaces/StorageUploadResult.md +1 -1
- package/docs/api/interfaces/StorageUrlOptions.md +1 -1
- package/docs/api/interfaces/StyleImport.md +1 -1
- package/docs/api/interfaces/SwitchProps.md +1 -1
- package/docs/api/interfaces/ToastActionElement.md +1 -1
- package/docs/api/interfaces/ToastProps.md +1 -1
- package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
- package/docs/api/interfaces/UsePublicFileDisplayOptions.md +1 -1
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
- package/docs/api/interfaces/UserEventAccess.md +1 -1
- package/docs/api/interfaces/UserMenuProps.md +1 -1
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +55 -20
- package/docs/api-reference/hooks.md +53 -0
- package/docs/api-reference/providers.md +60 -0
- package/docs/core-concepts/authentication.md +2 -0
- package/docs/documentation-index.md +0 -2
- package/docs/implementation-guides/authentication.md +1 -0
- package/docs/rbac/README.md +114 -38
- package/docs/rbac/api-reference.md +63 -16
- package/docs/rbac/getting-started.md +16 -16
- package/docs/rbac/quick-start.md +110 -35
- package/docs/rbac/troubleshooting.md +125 -2
- package/docs/security/README.md +59 -0
- package/package.json +1 -1
- package/src/components/NavigationMenu/NavigationMenu.test.tsx +38 -4
- package/src/components/NavigationMenu/NavigationMenu.tsx +71 -6
- package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +2 -2
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +48 -16
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.security.test.tsx +2 -1
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.unit.test.tsx +9 -9
- package/src/index.ts +3 -0
- package/src/providers/services/AuthServiceProvider.tsx +4 -3
- package/src/providers/services/UnifiedAuthProvider.tsx +1 -1
- package/src/rbac/api.test.ts +2 -2
- package/src/rbac/api.ts +2 -1
- package/src/rbac/components/PagePermissionGuard.tsx +21 -38
- package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +1 -1
- package/src/rbac/config.ts +2 -0
- package/src/rbac/engine.ts +17 -5
- package/src/rbac/security.ts +1 -1
- package/src/services/AuthService.ts +79 -1
- package/src/services/__tests__/AuthService.test.ts +184 -0
- package/src/types/database.ts +21 -9
- package/src/types/rbac-functions.ts +2 -1
- package/src/utils/__tests__/sessionTracking.unit.test.ts +6 -171
- package/src/utils/sessionTracking.ts +7 -81
- package/dist/chunk-B3QX32P5.js.map +0 -1
- package/dist/chunk-MOMYOQMC.js.map +0 -1
- package/dist/chunk-NFPV7MRN.js +0 -94
- package/dist/chunk-NFPV7MRN.js.map +0 -1
- package/dist/chunk-S63MFSY6.js.map +0 -1
- package/docs/rbac/breaking-changes-v3.md +0 -222
- package/docs/rbac/migration-guide.md +0 -260
- package/src/providers/AuthProvider.simplified.tsx +0 -974
- package/dist/{DataTable-WFCHVWTY.js.map → DataTable-D3BK2FCN.js.map} +0 -0
- package/dist/{UnifiedAuthProvider-XU4BHFXZ.js.map → UnifiedAuthProvider-A7I23UCN.js.map} +0 -0
- package/dist/{api-KG4A2X7P.js.map → api-PIE4JRFS.js.map} +0 -0
- package/dist/{chunk-DMNMZKWS.js.map → chunk-2W4WKJVF.js.map} +0 -0
- package/dist/{chunk-VJ7MPS2K.js.map → chunk-AWK2FAUN.js.map} +0 -0
- package/dist/{chunk-LT6RKRA7.js.map → chunk-D6MEKC27.js.map} +0 -0
- package/dist/{chunk-KBG34SVL.js.map → chunk-EYSXQ756.js.map} +0 -0
- package/dist/{chunk-ZXY5NTJB.js.map → chunk-EZ64QG2I.js.map} +0 -0
- package/dist/{chunk-QDDUU625.js.map → chunk-HADXAZT3.js.map} +0 -0
- package/dist/{chunk-IMZGJ2X7.js.map → chunk-HGZSO43Y.js.map} +0 -0
- package/dist/{chunk-GVRSXXAA.js.map → chunk-YFMENCR4.js.map} +0 -0
- package/dist/{validation-D8VcbTzC.d.ts → validation-DnhrNMju.d.ts} +2 -2
|
@@ -247,43 +247,90 @@ function UserActions() {
|
|
|
247
247
|
|
|
248
248
|
### PagePermissionGuard
|
|
249
249
|
|
|
250
|
-
|
|
250
|
+
**⚠️ CRITICAL: This is the PRIMARY component for protecting pages.** Always use `PagePermissionGuard` for page-level permissions. It automatically resolves scope from context and checks permissions in the database.
|
|
251
|
+
|
|
252
|
+
**Permission Format**: The component checks for permissions in the format `{operation}:page.{pageName}` (e.g., `read:page.dashboard`).
|
|
251
253
|
|
|
252
254
|
```typescript
|
|
253
255
|
interface PagePermissionGuardProps {
|
|
254
|
-
pageName: string;
|
|
255
|
-
operation: 'read' | '
|
|
256
|
-
children: React.ReactNode;
|
|
257
|
-
fallback?: React.ReactNode;
|
|
258
|
-
strictMode?: boolean;
|
|
259
|
-
auditLog?: boolean;
|
|
260
|
-
pageId?:
|
|
261
|
-
scope?: Scope;
|
|
262
|
-
onDenied?: (pageName: string, operation: string
|
|
263
|
-
loading?: React.ReactNode;
|
|
256
|
+
pageName: string; // Page name (e.g., "dashboard", "users") - must match rbac_app_pages.page_name
|
|
257
|
+
operation: 'read' | 'create' | 'update' | 'delete'; // Operation being performed
|
|
258
|
+
children: React.ReactNode; // Content to render if user has permission
|
|
259
|
+
fallback?: React.ReactNode; // Content to render if user lacks permission (default: Access Denied component)
|
|
260
|
+
strictMode?: boolean; // Enable strict mode for security logging (default: true)
|
|
261
|
+
auditLog?: boolean; // Enable audit logging (default: true)
|
|
262
|
+
pageId?: string; // Optional page ID override (defaults to pageName)
|
|
263
|
+
scope?: Scope; // Optional scope override (defaults to resolving from context)
|
|
264
|
+
onDenied?: (pageName: string, operation: string) => void; // Callback when access denied
|
|
265
|
+
loading?: React.ReactNode; // Loading state component (default: "Checking permissions...")
|
|
264
266
|
}
|
|
265
267
|
```
|
|
266
268
|
|
|
267
|
-
#### Usage
|
|
269
|
+
#### Basic Usage
|
|
268
270
|
|
|
269
271
|
```tsx
|
|
270
272
|
import { PagePermissionGuard } from '@jmruthers/pace-core/rbac';
|
|
271
273
|
|
|
272
|
-
function
|
|
274
|
+
function DashboardPage() {
|
|
275
|
+
return (
|
|
276
|
+
<PagePermissionGuard
|
|
277
|
+
pageName="dashboard"
|
|
278
|
+
operation="read"
|
|
279
|
+
fallback={<div>You don't have permission to view the dashboard</div>}
|
|
280
|
+
>
|
|
281
|
+
<DashboardContent />
|
|
282
|
+
</PagePermissionGuard>
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
#### Multiple Operations
|
|
288
|
+
|
|
289
|
+
```tsx
|
|
290
|
+
function UsersPage() {
|
|
273
291
|
return (
|
|
274
292
|
<div>
|
|
293
|
+
{/* Read permission - show user list */}
|
|
275
294
|
<PagePermissionGuard
|
|
276
|
-
pageName="
|
|
295
|
+
pageName="users"
|
|
277
296
|
operation="read"
|
|
278
|
-
fallback={
|
|
297
|
+
fallback={null}
|
|
298
|
+
>
|
|
299
|
+
<UserList />
|
|
300
|
+
</PagePermissionGuard>
|
|
301
|
+
|
|
302
|
+
{/* Create permission - show add button */}
|
|
303
|
+
<PagePermissionGuard
|
|
304
|
+
pageName="users"
|
|
305
|
+
operation="create"
|
|
306
|
+
fallback={null}
|
|
279
307
|
>
|
|
280
|
-
<
|
|
308
|
+
<AddUserButton />
|
|
309
|
+
</PagePermissionGuard>
|
|
310
|
+
|
|
311
|
+
{/* Update permission - show edit buttons */}
|
|
312
|
+
<PagePermissionGuard
|
|
313
|
+
pageName="users"
|
|
314
|
+
operation="update"
|
|
315
|
+
fallback={null}
|
|
316
|
+
>
|
|
317
|
+
<EditUserButtons />
|
|
281
318
|
</PagePermissionGuard>
|
|
282
319
|
</div>
|
|
283
320
|
);
|
|
284
321
|
}
|
|
285
322
|
```
|
|
286
323
|
|
|
324
|
+
#### Important Notes
|
|
325
|
+
|
|
326
|
+
1. **Automatic Scope Resolution**: `PagePermissionGuard` automatically resolves `organisationId` from `UnifiedAuthProvider` context. You don't need to pass `scope` unless you need custom scope.
|
|
327
|
+
|
|
328
|
+
2. **Permission Format**: The component checks for `{operation}:page.{pageName}` in the database. Ensure your `rbac_page_permissions` table has entries matching this format.
|
|
329
|
+
|
|
330
|
+
3. **Loading State**: The component shows a loading state while checking permissions. Provide a custom `loading` prop if needed.
|
|
331
|
+
|
|
332
|
+
4. **Error Handling**: If permission check fails, the component shows the `fallback`. Check browser console for `[PagePermissionGuard]` logs to debug issues.
|
|
333
|
+
|
|
287
334
|
### NavigationGuard
|
|
288
335
|
|
|
289
336
|
Conditionally render navigation items based on permissions.
|
|
@@ -70,32 +70,32 @@ export default App;
|
|
|
70
70
|
|
|
71
71
|
## 🎯 **When to Use What Pattern**
|
|
72
72
|
|
|
73
|
-
### Pattern 1:
|
|
74
|
-
**Use when**: Protecting entire pages or
|
|
75
|
-
**Benefits**: Automatic scope resolution,
|
|
73
|
+
### Pattern 1: PagePermissionGuard (REQUIRED for Pages)
|
|
74
|
+
**Use when**: Protecting entire pages or page-level access
|
|
75
|
+
**Benefits**: Automatic scope resolution, page-level permission checking, strict mode enforcement
|
|
76
76
|
|
|
77
77
|
```tsx
|
|
78
|
-
//
|
|
79
|
-
import {
|
|
78
|
+
// pages/Dashboard.tsx
|
|
79
|
+
import { PagePermissionGuard } from '@jmruthers/pace-core/rbac';
|
|
80
80
|
|
|
81
81
|
function Dashboard() {
|
|
82
82
|
return (
|
|
83
|
-
<
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
{
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
fallback={<div>Access Denied</div>}
|
|
91
|
-
>
|
|
83
|
+
<PagePermissionGuard
|
|
84
|
+
pageName="dashboard"
|
|
85
|
+
operation="read"
|
|
86
|
+
fallback={<div>Access Denied</div>}
|
|
87
|
+
>
|
|
88
|
+
<div>
|
|
89
|
+
<h1>Dashboard</h1>
|
|
92
90
|
<EventContent />
|
|
93
|
-
</
|
|
94
|
-
</
|
|
91
|
+
</div>
|
|
92
|
+
</PagePermissionGuard>
|
|
95
93
|
);
|
|
96
94
|
}
|
|
97
95
|
```
|
|
98
96
|
|
|
97
|
+
**⚠️ CRITICAL**: Always use `PagePermissionGuard` for pages. It's the only component that properly checks page-level permissions against the database.
|
|
98
|
+
|
|
99
99
|
### Pattern 2: usePermissions (For Data Fetching)
|
|
100
100
|
**Use when**: You need the actual permission data for custom logic
|
|
101
101
|
**Requirements**: Must provide explicit scope
|
package/docs/rbac/quick-start.md
CHANGED
|
@@ -25,9 +25,11 @@ A simple user management app that demonstrates:
|
|
|
25
25
|
|
|
26
26
|
1. **ALWAYS call `setupRBAC()` first** - This is MANDATORY and must be called before any RBAC features
|
|
27
27
|
2. **Never make direct database queries** to `rbac_apps`, `rbac_global_roles`, or other RBAC tables
|
|
28
|
-
3. **
|
|
29
|
-
4. **
|
|
30
|
-
5. **Use the exact app name** from your environment variable (must match database exactly)
|
|
28
|
+
3. **ALWAYS use `PagePermissionGuard`** for page-level permissions - This is the ONLY way to properly protect pages
|
|
29
|
+
4. **ALWAYS set up providers correctly** in the exact order shown below
|
|
30
|
+
5. **Use the exact app name** from your environment variable (must match database exactly, case-sensitive)
|
|
31
|
+
6. **Ensure database setup is complete** before starting the app - App must be registered with pages and permissions
|
|
32
|
+
7. **User must have organisation role** - Users need roles assigned in `rbac_organisation_roles` table
|
|
31
33
|
|
|
32
34
|
## 🚀 Step-by-Step Implementation
|
|
33
35
|
|
|
@@ -127,8 +129,11 @@ WHERE a.name = 'user-manager';
|
|
|
127
129
|
|
|
128
130
|
#### 5.3 Set Up Page Permissions
|
|
129
131
|
|
|
132
|
+
**CRITICAL**: Page permissions use the format `{operation}:page.{pageName}` (e.g., `read:page.dashboard`). The database stores permissions per page per role per organisation.
|
|
133
|
+
|
|
130
134
|
```sql
|
|
131
135
|
-- Set up basic permissions for each page (replace 'user-manager' with your actual app name)
|
|
136
|
+
-- IMPORTANT: Replace the organisation_id UUID with your actual organisation ID
|
|
132
137
|
WITH app_pages AS (
|
|
133
138
|
SELECT ap.id as page_id, ap.page_name, a.id as app_id
|
|
134
139
|
FROM rbac_app_pages ap
|
|
@@ -146,10 +151,24 @@ SELECT
|
|
|
146
151
|
WHEN role.role_name = 'member' AND ap.page_name IN ('dashboard', 'users') THEN true
|
|
147
152
|
ELSE false
|
|
148
153
|
END,
|
|
149
|
-
'00000000-0000-0000-0000-000000000000'::uuid --
|
|
154
|
+
'00000000-0000-0000-0000-000000000000'::uuid -- ⚠️ REPLACE THIS with your actual organisation ID
|
|
150
155
|
FROM app_pages ap
|
|
151
156
|
CROSS JOIN (SELECT unnest(ARRAY['read', 'create', 'update', 'delete']) as operation) op
|
|
152
157
|
CROSS JOIN (SELECT unnest(ARRAY['org_admin', 'leader', 'member']) as role_name) role;
|
|
158
|
+
|
|
159
|
+
-- Verify permissions were created correctly
|
|
160
|
+
SELECT
|
|
161
|
+
a.name as app_name,
|
|
162
|
+
ap.page_name,
|
|
163
|
+
pp.operation,
|
|
164
|
+
pp.role_name,
|
|
165
|
+
pp.allowed,
|
|
166
|
+
pp.organisation_id
|
|
167
|
+
FROM rbac_page_permissions pp
|
|
168
|
+
JOIN rbac_app_pages ap ON pp.app_page_id = ap.id
|
|
169
|
+
JOIN rbac_apps a ON ap.app_id = a.id
|
|
170
|
+
WHERE a.name = 'user-manager'
|
|
171
|
+
ORDER BY ap.page_name, pp.operation, pp.role_name;
|
|
153
172
|
```
|
|
154
173
|
|
|
155
174
|
#### 5.4 Assign User Roles
|
|
@@ -202,28 +221,23 @@ if (!supabaseUrl || !supabaseAnonKey) {
|
|
|
202
221
|
export const supabase = createClient(supabaseUrl, supabaseAnonKey)
|
|
203
222
|
```
|
|
204
223
|
|
|
205
|
-
### 8. Initialize RBAC (MANDATORY
|
|
224
|
+
### 8. Initialize RBAC (MANDATORY!)
|
|
206
225
|
|
|
207
|
-
**CRITICAL**: You MUST call `setupRBAC()` before using any RBAC features.
|
|
226
|
+
**CRITICAL**: You MUST call `setupRBAC()` before using any RBAC features. This configures rate limiting (default: 1000 requests/minute) and enables security validation.
|
|
208
227
|
|
|
209
|
-
|
|
210
|
-
// src/lib/rbac-setup.ts
|
|
211
|
-
import { setupRBAC } from '@jmruthers/pace-core/rbac'
|
|
212
|
-
import { supabase } from './supabase'
|
|
213
|
-
|
|
214
|
-
// ⚠️ REQUIRED: Initialize RBAC before using any RBAC features
|
|
215
|
-
setupRBAC(supabase)
|
|
216
|
-
```
|
|
217
|
-
|
|
218
|
-
**Then import this in your main entry point:**
|
|
228
|
+
**Option 1: Setup in main entry point (Recommended)**
|
|
219
229
|
|
|
220
230
|
```typescript
|
|
221
231
|
// src/main.tsx
|
|
222
232
|
import React from 'react'
|
|
223
233
|
import ReactDOM from 'react-dom/client'
|
|
224
|
-
import '
|
|
234
|
+
import { setupRBAC } from '@jmruthers/pace-core/rbac'
|
|
235
|
+
import { supabase } from './lib/supabase'
|
|
225
236
|
import App from './App'
|
|
226
237
|
|
|
238
|
+
// ⚠️ CRITICAL: Call setupRBAC BEFORE rendering App
|
|
239
|
+
setupRBAC(supabase)
|
|
240
|
+
|
|
227
241
|
ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
228
242
|
<React.StrictMode>
|
|
229
243
|
<App />
|
|
@@ -231,18 +245,24 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
|
231
245
|
)
|
|
232
246
|
```
|
|
233
247
|
|
|
234
|
-
**
|
|
248
|
+
**Option 2: Setup in separate file (Alternative)**
|
|
235
249
|
|
|
236
250
|
```typescript
|
|
237
|
-
// src/
|
|
238
|
-
import React from 'react'
|
|
251
|
+
// src/lib/rbac-setup.ts
|
|
239
252
|
import { setupRBAC } from '@jmruthers/pace-core/rbac'
|
|
240
|
-
import { supabase } from './
|
|
253
|
+
import { supabase } from './supabase'
|
|
241
254
|
|
|
242
|
-
// ⚠️ REQUIRED:
|
|
243
|
-
setupRBAC(supabase
|
|
255
|
+
// ⚠️ REQUIRED: Initialize RBAC before using any RBAC features
|
|
256
|
+
setupRBAC(supabase, {
|
|
257
|
+
// Optional: Configure rate limiting for high-traffic apps
|
|
258
|
+
security: {
|
|
259
|
+
maxPermissionChecksPerMinute: 2000 // Increase from default 1000 if needed
|
|
260
|
+
}
|
|
261
|
+
})
|
|
244
262
|
|
|
245
|
-
//
|
|
263
|
+
// src/main.tsx
|
|
264
|
+
import './lib/rbac-setup' // Import BEFORE App
|
|
265
|
+
import App from './App'
|
|
246
266
|
```
|
|
247
267
|
|
|
248
268
|
### 9. App Setup (CRITICAL)
|
|
@@ -560,12 +580,25 @@ export function Users() {
|
|
|
560
580
|
|
|
561
581
|
Your RBAC setup is working correctly if:
|
|
562
582
|
|
|
583
|
+
**Setup Checklist:**
|
|
584
|
+
- [ ] `setupRBAC(supabase)` is called before rendering App
|
|
585
|
+
- [ ] App is registered in `rbac_apps` table with `is_active = true`
|
|
586
|
+
- [ ] Pages exist in `rbac_app_pages` for your app
|
|
587
|
+
- [ ] Page permissions exist in `rbac_page_permissions` for your pages, roles, and organisation
|
|
588
|
+
- [ ] User has an active role in `rbac_organisation_roles` for the organisation
|
|
589
|
+
- [ ] `VITE_APP_NAME` environment variable matches database `rbac_apps.name` exactly
|
|
590
|
+
|
|
591
|
+
**Runtime Checklist:**
|
|
563
592
|
- [ ] You can log in successfully
|
|
593
|
+
- [ ] Browser console shows "RBAC system initialized successfully"
|
|
564
594
|
- [ ] You see the dashboard without "Access Denied" messages
|
|
595
|
+
- [ ] `PagePermissionGuard` shows loading state, then renders content (not fallback)
|
|
565
596
|
- [ ] You can navigate to the users page
|
|
566
597
|
- [ ] You see "Success! Your RBAC setup is working correctly" on the users page
|
|
567
598
|
- [ ] No 400/406 errors in the browser console
|
|
568
599
|
- [ ] No "App not found" errors in the console
|
|
600
|
+
- [ ] No "STRICT MODE VIOLATION" errors in the console
|
|
601
|
+
- [ ] No "rate_limit_exceeded" errors (if you see these, increase rate limit in setupRBAC)
|
|
569
602
|
|
|
570
603
|
## 🚨 Troubleshooting
|
|
571
604
|
|
|
@@ -579,33 +612,75 @@ import { setupRBAC } from '@jmruthers/pace-core/rbac'
|
|
|
579
612
|
setupRBAC(supabase) // Must be called BEFORE rendering app
|
|
580
613
|
```
|
|
581
614
|
|
|
582
|
-
### Issue: "Access Denied" on all pages
|
|
615
|
+
### Issue: "Access Denied" or "STRICT MODE VIOLATION" on all pages
|
|
616
|
+
|
|
617
|
+
**This is the most common issue. Check these in EXACT ORDER:**
|
|
583
618
|
|
|
584
|
-
**
|
|
619
|
+
1. **Verify setupRBAC was called**:
|
|
620
|
+
- Check browser console for "RBAC system initialized successfully"
|
|
621
|
+
- If missing, add `setupRBAC(supabase)` in your main.tsx before rendering
|
|
585
622
|
|
|
586
|
-
|
|
623
|
+
2. **Verify your app is registered and active**:
|
|
587
624
|
```sql
|
|
588
|
-
SELECT
|
|
625
|
+
SELECT id, name, display_name, requires_event, is_active
|
|
626
|
+
FROM rbac_apps
|
|
627
|
+
WHERE name = 'user-manager';
|
|
589
628
|
```
|
|
629
|
+
- Must return exactly 1 row
|
|
630
|
+
- `is_active` must be `true`
|
|
631
|
+
- `name` must match your `VITE_APP_NAME` exactly (case-sensitive)
|
|
590
632
|
|
|
591
|
-
|
|
633
|
+
3. **Check your environment variable matches database**:
|
|
592
634
|
```typescript
|
|
593
|
-
console.log('App name:', import.meta.env.VITE_APP_NAME);
|
|
635
|
+
console.log('App name from env:', import.meta.env.VITE_APP_NAME);
|
|
636
|
+
```
|
|
637
|
+
- Must match database `rbac_apps.name` exactly
|
|
638
|
+
|
|
639
|
+
4. **Verify pages exist for your app**:
|
|
640
|
+
```sql
|
|
641
|
+
SELECT ap.id, ap.page_name, ap.page_description
|
|
642
|
+
FROM rbac_app_pages ap
|
|
643
|
+
JOIN rbac_apps a ON ap.app_id = a.id
|
|
644
|
+
WHERE a.name = 'user-manager'
|
|
645
|
+
ORDER BY ap.page_name;
|
|
594
646
|
```
|
|
647
|
+
- Must have pages for `dashboard`, `users`, etc.
|
|
595
648
|
|
|
596
|
-
|
|
649
|
+
5. **Verify user has organisation role**:
|
|
597
650
|
```sql
|
|
598
|
-
SELECT
|
|
651
|
+
SELECT ror.*, u.email
|
|
652
|
+
FROM rbac_organisation_roles ror
|
|
653
|
+
JOIN auth.users u ON ror.user_id = u.id
|
|
654
|
+
WHERE ror.user_id = 'your-user-id'::uuid
|
|
655
|
+
AND ror.status = 'active';
|
|
599
656
|
```
|
|
657
|
+
- User must have at least one active role
|
|
658
|
+
- Must be for the organisation you're testing with
|
|
600
659
|
|
|
601
|
-
|
|
660
|
+
6. **Check page permissions exist for user's role**:
|
|
602
661
|
```sql
|
|
603
|
-
SELECT
|
|
662
|
+
SELECT
|
|
663
|
+
ap.page_name,
|
|
664
|
+
pp.operation,
|
|
665
|
+
pp.role_name,
|
|
666
|
+
pp.allowed,
|
|
667
|
+
pp.organisation_id
|
|
604
668
|
FROM rbac_page_permissions pp
|
|
605
669
|
JOIN rbac_app_pages ap ON pp.app_page_id = ap.id
|
|
606
670
|
JOIN rbac_apps a ON ap.app_id = a.id
|
|
607
|
-
WHERE a.name = 'user-manager'
|
|
671
|
+
WHERE a.name = 'user-manager'
|
|
672
|
+
AND pp.role_name = 'org_admin' -- Replace with your user's role
|
|
673
|
+
AND pp.organisation_id = 'your-org-id'::uuid -- Replace with your org ID
|
|
674
|
+
AND pp.allowed = true;
|
|
608
675
|
```
|
|
676
|
+
- Must have permissions for the pages you're trying to access
|
|
677
|
+
- Must have `allowed = true` for the operation (read, create, etc.)
|
|
678
|
+
- `organisation_id` must match the organisation the user is accessing
|
|
679
|
+
|
|
680
|
+
7. **Check browser console for specific errors**:
|
|
681
|
+
- Look for `[PagePermissionGuard]` error messages
|
|
682
|
+
- Look for `[useCan]` permission check logs
|
|
683
|
+
- Look for rate limit errors (if you see these, you may need to increase the limit)
|
|
609
684
|
|
|
610
685
|
### Issue: 400/406 Database Errors
|
|
611
686
|
|
|
@@ -12,11 +12,134 @@ This guide helps you resolve common issues when using the PACE Core RBAC system.
|
|
|
12
12
|
|
|
13
13
|
## 🚨 Critical Issues (Fix Immediately)
|
|
14
14
|
|
|
15
|
-
### 1. "
|
|
15
|
+
### 1. "STRICT MODE VIOLATION" Error - Page Access Denied
|
|
16
|
+
|
|
17
|
+
**Symptoms:**
|
|
18
|
+
- Console shows `[PagePermissionGuard] STRICT MODE VIOLATION: User attempted to access protected page without permission`
|
|
19
|
+
- Error object shows: `{ pageName: "menus", operation: "read", userId: "...", scope: {...} }`
|
|
20
|
+
- Pages show fallback/access denied content even for users who should have permission
|
|
21
|
+
- All permission checks return `false`
|
|
22
|
+
|
|
23
|
+
**Root Cause:** One or more of these issues:
|
|
24
|
+
1. Missing or incorrect database setup (app, pages, or permissions)
|
|
25
|
+
2. User doesn't have organisation role for the organisation being accessed
|
|
26
|
+
3. Page permissions don't exist for user's role and organisation
|
|
27
|
+
4. `setupRBAC()` wasn't called before using RBAC features
|
|
28
|
+
5. Rate limiting is too restrictive (unlikely with default 1000/minute)
|
|
29
|
+
|
|
30
|
+
**Fix (Check in order):**
|
|
31
|
+
|
|
32
|
+
**Step 1: Verify setupRBAC was called**
|
|
33
|
+
```typescript
|
|
34
|
+
// Check browser console for this message:
|
|
35
|
+
// "RBAC system initialized successfully"
|
|
36
|
+
|
|
37
|
+
// If missing, add to main.tsx:
|
|
38
|
+
import { setupRBAC } from '@jmruthers/pace-core/rbac';
|
|
39
|
+
setupRBAC(supabase); // BEFORE rendering App
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**Step 2: Verify app registration**
|
|
43
|
+
```sql
|
|
44
|
+
-- Check app exists and is active
|
|
45
|
+
SELECT id, name, display_name, requires_event, is_active
|
|
46
|
+
FROM rbac_apps
|
|
47
|
+
WHERE name = 'your-app-name'; -- Replace with actual app name
|
|
48
|
+
|
|
49
|
+
-- Must return 1 row with is_active = true
|
|
50
|
+
-- name must match VITE_APP_NAME environment variable exactly
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**Step 3: Verify pages exist**
|
|
54
|
+
```sql
|
|
55
|
+
-- Check pages exist for your app
|
|
56
|
+
SELECT ap.id, ap.page_name, ap.page_description
|
|
57
|
+
FROM rbac_app_pages ap
|
|
58
|
+
JOIN rbac_apps a ON ap.app_id = a.id
|
|
59
|
+
WHERE a.name = 'your-app-name'
|
|
60
|
+
ORDER BY ap.page_name;
|
|
61
|
+
|
|
62
|
+
-- Must have a page matching your pageName prop (e.g., "menus")
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**Step 4: Verify user has organisation role**
|
|
66
|
+
```sql
|
|
67
|
+
-- Check user's role for the organisation
|
|
68
|
+
SELECT ror.*, u.email
|
|
69
|
+
FROM rbac_organisation_roles ror
|
|
70
|
+
JOIN auth.users u ON ror.user_id = u.id
|
|
71
|
+
WHERE ror.user_id = 'user-id-from-error'::uuid
|
|
72
|
+
AND ror.organisation_id = 'org-id-from-scope'::uuid -- From error scope
|
|
73
|
+
AND ror.status = 'active';
|
|
74
|
+
|
|
75
|
+
-- User must have at least one active role (e.g., 'org_admin', 'leader', 'member')
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**Step 5: Verify page permissions exist**
|
|
79
|
+
```sql
|
|
80
|
+
-- Check permissions for user's role, page, and organisation
|
|
81
|
+
SELECT
|
|
82
|
+
ap.page_name,
|
|
83
|
+
pp.operation,
|
|
84
|
+
pp.role_name,
|
|
85
|
+
pp.allowed,
|
|
86
|
+
pp.organisation_id
|
|
87
|
+
FROM rbac_page_permissions pp
|
|
88
|
+
JOIN rbac_app_pages ap ON pp.app_page_id = ap.id
|
|
89
|
+
JOIN rbac_apps a ON ap.app_id = a.id
|
|
90
|
+
WHERE a.name = 'your-app-name'
|
|
91
|
+
AND ap.page_name = 'menus' -- From error pageName
|
|
92
|
+
AND pp.operation = 'read' -- From error operation
|
|
93
|
+
AND pp.role_name = 'org_admin' -- From Step 4 role
|
|
94
|
+
AND pp.organisation_id = 'org-id-from-scope'::uuid -- From error scope
|
|
95
|
+
AND pp.allowed = true;
|
|
96
|
+
|
|
97
|
+
-- Must return at least 1 row with allowed = true
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**Step 6: Create missing permissions if needed**
|
|
101
|
+
```sql
|
|
102
|
+
-- Grant read permission for a page to a role
|
|
103
|
+
INSERT INTO rbac_page_permissions (app_page_id, operation, role_name, allowed, organisation_id)
|
|
104
|
+
SELECT
|
|
105
|
+
ap.id,
|
|
106
|
+
'read',
|
|
107
|
+
'org_admin', -- Or user's actual role
|
|
108
|
+
true,
|
|
109
|
+
'your-org-id'::uuid -- From error scope
|
|
110
|
+
FROM rbac_app_pages ap
|
|
111
|
+
JOIN rbac_apps a ON ap.app_id = a.id
|
|
112
|
+
WHERE a.name = 'your-app-name'
|
|
113
|
+
AND ap.page_name = 'menus';
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### 2. "rate_limit_exceeded" Error
|
|
117
|
+
|
|
118
|
+
**Symptoms:**
|
|
119
|
+
- Console shows `[RBAC Security] Object { type: "rate_limit_exceeded", userId: "...", details: {...} }`
|
|
120
|
+
- Permission checks stop working temporarily
|
|
121
|
+
- Users can't access pages they should have permission for
|
|
122
|
+
|
|
123
|
+
**Root Cause:** Default rate limit (1000 requests/minute) exceeded
|
|
124
|
+
|
|
125
|
+
**Fix:**
|
|
126
|
+
```typescript
|
|
127
|
+
// Increase rate limit in setupRBAC
|
|
128
|
+
import { setupRBAC } from '@jmruthers/pace-core/rbac';
|
|
129
|
+
|
|
130
|
+
setupRBAC(supabase, {
|
|
131
|
+
security: {
|
|
132
|
+
maxPermissionChecksPerMinute: 2000 // Increase from default 1000
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
**Note:** Rate limiting is per user, so if you have many components checking permissions on page load, you may need a higher limit.
|
|
138
|
+
|
|
139
|
+
### 3. "Access Denied" on All Pages - Missing App ID
|
|
16
140
|
|
|
17
141
|
**Symptoms:**
|
|
18
142
|
- Users see "Access Denied" on every page
|
|
19
|
-
- Console shows `[PagePermissionGuard] STRICT MODE VIOLATION` errors
|
|
20
143
|
- Console shows `scope: { organisationId: "...", eventId: "..." }` (missing `appId`)
|
|
21
144
|
- All permission checks return `false`
|
|
22
145
|
|
package/docs/security/README.md
CHANGED
|
@@ -16,6 +16,7 @@ PACE Core is designed with security as a first-class concern, providing:
|
|
|
16
16
|
- **Comprehensive RBAC system** with fine-grained permissions
|
|
17
17
|
- **Secure data access** with row-level security
|
|
18
18
|
- **Audit logging** for compliance and monitoring
|
|
19
|
+
- **Automatic login history tracking** for security audits and compliance
|
|
19
20
|
- **Input validation** and sanitization
|
|
20
21
|
- **XSS protection** and secure coding practices
|
|
21
22
|
- **Auto-logout on inactivity** for enhanced security
|
|
@@ -59,6 +60,64 @@ Sessions are automatically managed by PACE Core:
|
|
|
59
60
|
- **Session validation** on every request
|
|
60
61
|
- **Automatic logout** on token expiration
|
|
61
62
|
- **Inactivity auto-logout** after 30 minutes of inactivity (configurable)
|
|
63
|
+
- **Automatic login history tracking** - All user logins are automatically recorded
|
|
64
|
+
|
|
65
|
+
### 2.1 Login History Tracking
|
|
66
|
+
|
|
67
|
+
PACE Core **automatically tracks all user logins** for security auditing and compliance - no manual intervention required.
|
|
68
|
+
|
|
69
|
+
- **Fully Automatic** - Simply use `UnifiedAuthProvider` with `appName` prop - tracking happens automatically
|
|
70
|
+
- **No Configuration Needed** - No calls to tracking functions, no setup code, no manual intervention
|
|
71
|
+
- **Complete Audit Trail** - Records user ID, email, timestamp, IP address, user agent, and application context
|
|
72
|
+
- **Database Storage** - All login events are stored in `rbac_user_login_history` table automatically
|
|
73
|
+
- **Application Context** - Tracks which application the user logged into (when `appName` is provided)
|
|
74
|
+
- **Non-Blocking** - Tracking failures don't prevent authentication from succeeding
|
|
75
|
+
- **Privacy Compliant** - Users can only view their own login history (RLS enforced)
|
|
76
|
+
|
|
77
|
+
**How It Works:**
|
|
78
|
+
|
|
79
|
+
Login history tracking is **completely automatic** when you use `UnifiedAuthProvider`. Simply provide the `appName` prop (which is already required) and tracking happens automatically:
|
|
80
|
+
|
|
81
|
+
```tsx
|
|
82
|
+
import { UnifiedAuthProvider } from '@jmruthers/pace-core';
|
|
83
|
+
|
|
84
|
+
function App() {
|
|
85
|
+
return (
|
|
86
|
+
<UnifiedAuthProvider
|
|
87
|
+
supabaseClient={supabaseClient}
|
|
88
|
+
appName="MY_APP" // Optional: Enables app-specific tracking
|
|
89
|
+
// ... other props
|
|
90
|
+
>
|
|
91
|
+
<YourApp />
|
|
92
|
+
</UnifiedAuthProvider>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
**Querying Login History:**
|
|
98
|
+
|
|
99
|
+
Login history can be queried directly from the database:
|
|
100
|
+
|
|
101
|
+
```sql
|
|
102
|
+
-- Get user's login history
|
|
103
|
+
SELECT
|
|
104
|
+
login_timestamp,
|
|
105
|
+
email,
|
|
106
|
+
ip_address,
|
|
107
|
+
user_agent,
|
|
108
|
+
app_id
|
|
109
|
+
FROM rbac_user_login_history
|
|
110
|
+
WHERE user_id = auth.uid()
|
|
111
|
+
ORDER BY login_timestamp DESC
|
|
112
|
+
LIMIT 100;
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
**Security Notes:**
|
|
116
|
+
|
|
117
|
+
- Login history insertion uses `SECURITY DEFINER` functions (bypasses RLS)
|
|
118
|
+
- RLS policies ensure users can only view their own login history
|
|
119
|
+
- Failed tracking attempts are logged but don't break authentication
|
|
120
|
+
- All tracking is asynchronous and non-blocking
|
|
62
121
|
|
|
63
122
|
```tsx
|
|
64
123
|
import { useUnifiedAuth } from '@jmruthers/pace-core';
|
package/package.json
CHANGED
|
@@ -64,12 +64,12 @@ const mockUseResolvedScope = vi.hoisted(() => vi.fn(() => ({
|
|
|
64
64
|
})));
|
|
65
65
|
|
|
66
66
|
const mockUsePermissions = vi.hoisted(() => vi.fn(() => ({
|
|
67
|
-
permissions: {} as any,
|
|
67
|
+
permissions: { '*': true } as any, // Grant all permissions by default (super admin access)
|
|
68
68
|
isLoading: false,
|
|
69
69
|
error: null,
|
|
70
|
-
hasPermission: vi.fn(() =>
|
|
71
|
-
hasAnyPermission: vi.fn(() =>
|
|
72
|
-
hasAllPermissions: vi.fn(() =>
|
|
70
|
+
hasPermission: vi.fn(() => true), // Default to allowing all permissions
|
|
71
|
+
hasAnyPermission: vi.fn(() => true), // Default to allowing all permissions
|
|
72
|
+
hasAllPermissions: vi.fn(() => true), // Default to allowing all permissions
|
|
73
73
|
refetch: vi.fn(),
|
|
74
74
|
})));
|
|
75
75
|
|
|
@@ -137,6 +137,40 @@ describe('NavigationMenu Component', () => {
|
|
|
137
137
|
console.log = vi.fn();
|
|
138
138
|
console.warn = vi.fn();
|
|
139
139
|
console.error = vi.fn();
|
|
140
|
+
|
|
141
|
+
// Reset mocks to default permissive state (super admin access)
|
|
142
|
+
// Tests that need to test permission filtering can override these
|
|
143
|
+
mockUsePermissions.mockReturnValue({
|
|
144
|
+
permissions: { '*': true } as any,
|
|
145
|
+
isLoading: false,
|
|
146
|
+
error: null,
|
|
147
|
+
hasPermission: vi.fn(() => true),
|
|
148
|
+
hasAnyPermission: vi.fn(() => true),
|
|
149
|
+
hasAllPermissions: vi.fn(() => true),
|
|
150
|
+
refetch: vi.fn(),
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
mockUseRBAC.mockReturnValue({
|
|
154
|
+
user: { id: 'test-user', email: 'test@example.com' },
|
|
155
|
+
globalRole: null,
|
|
156
|
+
organisationRole: null,
|
|
157
|
+
eventAppRole: null,
|
|
158
|
+
hasPermission: vi.fn(),
|
|
159
|
+
hasGlobalPermission: vi.fn(),
|
|
160
|
+
isSuperAdmin: false,
|
|
161
|
+
isOrgAdmin: false,
|
|
162
|
+
isEventAdmin: false,
|
|
163
|
+
canManageOrganisation: false,
|
|
164
|
+
canManageEvent: false,
|
|
165
|
+
isLoading: false,
|
|
166
|
+
error: null,
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
mockUseResolvedScope.mockReturnValue({
|
|
170
|
+
resolvedScope: { organisationId: 'test-org-1', eventId: undefined, appId: undefined },
|
|
171
|
+
isLoading: false,
|
|
172
|
+
});
|
|
173
|
+
|
|
140
174
|
// Note: hasPermission, hasRole, hasAccessLevel were removed from UnifiedAuthProvider
|
|
141
175
|
// Use useRBAC() hook for permissions instead
|
|
142
176
|
});
|