@jmruthers/pace-core 0.5.114 → 0.5.116
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/dist/{AuthService-CVgsgtaZ.d.ts → AuthService-D4646R4b.d.ts} +9 -4
- package/dist/{DataTable-3JRLZXER.js → DataTable-ZOAKQ3SU.js} +10 -9
- package/dist/{UnifiedAuthProvider-KZZUO27W.js → UnifiedAuthProvider-YFN7YGVN.js} +4 -3
- package/dist/{api-PKU4PUBO.js → api-TNIBJWLM.js} +3 -3
- package/dist/{audit-H4YJJF7R.js → audit-T36HM7IM.js} +2 -2
- package/dist/{chunk-4OX5PXHX.js → chunk-2GJ5GL77.js} +4 -5
- package/dist/chunk-2GJ5GL77.js.map +1 -0
- package/dist/{chunk-5YIZFEUQ.js → chunk-2LM4QQGH.js} +31 -35
- package/dist/chunk-2LM4QQGH.js.map +1 -0
- package/dist/{chunk-3OGQLOJM.js → chunk-3DBFLLLU.js} +30 -1
- package/dist/chunk-3DBFLLLU.js.map +1 -0
- package/dist/{chunk-KTHLNIMA.js → chunk-ECOVPXYS.js} +13 -62
- package/dist/chunk-ECOVPXYS.js.map +1 -0
- package/dist/{chunk-OO3V7W4H.js → chunk-KA3PSVNV.js} +87 -40
- package/dist/chunk-KA3PSVNV.js.map +1 -0
- package/dist/{chunk-HKWQN44G.js → chunk-KMPWND3F.js} +15 -15
- package/dist/{chunk-L36JW4KV.js → chunk-LFS45U62.js} +2 -2
- package/dist/{chunk-NEONKMTU.js → chunk-LZYHAL7Y.js} +9 -4
- package/dist/{chunk-NEONKMTU.js.map → chunk-LZYHAL7Y.js.map} +1 -1
- package/dist/{chunk-BUN7NMV7.js → chunk-O3FTRYEU.js} +2 -2
- package/dist/{chunk-F6QB26OS.js → chunk-P3PUOL6B.js} +80 -8
- package/dist/chunk-P3PUOL6B.js.map +1 -0
- package/dist/{chunk-ZPXWJA4H.js → chunk-PHDAXDHB.js} +131 -5
- package/dist/chunk-PHDAXDHB.js.map +1 -0
- package/dist/chunk-UJI6WSMD.js +201 -0
- package/dist/{chunk-5CDJCTOO.js.map → chunk-UJI6WSMD.js.map} +1 -1
- package/dist/{chunk-JHWQNJP3.js → chunk-UKZWNQMB.js} +65 -19
- package/dist/{chunk-JHWQNJP3.js.map → chunk-UKZWNQMB.js.map} +1 -1
- package/dist/{chunk-7H75SHXZ.js → chunk-VN3OOE35.js} +2 -2
- package/dist/{chunk-QKIVSZ2O.js → chunk-WP5I5GLN.js} +2 -2
- package/dist/components.d.ts +1 -1
- package/dist/components.js +12 -11
- package/dist/components.js.map +1 -1
- package/dist/hooks.d.ts +1 -1
- package/dist/hooks.js +10 -9
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +19 -16
- package/dist/index.js.map +1 -1
- package/dist/providers.d.ts +2 -2
- package/dist/providers.js +3 -2
- package/dist/rbac/index.d.ts +82 -1
- package/dist/rbac/index.js +13 -10
- package/dist/{useToast-DRah6K-g.d.ts → useToast-Cs_g32bg.d.ts} +8 -6
- package/dist/utils.js +6 -4
- package/dist/utils.js.map +1 -1
- package/dist/validation.js +3 -1
- package/dist/validation.js.map +1 -1
- package/docs/README.md +4 -0
- 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 +35 -12
- package/docs/api/classes/RBACCache.md +1 -1
- package/docs/api/classes/RBACEngine.md +1 -1
- 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 +1 -1
- 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/EventAppRoleData.md +71 -0
- 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/GrantEventAppRoleParams.md +122 -0
- 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 +27 -27
- 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 +1 -1
- package/docs/api/interfaces/RBACLogger.md +1 -1
- package/docs/api/interfaces/RevokeEventAppRoleParams.md +100 -0
- package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
- package/docs/api/interfaces/RoleManagementResult.md +52 -0
- 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 +43 -16
- package/docs/architecture/rpc-function-standards.md +193 -0
- package/package.json +1 -1
- package/src/__tests__/TEST_STANDARD.md +244 -2
- package/src/components/DataTable/__tests__/a11y.basic.test.tsx +46 -16
- package/src/components/DataTable/__tests__/keyboard.test.tsx +276 -217
- package/src/components/DataTable/components/DataTableCore.tsx +32 -17
- package/src/components/DataTable/components/DataTableToolbar.tsx +3 -2
- package/src/components/DataTable/components/EditableRow.tsx +18 -1
- package/src/components/DataTable/components/ImportModal.tsx +25 -2
- package/src/components/DataTable/components/ViewRowModal.tsx +1 -1
- package/src/components/DataTable/components/__tests__/AccessDeniedPage.test.tsx +735 -0
- package/src/components/DataTable/components/__tests__/BulkOperationsDropdown.test.tsx +572 -0
- package/src/components/DataTable/components/__tests__/ColumnVisibilityDropdown.test.tsx +708 -0
- package/src/components/DataTable/components/__tests__/DataTableErrorBoundary.test.tsx +451 -0
- package/src/components/DataTable/components/__tests__/DataTableModals.test.tsx +456 -0
- package/src/components/DataTable/components/__tests__/EditableRow.test.tsx +454 -0
- package/src/components/DataTable/components/__tests__/ExpandButton.test.tsx +462 -0
- package/src/components/DataTable/components/__tests__/FilterRow.test.tsx +423 -0
- package/src/components/DataTable/components/__tests__/GroupHeader.test.tsx +393 -0
- package/src/components/DataTable/components/__tests__/GroupingDropdown.test.tsx +617 -0
- package/src/components/DataTable/components/__tests__/ImportModal.test.tsx +734 -0
- package/src/components/DataTable/components/__tests__/ViewRowModal.test.tsx +412 -0
- package/src/components/DataTable/hooks/useTableHandlers.ts +4 -0
- package/src/components/EventSelector/EventSelector.tsx +5 -25
- package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +12 -7
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +4 -0
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.accessibility.test.tsx +7 -2
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.integration.test.tsx +13 -8
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.performance.test.tsx +109 -100
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.security.test.tsx +18 -13
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.unit.test.tsx +17 -12
- package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +2 -0
- package/src/components/PaceLoginPage/PaceLoginPage.tsx +11 -1
- package/src/components/PasswordReset/PasswordChangeForm.test.tsx +2 -2
- package/src/components/ProtectedRoute/ProtectedRoute.test.tsx +648 -0
- package/src/components/ProtectedRoute/ProtectedRoute.tsx +10 -7
- package/src/components/PublicLayout/__tests__/PublicErrorBoundary.test.tsx +4 -12
- package/src/components/Select/Select.tsx +8 -0
- package/src/components/Toast/Toast.test.tsx +8 -7
- package/src/components/Toast/Toast.tsx +4 -4
- package/src/hooks/__tests__/usePublicEvent.simple.test.ts +367 -3
- package/src/hooks/__tests__/usePublicFileDisplay.test.ts +916 -0
- package/src/hooks/useEventTheme.ts +49 -18
- package/src/hooks/usePermissionCache.ts +5 -3
- package/src/hooks/useSecureDataAccess.ts +11 -1
- package/src/hooks/useToast.ts +11 -12
- package/src/providers/services/EventServiceProvider.tsx +15 -8
- package/src/rbac/__tests__/cache-invalidation.test.ts +385 -0
- package/src/rbac/audit.test.ts +206 -0
- package/src/rbac/audit.ts +37 -2
- package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +26 -23
- package/src/rbac/errors.test.ts +340 -0
- package/src/rbac/hooks/index.ts +9 -0
- package/src/rbac/hooks/useResolvedScope.test.ts +1063 -0
- package/src/rbac/hooks/useRoleManagement.test.ts +908 -0
- package/src/rbac/hooks/useRoleManagement.ts +255 -0
- package/src/services/AuthService.ts +10 -0
- package/src/services/EventService.ts +111 -50
- package/src/services/__tests__/AuthService.test.ts +1 -1
- package/src/services/__tests__/EventService.test.ts +60 -45
- package/src/services/interfaces/IEventService.ts +1 -1
- package/src/utils/__tests__/deviceFingerprint.unit.test.ts +320 -0
- package/src/utils/__tests__/logger.unit.test.ts +398 -0
- package/src/utils/__tests__/validation.unit.test.ts +225 -1
- package/src/utils/file-reference.test.ts +214 -0
- package/dist/chunk-3OGQLOJM.js.map +0 -1
- package/dist/chunk-4OX5PXHX.js.map +0 -1
- package/dist/chunk-5CDJCTOO.js +0 -190
- package/dist/chunk-5YIZFEUQ.js.map +0 -1
- package/dist/chunk-F6QB26OS.js.map +0 -1
- package/dist/chunk-KTHLNIMA.js.map +0 -1
- package/dist/chunk-OO3V7W4H.js.map +0 -1
- package/dist/chunk-ZPXWJA4H.js.map +0 -1
- package/src/rbac/audit-enhanced.ts +0 -351
- /package/dist/{DataTable-3JRLZXER.js.map → DataTable-ZOAKQ3SU.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-KZZUO27W.js.map → UnifiedAuthProvider-YFN7YGVN.js.map} +0 -0
- /package/dist/{api-PKU4PUBO.js.map → api-TNIBJWLM.js.map} +0 -0
- /package/dist/{audit-H4YJJF7R.js.map → audit-T36HM7IM.js.map} +0 -0
- /package/dist/{chunk-HKWQN44G.js.map → chunk-KMPWND3F.js.map} +0 -0
- /package/dist/{chunk-L36JW4KV.js.map → chunk-LFS45U62.js.map} +0 -0
- /package/dist/{chunk-BUN7NMV7.js.map → chunk-O3FTRYEU.js.map} +0 -0
- /package/dist/{chunk-7H75SHXZ.js.map → chunk-VN3OOE35.js.map} +0 -0
- /package/dist/{chunk-QKIVSZ2O.js.map → chunk-WP5I5GLN.js.map} +0 -0
package/src/rbac/audit.test.ts
CHANGED
|
@@ -349,6 +349,212 @@ describe('RBACAuditManager', () => {
|
|
|
349
349
|
});
|
|
350
350
|
});
|
|
351
351
|
|
|
352
|
+
describe('Fallback Logging', () => {
|
|
353
|
+
let consoleLogSpy: any;
|
|
354
|
+
let consoleWarnSpy: any;
|
|
355
|
+
|
|
356
|
+
beforeEach(() => {
|
|
357
|
+
consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
358
|
+
consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
afterEach(() => {
|
|
362
|
+
consoleLogSpy.mockRestore();
|
|
363
|
+
consoleWarnSpy.mockRestore();
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
it('uses fallback logging when database insert fails', async () => {
|
|
367
|
+
const dbError = {
|
|
368
|
+
message: 'Database connection failed',
|
|
369
|
+
code: 'PGRST116',
|
|
370
|
+
details: 'Connection timeout',
|
|
371
|
+
hint: 'Check network connectivity'
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
mockSupabase.from().insert.mockResolvedValue({
|
|
375
|
+
data: null,
|
|
376
|
+
error: dbError
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
const event: PermissionCheckAuditEvent = {
|
|
380
|
+
type: 'permission_check',
|
|
381
|
+
userId: 'user-123' as UUID,
|
|
382
|
+
organisationId: 'org-456' as UUID,
|
|
383
|
+
permission: 'read:users',
|
|
384
|
+
decision: true,
|
|
385
|
+
source: 'api' as AuditEventSource,
|
|
386
|
+
duration_ms: 100
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
await auditManager.emitPermissionCheck(event);
|
|
390
|
+
|
|
391
|
+
// Verify fallback logging was called
|
|
392
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
393
|
+
'[RBAC Audit Fallback]',
|
|
394
|
+
expect.objectContaining({
|
|
395
|
+
timestamp: expect.any(String),
|
|
396
|
+
event: expect.objectContaining({
|
|
397
|
+
type: 'permission_check',
|
|
398
|
+
userId: 'user-123',
|
|
399
|
+
organisationId: 'org-456',
|
|
400
|
+
permission: 'read:users'
|
|
401
|
+
}),
|
|
402
|
+
error: 'Database connection failed',
|
|
403
|
+
note: 'Database audit logging failed, using console fallback'
|
|
404
|
+
})
|
|
405
|
+
);
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
it('uses fallback logging when exception occurs', async () => {
|
|
409
|
+
const networkError = new Error('Network error');
|
|
410
|
+
mockSupabase.from().insert.mockRejectedValue(networkError);
|
|
411
|
+
|
|
412
|
+
const event: PermissionCheckAuditEvent = {
|
|
413
|
+
type: 'permission_check',
|
|
414
|
+
userId: 'user-123' as UUID,
|
|
415
|
+
organisationId: 'org-456' as UUID,
|
|
416
|
+
permission: 'read:users',
|
|
417
|
+
decision: true,
|
|
418
|
+
source: 'api' as AuditEventSource,
|
|
419
|
+
duration_ms: 100
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
await auditManager.emitPermissionCheck(event);
|
|
423
|
+
|
|
424
|
+
// Verify fallback logging was called
|
|
425
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
426
|
+
'[RBAC Audit Fallback]',
|
|
427
|
+
expect.objectContaining({
|
|
428
|
+
timestamp: expect.any(String),
|
|
429
|
+
event: expect.objectContaining({
|
|
430
|
+
type: 'permission_check',
|
|
431
|
+
userId: 'user-123',
|
|
432
|
+
organisationId: 'org-456',
|
|
433
|
+
permission: 'read:users',
|
|
434
|
+
decision: true,
|
|
435
|
+
source: 'api',
|
|
436
|
+
duration_ms: 100
|
|
437
|
+
}),
|
|
438
|
+
error: 'Network error', // Error is converted to string message
|
|
439
|
+
note: 'Database audit logging failed, using console fallback'
|
|
440
|
+
})
|
|
441
|
+
);
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
it('can disable fallback logging', async () => {
|
|
445
|
+
auditManager.setFallbackEnabled(false);
|
|
446
|
+
|
|
447
|
+
const dbError = {
|
|
448
|
+
message: 'Database error',
|
|
449
|
+
code: 'PGRST116'
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
mockSupabase.from().insert.mockResolvedValue({
|
|
453
|
+
data: null,
|
|
454
|
+
error: dbError
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
const event: PermissionCheckAuditEvent = {
|
|
458
|
+
type: 'permission_check',
|
|
459
|
+
userId: 'user-123' as UUID,
|
|
460
|
+
organisationId: 'org-456' as UUID,
|
|
461
|
+
permission: 'read:users',
|
|
462
|
+
decision: true,
|
|
463
|
+
source: 'api' as AuditEventSource,
|
|
464
|
+
duration_ms: 100
|
|
465
|
+
};
|
|
466
|
+
|
|
467
|
+
await auditManager.emitPermissionCheck(event);
|
|
468
|
+
|
|
469
|
+
// Verify fallback logging was NOT called
|
|
470
|
+
expect(consoleLogSpy).not.toHaveBeenCalled();
|
|
471
|
+
|
|
472
|
+
// But warning should still be logged
|
|
473
|
+
expect(consoleWarnSpy).toHaveBeenCalled();
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
it('can re-enable fallback logging', async () => {
|
|
477
|
+
auditManager.setFallbackEnabled(false);
|
|
478
|
+
auditManager.setFallbackEnabled(true);
|
|
479
|
+
|
|
480
|
+
const dbError = {
|
|
481
|
+
message: 'Database error',
|
|
482
|
+
code: 'PGRST116'
|
|
483
|
+
};
|
|
484
|
+
|
|
485
|
+
mockSupabase.from().insert.mockResolvedValue({
|
|
486
|
+
data: null,
|
|
487
|
+
error: dbError
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
const event: PermissionCheckAuditEvent = {
|
|
491
|
+
type: 'permission_check',
|
|
492
|
+
userId: 'user-123' as UUID,
|
|
493
|
+
organisationId: 'org-456' as UUID,
|
|
494
|
+
permission: 'read:users',
|
|
495
|
+
decision: true,
|
|
496
|
+
source: 'api' as AuditEventSource,
|
|
497
|
+
duration_ms: 100
|
|
498
|
+
};
|
|
499
|
+
|
|
500
|
+
await auditManager.emitPermissionCheck(event);
|
|
501
|
+
|
|
502
|
+
// Verify fallback logging was called after re-enabling
|
|
503
|
+
expect(consoleLogSpy).toHaveBeenCalled();
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
it('fallback logging includes correct event data', async () => {
|
|
507
|
+
const dbError = {
|
|
508
|
+
message: 'Database error'
|
|
509
|
+
};
|
|
510
|
+
|
|
511
|
+
mockSupabase.from().insert.mockResolvedValue({
|
|
512
|
+
data: null,
|
|
513
|
+
error: dbError
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
const event: PermissionCheckAuditEvent = {
|
|
517
|
+
type: 'permission_check',
|
|
518
|
+
userId: 'user-123' as UUID,
|
|
519
|
+
organisationId: 'org-456' as UUID,
|
|
520
|
+
eventId: 'event-789',
|
|
521
|
+
appId: 'app-101' as UUID,
|
|
522
|
+
permission: 'read:users',
|
|
523
|
+
decision: false,
|
|
524
|
+
source: 'ui' as AuditEventSource,
|
|
525
|
+
bypass: true,
|
|
526
|
+
duration_ms: 250,
|
|
527
|
+
cache_hit: true,
|
|
528
|
+
cache_source: 'memory',
|
|
529
|
+
metadata: { test: 'data' }
|
|
530
|
+
};
|
|
531
|
+
|
|
532
|
+
await auditManager.emitPermissionCheck(event);
|
|
533
|
+
|
|
534
|
+
const fallbackCall = consoleLogSpy.mock.calls[0];
|
|
535
|
+
expect(fallbackCall[0]).toBe('[RBAC Audit Fallback]');
|
|
536
|
+
expect(fallbackCall[1]).toMatchObject({
|
|
537
|
+
event: {
|
|
538
|
+
type: 'permission_check',
|
|
539
|
+
userId: 'user-123',
|
|
540
|
+
organisationId: 'org-456',
|
|
541
|
+
eventId: 'event-789',
|
|
542
|
+
appId: 'app-101',
|
|
543
|
+
permission: 'read:users',
|
|
544
|
+
decision: false,
|
|
545
|
+
source: 'ui',
|
|
546
|
+
bypass: true,
|
|
547
|
+
duration_ms: 250,
|
|
548
|
+
cache_hit: true,
|
|
549
|
+
cache_source: 'memory',
|
|
550
|
+
metadata: { test: 'data' }
|
|
551
|
+
},
|
|
552
|
+
error: 'Database error',
|
|
553
|
+
note: 'Database audit logging failed, using console fallback'
|
|
554
|
+
});
|
|
555
|
+
});
|
|
556
|
+
});
|
|
557
|
+
|
|
352
558
|
describe('Event Retrieval', () => {
|
|
353
559
|
it('retrieves audit events for user', async () => {
|
|
354
560
|
const mockEvents = [
|
package/src/rbac/audit.ts
CHANGED
|
@@ -108,6 +108,7 @@ export type AuditEventPayload =
|
|
|
108
108
|
export class RBACAuditManager {
|
|
109
109
|
private supabase: SupabaseClient<Database>;
|
|
110
110
|
private enabled: boolean = true;
|
|
111
|
+
private fallbackEnabled: boolean = true;
|
|
111
112
|
|
|
112
113
|
constructor(supabase: SupabaseClient<Database>) {
|
|
113
114
|
this.supabase = supabase;
|
|
@@ -131,6 +132,15 @@ export class RBACAuditManager {
|
|
|
131
132
|
return this.enabled;
|
|
132
133
|
}
|
|
133
134
|
|
|
135
|
+
/**
|
|
136
|
+
* Enable or disable fallback logging (console logging when database fails)
|
|
137
|
+
*
|
|
138
|
+
* @param enabled - Whether to enable fallback logging
|
|
139
|
+
*/
|
|
140
|
+
setFallbackEnabled(enabled: boolean): void {
|
|
141
|
+
this.fallbackEnabled = enabled;
|
|
142
|
+
}
|
|
143
|
+
|
|
134
144
|
/**
|
|
135
145
|
* Emit an audit event
|
|
136
146
|
*
|
|
@@ -211,7 +221,7 @@ export class RBACAuditManager {
|
|
|
211
221
|
.insert([auditEvent]);
|
|
212
222
|
|
|
213
223
|
if (error) {
|
|
214
|
-
// Log the error for debugging
|
|
224
|
+
// Log the error for debugging
|
|
215
225
|
console.warn('[RBAC Audit] Failed to insert audit event:', {
|
|
216
226
|
error: error.message,
|
|
217
227
|
code: error.code,
|
|
@@ -219,13 +229,38 @@ export class RBACAuditManager {
|
|
|
219
229
|
hint: error.hint,
|
|
220
230
|
event: auditEvent
|
|
221
231
|
});
|
|
232
|
+
|
|
233
|
+
// Use fallback logging if enabled
|
|
234
|
+
if (this.fallbackEnabled) {
|
|
235
|
+
this.logFallbackEvent(event, error);
|
|
236
|
+
}
|
|
222
237
|
}
|
|
223
238
|
} catch (error) {
|
|
224
|
-
// Log unexpected errors
|
|
239
|
+
// Log unexpected errors
|
|
225
240
|
console.error('[RBAC Audit] Unexpected error during audit logging:', error);
|
|
241
|
+
|
|
242
|
+
// Use fallback logging if enabled
|
|
243
|
+
if (this.fallbackEnabled) {
|
|
244
|
+
this.logFallbackEvent(event, error);
|
|
245
|
+
}
|
|
226
246
|
}
|
|
227
247
|
}
|
|
228
248
|
|
|
249
|
+
/**
|
|
250
|
+
* Log event to console as fallback when database logging fails
|
|
251
|
+
*
|
|
252
|
+
* @param event - Audit event payload
|
|
253
|
+
* @param error - The error that occurred
|
|
254
|
+
*/
|
|
255
|
+
private logFallbackEvent(event: AuditEventPayload, error: any): void {
|
|
256
|
+
console.log('[RBAC Audit Fallback]', {
|
|
257
|
+
timestamp: new Date().toISOString(),
|
|
258
|
+
event,
|
|
259
|
+
error: error?.message || error,
|
|
260
|
+
note: 'Database audit logging failed, using console fallback'
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
|
|
229
264
|
/**
|
|
230
265
|
* Emit a permission check audit event
|
|
231
266
|
*
|
|
@@ -711,9 +711,10 @@ describe('PagePermissionGuard Component', () => {
|
|
|
711
711
|
});
|
|
712
712
|
|
|
713
713
|
describe('Security Features', () => {
|
|
714
|
-
it('
|
|
714
|
+
it('invokes onDenied callback when strict mode blocks access', async () => {
|
|
715
715
|
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
716
|
-
|
|
716
|
+
const onDenied = vi.fn();
|
|
717
|
+
|
|
717
718
|
mockUseCan.mockReturnValue({
|
|
718
719
|
can: false,
|
|
719
720
|
isLoading: false,
|
|
@@ -726,61 +727,63 @@ describe('PagePermissionGuard Component', () => {
|
|
|
726
727
|
operation={mockOperation}
|
|
727
728
|
strictMode={true}
|
|
728
729
|
fallback={<TestFallback />}
|
|
730
|
+
onDenied={onDenied}
|
|
729
731
|
>
|
|
730
732
|
<TestComponent>Protected Page</TestComponent>
|
|
731
733
|
</PagePermissionGuard>
|
|
732
734
|
);
|
|
733
735
|
|
|
734
736
|
await waitFor(() => {
|
|
735
|
-
expect(screen.
|
|
737
|
+
expect(screen.getByTestId('test-fallback')).toBeInTheDocument();
|
|
736
738
|
}, { interval: 10 });
|
|
737
739
|
|
|
738
|
-
expect(
|
|
739
|
-
expect.stringContaining('STRICT MODE VIOLATION'),
|
|
740
|
-
expect.objectContaining({
|
|
741
|
-
pageName: mockPageName,
|
|
742
|
-
operation: mockOperation,
|
|
743
|
-
userId: 'user-123'
|
|
744
|
-
})
|
|
745
|
-
);
|
|
740
|
+
expect(onDenied).toHaveBeenCalledWith(mockPageName, mockOperation);
|
|
746
741
|
|
|
747
742
|
consoleSpy.mockRestore();
|
|
748
743
|
});
|
|
749
744
|
|
|
750
|
-
it('
|
|
745
|
+
it('does not call onDenied multiple times when audit logging rerenders', async () => {
|
|
751
746
|
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
752
|
-
|
|
747
|
+
const onDenied = vi.fn();
|
|
748
|
+
|
|
753
749
|
mockUseCan.mockReturnValue({
|
|
754
750
|
can: false,
|
|
755
751
|
isLoading: false,
|
|
756
752
|
error: null
|
|
757
753
|
});
|
|
758
754
|
|
|
759
|
-
render(
|
|
755
|
+
const { rerender } = render(
|
|
760
756
|
<PagePermissionGuard
|
|
761
757
|
pageName={mockPageName}
|
|
762
758
|
operation={mockOperation}
|
|
763
759
|
auditLog={true}
|
|
764
760
|
fallback={<TestFallback />}
|
|
761
|
+
onDenied={onDenied}
|
|
765
762
|
>
|
|
766
763
|
<TestComponent>Protected Page</TestComponent>
|
|
767
764
|
</PagePermissionGuard>
|
|
768
765
|
);
|
|
769
766
|
|
|
770
767
|
await waitFor(() => {
|
|
771
|
-
expect(screen.
|
|
768
|
+
expect(screen.getByTestId('test-fallback')).toBeInTheDocument();
|
|
772
769
|
}, { interval: 10 });
|
|
773
770
|
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
771
|
+
rerender(
|
|
772
|
+
<PagePermissionGuard
|
|
773
|
+
pageName={mockPageName}
|
|
774
|
+
operation={mockOperation}
|
|
775
|
+
auditLog={true}
|
|
776
|
+
fallback={<TestFallback />}
|
|
777
|
+
onDenied={onDenied}
|
|
778
|
+
>
|
|
779
|
+
<TestComponent>Protected Page</TestComponent>
|
|
780
|
+
</PagePermissionGuard>
|
|
782
781
|
);
|
|
783
782
|
|
|
783
|
+
await waitFor(() => {
|
|
784
|
+
expect(onDenied).toHaveBeenCalledTimes(1);
|
|
785
|
+
}, { interval: 10 });
|
|
786
|
+
|
|
784
787
|
consoleSpy.mockRestore();
|
|
785
788
|
});
|
|
786
789
|
|