@jmruthers/pace-core 0.2.7 → 0.5.1
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/{DataTable-EEUDXPE5.js → DataTable-GX3XERFJ.js} +8 -4
- package/dist/{DataTable-C1AEm9Cx.d.ts → DataTable-ltTFXHS3.d.ts} +3 -1
- package/dist/{chunk-VYG4AXYW.js → chunk-5EL3KHOQ.js} +2 -2
- package/dist/{chunk-ETEJVKYK.js → chunk-6CR3MRZN.js} +1426 -62
- package/dist/chunk-6CR3MRZN.js.map +1 -0
- package/dist/chunk-AUE24LVR.js +268 -0
- package/dist/chunk-AUE24LVR.js.map +1 -0
- package/dist/chunk-COBPIXXQ.js +379 -0
- package/dist/chunk-COBPIXXQ.js.map +1 -0
- package/dist/{chunk-EWKPTNPO.js → chunk-GSNM5D6H.js} +388 -86
- package/dist/chunk-GSNM5D6H.js.map +1 -0
- package/dist/{chunk-2V3Y6YBC.js → chunk-OEGRKULD.js} +1 -42
- package/dist/chunk-OEGRKULD.js.map +1 -0
- package/dist/chunk-OYRY44Q2.js +62 -0
- package/dist/chunk-OYRY44Q2.js.map +1 -0
- package/dist/{chunk-RRUYHORU.js → chunk-T3XIA4AJ.js} +297 -433
- package/dist/chunk-T3XIA4AJ.js.map +1 -0
- package/dist/{chunk-HEMJ4SUJ.js → chunk-TGDCLPP2.js} +11 -7
- package/dist/{chunk-HEMJ4SUJ.js.map → chunk-TGDCLPP2.js.map} +1 -1
- package/dist/{chunk-HNDFPXUU.js → chunk-U6JDHVC2.js} +6 -4
- package/dist/{chunk-HNDFPXUU.js.map → chunk-U6JDHVC2.js.map} +1 -1
- package/dist/{chunk-TIVL4UQ7.js → chunk-XJK2J4N6.js} +6 -4
- package/dist/{chunk-TIVL4UQ7.js.map → chunk-XJK2J4N6.js.map} +1 -1
- package/dist/components.d.ts +2 -2
- package/dist/components.js +21 -20
- package/dist/components.js.map +1 -1
- package/dist/hooks.d.ts +1 -1
- package/dist/hooks.js +7 -7
- package/dist/index.d.ts +2 -2
- package/dist/index.js +26 -25
- package/dist/index.js.map +1 -1
- package/dist/providers.js +8 -7
- package/dist/rbac/index.d.ts +806 -806
- package/dist/rbac/index.js +937 -1179
- package/dist/rbac/index.js.map +1 -1
- package/dist/{types-DiRQsGJs.d.ts → types-BRDU7N6w.d.ts} +12 -1
- package/dist/utils.d.ts +2 -2
- package/dist/utils.js +6 -6
- package/docs/api/classes/ErrorBoundary.md +1 -1
- package/docs/api/classes/PublicErrorBoundary.md +1 -1
- package/docs/api/interfaces/AggregateConfig.md +4 -4
- 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/DataTableAction.md +21 -8
- package/docs/api/interfaces/DataTableColumn.md +1 -1
- package/docs/api/interfaces/DataTableProps.md +46 -33
- package/docs/api/interfaces/DataTableToolbarButton.md +7 -7
- package/docs/api/interfaces/EmptyStateConfig.md +5 -5
- package/docs/api/interfaces/EventContextType.md +1 -1
- package/docs/api/interfaces/EventLogoProps.md +1 -1
- package/docs/api/interfaces/EventProviderProps.md +1 -1
- package/docs/api/interfaces/FileSizeLimits.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/NavigationItem.md +1 -1
- package/docs/api/interfaces/NavigationMenuProps.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 +2 -2
- 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/PaletteData.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/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/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/UsePublicEventLogoOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
- package/docs/api/interfaces/UsePublicRouteParamsReturn.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 +3 -3
- package/package.json +5 -2
- package/src/__tests__/REBUILD_PLAN.md +223 -0
- package/src/__tests__/TESTING_GUIDELINES.md +341 -0
- package/src/__tests__/fixtures/mocks.ts +93 -0
- package/src/__tests__/helpers/component-test-utils.tsx +145 -0
- package/src/__tests__/helpers/test-utils.tsx +117 -0
- package/src/__tests__/integration/UserProfile.test.tsx +128 -0
- package/src/__tests__/setup.ts +37 -225
- package/src/__tests__/templates/component.test.template.tsx +97 -75
- package/src/__tests__/templates/hook.test.template.ts +173 -0
- package/src/__tests__/types/test.types.ts +106 -0
- package/src/components/Alert/Alert.test.tsx +496 -0
- package/src/components/Avatar/Avatar.test.tsx +484 -0
- package/src/components/Button/Button.test.tsx +662 -0
- package/src/components/Card/Card.test.tsx +593 -0
- package/src/components/Checkbox/Checkbox.test.tsx +461 -0
- package/src/components/DataTable/DataTable.tsx +9 -1
- package/src/components/DataTable/components/AccessDeniedPage.tsx +168 -0
- package/src/components/DataTable/components/ActionButtons.tsx +18 -1
- package/src/components/DataTable/components/DataTableCore.tsx +97 -11
- package/src/components/DataTable/components/DataTableToolbar.tsx +22 -10
- package/src/components/DataTable/components/UnifiedTableBody.tsx +33 -4
- package/src/components/DataTable/examples/HierarchicalActionsExample.tsx +1 -0
- package/src/components/DataTable/examples/HierarchicalExample.tsx +3 -0
- package/src/components/DataTable/examples/InitialPageSizeExample.tsx +3 -0
- package/src/components/DataTable/examples/PerformanceExample.tsx +3 -0
- package/src/components/DataTable/types.ts +39 -1
- package/src/components/Dialog/Dialog.test.tsx +1139 -0
- package/src/components/ErrorBoundary/ErrorBoundary.test.tsx +752 -0
- package/src/components/FileUpload/FileUpload.test.tsx +665 -0
- package/src/hooks/useCounter.test.ts +135 -0
- package/src/rbac/index.ts +3 -3
- package/dist/chunk-2V3Y6YBC.js.map +0 -1
- package/dist/chunk-BEZRLNK3.js +0 -1744
- package/dist/chunk-BEZRLNK3.js.map +0 -1
- package/dist/chunk-ETEJVKYK.js.map +0 -1
- package/dist/chunk-EWKPTNPO.js.map +0 -1
- package/dist/chunk-OHXGNT3K.js +0 -21
- package/dist/chunk-OHXGNT3K.js.map +0 -1
- package/dist/chunk-RRUYHORU.js.map +0 -1
- package/src/__tests__/README.md +0 -404
- package/src/__tests__/debug-provider.unit.test.tsx +0 -67
- package/src/__tests__/e2e/workflows.test.tsx +0 -373
- package/src/__tests__/hybridPermissions.unit.test.tsx +0 -474
- package/src/__tests__/index.integration.test.ts +0 -491
- package/src/__tests__/mocks/MockAuthProvider-standalone.tsx +0 -47
- package/src/__tests__/mocks/MockAuthProvider.tsx +0 -63
- package/src/__tests__/mocks/enhancedSupabaseMock.ts +0 -252
- package/src/__tests__/mocks/index.test.ts +0 -23
- package/src/__tests__/mocks/index.ts +0 -16
- package/src/__tests__/mocks/mockAuth.ts +0 -155
- package/src/__tests__/mocks/mockSupabase.ts +0 -83
- package/src/__tests__/mocks/mockSupabaseClient.ts +0 -63
- package/src/__tests__/mocks/providers.tsx +0 -22
- package/src/__tests__/patterns/__tests__/testPatterns.test.ts +0 -394
- package/src/__tests__/patterns/testPatterns.ts +0 -124
- package/src/__tests__/performance/componentPerformance.performance.test.ts +0 -27
- package/src/__tests__/performance/index.ts +0 -24
- package/src/__tests__/performance/performanceValidation.performance.test.ts +0 -15
- package/src/__tests__/security/security.unit.test.tsx +0 -7
- package/src/__tests__/security/securityValidation.security.test.tsx +0 -153
- package/src/__tests__/setupTests.d.ts +0 -1
- package/src/__tests__/shared/componentTestUtils.tsx +0 -475
- package/src/__tests__/shared/errorHandlingTestUtils.ts +0 -107
- package/src/__tests__/shared/index.ts +0 -81
- package/src/__tests__/shared/integrationTestUtils.tsx +0 -375
- package/src/__tests__/shared/performanceTestUtils.tsx +0 -476
- package/src/__tests__/shared/testUtils.optimized.tsx +0 -685
- package/src/__tests__/simple.test.tsx +0 -20
- package/src/__tests__/test-utils/dataFactories.ts +0 -60
- package/src/__tests__/test-utils/index.ts +0 -6
- package/src/__tests__/typeSafety.unit.test.ts +0 -65
- package/src/__tests__/unifiedAuth.unit.test.tsx +0 -151
- package/src/__tests__/utils/accessibilityHelpers.ts +0 -254
- package/src/__tests__/utils/assertions.ts +0 -50
- package/src/__tests__/utils/deterministicHelpers.ts +0 -31
- package/src/__tests__/utils/edgeCaseConfig.test.ts +0 -75
- package/src/__tests__/utils/edgeCaseConfig.ts +0 -98
- package/src/__tests__/utils/mockHelpers.ts +0 -149
- package/src/__tests__/utils/mockLoader.ts +0 -101
- package/src/__tests__/utils/performanceHelpers.ts +0 -55
- package/src/__tests__/utils/performanceTestHelpers.ts +0 -68
- package/src/__tests__/utils/testDataFactories.ts +0 -28
- package/src/__tests__/utils/testIsolation.ts +0 -67
- package/src/__tests__/utils/visualTestHelpers.ts +0 -20
- package/src/__tests__/visual/__snapshots__/componentSnapshots.visual.test.tsx.snap +0 -68
- package/src/__tests__/visual/__snapshots__/componentVisuals.visual.test.tsx.snap +0 -14
- package/src/__tests__/visual/__snapshots__/visualRegression.test.tsx.snap +0 -217
- package/src/__tests__/visual/__snapshots__/visualRegression.visual.test.tsx.snap +0 -24
- package/src/__tests__/visual/componentSnapshots.visual.test.tsx +0 -33
- package/src/__tests__/visual/componentVisuals.visual.test.tsx +0 -12
- package/src/__tests__/visual/visualRegression.visual.test.tsx +0 -20
- package/src/components/Alert/__tests__/Alert.unit.test.tsx +0 -381
- package/src/components/Avatar/__tests__/Avatar.unit.test.tsx +0 -232
- package/src/components/Button/__tests__/Button.accessibility.test.tsx +0 -131
- package/src/components/Button/__tests__/Button.comprehensive.test.tsx +0 -721
- package/src/components/Button/__tests__/Button.unit.test.tsx +0 -189
- package/src/components/Button/__tests__/EventSelector.integration.test.tsx +0 -285
- package/src/components/Card/__tests__/Card.accessibility.test.tsx +0 -394
- package/src/components/Card/__tests__/Card.comprehensive.test.tsx +0 -599
- package/src/components/Card/__tests__/Card.integration.test.tsx +0 -673
- package/src/components/Card/__tests__/Card.performance.test.tsx +0 -546
- package/src/components/Card/__tests__/Card.unit.test.tsx +0 -330
- package/src/components/Card/__tests__/Card.visual.test.tsx +0 -599
- package/src/components/Card/__tests__/README.md +0 -211
- package/src/components/Checkbox/__tests__/Checkbox.unit.test.tsx +0 -520
- package/src/components/DataTable/__tests__/DataTable.errorHandling.test.tsx +0 -251
- package/src/components/DataTable/__tests__/DataTable.hierarchical.test.tsx +0 -680
- package/src/components/DataTable/__tests__/DataTable.infinite-loop.test.tsx +0 -323
- package/src/components/DataTable/__tests__/DataTable.integration.test.tsx +0 -716
- package/src/components/DataTable/__tests__/DataTable.performance.test.tsx +0 -589
- package/src/components/DataTable/__tests__/DataTable.permissions.test.tsx +0 -316
- package/src/components/DataTable/__tests__/DataTable.regressionFixes.test.tsx +0 -546
- package/src/components/DataTable/__tests__/DataTable.selection.controlled.test.tsx +0 -386
- package/src/components/DataTable/__tests__/DataTable.selection.test.tsx +0 -338
- package/src/components/DataTable/__tests__/DataTable.sorting.test.tsx +0 -321
- package/src/components/DataTable/__tests__/DataTable.userWorkflows.test.tsx +0 -320
- package/src/components/DataTable/__tests__/DataTable.workflowValidation.test.tsx +0 -583
- package/src/components/DataTable/__tests__/DataTable.workflows.test.tsx +0 -711
- package/src/components/DataTable/__tests__/performance-regression.test.tsx +0 -777
- package/src/components/DataTable/__tests__/performance.test.tsx +0 -365
- package/src/components/DataTable/components/__tests__/ActionButtons.unit.test.tsx +0 -150
- package/src/components/DataTable/components/__tests__/BulkOperationsDropdown.test.tsx +0 -224
- package/src/components/DataTable/components/__tests__/ColumnVisibilityDropdown.unit.test.tsx +0 -244
- package/src/components/DataTable/components/__tests__/DataTable.accessibility.test.tsx +0 -629
- package/src/components/DataTable/components/__tests__/DataTable.integration.test.tsx +0 -470
- package/src/components/DataTable/components/__tests__/DataTable.performance.test.tsx +0 -160
- package/src/components/DataTable/components/__tests__/DataTable.real.test.tsx +0 -251
- package/src/components/DataTable/components/__tests__/DataTable.security.test.tsx +0 -171
- package/src/components/DataTable/components/__tests__/DataTable.unit.test.tsx +0 -290
- package/src/components/DataTable/components/__tests__/DataTableBody.unit.test.tsx +0 -147
- package/src/components/DataTable/components/__tests__/DataTableErrorBoundary.unit.test.tsx +0 -182
- package/src/components/DataTable/components/__tests__/DataTableModals.unit.test.tsx +0 -123
- package/src/components/DataTable/components/__tests__/EditableRow.unit.test.tsx +0 -660
- package/src/components/DataTable/components/__tests__/EmptyState.unit.test.tsx +0 -256
- package/src/components/DataTable/components/__tests__/ExpandButton.test.tsx +0 -498
- package/src/components/DataTable/components/__tests__/FilterRow.unit.test.tsx +0 -112
- package/src/components/DataTable/components/__tests__/FilteringToggle.unit.test.tsx +0 -133
- package/src/components/DataTable/components/__tests__/GroupHeader.unit.test.tsx +0 -172
- package/src/components/DataTable/components/__tests__/GroupingDropdown.unit.test.tsx +0 -222
- package/src/components/DataTable/components/__tests__/ImportModal.unit.test.tsx +0 -780
- package/src/components/DataTable/components/__tests__/LoadingState.unit.test.tsx +0 -65
- package/src/components/DataTable/components/__tests__/PaginationControls.unit.test.tsx +0 -634
- package/src/components/DataTable/components/__tests__/StateComponents.unit.test.tsx +0 -48
- package/src/components/DataTable/components/__tests__/UnifiedTableBody.hierarchical.test.tsx +0 -541
- package/src/components/DataTable/components/__tests__/ViewRowModal.unit.test.tsx +0 -228
- package/src/components/DataTable/components/__tests__/VirtualizedDataTable.unit.test.tsx +0 -568
- package/src/components/DataTable/core/__tests__/ActionManager.unit.test.ts +0 -405
- package/src/components/DataTable/core/__tests__/ArchitectureIntegration.unit.test.tsx +0 -445
- package/src/components/DataTable/core/__tests__/ColumnFactory.unit.test.ts +0 -288
- package/src/components/DataTable/core/__tests__/ColumnManager.unit.test.ts +0 -623
- package/src/components/DataTable/core/__tests__/DataManager.unit.test.ts +0 -431
- package/src/components/DataTable/core/__tests__/DataTableContext.unit.test.tsx +0 -433
- package/src/components/DataTable/core/__tests__/LocalDataAdapter.unit.test.ts +0 -422
- package/src/components/DataTable/core/__tests__/PluginRegistry.unit.test.tsx +0 -207
- package/src/components/DataTable/core/__tests__/StateManager.unit.test.ts +0 -278
- package/src/components/DataTable/examples/__tests__/PerformanceExample.unit.test.tsx +0 -281
- package/src/components/DataTable/hooks/__tests__/useColumnOrderPersistence.unit.test.ts +0 -407
- package/src/components/DataTable/hooks/__tests__/useColumnReordering.unit.test.ts +0 -679
- package/src/components/DataTable/utils/__tests__/debugTools.unit.test.ts +0 -267
- package/src/components/DataTable/utils/__tests__/errorHandling.unit.test.ts +0 -467
- package/src/components/DataTable/utils/__tests__/exportUtils.unit.test.ts +0 -380
- package/src/components/DataTable/utils/__tests__/flexibleImport.unit.test.ts +0 -233
- package/src/components/DataTable/utils/__tests__/performanceUtils.unit.test.ts +0 -414
- package/src/components/Dialog/__tests__/Dialog.accessibility.test.tsx +0 -521
- package/src/components/Dialog/__tests__/Dialog.auto-size.example.tsx +0 -157
- package/src/components/Dialog/__tests__/Dialog.enhanced.test.tsx +0 -538
- package/src/components/Dialog/__tests__/Dialog.unit.test.tsx +0 -1373
- package/src/components/Dialog/examples/__tests__/SmartDialogExample.unit.test.tsx +0 -151
- package/src/components/Dialog/utils/__tests__/safeHtml.unit.test.ts +0 -611
- package/src/components/ErrorBoundary/__tests__/ErrorBoundary.accessibility.test.tsx +0 -517
- package/src/components/ErrorBoundary/__tests__/ErrorBoundary.integration.test.tsx +0 -572
- package/src/components/ErrorBoundary/__tests__/ErrorBoundary.unit.test.tsx +0 -579
- package/src/components/EventSelector/__tests__/EventSelector.test.tsx +0 -528
- package/src/components/FileUpload/__tests__/FileUpload.integration.test.tsx +0 -992
- package/src/components/FileUpload/__tests__/FileUpload.real.test.tsx +0 -927
- package/src/components/FileUpload/__tests__/FileUpload.test.tsx +0 -855
- package/src/components/FileUpload/__tests__/FileUpload.unit.test.tsx +0 -1311
- package/src/components/FileUpload/__tests__/FileUpload.unmocked.test.tsx +0 -937
- package/src/components/Footer/__tests__/Footer.accessibility.test.tsx +0 -359
- package/src/components/Footer/__tests__/Footer.integration.test.tsx +0 -353
- package/src/components/Footer/__tests__/Footer.performance.test.tsx +0 -309
- package/src/components/Footer/__tests__/Footer.unit.test.tsx +0 -309
- package/src/components/Footer/__tests__/Footer.visual.test.tsx +0 -335
- package/src/components/Form/__tests__/Form.accessibility.test.tsx +0 -820
- package/src/components/Form/__tests__/Form.unit.test.tsx +0 -305
- package/src/components/Form/__tests__/FormErrorSummary.unit.test.tsx +0 -285
- package/src/components/Form/__tests__/FormFieldset.unit.test.tsx +0 -241
- package/src/components/Header/__tests__/Header.accessibility.test.tsx +0 -382
- package/src/components/Header/__tests__/Header.comprehensive.test.tsx +0 -509
- package/src/components/Header/__tests__/Header.unit.test.tsx +0 -335
- package/src/components/InactivityWarningModal/InactivityWarningModal.test.tsx +0 -196
- package/src/components/InactivityWarningModal/__tests__/InactivityWarningModal.unit.test.tsx +0 -224
- package/src/components/Input/__tests__/Input.accessibility.test.tsx +0 -632
- package/src/components/Input/__tests__/Input.unit.test.tsx +0 -1121
- package/src/components/Label/__tests__/Label.accessibility.test.tsx +0 -239
- package/src/components/Label/__tests__/Label.unit.test.tsx +0 -331
- package/src/components/LoadingSpinner/__tests__/LoadingSpinner.accessibility.test.tsx +0 -116
- package/src/components/LoadingSpinner/__tests__/LoadingSpinner.unit.test.tsx +0 -144
- package/src/components/LoginForm/__tests__/LoginForm.accessibility.test.tsx +0 -201
- package/src/components/LoginForm/__tests__/LoginForm.unit.test.tsx +0 -119
- package/src/components/NavigationMenu/__tests__/NavigationMenu.accessibility.test.tsx +0 -378
- package/src/components/NavigationMenu/__tests__/NavigationMenu.enhanced.test.tsx +0 -768
- package/src/components/NavigationMenu/__tests__/NavigationMenu.integration.test.tsx +0 -576
- package/src/components/NavigationMenu/__tests__/NavigationMenu.performance.test.tsx +0 -585
- package/src/components/NavigationMenu/__tests__/NavigationMenu.real.component.test.tsx +0 -783
- package/src/components/NavigationMenu/__tests__/NavigationMenu.security.enhanced.test.tsx +0 -810
- package/src/components/NavigationMenu/__tests__/NavigationMenu.security.test.tsx +0 -494
- package/src/components/NavigationMenu/__tests__/NavigationMenu.unit.test.tsx +0 -331
- package/src/components/NavigationMenu/__tests__/NavigationMenu.userWorkflows.test.tsx +0 -347
- package/src/components/NavigationMenu/__tests__/NavigationMenu.workflows.test.tsx +0 -584
- package/src/components/OrganisationSelector/__tests__/OrganisationSelector.unit.test.tsx +0 -664
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.accessibility.test.tsx +0 -288
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.integration.test.tsx +0 -893
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.performance.test.tsx +0 -629
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.security.test.tsx +0 -782
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.unit.test.tsx +0 -904
- package/src/components/PaceLoginPage/__tests__/PaceLoginPage.accessibility.test.tsx +0 -463
- package/src/components/PaceLoginPage/__tests__/PaceLoginPage.integration.test.tsx +0 -586
- package/src/components/PaceLoginPage/__tests__/PaceLoginPage.unit.test.tsx +0 -533
- package/src/components/PasswordReset/__tests__/PasswordChangeForm.accessibility.test.tsx +0 -408
- package/src/components/PasswordReset/__tests__/PasswordChangeForm.unit.test.tsx +0 -561
- package/src/components/PasswordReset/__tests__/PasswordReset.integration.test.tsx +0 -304
- package/src/components/PasswordReset/__tests__/PasswordResetForm.accessibility.test.tsx +0 -20
- package/src/components/PasswordReset/__tests__/PasswordResetForm.unit.test.tsx +0 -523
- package/src/components/PasswordReset/__tests__/__mocks__/UnifiedAuthProvider.ts +0 -29
- package/src/components/Print/__tests__/Print.comprehensive.test.tsx +0 -331
- package/src/components/PrintButton/__tests__/PrintButton.unit.test.tsx +0 -429
- package/src/components/PrintButton/__tests__/PrintButtonGroup.unit.test.tsx +0 -277
- package/src/components/PrintButton/__tests__/PrintToolbar.unit.test.tsx +0 -264
- package/src/components/PrintCard/__tests__/PrintCard.unit.test.tsx +0 -233
- package/src/components/PrintCard/__tests__/PrintCardContent.test.tsx +0 -284
- package/src/components/PrintCard/__tests__/PrintCardGrid.unit.test.tsx +0 -214
- package/src/components/PrintCard/__tests__/PrintCardImage.unit.test.tsx +0 -264
- package/src/components/PrintDataTable/__tests__/PrintDataTable.unit.test.tsx +0 -361
- package/src/components/PrintDataTable/__tests__/PrintTableGroup.unit.test.tsx +0 -314
- package/src/components/PrintDataTable/__tests__/PrintTableRow.unit.test.tsx +0 -362
- package/src/components/PrintFooter/__tests__/PrintFooter.unit.test.tsx +0 -500
- package/src/components/PrintFooter/__tests__/PrintFooterContent.unit.test.tsx +0 -321
- package/src/components/PrintFooter/__tests__/PrintFooterInfo.unit.test.tsx +0 -335
- package/src/components/PrintFooter/__tests__/PrintPageNumber.unit.test.tsx +0 -340
- package/src/components/PrintGrid/__tests__/PrintGrid.unit.test.tsx +0 -340
- package/src/components/PrintGrid/__tests__/PrintGridBreakpoint.unit.test.tsx +0 -261
- package/src/components/PrintGrid/__tests__/PrintGridContainer.unit.test.tsx +0 -338
- package/src/components/PrintGrid/__tests__/PrintGridItem.unit.test.tsx +0 -338
- package/src/components/PrintHeader/__tests__/PrintCoverHeader.unit.test.tsx +0 -309
- package/src/components/PrintHeader/__tests__/PrintHeader.unit.test.tsx +0 -202
- package/src/components/PrintLayout/__tests__/PrintLayout.unit.test.tsx +0 -238
- package/src/components/PrintPageBreak/__tests__/PrintPageBreak.unit.test.tsx +0 -263
- package/src/components/PrintPageBreak/__tests__/PrintPageBreakGroup.unit.test.tsx +0 -239
- package/src/components/PrintPageBreak/__tests__/PrintPageBreakIndicator.unit.test.tsx +0 -235
- package/src/components/PrintSection/__tests__/PrintColumn.unit.test.tsx +0 -385
- package/src/components/PrintSection/__tests__/PrintDivider.unit.test.tsx +0 -373
- package/src/components/PrintSection/__tests__/PrintSection.unit.test.tsx +0 -390
- package/src/components/PrintSection/__tests__/PrintSectionContent.unit.test.tsx +0 -321
- package/src/components/PrintSection/__tests__/PrintSectionHeader.unit.test.tsx +0 -334
- package/src/components/PrintText/__tests__/PrintText.unit.test.tsx +0 -351
- package/src/components/Progress/__tests__/Progress.accessibility.test.tsx +0 -240
- package/src/components/Progress/__tests__/Progress.unit.test.tsx +0 -242
- package/src/components/PublicLayout/__tests__/EventLogo.test.tsx +0 -761
- package/src/components/PublicLayout/__tests__/PublicErrorBoundary.simplified.test.tsx +0 -228
- package/src/components/PublicLayout/__tests__/PublicErrorBoundary.test.tsx +0 -228
- package/src/components/PublicLayout/__tests__/PublicLoadingSpinner.test.tsx +0 -459
- package/src/components/PublicLayout/__tests__/PublicPageFooter.test.tsx +0 -362
- package/src/components/PublicLayout/__tests__/PublicPageHeader.test.tsx +0 -522
- package/src/components/PublicLayout/__tests__/PublicPageLayout.test.tsx +0 -599
- package/src/components/PublicLayout/__tests__/PublicPageProvider.test.tsx +0 -513
- package/src/components/RBAC/__tests__/PagePermissionGuard.unit.test.tsx +0 -683
- package/src/components/RBAC/__tests__/RBAC.integration.test.tsx +0 -573
- package/src/components/RBAC/__tests__/RBACGuard.unit.test.tsx +0 -467
- package/src/components/RBAC/__tests__/RBACProvider.accessibility.test.tsx +0 -475
- package/src/components/RBAC/__tests__/RBACProvider.advanced.test.tsx +0 -569
- package/src/components/RBAC/__tests__/RBACProvider.integration.test.tsx +0 -352
- package/src/components/RBAC/__tests__/RBACProvider.unit.test.tsx +0 -128
- package/src/components/RBAC/__tests__/RoleBasedContent.unit.test.tsx +0 -657
- package/src/components/Select/__tests__/SearchableSelect.unit.test.tsx +0 -437
- package/src/components/Select/__tests__/Select.accessibility.test.tsx +0 -1202
- package/src/components/Select/__tests__/Select.actual.test.tsx +0 -774
- package/src/components/Select/__tests__/Select.comprehensive.test.tsx +0 -837
- package/src/components/Select/__tests__/Select.enhanced.test.tsx +0 -1101
- package/src/components/Select/__tests__/Select.integration.test.tsx +0 -772
- package/src/components/Select/__tests__/Select.performance.test.tsx +0 -695
- package/src/components/Select/__tests__/Select.real-world.test.tsx +0 -1046
- package/src/components/Select/__tests__/Select.search-algorithms.test.tsx +0 -968
- package/src/components/Select/__tests__/Select.unit.test.tsx +0 -647
- package/src/components/Select/__tests__/Select.utils.test.tsx +0 -890
- package/src/components/Table/__tests__/Table.accessibility.test.tsx +0 -233
- package/src/components/Table/__tests__/Table.unit.test.tsx +0 -235
- package/src/components/Toast/__tests__/Toast.accessibility.test.tsx +0 -238
- package/src/components/Toast/__tests__/Toast.integration.test.tsx +0 -699
- package/src/components/Toast/__tests__/Toast.unit.test.tsx +0 -750
- package/src/components/Tooltip/__tests__/Tooltip.accessibility.test.tsx +0 -121
- package/src/components/Tooltip/__tests__/Tooltip.unit.test.tsx +0 -185
- package/src/components/UserMenu/__tests__/UserMenu.accessibility.test.tsx +0 -139
- package/src/components/UserMenu/__tests__/UserMenu.integration.test.tsx +0 -188
- package/src/components/UserMenu/__tests__/UserMenu.unit.test.tsx +0 -458
- package/src/components/__tests__/EdgeCaseTesting.enhanced.test.tsx +0 -524
- package/src/components/__tests__/ErrorTesting.enhanced.test.tsx +0 -455
- package/src/components/__tests__/SuperAdminGuard.test.tsx +0 -456
- package/src/components/__tests__/SuperAdminGuard.unit.test.tsx +0 -456
- package/src/components/examples/__tests__/PermissionExample.unit.test.tsx +0 -360
- package/src/hooks/__tests__/hooks.integration.test.tsx +0 -575
- package/src/hooks/__tests__/useApiFetch.unit.test.ts +0 -115
- package/src/hooks/__tests__/useComponentPerformance.unit.test.tsx +0 -133
- package/src/hooks/__tests__/useDebounce.unit.test.ts +0 -82
- package/src/hooks/__tests__/useFocusTrap.unit.test.tsx +0 -293
- package/src/hooks/__tests__/useInactivityTracker.unit.test.ts +0 -385
- package/src/hooks/__tests__/useOrganisationPermissions.unit.test.tsx +0 -286
- package/src/hooks/__tests__/useOrganisationSecurity.unit.test.tsx +0 -838
- package/src/hooks/__tests__/usePermissionCache.unit.test.ts +0 -627
- package/src/hooks/__tests__/useRBAC.unit.test.ts +0 -911
- package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +0 -537
- package/src/hooks/__tests__/useToast.unit.test.tsx +0 -62
- package/src/hooks/__tests__/useZodForm.unit.test.tsx +0 -37
- package/src/hooks/public/__tests__/usePublicEvent.test.tsx +0 -397
- package/src/hooks/public/__tests__/usePublicEventLogo.test.tsx +0 -690
- package/src/hooks/public/__tests__/usePublicRouteParams.test.tsx +0 -449
- package/src/providers/__tests__/EventProvider.unit.test.tsx +0 -768
- package/src/providers/__tests__/OrganisationProvider.basic.test.tsx +0 -116
- package/src/providers/__tests__/OrganisationProvider.unit.test.tsx +0 -1312
- package/src/providers/__tests__/UnifiedAuthProvider.inactivity.test.tsx +0 -601
- package/src/providers/__tests__/UnifiedAuthProvider.unit.test.tsx +0 -683
- package/src/providers/__tests__/index.unit.test.ts +0 -78
- package/src/rbac/__tests__/PagePermissionGuard.test.tsx +0 -673
- package/src/rbac/__tests__/README.md +0 -170
- package/src/rbac/__tests__/RoleBasedRouter.test.tsx +0 -709
- package/src/rbac/__tests__/TestContext.tsx +0 -72
- package/src/rbac/__tests__/__mocks__/cache.ts +0 -144
- package/src/rbac/__tests__/__mocks__/supabase.ts +0 -152
- package/src/rbac/__tests__/adapters-hooks-comprehensive.test.tsx +0 -782
- package/src/rbac/__tests__/adapters-hooks.test.tsx +0 -561
- package/src/rbac/__tests__/adapters.comprehensive.test.tsx +0 -963
- package/src/rbac/__tests__/adapters.test.tsx +0 -444
- package/src/rbac/__tests__/api.test.ts +0 -620
- package/src/rbac/__tests__/audit-observability-comprehensive.test.ts +0 -792
- package/src/rbac/__tests__/audit-observability.test.ts +0 -549
- package/src/rbac/__tests__/audit.test.ts +0 -616
- package/src/rbac/__tests__/build-contract-compliance-simple.test.ts +0 -230
- package/src/rbac/__tests__/cache-invalidation-comprehensive.test.ts +0 -889
- package/src/rbac/__tests__/cache-invalidation.test.ts +0 -457
- package/src/rbac/__tests__/cache.test.ts +0 -458
- package/src/rbac/__tests__/components-navigation-guard.enhanced.test.tsx +0 -859
- package/src/rbac/__tests__/components-navigation-guard.test.tsx +0 -895
- package/src/rbac/__tests__/components-navigation-provider.test.tsx +0 -692
- package/src/rbac/__tests__/components-page-permission-guard.test.tsx +0 -673
- package/src/rbac/__tests__/components-page-permission-provider.test.tsx +0 -614
- package/src/rbac/__tests__/components-permission-enforcer.enhanced.fixed.test.tsx +0 -836
- package/src/rbac/__tests__/components-permission-enforcer.enhanced.test.tsx +0 -837
- package/src/rbac/__tests__/components-permission-enforcer.test.tsx +0 -825
- package/src/rbac/__tests__/components-role-based-router.test.tsx +0 -709
- package/src/rbac/__tests__/components-secure-data-provider.test.tsx +0 -607
- package/src/rbac/__tests__/config.test.ts +0 -583
- package/src/rbac/__tests__/core-logic-unit.test.ts +0 -190
- package/src/rbac/__tests__/core-permission-logic-comprehensive.test.ts +0 -1467
- package/src/rbac/__tests__/core-permission-logic-fixed.test.ts +0 -151
- package/src/rbac/__tests__/core-permission-logic-simple.test.ts +0 -968
- package/src/rbac/__tests__/core-permission-logic.test.ts +0 -966
- package/src/rbac/__tests__/edge-cases-comprehensive.test.ts +0 -988
- package/src/rbac/__tests__/edge-cases.test.ts +0 -654
- package/src/rbac/__tests__/engine.test.ts +0 -361
- package/src/rbac/__tests__/engine.unit.test.ts +0 -361
- package/src/rbac/__tests__/hooks.enhanced.test.tsx +0 -979
- package/src/rbac/__tests__/hooks.fixed.test.tsx +0 -475
- package/src/rbac/__tests__/hooks.test.tsx +0 -385
- package/src/rbac/__tests__/index.test.ts +0 -269
- package/src/rbac/__tests__/integration.enhanced.test.tsx +0 -824
- package/src/rbac/__tests__/page-permission-guard-super-admin.test.tsx +0 -261
- package/src/rbac/__tests__/performance.enhanced.test.tsx +0 -724
- package/src/rbac/__tests__/permissions.test.ts +0 -383
- package/src/rbac/__tests__/requires-event.test.ts +0 -330
- package/src/rbac/__tests__/scope-isolation-comprehensive.test.ts +0 -1349
- package/src/rbac/__tests__/scope-isolation.test.ts +0 -755
- package/src/rbac/__tests__/secure-client-rls-comprehensive.test.ts +0 -592
- package/src/rbac/__tests__/secure-client-rls.test.ts +0 -377
- package/src/rbac/__tests__/security.test.ts +0 -296
- package/src/rbac/__tests__/setup.ts +0 -228
- package/src/rbac/__tests__/test-utils-enhanced.tsx +0 -400
- package/src/rbac/__tests__/types.test.ts +0 -685
- package/src/rbac/components/__tests__/EnhancedNavigationMenu.test.tsx +0 -631
- package/src/rbac/components/__tests__/NavigationProvider.test.tsx +0 -667
- package/src/rbac/components/__tests__/PagePermissionProvider.test.tsx +0 -647
- package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +0 -496
- package/src/rbac/testing/__tests__/index.test.tsx +0 -342
- package/src/rbac/utils/__tests__/eventContext.test.ts +0 -428
- package/src/rbac/utils/__tests__/eventContext.unit.test.ts +0 -428
- package/src/styles/__tests__/styles.unit.test.ts +0 -164
- package/src/test-dom-cleanup.test.tsx +0 -38
- package/src/theming/__tests__/README.md +0 -335
- package/src/theming/__tests__/runtime.accessibility.test.ts +0 -474
- package/src/theming/__tests__/runtime.error.test.ts +0 -616
- package/src/theming/__tests__/runtime.integration.test.ts +0 -376
- package/src/theming/__tests__/runtime.performance.test.ts +0 -411
- package/src/theming/__tests__/runtime.unit.test.ts +0 -470
- package/src/types/__tests__/database.unit.test.ts +0 -489
- package/src/types/__tests__/guards.unit.test.ts +0 -146
- package/src/types/__tests__/index.unit.test.ts +0 -77
- package/src/types/__tests__/organisation.unit.test.ts +0 -713
- package/src/types/__tests__/rbac.unit.test.ts +0 -621
- package/src/types/__tests__/security.unit.test.ts +0 -347
- package/src/types/__tests__/supabase.unit.test.ts +0 -658
- package/src/types/__tests__/theme.unit.test.ts +0 -218
- package/src/types/__tests__/unified.unit.test.ts +0 -537
- package/src/types/__tests__/validation.unit.test.ts +0 -616
- package/src/utils/__tests__/appConfig.unit.test.ts +0 -55
- package/src/utils/__tests__/appNameResolver.unit.test.ts +0 -137
- package/src/utils/__tests__/audit.unit.test.ts +0 -69
- package/src/utils/__tests__/auth-utils.unit.test.ts +0 -70
- package/src/utils/__tests__/bundleAnalysis.unit.test.ts +0 -317
- package/src/utils/__tests__/cn.unit.test.ts +0 -34
- package/src/utils/__tests__/deviceFingerprint.unit.test.ts +0 -480
- package/src/utils/__tests__/dynamicUtils.unit.test.ts +0 -322
- package/src/utils/__tests__/formatDate.unit.test.ts +0 -109
- package/src/utils/__tests__/formatting.unit.test.ts +0 -66
- package/src/utils/__tests__/index.unit.test.ts +0 -251
- package/src/utils/__tests__/lazyLoad.unit.test.tsx +0 -304
- package/src/utils/__tests__/organisationContext.unit.test.ts +0 -192
- package/src/utils/__tests__/performanceBudgets.unit.test.ts +0 -259
- package/src/utils/__tests__/permissionTypes.unit.test.ts +0 -250
- package/src/utils/__tests__/permissionUtils.unit.test.ts +0 -362
- package/src/utils/__tests__/sanitization.unit.test.ts +0 -346
- package/src/utils/__tests__/schemaUtils.unit.test.ts +0 -441
- package/src/utils/__tests__/secureDataAccess.unit.test.ts +0 -334
- package/src/utils/__tests__/secureErrors.unit.test.ts +0 -377
- package/src/utils/__tests__/secureStorage.unit.test.ts +0 -293
- package/src/utils/__tests__/security.unit.test.ts +0 -127
- package/src/utils/__tests__/securityMonitor.unit.test.ts +0 -280
- package/src/utils/__tests__/sessionTracking.unit.test.ts +0 -370
- package/src/utils/__tests__/validation.unit.test.ts +0 -84
- package/src/utils/__tests__/validationUtils.unit.test.ts +0 -571
- package/src/utils/print/__tests__/PrintDataProcessor.unit.test.ts +0 -219
- package/src/utils/print/__tests__/usePrintOptimization.unit.test.tsx +0 -353
- package/src/utils/storage/__tests__/config.unit.test.ts +0 -206
- package/src/utils/storage/__tests__/helpers.unit.test.ts +0 -648
- package/src/utils/storage/__tests__/index.unit.test.ts +0 -167
- package/src/utils/storage/__tests__/types.unit.test.ts +0 -441
- package/src/validation/__tests__/common.unit.test.ts +0 -101
- package/src/validation/__tests__/csrf.unit.test.ts +0 -302
- package/src/validation/__tests__/passwordSchema.unit.test.ts +0 -98
- package/src/validation/__tests__/sqlInjectionProtection.unit.test.ts +0 -466
- /package/dist/{DataTable-EEUDXPE5.js.map → DataTable-GX3XERFJ.js.map} +0 -0
- /package/dist/{chunk-VYG4AXYW.js.map → chunk-5EL3KHOQ.js.map} +0 -0
package/dist/rbac/index.js
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useAccessLevel,
|
|
3
|
+
useCachedPermissions,
|
|
4
|
+
useCan,
|
|
5
|
+
useHasAllPermissions,
|
|
6
|
+
useHasAnyPermission,
|
|
7
|
+
useMultiplePermissions,
|
|
8
|
+
usePermissions
|
|
9
|
+
} from "../chunk-AUE24LVR.js";
|
|
1
10
|
import {
|
|
2
11
|
CACHE_PATTERNS,
|
|
3
12
|
OrganisationContextRequiredError,
|
|
@@ -28,19 +37,19 @@ import {
|
|
|
28
37
|
} from "../chunk-7BNPOCLL.js";
|
|
29
38
|
import {
|
|
30
39
|
useSecureDataAccess
|
|
31
|
-
} from "../chunk-
|
|
40
|
+
} from "../chunk-XJK2J4N6.js";
|
|
41
|
+
import "../chunk-COBPIXXQ.js";
|
|
32
42
|
import {
|
|
33
43
|
init_UnifiedAuthProvider,
|
|
34
44
|
useUnifiedAuth
|
|
35
|
-
} from "../chunk-
|
|
36
|
-
import "../chunk-ETEJVKYK.js";
|
|
45
|
+
} from "../chunk-6CR3MRZN.js";
|
|
37
46
|
import "../chunk-YDJW5XTN.js";
|
|
38
47
|
import {
|
|
39
48
|
getCurrentAppName,
|
|
40
49
|
init_appNameResolver
|
|
41
50
|
} from "../chunk-MZBUOP4P.js";
|
|
42
|
-
import "../chunk-
|
|
43
|
-
import "../chunk-
|
|
51
|
+
import "../chunk-OEGRKULD.js";
|
|
52
|
+
import "../chunk-OYRY44Q2.js";
|
|
44
53
|
import "../chunk-PLDDJCW6.js";
|
|
45
54
|
|
|
46
55
|
// src/rbac/secureClient.ts
|
|
@@ -170,833 +179,523 @@ function fromSupabaseClient(client, organisationId, eventId, appId) {
|
|
|
170
179
|
throw new Error("fromSupabaseClient is not supported. Use createSecureClient instead.");
|
|
171
180
|
}
|
|
172
181
|
|
|
173
|
-
// src/rbac/
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
};
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
setCan(true);
|
|
225
|
-
setIsLoading(false);
|
|
226
|
-
return;
|
|
227
|
-
}
|
|
228
|
-
} catch (error2) {
|
|
229
|
-
console.error("[useCan] Error checking super admin status:", error2);
|
|
230
|
-
}
|
|
231
|
-
if (!scope || !scope.organisationId || !scope.appId) {
|
|
232
|
-
console.log("[useCan] Incomplete scope, waiting for resolution:", scope);
|
|
233
|
-
setCan(false);
|
|
234
|
-
setIsLoading(true);
|
|
235
|
-
return;
|
|
236
|
-
}
|
|
237
|
-
console.log("[useCan] Scope is complete, checking permission...");
|
|
238
|
-
console.log("[useCan] Detailed scope info:", {
|
|
239
|
-
organisationId: scope.organisationId,
|
|
240
|
-
eventId: scope.eventId,
|
|
241
|
-
appId: scope.appId,
|
|
242
|
-
permission,
|
|
182
|
+
// src/rbac/components/PagePermissionProvider.tsx
|
|
183
|
+
init_UnifiedAuthProvider();
|
|
184
|
+
import { createContext, useContext, useState, useCallback, useMemo, useEffect } from "react";
|
|
185
|
+
import { jsx } from "react/jsx-runtime";
|
|
186
|
+
var PagePermissionContext = createContext(null);
|
|
187
|
+
function PagePermissionProvider({
|
|
188
|
+
children,
|
|
189
|
+
strictMode = true,
|
|
190
|
+
auditLog = true,
|
|
191
|
+
onPageAccess,
|
|
192
|
+
onStrictModeViolation,
|
|
193
|
+
maxHistorySize = 1e3
|
|
194
|
+
}) {
|
|
195
|
+
const { user, selectedOrganisationId, selectedEventId } = useUnifiedAuth();
|
|
196
|
+
const [pageAccessHistory, setPageAccessHistory] = useState([]);
|
|
197
|
+
const [isEnabled, setIsEnabled] = useState(true);
|
|
198
|
+
const currentScope = useMemo(() => {
|
|
199
|
+
if (!selectedOrganisationId) return null;
|
|
200
|
+
return {
|
|
201
|
+
organisationId: selectedOrganisationId,
|
|
202
|
+
eventId: selectedEventId || void 0,
|
|
203
|
+
appId: void 0
|
|
204
|
+
};
|
|
205
|
+
}, [selectedOrganisationId, selectedEventId]);
|
|
206
|
+
const hasPagePermission = useCallback((pageName, operation, pageId, scope) => {
|
|
207
|
+
if (!isEnabled) return true;
|
|
208
|
+
if (!user?.id) return false;
|
|
209
|
+
const effectiveScope = scope || currentScope;
|
|
210
|
+
if (!effectiveScope) return false;
|
|
211
|
+
const permission = `${operation}:page.${pageName}`;
|
|
212
|
+
return false;
|
|
213
|
+
}, [isEnabled, user?.id, currentScope]);
|
|
214
|
+
const getPagePermissions = useCallback(() => {
|
|
215
|
+
if (!isEnabled || !user?.id) return {};
|
|
216
|
+
return {};
|
|
217
|
+
}, [isEnabled, user?.id]);
|
|
218
|
+
const getPageAccessHistory = useCallback(() => {
|
|
219
|
+
return [...pageAccessHistory];
|
|
220
|
+
}, [pageAccessHistory]);
|
|
221
|
+
const clearPageAccessHistory = useCallback(() => {
|
|
222
|
+
setPageAccessHistory([]);
|
|
223
|
+
}, []);
|
|
224
|
+
const recordPageAccess = useCallback((pageName, operation, allowed, pageId, scope) => {
|
|
225
|
+
if (!auditLog || !user?.id) return;
|
|
226
|
+
const record = {
|
|
227
|
+
pageName,
|
|
228
|
+
operation,
|
|
229
|
+
userId: user.id,
|
|
230
|
+
scope: scope || currentScope || { organisationId: "" },
|
|
231
|
+
allowed,
|
|
232
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
243
233
|
pageId
|
|
234
|
+
};
|
|
235
|
+
setPageAccessHistory((prev) => {
|
|
236
|
+
const newHistory = [record, ...prev];
|
|
237
|
+
return newHistory.slice(0, maxHistorySize);
|
|
244
238
|
});
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
setError(null);
|
|
248
|
-
console.log("[useCan] About to call isPermitted/isPermittedCached...");
|
|
249
|
-
const result = useCache ? await isPermittedCached({ userId, scope, permission, pageId }) : await isPermitted({ userId, scope, permission, pageId });
|
|
250
|
-
console.log("[useCan] Permission check result:", result);
|
|
251
|
-
console.log("[useCan] Permission check details:", {
|
|
252
|
-
userId,
|
|
253
|
-
scope,
|
|
254
|
-
permission,
|
|
255
|
-
pageId,
|
|
256
|
-
result,
|
|
257
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
258
|
-
});
|
|
259
|
-
setCan(result);
|
|
260
|
-
} catch (err) {
|
|
261
|
-
console.error("[useCan] Permission check error:", err);
|
|
262
|
-
console.error("[useCan] Error details:", {
|
|
263
|
-
userId,
|
|
264
|
-
scope,
|
|
265
|
-
permission,
|
|
266
|
-
pageId,
|
|
267
|
-
error: err instanceof Error ? err.message : "Unknown error",
|
|
268
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
269
|
-
});
|
|
270
|
-
setError(err instanceof Error ? err : new Error("Failed to check permission"));
|
|
271
|
-
setCan(false);
|
|
272
|
-
} finally {
|
|
273
|
-
setIsLoading(false);
|
|
274
|
-
}
|
|
275
|
-
}, [userId, scope.organisationId, scope.eventId, scope.appId, permission, pageId, useCache]);
|
|
276
|
-
useEffect(() => {
|
|
277
|
-
check();
|
|
278
|
-
}, [check]);
|
|
279
|
-
return {
|
|
280
|
-
can,
|
|
281
|
-
isLoading,
|
|
282
|
-
error,
|
|
283
|
-
check
|
|
284
|
-
};
|
|
285
|
-
}
|
|
286
|
-
function useAccessLevel(userId, scope) {
|
|
287
|
-
const [accessLevel, setAccessLevel] = useState(null);
|
|
288
|
-
const [isLoading, setIsLoading] = useState(true);
|
|
289
|
-
const [error, setError] = useState(null);
|
|
290
|
-
const fetchAccessLevel = useCallback(async () => {
|
|
291
|
-
if (!userId) {
|
|
292
|
-
setAccessLevel(null);
|
|
293
|
-
setIsLoading(false);
|
|
294
|
-
return;
|
|
239
|
+
if (onPageAccess) {
|
|
240
|
+
onPageAccess(pageName, operation, allowed, record);
|
|
295
241
|
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
setError(null);
|
|
299
|
-
const result = await getAccessLevel({ userId, scope });
|
|
300
|
-
setAccessLevel(result);
|
|
301
|
-
} catch (err) {
|
|
302
|
-
setError(err instanceof Error ? err : new Error("Failed to fetch access level"));
|
|
303
|
-
setAccessLevel(null);
|
|
304
|
-
} finally {
|
|
305
|
-
setIsLoading(false);
|
|
242
|
+
if (strictMode && !allowed && onStrictModeViolation) {
|
|
243
|
+
onStrictModeViolation(pageName, operation, record);
|
|
306
244
|
}
|
|
307
|
-
}, [
|
|
245
|
+
}, [auditLog, user?.id, currentScope, maxHistorySize, onPageAccess, onStrictModeViolation, strictMode]);
|
|
246
|
+
const contextValue = useMemo(() => ({
|
|
247
|
+
hasPagePermission,
|
|
248
|
+
getPagePermissions,
|
|
249
|
+
isEnabled,
|
|
250
|
+
isStrictMode: strictMode,
|
|
251
|
+
isAuditLogEnabled: auditLog,
|
|
252
|
+
getPageAccessHistory,
|
|
253
|
+
clearPageAccessHistory
|
|
254
|
+
}), [
|
|
255
|
+
hasPagePermission,
|
|
256
|
+
getPagePermissions,
|
|
257
|
+
isEnabled,
|
|
258
|
+
strictMode,
|
|
259
|
+
auditLog,
|
|
260
|
+
getPageAccessHistory,
|
|
261
|
+
clearPageAccessHistory
|
|
262
|
+
]);
|
|
308
263
|
useEffect(() => {
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
return {
|
|
312
|
-
accessLevel,
|
|
313
|
-
isLoading,
|
|
314
|
-
error,
|
|
315
|
-
refetch: fetchAccessLevel
|
|
316
|
-
};
|
|
317
|
-
}
|
|
318
|
-
function useMultiplePermissions(userId, scope, permissions, pageId, useCache = true) {
|
|
319
|
-
const [permissionResults, setPermissionResults] = useState({});
|
|
320
|
-
const [isLoading, setIsLoading] = useState(true);
|
|
321
|
-
const [error, setError] = useState(null);
|
|
322
|
-
const fetchPermissions = useCallback(async () => {
|
|
323
|
-
if (!userId || permissions.length === 0) {
|
|
324
|
-
setPermissionResults({});
|
|
325
|
-
setIsLoading(false);
|
|
326
|
-
return;
|
|
327
|
-
}
|
|
328
|
-
try {
|
|
329
|
-
setIsLoading(true);
|
|
330
|
-
setError(null);
|
|
331
|
-
const results = {};
|
|
332
|
-
const promises = permissions.map(async (permission) => {
|
|
333
|
-
const result = useCache ? await isPermittedCached({ userId, scope, permission, pageId }) : await isPermitted({ userId, scope, permission, pageId });
|
|
334
|
-
return { permission, result };
|
|
335
|
-
});
|
|
336
|
-
const resolved = await Promise.all(promises);
|
|
337
|
-
resolved.forEach(({ permission, result }) => {
|
|
338
|
-
results[permission] = result;
|
|
339
|
-
});
|
|
340
|
-
setPermissionResults(results);
|
|
341
|
-
} catch (err) {
|
|
342
|
-
setError(err instanceof Error ? err : new Error("Failed to check permissions"));
|
|
343
|
-
setPermissionResults({});
|
|
344
|
-
} finally {
|
|
345
|
-
setIsLoading(false);
|
|
264
|
+
if (strictMode && auditLog) {
|
|
265
|
+
console.log(`[PagePermissionProvider] Strict mode enabled - all page access attempts will be logged and enforced`);
|
|
346
266
|
}
|
|
347
|
-
}, [
|
|
348
|
-
|
|
349
|
-
fetchPermissions();
|
|
350
|
-
}, [fetchPermissions]);
|
|
351
|
-
return {
|
|
352
|
-
permissions: permissionResults,
|
|
353
|
-
isLoading,
|
|
354
|
-
error,
|
|
355
|
-
refetch: fetchPermissions
|
|
356
|
-
};
|
|
267
|
+
}, [strictMode, auditLog]);
|
|
268
|
+
return /* @__PURE__ */ jsx(PagePermissionContext.Provider, { value: contextValue, children });
|
|
357
269
|
}
|
|
358
|
-
function
|
|
359
|
-
const
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
);
|
|
365
|
-
const hasAny = useMemo(() => {
|
|
366
|
-
return Object.values(permissionResults).some(Boolean);
|
|
367
|
-
}, [permissionResults]);
|
|
368
|
-
return {
|
|
369
|
-
hasAny,
|
|
370
|
-
isLoading,
|
|
371
|
-
error,
|
|
372
|
-
refetch
|
|
373
|
-
};
|
|
270
|
+
function usePagePermissions() {
|
|
271
|
+
const context = useContext(PagePermissionContext);
|
|
272
|
+
if (!context) {
|
|
273
|
+
throw new Error("usePagePermissions must be used within a PagePermissionProvider");
|
|
274
|
+
}
|
|
275
|
+
return context;
|
|
374
276
|
}
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
const
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
isLoading,
|
|
388
|
-
error,
|
|
389
|
-
refetch
|
|
390
|
-
};
|
|
277
|
+
|
|
278
|
+
// src/rbac/components/PagePermissionGuard.tsx
|
|
279
|
+
import { useMemo as useMemo2, useEffect as useEffect2, useState as useState2 } from "react";
|
|
280
|
+
init_UnifiedAuthProvider();
|
|
281
|
+
|
|
282
|
+
// src/rbac/utils/eventContext.ts
|
|
283
|
+
async function getOrganisationFromEvent(supabase, eventId) {
|
|
284
|
+
const { data, error } = await supabase.from("event").select("organisation_id").eq("event_id", eventId).single();
|
|
285
|
+
if (error || !data) {
|
|
286
|
+
return null;
|
|
287
|
+
}
|
|
288
|
+
return data.organisation_id;
|
|
391
289
|
}
|
|
392
|
-
function
|
|
393
|
-
const
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
if (!userId) {
|
|
398
|
-
setPermissions({});
|
|
399
|
-
setIsLoading(false);
|
|
400
|
-
return;
|
|
401
|
-
}
|
|
402
|
-
try {
|
|
403
|
-
setIsLoading(true);
|
|
404
|
-
setError(null);
|
|
405
|
-
const result = await getPermissionMap({ userId, scope });
|
|
406
|
-
setPermissions(result);
|
|
407
|
-
} catch (err) {
|
|
408
|
-
setError(err instanceof Error ? err : new Error("Failed to fetch cached permissions"));
|
|
409
|
-
} finally {
|
|
410
|
-
setIsLoading(false);
|
|
411
|
-
}
|
|
412
|
-
}, [userId, scope.organisationId, scope.eventId, scope.appId]);
|
|
413
|
-
useEffect(() => {
|
|
414
|
-
fetchCachedPermissions();
|
|
415
|
-
}, [fetchCachedPermissions]);
|
|
290
|
+
async function createScopeFromEvent(supabase, eventId, appId) {
|
|
291
|
+
const organisationId = await getOrganisationFromEvent(supabase, eventId);
|
|
292
|
+
if (!organisationId) {
|
|
293
|
+
return null;
|
|
294
|
+
}
|
|
416
295
|
return {
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
refetch: fetchCachedPermissions
|
|
296
|
+
organisationId,
|
|
297
|
+
eventId,
|
|
298
|
+
appId
|
|
421
299
|
};
|
|
422
300
|
}
|
|
423
301
|
|
|
424
|
-
// src/rbac/
|
|
425
|
-
|
|
426
|
-
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
427
|
-
function
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
permission,
|
|
431
|
-
pageId,
|
|
302
|
+
// src/rbac/components/PagePermissionGuard.tsx
|
|
303
|
+
init_appNameResolver();
|
|
304
|
+
import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
305
|
+
function PagePermissionGuard({
|
|
306
|
+
pageName,
|
|
307
|
+
operation,
|
|
432
308
|
children,
|
|
433
|
-
fallback =
|
|
434
|
-
onDenied,
|
|
435
|
-
loading = null,
|
|
436
|
-
// NEW: Phase 1 - Enhanced Security Features
|
|
309
|
+
fallback = /* @__PURE__ */ jsx2(DefaultAccessDenied, {}),
|
|
437
310
|
strictMode = true,
|
|
438
311
|
auditLog = true,
|
|
439
|
-
|
|
312
|
+
pageId,
|
|
313
|
+
scope,
|
|
314
|
+
onDenied,
|
|
315
|
+
loading = /* @__PURE__ */ jsx2(DefaultLoading, {})
|
|
440
316
|
}) {
|
|
441
|
-
const
|
|
442
|
-
const
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
317
|
+
const { user, selectedOrganisationId, selectedEventId, supabase } = useUnifiedAuth();
|
|
318
|
+
const [hasChecked, setHasChecked] = useState2(false);
|
|
319
|
+
const [checkError, setCheckError] = useState2(null);
|
|
320
|
+
const [resolvedScope, setResolvedScope] = useState2(null);
|
|
321
|
+
useEffect2(() => {
|
|
322
|
+
const resolveScope = async () => {
|
|
323
|
+
if (scope) {
|
|
324
|
+
setResolvedScope(scope);
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
let appId = void 0;
|
|
328
|
+
if (supabase) {
|
|
329
|
+
const appName = getCurrentAppName();
|
|
330
|
+
if (appName) {
|
|
331
|
+
try {
|
|
332
|
+
console.log("[PagePermissionGuard] Resolving app name to ID:", appName);
|
|
333
|
+
const { data: app, error: error2 } = await supabase.from("rbac_apps").select("id, name, is_active").eq("name", appName).eq("is_active", true).single();
|
|
334
|
+
if (error2) {
|
|
335
|
+
console.error("[PagePermissionGuard] Database error resolving app ID:", error2);
|
|
336
|
+
const { data: inactiveApp } = await supabase.from("rbac_apps").select("id, name, is_active").eq("name", appName).single();
|
|
337
|
+
if (inactiveApp) {
|
|
338
|
+
console.error(`[PagePermissionGuard] App "${appName}" exists but is inactive (is_active: ${inactiveApp.is_active})`);
|
|
339
|
+
} else {
|
|
340
|
+
console.error(`[PagePermissionGuard] App "${appName}" not found in rbac_apps table`);
|
|
341
|
+
}
|
|
342
|
+
} else if (app) {
|
|
343
|
+
appId = app.id;
|
|
344
|
+
console.log("[PagePermissionGuard] Successfully resolved app ID:", app.id);
|
|
345
|
+
} else {
|
|
346
|
+
console.error("[PagePermissionGuard] No app data returned for:", appName);
|
|
347
|
+
}
|
|
348
|
+
} catch (error2) {
|
|
349
|
+
console.error("[PagePermissionGuard] Unexpected error resolving app ID:", error2);
|
|
350
|
+
}
|
|
351
|
+
} else {
|
|
352
|
+
console.error("[PagePermissionGuard] No app name found. Make sure to call setRBACAppName() in your app setup.");
|
|
452
353
|
}
|
|
453
354
|
}
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
355
|
+
if (selectedOrganisationId && selectedEventId) {
|
|
356
|
+
if (!appId) {
|
|
357
|
+
if (false) {
|
|
358
|
+
console.warn("[PagePermissionGuard] App ID not resolved in test environment, proceeding without it");
|
|
359
|
+
} else {
|
|
360
|
+
console.error("[PagePermissionGuard] CRITICAL: App ID not resolved. Check console for details.");
|
|
361
|
+
setCheckError(new Error("App ID not resolved. Check console for database errors."));
|
|
362
|
+
setResolvedScope(null);
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
if (appId) {
|
|
367
|
+
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
368
|
+
if (!uuidRegex.test(appId)) {
|
|
369
|
+
console.error("[PagePermissionGuard] CRITICAL: App ID is not a valid UUID:", appId);
|
|
370
|
+
setCheckError(new Error(`Invalid app ID format: ${appId}. Expected UUID.`));
|
|
371
|
+
setResolvedScope(null);
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
const resolvedScope2 = {
|
|
376
|
+
organisationId: selectedOrganisationId,
|
|
377
|
+
eventId: selectedEventId,
|
|
378
|
+
appId
|
|
379
|
+
};
|
|
380
|
+
console.log("[PagePermissionGuard] Setting resolved scope:", resolvedScope2);
|
|
381
|
+
setResolvedScope(resolvedScope2);
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
if (selectedOrganisationId) {
|
|
385
|
+
if (!appId) {
|
|
386
|
+
if (false) {
|
|
387
|
+
console.warn("[PagePermissionGuard] App ID not resolved in test environment, proceeding without it");
|
|
388
|
+
} else {
|
|
389
|
+
console.error("[PagePermissionGuard] CRITICAL: App ID not resolved. Check console for details.");
|
|
390
|
+
setCheckError(new Error("App ID not resolved. Check console for database errors."));
|
|
391
|
+
setResolvedScope(null);
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
if (appId) {
|
|
396
|
+
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
397
|
+
if (!uuidRegex.test(appId)) {
|
|
398
|
+
console.error("[PagePermissionGuard] CRITICAL: App ID is not a valid UUID:", appId);
|
|
399
|
+
setCheckError(new Error(`Invalid app ID format: ${appId}. Expected UUID.`));
|
|
400
|
+
setResolvedScope(null);
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
const resolvedScope2 = {
|
|
405
|
+
organisationId: selectedOrganisationId,
|
|
406
|
+
eventId: selectedEventId || void 0,
|
|
407
|
+
appId
|
|
408
|
+
};
|
|
409
|
+
console.log("[PagePermissionGuard] Setting resolved scope (org only):", resolvedScope2);
|
|
410
|
+
setResolvedScope(resolvedScope2);
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
if (selectedEventId && supabase) {
|
|
414
|
+
try {
|
|
415
|
+
const eventScope = await createScopeFromEvent(supabase, selectedEventId);
|
|
416
|
+
if (!eventScope) {
|
|
417
|
+
setCheckError(new Error("Could not resolve organization from event context"));
|
|
418
|
+
setResolvedScope(null);
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
setResolvedScope({
|
|
422
|
+
...eventScope,
|
|
423
|
+
appId: appId || eventScope.appId
|
|
424
|
+
});
|
|
425
|
+
} catch (error2) {
|
|
426
|
+
setCheckError(error2);
|
|
427
|
+
setResolvedScope(null);
|
|
428
|
+
}
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
setCheckError(new Error("Either organisation context or event context is required for page permission checking"));
|
|
432
|
+
setResolvedScope(null);
|
|
433
|
+
};
|
|
434
|
+
resolveScope();
|
|
435
|
+
}, [scope, selectedOrganisationId, selectedEventId, supabase]);
|
|
436
|
+
const effectivePageId = useMemo2(() => {
|
|
437
|
+
return pageId || pageName;
|
|
438
|
+
}, [pageId, pageName]);
|
|
439
|
+
const permission = useMemo2(() => {
|
|
440
|
+
return `${operation}:page.${pageName}`;
|
|
441
|
+
}, [operation, pageName]);
|
|
442
|
+
console.log("[PagePermissionGuard] Calling useCan with scope:", resolvedScope);
|
|
443
|
+
console.log("[PagePermissionGuard] resolvedScope:", resolvedScope);
|
|
444
|
+
console.log("[PagePermissionGuard] selectedEventId:", selectedEventId);
|
|
445
|
+
console.log("[PagePermissionGuard] About to call useCan with:", {
|
|
446
|
+
userId: user?.id || "",
|
|
447
|
+
scope: resolvedScope || { organisationId: "", appId: "", eventId: selectedEventId || void 0 },
|
|
448
|
+
permission,
|
|
449
|
+
pageId: effectivePageId,
|
|
450
|
+
useCache: true
|
|
451
|
+
});
|
|
452
|
+
const { can, isLoading: canIsLoading, error: canError } = useCan(
|
|
453
|
+
user?.id || "",
|
|
454
|
+
resolvedScope || { organisationId: "", appId: "", eventId: selectedEventId || void 0 },
|
|
455
|
+
permission,
|
|
456
|
+
effectivePageId,
|
|
457
|
+
true
|
|
458
|
+
// Use cache
|
|
459
|
+
);
|
|
460
|
+
console.log("[PagePermissionGuard] useCan returned:", { can, canIsLoading, canError });
|
|
461
|
+
const isLoading = !resolvedScope || canIsLoading;
|
|
462
|
+
const error = checkError || canError;
|
|
463
|
+
console.log("[PagePermissionGuard] Combined state:", {
|
|
464
|
+
can,
|
|
465
|
+
isLoading,
|
|
466
|
+
canIsLoading,
|
|
467
|
+
resolvedScopeExists: !!resolvedScope,
|
|
468
|
+
error: error?.message
|
|
469
|
+
});
|
|
470
|
+
useEffect2(() => {
|
|
471
|
+
if (!isLoading && !error) {
|
|
472
|
+
setHasChecked(true);
|
|
473
|
+
setCheckError(null);
|
|
474
|
+
if (!can && onDenied) {
|
|
475
|
+
onDenied(pageName, operation);
|
|
476
|
+
}
|
|
477
|
+
} else if (error) {
|
|
478
|
+
setCheckError(error);
|
|
479
|
+
setHasChecked(true);
|
|
488
480
|
}
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
481
|
+
}, [can, isLoading, error, pageName, operation, onDenied]);
|
|
482
|
+
useEffect2(() => {
|
|
483
|
+
if (auditLog && hasChecked && !isLoading) {
|
|
484
|
+
console.log(`[PagePermissionGuard] Page access attempt:`, {
|
|
485
|
+
pageName,
|
|
486
|
+
operation,
|
|
487
|
+
userId: user?.id,
|
|
488
|
+
scope: resolvedScope,
|
|
489
|
+
allowed: can,
|
|
498
490
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
499
491
|
});
|
|
500
492
|
}
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
493
|
+
}, [auditLog, hasChecked, isLoading, pageName, operation, user?.id, resolvedScope, can]);
|
|
494
|
+
useEffect2(() => {
|
|
495
|
+
if (strictMode && hasChecked && !isLoading && !can) {
|
|
496
|
+
console.error(`[PagePermissionGuard] STRICT MODE VIOLATION: User attempted to access protected page without permission`, {
|
|
497
|
+
pageName,
|
|
498
|
+
operation,
|
|
499
|
+
userId: user?.id,
|
|
500
|
+
scope: resolvedScope,
|
|
507
501
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
508
502
|
});
|
|
509
503
|
}
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
}
|
|
513
|
-
return /* @__PURE__ */ jsx(Fragment, { children: fallback });
|
|
504
|
+
}, [strictMode, hasChecked, isLoading, can, pageName, operation, user?.id, resolvedScope]);
|
|
505
|
+
if (isLoading || !resolvedScope || !hasChecked) {
|
|
506
|
+
return /* @__PURE__ */ jsx2(Fragment, { children: loading });
|
|
514
507
|
}
|
|
515
|
-
if (
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
522
|
-
});
|
|
508
|
+
if (checkError) {
|
|
509
|
+
console.error(`[PagePermissionGuard] Permission check failed for page ${pageName}:`, checkError);
|
|
510
|
+
return /* @__PURE__ */ jsx2(Fragment, { children: fallback });
|
|
511
|
+
}
|
|
512
|
+
if (!can) {
|
|
513
|
+
return /* @__PURE__ */ jsx2(Fragment, { children: fallback });
|
|
523
514
|
}
|
|
524
|
-
return /* @__PURE__ */
|
|
515
|
+
return /* @__PURE__ */ jsx2(Fragment, { children });
|
|
525
516
|
}
|
|
526
|
-
function
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
517
|
+
function DefaultAccessDenied() {
|
|
518
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center min-h-[200px] p-8 text-center", children: [
|
|
519
|
+
/* @__PURE__ */ jsx2("div", { className: "mb-4", children: /* @__PURE__ */ jsx2("svg", { className: "w-16 h-16 text-acc-500 mx-auto", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx2("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z" }) }) }),
|
|
520
|
+
/* @__PURE__ */ jsx2("h2", { className: "text-xl font-semibold text-sec-900 mb-2", children: "Access Denied" }),
|
|
521
|
+
/* @__PURE__ */ jsx2("p", { className: "text-sec-600 mb-4", children: "You don't have permission to access this page." }),
|
|
522
|
+
/* @__PURE__ */ jsx2(
|
|
523
|
+
"button",
|
|
524
|
+
{
|
|
525
|
+
onClick: () => window.history.back(),
|
|
526
|
+
className: "px-4 py-2 bg-main-600 text-main-50 rounded-md hover:bg-main-700 transition-colors",
|
|
527
|
+
children: "Go Back"
|
|
528
|
+
}
|
|
529
|
+
)
|
|
530
|
+
] });
|
|
531
|
+
}
|
|
532
|
+
function DefaultLoading() {
|
|
533
|
+
return /* @__PURE__ */ jsx2("div", { className: "flex items-center justify-center min-h-[200px] p-8", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center space-x-2", children: [
|
|
534
|
+
/* @__PURE__ */ jsx2("div", { className: "animate-spin rounded-full h-8 w-8 border-b-2 border-main-600" }),
|
|
535
|
+
/* @__PURE__ */ jsx2("span", { className: "text-sec-600", children: "Checking permissions..." })
|
|
536
|
+
] }) });
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// src/rbac/components/SecureDataProvider.tsx
|
|
540
|
+
init_UnifiedAuthProvider();
|
|
541
|
+
import { createContext as createContext2, useContext as useContext2, useState as useState3, useCallback as useCallback3, useMemo as useMemo3, useEffect as useEffect3 } from "react";
|
|
542
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
543
|
+
var SecureDataContext = createContext2(null);
|
|
544
|
+
function SecureDataProvider({
|
|
530
545
|
children,
|
|
531
|
-
|
|
532
|
-
|
|
546
|
+
strictMode = true,
|
|
547
|
+
auditLog = true,
|
|
548
|
+
onDataAccess,
|
|
549
|
+
onStrictModeViolation,
|
|
550
|
+
maxHistorySize = 1e3,
|
|
551
|
+
enforceRLS = true
|
|
533
552
|
}) {
|
|
534
|
-
const
|
|
535
|
-
const
|
|
536
|
-
|
|
537
|
-
|
|
553
|
+
const { user, selectedOrganisationId, selectedEventId } = useUnifiedAuth();
|
|
554
|
+
const { validateContext } = useSecureDataAccess();
|
|
555
|
+
const [dataAccessHistory, setDataAccessHistory] = useState3([]);
|
|
556
|
+
const [isEnabled, setIsEnabled] = useState3(true);
|
|
557
|
+
const currentScope = useMemo3(() => {
|
|
558
|
+
if (!selectedOrganisationId) return null;
|
|
559
|
+
return {
|
|
560
|
+
organisationId: selectedOrganisationId,
|
|
561
|
+
eventId: selectedEventId || void 0,
|
|
562
|
+
appId: void 0
|
|
563
|
+
};
|
|
564
|
+
}, [selectedOrganisationId, selectedEventId]);
|
|
565
|
+
const isDataAccessAllowed = useCallback3((table, operation, scope) => {
|
|
566
|
+
if (!isEnabled) return true;
|
|
567
|
+
if (!user?.id) return false;
|
|
568
|
+
const effectiveScope = scope || currentScope;
|
|
569
|
+
if (!effectiveScope) return false;
|
|
570
|
+
const permission = `${operation}:data.${table}`;
|
|
571
|
+
return true;
|
|
572
|
+
}, [isEnabled, user?.id, currentScope]);
|
|
573
|
+
const getDataAccessPermissions = useCallback3(() => {
|
|
574
|
+
if (!isEnabled || !user?.id) return {};
|
|
575
|
+
return {};
|
|
576
|
+
}, [isEnabled, user?.id]);
|
|
577
|
+
const getDataAccessHistory = useCallback3(() => {
|
|
578
|
+
return [...dataAccessHistory];
|
|
579
|
+
}, [dataAccessHistory]);
|
|
580
|
+
const clearDataAccessHistory = useCallback3(() => {
|
|
581
|
+
setDataAccessHistory([]);
|
|
582
|
+
}, []);
|
|
583
|
+
const validateDataAccess = useCallback3((table, operation, scope) => {
|
|
584
|
+
if (!isEnabled) return true;
|
|
585
|
+
if (!user?.id) return false;
|
|
586
|
+
const effectiveScope = scope || currentScope;
|
|
587
|
+
if (!effectiveScope) return false;
|
|
538
588
|
try {
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
if (globalUser?.id) {
|
|
544
|
-
effectiveUserId = globalUser.id;
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
} catch (error2) {
|
|
548
|
-
logger.debug("Could not infer userId from context:", error2);
|
|
589
|
+
validateContext();
|
|
590
|
+
} catch (error) {
|
|
591
|
+
console.error(`[SecureDataProvider] Organisation context validation failed:`, error);
|
|
592
|
+
return false;
|
|
549
593
|
}
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
logger.error("AccessLevelGuard: No userId provided and could not infer from context");
|
|
554
|
-
return /* @__PURE__ */ jsxs("div", { className: "rbac-error", role: "alert", children: [
|
|
555
|
-
/* @__PURE__ */ jsx("p", { children: "Access level check failed: User context not available" }),
|
|
556
|
-
/* @__PURE__ */ jsxs("details", { children: [
|
|
557
|
-
/* @__PURE__ */ jsx("summary", { children: "Debug info" }),
|
|
558
|
-
/* @__PURE__ */ jsx("p", { children: "Make sure to either:" }),
|
|
559
|
-
/* @__PURE__ */ jsxs("ul", { children: [
|
|
560
|
-
/* @__PURE__ */ jsx("li", { children: "Pass userId prop explicitly" }),
|
|
561
|
-
/* @__PURE__ */ jsx("li", { children: "Wrap your app with an auth provider" }),
|
|
562
|
-
/* @__PURE__ */ jsx("li", { children: "Set window.__PACE_USER__ with user data" })
|
|
563
|
-
] })
|
|
564
|
-
] })
|
|
565
|
-
] });
|
|
566
|
-
}
|
|
567
|
-
if (isLoading) {
|
|
568
|
-
return loading || /* @__PURE__ */ jsx("div", { className: "rbac-loading", role: "status", "aria-live": "polite", children: /* @__PURE__ */ jsx("span", { className: "sr-only", children: "Checking access level..." }) });
|
|
569
|
-
}
|
|
570
|
-
if (error) {
|
|
571
|
-
logger.error("Access level check failed:", error);
|
|
572
|
-
return fallback;
|
|
573
|
-
}
|
|
574
|
-
const levelHierarchy = ["viewer", "participant", "planner", "admin", "super"];
|
|
575
|
-
const userLevelIndex = accessLevel ? levelHierarchy.indexOf(accessLevel) : -1;
|
|
576
|
-
const requiredLevelIndex = levelHierarchy.indexOf(minLevel);
|
|
577
|
-
if (userLevelIndex < requiredLevelIndex) {
|
|
578
|
-
return /* @__PURE__ */ jsx(Fragment, { children: fallback });
|
|
579
|
-
}
|
|
580
|
-
return /* @__PURE__ */ jsx(Fragment, { children });
|
|
581
|
-
}
|
|
582
|
-
function withPermissionGuard(config, handler) {
|
|
583
|
-
return async (...args) => {
|
|
584
|
-
const [req] = args;
|
|
585
|
-
const userId = req.user?.id;
|
|
586
|
-
const organisationId = req.organisationId;
|
|
587
|
-
const eventId = req.eventId;
|
|
588
|
-
const appId = req.appId;
|
|
589
|
-
if (!userId || !organisationId) {
|
|
590
|
-
throw new Error("User context required for permission check");
|
|
591
|
-
}
|
|
592
|
-
const { isPermitted: isPermitted2 } = await import("../api-ETQ6YJ3C.js");
|
|
593
|
-
const hasPermission2 = await isPermitted2({
|
|
594
|
-
userId,
|
|
595
|
-
scope: { organisationId, eventId, appId },
|
|
596
|
-
permission: config.permission,
|
|
597
|
-
pageId: config.pageId
|
|
598
|
-
});
|
|
599
|
-
if (!hasPermission2) {
|
|
600
|
-
throw new Error(`Permission denied: ${config.permission}`);
|
|
601
|
-
}
|
|
602
|
-
return handler(...args);
|
|
603
|
-
};
|
|
604
|
-
}
|
|
605
|
-
function withAccessLevelGuard(minLevel, handler) {
|
|
606
|
-
return async (...args) => {
|
|
607
|
-
const [req] = args;
|
|
608
|
-
const userId = req.user?.id;
|
|
609
|
-
const organisationId = req.organisationId;
|
|
610
|
-
const eventId = req.eventId;
|
|
611
|
-
const appId = req.appId;
|
|
612
|
-
if (!userId || !organisationId) {
|
|
613
|
-
throw new Error("User context required for access level check");
|
|
614
|
-
}
|
|
615
|
-
const { getAccessLevel: getAccessLevel2 } = await import("../api-ETQ6YJ3C.js");
|
|
616
|
-
const accessLevel = await getAccessLevel2({
|
|
617
|
-
userId,
|
|
618
|
-
scope: { organisationId, eventId, appId }
|
|
619
|
-
});
|
|
620
|
-
const levelHierarchy = ["viewer", "participant", "planner", "admin", "super"];
|
|
621
|
-
const userLevelIndex = levelHierarchy.indexOf(accessLevel);
|
|
622
|
-
const requiredLevelIndex = levelHierarchy.indexOf(minLevel);
|
|
623
|
-
if (userLevelIndex < requiredLevelIndex) {
|
|
624
|
-
throw new Error(`Access level required: ${minLevel}, got: ${accessLevel}`);
|
|
625
|
-
}
|
|
626
|
-
return handler(...args);
|
|
627
|
-
};
|
|
628
|
-
}
|
|
629
|
-
function withRoleGuard(config, handler) {
|
|
630
|
-
return async (...args) => {
|
|
631
|
-
const [req] = args;
|
|
632
|
-
const userId = req.user?.id;
|
|
633
|
-
const organisationId = req.organisationId;
|
|
634
|
-
const eventId = req.eventId;
|
|
635
|
-
const appId = req.appId;
|
|
636
|
-
if (!userId || !organisationId) {
|
|
637
|
-
throw new Error("User context required for role check");
|
|
638
|
-
}
|
|
639
|
-
if (config.globalRoles && config.globalRoles.length > 0) {
|
|
640
|
-
const { isSuperAdmin } = await import("../api-ETQ6YJ3C.js");
|
|
641
|
-
const isSuper = await isSuperAdmin(userId);
|
|
642
|
-
if (isSuper) {
|
|
643
|
-
if (organisationId) {
|
|
644
|
-
const { emitAuditEvent: emitAuditEvent2 } = await import("../audit-BUW3LMJB.js");
|
|
645
|
-
await emitAuditEvent2({
|
|
646
|
-
type: "permission_check",
|
|
647
|
-
userId,
|
|
648
|
-
organisationId,
|
|
649
|
-
eventId,
|
|
650
|
-
appId,
|
|
651
|
-
permission: "bypass:all",
|
|
652
|
-
decision: true,
|
|
653
|
-
source: "api",
|
|
654
|
-
bypass: true,
|
|
655
|
-
duration_ms: 0,
|
|
656
|
-
metadata: {
|
|
657
|
-
operation: "role_guard",
|
|
658
|
-
reason: "super_admin_bypass"
|
|
659
|
-
}
|
|
660
|
-
});
|
|
661
|
-
}
|
|
662
|
-
return handler(...args);
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
if (config.organisationRoles && config.organisationRoles.length > 0) {
|
|
666
|
-
const { isOrganisationAdmin } = await import("../api-ETQ6YJ3C.js");
|
|
667
|
-
const isOrgAdmin = await isOrganisationAdmin(userId, organisationId);
|
|
668
|
-
if (!isOrgAdmin && config.requireAll !== false) {
|
|
669
|
-
throw new Error(`Organisation admin role required`);
|
|
670
|
-
}
|
|
671
|
-
}
|
|
672
|
-
if (config.eventAppRoles && config.eventAppRoles.length > 0 && eventId && appId) {
|
|
673
|
-
const { isEventAdmin } = await import("../api-ETQ6YJ3C.js");
|
|
674
|
-
const isEventAdminUser = await isEventAdmin(userId, { organisationId, eventId, appId });
|
|
675
|
-
if (!isEventAdminUser && config.requireAll !== false) {
|
|
676
|
-
throw new Error(`Event admin role required`);
|
|
677
|
-
}
|
|
678
|
-
}
|
|
679
|
-
if (organisationId) {
|
|
680
|
-
const { emitAuditEvent: emitAuditEvent2 } = await import("../audit-BUW3LMJB.js");
|
|
681
|
-
await emitAuditEvent2({
|
|
682
|
-
type: "permission_check",
|
|
683
|
-
userId,
|
|
684
|
-
organisationId,
|
|
685
|
-
eventId,
|
|
686
|
-
appId,
|
|
687
|
-
permission: "role:check",
|
|
688
|
-
decision: true,
|
|
689
|
-
source: "api",
|
|
690
|
-
bypass: false,
|
|
691
|
-
duration_ms: 0,
|
|
692
|
-
metadata: {
|
|
693
|
-
operation: "role_guard"
|
|
694
|
-
}
|
|
695
|
-
});
|
|
696
|
-
}
|
|
697
|
-
return handler(...args);
|
|
698
|
-
};
|
|
699
|
-
}
|
|
700
|
-
function createRBACMiddleware(config) {
|
|
701
|
-
return async (req, res, next) => {
|
|
702
|
-
const { pathname } = req.nextUrl;
|
|
703
|
-
const userId = req.user?.id;
|
|
704
|
-
const organisationId = req.organisationId;
|
|
705
|
-
if (!userId || !organisationId) {
|
|
706
|
-
return res.redirect(config.fallbackUrl || "/login");
|
|
707
|
-
}
|
|
708
|
-
const protectedRoute = config.protectedRoutes.find(
|
|
709
|
-
(route) => pathname.startsWith(route.path)
|
|
710
|
-
);
|
|
711
|
-
if (protectedRoute) {
|
|
712
|
-
try {
|
|
713
|
-
const { isPermitted: isPermitted2 } = await import("../api-ETQ6YJ3C.js");
|
|
714
|
-
const hasPermission2 = await isPermitted2({
|
|
715
|
-
userId,
|
|
716
|
-
scope: { organisationId },
|
|
717
|
-
permission: protectedRoute.permission,
|
|
718
|
-
pageId: protectedRoute.pageId
|
|
719
|
-
});
|
|
720
|
-
if (!hasPermission2) {
|
|
721
|
-
return res.redirect(config.fallbackUrl || "/access-denied");
|
|
722
|
-
}
|
|
723
|
-
} catch (_error) {
|
|
724
|
-
return res.redirect(config.fallbackUrl || "/access-denied");
|
|
725
|
-
}
|
|
726
|
-
}
|
|
727
|
-
next();
|
|
728
|
-
};
|
|
729
|
-
}
|
|
730
|
-
function createRBACExpressMiddleware(config) {
|
|
731
|
-
return async (req, res, next) => {
|
|
732
|
-
const userId = req.user?.id;
|
|
733
|
-
const organisationId = req.organisationId;
|
|
734
|
-
const eventId = req.eventId;
|
|
735
|
-
const appId = req.appId;
|
|
736
|
-
if (!userId || !organisationId) {
|
|
737
|
-
return res.status(401).json({ error: "User context required" });
|
|
738
|
-
}
|
|
739
|
-
try {
|
|
740
|
-
const { isPermitted: isPermitted2 } = await import("../api-ETQ6YJ3C.js");
|
|
741
|
-
const hasPermission2 = await isPermitted2({
|
|
742
|
-
userId,
|
|
743
|
-
scope: { organisationId, eventId, appId },
|
|
744
|
-
permission: config.permission,
|
|
745
|
-
pageId: config.pageId
|
|
746
|
-
});
|
|
747
|
-
if (!hasPermission2) {
|
|
748
|
-
return res.status(403).json({ error: "Permission denied" });
|
|
749
|
-
}
|
|
750
|
-
next();
|
|
751
|
-
} catch (_error) {
|
|
752
|
-
return res.status(500).json({ error: "Permission check failed" });
|
|
753
|
-
}
|
|
754
|
-
};
|
|
755
|
-
}
|
|
756
|
-
function hasPermissionCached(userId, scope, _permission, _pageId) {
|
|
757
|
-
const cacheKey = RBACCache.generatePermissionKey({
|
|
758
|
-
userId,
|
|
759
|
-
organisationId: scope.organisationId,
|
|
760
|
-
eventId: scope.eventId,
|
|
761
|
-
appId: scope.appId
|
|
762
|
-
});
|
|
763
|
-
return rbacCache.get(cacheKey) || false;
|
|
764
|
-
}
|
|
765
|
-
function hasAnyPermissionCached(userId, scope, permissions, pageId) {
|
|
766
|
-
return permissions.some(
|
|
767
|
-
(permission) => hasPermissionCached(userId, scope, permission, pageId)
|
|
768
|
-
);
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
// src/rbac/components/PagePermissionProvider.tsx
|
|
772
|
-
init_UnifiedAuthProvider();
|
|
773
|
-
import { createContext, useContext as useContext2, useState as useState2, useCallback as useCallback2, useMemo as useMemo2, useEffect as useEffect2 } from "react";
|
|
774
|
-
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
775
|
-
var PagePermissionContext = createContext(null);
|
|
776
|
-
function PagePermissionProvider({
|
|
777
|
-
children,
|
|
778
|
-
strictMode = true,
|
|
779
|
-
auditLog = true,
|
|
780
|
-
onPageAccess,
|
|
781
|
-
onStrictModeViolation,
|
|
782
|
-
maxHistorySize = 1e3
|
|
783
|
-
}) {
|
|
784
|
-
const { user, selectedOrganisationId, selectedEventId } = useUnifiedAuth();
|
|
785
|
-
const [pageAccessHistory, setPageAccessHistory] = useState2([]);
|
|
786
|
-
const [isEnabled, setIsEnabled] = useState2(true);
|
|
787
|
-
const currentScope = useMemo2(() => {
|
|
788
|
-
if (!selectedOrganisationId) return null;
|
|
789
|
-
return {
|
|
790
|
-
organisationId: selectedOrganisationId,
|
|
791
|
-
eventId: selectedEventId || void 0,
|
|
792
|
-
appId: void 0
|
|
793
|
-
};
|
|
794
|
-
}, [selectedOrganisationId, selectedEventId]);
|
|
795
|
-
const hasPagePermission = useCallback2((pageName, operation, pageId, scope) => {
|
|
796
|
-
if (!isEnabled) return true;
|
|
797
|
-
if (!user?.id) return false;
|
|
798
|
-
const effectiveScope = scope || currentScope;
|
|
799
|
-
if (!effectiveScope) return false;
|
|
800
|
-
const permission = `${operation}:page.${pageName}`;
|
|
801
|
-
return false;
|
|
802
|
-
}, [isEnabled, user?.id, currentScope]);
|
|
803
|
-
const getPagePermissions = useCallback2(() => {
|
|
804
|
-
if (!isEnabled || !user?.id) return {};
|
|
805
|
-
return {};
|
|
806
|
-
}, [isEnabled, user?.id]);
|
|
807
|
-
const getPageAccessHistory = useCallback2(() => {
|
|
808
|
-
return [...pageAccessHistory];
|
|
809
|
-
}, [pageAccessHistory]);
|
|
810
|
-
const clearPageAccessHistory = useCallback2(() => {
|
|
811
|
-
setPageAccessHistory([]);
|
|
812
|
-
}, []);
|
|
813
|
-
const recordPageAccess = useCallback2((pageName, operation, allowed, pageId, scope) => {
|
|
594
|
+
return isDataAccessAllowed(table, operation, effectiveScope);
|
|
595
|
+
}, [isEnabled, user?.id, currentScope, validateContext, isDataAccessAllowed]);
|
|
596
|
+
const recordDataAccess = useCallback3((table, operation, allowed, query, filters, scope) => {
|
|
814
597
|
if (!auditLog || !user?.id) return;
|
|
815
598
|
const record = {
|
|
816
|
-
|
|
599
|
+
table,
|
|
817
600
|
operation,
|
|
818
601
|
userId: user.id,
|
|
819
602
|
scope: scope || currentScope || { organisationId: "" },
|
|
820
603
|
allowed,
|
|
821
604
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
822
|
-
|
|
605
|
+
query,
|
|
606
|
+
filters
|
|
823
607
|
};
|
|
824
|
-
|
|
608
|
+
setDataAccessHistory((prev) => {
|
|
825
609
|
const newHistory = [record, ...prev];
|
|
826
610
|
return newHistory.slice(0, maxHistorySize);
|
|
827
611
|
});
|
|
828
|
-
if (
|
|
829
|
-
|
|
612
|
+
if (onDataAccess) {
|
|
613
|
+
onDataAccess(table, operation, allowed, record);
|
|
830
614
|
}
|
|
831
615
|
if (strictMode && !allowed && onStrictModeViolation) {
|
|
832
|
-
onStrictModeViolation(
|
|
616
|
+
onStrictModeViolation(table, operation, record);
|
|
833
617
|
}
|
|
834
|
-
}, [auditLog, user?.id, currentScope, maxHistorySize,
|
|
835
|
-
const contextValue =
|
|
836
|
-
|
|
837
|
-
|
|
618
|
+
}, [auditLog, user?.id, currentScope, maxHistorySize, onDataAccess, onStrictModeViolation, strictMode]);
|
|
619
|
+
const contextValue = useMemo3(() => ({
|
|
620
|
+
isDataAccessAllowed,
|
|
621
|
+
getDataAccessPermissions,
|
|
838
622
|
isEnabled,
|
|
839
623
|
isStrictMode: strictMode,
|
|
840
624
|
isAuditLogEnabled: auditLog,
|
|
841
|
-
|
|
842
|
-
|
|
625
|
+
getDataAccessHistory,
|
|
626
|
+
clearDataAccessHistory,
|
|
627
|
+
validateDataAccess
|
|
843
628
|
}), [
|
|
844
|
-
|
|
845
|
-
|
|
629
|
+
isDataAccessAllowed,
|
|
630
|
+
getDataAccessPermissions,
|
|
846
631
|
isEnabled,
|
|
847
632
|
strictMode,
|
|
848
633
|
auditLog,
|
|
849
|
-
|
|
850
|
-
|
|
634
|
+
getDataAccessHistory,
|
|
635
|
+
clearDataAccessHistory,
|
|
636
|
+
validateDataAccess
|
|
851
637
|
]);
|
|
852
|
-
|
|
638
|
+
useEffect3(() => {
|
|
853
639
|
if (strictMode && auditLog) {
|
|
854
|
-
console.log(`[
|
|
640
|
+
console.log(`[SecureDataProvider] Strict mode enabled - all data access attempts will be logged and enforced`);
|
|
855
641
|
}
|
|
856
642
|
}, [strictMode, auditLog]);
|
|
857
|
-
|
|
643
|
+
useEffect3(() => {
|
|
644
|
+
if (enforceRLS && auditLog) {
|
|
645
|
+
console.log(`[SecureDataProvider] RLS enforcement enabled - all queries will include organisation context`);
|
|
646
|
+
}
|
|
647
|
+
}, [enforceRLS, auditLog]);
|
|
648
|
+
return /* @__PURE__ */ jsx3(SecureDataContext.Provider, { value: contextValue, children });
|
|
858
649
|
}
|
|
859
|
-
function
|
|
860
|
-
const context = useContext2(
|
|
650
|
+
function useSecureData() {
|
|
651
|
+
const context = useContext2(SecureDataContext);
|
|
861
652
|
if (!context) {
|
|
862
|
-
throw new Error("
|
|
653
|
+
throw new Error("useSecureData must be used within a SecureDataProvider");
|
|
863
654
|
}
|
|
864
655
|
return context;
|
|
865
656
|
}
|
|
866
657
|
|
|
867
|
-
// src/rbac/components/
|
|
868
|
-
import { useMemo as
|
|
658
|
+
// src/rbac/components/PermissionEnforcer.tsx
|
|
659
|
+
import { useMemo as useMemo4, useEffect as useEffect4, useState as useState4 } from "react";
|
|
869
660
|
init_UnifiedAuthProvider();
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
const { data, error } = await supabase.from("event").select("organisation_id").eq("event_id", eventId).single();
|
|
874
|
-
if (error || !data) {
|
|
875
|
-
return null;
|
|
876
|
-
}
|
|
877
|
-
return data.organisation_id;
|
|
878
|
-
}
|
|
879
|
-
async function createScopeFromEvent(supabase, eventId, appId) {
|
|
880
|
-
const organisationId = await getOrganisationFromEvent(supabase, eventId);
|
|
881
|
-
if (!organisationId) {
|
|
882
|
-
return null;
|
|
883
|
-
}
|
|
884
|
-
return {
|
|
885
|
-
organisationId,
|
|
886
|
-
eventId,
|
|
887
|
-
appId
|
|
888
|
-
};
|
|
889
|
-
}
|
|
890
|
-
|
|
891
|
-
// src/rbac/components/PagePermissionGuard.tsx
|
|
892
|
-
init_appNameResolver();
|
|
893
|
-
import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
894
|
-
function PagePermissionGuard({
|
|
895
|
-
pageName,
|
|
661
|
+
import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
662
|
+
function PermissionEnforcer({
|
|
663
|
+
permissions,
|
|
896
664
|
operation,
|
|
897
665
|
children,
|
|
898
|
-
fallback = /* @__PURE__ */
|
|
666
|
+
fallback = /* @__PURE__ */ jsx4(DefaultAccessDenied2, {}),
|
|
899
667
|
strictMode = true,
|
|
900
668
|
auditLog = true,
|
|
901
|
-
pageId,
|
|
902
669
|
scope,
|
|
903
670
|
onDenied,
|
|
904
|
-
loading = /* @__PURE__ */
|
|
671
|
+
loading = /* @__PURE__ */ jsx4(DefaultLoading2, {}),
|
|
672
|
+
requireAll = true
|
|
905
673
|
}) {
|
|
906
674
|
const { user, selectedOrganisationId, selectedEventId, supabase } = useUnifiedAuth();
|
|
907
|
-
const [hasChecked, setHasChecked] =
|
|
908
|
-
const [checkError, setCheckError] =
|
|
909
|
-
const [
|
|
910
|
-
|
|
675
|
+
const [hasChecked, setHasChecked] = useState4(false);
|
|
676
|
+
const [checkError, setCheckError] = useState4(null);
|
|
677
|
+
const [permissionResults, setPermissionResults] = useState4({});
|
|
678
|
+
const [resolvedScope, setResolvedScope] = useState4(null);
|
|
679
|
+
useEffect4(() => {
|
|
911
680
|
const resolveScope = async () => {
|
|
912
681
|
if (scope) {
|
|
913
682
|
setResolvedScope(scope);
|
|
914
683
|
return;
|
|
915
684
|
}
|
|
916
|
-
let appId = void 0;
|
|
917
|
-
if (supabase) {
|
|
918
|
-
const appName = getCurrentAppName();
|
|
919
|
-
if (appName) {
|
|
920
|
-
try {
|
|
921
|
-
console.log("[PagePermissionGuard] Resolving app name to ID:", appName);
|
|
922
|
-
const { data: app, error: error2 } = await supabase.from("rbac_apps").select("id, name, is_active").eq("name", appName).eq("is_active", true).single();
|
|
923
|
-
if (error2) {
|
|
924
|
-
console.error("[PagePermissionGuard] Database error resolving app ID:", error2);
|
|
925
|
-
const { data: inactiveApp } = await supabase.from("rbac_apps").select("id, name, is_active").eq("name", appName).single();
|
|
926
|
-
if (inactiveApp) {
|
|
927
|
-
console.error(`[PagePermissionGuard] App "${appName}" exists but is inactive (is_active: ${inactiveApp.is_active})`);
|
|
928
|
-
} else {
|
|
929
|
-
console.error(`[PagePermissionGuard] App "${appName}" not found in rbac_apps table`);
|
|
930
|
-
}
|
|
931
|
-
} else if (app) {
|
|
932
|
-
appId = app.id;
|
|
933
|
-
console.log("[PagePermissionGuard] Successfully resolved app ID:", app.id);
|
|
934
|
-
} else {
|
|
935
|
-
console.error("[PagePermissionGuard] No app data returned for:", appName);
|
|
936
|
-
}
|
|
937
|
-
} catch (error2) {
|
|
938
|
-
console.error("[PagePermissionGuard] Unexpected error resolving app ID:", error2);
|
|
939
|
-
}
|
|
940
|
-
} else {
|
|
941
|
-
console.error("[PagePermissionGuard] No app name found. Make sure to call setRBACAppName() in your app setup.");
|
|
942
|
-
}
|
|
943
|
-
}
|
|
944
685
|
if (selectedOrganisationId && selectedEventId) {
|
|
945
|
-
|
|
946
|
-
if (false) {
|
|
947
|
-
console.warn("[PagePermissionGuard] App ID not resolved in test environment, proceeding without it");
|
|
948
|
-
} else {
|
|
949
|
-
console.error("[PagePermissionGuard] CRITICAL: App ID not resolved. Check console for details.");
|
|
950
|
-
setCheckError(new Error("App ID not resolved. Check console for database errors."));
|
|
951
|
-
setResolvedScope(null);
|
|
952
|
-
return;
|
|
953
|
-
}
|
|
954
|
-
}
|
|
955
|
-
if (appId) {
|
|
956
|
-
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
957
|
-
if (!uuidRegex.test(appId)) {
|
|
958
|
-
console.error("[PagePermissionGuard] CRITICAL: App ID is not a valid UUID:", appId);
|
|
959
|
-
setCheckError(new Error(`Invalid app ID format: ${appId}. Expected UUID.`));
|
|
960
|
-
setResolvedScope(null);
|
|
961
|
-
return;
|
|
962
|
-
}
|
|
963
|
-
}
|
|
964
|
-
const resolvedScope2 = {
|
|
686
|
+
setResolvedScope({
|
|
965
687
|
organisationId: selectedOrganisationId,
|
|
966
688
|
eventId: selectedEventId,
|
|
967
|
-
appId
|
|
968
|
-
};
|
|
969
|
-
console.log("[PagePermissionGuard] Setting resolved scope:", resolvedScope2);
|
|
970
|
-
setResolvedScope(resolvedScope2);
|
|
689
|
+
appId: void 0
|
|
690
|
+
});
|
|
971
691
|
return;
|
|
972
692
|
}
|
|
973
693
|
if (selectedOrganisationId) {
|
|
974
|
-
|
|
975
|
-
if (false) {
|
|
976
|
-
console.warn("[PagePermissionGuard] App ID not resolved in test environment, proceeding without it");
|
|
977
|
-
} else {
|
|
978
|
-
console.error("[PagePermissionGuard] CRITICAL: App ID not resolved. Check console for details.");
|
|
979
|
-
setCheckError(new Error("App ID not resolved. Check console for database errors."));
|
|
980
|
-
setResolvedScope(null);
|
|
981
|
-
return;
|
|
982
|
-
}
|
|
983
|
-
}
|
|
984
|
-
if (appId) {
|
|
985
|
-
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
986
|
-
if (!uuidRegex.test(appId)) {
|
|
987
|
-
console.error("[PagePermissionGuard] CRITICAL: App ID is not a valid UUID:", appId);
|
|
988
|
-
setCheckError(new Error(`Invalid app ID format: ${appId}. Expected UUID.`));
|
|
989
|
-
setResolvedScope(null);
|
|
990
|
-
return;
|
|
991
|
-
}
|
|
992
|
-
}
|
|
993
|
-
const resolvedScope2 = {
|
|
694
|
+
setResolvedScope({
|
|
994
695
|
organisationId: selectedOrganisationId,
|
|
995
696
|
eventId: selectedEventId || void 0,
|
|
996
|
-
appId
|
|
997
|
-
};
|
|
998
|
-
console.log("[PagePermissionGuard] Setting resolved scope (org only):", resolvedScope2);
|
|
999
|
-
setResolvedScope(resolvedScope2);
|
|
697
|
+
appId: void 0
|
|
698
|
+
});
|
|
1000
699
|
return;
|
|
1001
700
|
}
|
|
1002
701
|
if (selectedEventId && supabase) {
|
|
@@ -1004,111 +703,86 @@ function PagePermissionGuard({
|
|
|
1004
703
|
const eventScope = await createScopeFromEvent(supabase, selectedEventId);
|
|
1005
704
|
if (!eventScope) {
|
|
1006
705
|
setCheckError(new Error("Could not resolve organization from event context"));
|
|
1007
|
-
setResolvedScope(null);
|
|
1008
706
|
return;
|
|
1009
707
|
}
|
|
1010
|
-
setResolvedScope(
|
|
1011
|
-
...eventScope,
|
|
1012
|
-
appId: appId || eventScope.appId
|
|
1013
|
-
});
|
|
708
|
+
setResolvedScope(eventScope);
|
|
1014
709
|
} catch (error2) {
|
|
1015
710
|
setCheckError(error2);
|
|
1016
|
-
setResolvedScope(null);
|
|
1017
711
|
}
|
|
1018
712
|
return;
|
|
1019
713
|
}
|
|
1020
|
-
setCheckError(new Error("Either organisation context or event context is required for
|
|
1021
|
-
setResolvedScope(null);
|
|
714
|
+
setCheckError(new Error("Either organisation context or event context is required for permission checking"));
|
|
1022
715
|
};
|
|
1023
716
|
resolveScope();
|
|
1024
717
|
}, [scope, selectedOrganisationId, selectedEventId, supabase]);
|
|
1025
|
-
const
|
|
1026
|
-
|
|
1027
|
-
}, [pageId, pageName]);
|
|
1028
|
-
const permission = useMemo3(() => {
|
|
1029
|
-
return `${operation}:page.${pageName}`;
|
|
1030
|
-
}, [operation, pageName]);
|
|
1031
|
-
console.log("[PagePermissionGuard] Calling useCan with scope:", resolvedScope);
|
|
1032
|
-
console.log("[PagePermissionGuard] resolvedScope:", resolvedScope);
|
|
1033
|
-
console.log("[PagePermissionGuard] selectedEventId:", selectedEventId);
|
|
1034
|
-
console.log("[PagePermissionGuard] About to call useCan with:", {
|
|
1035
|
-
userId: user?.id || "",
|
|
1036
|
-
scope: resolvedScope || { organisationId: "", appId: "", eventId: selectedEventId || void 0 },
|
|
1037
|
-
permission,
|
|
1038
|
-
pageId: effectivePageId,
|
|
1039
|
-
useCache: true
|
|
1040
|
-
});
|
|
1041
|
-
const { can, isLoading: canIsLoading, error: canError } = useCan(
|
|
718
|
+
const representativePermission = permissions[0];
|
|
719
|
+
const { can, isLoading, error } = useCan(
|
|
1042
720
|
user?.id || "",
|
|
1043
|
-
resolvedScope || {
|
|
1044
|
-
|
|
1045
|
-
|
|
721
|
+
resolvedScope || { eventId: selectedEventId || void 0 },
|
|
722
|
+
representativePermission,
|
|
723
|
+
void 0,
|
|
1046
724
|
true
|
|
1047
725
|
// Use cache
|
|
1048
726
|
);
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
isLoading,
|
|
1055
|
-
canIsLoading,
|
|
1056
|
-
resolvedScopeExists: !!resolvedScope,
|
|
1057
|
-
error: error?.message
|
|
1058
|
-
});
|
|
1059
|
-
useEffect3(() => {
|
|
727
|
+
const hasRequiredPermissions = useMemo4(() => {
|
|
728
|
+
if (permissions.length === 0) return true;
|
|
729
|
+
return can;
|
|
730
|
+
}, [permissions, can]);
|
|
731
|
+
useEffect4(() => {
|
|
1060
732
|
if (!isLoading && !error) {
|
|
1061
733
|
setHasChecked(true);
|
|
1062
734
|
setCheckError(null);
|
|
1063
|
-
if (!
|
|
1064
|
-
onDenied(
|
|
735
|
+
if (!hasRequiredPermissions && onDenied) {
|
|
736
|
+
onDenied(permissions, operation);
|
|
1065
737
|
}
|
|
1066
738
|
} else if (error) {
|
|
1067
739
|
setCheckError(error);
|
|
1068
740
|
setHasChecked(true);
|
|
1069
741
|
}
|
|
1070
|
-
}, [
|
|
1071
|
-
|
|
742
|
+
}, [hasRequiredPermissions, isLoading, error, permissions, operation, onDenied]);
|
|
743
|
+
useEffect4(() => {
|
|
1072
744
|
if (auditLog && hasChecked && !isLoading) {
|
|
1073
|
-
console.log(`[
|
|
1074
|
-
|
|
745
|
+
console.log(`[PermissionEnforcer] Permission check attempt:`, {
|
|
746
|
+
permissions,
|
|
1075
747
|
operation,
|
|
1076
748
|
userId: user?.id,
|
|
1077
749
|
scope: resolvedScope,
|
|
1078
|
-
allowed:
|
|
750
|
+
allowed: hasRequiredPermissions,
|
|
751
|
+
requireAll,
|
|
1079
752
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1080
753
|
});
|
|
1081
754
|
}
|
|
1082
|
-
}, [auditLog, hasChecked, isLoading,
|
|
1083
|
-
|
|
1084
|
-
if (strictMode && hasChecked && !isLoading && !
|
|
1085
|
-
console.error(`[
|
|
1086
|
-
|
|
755
|
+
}, [auditLog, hasChecked, isLoading, permissions, operation, user?.id, resolvedScope, hasRequiredPermissions, requireAll]);
|
|
756
|
+
useEffect4(() => {
|
|
757
|
+
if (strictMode && hasChecked && !isLoading && !hasRequiredPermissions) {
|
|
758
|
+
console.error(`[PermissionEnforcer] STRICT MODE VIOLATION: User attempted to perform operation without permission`, {
|
|
759
|
+
permissions,
|
|
1087
760
|
operation,
|
|
1088
761
|
userId: user?.id,
|
|
1089
762
|
scope: resolvedScope,
|
|
763
|
+
requireAll,
|
|
1090
764
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1091
765
|
});
|
|
1092
766
|
}
|
|
1093
|
-
}, [strictMode, hasChecked, isLoading,
|
|
1094
|
-
if (isLoading || !
|
|
1095
|
-
return /* @__PURE__ */
|
|
767
|
+
}, [strictMode, hasChecked, isLoading, hasRequiredPermissions, permissions, operation, user?.id, resolvedScope, requireAll]);
|
|
768
|
+
if (isLoading || !hasChecked) {
|
|
769
|
+
return /* @__PURE__ */ jsx4(Fragment2, { children: loading });
|
|
1096
770
|
}
|
|
1097
771
|
if (checkError) {
|
|
1098
|
-
console.error(`[
|
|
1099
|
-
return /* @__PURE__ */
|
|
772
|
+
console.error(`[PermissionEnforcer] Permission check failed for operation ${operation}:`, checkError);
|
|
773
|
+
return /* @__PURE__ */ jsx4(Fragment2, { children: fallback });
|
|
1100
774
|
}
|
|
1101
|
-
if (!
|
|
1102
|
-
return /* @__PURE__ */
|
|
775
|
+
if (!hasRequiredPermissions) {
|
|
776
|
+
return /* @__PURE__ */ jsx4(Fragment2, { children: fallback });
|
|
1103
777
|
}
|
|
1104
|
-
return /* @__PURE__ */
|
|
778
|
+
return /* @__PURE__ */ jsx4(Fragment2, { children });
|
|
1105
779
|
}
|
|
1106
|
-
function
|
|
780
|
+
function DefaultAccessDenied2() {
|
|
1107
781
|
return /* @__PURE__ */ jsxs2("div", { className: "flex flex-col items-center justify-center min-h-[200px] p-8 text-center", children: [
|
|
1108
|
-
/* @__PURE__ */
|
|
1109
|
-
/* @__PURE__ */
|
|
1110
|
-
/* @__PURE__ */
|
|
1111
|
-
/* @__PURE__ */
|
|
782
|
+
/* @__PURE__ */ jsx4("div", { className: "mb-4", children: /* @__PURE__ */ jsx4("svg", { className: "w-16 h-16 text-acc-500 mx-auto", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx4("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z" }) }) }),
|
|
783
|
+
/* @__PURE__ */ jsx4("h2", { className: "text-xl font-semibold text-sec-900 mb-2", children: "Access Denied" }),
|
|
784
|
+
/* @__PURE__ */ jsx4("p", { className: "text-sec-600 mb-4", children: "You don't have permission to perform this operation." }),
|
|
785
|
+
/* @__PURE__ */ jsx4(
|
|
1112
786
|
"button",
|
|
1113
787
|
{
|
|
1114
788
|
onClick: () => window.history.back(),
|
|
@@ -1118,32 +792,36 @@ function DefaultAccessDenied() {
|
|
|
1118
792
|
)
|
|
1119
793
|
] });
|
|
1120
794
|
}
|
|
1121
|
-
function
|
|
1122
|
-
return /* @__PURE__ */
|
|
1123
|
-
/* @__PURE__ */
|
|
1124
|
-
/* @__PURE__ */
|
|
795
|
+
function DefaultLoading2() {
|
|
796
|
+
return /* @__PURE__ */ jsx4("div", { className: "flex items-center justify-center min-h-[200px] p-8", children: /* @__PURE__ */ jsxs2("div", { className: "flex items-center space-x-2", children: [
|
|
797
|
+
/* @__PURE__ */ jsx4("div", { className: "animate-spin rounded-full h-8 w-8 border-b-2 border-main-600" }),
|
|
798
|
+
/* @__PURE__ */ jsx4("span", { className: "text-sec-600", children: "Checking permissions..." })
|
|
1125
799
|
] }) });
|
|
1126
800
|
}
|
|
1127
801
|
|
|
1128
|
-
// src/rbac/components/
|
|
802
|
+
// src/rbac/components/RoleBasedRouter.tsx
|
|
803
|
+
import { useMemo as useMemo5, useCallback as useCallback5, useEffect as useEffect5, useState as useState5, createContext as createContext3, useContext as useContext3 } from "react";
|
|
804
|
+
import { useLocation, useNavigate, Outlet } from "react-router-dom";
|
|
1129
805
|
init_UnifiedAuthProvider();
|
|
1130
|
-
import {
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
806
|
+
import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
807
|
+
var RoleBasedRouterContext = createContext3(null);
|
|
808
|
+
function RoleBasedRouter({
|
|
809
|
+
routes,
|
|
810
|
+
fallbackRoute = "/unauthorized",
|
|
1134
811
|
children,
|
|
1135
812
|
strictMode = true,
|
|
1136
813
|
auditLog = true,
|
|
1137
|
-
|
|
814
|
+
onRouteAccess,
|
|
1138
815
|
onStrictModeViolation,
|
|
1139
816
|
maxHistorySize = 1e3,
|
|
1140
|
-
|
|
817
|
+
unauthorizedComponent: UnauthorizedComponent = DefaultUnauthorizedComponent
|
|
1141
818
|
}) {
|
|
1142
819
|
const { user, selectedOrganisationId, selectedEventId } = useUnifiedAuth();
|
|
1143
|
-
const
|
|
1144
|
-
const
|
|
1145
|
-
const [
|
|
1146
|
-
const
|
|
820
|
+
const location = useLocation();
|
|
821
|
+
const navigate = useNavigate();
|
|
822
|
+
const [routeAccessHistory, setRouteAccessHistory] = useState5([]);
|
|
823
|
+
const [currentRoute, setCurrentRoute] = useState5("");
|
|
824
|
+
const currentScope = useMemo5(() => {
|
|
1147
825
|
if (!selectedOrganisationId) return null;
|
|
1148
826
|
return {
|
|
1149
827
|
organisationId: selectedOrganisationId,
|
|
@@ -1151,281 +829,14 @@ function SecureDataProvider({
|
|
|
1151
829
|
appId: void 0
|
|
1152
830
|
};
|
|
1153
831
|
}, [selectedOrganisationId, selectedEventId]);
|
|
1154
|
-
const
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
const getDataAccessPermissions = useCallback4(() => {
|
|
1163
|
-
if (!isEnabled || !user?.id) return {};
|
|
1164
|
-
return {};
|
|
1165
|
-
}, [isEnabled, user?.id]);
|
|
1166
|
-
const getDataAccessHistory = useCallback4(() => {
|
|
1167
|
-
return [...dataAccessHistory];
|
|
1168
|
-
}, [dataAccessHistory]);
|
|
1169
|
-
const clearDataAccessHistory = useCallback4(() => {
|
|
1170
|
-
setDataAccessHistory([]);
|
|
1171
|
-
}, []);
|
|
1172
|
-
const validateDataAccess = useCallback4((table, operation, scope) => {
|
|
1173
|
-
if (!isEnabled) return true;
|
|
1174
|
-
if (!user?.id) return false;
|
|
1175
|
-
const effectiveScope = scope || currentScope;
|
|
1176
|
-
if (!effectiveScope) return false;
|
|
1177
|
-
try {
|
|
1178
|
-
validateContext();
|
|
1179
|
-
} catch (error) {
|
|
1180
|
-
console.error(`[SecureDataProvider] Organisation context validation failed:`, error);
|
|
1181
|
-
return false;
|
|
1182
|
-
}
|
|
1183
|
-
return isDataAccessAllowed(table, operation, effectiveScope);
|
|
1184
|
-
}, [isEnabled, user?.id, currentScope, validateContext, isDataAccessAllowed]);
|
|
1185
|
-
const recordDataAccess = useCallback4((table, operation, allowed, query, filters, scope) => {
|
|
1186
|
-
if (!auditLog || !user?.id) return;
|
|
1187
|
-
const record = {
|
|
1188
|
-
table,
|
|
1189
|
-
operation,
|
|
1190
|
-
userId: user.id,
|
|
1191
|
-
scope: scope || currentScope || { organisationId: "" },
|
|
1192
|
-
allowed,
|
|
1193
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1194
|
-
query,
|
|
1195
|
-
filters
|
|
1196
|
-
};
|
|
1197
|
-
setDataAccessHistory((prev) => {
|
|
1198
|
-
const newHistory = [record, ...prev];
|
|
1199
|
-
return newHistory.slice(0, maxHistorySize);
|
|
1200
|
-
});
|
|
1201
|
-
if (onDataAccess) {
|
|
1202
|
-
onDataAccess(table, operation, allowed, record);
|
|
1203
|
-
}
|
|
1204
|
-
if (strictMode && !allowed && onStrictModeViolation) {
|
|
1205
|
-
onStrictModeViolation(table, operation, record);
|
|
1206
|
-
}
|
|
1207
|
-
}, [auditLog, user?.id, currentScope, maxHistorySize, onDataAccess, onStrictModeViolation, strictMode]);
|
|
1208
|
-
const contextValue = useMemo4(() => ({
|
|
1209
|
-
isDataAccessAllowed,
|
|
1210
|
-
getDataAccessPermissions,
|
|
1211
|
-
isEnabled,
|
|
1212
|
-
isStrictMode: strictMode,
|
|
1213
|
-
isAuditLogEnabled: auditLog,
|
|
1214
|
-
getDataAccessHistory,
|
|
1215
|
-
clearDataAccessHistory,
|
|
1216
|
-
validateDataAccess
|
|
1217
|
-
}), [
|
|
1218
|
-
isDataAccessAllowed,
|
|
1219
|
-
getDataAccessPermissions,
|
|
1220
|
-
isEnabled,
|
|
1221
|
-
strictMode,
|
|
1222
|
-
auditLog,
|
|
1223
|
-
getDataAccessHistory,
|
|
1224
|
-
clearDataAccessHistory,
|
|
1225
|
-
validateDataAccess
|
|
1226
|
-
]);
|
|
1227
|
-
useEffect4(() => {
|
|
1228
|
-
if (strictMode && auditLog) {
|
|
1229
|
-
console.log(`[SecureDataProvider] Strict mode enabled - all data access attempts will be logged and enforced`);
|
|
1230
|
-
}
|
|
1231
|
-
}, [strictMode, auditLog]);
|
|
1232
|
-
useEffect4(() => {
|
|
1233
|
-
if (enforceRLS && auditLog) {
|
|
1234
|
-
console.log(`[SecureDataProvider] RLS enforcement enabled - all queries will include organisation context`);
|
|
1235
|
-
}
|
|
1236
|
-
}, [enforceRLS, auditLog]);
|
|
1237
|
-
return /* @__PURE__ */ jsx4(SecureDataContext.Provider, { value: contextValue, children });
|
|
1238
|
-
}
|
|
1239
|
-
function useSecureData() {
|
|
1240
|
-
const context = useContext3(SecureDataContext);
|
|
1241
|
-
if (!context) {
|
|
1242
|
-
throw new Error("useSecureData must be used within a SecureDataProvider");
|
|
1243
|
-
}
|
|
1244
|
-
return context;
|
|
1245
|
-
}
|
|
1246
|
-
|
|
1247
|
-
// src/rbac/components/PermissionEnforcer.tsx
|
|
1248
|
-
import { useMemo as useMemo5, useEffect as useEffect5, useState as useState5 } from "react";
|
|
1249
|
-
init_UnifiedAuthProvider();
|
|
1250
|
-
import { Fragment as Fragment3, jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
1251
|
-
function PermissionEnforcer({
|
|
1252
|
-
permissions,
|
|
1253
|
-
operation,
|
|
1254
|
-
children,
|
|
1255
|
-
fallback = /* @__PURE__ */ jsx5(DefaultAccessDenied2, {}),
|
|
1256
|
-
strictMode = true,
|
|
1257
|
-
auditLog = true,
|
|
1258
|
-
scope,
|
|
1259
|
-
onDenied,
|
|
1260
|
-
loading = /* @__PURE__ */ jsx5(DefaultLoading2, {}),
|
|
1261
|
-
requireAll = true
|
|
1262
|
-
}) {
|
|
1263
|
-
const { user, selectedOrganisationId, selectedEventId, supabase } = useUnifiedAuth();
|
|
1264
|
-
const [hasChecked, setHasChecked] = useState5(false);
|
|
1265
|
-
const [checkError, setCheckError] = useState5(null);
|
|
1266
|
-
const [permissionResults, setPermissionResults] = useState5({});
|
|
1267
|
-
const [resolvedScope, setResolvedScope] = useState5(null);
|
|
1268
|
-
useEffect5(() => {
|
|
1269
|
-
const resolveScope = async () => {
|
|
1270
|
-
if (scope) {
|
|
1271
|
-
setResolvedScope(scope);
|
|
1272
|
-
return;
|
|
1273
|
-
}
|
|
1274
|
-
if (selectedOrganisationId && selectedEventId) {
|
|
1275
|
-
setResolvedScope({
|
|
1276
|
-
organisationId: selectedOrganisationId,
|
|
1277
|
-
eventId: selectedEventId,
|
|
1278
|
-
appId: void 0
|
|
1279
|
-
});
|
|
1280
|
-
return;
|
|
1281
|
-
}
|
|
1282
|
-
if (selectedOrganisationId) {
|
|
1283
|
-
setResolvedScope({
|
|
1284
|
-
organisationId: selectedOrganisationId,
|
|
1285
|
-
eventId: selectedEventId || void 0,
|
|
1286
|
-
appId: void 0
|
|
1287
|
-
});
|
|
1288
|
-
return;
|
|
1289
|
-
}
|
|
1290
|
-
if (selectedEventId && supabase) {
|
|
1291
|
-
try {
|
|
1292
|
-
const eventScope = await createScopeFromEvent(supabase, selectedEventId);
|
|
1293
|
-
if (!eventScope) {
|
|
1294
|
-
setCheckError(new Error("Could not resolve organization from event context"));
|
|
1295
|
-
return;
|
|
1296
|
-
}
|
|
1297
|
-
setResolvedScope(eventScope);
|
|
1298
|
-
} catch (error2) {
|
|
1299
|
-
setCheckError(error2);
|
|
1300
|
-
}
|
|
1301
|
-
return;
|
|
1302
|
-
}
|
|
1303
|
-
setCheckError(new Error("Either organisation context or event context is required for permission checking"));
|
|
1304
|
-
};
|
|
1305
|
-
resolveScope();
|
|
1306
|
-
}, [scope, selectedOrganisationId, selectedEventId, supabase]);
|
|
1307
|
-
const representativePermission = permissions[0];
|
|
1308
|
-
const { can, isLoading, error } = useCan(
|
|
1309
|
-
user?.id || "",
|
|
1310
|
-
resolvedScope || { eventId: selectedEventId || void 0 },
|
|
1311
|
-
representativePermission,
|
|
1312
|
-
void 0,
|
|
1313
|
-
true
|
|
1314
|
-
// Use cache
|
|
1315
|
-
);
|
|
1316
|
-
const hasRequiredPermissions = useMemo5(() => {
|
|
1317
|
-
if (permissions.length === 0) return true;
|
|
1318
|
-
return can;
|
|
1319
|
-
}, [permissions, can]);
|
|
1320
|
-
useEffect5(() => {
|
|
1321
|
-
if (!isLoading && !error) {
|
|
1322
|
-
setHasChecked(true);
|
|
1323
|
-
setCheckError(null);
|
|
1324
|
-
if (!hasRequiredPermissions && onDenied) {
|
|
1325
|
-
onDenied(permissions, operation);
|
|
1326
|
-
}
|
|
1327
|
-
} else if (error) {
|
|
1328
|
-
setCheckError(error);
|
|
1329
|
-
setHasChecked(true);
|
|
1330
|
-
}
|
|
1331
|
-
}, [hasRequiredPermissions, isLoading, error, permissions, operation, onDenied]);
|
|
1332
|
-
useEffect5(() => {
|
|
1333
|
-
if (auditLog && hasChecked && !isLoading) {
|
|
1334
|
-
console.log(`[PermissionEnforcer] Permission check attempt:`, {
|
|
1335
|
-
permissions,
|
|
1336
|
-
operation,
|
|
1337
|
-
userId: user?.id,
|
|
1338
|
-
scope: resolvedScope,
|
|
1339
|
-
allowed: hasRequiredPermissions,
|
|
1340
|
-
requireAll,
|
|
1341
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1342
|
-
});
|
|
1343
|
-
}
|
|
1344
|
-
}, [auditLog, hasChecked, isLoading, permissions, operation, user?.id, resolvedScope, hasRequiredPermissions, requireAll]);
|
|
1345
|
-
useEffect5(() => {
|
|
1346
|
-
if (strictMode && hasChecked && !isLoading && !hasRequiredPermissions) {
|
|
1347
|
-
console.error(`[PermissionEnforcer] STRICT MODE VIOLATION: User attempted to perform operation without permission`, {
|
|
1348
|
-
permissions,
|
|
1349
|
-
operation,
|
|
1350
|
-
userId: user?.id,
|
|
1351
|
-
scope: resolvedScope,
|
|
1352
|
-
requireAll,
|
|
1353
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1354
|
-
});
|
|
1355
|
-
}
|
|
1356
|
-
}, [strictMode, hasChecked, isLoading, hasRequiredPermissions, permissions, operation, user?.id, resolvedScope, requireAll]);
|
|
1357
|
-
if (isLoading || !hasChecked) {
|
|
1358
|
-
return /* @__PURE__ */ jsx5(Fragment3, { children: loading });
|
|
1359
|
-
}
|
|
1360
|
-
if (checkError) {
|
|
1361
|
-
console.error(`[PermissionEnforcer] Permission check failed for operation ${operation}:`, checkError);
|
|
1362
|
-
return /* @__PURE__ */ jsx5(Fragment3, { children: fallback });
|
|
1363
|
-
}
|
|
1364
|
-
if (!hasRequiredPermissions) {
|
|
1365
|
-
return /* @__PURE__ */ jsx5(Fragment3, { children: fallback });
|
|
1366
|
-
}
|
|
1367
|
-
return /* @__PURE__ */ jsx5(Fragment3, { children });
|
|
1368
|
-
}
|
|
1369
|
-
function DefaultAccessDenied2() {
|
|
1370
|
-
return /* @__PURE__ */ jsxs3("div", { className: "flex flex-col items-center justify-center min-h-[200px] p-8 text-center", children: [
|
|
1371
|
-
/* @__PURE__ */ jsx5("div", { className: "mb-4", children: /* @__PURE__ */ jsx5("svg", { className: "w-16 h-16 text-acc-500 mx-auto", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx5("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z" }) }) }),
|
|
1372
|
-
/* @__PURE__ */ jsx5("h2", { className: "text-xl font-semibold text-sec-900 mb-2", children: "Access Denied" }),
|
|
1373
|
-
/* @__PURE__ */ jsx5("p", { className: "text-sec-600 mb-4", children: "You don't have permission to perform this operation." }),
|
|
1374
|
-
/* @__PURE__ */ jsx5(
|
|
1375
|
-
"button",
|
|
1376
|
-
{
|
|
1377
|
-
onClick: () => window.history.back(),
|
|
1378
|
-
className: "px-4 py-2 bg-main-600 text-main-50 rounded-md hover:bg-main-700 transition-colors",
|
|
1379
|
-
children: "Go Back"
|
|
1380
|
-
}
|
|
1381
|
-
)
|
|
1382
|
-
] });
|
|
1383
|
-
}
|
|
1384
|
-
function DefaultLoading2() {
|
|
1385
|
-
return /* @__PURE__ */ jsx5("div", { className: "flex items-center justify-center min-h-[200px] p-8", children: /* @__PURE__ */ jsxs3("div", { className: "flex items-center space-x-2", children: [
|
|
1386
|
-
/* @__PURE__ */ jsx5("div", { className: "animate-spin rounded-full h-8 w-8 border-b-2 border-main-600" }),
|
|
1387
|
-
/* @__PURE__ */ jsx5("span", { className: "text-sec-600", children: "Checking permissions..." })
|
|
1388
|
-
] }) });
|
|
1389
|
-
}
|
|
1390
|
-
|
|
1391
|
-
// src/rbac/components/RoleBasedRouter.tsx
|
|
1392
|
-
import { useMemo as useMemo6, useCallback as useCallback6, useEffect as useEffect6, useState as useState6, createContext as createContext3, useContext as useContext4 } from "react";
|
|
1393
|
-
import { useLocation, useNavigate, Outlet } from "react-router-dom";
|
|
1394
|
-
init_UnifiedAuthProvider();
|
|
1395
|
-
import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1396
|
-
var RoleBasedRouterContext = createContext3(null);
|
|
1397
|
-
function RoleBasedRouter({
|
|
1398
|
-
routes,
|
|
1399
|
-
fallbackRoute = "/unauthorized",
|
|
1400
|
-
children,
|
|
1401
|
-
strictMode = true,
|
|
1402
|
-
auditLog = true,
|
|
1403
|
-
onRouteAccess,
|
|
1404
|
-
onStrictModeViolation,
|
|
1405
|
-
maxHistorySize = 1e3,
|
|
1406
|
-
unauthorizedComponent: UnauthorizedComponent = DefaultUnauthorizedComponent
|
|
1407
|
-
}) {
|
|
1408
|
-
const { user, selectedOrganisationId, selectedEventId } = useUnifiedAuth();
|
|
1409
|
-
const location = useLocation();
|
|
1410
|
-
const navigate = useNavigate();
|
|
1411
|
-
const [routeAccessHistory, setRouteAccessHistory] = useState6([]);
|
|
1412
|
-
const [currentRoute, setCurrentRoute] = useState6("");
|
|
1413
|
-
const currentScope = useMemo6(() => {
|
|
1414
|
-
if (!selectedOrganisationId) return null;
|
|
1415
|
-
return {
|
|
1416
|
-
organisationId: selectedOrganisationId,
|
|
1417
|
-
eventId: selectedEventId || void 0,
|
|
1418
|
-
appId: void 0
|
|
1419
|
-
};
|
|
1420
|
-
}, [selectedOrganisationId, selectedEventId]);
|
|
1421
|
-
const currentRouteConfig = useMemo6(() => {
|
|
1422
|
-
const currentPath = location.pathname;
|
|
1423
|
-
return routes.find((route) => route.path === currentPath) || null;
|
|
1424
|
-
}, [routes, location.pathname]);
|
|
1425
|
-
const canAccessRoute = useCallback6((path) => {
|
|
1426
|
-
if (!user?.id || !currentScope) return false;
|
|
1427
|
-
const routeConfig = routes.find((route) => route.path === path);
|
|
1428
|
-
if (!routeConfig) return false;
|
|
832
|
+
const currentRouteConfig = useMemo5(() => {
|
|
833
|
+
const currentPath = location.pathname;
|
|
834
|
+
return routes.find((route) => route.path === currentPath) || null;
|
|
835
|
+
}, [routes, location.pathname]);
|
|
836
|
+
const canAccessRoute = useCallback5((path) => {
|
|
837
|
+
if (!user?.id || !currentScope) return false;
|
|
838
|
+
const routeConfig = routes.find((route) => route.path === path);
|
|
839
|
+
if (!routeConfig) return false;
|
|
1429
840
|
return true;
|
|
1430
841
|
}, [user?.id, currentScope, routes]);
|
|
1431
842
|
const { can: canAccessCurrentRoute, isLoading: permissionLoading } = useCan(
|
|
@@ -1437,20 +848,20 @@ function RoleBasedRouter({
|
|
|
1437
848
|
const hasPermissions = currentRouteConfig?.permissions && currentRouteConfig.permissions.length > 0;
|
|
1438
849
|
const finalCanAccess = hasPermissions ? canAccessCurrentRoute : false;
|
|
1439
850
|
const finalLoading = hasPermissions ? permissionLoading : false;
|
|
1440
|
-
const getAccessibleRoutes =
|
|
851
|
+
const getAccessibleRoutes = useCallback5(() => {
|
|
1441
852
|
if (!user?.id || !currentScope) return [];
|
|
1442
853
|
return routes.filter((route) => canAccessRoute(route.path));
|
|
1443
854
|
}, [user?.id, currentScope, routes, canAccessRoute]);
|
|
1444
|
-
const getRouteConfig =
|
|
855
|
+
const getRouteConfig = useCallback5((path) => {
|
|
1445
856
|
return routes.find((route) => route.path === path) || null;
|
|
1446
857
|
}, [routes]);
|
|
1447
|
-
const getRouteAccessHistory =
|
|
858
|
+
const getRouteAccessHistory = useCallback5(() => {
|
|
1448
859
|
return [...routeAccessHistory];
|
|
1449
860
|
}, [routeAccessHistory]);
|
|
1450
|
-
const clearRouteAccessHistory =
|
|
861
|
+
const clearRouteAccessHistory = useCallback5(() => {
|
|
1451
862
|
setRouteAccessHistory([]);
|
|
1452
863
|
}, []);
|
|
1453
|
-
const recordRouteAccess =
|
|
864
|
+
const recordRouteAccess = useCallback5((route, allowed, routeConfig) => {
|
|
1454
865
|
if (!auditLog || !user?.id || !currentScope) return;
|
|
1455
866
|
const record = {
|
|
1456
867
|
route,
|
|
@@ -1474,7 +885,7 @@ function RoleBasedRouter({
|
|
|
1474
885
|
onStrictModeViolation(route, record);
|
|
1475
886
|
}
|
|
1476
887
|
}, [auditLog, user?.id, currentScope, maxHistorySize, onRouteAccess, onStrictModeViolation, strictMode]);
|
|
1477
|
-
|
|
888
|
+
useEffect5(() => {
|
|
1478
889
|
const currentPath = location.pathname;
|
|
1479
890
|
setCurrentRoute(currentPath);
|
|
1480
891
|
if (!currentRouteConfig) {
|
|
@@ -1503,7 +914,7 @@ function RoleBasedRouter({
|
|
|
1503
914
|
navigate(fallbackRoute, { replace: true });
|
|
1504
915
|
}
|
|
1505
916
|
}, [location.pathname, currentRouteConfig, canAccessCurrentRoute, recordRouteAccess, strictMode, user?.id, currentScope, onStrictModeViolation, navigate, fallbackRoute]);
|
|
1506
|
-
const contextValue =
|
|
917
|
+
const contextValue = useMemo5(() => ({
|
|
1507
918
|
getAccessibleRoutes,
|
|
1508
919
|
canAccessRoute,
|
|
1509
920
|
getRouteConfig,
|
|
@@ -1521,13 +932,13 @@ function RoleBasedRouter({
|
|
|
1521
932
|
auditLog
|
|
1522
933
|
]);
|
|
1523
934
|
if (finalLoading) {
|
|
1524
|
-
return /* @__PURE__ */
|
|
1525
|
-
/* @__PURE__ */
|
|
1526
|
-
/* @__PURE__ */
|
|
935
|
+
return /* @__PURE__ */ jsx5("div", { className: "flex items-center justify-center min-h-screen", children: /* @__PURE__ */ jsxs3("div", { className: "text-center", children: [
|
|
936
|
+
/* @__PURE__ */ jsx5("div", { className: "animate-spin rounded-full h-8 w-8 border-b-2 border-main-600 mx-auto mb-4" }),
|
|
937
|
+
/* @__PURE__ */ jsx5("p", { className: "text-sec-600", children: "Checking permissions..." })
|
|
1527
938
|
] }) });
|
|
1528
939
|
}
|
|
1529
940
|
if (currentRouteConfig && !finalCanAccess) {
|
|
1530
|
-
return /* @__PURE__ */
|
|
941
|
+
return /* @__PURE__ */ jsx5(
|
|
1531
942
|
UnauthorizedComponent,
|
|
1532
943
|
{
|
|
1533
944
|
route: currentRoute,
|
|
@@ -1535,31 +946,31 @@ function RoleBasedRouter({
|
|
|
1535
946
|
}
|
|
1536
947
|
);
|
|
1537
948
|
}
|
|
1538
|
-
return /* @__PURE__ */
|
|
949
|
+
return /* @__PURE__ */ jsxs3(RoleBasedRouterContext.Provider, { value: contextValue, children: [
|
|
1539
950
|
children,
|
|
1540
|
-
/* @__PURE__ */
|
|
951
|
+
/* @__PURE__ */ jsx5(Outlet, {})
|
|
1541
952
|
] });
|
|
1542
953
|
}
|
|
1543
954
|
function useRoleBasedRouter() {
|
|
1544
|
-
const context =
|
|
955
|
+
const context = useContext3(RoleBasedRouterContext);
|
|
1545
956
|
if (!context) {
|
|
1546
957
|
throw new Error("useRoleBasedRouter must be used within a RoleBasedRouter");
|
|
1547
958
|
}
|
|
1548
959
|
return context;
|
|
1549
960
|
}
|
|
1550
961
|
function DefaultUnauthorizedComponent({ route, reason }) {
|
|
1551
|
-
return /* @__PURE__ */
|
|
1552
|
-
/* @__PURE__ */
|
|
1553
|
-
/* @__PURE__ */
|
|
1554
|
-
/* @__PURE__ */
|
|
962
|
+
return /* @__PURE__ */ jsxs3("div", { className: "flex flex-col items-center justify-center min-h-screen p-8 text-center", children: [
|
|
963
|
+
/* @__PURE__ */ jsx5("div", { className: "mb-4", children: /* @__PURE__ */ jsx5("svg", { className: "w-16 h-16 text-acc-500 mx-auto", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx5("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z" }) }) }),
|
|
964
|
+
/* @__PURE__ */ jsx5("h2", { className: "text-xl font-semibold text-sec-900 mb-2", children: "Access Denied" }),
|
|
965
|
+
/* @__PURE__ */ jsxs3("p", { className: "text-sec-600 mb-4", children: [
|
|
1555
966
|
"You don't have permission to access ",
|
|
1556
|
-
/* @__PURE__ */
|
|
967
|
+
/* @__PURE__ */ jsx5("code", { className: "bg-sec-100 px-2 py-1 rounded", children: route })
|
|
1557
968
|
] }),
|
|
1558
|
-
/* @__PURE__ */
|
|
969
|
+
/* @__PURE__ */ jsxs3("p", { className: "text-sm text-sec-500 mb-4", children: [
|
|
1559
970
|
"Reason: ",
|
|
1560
971
|
reason
|
|
1561
972
|
] }),
|
|
1562
|
-
/* @__PURE__ */
|
|
973
|
+
/* @__PURE__ */ jsx5(
|
|
1563
974
|
"button",
|
|
1564
975
|
{
|
|
1565
976
|
onClick: () => window.history.back(),
|
|
@@ -1572,8 +983,8 @@ function DefaultUnauthorizedComponent({ route, reason }) {
|
|
|
1572
983
|
|
|
1573
984
|
// src/rbac/components/NavigationProvider.tsx
|
|
1574
985
|
init_UnifiedAuthProvider();
|
|
1575
|
-
import { createContext as createContext4, useContext as
|
|
1576
|
-
import { jsx as
|
|
986
|
+
import { createContext as createContext4, useContext as useContext4, useState as useState6, useCallback as useCallback6, useMemo as useMemo6, useEffect as useEffect6 } from "react";
|
|
987
|
+
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
1577
988
|
var NavigationContext = createContext4(null);
|
|
1578
989
|
function NavigationProvider({
|
|
1579
990
|
children,
|
|
@@ -1584,9 +995,9 @@ function NavigationProvider({
|
|
|
1584
995
|
maxHistorySize = 1e3
|
|
1585
996
|
}) {
|
|
1586
997
|
const { user, selectedOrganisationId, selectedEventId } = useUnifiedAuth();
|
|
1587
|
-
const [navigationAccessHistory, setNavigationAccessHistory] =
|
|
1588
|
-
const [isEnabled, setIsEnabled] =
|
|
1589
|
-
const currentScope =
|
|
998
|
+
const [navigationAccessHistory, setNavigationAccessHistory] = useState6([]);
|
|
999
|
+
const [isEnabled, setIsEnabled] = useState6(true);
|
|
1000
|
+
const currentScope = useMemo6(() => {
|
|
1590
1001
|
if (!selectedOrganisationId) return null;
|
|
1591
1002
|
return {
|
|
1592
1003
|
organisationId: selectedOrganisationId,
|
|
@@ -1594,27 +1005,27 @@ function NavigationProvider({
|
|
|
1594
1005
|
appId: void 0
|
|
1595
1006
|
};
|
|
1596
1007
|
}, [selectedOrganisationId, selectedEventId]);
|
|
1597
|
-
const hasNavigationPermission =
|
|
1008
|
+
const hasNavigationPermission = useCallback6((item) => {
|
|
1598
1009
|
if (!isEnabled) return true;
|
|
1599
1010
|
if (!user?.id) return false;
|
|
1600
1011
|
if (!currentScope) return false;
|
|
1601
1012
|
return true;
|
|
1602
1013
|
}, [isEnabled, user?.id, currentScope]);
|
|
1603
|
-
const getNavigationPermissions =
|
|
1014
|
+
const getNavigationPermissions = useCallback6(() => {
|
|
1604
1015
|
if (!isEnabled || !user?.id) return {};
|
|
1605
1016
|
return {};
|
|
1606
1017
|
}, [isEnabled, user?.id]);
|
|
1607
|
-
const getFilteredNavigationItems =
|
|
1018
|
+
const getFilteredNavigationItems = useCallback6((items) => {
|
|
1608
1019
|
if (!isEnabled) return items;
|
|
1609
1020
|
return items.filter((item) => hasNavigationPermission(item));
|
|
1610
1021
|
}, [isEnabled, hasNavigationPermission]);
|
|
1611
|
-
const getNavigationAccessHistory =
|
|
1022
|
+
const getNavigationAccessHistory = useCallback6(() => {
|
|
1612
1023
|
return [...navigationAccessHistory];
|
|
1613
1024
|
}, [navigationAccessHistory]);
|
|
1614
|
-
const clearNavigationAccessHistory =
|
|
1025
|
+
const clearNavigationAccessHistory = useCallback6(() => {
|
|
1615
1026
|
setNavigationAccessHistory([]);
|
|
1616
1027
|
}, []);
|
|
1617
|
-
const recordNavigationAccess =
|
|
1028
|
+
const recordNavigationAccess = useCallback6((item, allowed) => {
|
|
1618
1029
|
if (!auditLog || !user?.id || !currentScope) return;
|
|
1619
1030
|
const record = {
|
|
1620
1031
|
navigationItem: item.id,
|
|
@@ -1638,7 +1049,7 @@ function NavigationProvider({
|
|
|
1638
1049
|
onStrictModeViolation(item, record);
|
|
1639
1050
|
}
|
|
1640
1051
|
}, [auditLog, user?.id, currentScope, maxHistorySize, onNavigationAccess, onStrictModeViolation, strictMode]);
|
|
1641
|
-
const contextValue =
|
|
1052
|
+
const contextValue = useMemo6(() => ({
|
|
1642
1053
|
hasNavigationPermission,
|
|
1643
1054
|
getNavigationPermissions,
|
|
1644
1055
|
getFilteredNavigationItems,
|
|
@@ -1657,15 +1068,15 @@ function NavigationProvider({
|
|
|
1657
1068
|
getNavigationAccessHistory,
|
|
1658
1069
|
clearNavigationAccessHistory
|
|
1659
1070
|
]);
|
|
1660
|
-
|
|
1071
|
+
useEffect6(() => {
|
|
1661
1072
|
if (strictMode && auditLog) {
|
|
1662
1073
|
console.log(`[NavigationProvider] Strict mode enabled - all navigation access attempts will be logged and enforced`);
|
|
1663
1074
|
}
|
|
1664
1075
|
}, [strictMode, auditLog]);
|
|
1665
|
-
return /* @__PURE__ */
|
|
1076
|
+
return /* @__PURE__ */ jsx6(NavigationContext.Provider, { value: contextValue, children });
|
|
1666
1077
|
}
|
|
1667
1078
|
function useNavigationPermissions() {
|
|
1668
|
-
const context =
|
|
1079
|
+
const context = useContext4(NavigationContext);
|
|
1669
1080
|
if (!context) {
|
|
1670
1081
|
throw new Error("useNavigationPermissions must be used within a NavigationProvider");
|
|
1671
1082
|
}
|
|
@@ -1673,25 +1084,25 @@ function useNavigationPermissions() {
|
|
|
1673
1084
|
}
|
|
1674
1085
|
|
|
1675
1086
|
// src/rbac/components/NavigationGuard.tsx
|
|
1676
|
-
import { useMemo as
|
|
1087
|
+
import { useMemo as useMemo7, useEffect as useEffect7, useState as useState7 } from "react";
|
|
1677
1088
|
init_UnifiedAuthProvider();
|
|
1678
|
-
import { Fragment as
|
|
1089
|
+
import { Fragment as Fragment3, jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1679
1090
|
function NavigationGuard({
|
|
1680
1091
|
navigationItem,
|
|
1681
1092
|
children,
|
|
1682
|
-
fallback = /* @__PURE__ */
|
|
1093
|
+
fallback = /* @__PURE__ */ jsx7(DefaultAccessDenied3, {}),
|
|
1683
1094
|
strictMode = true,
|
|
1684
1095
|
auditLog = true,
|
|
1685
1096
|
scope,
|
|
1686
1097
|
onDenied,
|
|
1687
|
-
loading = /* @__PURE__ */
|
|
1098
|
+
loading = /* @__PURE__ */ jsx7(DefaultLoading3, {}),
|
|
1688
1099
|
requireAll = true
|
|
1689
1100
|
}) {
|
|
1690
1101
|
const { user, selectedOrganisationId, selectedEventId, supabase } = useUnifiedAuth();
|
|
1691
|
-
const [hasChecked, setHasChecked] =
|
|
1692
|
-
const [checkError, setCheckError] =
|
|
1693
|
-
const [resolvedScope, setResolvedScope] =
|
|
1694
|
-
|
|
1102
|
+
const [hasChecked, setHasChecked] = useState7(false);
|
|
1103
|
+
const [checkError, setCheckError] = useState7(null);
|
|
1104
|
+
const [resolvedScope, setResolvedScope] = useState7(null);
|
|
1105
|
+
useEffect7(() => {
|
|
1695
1106
|
const resolveScope = async () => {
|
|
1696
1107
|
if (scope) {
|
|
1697
1108
|
setResolvedScope(scope);
|
|
@@ -1739,11 +1150,11 @@ function NavigationGuard({
|
|
|
1739
1150
|
true
|
|
1740
1151
|
// Use cache
|
|
1741
1152
|
);
|
|
1742
|
-
const hasRequiredPermissions =
|
|
1153
|
+
const hasRequiredPermissions = useMemo7(() => {
|
|
1743
1154
|
if (navigationItem.permissions.length === 0) return true;
|
|
1744
1155
|
return can;
|
|
1745
1156
|
}, [navigationItem.permissions, can]);
|
|
1746
|
-
|
|
1157
|
+
useEffect7(() => {
|
|
1747
1158
|
if (!isLoading && !error) {
|
|
1748
1159
|
setHasChecked(true);
|
|
1749
1160
|
setCheckError(null);
|
|
@@ -1755,7 +1166,7 @@ function NavigationGuard({
|
|
|
1755
1166
|
setHasChecked(true);
|
|
1756
1167
|
}
|
|
1757
1168
|
}, [hasRequiredPermissions, isLoading, error, navigationItem, onDenied]);
|
|
1758
|
-
|
|
1169
|
+
useEffect7(() => {
|
|
1759
1170
|
if (auditLog && hasChecked && !isLoading) {
|
|
1760
1171
|
console.log(`[NavigationGuard] Navigation access attempt:`, {
|
|
1761
1172
|
navigationItem: navigationItem.id,
|
|
@@ -1768,7 +1179,7 @@ function NavigationGuard({
|
|
|
1768
1179
|
});
|
|
1769
1180
|
}
|
|
1770
1181
|
}, [auditLog, hasChecked, isLoading, navigationItem, user?.id, resolvedScope, hasRequiredPermissions, requireAll]);
|
|
1771
|
-
|
|
1182
|
+
useEffect7(() => {
|
|
1772
1183
|
if (strictMode && hasChecked && !isLoading && !hasRequiredPermissions) {
|
|
1773
1184
|
console.error(`[NavigationGuard] STRICT MODE VIOLATION: User attempted to access protected navigation item without permission`, {
|
|
1774
1185
|
navigationItem: navigationItem.id,
|
|
@@ -1781,34 +1192,34 @@ function NavigationGuard({
|
|
|
1781
1192
|
}
|
|
1782
1193
|
}, [strictMode, hasChecked, isLoading, hasRequiredPermissions, navigationItem, user?.id, resolvedScope, requireAll]);
|
|
1783
1194
|
if (isLoading || !resolvedScope || !hasChecked) {
|
|
1784
|
-
return /* @__PURE__ */
|
|
1195
|
+
return /* @__PURE__ */ jsx7(Fragment3, { children: loading });
|
|
1785
1196
|
}
|
|
1786
1197
|
if (checkError) {
|
|
1787
1198
|
console.error(`[NavigationGuard] Permission check failed for navigation item ${navigationItem.id}:`, checkError);
|
|
1788
|
-
return /* @__PURE__ */
|
|
1199
|
+
return /* @__PURE__ */ jsx7(Fragment3, { children: fallback });
|
|
1789
1200
|
}
|
|
1790
1201
|
if (!hasRequiredPermissions) {
|
|
1791
|
-
return /* @__PURE__ */
|
|
1202
|
+
return /* @__PURE__ */ jsx7(Fragment3, { children: fallback });
|
|
1792
1203
|
}
|
|
1793
|
-
return /* @__PURE__ */
|
|
1204
|
+
return /* @__PURE__ */ jsx7(Fragment3, { children });
|
|
1794
1205
|
}
|
|
1795
1206
|
function DefaultAccessDenied3() {
|
|
1796
|
-
return /* @__PURE__ */
|
|
1797
|
-
/* @__PURE__ */
|
|
1798
|
-
/* @__PURE__ */
|
|
1207
|
+
return /* @__PURE__ */ jsx7("div", { className: "flex items-center justify-center p-2 text-center", children: /* @__PURE__ */ jsxs4("div", { className: "flex items-center space-x-2", children: [
|
|
1208
|
+
/* @__PURE__ */ jsx7("svg", { className: "w-4 h-4 text-acc-500", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx7("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z" }) }),
|
|
1209
|
+
/* @__PURE__ */ jsx7("span", { className: "text-sm text-sec-600", children: "Access Denied" })
|
|
1799
1210
|
] }) });
|
|
1800
1211
|
}
|
|
1801
1212
|
function DefaultLoading3() {
|
|
1802
|
-
return /* @__PURE__ */
|
|
1803
|
-
/* @__PURE__ */
|
|
1804
|
-
/* @__PURE__ */
|
|
1213
|
+
return /* @__PURE__ */ jsx7("div", { className: "flex items-center justify-center p-2", children: /* @__PURE__ */ jsxs4("div", { className: "flex items-center space-x-2", children: [
|
|
1214
|
+
/* @__PURE__ */ jsx7("div", { className: "animate-spin rounded-full h-4 w-4 border-b-2 border-main-600" }),
|
|
1215
|
+
/* @__PURE__ */ jsx7("span", { className: "text-sm text-sec-600", children: "Checking..." })
|
|
1805
1216
|
] }) });
|
|
1806
1217
|
}
|
|
1807
1218
|
var NavigationGuard_default = NavigationGuard;
|
|
1808
1219
|
|
|
1809
1220
|
// src/rbac/components/EnhancedNavigationMenu.tsx
|
|
1810
|
-
import { useMemo as
|
|
1811
|
-
import { jsx as
|
|
1221
|
+
import { useMemo as useMemo8, useCallback as useCallback8, useEffect as useEffect8, useState as useState8 } from "react";
|
|
1222
|
+
import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1812
1223
|
function EnhancedNavigationMenu({
|
|
1813
1224
|
items,
|
|
1814
1225
|
strictMode = true,
|
|
@@ -1831,12 +1242,12 @@ function EnhancedNavigationMenu({
|
|
|
1831
1242
|
isStrictMode,
|
|
1832
1243
|
isAuditLogEnabled
|
|
1833
1244
|
} = useNavigationPermissions();
|
|
1834
|
-
const [navigationHistory, setNavigationHistory] =
|
|
1835
|
-
const filteredItems =
|
|
1245
|
+
const [navigationHistory, setNavigationHistory] = useState8([]);
|
|
1246
|
+
const filteredItems = useMemo8(() => {
|
|
1836
1247
|
if (!isEnabled) return items;
|
|
1837
1248
|
return getFilteredNavigationItems(items);
|
|
1838
1249
|
}, [isEnabled, items, getFilteredNavigationItems]);
|
|
1839
|
-
const handleItemClick =
|
|
1250
|
+
const handleItemClick = useCallback8((item) => {
|
|
1840
1251
|
if (onItemClick) {
|
|
1841
1252
|
onItemClick(item);
|
|
1842
1253
|
}
|
|
@@ -1853,7 +1264,7 @@ function EnhancedNavigationMenu({
|
|
|
1853
1264
|
return newHistory.slice(0, 10);
|
|
1854
1265
|
});
|
|
1855
1266
|
}, [onItemClick, auditLog]);
|
|
1856
|
-
const handleNavigationAccess =
|
|
1267
|
+
const handleNavigationAccess = useCallback8((item, allowed) => {
|
|
1857
1268
|
if (onNavigationAccess) {
|
|
1858
1269
|
onNavigationAccess(item, allowed);
|
|
1859
1270
|
}
|
|
@@ -1866,7 +1277,7 @@ function EnhancedNavigationMenu({
|
|
|
1866
1277
|
});
|
|
1867
1278
|
}
|
|
1868
1279
|
}, [onNavigationAccess, auditLog, strictMode]);
|
|
1869
|
-
const handleStrictModeViolation =
|
|
1280
|
+
const handleStrictModeViolation = useCallback8((item) => {
|
|
1870
1281
|
if (onStrictModeViolation) {
|
|
1871
1282
|
onStrictModeViolation(item);
|
|
1872
1283
|
}
|
|
@@ -1879,31 +1290,31 @@ function EnhancedNavigationMenu({
|
|
|
1879
1290
|
});
|
|
1880
1291
|
}
|
|
1881
1292
|
}, [onStrictModeViolation, strictMode]);
|
|
1882
|
-
const defaultRenderItem =
|
|
1293
|
+
const defaultRenderItem = useCallback8((item, isAuthorized) => {
|
|
1883
1294
|
const isActive = activePath === item.path;
|
|
1884
1295
|
const isDisabled = !isAuthorized;
|
|
1885
|
-
return /* @__PURE__ */
|
|
1296
|
+
return /* @__PURE__ */ jsx8(
|
|
1886
1297
|
NavigationGuard_default,
|
|
1887
1298
|
{
|
|
1888
1299
|
navigationItem: item,
|
|
1889
1300
|
strictMode,
|
|
1890
1301
|
auditLog,
|
|
1891
1302
|
onDenied: handleStrictModeViolation,
|
|
1892
|
-
fallback: hideUnauthorizedItems ? null : /* @__PURE__ */
|
|
1893
|
-
item.meta?.icon && /* @__PURE__ */
|
|
1894
|
-
/* @__PURE__ */
|
|
1895
|
-
/* @__PURE__ */
|
|
1303
|
+
fallback: hideUnauthorizedItems ? null : /* @__PURE__ */ jsx8("div", { className: `${itemClassName} ${disabledItemClassName}`, children: /* @__PURE__ */ jsxs5("div", { className: "flex items-center space-x-2", children: [
|
|
1304
|
+
item.meta?.icon && /* @__PURE__ */ jsx8("span", { className: "text-sm", children: item.meta.icon }),
|
|
1305
|
+
/* @__PURE__ */ jsx8("span", { children: item.label }),
|
|
1306
|
+
/* @__PURE__ */ jsx8("span", { className: "text-xs text-sec-400", children: "(Access Denied)" })
|
|
1896
1307
|
] }) }),
|
|
1897
|
-
children: /* @__PURE__ */
|
|
1308
|
+
children: /* @__PURE__ */ jsx8(
|
|
1898
1309
|
"button",
|
|
1899
1310
|
{
|
|
1900
1311
|
onClick: () => handleItemClick(item),
|
|
1901
1312
|
className: `${itemClassName} ${isActive ? activeItemClassName : ""} ${isDisabled ? disabledItemClassName : "hover:bg-sec-100"}`,
|
|
1902
1313
|
disabled: isDisabled,
|
|
1903
|
-
children: /* @__PURE__ */
|
|
1904
|
-
item.meta?.icon && /* @__PURE__ */
|
|
1905
|
-
/* @__PURE__ */
|
|
1906
|
-
item.meta?.description && /* @__PURE__ */
|
|
1314
|
+
children: /* @__PURE__ */ jsxs5("div", { className: "flex items-center space-x-2", children: [
|
|
1315
|
+
item.meta?.icon && /* @__PURE__ */ jsx8("span", { className: "text-sm", children: item.meta.icon }),
|
|
1316
|
+
/* @__PURE__ */ jsx8("span", { children: item.label }),
|
|
1317
|
+
item.meta?.description && /* @__PURE__ */ jsx8("span", { className: "text-xs text-sec-500 ml-auto", children: item.meta.description })
|
|
1907
1318
|
] })
|
|
1908
1319
|
}
|
|
1909
1320
|
)
|
|
@@ -1921,12 +1332,12 @@ function EnhancedNavigationMenu({
|
|
|
1921
1332
|
handleStrictModeViolation,
|
|
1922
1333
|
handleItemClick
|
|
1923
1334
|
]);
|
|
1924
|
-
|
|
1335
|
+
useEffect8(() => {
|
|
1925
1336
|
if (strictMode && auditLog) {
|
|
1926
1337
|
console.log(`[EnhancedNavigationMenu] Strict mode enabled - all navigation access attempts will be logged and enforced`);
|
|
1927
1338
|
}
|
|
1928
1339
|
}, [strictMode, auditLog]);
|
|
1929
|
-
|
|
1340
|
+
useEffect8(() => {
|
|
1930
1341
|
if (auditLog) {
|
|
1931
1342
|
console.log(`[EnhancedNavigationMenu] Navigation menu initialized:`, {
|
|
1932
1343
|
totalItems: items.length,
|
|
@@ -1936,7 +1347,7 @@ function EnhancedNavigationMenu({
|
|
|
1936
1347
|
});
|
|
1937
1348
|
}
|
|
1938
1349
|
}, [items.length, filteredItems.length, strictMode, auditLog]);
|
|
1939
|
-
return /* @__PURE__ */
|
|
1350
|
+
return /* @__PURE__ */ jsx8("nav", { className, children: filteredItems.map((item) => {
|
|
1940
1351
|
const isAuthorized = hasNavigationPermission(item);
|
|
1941
1352
|
if (renderItem) {
|
|
1942
1353
|
return renderItem(item, isAuthorized);
|
|
@@ -1945,6 +1356,353 @@ function EnhancedNavigationMenu({
|
|
|
1945
1356
|
}) });
|
|
1946
1357
|
}
|
|
1947
1358
|
|
|
1359
|
+
// src/rbac/adapters.tsx
|
|
1360
|
+
import React9, { useContext as useContext5 } from "react";
|
|
1361
|
+
import { Fragment as Fragment4, jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1362
|
+
function PermissionGuard({
|
|
1363
|
+
userId,
|
|
1364
|
+
scope,
|
|
1365
|
+
permission,
|
|
1366
|
+
pageId,
|
|
1367
|
+
children,
|
|
1368
|
+
fallback = null,
|
|
1369
|
+
onDenied,
|
|
1370
|
+
loading = null,
|
|
1371
|
+
// NEW: Phase 1 - Enhanced Security Features
|
|
1372
|
+
strictMode = true,
|
|
1373
|
+
auditLog = true,
|
|
1374
|
+
enforceAudit = true
|
|
1375
|
+
}) {
|
|
1376
|
+
const logger = getRBACLogger();
|
|
1377
|
+
const authContext = useContext5(React9.createContext(null));
|
|
1378
|
+
let effectiveUserId = userId;
|
|
1379
|
+
if (!effectiveUserId) {
|
|
1380
|
+
try {
|
|
1381
|
+
if (authContext?.user?.id) {
|
|
1382
|
+
effectiveUserId = authContext.user.id;
|
|
1383
|
+
} else {
|
|
1384
|
+
const globalUser = window.__PACE_USER__;
|
|
1385
|
+
if (globalUser?.id) {
|
|
1386
|
+
effectiveUserId = globalUser.id;
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
} catch (error2) {
|
|
1390
|
+
logger.debug("Could not infer userId from context:", error2);
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
const { can, isLoading, error } = useCan(effectiveUserId || "", scope, permission, pageId);
|
|
1394
|
+
if (!effectiveUserId) {
|
|
1395
|
+
logger.error("PermissionGuard: No userId provided and could not infer from context");
|
|
1396
|
+
return /* @__PURE__ */ jsxs6("div", { className: "rbac-error", role: "alert", children: [
|
|
1397
|
+
/* @__PURE__ */ jsx9("p", { children: "Permission check failed: User context not available" }),
|
|
1398
|
+
/* @__PURE__ */ jsxs6("details", { children: [
|
|
1399
|
+
/* @__PURE__ */ jsx9("summary", { children: "Debug info" }),
|
|
1400
|
+
/* @__PURE__ */ jsx9("p", { children: "Make sure to either:" }),
|
|
1401
|
+
/* @__PURE__ */ jsxs6("ul", { children: [
|
|
1402
|
+
/* @__PURE__ */ jsx9("li", { children: "Pass userId prop explicitly" }),
|
|
1403
|
+
/* @__PURE__ */ jsx9("li", { children: "Wrap your app with an auth provider" }),
|
|
1404
|
+
/* @__PURE__ */ jsx9("li", { children: "Set window.__PACE_USER__ with user data" })
|
|
1405
|
+
] })
|
|
1406
|
+
] })
|
|
1407
|
+
] });
|
|
1408
|
+
}
|
|
1409
|
+
if (isLoading) {
|
|
1410
|
+
return loading || /* @__PURE__ */ jsx9("div", { className: "rbac-loading", role: "status", "aria-live": "polite", children: /* @__PURE__ */ jsx9("span", { className: "sr-only", children: "Checking permissions..." }) });
|
|
1411
|
+
}
|
|
1412
|
+
if (error) {
|
|
1413
|
+
logger.error("Permission check failed:", error);
|
|
1414
|
+
if (auditLog) {
|
|
1415
|
+
logger.info(`[PermissionGuard] Permission check failed:`, {
|
|
1416
|
+
userId: effectiveUserId,
|
|
1417
|
+
scope,
|
|
1418
|
+
permission,
|
|
1419
|
+
pageId,
|
|
1420
|
+
error: error.message,
|
|
1421
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1422
|
+
});
|
|
1423
|
+
}
|
|
1424
|
+
return fallback;
|
|
1425
|
+
}
|
|
1426
|
+
if (!can) {
|
|
1427
|
+
if (auditLog) {
|
|
1428
|
+
logger.info(`[PermissionGuard] Permission denied:`, {
|
|
1429
|
+
userId: effectiveUserId,
|
|
1430
|
+
scope,
|
|
1431
|
+
permission,
|
|
1432
|
+
pageId,
|
|
1433
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1434
|
+
});
|
|
1435
|
+
}
|
|
1436
|
+
if (strictMode) {
|
|
1437
|
+
logger.error(`[PermissionGuard] STRICT MODE VIOLATION: User attempted to access protected resource without permission`, {
|
|
1438
|
+
userId: effectiveUserId,
|
|
1439
|
+
scope,
|
|
1440
|
+
permission,
|
|
1441
|
+
pageId,
|
|
1442
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1443
|
+
});
|
|
1444
|
+
}
|
|
1445
|
+
if (onDenied) {
|
|
1446
|
+
onDenied();
|
|
1447
|
+
}
|
|
1448
|
+
return /* @__PURE__ */ jsx9(Fragment4, { children: fallback });
|
|
1449
|
+
}
|
|
1450
|
+
if (auditLog) {
|
|
1451
|
+
logger.info(`[PermissionGuard] Permission granted:`, {
|
|
1452
|
+
userId: effectiveUserId,
|
|
1453
|
+
scope,
|
|
1454
|
+
permission,
|
|
1455
|
+
pageId,
|
|
1456
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1457
|
+
});
|
|
1458
|
+
}
|
|
1459
|
+
return /* @__PURE__ */ jsx9(Fragment4, { children });
|
|
1460
|
+
}
|
|
1461
|
+
function AccessLevelGuard({
|
|
1462
|
+
userId,
|
|
1463
|
+
scope,
|
|
1464
|
+
minLevel,
|
|
1465
|
+
children,
|
|
1466
|
+
fallback = null,
|
|
1467
|
+
loading = null
|
|
1468
|
+
}) {
|
|
1469
|
+
const logger = getRBACLogger();
|
|
1470
|
+
const authContext = useContext5(React9.createContext(null));
|
|
1471
|
+
let effectiveUserId = userId;
|
|
1472
|
+
if (!effectiveUserId) {
|
|
1473
|
+
try {
|
|
1474
|
+
if (authContext?.user?.id) {
|
|
1475
|
+
effectiveUserId = authContext.user.id;
|
|
1476
|
+
} else {
|
|
1477
|
+
const globalUser = window.__PACE_USER__;
|
|
1478
|
+
if (globalUser?.id) {
|
|
1479
|
+
effectiveUserId = globalUser.id;
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
} catch (error2) {
|
|
1483
|
+
logger.debug("Could not infer userId from context:", error2);
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
const { accessLevel, isLoading, error } = useAccessLevel(effectiveUserId || "", scope);
|
|
1487
|
+
if (!effectiveUserId) {
|
|
1488
|
+
logger.error("AccessLevelGuard: No userId provided and could not infer from context");
|
|
1489
|
+
return /* @__PURE__ */ jsxs6("div", { className: "rbac-error", role: "alert", children: [
|
|
1490
|
+
/* @__PURE__ */ jsx9("p", { children: "Access level check failed: User context not available" }),
|
|
1491
|
+
/* @__PURE__ */ jsxs6("details", { children: [
|
|
1492
|
+
/* @__PURE__ */ jsx9("summary", { children: "Debug info" }),
|
|
1493
|
+
/* @__PURE__ */ jsx9("p", { children: "Make sure to either:" }),
|
|
1494
|
+
/* @__PURE__ */ jsxs6("ul", { children: [
|
|
1495
|
+
/* @__PURE__ */ jsx9("li", { children: "Pass userId prop explicitly" }),
|
|
1496
|
+
/* @__PURE__ */ jsx9("li", { children: "Wrap your app with an auth provider" }),
|
|
1497
|
+
/* @__PURE__ */ jsx9("li", { children: "Set window.__PACE_USER__ with user data" })
|
|
1498
|
+
] })
|
|
1499
|
+
] })
|
|
1500
|
+
] });
|
|
1501
|
+
}
|
|
1502
|
+
if (isLoading) {
|
|
1503
|
+
return loading || /* @__PURE__ */ jsx9("div", { className: "rbac-loading", role: "status", "aria-live": "polite", children: /* @__PURE__ */ jsx9("span", { className: "sr-only", children: "Checking access level..." }) });
|
|
1504
|
+
}
|
|
1505
|
+
if (error) {
|
|
1506
|
+
logger.error("Access level check failed:", error);
|
|
1507
|
+
return fallback;
|
|
1508
|
+
}
|
|
1509
|
+
const levelHierarchy = ["viewer", "participant", "planner", "admin", "super"];
|
|
1510
|
+
const userLevelIndex = accessLevel ? levelHierarchy.indexOf(accessLevel) : -1;
|
|
1511
|
+
const requiredLevelIndex = levelHierarchy.indexOf(minLevel);
|
|
1512
|
+
if (userLevelIndex < requiredLevelIndex) {
|
|
1513
|
+
return /* @__PURE__ */ jsx9(Fragment4, { children: fallback });
|
|
1514
|
+
}
|
|
1515
|
+
return /* @__PURE__ */ jsx9(Fragment4, { children });
|
|
1516
|
+
}
|
|
1517
|
+
function withPermissionGuard(config, handler) {
|
|
1518
|
+
return async (...args) => {
|
|
1519
|
+
const [req] = args;
|
|
1520
|
+
const userId = req.user?.id;
|
|
1521
|
+
const organisationId = req.organisationId;
|
|
1522
|
+
const eventId = req.eventId;
|
|
1523
|
+
const appId = req.appId;
|
|
1524
|
+
if (!userId || !organisationId) {
|
|
1525
|
+
throw new Error("User context required for permission check");
|
|
1526
|
+
}
|
|
1527
|
+
const { isPermitted: isPermitted2 } = await import("../api-ETQ6YJ3C.js");
|
|
1528
|
+
const hasPermission2 = await isPermitted2({
|
|
1529
|
+
userId,
|
|
1530
|
+
scope: { organisationId, eventId, appId },
|
|
1531
|
+
permission: config.permission,
|
|
1532
|
+
pageId: config.pageId
|
|
1533
|
+
});
|
|
1534
|
+
if (!hasPermission2) {
|
|
1535
|
+
throw new Error(`Permission denied: ${config.permission}`);
|
|
1536
|
+
}
|
|
1537
|
+
return handler(...args);
|
|
1538
|
+
};
|
|
1539
|
+
}
|
|
1540
|
+
function withAccessLevelGuard(minLevel, handler) {
|
|
1541
|
+
return async (...args) => {
|
|
1542
|
+
const [req] = args;
|
|
1543
|
+
const userId = req.user?.id;
|
|
1544
|
+
const organisationId = req.organisationId;
|
|
1545
|
+
const eventId = req.eventId;
|
|
1546
|
+
const appId = req.appId;
|
|
1547
|
+
if (!userId || !organisationId) {
|
|
1548
|
+
throw new Error("User context required for access level check");
|
|
1549
|
+
}
|
|
1550
|
+
const { getAccessLevel: getAccessLevel2 } = await import("../api-ETQ6YJ3C.js");
|
|
1551
|
+
const accessLevel = await getAccessLevel2({
|
|
1552
|
+
userId,
|
|
1553
|
+
scope: { organisationId, eventId, appId }
|
|
1554
|
+
});
|
|
1555
|
+
const levelHierarchy = ["viewer", "participant", "planner", "admin", "super"];
|
|
1556
|
+
const userLevelIndex = levelHierarchy.indexOf(accessLevel);
|
|
1557
|
+
const requiredLevelIndex = levelHierarchy.indexOf(minLevel);
|
|
1558
|
+
if (userLevelIndex < requiredLevelIndex) {
|
|
1559
|
+
throw new Error(`Access level required: ${minLevel}, got: ${accessLevel}`);
|
|
1560
|
+
}
|
|
1561
|
+
return handler(...args);
|
|
1562
|
+
};
|
|
1563
|
+
}
|
|
1564
|
+
function withRoleGuard(config, handler) {
|
|
1565
|
+
return async (...args) => {
|
|
1566
|
+
const [req] = args;
|
|
1567
|
+
const userId = req.user?.id;
|
|
1568
|
+
const organisationId = req.organisationId;
|
|
1569
|
+
const eventId = req.eventId;
|
|
1570
|
+
const appId = req.appId;
|
|
1571
|
+
if (!userId || !organisationId) {
|
|
1572
|
+
throw new Error("User context required for role check");
|
|
1573
|
+
}
|
|
1574
|
+
if (config.globalRoles && config.globalRoles.length > 0) {
|
|
1575
|
+
const { isSuperAdmin } = await import("../api-ETQ6YJ3C.js");
|
|
1576
|
+
const isSuper = await isSuperAdmin(userId);
|
|
1577
|
+
if (isSuper) {
|
|
1578
|
+
if (organisationId) {
|
|
1579
|
+
const { emitAuditEvent: emitAuditEvent2 } = await import("../audit-BUW3LMJB.js");
|
|
1580
|
+
await emitAuditEvent2({
|
|
1581
|
+
type: "permission_check",
|
|
1582
|
+
userId,
|
|
1583
|
+
organisationId,
|
|
1584
|
+
eventId,
|
|
1585
|
+
appId,
|
|
1586
|
+
permission: "bypass:all",
|
|
1587
|
+
decision: true,
|
|
1588
|
+
source: "api",
|
|
1589
|
+
bypass: true,
|
|
1590
|
+
duration_ms: 0,
|
|
1591
|
+
metadata: {
|
|
1592
|
+
operation: "role_guard",
|
|
1593
|
+
reason: "super_admin_bypass"
|
|
1594
|
+
}
|
|
1595
|
+
});
|
|
1596
|
+
}
|
|
1597
|
+
return handler(...args);
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
if (config.organisationRoles && config.organisationRoles.length > 0) {
|
|
1601
|
+
const { isOrganisationAdmin } = await import("../api-ETQ6YJ3C.js");
|
|
1602
|
+
const isOrgAdmin = await isOrganisationAdmin(userId, organisationId);
|
|
1603
|
+
if (!isOrgAdmin && config.requireAll !== false) {
|
|
1604
|
+
throw new Error(`Organisation admin role required`);
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
if (config.eventAppRoles && config.eventAppRoles.length > 0 && eventId && appId) {
|
|
1608
|
+
const { isEventAdmin } = await import("../api-ETQ6YJ3C.js");
|
|
1609
|
+
const isEventAdminUser = await isEventAdmin(userId, { organisationId, eventId, appId });
|
|
1610
|
+
if (!isEventAdminUser && config.requireAll !== false) {
|
|
1611
|
+
throw new Error(`Event admin role required`);
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
if (organisationId) {
|
|
1615
|
+
const { emitAuditEvent: emitAuditEvent2 } = await import("../audit-BUW3LMJB.js");
|
|
1616
|
+
await emitAuditEvent2({
|
|
1617
|
+
type: "permission_check",
|
|
1618
|
+
userId,
|
|
1619
|
+
organisationId,
|
|
1620
|
+
eventId,
|
|
1621
|
+
appId,
|
|
1622
|
+
permission: "role:check",
|
|
1623
|
+
decision: true,
|
|
1624
|
+
source: "api",
|
|
1625
|
+
bypass: false,
|
|
1626
|
+
duration_ms: 0,
|
|
1627
|
+
metadata: {
|
|
1628
|
+
operation: "role_guard"
|
|
1629
|
+
}
|
|
1630
|
+
});
|
|
1631
|
+
}
|
|
1632
|
+
return handler(...args);
|
|
1633
|
+
};
|
|
1634
|
+
}
|
|
1635
|
+
function createRBACMiddleware(config) {
|
|
1636
|
+
return async (req, res, next) => {
|
|
1637
|
+
const { pathname } = req.nextUrl;
|
|
1638
|
+
const userId = req.user?.id;
|
|
1639
|
+
const organisationId = req.organisationId;
|
|
1640
|
+
if (!userId || !organisationId) {
|
|
1641
|
+
return res.redirect(config.fallbackUrl || "/login");
|
|
1642
|
+
}
|
|
1643
|
+
const protectedRoute = config.protectedRoutes.find(
|
|
1644
|
+
(route) => pathname.startsWith(route.path)
|
|
1645
|
+
);
|
|
1646
|
+
if (protectedRoute) {
|
|
1647
|
+
try {
|
|
1648
|
+
const { isPermitted: isPermitted2 } = await import("../api-ETQ6YJ3C.js");
|
|
1649
|
+
const hasPermission2 = await isPermitted2({
|
|
1650
|
+
userId,
|
|
1651
|
+
scope: { organisationId },
|
|
1652
|
+
permission: protectedRoute.permission,
|
|
1653
|
+
pageId: protectedRoute.pageId
|
|
1654
|
+
});
|
|
1655
|
+
if (!hasPermission2) {
|
|
1656
|
+
return res.redirect(config.fallbackUrl || "/access-denied");
|
|
1657
|
+
}
|
|
1658
|
+
} catch (_error) {
|
|
1659
|
+
return res.redirect(config.fallbackUrl || "/access-denied");
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
next();
|
|
1663
|
+
};
|
|
1664
|
+
}
|
|
1665
|
+
function createRBACExpressMiddleware(config) {
|
|
1666
|
+
return async (req, res, next) => {
|
|
1667
|
+
const userId = req.user?.id;
|
|
1668
|
+
const organisationId = req.organisationId;
|
|
1669
|
+
const eventId = req.eventId;
|
|
1670
|
+
const appId = req.appId;
|
|
1671
|
+
if (!userId || !organisationId) {
|
|
1672
|
+
return res.status(401).json({ error: "User context required" });
|
|
1673
|
+
}
|
|
1674
|
+
try {
|
|
1675
|
+
const { isPermitted: isPermitted2 } = await import("../api-ETQ6YJ3C.js");
|
|
1676
|
+
const hasPermission2 = await isPermitted2({
|
|
1677
|
+
userId,
|
|
1678
|
+
scope: { organisationId, eventId, appId },
|
|
1679
|
+
permission: config.permission,
|
|
1680
|
+
pageId: config.pageId
|
|
1681
|
+
});
|
|
1682
|
+
if (!hasPermission2) {
|
|
1683
|
+
return res.status(403).json({ error: "Permission denied" });
|
|
1684
|
+
}
|
|
1685
|
+
next();
|
|
1686
|
+
} catch (_error) {
|
|
1687
|
+
return res.status(500).json({ error: "Permission check failed" });
|
|
1688
|
+
}
|
|
1689
|
+
};
|
|
1690
|
+
}
|
|
1691
|
+
function hasPermissionCached(userId, scope, _permission, _pageId) {
|
|
1692
|
+
const cacheKey = RBACCache.generatePermissionKey({
|
|
1693
|
+
userId,
|
|
1694
|
+
organisationId: scope.organisationId,
|
|
1695
|
+
eventId: scope.eventId,
|
|
1696
|
+
appId: scope.appId
|
|
1697
|
+
});
|
|
1698
|
+
return rbacCache.get(cacheKey) || false;
|
|
1699
|
+
}
|
|
1700
|
+
function hasAnyPermissionCached(userId, scope, permissions, pageId) {
|
|
1701
|
+
return permissions.some(
|
|
1702
|
+
(permission) => hasPermissionCached(userId, scope, permission, pageId)
|
|
1703
|
+
);
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1948
1706
|
// src/rbac/permissions.ts
|
|
1949
1707
|
var GLOBAL_PERMISSIONS = {
|
|
1950
1708
|
MANAGE_ALL: "manage:*",
|