@jmruthers/pace-core 0.6.5 → 0.6.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +104 -0
- package/README.md +5 -403
- package/audit-tool/00-dependencies.cjs +394 -0
- package/audit-tool/audits/01-pace-core-compliance.cjs +556 -0
- package/audit-tool/audits/02-project-structure.cjs +255 -0
- package/audit-tool/audits/03-architecture.cjs +196 -0
- package/audit-tool/audits/04-code-quality.cjs +149 -0
- package/audit-tool/audits/05-styling.cjs +224 -0
- package/audit-tool/audits/06-security-rbac.cjs +544 -0
- package/audit-tool/audits/07-api-tech-stack.cjs +301 -0
- package/audit-tool/audits/08-testing-documentation.cjs +202 -0
- package/audit-tool/audits/09-operations.cjs +208 -0
- package/audit-tool/index.cjs +291 -0
- package/audit-tool/utils/code-utils.cjs +218 -0
- package/audit-tool/utils/file-utils.cjs +230 -0
- package/audit-tool/utils/report-utils.cjs +241 -0
- package/core-usage-manifest.json +93 -0
- package/cursor-rules/00-standards-overview.mdc +156 -0
- package/cursor-rules/01-pace-core-compliance.mdc +586 -0
- package/cursor-rules/02-project-structure.mdc +42 -4
- package/cursor-rules/{03-solid-principles.mdc → 03-architecture.mdc} +126 -10
- package/cursor-rules/04-code-quality.mdc +419 -0
- package/cursor-rules/{08-markup-quality.mdc → 05-styling.mdc} +104 -34
- package/cursor-rules/06-security-rbac.mdc +518 -0
- package/cursor-rules/07-api-tech-stack.mdc +377 -0
- package/cursor-rules/08-testing-documentation.mdc +324 -0
- package/cursor-rules/09-operations.mdc +365 -0
- package/dist/{AuthService-Cb34EQs3.d.ts → AuthService-DmfO5rGS.d.ts} +10 -0
- package/dist/DataTable-7PMH7XN7.js +15 -0
- package/dist/{DataTable-BMRU8a1j.d.ts → DataTable-DRUIgtUH.d.ts} +1 -1
- package/dist/{PublicPageProvider-QTFVrL-Z.d.ts → PublicPageProvider-DlsCaR5v.d.ts} +33 -72
- package/dist/UnifiedAuthProvider-ZT6TIGM7.js +7 -0
- package/dist/api-Y4MQWOFW.js +4 -0
- package/dist/audit-MYQXYZFU.js +3 -0
- package/dist/{chunk-DGUM43GV.js → chunk-3RG5ZIWI.js} +1 -4
- package/dist/{chunk-QXHPKYJV.js → chunk-4SXLQIZO.js} +1 -26
- package/dist/{chunk-UPPMRMYG.js → chunk-5X4QLXRG.js} +73 -151
- package/dist/chunk-6F3IILHI.js +62 -0
- package/dist/{chunk-E66EQZE6.js → chunk-6GLLNA6U.js} +3 -9
- package/dist/{chunk-ZSAAAMVR.js → chunk-6QYDGKQY.js} +1 -4
- package/dist/{chunk-FMUCXFII.js → chunk-7ILTDCL2.js} +9 -5
- package/dist/{chunk-M43Y4SSO.js → chunk-A3W6LW53.js} +15 -13
- package/dist/{chunk-63FOKYGO.js → chunk-AHU7G2R5.js} +2 -11
- package/dist/{chunk-HU2C6SSC.js → chunk-BM4CQ5P3.js} +606 -559
- package/dist/chunk-C7NSAPTL.js +1 -0
- package/dist/{chunk-J36DSWQK.js → chunk-FEJLJNWA.js} +7 -41
- package/dist/{chunk-IHB5DR3H.js → chunk-FTCRZOG2.js} +188 -387
- package/dist/{chunk-G37KK66H.js → chunk-FYHN4DD5.js} +60 -19
- package/dist/chunk-GHYHJTYV.js +994 -0
- package/dist/{chunk-VBXEHIUJ.js → chunk-HF6O3O37.js} +6 -88
- package/dist/{chunk-FFQEQTNW.js → chunk-IUBRCBSY.js} +134 -45
- package/dist/{chunk-6COVEUS7.js → chunk-JGWDVX64.js} +983 -1034
- package/dist/{chunk-RGAWHO7N.js → chunk-L4XMVJKY.js} +77 -222
- package/dist/chunk-MBADTM7L.js +64 -0
- package/dist/{chunk-M7MPQISP.js → chunk-OJ4SKRSV.js} +3 -16
- package/dist/{chunk-IVOFDYWT.js → chunk-Q7Q7V5NV.js} +2109 -1604
- package/dist/{chunk-JGRYX5UX.js → chunk-S7DKJPLT.js} +29 -58
- package/dist/{chunk-PWLANIRT.js → chunk-TTRFSOKR.js} +1 -7
- package/dist/{chunk-5DRSZLL2.js → chunk-UH3NTO3F.js} +1 -6
- package/dist/{chunk-NTM7ZSB6.js → chunk-VBCS3DUA.js} +261 -168
- package/dist/{chunk-EFN2EIMK.js → chunk-ZFYPMX46.js} +271 -87
- package/dist/{chunk-L4OXEN46.js → chunk-ZKAWKYT4.js} +10 -24
- package/dist/components.d.ts +7 -5
- package/dist/components.js +46 -257
- package/dist/{database.generated-CzIvgcPu.d.ts → database.generated-CcnC_DRc.d.ts} +4795 -3691
- package/dist/eslint-rules/index.cjs +35 -0
- package/{src/eslint-rules/pace-core-compliance.cjs → dist/eslint-rules/rules/01-pace-core-compliance.cjs} +234 -235
- package/dist/eslint-rules/rules/04-code-quality.cjs +290 -0
- package/dist/eslint-rules/rules/05-styling.cjs +61 -0
- package/dist/eslint-rules/rules/06-security-rbac.cjs +806 -0
- package/dist/eslint-rules/rules/07-api-tech-stack.cjs +263 -0
- package/dist/eslint-rules/rules/08-testing.cjs +94 -0
- package/dist/eslint-rules/utils/helpers.cjs +42 -0
- package/dist/eslint-rules/utils/manifest-loader.cjs +75 -0
- package/dist/hooks.d.ts +6 -6
- package/dist/hooks.js +62 -172
- package/dist/icons/index.d.ts +1 -0
- package/dist/icons/index.js +1 -0
- package/dist/index.d.ts +12 -11
- package/dist/index.js +67 -660
- package/dist/providers.d.ts +2 -2
- package/dist/providers.js +8 -35
- package/dist/rbac/eslint-rules.d.ts +46 -44
- package/dist/rbac/eslint-rules.js +7 -4
- package/dist/rbac/index.d.ts +109 -586
- package/dist/rbac/index.js +14 -207
- package/dist/styles/index.js +2 -12
- package/dist/theming/runtime.d.ts +14 -1
- package/dist/theming/runtime.js +3 -19
- package/dist/{timezone-CHhWg6b4.d.ts → timezone-BZe_eUxx.d.ts} +175 -1
- package/dist/{types-CkbwOr4Y.d.ts → types-DXstZpNI.d.ts} +4 -17
- package/dist/types-t9H8qKRw.d.ts +55 -0
- package/dist/types.d.ts +1 -1
- package/dist/types.js +7 -94
- package/dist/{usePublicRouteParams-ClnV4tnv.d.ts → usePublicRouteParams-MamNgwqe.d.ts} +20 -20
- package/dist/utils.d.ts +24 -117
- package/dist/utils.js +54 -392
- package/docs/README.md +17 -7
- package/docs/api/README.md +4 -402
- package/docs/api/modules.md +301 -871
- package/docs/api-reference/components.md +21 -21
- package/docs/api-reference/deprecated.md +31 -6
- package/docs/api-reference/hooks.md +80 -80
- package/docs/api-reference/rpc-functions.md +78 -3
- package/docs/api-reference/types.md +1 -1
- package/docs/api-reference/utilities.md +1 -1
- package/docs/architecture/README.md +1 -1
- package/docs/core-concepts/events.md +3 -3
- package/docs/core-concepts/organisations.md +6 -6
- package/docs/core-concepts/permissions.md +6 -6
- package/docs/documentation-index.md +12 -18
- package/docs/getting-started/cursor-rules.md +3 -23
- package/docs/getting-started/dependencies.md +650 -0
- package/docs/getting-started/documentation-index.md +1 -1
- package/docs/getting-started/examples/README.md +4 -4
- package/docs/getting-started/examples/full-featured-app.md +1 -1
- package/docs/getting-started/faq.md +2 -2
- package/docs/getting-started/installation-guide.md +20 -7
- package/docs/getting-started/quick-reference.md +4 -4
- package/docs/getting-started/quick-start.md +23 -12
- package/docs/implementation-guides/authentication.md +15 -15
- package/docs/implementation-guides/component-styling.md +1 -1
- package/docs/implementation-guides/data-tables.md +126 -33
- package/docs/implementation-guides/datatable-rbac-usage.md +1 -1
- package/docs/implementation-guides/dynamic-colors.md +3 -3
- package/docs/implementation-guides/file-upload-storage.md +2 -2
- package/docs/implementation-guides/hierarchical-datatable.md +40 -60
- package/docs/implementation-guides/inactivity-tracking.md +3 -3
- package/docs/implementation-guides/large-datasets.md +3 -2
- package/docs/implementation-guides/organisation-security.md +2 -2
- package/docs/implementation-guides/performance.md +2 -2
- package/docs/implementation-guides/permission-enforcement.md +5 -1
- package/docs/migration/V0.3.44_organisation-context-timing-fix.md +1 -1
- package/docs/migration/V0.4.0_rbac-migration.md +6 -6
- package/docs/rbac/MIGRATION_GUIDE.md +819 -0
- package/docs/rbac/RBAC_CONTRACT.md +724 -0
- package/docs/rbac/README.md +17 -8
- package/docs/rbac/advanced-patterns.md +6 -6
- package/docs/rbac/api-reference.md +20 -20
- package/docs/rbac/edge-functions-guide.md +376 -0
- package/docs/rbac/event-based-apps.md +3 -3
- package/docs/rbac/examples.md +41 -41
- package/docs/rbac/getting-started.md +37 -37
- package/docs/rbac/performance.md +1 -1
- package/docs/rbac/quick-start.md +52 -52
- package/docs/rbac/secure-client-protection.md +1 -35
- package/docs/rbac/troubleshooting.md +1 -1
- package/docs/security/README.md +5 -5
- package/docs/standards/0-standards-overview.md +220 -0
- package/docs/standards/1-pace-core-compliance-standards.md +986 -0
- package/docs/standards/2-project-structure-standards.md +949 -0
- package/docs/standards/3-architecture-standards.md +606 -0
- package/docs/standards/4-code-quality-standards.md +728 -0
- package/docs/standards/5-styling-standards.md +348 -0
- package/docs/standards/{07-rbac-and-rls-standard.md → 6-security-rbac-standards.md} +269 -66
- package/docs/standards/7-api-tech-stack-standards.md +662 -0
- package/docs/standards/8-testing-documentation-standards.md +401 -0
- package/docs/standards/9-operations-standards.md +1102 -0
- package/docs/standards/README.md +185 -57
- package/docs/troubleshooting/README.md +4 -4
- package/docs/troubleshooting/common-issues.md +2 -2
- package/docs/troubleshooting/debugging.md +9 -9
- package/docs/troubleshooting/migration.md +4 -4
- package/docs/troubleshooting/organisation-context-setup.md +42 -19
- package/eslint-config-pace-core.cjs +33 -6
- package/package.json +35 -23
- package/scripts/install-cursor-rules.cjs +25 -6
- package/scripts/install-eslint-config.cjs +284 -0
- package/src/__tests__/fixtures/supabase.ts +1 -1
- package/src/__tests__/helpers/__tests__/component-test-utils.test.tsx +3 -3
- package/src/__tests__/helpers/__tests__/optimized-test-setup.test.ts +1 -1
- package/src/__tests__/helpers/__tests__/supabaseMock.test.ts +1 -1
- package/src/__tests__/helpers/__tests__/test-providers.test.tsx +2 -2
- package/src/__tests__/helpers/__tests__/test-utils.test.tsx +13 -13
- package/src/__tests__/helpers/component-test-utils.tsx +1 -1
- package/src/__tests__/helpers/supabaseMock.ts +2 -2
- package/src/__tests__/integration/UserProfile.test.tsx +14 -14
- package/src/__tests__/public-recipe-view.test.ts +38 -9
- package/src/__tests__/rbac/PagePermissionGuard.test.tsx +6 -6
- package/src/__tests__/templates/accessibility.test.template.tsx +9 -9
- package/src/__tests__/templates/component.test.template.tsx +18 -15
- package/src/components/Button/Button.tsx +5 -1
- package/src/components/Calendar/Calendar.tsx +201 -47
- package/src/components/ContextSelector/ContextSelector.tsx +106 -119
- package/src/components/DataTable/AUDIT_REPORT.md +293 -0
- package/src/components/DataTable/__tests__/DataTableCore.test.tsx +10 -2
- package/src/components/DataTable/__tests__/a11y.basic.test.tsx +10 -4
- package/src/components/DataTable/__tests__/test-utils/sharedTestUtils.tsx +9 -9
- package/src/components/DataTable/components/ColumnFilter.tsx +63 -74
- package/src/components/DataTable/components/ColumnVisibilityDropdown.tsx +43 -41
- package/src/components/DataTable/components/DataTableCore.tsx +186 -13
- package/src/components/DataTable/components/DataTableErrorBoundary.tsx +9 -11
- package/src/components/DataTable/components/DataTableLayout.tsx +35 -21
- package/src/components/DataTable/components/EditFields.tsx +23 -3
- package/src/components/DataTable/components/EditableRow.tsx +12 -9
- package/src/components/DataTable/components/EmptyState.tsx +10 -9
- package/src/components/DataTable/components/FilterRow.tsx +2 -4
- package/src/components/DataTable/components/ImportModal.tsx +124 -126
- package/src/components/DataTable/components/LoadingState.tsx +5 -6
- package/src/components/DataTable/components/RowComponent.tsx +12 -0
- package/src/components/DataTable/components/SortIndicator.tsx +50 -0
- package/src/components/DataTable/components/__tests__/COVERAGE_NOTE.md +4 -4
- package/src/components/DataTable/components/__tests__/ColumnFilter.test.tsx +23 -82
- package/src/components/DataTable/components/__tests__/DataTableErrorBoundary.test.tsx +37 -9
- package/src/components/DataTable/components/__tests__/EmptyState.test.tsx +7 -4
- package/src/components/DataTable/components/__tests__/FilterRow.test.tsx +12 -4
- package/src/components/DataTable/components/__tests__/LoadingState.test.tsx +41 -27
- package/src/components/DataTable/components/hooks/usePermissionTracking.ts +0 -4
- package/src/components/DataTable/components/index.ts +2 -1
- package/src/components/DataTable/hooks/__tests__/useDataTableState.test.ts +51 -47
- package/src/components/DataTable/hooks/useDataTablePermissions.ts +24 -21
- package/src/components/DataTable/hooks/useDataTableState.ts +125 -9
- package/src/components/DataTable/hooks/useTableColumns.ts +40 -2
- package/src/components/DataTable/hooks/useTableHandlers.ts +11 -0
- package/src/components/DataTable/types.ts +5 -18
- package/src/components/DataTable/utils/a11yUtils.ts +17 -0
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +2 -1
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.tsx +11 -15
- package/src/components/DateTimeField/DateTimeField.tsx +10 -9
- package/src/components/Dialog/Dialog.test.tsx +128 -104
- package/src/components/Dialog/Dialog.tsx +742 -24
- package/src/components/ErrorBoundary/ErrorBoundary.tsx +77 -79
- package/src/components/FileDisplay/FileDisplay.test.tsx +4 -2
- package/src/components/FileDisplay/FileDisplay.tsx +23 -17
- package/src/components/FileUpload/FileUpload.test.tsx +52 -14
- package/src/components/FileUpload/FileUpload.tsx +112 -130
- package/src/components/Form/Form.test.tsx +6 -8
- package/src/components/Form/Form.tsx +365 -4
- package/src/components/NavigationMenu/NavigationMenu.test.tsx +14 -13
- package/src/components/NavigationMenu/useNavigationFiltering.ts +11 -21
- package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +6 -4
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +11 -15
- package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +108 -61
- package/src/components/PaceLoginPage/PaceLoginPage.tsx +27 -3
- package/src/components/Progress/Progress.tsx +2 -4
- package/src/components/ProtectedRoute/ProtectedRoute.tsx +8 -8
- package/src/components/Select/Select.tsx +109 -98
- package/src/components/Select/types.ts +4 -1
- package/src/components/UserMenu/UserMenu.tsx +9 -6
- package/src/hooks/__tests__/ServiceHooks.test.tsx +16 -16
- package/src/hooks/__tests__/hooks.integration.test.tsx +55 -57
- package/src/hooks/__tests__/useAppConfig.unit.test.ts +129 -67
- package/src/hooks/__tests__/useFocusTrap.unit.test.tsx +97 -97
- package/src/hooks/__tests__/usePublicEvent.simple.test.ts +149 -67
- package/src/hooks/__tests__/usePublicEvent.test.ts +149 -79
- package/src/hooks/__tests__/usePublicEvent.unit.test.ts +158 -109
- package/src/hooks/__tests__/useSessionDraft.test.ts +163 -0
- package/src/hooks/__tests__/useSessionRestoration.unit.test.tsx +10 -5
- package/src/hooks/public/usePublicEvent.ts +67 -195
- package/src/hooks/public/usePublicEventLogo.test.ts +70 -17
- package/src/hooks/public/usePublicEventLogo.ts +24 -14
- package/src/hooks/public/usePublicFileDisplay.ts +2 -2
- package/src/hooks/public/usePublicRouteParams.ts +5 -5
- package/src/hooks/useAppConfig.ts +28 -26
- package/src/hooks/useEventTheme.test.ts +217 -239
- package/src/hooks/useEventTheme.ts +16 -28
- package/src/hooks/useFileDisplay.ts +2 -2
- package/src/hooks/useOrganisationPermissions.ts +5 -7
- package/src/hooks/useQueryCache.ts +0 -1
- package/src/hooks/useSessionDraft.ts +380 -0
- package/src/hooks/useSessionRestoration.ts +3 -1
- package/src/icons/index.ts +27 -0
- package/src/index.ts +5 -0
- package/src/providers/OrganisationProvider.tsx +23 -14
- package/src/providers/UnifiedAuthProvider.smoke.test.tsx +21 -21
- package/src/providers/__tests__/AuthProvider.test.tsx +21 -21
- package/src/providers/__tests__/EventProvider.test.tsx +61 -61
- package/src/providers/__tests__/InactivityProvider.test.tsx +56 -56
- package/src/providers/__tests__/OrganisationProvider.test.tsx +75 -75
- package/src/providers/__tests__/ProviderLifecycle.test.tsx +37 -37
- package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +103 -103
- package/src/providers/services/EventServiceProvider.tsx +1 -24
- package/src/providers/services/UnifiedAuthProvider.tsx +5 -48
- package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +7 -7
- package/src/providers/services/__tests__/UnifiedAuthProvider.integration.test.tsx +13 -10
- package/src/rbac/__tests__/adapters.comprehensive.test.tsx +7 -457
- package/src/rbac/__tests__/auth-rbac.e2e.test.tsx +33 -7
- package/src/rbac/adapters.tsx +7 -295
- package/src/rbac/api.test.ts +44 -56
- package/src/rbac/api.ts +10 -17
- package/src/rbac/cache-invalidation.ts +0 -1
- package/src/rbac/compliance/index.ts +10 -0
- package/src/rbac/compliance/pattern-detector.ts +553 -0
- package/src/rbac/compliance/runtime-compliance.ts +22 -0
- package/src/rbac/components/AccessDenied.tsx +150 -0
- package/src/rbac/components/NavigationGuard.tsx +12 -20
- package/src/rbac/components/PagePermissionGuard.tsx +4 -24
- package/src/rbac/components/__tests__/NavigationGuard.test.tsx +21 -8
- package/src/rbac/components/index.ts +3 -41
- package/src/rbac/eslint-rules.js +1 -1
- package/src/rbac/hooks/index.ts +0 -3
- package/src/rbac/hooks/permissions/index.ts +0 -3
- package/src/rbac/hooks/permissions/useAccessLevel.ts +4 -8
- package/src/rbac/hooks/usePermissions.ts +0 -3
- package/src/rbac/hooks/useResolvedScope.test.ts +57 -47
- package/src/rbac/hooks/useResolvedScope.ts +58 -140
- package/src/rbac/hooks/useResourcePermissions.test.ts +124 -38
- package/src/rbac/hooks/useResourcePermissions.ts +139 -48
- package/src/rbac/hooks/useRoleManagement.test.ts +65 -22
- package/src/rbac/hooks/useRoleManagement.ts +147 -19
- package/src/rbac/hooks/useSecureSupabase.ts +4 -8
- package/src/rbac/index.ts +7 -9
- package/src/rbac/utils/contextValidator.ts +9 -7
- package/src/services/AuthService.ts +130 -18
- package/src/services/EventService.ts +4 -97
- package/src/services/InactivityService.ts +16 -0
- package/src/services/OrganisationService.ts +7 -44
- package/src/services/__tests__/OrganisationService.test.ts +26 -8
- package/src/services/base/BaseService.ts +0 -3
- package/src/styles/core.css +7 -0
- package/src/theming/__tests__/parseEventColours.test.ts +9 -3
- package/src/theming/parseEventColours.ts +22 -10
- package/src/types/database.generated.ts +4733 -3809
- package/src/utils/__tests__/lazyLoad.unit.test.tsx +42 -39
- package/src/utils/__tests__/organisationContext.unit.test.ts +9 -10
- package/src/utils/context/organisationContext.test.ts +13 -28
- package/src/utils/context/organisationContext.ts +21 -52
- package/src/utils/dynamic/dynamicUtils.ts +1 -1
- package/src/utils/file-reference/index.ts +39 -15
- package/src/utils/formatting/formatDateTime.test.ts +3 -2
- package/src/utils/google-places/loadGoogleMapsScript.ts +29 -4
- package/src/utils/index.ts +4 -1
- package/src/utils/persistence/__tests__/keyDerivation.test.ts +135 -0
- package/src/utils/persistence/__tests__/sensitiveFieldDetection.test.ts +123 -0
- package/src/utils/persistence/keyDerivation.ts +304 -0
- package/src/utils/persistence/sensitiveFieldDetection.ts +212 -0
- package/src/utils/security/secureStorage.ts +5 -5
- package/src/utils/storage/README.md +1 -1
- package/src/utils/storage/helpers.ts +3 -3
- package/src/utils/supabase/createBaseClient.ts +147 -0
- package/src/utils/timezone/timezone.test.ts +1 -2
- package/src/utils/timezone/timezone.ts +1 -1
- package/src/utils/validation/csrf.ts +4 -4
- package/cursor-rules/00-pace-core-compliance.mdc +0 -331
- package/cursor-rules/01-standards-compliance.mdc +0 -244
- package/cursor-rules/04-testing-standards.mdc +0 -268
- package/cursor-rules/05-bug-reports-and-features.mdc +0 -246
- package/cursor-rules/06-code-quality.mdc +0 -309
- package/cursor-rules/07-tech-stack-compliance.mdc +0 -214
- package/cursor-rules/CHANGELOG.md +0 -119
- package/cursor-rules/README.md +0 -192
- package/dist/DataTable-AOVNCPTX.js +0 -175
- package/dist/DataTable-AOVNCPTX.js.map +0 -1
- package/dist/UnifiedAuthProvider-4SBX4LU5.js +0 -18
- package/dist/UnifiedAuthProvider-4SBX4LU5.js.map +0 -1
- package/dist/api-O6HTBX5Y.js +0 -52
- package/dist/api-O6HTBX5Y.js.map +0 -1
- package/dist/audit-V53FV5AG.js +0 -17
- package/dist/audit-V53FV5AG.js.map +0 -1
- package/dist/chunk-5DRSZLL2.js.map +0 -1
- package/dist/chunk-63FOKYGO.js.map +0 -1
- package/dist/chunk-6COVEUS7.js.map +0 -1
- package/dist/chunk-AFVQODI2.js +0 -263
- package/dist/chunk-AFVQODI2.js.map +0 -1
- package/dist/chunk-DGUM43GV.js.map +0 -1
- package/dist/chunk-E66EQZE6.js.map +0 -1
- package/dist/chunk-EFN2EIMK.js.map +0 -1
- package/dist/chunk-FFQEQTNW.js.map +0 -1
- package/dist/chunk-FMUCXFII.js.map +0 -1
- package/dist/chunk-G37KK66H.js.map +0 -1
- package/dist/chunk-G7QEZTYQ.js +0 -2053
- package/dist/chunk-G7QEZTYQ.js.map +0 -1
- package/dist/chunk-HU2C6SSC.js.map +0 -1
- package/dist/chunk-IHB5DR3H.js.map +0 -1
- package/dist/chunk-IVOFDYWT.js.map +0 -1
- package/dist/chunk-J36DSWQK.js.map +0 -1
- package/dist/chunk-JGRYX5UX.js.map +0 -1
- package/dist/chunk-KQCRWDSA.js +0 -1
- package/dist/chunk-KQCRWDSA.js.map +0 -1
- package/dist/chunk-L4OXEN46.js.map +0 -1
- package/dist/chunk-LMC26NLJ.js +0 -84
- package/dist/chunk-LMC26NLJ.js.map +0 -1
- package/dist/chunk-M43Y4SSO.js.map +0 -1
- package/dist/chunk-M7MPQISP.js.map +0 -1
- package/dist/chunk-NTM7ZSB6.js.map +0 -1
- package/dist/chunk-PWLANIRT.js.map +0 -1
- package/dist/chunk-QXHPKYJV.js.map +0 -1
- package/dist/chunk-RGAWHO7N.js.map +0 -1
- package/dist/chunk-UPPMRMYG.js.map +0 -1
- package/dist/chunk-VBXEHIUJ.js.map +0 -1
- package/dist/chunk-ZSAAAMVR.js.map +0 -1
- package/dist/components.js.map +0 -1
- package/dist/contextValidator-5OGXSPKS.js +0 -9
- package/dist/contextValidator-5OGXSPKS.js.map +0 -1
- package/dist/eslint-rules/pace-core-compliance.cjs +0 -510
- package/dist/hooks.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/providers.js.map +0 -1
- package/dist/rbac/eslint-rules.js.map +0 -1
- package/dist/rbac/index.js.map +0 -1
- package/dist/styles/index.js.map +0 -1
- package/dist/theming/runtime.js.map +0 -1
- package/dist/types.js.map +0 -1
- package/dist/utils.js.map +0 -1
- package/docs/best-practices/README.md +0 -472
- package/docs/best-practices/accessibility.md +0 -601
- package/docs/best-practices/common-patterns.md +0 -516
- package/docs/best-practices/deployment.md +0 -1103
- package/docs/best-practices/performance.md +0 -1328
- package/docs/best-practices/security.md +0 -940
- package/docs/best-practices/testing.md +0 -1034
- package/docs/rbac/compliance/compliance-guide.md +0 -544
- package/docs/standards/01-architecture-standard.md +0 -44
- package/docs/standards/02-api-and-rpc-standard.md +0 -39
- package/docs/standards/03-component-standard.md +0 -32
- package/docs/standards/04-code-style-standard.md +0 -32
- package/docs/standards/05-security-standard.md +0 -44
- package/docs/standards/06-testing-and-docs-standard.md +0 -29
- package/docs/standards/pace-core-compliance.md +0 -432
- package/scripts/audit/core/checks/accessibility.cjs +0 -197
- package/scripts/audit/core/checks/api-usage.cjs +0 -191
- package/scripts/audit/core/checks/bundle.cjs +0 -142
- package/scripts/audit/core/checks/compliance.cjs +0 -2706
- package/scripts/audit/core/checks/config.cjs +0 -54
- package/scripts/audit/core/checks/coverage.cjs +0 -84
- package/scripts/audit/core/checks/dependencies.cjs +0 -994
- package/scripts/audit/core/checks/documentation.cjs +0 -268
- package/scripts/audit/core/checks/environment.cjs +0 -116
- package/scripts/audit/core/checks/error-handling.cjs +0 -340
- package/scripts/audit/core/checks/forms.cjs +0 -172
- package/scripts/audit/core/checks/heuristics.cjs +0 -68
- package/scripts/audit/core/checks/hooks.cjs +0 -334
- package/scripts/audit/core/checks/imports.cjs +0 -244
- package/scripts/audit/core/checks/performance.cjs +0 -325
- package/scripts/audit/core/checks/routes.cjs +0 -117
- package/scripts/audit/core/checks/state.cjs +0 -130
- package/scripts/audit/core/checks/structure.cjs +0 -65
- package/scripts/audit/core/checks/style.cjs +0 -584
- package/scripts/audit/core/checks/testing.cjs +0 -122
- package/scripts/audit/core/checks/typescript.cjs +0 -61
- package/scripts/audit/core/scanner.cjs +0 -199
- package/scripts/audit/core/utils.cjs +0 -137
- package/scripts/audit/index.cjs +0 -223
- package/scripts/audit/reporters/console.cjs +0 -151
- package/scripts/audit/reporters/json.cjs +0 -54
- package/scripts/audit/reporters/markdown.cjs +0 -124
- package/scripts/audit-consuming-app.cjs +0 -86
- package/src/components/DataTable/components/DataTableBody.tsx +0 -454
- package/src/components/DataTable/components/DraggableColumnHeader.tsx +0 -156
- package/src/components/DataTable/components/ExpandButton.tsx +0 -113
- package/src/components/DataTable/components/GroupHeader.tsx +0 -54
- package/src/components/DataTable/components/ViewRowModal.tsx +0 -68
- package/src/components/DataTable/components/VirtualizedDataTable.tsx +0 -525
- package/src/components/DataTable/components/__tests__/ExpandButton.test.tsx +0 -462
- package/src/components/DataTable/components/__tests__/GroupHeader.test.tsx +0 -393
- package/src/components/DataTable/components/__tests__/ViewRowModal.test.tsx +0 -476
- package/src/components/DataTable/components/__tests__/VirtualizedDataTable.test.tsx +0 -128
- package/src/components/DataTable/core/DataTableContext.tsx +0 -216
- package/src/components/DataTable/core/__tests__/DataTableContext.test.tsx +0 -136
- package/src/components/DataTable/hooks/__tests__/useColumnReordering.test.ts +0 -570
- package/src/components/DataTable/hooks/useColumnReordering.ts +0 -123
- package/src/components/DataTable/utils/debugTools.ts +0 -514
- package/src/eslint-rules/pace-core-compliance.js +0 -638
- package/src/rbac/components/EnhancedNavigationMenu.test.tsx +0 -555
- package/src/rbac/components/EnhancedNavigationMenu.tsx +0 -293
- package/src/rbac/components/NavigationProvider.test.tsx +0 -481
- package/src/rbac/components/NavigationProvider.tsx +0 -345
- package/src/rbac/components/PagePermissionProvider.test.tsx +0 -476
- package/src/rbac/components/PagePermissionProvider.tsx +0 -279
- package/src/rbac/components/PermissionEnforcer.tsx +0 -312
- package/src/rbac/components/RoleBasedRouter.tsx +0 -440
- package/src/rbac/components/SecureDataProvider.test.tsx +0 -543
- package/src/rbac/components/SecureDataProvider.tsx +0 -339
- package/src/rbac/components/__tests__/EnhancedNavigationMenu.test.tsx +0 -620
- package/src/rbac/components/__tests__/NavigationProvider.test.tsx +0 -726
- package/src/rbac/components/__tests__/PagePermissionProvider.test.tsx +0 -661
- package/src/rbac/components/__tests__/PermissionEnforcer.test.tsx +0 -881
- package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +0 -783
- package/src/rbac/components/__tests__/SecureDataProvider.fixed.test.tsx +0 -645
- package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +0 -659
- package/src/rbac/hooks/permissions/useCachedPermissions.ts +0 -79
- package/src/rbac/hooks/permissions/useHasAllPermissions.ts +0 -90
- package/src/rbac/hooks/permissions/useHasAnyPermission.ts +0 -90
package/dist/chunk-G7QEZTYQ.js
DELETED
|
@@ -1,2053 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
scopeEqual,
|
|
3
|
-
useAccessLevel,
|
|
4
|
-
useCan,
|
|
5
|
-
useMultiplePermissions,
|
|
6
|
-
useResolvedScope,
|
|
7
|
-
useSecureSupabase
|
|
8
|
-
} from "./chunk-HU2C6SSC.js";
|
|
9
|
-
import {
|
|
10
|
-
useOrganisationSecurity
|
|
11
|
-
} from "./chunk-NTM7ZSB6.js";
|
|
12
|
-
import {
|
|
13
|
-
useUnifiedAuth
|
|
14
|
-
} from "./chunk-IHB5DR3H.js";
|
|
15
|
-
import {
|
|
16
|
-
RBACCache,
|
|
17
|
-
getRBACConfig,
|
|
18
|
-
getRBACLogger,
|
|
19
|
-
rbacCache
|
|
20
|
-
} from "./chunk-EFN2EIMK.js";
|
|
21
|
-
import {
|
|
22
|
-
RBACNotInitializedError
|
|
23
|
-
} from "./chunk-AFVQODI2.js";
|
|
24
|
-
import {
|
|
25
|
-
createLogger,
|
|
26
|
-
logger
|
|
27
|
-
} from "./chunk-PWLANIRT.js";
|
|
28
|
-
|
|
29
|
-
// src/rbac/types/functions.ts
|
|
30
|
-
var RPCFunction = /* @__PURE__ */ ((RPCFunction2) => {
|
|
31
|
-
RPCFunction2["RBAC_PERMISSION_CHECK"] = "rbac_permission_check";
|
|
32
|
-
RPCFunction2["RBAC_PERMISSIONS_GET"] = "rbac_permissions_get";
|
|
33
|
-
RPCFunction2["RBAC_ACCESS_VALIDATE"] = "rbac_access_validate";
|
|
34
|
-
RPCFunction2["RBAC_PAGE_ACCESS_CHECK"] = "rbac_page_access_check";
|
|
35
|
-
RPCFunction2["RBAC_ROLE_GRANT"] = "rbac_role_grant";
|
|
36
|
-
RPCFunction2["RBAC_ROLE_REVOKE"] = "rbac_role_revoke";
|
|
37
|
-
RPCFunction2["RBAC_ROLES_LIST"] = "rbac_roles_list";
|
|
38
|
-
RPCFunction2["RBAC_ROLE_VALIDATE"] = "rbac_role_validate";
|
|
39
|
-
RPCFunction2["RBAC_SESSION_TRACK"] = "rbac_session_track";
|
|
40
|
-
RPCFunction2["RBAC_AUDIT_LOG"] = "rbac_audit_log";
|
|
41
|
-
return RPCFunction2;
|
|
42
|
-
})(RPCFunction || {});
|
|
43
|
-
var RBACErrorCode = /* @__PURE__ */ ((RBACErrorCode2) => {
|
|
44
|
-
RBACErrorCode2["USER_NOT_FOUND"] = "USER_NOT_FOUND";
|
|
45
|
-
RBACErrorCode2["INVALID_ROLE_TYPE"] = "INVALID_ROLE_TYPE";
|
|
46
|
-
RBACErrorCode2["INVALID_ROLE_NAME"] = "INVALID_ROLE_NAME";
|
|
47
|
-
RBACErrorCode2["MISSING_ORGANISATION_ID"] = "MISSING_ORGANISATION_ID";
|
|
48
|
-
RBACErrorCode2["MISSING_EVENT_APP_CONTEXT"] = "MISSING_EVENT_APP_CONTEXT";
|
|
49
|
-
RBACErrorCode2["ORGANISATION_NOT_FOUND"] = "ORGANISATION_NOT_FOUND";
|
|
50
|
-
RBACErrorCode2["EVENT_NOT_FOUND"] = "EVENT_NOT_FOUND";
|
|
51
|
-
RBACErrorCode2["APP_NOT_FOUND"] = "APP_NOT_FOUND";
|
|
52
|
-
RBACErrorCode2["INVALID_SESSION_TYPE"] = "INVALID_SESSION_TYPE";
|
|
53
|
-
RBACErrorCode2["INVALID_EVENT_TYPE"] = "INVALID_EVENT_TYPE";
|
|
54
|
-
RBACErrorCode2["DATABASE_ERROR"] = "DATABASE_ERROR";
|
|
55
|
-
RBACErrorCode2["ROLE_NOT_FOUND"] = "ROLE_NOT_FOUND";
|
|
56
|
-
RBACErrorCode2["USER_NOT_AUTHENTICATED"] = "USER_NOT_AUTHENTICATED";
|
|
57
|
-
RBACErrorCode2["INVALID_GLOBAL_ROLE"] = "INVALID_GLOBAL_ROLE";
|
|
58
|
-
RBACErrorCode2["INVALID_ORGANISATION_ROLE"] = "INVALID_ORGANISATION_ROLE";
|
|
59
|
-
RBACErrorCode2["INVALID_EVENT_APP_ROLE"] = "INVALID_EVENT_APP_ROLE";
|
|
60
|
-
RBACErrorCode2["INVALID_EVENT_APP_FORMAT"] = "INVALID_EVENT_APP_FORMAT";
|
|
61
|
-
RBACErrorCode2["MISSING_CONTEXT"] = "MISSING_CONTEXT";
|
|
62
|
-
RBACErrorCode2["INVALID_CONTEXT"] = "INVALID_CONTEXT";
|
|
63
|
-
RBACErrorCode2["GRANTED_BY_NOT_FOUND"] = "GRANTED_BY_NOT_FOUND";
|
|
64
|
-
return RBACErrorCode2;
|
|
65
|
-
})(RBACErrorCode || {});
|
|
66
|
-
|
|
67
|
-
// src/rbac/components/PagePermissionProvider.tsx
|
|
68
|
-
import { createContext, useContext, useState, useCallback, useMemo, useEffect } from "react";
|
|
69
|
-
import { jsx } from "react/jsx-runtime";
|
|
70
|
-
var log = createLogger("PagePermissionProvider");
|
|
71
|
-
var PagePermissionContext = createContext(null);
|
|
72
|
-
function PagePermissionProvider({
|
|
73
|
-
children,
|
|
74
|
-
strictMode = true,
|
|
75
|
-
auditLog = true,
|
|
76
|
-
onPageAccess,
|
|
77
|
-
onStrictModeViolation,
|
|
78
|
-
maxHistorySize = 1e3
|
|
79
|
-
}) {
|
|
80
|
-
const { user, selectedOrganisation, selectedEvent, supabase } = useUnifiedAuth();
|
|
81
|
-
const [pageAccessHistory, setPageAccessHistory] = useState([]);
|
|
82
|
-
const [isEnabled, setIsEnabled] = useState(true);
|
|
83
|
-
const { resolvedScope } = useResolvedScope({
|
|
84
|
-
supabase,
|
|
85
|
-
selectedOrganisationId: selectedOrganisation?.id || null,
|
|
86
|
-
selectedEventId: selectedEvent?.event_id || null
|
|
87
|
-
});
|
|
88
|
-
const currentScope = resolvedScope;
|
|
89
|
-
const hasPagePermission = useCallback((pageName, operation, pageId, scope) => {
|
|
90
|
-
if (!isEnabled) return true;
|
|
91
|
-
if (!user?.id) return false;
|
|
92
|
-
const effectiveScope = scope || currentScope;
|
|
93
|
-
if (!effectiveScope) return false;
|
|
94
|
-
const permission = `${operation}:page.${pageName}`;
|
|
95
|
-
return false;
|
|
96
|
-
}, [isEnabled, user?.id, currentScope]);
|
|
97
|
-
const getPagePermissions = useCallback(() => {
|
|
98
|
-
if (!isEnabled || !user?.id) return {};
|
|
99
|
-
return {};
|
|
100
|
-
}, [isEnabled, user?.id]);
|
|
101
|
-
const getPageAccessHistory = useCallback(() => {
|
|
102
|
-
return [...pageAccessHistory];
|
|
103
|
-
}, [pageAccessHistory]);
|
|
104
|
-
const clearPageAccessHistory = useCallback(() => {
|
|
105
|
-
setPageAccessHistory([]);
|
|
106
|
-
}, []);
|
|
107
|
-
const recordPageAccess = useCallback((pageName, operation, allowed, pageId, scope) => {
|
|
108
|
-
if (!auditLog || !user?.id) return;
|
|
109
|
-
const record = {
|
|
110
|
-
pageName,
|
|
111
|
-
operation,
|
|
112
|
-
userId: user.id,
|
|
113
|
-
scope: scope || currentScope || { organisationId: "" },
|
|
114
|
-
allowed,
|
|
115
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
116
|
-
pageId
|
|
117
|
-
};
|
|
118
|
-
setPageAccessHistory((prev) => {
|
|
119
|
-
const newHistory = [record, ...prev];
|
|
120
|
-
return newHistory.slice(0, maxHistorySize);
|
|
121
|
-
});
|
|
122
|
-
if (onPageAccess) {
|
|
123
|
-
onPageAccess(pageName, operation, allowed, record);
|
|
124
|
-
}
|
|
125
|
-
if (strictMode && !allowed && onStrictModeViolation) {
|
|
126
|
-
onStrictModeViolation(pageName, operation, record);
|
|
127
|
-
}
|
|
128
|
-
}, [auditLog, user?.id, currentScope, maxHistorySize, onPageAccess, onStrictModeViolation, strictMode]);
|
|
129
|
-
const contextValue = useMemo(() => ({
|
|
130
|
-
hasPagePermission,
|
|
131
|
-
getPagePermissions,
|
|
132
|
-
isEnabled,
|
|
133
|
-
isStrictMode: strictMode,
|
|
134
|
-
isAuditLogEnabled: auditLog,
|
|
135
|
-
getPageAccessHistory,
|
|
136
|
-
clearPageAccessHistory
|
|
137
|
-
}), [
|
|
138
|
-
hasPagePermission,
|
|
139
|
-
getPagePermissions,
|
|
140
|
-
isEnabled,
|
|
141
|
-
strictMode,
|
|
142
|
-
auditLog,
|
|
143
|
-
getPageAccessHistory,
|
|
144
|
-
clearPageAccessHistory
|
|
145
|
-
]);
|
|
146
|
-
useEffect(() => {
|
|
147
|
-
if (strictMode && auditLog) {
|
|
148
|
-
log.debug("Strict mode enabled - all page access attempts will be logged and enforced");
|
|
149
|
-
}
|
|
150
|
-
}, [strictMode, auditLog]);
|
|
151
|
-
return /* @__PURE__ */ jsx(PagePermissionContext.Provider, { value: contextValue, children });
|
|
152
|
-
}
|
|
153
|
-
function usePagePermissions() {
|
|
154
|
-
const context = useContext(PagePermissionContext);
|
|
155
|
-
if (!context) {
|
|
156
|
-
throw new Error("usePagePermissions must be used within a PagePermissionProvider");
|
|
157
|
-
}
|
|
158
|
-
return context;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// src/rbac/components/PagePermissionGuard.tsx
|
|
162
|
-
import React2, { useMemo as useMemo2, useEffect as useEffect2, useState as useState2, useRef } from "react";
|
|
163
|
-
import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
164
|
-
var PagePermissionGuardComponent = ({
|
|
165
|
-
pageName,
|
|
166
|
-
operation,
|
|
167
|
-
children,
|
|
168
|
-
fallback = /* @__PURE__ */ jsx2(DefaultAccessDenied, {}),
|
|
169
|
-
strictMode = true,
|
|
170
|
-
auditLog = true,
|
|
171
|
-
pageId,
|
|
172
|
-
scope,
|
|
173
|
-
onDenied,
|
|
174
|
-
loading = /* @__PURE__ */ jsx2(DefaultLoading, {})
|
|
175
|
-
}) => {
|
|
176
|
-
const renderCountRef = useRef(0);
|
|
177
|
-
renderCountRef.current += 1;
|
|
178
|
-
const instanceId = useMemo2(() => Math.random().toString(36).substr(2, 9), []);
|
|
179
|
-
const { user, selectedOrganisation, selectedEvent, supabase, appId: contextAppId, appName } = useUnifiedAuth();
|
|
180
|
-
const [hasChecked, setHasChecked] = useState2(false);
|
|
181
|
-
const hasLoggedSuperAdminRef = useRef(false);
|
|
182
|
-
const effectivePageId = useMemo2(() => {
|
|
183
|
-
return pageId || pageName;
|
|
184
|
-
}, [pageId, pageName]);
|
|
185
|
-
const [isSuperAdmin, setIsSuperAdmin] = useState2(null);
|
|
186
|
-
useEffect2(() => {
|
|
187
|
-
if (!user?.id) {
|
|
188
|
-
setIsSuperAdmin(false);
|
|
189
|
-
return;
|
|
190
|
-
}
|
|
191
|
-
let cancelled = false;
|
|
192
|
-
const checkSuperAdmin = async () => {
|
|
193
|
-
const startTime = Date.now();
|
|
194
|
-
try {
|
|
195
|
-
const { isSuperAdmin: checkSuperAdmin2 } = await import("./api-O6HTBX5Y.js");
|
|
196
|
-
const timeoutPromise = new Promise((_, reject) => {
|
|
197
|
-
setTimeout(() => reject(new Error("Super admin check timeout")), 1e4);
|
|
198
|
-
});
|
|
199
|
-
const isSuper = await Promise.race([
|
|
200
|
-
checkSuperAdmin2(user.id),
|
|
201
|
-
timeoutPromise
|
|
202
|
-
]);
|
|
203
|
-
const elapsed = Date.now() - startTime;
|
|
204
|
-
if (!cancelled) {
|
|
205
|
-
setIsSuperAdmin(isSuper);
|
|
206
|
-
if (false) {
|
|
207
|
-
console.log("[PagePermissionGuard] Super admin check completed", {
|
|
208
|
-
userId: user.id,
|
|
209
|
-
isSuperAdmin: isSuper,
|
|
210
|
-
elapsedMs: elapsed
|
|
211
|
-
});
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
} catch (err) {
|
|
215
|
-
const elapsed = Date.now() - startTime;
|
|
216
|
-
if (!cancelled) {
|
|
217
|
-
console.error("[PagePermissionGuard] Error checking super admin", {
|
|
218
|
-
error: err,
|
|
219
|
-
userId: user.id,
|
|
220
|
-
elapsedMs: elapsed
|
|
221
|
-
});
|
|
222
|
-
setIsSuperAdmin(false);
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
};
|
|
226
|
-
checkSuperAdmin();
|
|
227
|
-
return () => {
|
|
228
|
-
cancelled = true;
|
|
229
|
-
};
|
|
230
|
-
}, [user?.id]);
|
|
231
|
-
const { resolvedScope: hookResolvedScope, isLoading: scopeLoading, error: scopeError } = useResolvedScope({
|
|
232
|
-
supabase,
|
|
233
|
-
selectedOrganisationId: selectedOrganisation?.id || null,
|
|
234
|
-
selectedEventId: selectedEvent?.event_id || null
|
|
235
|
-
});
|
|
236
|
-
const shouldBypassScopeForSuperAdmin = isSuperAdmin === true;
|
|
237
|
-
const allowsOptionalContexts = appName === "PORTAL" || appName === "ADMIN";
|
|
238
|
-
const effectiveScope = scope || (hookResolvedScope ? {
|
|
239
|
-
...hookResolvedScope,
|
|
240
|
-
appId: hookResolvedScope.appId || (allowsOptionalContexts ? contextAppId : void 0)
|
|
241
|
-
} : allowsOptionalContexts && contextAppId ? {
|
|
242
|
-
organisationId: void 0,
|
|
243
|
-
eventId: void 0,
|
|
244
|
-
appId: contextAppId
|
|
245
|
-
} : selectedEvent?.event_id ? {
|
|
246
|
-
organisationId: void 0,
|
|
247
|
-
// Will be derived from event
|
|
248
|
-
eventId: selectedEvent.event_id,
|
|
249
|
-
appId: contextAppId || void 0
|
|
250
|
-
} : null);
|
|
251
|
-
const checkError = scopeError;
|
|
252
|
-
const permission = useMemo2(() => {
|
|
253
|
-
return `${operation}:page.${pageName}`;
|
|
254
|
-
}, [operation, pageName]);
|
|
255
|
-
const prevScopeRef = useRef(null);
|
|
256
|
-
const stableScope = useMemo2(() => {
|
|
257
|
-
if (allowsOptionalContexts && effectiveScope) {
|
|
258
|
-
const newScope2 = {
|
|
259
|
-
organisationId: effectiveScope.organisationId,
|
|
260
|
-
appId: effectiveScope.appId || contextAppId || void 0,
|
|
261
|
-
eventId: effectiveScope.eventId
|
|
262
|
-
};
|
|
263
|
-
if (scopeEqual(prevScopeRef.current, newScope2)) {
|
|
264
|
-
return prevScopeRef.current;
|
|
265
|
-
}
|
|
266
|
-
prevScopeRef.current = newScope2;
|
|
267
|
-
return newScope2;
|
|
268
|
-
}
|
|
269
|
-
const newScope = effectiveScope && effectiveScope.organisationId ? {
|
|
270
|
-
organisationId: effectiveScope.organisationId,
|
|
271
|
-
appId: effectiveScope.appId || contextAppId || void 0,
|
|
272
|
-
eventId: effectiveScope.eventId || void 0
|
|
273
|
-
} : {
|
|
274
|
-
organisationId: effectiveScope?.organisationId || void 0,
|
|
275
|
-
appId: effectiveScope?.appId || contextAppId || void 0,
|
|
276
|
-
eventId: effectiveScope?.eventId || selectedEvent?.event_id || void 0
|
|
277
|
-
};
|
|
278
|
-
if (scopeEqual(prevScopeRef.current, newScope)) {
|
|
279
|
-
return prevScopeRef.current;
|
|
280
|
-
}
|
|
281
|
-
prevScopeRef.current = newScope;
|
|
282
|
-
return newScope;
|
|
283
|
-
}, [effectiveScope, appName, contextAppId, selectedEvent?.event_id]);
|
|
284
|
-
const scopeForPermissionCheck = shouldBypassScopeForSuperAdmin && !stableScope?.organisationId ? {
|
|
285
|
-
organisationId: void 0,
|
|
286
|
-
appId: contextAppId || void 0,
|
|
287
|
-
eventId: selectedEvent?.event_id || void 0
|
|
288
|
-
} : stableScope;
|
|
289
|
-
const shouldSkipPermissionCheck = isSuperAdmin === true;
|
|
290
|
-
const { can, isLoading: canIsLoading, error: canError } = useCan(
|
|
291
|
-
user?.id || "",
|
|
292
|
-
shouldSkipPermissionCheck ? { organisationId: void 0, appId: contextAppId || void 0, eventId: void 0 } : scopeForPermissionCheck,
|
|
293
|
-
permission,
|
|
294
|
-
effectivePageId,
|
|
295
|
-
true,
|
|
296
|
-
// Use cache
|
|
297
|
-
isSuperAdmin,
|
|
298
|
-
// precomputedSuperAdmin - null if checking, true/false if checked
|
|
299
|
-
appName
|
|
300
|
-
// Pass appName for PORTAL/ADMIN special case
|
|
301
|
-
);
|
|
302
|
-
const effectiveCan = shouldSkipPermissionCheck ? true : can;
|
|
303
|
-
const effectiveIsLoading = shouldSkipPermissionCheck ? false : canIsLoading;
|
|
304
|
-
const isLoading = shouldBypassScopeForSuperAdmin ? effectiveIsLoading : scopeLoading || effectiveIsLoading;
|
|
305
|
-
const error = checkError || canError;
|
|
306
|
-
useEffect2(() => {
|
|
307
|
-
if (!isLoading && !error) {
|
|
308
|
-
setHasChecked(true);
|
|
309
|
-
if (!effectiveCan && onDenied) {
|
|
310
|
-
onDenied(pageName, operation);
|
|
311
|
-
}
|
|
312
|
-
} else if (error) {
|
|
313
|
-
setHasChecked(true);
|
|
314
|
-
}
|
|
315
|
-
}, [effectiveCan, isLoading, error, pageName, operation, onDenied]);
|
|
316
|
-
useEffect2(() => {
|
|
317
|
-
if (auditLog && hasChecked && !isLoading) {
|
|
318
|
-
const rbacLogger = getRBACLogger();
|
|
319
|
-
rbacLogger.debug("Page access attempt:", {
|
|
320
|
-
pageName,
|
|
321
|
-
operation,
|
|
322
|
-
userId: user?.id,
|
|
323
|
-
scope: effectiveScope,
|
|
324
|
-
allowed: effectiveCan,
|
|
325
|
-
isSuperAdmin,
|
|
326
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
327
|
-
});
|
|
328
|
-
}
|
|
329
|
-
}, [auditLog, hasChecked, isLoading, pageName, operation, user?.id, effectiveScope, effectiveCan, isSuperAdmin]);
|
|
330
|
-
useEffect2(() => {
|
|
331
|
-
if (strictMode && hasChecked && !isLoading && !effectiveCan && !shouldBypassScopeForSuperAdmin) {
|
|
332
|
-
const logger2 = getRBACLogger();
|
|
333
|
-
logger2.error(`STRICT MODE VIOLATION: User attempted to access protected page without permission`, {
|
|
334
|
-
pageName,
|
|
335
|
-
operation,
|
|
336
|
-
permission: `${operation}:page.${pageName}`,
|
|
337
|
-
pageId: effectivePageId,
|
|
338
|
-
userId: user?.id,
|
|
339
|
-
scope: effectiveScope,
|
|
340
|
-
scopeValid: allowsOptionalContexts ? true : effectiveScope !== null,
|
|
341
|
-
// PORTAL/ADMIN allow scope without org/event
|
|
342
|
-
checkError,
|
|
343
|
-
canError,
|
|
344
|
-
isSuperAdmin,
|
|
345
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
346
|
-
});
|
|
347
|
-
}
|
|
348
|
-
}, [strictMode, hasChecked, isLoading, effectiveCan, shouldBypassScopeForSuperAdmin, pageName, operation, effectivePageId, user?.id, effectiveScope, allowsOptionalContexts, checkError, canError, isSuperAdmin]);
|
|
349
|
-
const hasValidScopeForPagePermissions = shouldBypassScopeForSuperAdmin ? true : allowsOptionalContexts ? true : effectiveScope !== null;
|
|
350
|
-
const hasValidUser = user && user.id;
|
|
351
|
-
const isPermissionCheckComplete = hasChecked && !isLoading;
|
|
352
|
-
const shouldShowAccessDenied = isPermissionCheckComplete && hasValidScopeForPagePermissions && hasValidUser && !checkError && !effectiveCan;
|
|
353
|
-
const shouldShowContent = isPermissionCheckComplete && hasValidScopeForPagePermissions && hasValidUser && !checkError && effectiveCan;
|
|
354
|
-
const scopeKey = effectiveScope ? `${effectiveScope.organisationId}-${effectiveScope.eventId}-${effectiveScope.appId}` : "no-scope";
|
|
355
|
-
const permissionKey = `${scopeKey}-${can}-${isLoading}-${!!checkError}-${hasChecked}`;
|
|
356
|
-
const lastLogStateRef = useRef("");
|
|
357
|
-
useEffect2(() => {
|
|
358
|
-
if (false) {
|
|
359
|
-
const currentState = JSON.stringify({
|
|
360
|
-
pageName,
|
|
361
|
-
userId: user?.id,
|
|
362
|
-
isSuperAdmin,
|
|
363
|
-
isLoading,
|
|
364
|
-
scopeLoading,
|
|
365
|
-
canIsLoading,
|
|
366
|
-
hasChecked,
|
|
367
|
-
hasValidUser,
|
|
368
|
-
effectiveCan
|
|
369
|
-
});
|
|
370
|
-
if (currentState !== lastLogStateRef.current) {
|
|
371
|
-
lastLogStateRef.current = currentState;
|
|
372
|
-
console.log("[PagePermissionGuard] Permission check state", {
|
|
373
|
-
pageName,
|
|
374
|
-
userId: user?.id,
|
|
375
|
-
isSuperAdmin,
|
|
376
|
-
isLoading,
|
|
377
|
-
scopeLoading,
|
|
378
|
-
canIsLoading,
|
|
379
|
-
hasChecked,
|
|
380
|
-
hasValidUser,
|
|
381
|
-
effectiveCan,
|
|
382
|
-
stableScope,
|
|
383
|
-
effectiveScope
|
|
384
|
-
});
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
}, [pageName, user?.id, isSuperAdmin, isLoading, scopeLoading, canIsLoading, hasChecked, hasValidUser, effectiveCan, stableScope, effectiveScope]);
|
|
388
|
-
useEffect2(() => {
|
|
389
|
-
if (isLoading && isSuperAdmin === null && hasValidUser) {
|
|
390
|
-
const timeout = setTimeout(() => {
|
|
391
|
-
console.warn("[PagePermissionGuard] Permission check taking longer than expected", {
|
|
392
|
-
pageName,
|
|
393
|
-
userId: user?.id,
|
|
394
|
-
isSuperAdmin,
|
|
395
|
-
scopeLoading,
|
|
396
|
-
canIsLoading,
|
|
397
|
-
hasChecked,
|
|
398
|
-
stableScope,
|
|
399
|
-
effectiveScope,
|
|
400
|
-
appName
|
|
401
|
-
});
|
|
402
|
-
}, 5e3);
|
|
403
|
-
return () => clearTimeout(timeout);
|
|
404
|
-
}
|
|
405
|
-
}, [isLoading, isSuperAdmin, hasValidUser, pageName, user?.id, scopeLoading, canIsLoading, hasChecked, stableScope, effectiveScope, appName]);
|
|
406
|
-
useEffect2(() => {
|
|
407
|
-
if (isSuperAdmin === true && hasValidUser && !hasLoggedSuperAdminRef.current && false) {
|
|
408
|
-
hasLoggedSuperAdminRef.current = true;
|
|
409
|
-
console.log("[PagePermissionGuard] Super admin access granted - bypassing all checks", {
|
|
410
|
-
pageName,
|
|
411
|
-
userId: user?.id,
|
|
412
|
-
operation
|
|
413
|
-
});
|
|
414
|
-
}
|
|
415
|
-
if (isSuperAdmin !== true) {
|
|
416
|
-
hasLoggedSuperAdminRef.current = false;
|
|
417
|
-
}
|
|
418
|
-
}, [isSuperAdmin, hasValidUser, pageName, user?.id, operation]);
|
|
419
|
-
if (isSuperAdmin === true && hasValidUser) {
|
|
420
|
-
return /* @__PURE__ */ jsx2(Fragment, { children });
|
|
421
|
-
}
|
|
422
|
-
if (isLoading || !hasValidUser || !hasChecked || isSuperAdmin === null) {
|
|
423
|
-
return loading || /* @__PURE__ */ jsx2("div", { children: "Checking permissions..." });
|
|
424
|
-
}
|
|
425
|
-
if (checkError && !can) {
|
|
426
|
-
return fallback;
|
|
427
|
-
}
|
|
428
|
-
if (shouldShowAccessDenied) {
|
|
429
|
-
return fallback;
|
|
430
|
-
}
|
|
431
|
-
if (shouldShowContent) {
|
|
432
|
-
return /* @__PURE__ */ jsx2(Fragment, { children });
|
|
433
|
-
}
|
|
434
|
-
return fallback;
|
|
435
|
-
};
|
|
436
|
-
function DefaultAccessDenied() {
|
|
437
|
-
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center min-h-[200px] p-8 text-center", children: [
|
|
438
|
-
/* @__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" }) }) }),
|
|
439
|
-
/* @__PURE__ */ jsx2("h2", { className: "text-xl font-semibold text-sec-900 mb-2", children: "Access Denied" }),
|
|
440
|
-
/* @__PURE__ */ jsx2("p", { className: "text-sec-600 mb-4", children: "You don't have permission to access this page." }),
|
|
441
|
-
/* @__PURE__ */ jsx2(
|
|
442
|
-
"button",
|
|
443
|
-
{
|
|
444
|
-
onClick: () => window.history.back(),
|
|
445
|
-
className: "px-4 py-2 bg-main-600 text-main-50 rounded-md hover:bg-main-700 transition-colors",
|
|
446
|
-
children: "Go Back"
|
|
447
|
-
}
|
|
448
|
-
)
|
|
449
|
-
] });
|
|
450
|
-
}
|
|
451
|
-
function DefaultLoading() {
|
|
452
|
-
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: [
|
|
453
|
-
/* @__PURE__ */ jsx2("div", { className: "animate-spin rounded-full size-8 border-b-2 border-main-600" }),
|
|
454
|
-
/* @__PURE__ */ jsx2("span", { className: "text-sec-600", children: "Checking permissions..." })
|
|
455
|
-
] }) });
|
|
456
|
-
}
|
|
457
|
-
var PagePermissionGuard = React2.memo(PagePermissionGuardComponent);
|
|
458
|
-
|
|
459
|
-
// src/rbac/components/SecureDataProvider.tsx
|
|
460
|
-
import { createContext as createContext2, useContext as useContext2, useState as useState3, useCallback as useCallback3, useMemo as useMemo3, useEffect as useEffect3 } from "react";
|
|
461
|
-
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
462
|
-
var SecureDataContext = createContext2(null);
|
|
463
|
-
function SecureDataProvider({
|
|
464
|
-
children,
|
|
465
|
-
strictMode = true,
|
|
466
|
-
auditLog = true,
|
|
467
|
-
onDataAccess,
|
|
468
|
-
onStrictModeViolation,
|
|
469
|
-
maxHistorySize = 1e3,
|
|
470
|
-
enforceRLS = true
|
|
471
|
-
}) {
|
|
472
|
-
const { user, selectedOrganisation, selectedEvent, supabase } = useUnifiedAuth();
|
|
473
|
-
const secureSupabase = useSecureSupabase(supabase);
|
|
474
|
-
const { superAdminContext } = useOrganisationSecurity();
|
|
475
|
-
const [dataAccessHistory, setDataAccessHistory] = useState3([]);
|
|
476
|
-
const [isEnabled, setIsEnabled] = useState3(true);
|
|
477
|
-
const { resolvedScope } = useResolvedScope({
|
|
478
|
-
supabase,
|
|
479
|
-
selectedOrganisationId: selectedOrganisation?.id || null,
|
|
480
|
-
selectedEventId: selectedEvent?.event_id || null
|
|
481
|
-
});
|
|
482
|
-
const validateContext = useCallback3(() => {
|
|
483
|
-
if (!secureSupabase) {
|
|
484
|
-
throw new Error("No Supabase client available");
|
|
485
|
-
}
|
|
486
|
-
if (!user) {
|
|
487
|
-
throw new Error("User must be authenticated");
|
|
488
|
-
}
|
|
489
|
-
if (!superAdminContext.isSuperAdmin && !resolvedScope?.organisationId) {
|
|
490
|
-
throw new Error("Organisation context is required for data access");
|
|
491
|
-
}
|
|
492
|
-
}, [secureSupabase, user, superAdminContext.isSuperAdmin, resolvedScope?.organisationId]);
|
|
493
|
-
const currentScope = resolvedScope;
|
|
494
|
-
const isDataAccessAllowed = useCallback3((table, operation, scope) => {
|
|
495
|
-
if (!isEnabled) return true;
|
|
496
|
-
if (!user?.id) return false;
|
|
497
|
-
const effectiveScope = scope || currentScope;
|
|
498
|
-
if (!effectiveScope) return false;
|
|
499
|
-
const permission = `${operation}:data.${table}`;
|
|
500
|
-
return true;
|
|
501
|
-
}, [isEnabled, user?.id, currentScope]);
|
|
502
|
-
const getDataAccessPermissions = useCallback3(() => {
|
|
503
|
-
if (!isEnabled || !user?.id) return {};
|
|
504
|
-
return {};
|
|
505
|
-
}, [isEnabled, user?.id]);
|
|
506
|
-
const getDataAccessHistory = useCallback3(() => {
|
|
507
|
-
return [...dataAccessHistory];
|
|
508
|
-
}, [dataAccessHistory]);
|
|
509
|
-
const clearDataAccessHistory = useCallback3(() => {
|
|
510
|
-
setDataAccessHistory([]);
|
|
511
|
-
}, []);
|
|
512
|
-
const validateDataAccess = useCallback3((table, operation, scope) => {
|
|
513
|
-
if (!isEnabled) return true;
|
|
514
|
-
if (!user?.id) return false;
|
|
515
|
-
const effectiveScope = scope || currentScope;
|
|
516
|
-
if (!effectiveScope) return false;
|
|
517
|
-
try {
|
|
518
|
-
validateContext();
|
|
519
|
-
} catch (error) {
|
|
520
|
-
const logger2 = getRBACLogger();
|
|
521
|
-
logger2.error("Organisation context validation failed:", error);
|
|
522
|
-
return false;
|
|
523
|
-
}
|
|
524
|
-
return isDataAccessAllowed(table, operation, effectiveScope);
|
|
525
|
-
}, [isEnabled, user?.id, currentScope, validateContext, isDataAccessAllowed]);
|
|
526
|
-
const recordDataAccess = useCallback3((table, operation, allowed, query, filters, scope) => {
|
|
527
|
-
if (!auditLog || !user?.id) return;
|
|
528
|
-
const record = {
|
|
529
|
-
table,
|
|
530
|
-
operation,
|
|
531
|
-
userId: user.id,
|
|
532
|
-
scope: scope || currentScope || { organisationId: "" },
|
|
533
|
-
allowed,
|
|
534
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
535
|
-
query,
|
|
536
|
-
filters
|
|
537
|
-
};
|
|
538
|
-
setDataAccessHistory((prev) => {
|
|
539
|
-
const newHistory = [record, ...prev];
|
|
540
|
-
return newHistory.slice(0, maxHistorySize);
|
|
541
|
-
});
|
|
542
|
-
if (onDataAccess) {
|
|
543
|
-
onDataAccess(table, operation, allowed, record);
|
|
544
|
-
}
|
|
545
|
-
if (strictMode && !allowed && onStrictModeViolation) {
|
|
546
|
-
onStrictModeViolation(table, operation, record);
|
|
547
|
-
}
|
|
548
|
-
}, [auditLog, user?.id, currentScope, maxHistorySize, onDataAccess, onStrictModeViolation, strictMode]);
|
|
549
|
-
const contextValue = useMemo3(() => ({
|
|
550
|
-
isDataAccessAllowed,
|
|
551
|
-
getDataAccessPermissions,
|
|
552
|
-
isEnabled,
|
|
553
|
-
isStrictMode: strictMode,
|
|
554
|
-
isAuditLogEnabled: auditLog,
|
|
555
|
-
getDataAccessHistory,
|
|
556
|
-
clearDataAccessHistory,
|
|
557
|
-
validateDataAccess
|
|
558
|
-
}), [
|
|
559
|
-
isDataAccessAllowed,
|
|
560
|
-
getDataAccessPermissions,
|
|
561
|
-
isEnabled,
|
|
562
|
-
strictMode,
|
|
563
|
-
auditLog,
|
|
564
|
-
getDataAccessHistory,
|
|
565
|
-
clearDataAccessHistory,
|
|
566
|
-
validateDataAccess
|
|
567
|
-
]);
|
|
568
|
-
useEffect3(() => {
|
|
569
|
-
if (strictMode && auditLog) {
|
|
570
|
-
const logger2 = getRBACLogger();
|
|
571
|
-
logger2.debug("Strict mode enabled - all data access attempts will be logged and enforced");
|
|
572
|
-
}
|
|
573
|
-
}, [strictMode, auditLog]);
|
|
574
|
-
useEffect3(() => {
|
|
575
|
-
if (enforceRLS && auditLog) {
|
|
576
|
-
const logger2 = getRBACLogger();
|
|
577
|
-
}
|
|
578
|
-
}, [enforceRLS, auditLog]);
|
|
579
|
-
return /* @__PURE__ */ jsx3(SecureDataContext.Provider, { value: contextValue, children });
|
|
580
|
-
}
|
|
581
|
-
function useSecureData() {
|
|
582
|
-
const context = useContext2(SecureDataContext);
|
|
583
|
-
if (!context) {
|
|
584
|
-
throw new Error("useSecureData must be used within a SecureDataProvider");
|
|
585
|
-
}
|
|
586
|
-
return context;
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
// src/rbac/components/PermissionEnforcer.tsx
|
|
590
|
-
import { useMemo as useMemo4, useEffect as useEffect4, useState as useState4, useRef as useRef2 } from "react";
|
|
591
|
-
import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
592
|
-
var log2 = createLogger("PermissionEnforcer");
|
|
593
|
-
function PermissionEnforcer({
|
|
594
|
-
permissions,
|
|
595
|
-
operation,
|
|
596
|
-
children,
|
|
597
|
-
fallback = /* @__PURE__ */ jsx4(DefaultAccessDenied2, {}),
|
|
598
|
-
strictMode = true,
|
|
599
|
-
auditLog = true,
|
|
600
|
-
scope,
|
|
601
|
-
onDenied,
|
|
602
|
-
loading = /* @__PURE__ */ jsx4(DefaultLoading2, {}),
|
|
603
|
-
requireAll = true
|
|
604
|
-
}) {
|
|
605
|
-
const { user, selectedOrganisation, selectedEvent, supabase } = useUnifiedAuth();
|
|
606
|
-
const [hasChecked, setHasChecked] = useState4(false);
|
|
607
|
-
const { resolvedScope, isLoading: scopeLoading, error: scopeError } = useResolvedScope({
|
|
608
|
-
supabase,
|
|
609
|
-
selectedOrganisationId: selectedOrganisation?.id || null,
|
|
610
|
-
selectedEventId: selectedEvent?.event_id || null
|
|
611
|
-
});
|
|
612
|
-
const scopeToUse = scope || resolvedScope;
|
|
613
|
-
const scopeOrgId = scopeToUse?.organisationId || "";
|
|
614
|
-
const scopeEventId = scopeToUse?.eventId || void 0;
|
|
615
|
-
const scopeAppId = scopeToUse?.appId || void 0;
|
|
616
|
-
const effectiveScope = useMemo4(() => {
|
|
617
|
-
const newScope = {};
|
|
618
|
-
if (scopeOrgId) {
|
|
619
|
-
newScope.organisationId = scopeOrgId;
|
|
620
|
-
}
|
|
621
|
-
if (scopeEventId) {
|
|
622
|
-
newScope.eventId = scopeEventId;
|
|
623
|
-
}
|
|
624
|
-
if (scopeAppId) {
|
|
625
|
-
newScope.appId = scopeAppId;
|
|
626
|
-
}
|
|
627
|
-
return newScope;
|
|
628
|
-
}, [scopeOrgId, scopeEventId, scopeAppId]);
|
|
629
|
-
const checkError = scopeError;
|
|
630
|
-
const { results: permissionResults, isLoading: permissionsLoading, error: permissionsError } = useMultiplePermissions(
|
|
631
|
-
user?.id || "",
|
|
632
|
-
effectiveScope || { eventId: selectedEvent?.event_id || void 0 },
|
|
633
|
-
permissions,
|
|
634
|
-
true
|
|
635
|
-
// Use cache
|
|
636
|
-
);
|
|
637
|
-
const isLoading = scopeLoading || permissionsLoading;
|
|
638
|
-
const error = checkError || permissionsError;
|
|
639
|
-
const hasRequiredPermissions = useMemo4(() => {
|
|
640
|
-
if (permissions.length === 0) return true;
|
|
641
|
-
if (!permissionResults || Object.keys(permissionResults).length === 0) {
|
|
642
|
-
return false;
|
|
643
|
-
}
|
|
644
|
-
if (requireAll) {
|
|
645
|
-
return Object.values(permissionResults).every((result) => result === true);
|
|
646
|
-
} else {
|
|
647
|
-
return Object.values(permissionResults).some((result) => result === true);
|
|
648
|
-
}
|
|
649
|
-
}, [permissions, permissionResults, requireAll]);
|
|
650
|
-
useEffect4(() => {
|
|
651
|
-
if (!isLoading && !error) {
|
|
652
|
-
setHasChecked(true);
|
|
653
|
-
if (!hasRequiredPermissions && onDenied) {
|
|
654
|
-
onDenied(permissions, operation);
|
|
655
|
-
}
|
|
656
|
-
} else if (error) {
|
|
657
|
-
setHasChecked(true);
|
|
658
|
-
}
|
|
659
|
-
}, [hasRequiredPermissions, isLoading, error, permissions, operation, onDenied]);
|
|
660
|
-
const permissionsKey = permissions.join(",");
|
|
661
|
-
const lastLoggedKeyRef = useRef2(null);
|
|
662
|
-
useEffect4(() => {
|
|
663
|
-
if (auditLog && hasChecked && !isLoading) {
|
|
664
|
-
const logKey = `${operation}-${user?.id}-${permissionsKey}-${hasRequiredPermissions}-${scopeOrgId}-${scopeEventId}-${scopeAppId}`;
|
|
665
|
-
if (lastLoggedKeyRef.current !== logKey) {
|
|
666
|
-
lastLoggedKeyRef.current = logKey;
|
|
667
|
-
log2.debug("Permission check attempt:", {
|
|
668
|
-
permissions,
|
|
669
|
-
operation,
|
|
670
|
-
userId: user?.id,
|
|
671
|
-
scope: effectiveScope,
|
|
672
|
-
allowed: hasRequiredPermissions,
|
|
673
|
-
requireAll,
|
|
674
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
675
|
-
});
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
}, [auditLog, hasChecked, isLoading, permissionsKey, operation, user?.id, scopeOrgId, scopeEventId, scopeAppId, hasRequiredPermissions]);
|
|
679
|
-
useEffect4(() => {
|
|
680
|
-
if (strictMode && hasChecked && !isLoading && !hasRequiredPermissions) {
|
|
681
|
-
const logger2 = getRBACLogger();
|
|
682
|
-
logger2.error(`STRICT MODE VIOLATION: User attempted to perform operation without permission`, {
|
|
683
|
-
permissions,
|
|
684
|
-
operation,
|
|
685
|
-
userId: user?.id,
|
|
686
|
-
scope: effectiveScope,
|
|
687
|
-
requireAll,
|
|
688
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
689
|
-
});
|
|
690
|
-
}
|
|
691
|
-
}, [strictMode, hasChecked, isLoading, hasRequiredPermissions, permissions, operation, user?.id, effectiveScope, requireAll]);
|
|
692
|
-
if (isLoading || !hasChecked) {
|
|
693
|
-
return /* @__PURE__ */ jsx4(Fragment2, { children: loading });
|
|
694
|
-
}
|
|
695
|
-
if (checkError) {
|
|
696
|
-
const logger2 = getRBACLogger();
|
|
697
|
-
logger2.error(`Permission check failed for operation ${operation}:`, checkError);
|
|
698
|
-
return /* @__PURE__ */ jsx4(Fragment2, { children: fallback });
|
|
699
|
-
}
|
|
700
|
-
if (!hasRequiredPermissions) {
|
|
701
|
-
return /* @__PURE__ */ jsx4(Fragment2, { children: fallback });
|
|
702
|
-
}
|
|
703
|
-
return /* @__PURE__ */ jsx4(Fragment2, { children });
|
|
704
|
-
}
|
|
705
|
-
function DefaultAccessDenied2() {
|
|
706
|
-
return /* @__PURE__ */ jsxs2("div", { className: "flex flex-col items-center justify-center min-h-[200px] p-8 text-center", children: [
|
|
707
|
-
/* @__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" }) }) }),
|
|
708
|
-
/* @__PURE__ */ jsx4("h2", { className: "text-xl font-semibold text-sec-900 mb-2", children: "Access Denied" }),
|
|
709
|
-
/* @__PURE__ */ jsx4("p", { className: "text-sec-600 mb-4", children: "You don't have permission to perform this operation." }),
|
|
710
|
-
/* @__PURE__ */ jsx4(
|
|
711
|
-
"button",
|
|
712
|
-
{
|
|
713
|
-
onClick: () => window.history.back(),
|
|
714
|
-
className: "px-4 py-2 bg-main-600 text-main-50 rounded-md hover:bg-main-700 transition-colors",
|
|
715
|
-
children: "Go Back"
|
|
716
|
-
}
|
|
717
|
-
)
|
|
718
|
-
] });
|
|
719
|
-
}
|
|
720
|
-
function DefaultLoading2() {
|
|
721
|
-
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: [
|
|
722
|
-
/* @__PURE__ */ jsx4("div", { className: "animate-spin rounded-full size-8 border-b-2 border-main-600" }),
|
|
723
|
-
/* @__PURE__ */ jsx4("span", { className: "text-sec-600", children: "Checking permissions..." })
|
|
724
|
-
] }) });
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
// src/rbac/components/RoleBasedRouter.tsx
|
|
728
|
-
import { useMemo as useMemo5, useCallback as useCallback5, useEffect as useEffect5, useState as useState5, createContext as createContext3, useContext as useContext3 } from "react";
|
|
729
|
-
import { useLocation, useNavigate, Outlet } from "react-router-dom";
|
|
730
|
-
import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
731
|
-
var RoleBasedRouterContext = createContext3(null);
|
|
732
|
-
function RoleBasedRouter({
|
|
733
|
-
routes,
|
|
734
|
-
fallbackRoute = "/unauthorized",
|
|
735
|
-
children,
|
|
736
|
-
strictMode = true,
|
|
737
|
-
auditLog = true,
|
|
738
|
-
onRouteAccess,
|
|
739
|
-
onStrictModeViolation,
|
|
740
|
-
maxHistorySize = 1e3,
|
|
741
|
-
unauthorizedComponent: UnauthorizedComponent = DefaultUnauthorizedComponent
|
|
742
|
-
}) {
|
|
743
|
-
const { user, selectedOrganisation, selectedEvent, supabase } = useUnifiedAuth();
|
|
744
|
-
const location = useLocation();
|
|
745
|
-
const navigate = useNavigate();
|
|
746
|
-
const [routeAccessHistory, setRouteAccessHistory] = useState5([]);
|
|
747
|
-
const [currentRoute, setCurrentRoute] = useState5("");
|
|
748
|
-
const { resolvedScope } = useResolvedScope({
|
|
749
|
-
supabase,
|
|
750
|
-
selectedOrganisationId: selectedOrganisation?.id || null,
|
|
751
|
-
selectedEventId: selectedEvent?.event_id || null
|
|
752
|
-
});
|
|
753
|
-
const currentScope = resolvedScope;
|
|
754
|
-
const currentRouteConfig = useMemo5(() => {
|
|
755
|
-
const currentPath = location.pathname;
|
|
756
|
-
return routes.find((route) => route.path === currentPath) || null;
|
|
757
|
-
}, [routes, location.pathname]);
|
|
758
|
-
const canAccessRoute = useCallback5((path) => {
|
|
759
|
-
if (!user?.id || !currentScope) return false;
|
|
760
|
-
const routeConfig = routes.find((route) => route.path === path);
|
|
761
|
-
if (!routeConfig) return false;
|
|
762
|
-
return true;
|
|
763
|
-
}, [user?.id, currentScope, routes]);
|
|
764
|
-
const { can: canAccessCurrentRoute, isLoading: permissionLoading } = useCan(
|
|
765
|
-
user?.id || "",
|
|
766
|
-
currentScope || { organisationId: "", eventId: void 0, appId: void 0 },
|
|
767
|
-
currentRouteConfig?.permissions?.[0] || "read:page",
|
|
768
|
-
currentRouteConfig?.pageId,
|
|
769
|
-
true,
|
|
770
|
-
// useCache
|
|
771
|
-
null,
|
|
772
|
-
// precomputedSuperAdmin - not checked yet
|
|
773
|
-
void 0
|
|
774
|
-
// appName
|
|
775
|
-
);
|
|
776
|
-
const isPublicRoute = currentRouteConfig?.public === true;
|
|
777
|
-
const hasPermissions = currentRouteConfig?.permissions && currentRouteConfig.permissions.length > 0;
|
|
778
|
-
const finalCanAccess = isPublicRoute ? true : hasPermissions ? canAccessCurrentRoute : false;
|
|
779
|
-
const finalLoading = isPublicRoute ? false : hasPermissions ? permissionLoading : false;
|
|
780
|
-
const getAccessibleRoutes = useCallback5(() => {
|
|
781
|
-
if (!user?.id || !currentScope) return [];
|
|
782
|
-
return routes.filter((route) => canAccessRoute(route.path));
|
|
783
|
-
}, [user?.id, currentScope, routes, canAccessRoute]);
|
|
784
|
-
const getRouteConfig = useCallback5((path) => {
|
|
785
|
-
return routes.find((route) => route.path === path) || null;
|
|
786
|
-
}, [routes]);
|
|
787
|
-
const getRouteAccessHistory = useCallback5(() => {
|
|
788
|
-
return [...routeAccessHistory];
|
|
789
|
-
}, [routeAccessHistory]);
|
|
790
|
-
const clearRouteAccessHistory = useCallback5(() => {
|
|
791
|
-
setRouteAccessHistory([]);
|
|
792
|
-
}, []);
|
|
793
|
-
const recordRouteAccess = useCallback5((route, allowed, routeConfig) => {
|
|
794
|
-
if (!auditLog || !user?.id || !currentScope) return;
|
|
795
|
-
const record = {
|
|
796
|
-
route,
|
|
797
|
-
permissions: routeConfig.permissions,
|
|
798
|
-
userId: user.id,
|
|
799
|
-
scope: currentScope,
|
|
800
|
-
allowed,
|
|
801
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
802
|
-
pageId: routeConfig.pageId,
|
|
803
|
-
roles: routeConfig.roles,
|
|
804
|
-
accessLevel: routeConfig.accessLevel
|
|
805
|
-
};
|
|
806
|
-
setRouteAccessHistory((prev) => {
|
|
807
|
-
const newHistory = [record, ...prev];
|
|
808
|
-
return newHistory.slice(0, maxHistorySize);
|
|
809
|
-
});
|
|
810
|
-
if (onRouteAccess) {
|
|
811
|
-
onRouteAccess(route, allowed, record);
|
|
812
|
-
}
|
|
813
|
-
if (strictMode && !allowed && onStrictModeViolation) {
|
|
814
|
-
onStrictModeViolation(route, record);
|
|
815
|
-
}
|
|
816
|
-
}, [auditLog, user?.id, currentScope, maxHistorySize, onRouteAccess, onStrictModeViolation, strictMode]);
|
|
817
|
-
useEffect5(() => {
|
|
818
|
-
const currentPath = location.pathname;
|
|
819
|
-
setCurrentRoute(currentPath);
|
|
820
|
-
if (!currentRouteConfig) {
|
|
821
|
-
if (strictMode) {
|
|
822
|
-
const logger2 = getRBACLogger();
|
|
823
|
-
logger2.error(`STRICT MODE VIOLATION: Route not found in configuration`, {
|
|
824
|
-
route: currentPath,
|
|
825
|
-
userId: user?.id,
|
|
826
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
827
|
-
});
|
|
828
|
-
if (onStrictModeViolation) {
|
|
829
|
-
onStrictModeViolation(currentPath, {
|
|
830
|
-
route: currentPath,
|
|
831
|
-
permissions: [],
|
|
832
|
-
userId: user?.id || "",
|
|
833
|
-
scope: currentScope || { organisationId: "" },
|
|
834
|
-
allowed: false,
|
|
835
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
836
|
-
});
|
|
837
|
-
}
|
|
838
|
-
}
|
|
839
|
-
return;
|
|
840
|
-
}
|
|
841
|
-
const allowed = finalCanAccess;
|
|
842
|
-
recordRouteAccess(currentPath, allowed, currentRouteConfig);
|
|
843
|
-
if (!allowed && !isPublicRoute) {
|
|
844
|
-
navigate(fallbackRoute, { replace: true });
|
|
845
|
-
}
|
|
846
|
-
}, [location.pathname, currentRouteConfig, canAccessCurrentRoute, recordRouteAccess, strictMode, user?.id, currentScope, onStrictModeViolation, navigate, fallbackRoute, isPublicRoute]);
|
|
847
|
-
const contextValue = useMemo5(() => ({
|
|
848
|
-
getAccessibleRoutes,
|
|
849
|
-
canAccessRoute,
|
|
850
|
-
getRouteConfig,
|
|
851
|
-
getRouteAccessHistory,
|
|
852
|
-
clearRouteAccessHistory,
|
|
853
|
-
isStrictMode: strictMode,
|
|
854
|
-
isAuditLogEnabled: auditLog
|
|
855
|
-
}), [
|
|
856
|
-
getAccessibleRoutes,
|
|
857
|
-
canAccessRoute,
|
|
858
|
-
getRouteConfig,
|
|
859
|
-
getRouteAccessHistory,
|
|
860
|
-
clearRouteAccessHistory,
|
|
861
|
-
strictMode,
|
|
862
|
-
auditLog
|
|
863
|
-
]);
|
|
864
|
-
if (finalLoading && !isPublicRoute) {
|
|
865
|
-
return /* @__PURE__ */ jsx5("div", { className: "flex items-center justify-center min-h-screen", children: /* @__PURE__ */ jsxs3("div", { className: "text-center", children: [
|
|
866
|
-
/* @__PURE__ */ jsx5("div", { className: "animate-spin rounded-full size-8 border-b-2 border-main-600 mx-auto mb-4" }),
|
|
867
|
-
/* @__PURE__ */ jsx5("p", { className: "text-sec-600", children: "Checking permissions..." })
|
|
868
|
-
] }) });
|
|
869
|
-
}
|
|
870
|
-
if (currentRouteConfig && !finalCanAccess && !isPublicRoute) {
|
|
871
|
-
return /* @__PURE__ */ jsx5(
|
|
872
|
-
UnauthorizedComponent,
|
|
873
|
-
{
|
|
874
|
-
route: currentRoute,
|
|
875
|
-
reason: "Insufficient permissions"
|
|
876
|
-
}
|
|
877
|
-
);
|
|
878
|
-
}
|
|
879
|
-
return /* @__PURE__ */ jsxs3(RoleBasedRouterContext.Provider, { value: contextValue, children: [
|
|
880
|
-
children,
|
|
881
|
-
/* @__PURE__ */ jsx5(Outlet, {})
|
|
882
|
-
] });
|
|
883
|
-
}
|
|
884
|
-
function useRoleBasedRouter() {
|
|
885
|
-
const context = useContext3(RoleBasedRouterContext);
|
|
886
|
-
if (!context) {
|
|
887
|
-
throw new Error("useRoleBasedRouter must be used within a RoleBasedRouter");
|
|
888
|
-
}
|
|
889
|
-
return context;
|
|
890
|
-
}
|
|
891
|
-
function DefaultUnauthorizedComponent({ route, reason }) {
|
|
892
|
-
return /* @__PURE__ */ jsxs3("div", { className: "flex flex-col items-center justify-center min-h-screen p-8 text-center", children: [
|
|
893
|
-
/* @__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" }) }) }),
|
|
894
|
-
/* @__PURE__ */ jsx5("h2", { className: "text-xl font-semibold text-sec-900 mb-2", children: "Access Denied" }),
|
|
895
|
-
/* @__PURE__ */ jsxs3("p", { className: "text-sec-600 mb-4", children: [
|
|
896
|
-
"You don't have permission to access ",
|
|
897
|
-
/* @__PURE__ */ jsx5("code", { className: "bg-sec-100 px-2 py-1 rounded", children: route })
|
|
898
|
-
] }),
|
|
899
|
-
/* @__PURE__ */ jsxs3("p", { className: "text-sm text-sec-500 mb-4", children: [
|
|
900
|
-
"Reason: ",
|
|
901
|
-
reason
|
|
902
|
-
] }),
|
|
903
|
-
/* @__PURE__ */ jsx5(
|
|
904
|
-
"button",
|
|
905
|
-
{
|
|
906
|
-
onClick: () => window.history.back(),
|
|
907
|
-
className: "px-4 py-2 bg-main-600 text-main-50 rounded-md hover:bg-main-700 transition-colors",
|
|
908
|
-
children: "Go Back"
|
|
909
|
-
}
|
|
910
|
-
)
|
|
911
|
-
] });
|
|
912
|
-
}
|
|
913
|
-
|
|
914
|
-
// src/rbac/components/NavigationProvider.tsx
|
|
915
|
-
import { createContext as createContext4, useContext as useContext4, useState as useState6, useCallback as useCallback6, useMemo as useMemo6, useEffect as useEffect6 } from "react";
|
|
916
|
-
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
917
|
-
var NavigationContext = createContext4(null);
|
|
918
|
-
function NavigationProvider({
|
|
919
|
-
children,
|
|
920
|
-
strictMode = true,
|
|
921
|
-
auditLog = true,
|
|
922
|
-
onNavigationAccess,
|
|
923
|
-
onStrictModeViolation,
|
|
924
|
-
maxHistorySize = 1e3
|
|
925
|
-
}) {
|
|
926
|
-
const { user, selectedOrganisation, selectedEvent, supabase } = useUnifiedAuth();
|
|
927
|
-
const [navigationAccessHistory, setNavigationAccessHistory] = useState6([]);
|
|
928
|
-
const [isEnabled, setIsEnabled] = useState6(true);
|
|
929
|
-
const { resolvedScope } = useResolvedScope({
|
|
930
|
-
supabase,
|
|
931
|
-
selectedOrganisationId: selectedOrganisation?.id || null,
|
|
932
|
-
selectedEventId: selectedEvent?.event_id || null
|
|
933
|
-
});
|
|
934
|
-
const currentScope = resolvedScope;
|
|
935
|
-
const hasNavigationPermission = useCallback6((item) => {
|
|
936
|
-
if (!isEnabled) return true;
|
|
937
|
-
if (!user?.id) return false;
|
|
938
|
-
if (!currentScope) return false;
|
|
939
|
-
if (!item.permissions || item.permissions.length === 0) {
|
|
940
|
-
logger.warn("NavigationProvider", `Navigation item "${item.id}" has no permissions defined - denying access`);
|
|
941
|
-
return false;
|
|
942
|
-
}
|
|
943
|
-
const permission = item.permissions[0];
|
|
944
|
-
const { can, error } = useCan(
|
|
945
|
-
user.id,
|
|
946
|
-
currentScope,
|
|
947
|
-
permission,
|
|
948
|
-
item.pageId,
|
|
949
|
-
true,
|
|
950
|
-
// useCache
|
|
951
|
-
null,
|
|
952
|
-
// precomputedSuperAdmin - not checked yet
|
|
953
|
-
void 0
|
|
954
|
-
// appName
|
|
955
|
-
);
|
|
956
|
-
if (error) {
|
|
957
|
-
logger.warn("NavigationProvider", `Permission check error for "${item.id}": ${error.message} - allowing access for graceful degradation`);
|
|
958
|
-
return true;
|
|
959
|
-
}
|
|
960
|
-
return can;
|
|
961
|
-
}, [isEnabled, user?.id, currentScope]);
|
|
962
|
-
const getNavigationPermissions = useCallback6(() => {
|
|
963
|
-
if (!isEnabled || !user?.id) return {};
|
|
964
|
-
return {};
|
|
965
|
-
}, [isEnabled, user?.id]);
|
|
966
|
-
const getFilteredNavigationItems = useCallback6((items) => {
|
|
967
|
-
if (!isEnabled) return items;
|
|
968
|
-
return items.filter((item) => hasNavigationPermission(item));
|
|
969
|
-
}, [isEnabled, hasNavigationPermission]);
|
|
970
|
-
const getNavigationAccessHistory = useCallback6(() => {
|
|
971
|
-
return [...navigationAccessHistory];
|
|
972
|
-
}, [navigationAccessHistory]);
|
|
973
|
-
const clearNavigationAccessHistory = useCallback6(() => {
|
|
974
|
-
setNavigationAccessHistory([]);
|
|
975
|
-
}, []);
|
|
976
|
-
const recordNavigationAccess = useCallback6((item, allowed) => {
|
|
977
|
-
if (!auditLog || !user?.id || !currentScope) return;
|
|
978
|
-
const record = {
|
|
979
|
-
navigationItem: item.id,
|
|
980
|
-
permissions: item.permissions,
|
|
981
|
-
userId: user.id,
|
|
982
|
-
scope: currentScope,
|
|
983
|
-
allowed,
|
|
984
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
985
|
-
pageId: item.pageId,
|
|
986
|
-
roles: item.roles,
|
|
987
|
-
accessLevel: item.accessLevel
|
|
988
|
-
};
|
|
989
|
-
setNavigationAccessHistory((prev) => {
|
|
990
|
-
const newHistory = [record, ...prev];
|
|
991
|
-
return newHistory.slice(0, maxHistorySize);
|
|
992
|
-
});
|
|
993
|
-
if (onNavigationAccess) {
|
|
994
|
-
onNavigationAccess(item, allowed, record);
|
|
995
|
-
}
|
|
996
|
-
if (strictMode && !allowed && onStrictModeViolation) {
|
|
997
|
-
onStrictModeViolation(item, record);
|
|
998
|
-
}
|
|
999
|
-
}, [auditLog, user?.id, currentScope, maxHistorySize, onNavigationAccess, onStrictModeViolation, strictMode]);
|
|
1000
|
-
const contextValue = useMemo6(() => ({
|
|
1001
|
-
hasNavigationPermission,
|
|
1002
|
-
getNavigationPermissions,
|
|
1003
|
-
getFilteredNavigationItems,
|
|
1004
|
-
isEnabled,
|
|
1005
|
-
isStrictMode: strictMode,
|
|
1006
|
-
isAuditLogEnabled: auditLog,
|
|
1007
|
-
getNavigationAccessHistory,
|
|
1008
|
-
clearNavigationAccessHistory
|
|
1009
|
-
}), [
|
|
1010
|
-
hasNavigationPermission,
|
|
1011
|
-
getNavigationPermissions,
|
|
1012
|
-
getFilteredNavigationItems,
|
|
1013
|
-
isEnabled,
|
|
1014
|
-
strictMode,
|
|
1015
|
-
auditLog,
|
|
1016
|
-
getNavigationAccessHistory,
|
|
1017
|
-
clearNavigationAccessHistory
|
|
1018
|
-
]);
|
|
1019
|
-
useEffect6(() => {
|
|
1020
|
-
if (strictMode && auditLog) {
|
|
1021
|
-
const logger2 = getRBACLogger();
|
|
1022
|
-
}
|
|
1023
|
-
}, [strictMode, auditLog]);
|
|
1024
|
-
return /* @__PURE__ */ jsx6(NavigationContext.Provider, { value: contextValue, children });
|
|
1025
|
-
}
|
|
1026
|
-
function useNavigationPermissions() {
|
|
1027
|
-
const context = useContext4(NavigationContext);
|
|
1028
|
-
if (!context) {
|
|
1029
|
-
throw new Error("useNavigationPermissions must be used within a NavigationProvider");
|
|
1030
|
-
}
|
|
1031
|
-
return context;
|
|
1032
|
-
}
|
|
1033
|
-
|
|
1034
|
-
// src/rbac/components/NavigationGuard.tsx
|
|
1035
|
-
import { useMemo as useMemo7, useEffect as useEffect7, useState as useState7 } from "react";
|
|
1036
|
-
import { Fragment as Fragment3, jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1037
|
-
function NavigationGuard({
|
|
1038
|
-
navigationItem,
|
|
1039
|
-
children,
|
|
1040
|
-
fallback = /* @__PURE__ */ jsx7(DefaultAccessDenied3, {}),
|
|
1041
|
-
strictMode = true,
|
|
1042
|
-
auditLog = true,
|
|
1043
|
-
scope,
|
|
1044
|
-
onDenied,
|
|
1045
|
-
loading = /* @__PURE__ */ jsx7(DefaultLoading3, {}),
|
|
1046
|
-
requireAll = true
|
|
1047
|
-
}) {
|
|
1048
|
-
const { user, selectedOrganisation, selectedEvent, supabase } = useUnifiedAuth();
|
|
1049
|
-
const [hasChecked, setHasChecked] = useState7(false);
|
|
1050
|
-
const { resolvedScope: hookResolvedScope, isLoading: scopeLoading, error: scopeError } = useResolvedScope({
|
|
1051
|
-
supabase,
|
|
1052
|
-
selectedOrganisationId: selectedOrganisation?.id || null,
|
|
1053
|
-
selectedEventId: selectedEvent?.event_id || null
|
|
1054
|
-
});
|
|
1055
|
-
const effectiveScope = scope || hookResolvedScope;
|
|
1056
|
-
const checkError = scopeError;
|
|
1057
|
-
const { results: permissionResults, isLoading: permissionsLoading, error: permissionsError } = useMultiplePermissions(
|
|
1058
|
-
user?.id || "",
|
|
1059
|
-
effectiveScope || { eventId: selectedEvent?.event_id || void 0 },
|
|
1060
|
-
navigationItem.permissions || [],
|
|
1061
|
-
true
|
|
1062
|
-
// Use cache
|
|
1063
|
-
);
|
|
1064
|
-
const isLoading = scopeLoading || permissionsLoading;
|
|
1065
|
-
const error = checkError || permissionsError;
|
|
1066
|
-
const hasRequiredPermissions = useMemo7(() => {
|
|
1067
|
-
if (!navigationItem.permissions || navigationItem.permissions.length === 0) return true;
|
|
1068
|
-
if (requireAll) {
|
|
1069
|
-
return Object.values(permissionResults).every((result) => result === true);
|
|
1070
|
-
} else {
|
|
1071
|
-
return Object.values(permissionResults).some((result) => result === true);
|
|
1072
|
-
}
|
|
1073
|
-
}, [navigationItem.permissions, permissionResults, requireAll]);
|
|
1074
|
-
useEffect7(() => {
|
|
1075
|
-
if (!isLoading && !error) {
|
|
1076
|
-
setHasChecked(true);
|
|
1077
|
-
if (!hasRequiredPermissions && onDenied) {
|
|
1078
|
-
onDenied(navigationItem);
|
|
1079
|
-
}
|
|
1080
|
-
} else if (error) {
|
|
1081
|
-
setHasChecked(true);
|
|
1082
|
-
}
|
|
1083
|
-
}, [hasRequiredPermissions, isLoading, error, navigationItem, onDenied]);
|
|
1084
|
-
useEffect7(() => {
|
|
1085
|
-
if (auditLog && hasChecked && !isLoading) {
|
|
1086
|
-
}
|
|
1087
|
-
}, [auditLog, hasChecked, isLoading, navigationItem, user?.id, effectiveScope, hasRequiredPermissions, requireAll]);
|
|
1088
|
-
useEffect7(() => {
|
|
1089
|
-
if (strictMode && hasChecked && !isLoading && !hasRequiredPermissions) {
|
|
1090
|
-
const logger2 = getRBACLogger();
|
|
1091
|
-
logger2.error(`STRICT MODE VIOLATION: User attempted to access protected navigation item without permission`, {
|
|
1092
|
-
navigationItem: navigationItem.id,
|
|
1093
|
-
permissions: navigationItem.permissions,
|
|
1094
|
-
userId: user?.id,
|
|
1095
|
-
scope: effectiveScope,
|
|
1096
|
-
requireAll,
|
|
1097
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1098
|
-
});
|
|
1099
|
-
}
|
|
1100
|
-
}, [strictMode, hasChecked, isLoading, hasRequiredPermissions, navigationItem, user?.id, effectiveScope, requireAll]);
|
|
1101
|
-
if (isLoading || !effectiveScope || !hasChecked) {
|
|
1102
|
-
return /* @__PURE__ */ jsx7(Fragment3, { children: loading });
|
|
1103
|
-
}
|
|
1104
|
-
if (checkError) {
|
|
1105
|
-
const logger2 = getRBACLogger();
|
|
1106
|
-
logger2.error(`Permission check failed for navigation item ${navigationItem.id}:`, checkError);
|
|
1107
|
-
return /* @__PURE__ */ jsx7(Fragment3, { children: fallback });
|
|
1108
|
-
}
|
|
1109
|
-
if (!hasRequiredPermissions) {
|
|
1110
|
-
return /* @__PURE__ */ jsx7(Fragment3, { children: fallback });
|
|
1111
|
-
}
|
|
1112
|
-
return /* @__PURE__ */ jsx7(Fragment3, { children });
|
|
1113
|
-
}
|
|
1114
|
-
function DefaultAccessDenied3() {
|
|
1115
|
-
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: [
|
|
1116
|
-
/* @__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" }) }),
|
|
1117
|
-
/* @__PURE__ */ jsx7("span", { className: "text-sm text-sec-600", children: "Access Denied" })
|
|
1118
|
-
] }) });
|
|
1119
|
-
}
|
|
1120
|
-
function DefaultLoading3() {
|
|
1121
|
-
return /* @__PURE__ */ jsx7("div", { className: "flex items-center justify-center p-2", children: /* @__PURE__ */ jsxs4("div", { className: "flex items-center space-x-2", children: [
|
|
1122
|
-
/* @__PURE__ */ jsx7("div", { className: "animate-spin rounded-full size-4 border-b-2 border-main-600" }),
|
|
1123
|
-
/* @__PURE__ */ jsx7("span", { className: "text-sm text-sec-600", children: "Checking..." })
|
|
1124
|
-
] }) });
|
|
1125
|
-
}
|
|
1126
|
-
var NavigationGuard_default = NavigationGuard;
|
|
1127
|
-
|
|
1128
|
-
// src/rbac/components/EnhancedNavigationMenu.tsx
|
|
1129
|
-
import { useMemo as useMemo8, useCallback as useCallback8, useEffect as useEffect8, useState as useState8 } from "react";
|
|
1130
|
-
import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1131
|
-
function EnhancedNavigationMenu({
|
|
1132
|
-
items,
|
|
1133
|
-
strictMode = true,
|
|
1134
|
-
auditLog = true,
|
|
1135
|
-
onNavigationAccess,
|
|
1136
|
-
onStrictModeViolation,
|
|
1137
|
-
className = "flex flex-col space-y-1",
|
|
1138
|
-
itemClassName = "px-3 py-2 rounded-md text-sm font-medium transition-colors",
|
|
1139
|
-
activeItemClassName = "bg-main-100 text-main-700",
|
|
1140
|
-
disabledItemClassName = "text-sec-400 cursor-not-allowed",
|
|
1141
|
-
hideUnauthorizedItems = false,
|
|
1142
|
-
renderItem,
|
|
1143
|
-
activePath,
|
|
1144
|
-
onItemClick
|
|
1145
|
-
}) {
|
|
1146
|
-
const {
|
|
1147
|
-
hasNavigationPermission,
|
|
1148
|
-
getFilteredNavigationItems,
|
|
1149
|
-
isEnabled,
|
|
1150
|
-
isStrictMode,
|
|
1151
|
-
isAuditLogEnabled
|
|
1152
|
-
} = useNavigationPermissions();
|
|
1153
|
-
const [navigationHistory, setNavigationHistory] = useState8([]);
|
|
1154
|
-
const filteredItems = useMemo8(() => {
|
|
1155
|
-
if (!isEnabled) return items;
|
|
1156
|
-
return getFilteredNavigationItems(items);
|
|
1157
|
-
}, [isEnabled, items, getFilteredNavigationItems]);
|
|
1158
|
-
const handleNavigationAccess = useCallback8((item, allowed) => {
|
|
1159
|
-
if (onNavigationAccess) {
|
|
1160
|
-
onNavigationAccess(item, allowed);
|
|
1161
|
-
}
|
|
1162
|
-
if (auditLog) {
|
|
1163
|
-
const logger2 = getRBACLogger();
|
|
1164
|
-
logger2.debug("Navigation access attempt:", {
|
|
1165
|
-
item: item.id,
|
|
1166
|
-
allowed,
|
|
1167
|
-
strictMode,
|
|
1168
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1169
|
-
});
|
|
1170
|
-
}
|
|
1171
|
-
}, [onNavigationAccess, auditLog, strictMode]);
|
|
1172
|
-
const handleStrictModeViolation = useCallback8((item) => {
|
|
1173
|
-
if (onStrictModeViolation) {
|
|
1174
|
-
onStrictModeViolation(item);
|
|
1175
|
-
}
|
|
1176
|
-
if (strictMode) {
|
|
1177
|
-
const logger2 = getRBACLogger();
|
|
1178
|
-
logger2.error(`STRICT MODE VIOLATION: User attempted to access protected navigation item without permission`, {
|
|
1179
|
-
item: item.id,
|
|
1180
|
-
path: item.path,
|
|
1181
|
-
permissions: item.permissions,
|
|
1182
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1183
|
-
});
|
|
1184
|
-
}
|
|
1185
|
-
}, [onStrictModeViolation, strictMode]);
|
|
1186
|
-
const handleItemClick = useCallback8((item) => {
|
|
1187
|
-
const isAuthorized = hasNavigationPermission(item);
|
|
1188
|
-
handleNavigationAccess(item, isAuthorized);
|
|
1189
|
-
if (onItemClick) {
|
|
1190
|
-
onItemClick(item);
|
|
1191
|
-
}
|
|
1192
|
-
if (auditLog) {
|
|
1193
|
-
}
|
|
1194
|
-
setNavigationHistory((prev) => {
|
|
1195
|
-
const newHistory = [item, ...prev.filter((i) => i.id !== item.id)];
|
|
1196
|
-
return newHistory.slice(0, 10);
|
|
1197
|
-
});
|
|
1198
|
-
}, [onItemClick, auditLog, hasNavigationPermission, handleNavigationAccess]);
|
|
1199
|
-
const defaultRenderItem = useCallback8((item, isAuthorized) => {
|
|
1200
|
-
const isActive = activePath === item.path;
|
|
1201
|
-
const isDisabled = !isAuthorized;
|
|
1202
|
-
return /* @__PURE__ */ jsx8(
|
|
1203
|
-
NavigationGuard_default,
|
|
1204
|
-
{
|
|
1205
|
-
navigationItem: item,
|
|
1206
|
-
strictMode,
|
|
1207
|
-
auditLog,
|
|
1208
|
-
onDenied: handleStrictModeViolation,
|
|
1209
|
-
fallback: hideUnauthorizedItems ? null : /* @__PURE__ */ jsx8("div", { className: `${itemClassName} ${disabledItemClassName}`, children: /* @__PURE__ */ jsxs5("div", { className: "flex items-center space-x-2", children: [
|
|
1210
|
-
item.meta?.icon && /* @__PURE__ */ jsx8("span", { className: "text-sm", children: item.meta.icon }),
|
|
1211
|
-
/* @__PURE__ */ jsx8("span", { children: item.label }),
|
|
1212
|
-
/* @__PURE__ */ jsx8("span", { className: "text-xs text-sec-400", children: "(Access Denied)" })
|
|
1213
|
-
] }) }),
|
|
1214
|
-
children: /* @__PURE__ */ jsx8(
|
|
1215
|
-
"button",
|
|
1216
|
-
{
|
|
1217
|
-
onClick: () => handleItemClick(item),
|
|
1218
|
-
className: `${itemClassName} ${isActive ? activeItemClassName : ""} ${isDisabled ? disabledItemClassName : "hover:bg-sec-100"}`,
|
|
1219
|
-
disabled: isDisabled,
|
|
1220
|
-
children: /* @__PURE__ */ jsxs5("div", { className: "flex items-center space-x-2", children: [
|
|
1221
|
-
item.meta?.icon && /* @__PURE__ */ jsx8("span", { className: "text-sm", children: item.meta.icon }),
|
|
1222
|
-
/* @__PURE__ */ jsx8("span", { children: item.label }),
|
|
1223
|
-
item.meta?.description && /* @__PURE__ */ jsx8("span", { className: "text-xs text-sec-500 ml-auto", children: item.meta.description })
|
|
1224
|
-
] })
|
|
1225
|
-
}
|
|
1226
|
-
)
|
|
1227
|
-
},
|
|
1228
|
-
item.id
|
|
1229
|
-
);
|
|
1230
|
-
}, [
|
|
1231
|
-
activePath,
|
|
1232
|
-
itemClassName,
|
|
1233
|
-
activeItemClassName,
|
|
1234
|
-
disabledItemClassName,
|
|
1235
|
-
hideUnauthorizedItems,
|
|
1236
|
-
strictMode,
|
|
1237
|
-
auditLog,
|
|
1238
|
-
handleStrictModeViolation,
|
|
1239
|
-
handleItemClick
|
|
1240
|
-
]);
|
|
1241
|
-
useEffect8(() => {
|
|
1242
|
-
if (strictMode && auditLog) {
|
|
1243
|
-
const logger2 = getRBACLogger();
|
|
1244
|
-
}
|
|
1245
|
-
}, [strictMode, auditLog]);
|
|
1246
|
-
useEffect8(() => {
|
|
1247
|
-
if (auditLog) {
|
|
1248
|
-
}
|
|
1249
|
-
}, [items.length, filteredItems.length, strictMode, auditLog]);
|
|
1250
|
-
return /* @__PURE__ */ jsx8("nav", { className, children: filteredItems.map((item) => {
|
|
1251
|
-
const isAuthorized = hasNavigationPermission(item);
|
|
1252
|
-
if (renderItem) {
|
|
1253
|
-
return renderItem(item, isAuthorized);
|
|
1254
|
-
}
|
|
1255
|
-
return defaultRenderItem(item, isAuthorized);
|
|
1256
|
-
}) });
|
|
1257
|
-
}
|
|
1258
|
-
|
|
1259
|
-
// src/rbac/adapters.tsx
|
|
1260
|
-
import { Fragment as Fragment4, jsx as jsx9 } from "react/jsx-runtime";
|
|
1261
|
-
function PermissionGuard({
|
|
1262
|
-
userId,
|
|
1263
|
-
scope,
|
|
1264
|
-
permission,
|
|
1265
|
-
pageId,
|
|
1266
|
-
children,
|
|
1267
|
-
fallback = null,
|
|
1268
|
-
onDenied,
|
|
1269
|
-
loading = null,
|
|
1270
|
-
// NEW: Phase 1 - Enhanced Security Features
|
|
1271
|
-
strictMode = true,
|
|
1272
|
-
auditLog = true,
|
|
1273
|
-
enforceAudit = true
|
|
1274
|
-
}) {
|
|
1275
|
-
const logger2 = getRBACLogger();
|
|
1276
|
-
let authContext = null;
|
|
1277
|
-
try {
|
|
1278
|
-
authContext = useUnifiedAuth();
|
|
1279
|
-
} catch (error2) {
|
|
1280
|
-
if (error2 instanceof Error && error2.message.includes("must be used within")) {
|
|
1281
|
-
authContext = null;
|
|
1282
|
-
} else {
|
|
1283
|
-
throw error2;
|
|
1284
|
-
}
|
|
1285
|
-
}
|
|
1286
|
-
const effectiveUserId = userId ?? authContext?.user?.id ?? null;
|
|
1287
|
-
const { can, isLoading, error } = useCan(
|
|
1288
|
-
effectiveUserId || "",
|
|
1289
|
-
scope,
|
|
1290
|
-
permission,
|
|
1291
|
-
pageId,
|
|
1292
|
-
true,
|
|
1293
|
-
// useCache
|
|
1294
|
-
null,
|
|
1295
|
-
// precomputedSuperAdmin - not checked yet
|
|
1296
|
-
void 0
|
|
1297
|
-
// appName
|
|
1298
|
-
);
|
|
1299
|
-
if (!effectiveUserId) {
|
|
1300
|
-
logger2.error("PermissionGuard: No userId provided and could not infer from context");
|
|
1301
|
-
return fallback ?? null;
|
|
1302
|
-
}
|
|
1303
|
-
if (isLoading) {
|
|
1304
|
-
return loading || /* @__PURE__ */ jsx9("div", { className: "rbac-loading", role: "status", "aria-live": "polite", children: /* @__PURE__ */ jsx9("span", { className: "sr-only", children: "Checking permissions..." }) });
|
|
1305
|
-
}
|
|
1306
|
-
if (error) {
|
|
1307
|
-
logger2.error("Permission check failed:", error);
|
|
1308
|
-
if (auditLog) {
|
|
1309
|
-
}
|
|
1310
|
-
return fallback;
|
|
1311
|
-
}
|
|
1312
|
-
if (!can) {
|
|
1313
|
-
if (auditLog) {
|
|
1314
|
-
}
|
|
1315
|
-
if (strictMode) {
|
|
1316
|
-
logger2.error(`[PermissionGuard] STRICT MODE VIOLATION: User attempted to access protected resource without permission`, {
|
|
1317
|
-
userId: effectiveUserId,
|
|
1318
|
-
scope,
|
|
1319
|
-
permission,
|
|
1320
|
-
pageId,
|
|
1321
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1322
|
-
});
|
|
1323
|
-
}
|
|
1324
|
-
if (onDenied) {
|
|
1325
|
-
onDenied();
|
|
1326
|
-
}
|
|
1327
|
-
return /* @__PURE__ */ jsx9(Fragment4, { children: fallback });
|
|
1328
|
-
}
|
|
1329
|
-
if (auditLog) {
|
|
1330
|
-
}
|
|
1331
|
-
return /* @__PURE__ */ jsx9(Fragment4, { children });
|
|
1332
|
-
}
|
|
1333
|
-
function AccessLevelGuard({
|
|
1334
|
-
userId,
|
|
1335
|
-
scope,
|
|
1336
|
-
minLevel,
|
|
1337
|
-
children,
|
|
1338
|
-
fallback = null,
|
|
1339
|
-
loading = null
|
|
1340
|
-
}) {
|
|
1341
|
-
const logger2 = getRBACLogger();
|
|
1342
|
-
let authContext = null;
|
|
1343
|
-
try {
|
|
1344
|
-
authContext = useUnifiedAuth();
|
|
1345
|
-
} catch (error2) {
|
|
1346
|
-
if (error2 instanceof Error && error2.message.includes("must be used within")) {
|
|
1347
|
-
authContext = null;
|
|
1348
|
-
} else {
|
|
1349
|
-
throw error2;
|
|
1350
|
-
}
|
|
1351
|
-
}
|
|
1352
|
-
const effectiveUserId = userId ?? authContext?.user?.id ?? null;
|
|
1353
|
-
const { accessLevel, isLoading, error } = useAccessLevel(effectiveUserId || "", scope);
|
|
1354
|
-
if (!effectiveUserId) {
|
|
1355
|
-
logger2.error("AccessLevelGuard: No userId provided and could not infer from context");
|
|
1356
|
-
return fallback ?? null;
|
|
1357
|
-
}
|
|
1358
|
-
if (isLoading) {
|
|
1359
|
-
return loading || /* @__PURE__ */ jsx9("div", { className: "rbac-loading", role: "status", "aria-live": "polite", children: /* @__PURE__ */ jsx9("span", { className: "sr-only", children: "Checking access level..." }) });
|
|
1360
|
-
}
|
|
1361
|
-
if (error) {
|
|
1362
|
-
logger2.error("Access level check failed:", error);
|
|
1363
|
-
return fallback;
|
|
1364
|
-
}
|
|
1365
|
-
const levelHierarchy = ["viewer", "participant", "planner", "admin", "super"];
|
|
1366
|
-
const userLevelIndex = accessLevel ? levelHierarchy.indexOf(accessLevel) : -1;
|
|
1367
|
-
const requiredLevelIndex = levelHierarchy.indexOf(minLevel);
|
|
1368
|
-
if (userLevelIndex < requiredLevelIndex) {
|
|
1369
|
-
return /* @__PURE__ */ jsx9(Fragment4, { children: fallback });
|
|
1370
|
-
}
|
|
1371
|
-
return /* @__PURE__ */ jsx9(Fragment4, { children });
|
|
1372
|
-
}
|
|
1373
|
-
function withPermissionGuard(config, handler) {
|
|
1374
|
-
return async (...args) => {
|
|
1375
|
-
const [req] = args;
|
|
1376
|
-
const userId = req.user?.id;
|
|
1377
|
-
const organisationId = req.organisationId;
|
|
1378
|
-
const eventId = req.eventId;
|
|
1379
|
-
const appId = req.appId;
|
|
1380
|
-
if (!userId || !organisationId) {
|
|
1381
|
-
throw new Error("User context required for permission check");
|
|
1382
|
-
}
|
|
1383
|
-
const { isPermitted: isPermitted2 } = await import("./api-O6HTBX5Y.js");
|
|
1384
|
-
const hasPermission2 = await isPermitted2({
|
|
1385
|
-
userId,
|
|
1386
|
-
scope: { organisationId, eventId, appId },
|
|
1387
|
-
permission: config.permission,
|
|
1388
|
-
pageId: config.pageId
|
|
1389
|
-
});
|
|
1390
|
-
if (!hasPermission2) {
|
|
1391
|
-
throw new Error(`Permission denied: ${config.permission}`);
|
|
1392
|
-
}
|
|
1393
|
-
return handler(...args);
|
|
1394
|
-
};
|
|
1395
|
-
}
|
|
1396
|
-
function withAccessLevelGuard(minLevel, handler) {
|
|
1397
|
-
return async (...args) => {
|
|
1398
|
-
const [req] = args;
|
|
1399
|
-
const userId = req.user?.id;
|
|
1400
|
-
const organisationId = req.organisationId;
|
|
1401
|
-
const eventId = req.eventId;
|
|
1402
|
-
const appId = req.appId;
|
|
1403
|
-
if (!userId || !organisationId) {
|
|
1404
|
-
throw new Error("User context required for access level check");
|
|
1405
|
-
}
|
|
1406
|
-
const { getAccessLevel: getAccessLevel2 } = await import("./api-O6HTBX5Y.js");
|
|
1407
|
-
const accessLevel = await getAccessLevel2({
|
|
1408
|
-
userId,
|
|
1409
|
-
scope: { organisationId, eventId, appId }
|
|
1410
|
-
});
|
|
1411
|
-
const levelHierarchy = ["viewer", "participant", "planner", "admin", "super"];
|
|
1412
|
-
const userLevelIndex = levelHierarchy.indexOf(accessLevel);
|
|
1413
|
-
const requiredLevelIndex = levelHierarchy.indexOf(minLevel);
|
|
1414
|
-
if (userLevelIndex < requiredLevelIndex) {
|
|
1415
|
-
throw new Error(`Access level required: ${minLevel}, got: ${accessLevel}`);
|
|
1416
|
-
}
|
|
1417
|
-
return handler(...args);
|
|
1418
|
-
};
|
|
1419
|
-
}
|
|
1420
|
-
function withRoleGuard(config, handler) {
|
|
1421
|
-
return async (...args) => {
|
|
1422
|
-
const [req] = args;
|
|
1423
|
-
const userId = req.user?.id;
|
|
1424
|
-
const organisationId = req.organisationId;
|
|
1425
|
-
const eventId = req.eventId;
|
|
1426
|
-
const appId = req.appId;
|
|
1427
|
-
if (!userId || !organisationId) {
|
|
1428
|
-
throw new Error("User context required for role check");
|
|
1429
|
-
}
|
|
1430
|
-
if (config.globalRoles && config.globalRoles.length > 0) {
|
|
1431
|
-
const { isSuperAdmin } = await import("./api-O6HTBX5Y.js");
|
|
1432
|
-
const isSuper = await isSuperAdmin(userId);
|
|
1433
|
-
if (isSuper) {
|
|
1434
|
-
if (organisationId) {
|
|
1435
|
-
const { emitAuditEvent: emitAuditEvent2 } = await import("./audit-V53FV5AG.js");
|
|
1436
|
-
await emitAuditEvent2({
|
|
1437
|
-
type: "permission_check",
|
|
1438
|
-
userId,
|
|
1439
|
-
organisationId,
|
|
1440
|
-
eventId,
|
|
1441
|
-
appId,
|
|
1442
|
-
permission: "bypass:all",
|
|
1443
|
-
decision: true,
|
|
1444
|
-
source: "api",
|
|
1445
|
-
bypass: true,
|
|
1446
|
-
duration_ms: 0,
|
|
1447
|
-
metadata: {
|
|
1448
|
-
operation: "role_guard",
|
|
1449
|
-
reason: "super_admin_bypass"
|
|
1450
|
-
}
|
|
1451
|
-
});
|
|
1452
|
-
}
|
|
1453
|
-
return handler(...args);
|
|
1454
|
-
}
|
|
1455
|
-
}
|
|
1456
|
-
if (config.organisationRoles && config.organisationRoles.length > 0) {
|
|
1457
|
-
const { isOrganisationAdmin } = await import("./api-O6HTBX5Y.js");
|
|
1458
|
-
const isOrgAdmin = await isOrganisationAdmin(userId, organisationId);
|
|
1459
|
-
if (!isOrgAdmin && config.requireAll !== false) {
|
|
1460
|
-
throw new Error(`Organisation admin role required`);
|
|
1461
|
-
}
|
|
1462
|
-
}
|
|
1463
|
-
if (config.eventAppRoles && config.eventAppRoles.length > 0 && eventId && appId) {
|
|
1464
|
-
const { isEventAdmin } = await import("./api-O6HTBX5Y.js");
|
|
1465
|
-
const isEventAdminUser = await isEventAdmin(userId, { organisationId, eventId, appId });
|
|
1466
|
-
if (!isEventAdminUser && config.requireAll !== false) {
|
|
1467
|
-
throw new Error(`Event admin role required`);
|
|
1468
|
-
}
|
|
1469
|
-
}
|
|
1470
|
-
if (organisationId) {
|
|
1471
|
-
const { emitAuditEvent: emitAuditEvent2 } = await import("./audit-V53FV5AG.js");
|
|
1472
|
-
await emitAuditEvent2({
|
|
1473
|
-
type: "permission_check",
|
|
1474
|
-
userId,
|
|
1475
|
-
organisationId,
|
|
1476
|
-
eventId,
|
|
1477
|
-
appId,
|
|
1478
|
-
permission: "role:check",
|
|
1479
|
-
decision: true,
|
|
1480
|
-
source: "api",
|
|
1481
|
-
bypass: false,
|
|
1482
|
-
duration_ms: 0,
|
|
1483
|
-
metadata: {
|
|
1484
|
-
operation: "role_guard"
|
|
1485
|
-
}
|
|
1486
|
-
});
|
|
1487
|
-
}
|
|
1488
|
-
return handler(...args);
|
|
1489
|
-
};
|
|
1490
|
-
}
|
|
1491
|
-
function createRBACMiddleware(config) {
|
|
1492
|
-
return async (req, res, next) => {
|
|
1493
|
-
const { pathname } = req.nextUrl;
|
|
1494
|
-
const userId = req.user?.id;
|
|
1495
|
-
const organisationId = req.organisationId;
|
|
1496
|
-
if (!userId || !organisationId) {
|
|
1497
|
-
return res.redirect(config.fallbackUrl || "/login");
|
|
1498
|
-
}
|
|
1499
|
-
const protectedRoute = config.protectedRoutes.find(
|
|
1500
|
-
(route) => pathname.startsWith(route.path)
|
|
1501
|
-
);
|
|
1502
|
-
if (protectedRoute) {
|
|
1503
|
-
try {
|
|
1504
|
-
const { isPermitted: isPermitted2 } = await import("./api-O6HTBX5Y.js");
|
|
1505
|
-
const hasPermission2 = await isPermitted2({
|
|
1506
|
-
userId,
|
|
1507
|
-
scope: { organisationId },
|
|
1508
|
-
permission: protectedRoute.permission,
|
|
1509
|
-
pageId: protectedRoute.pageId
|
|
1510
|
-
});
|
|
1511
|
-
if (!hasPermission2) {
|
|
1512
|
-
return res.redirect(config.fallbackUrl || "/access-denied");
|
|
1513
|
-
}
|
|
1514
|
-
} catch (_error) {
|
|
1515
|
-
return res.redirect(config.fallbackUrl || "/access-denied");
|
|
1516
|
-
}
|
|
1517
|
-
}
|
|
1518
|
-
next();
|
|
1519
|
-
};
|
|
1520
|
-
}
|
|
1521
|
-
function createRBACExpressMiddleware(config) {
|
|
1522
|
-
return async (req, res, next) => {
|
|
1523
|
-
const userId = req.user?.id;
|
|
1524
|
-
const organisationId = req.organisationId;
|
|
1525
|
-
const eventId = req.eventId;
|
|
1526
|
-
const appId = req.appId;
|
|
1527
|
-
if (!userId || !organisationId) {
|
|
1528
|
-
return res.status(401).json({ error: "User context required" });
|
|
1529
|
-
}
|
|
1530
|
-
try {
|
|
1531
|
-
const { isPermitted: isPermitted2 } = await import("./api-O6HTBX5Y.js");
|
|
1532
|
-
const hasPermission2 = await isPermitted2({
|
|
1533
|
-
userId,
|
|
1534
|
-
scope: { organisationId, eventId, appId },
|
|
1535
|
-
permission: config.permission,
|
|
1536
|
-
pageId: config.pageId
|
|
1537
|
-
});
|
|
1538
|
-
if (!hasPermission2) {
|
|
1539
|
-
return res.status(403).json({ error: "Permission denied" });
|
|
1540
|
-
}
|
|
1541
|
-
next();
|
|
1542
|
-
} catch (_error) {
|
|
1543
|
-
return res.status(500).json({ error: "Permission check failed" });
|
|
1544
|
-
}
|
|
1545
|
-
};
|
|
1546
|
-
}
|
|
1547
|
-
function hasPermissionCached(userId, scope, _permission, _pageId) {
|
|
1548
|
-
const cacheKey = RBACCache.generatePermissionKey({
|
|
1549
|
-
userId,
|
|
1550
|
-
organisationId: scope.organisationId,
|
|
1551
|
-
eventId: scope.eventId,
|
|
1552
|
-
appId: scope.appId
|
|
1553
|
-
});
|
|
1554
|
-
return rbacCache.get(cacheKey) || false;
|
|
1555
|
-
}
|
|
1556
|
-
function hasAnyPermissionCached(userId, scope, permissions, pageId) {
|
|
1557
|
-
return permissions.some(
|
|
1558
|
-
(permission) => hasPermissionCached(userId, scope, permission, pageId)
|
|
1559
|
-
);
|
|
1560
|
-
}
|
|
1561
|
-
|
|
1562
|
-
// src/rbac/permissions.ts
|
|
1563
|
-
var log3 = createLogger("RBACPermissions");
|
|
1564
|
-
var GLOBAL_PERMISSIONS = {
|
|
1565
|
-
READ_ALL: "read:*",
|
|
1566
|
-
CREATE_ALL: "create:*",
|
|
1567
|
-
UPDATE_ALL: "update:*",
|
|
1568
|
-
DELETE_ALL: "delete:*"
|
|
1569
|
-
};
|
|
1570
|
-
var ORGANISATION_PERMISSIONS = {
|
|
1571
|
-
// Organisation management
|
|
1572
|
-
READ_ORGANISATION: "read:organisation",
|
|
1573
|
-
UPDATE_ORGANISATION: "update:organisation",
|
|
1574
|
-
DELETE_ORGANISATION: "delete:organisation",
|
|
1575
|
-
// User management
|
|
1576
|
-
READ_USERS: "read:users",
|
|
1577
|
-
CREATE_USERS: "create:users",
|
|
1578
|
-
UPDATE_USERS: "update:users",
|
|
1579
|
-
DELETE_USERS: "delete:users",
|
|
1580
|
-
// Role management
|
|
1581
|
-
READ_ROLES: "read:roles",
|
|
1582
|
-
CREATE_ROLES: "create:roles",
|
|
1583
|
-
UPDATE_ROLES: "update:roles",
|
|
1584
|
-
DELETE_ROLES: "delete:roles",
|
|
1585
|
-
// Event management
|
|
1586
|
-
READ_EVENTS: "read:events",
|
|
1587
|
-
CREATE_EVENTS: "create:events",
|
|
1588
|
-
UPDATE_EVENTS: "update:events",
|
|
1589
|
-
DELETE_EVENTS: "delete:events",
|
|
1590
|
-
// App management
|
|
1591
|
-
READ_APPS: "read:apps",
|
|
1592
|
-
CREATE_APPS: "create:apps",
|
|
1593
|
-
UPDATE_APPS: "update:apps",
|
|
1594
|
-
DELETE_APPS: "delete:apps"
|
|
1595
|
-
};
|
|
1596
|
-
var EVENT_APP_PERMISSIONS = {
|
|
1597
|
-
// Event management
|
|
1598
|
-
READ_EVENT: "read:event",
|
|
1599
|
-
CREATE_EVENT: "create:event",
|
|
1600
|
-
UPDATE_EVENT: "update:event",
|
|
1601
|
-
DELETE_EVENT: "delete:event",
|
|
1602
|
-
// App management
|
|
1603
|
-
READ_APP: "read:app",
|
|
1604
|
-
CREATE_APP: "create:app",
|
|
1605
|
-
UPDATE_APP: "update:app",
|
|
1606
|
-
DELETE_APP: "delete:app",
|
|
1607
|
-
// Team management
|
|
1608
|
-
READ_TEAM: "read:team",
|
|
1609
|
-
CREATE_TEAM: "create:team",
|
|
1610
|
-
UPDATE_TEAM: "update:team",
|
|
1611
|
-
DELETE_TEAM: "delete:team",
|
|
1612
|
-
// Team members
|
|
1613
|
-
READ_TEAM_MEMBERS: "read:team.members",
|
|
1614
|
-
CREATE_TEAM_MEMBERS: "create:team.members",
|
|
1615
|
-
UPDATE_TEAM_MEMBERS: "update:team.members",
|
|
1616
|
-
DELETE_TEAM_MEMBERS: "delete:team.members",
|
|
1617
|
-
// Event content
|
|
1618
|
-
READ_EVENT_CONTENT: "read:event.content",
|
|
1619
|
-
CREATE_EVENT_CONTENT: "create:event.content",
|
|
1620
|
-
UPDATE_EVENT_CONTENT: "update:event.content",
|
|
1621
|
-
DELETE_EVENT_CONTENT: "delete:event.content",
|
|
1622
|
-
// Event settings
|
|
1623
|
-
READ_EVENT_SETTINGS: "read:event.settings",
|
|
1624
|
-
CREATE_EVENT_SETTINGS: "create:event.settings",
|
|
1625
|
-
UPDATE_EVENT_SETTINGS: "update:event.settings",
|
|
1626
|
-
DELETE_EVENT_SETTINGS: "delete:event.settings"
|
|
1627
|
-
};
|
|
1628
|
-
var PAGE_PERMISSIONS = {
|
|
1629
|
-
// General page access (generic - used for wildcard checks)
|
|
1630
|
-
READ_PAGE: "read:page",
|
|
1631
|
-
CREATE_PAGE: "create:page",
|
|
1632
|
-
UPDATE_PAGE: "update:page",
|
|
1633
|
-
DELETE_PAGE: "delete:page",
|
|
1634
|
-
// Admin pages
|
|
1635
|
-
READ_ADMIN: "read:page.admin",
|
|
1636
|
-
CREATE_ADMIN: "create:page.admin",
|
|
1637
|
-
UPDATE_ADMIN: "update:page.admin",
|
|
1638
|
-
DELETE_ADMIN: "delete:page.admin",
|
|
1639
|
-
// Dashboard pages
|
|
1640
|
-
READ_DASHBOARD: "read:page.dashboard",
|
|
1641
|
-
CREATE_DASHBOARD: "create:page.dashboard",
|
|
1642
|
-
UPDATE_DASHBOARD: "update:page.dashboard",
|
|
1643
|
-
DELETE_DASHBOARD: "delete:page.dashboard",
|
|
1644
|
-
// Settings pages
|
|
1645
|
-
READ_SETTINGS: "read:page.settings",
|
|
1646
|
-
CREATE_SETTINGS: "create:page.settings",
|
|
1647
|
-
UPDATE_SETTINGS: "update:page.settings",
|
|
1648
|
-
DELETE_SETTINGS: "delete:page.settings",
|
|
1649
|
-
// Reports pages
|
|
1650
|
-
READ_REPORTS: "read:page.reports",
|
|
1651
|
-
CREATE_REPORTS: "create:page.reports",
|
|
1652
|
-
UPDATE_REPORTS: "update:page.reports",
|
|
1653
|
-
DELETE_REPORTS: "delete:page.reports"
|
|
1654
|
-
};
|
|
1655
|
-
function isValidPermission(permission) {
|
|
1656
|
-
const pattern = /^(read|create|update|delete):[a-z0-9]+(\.[a-z0-9]+)*$|^(read|create|update|delete):\*$/;
|
|
1657
|
-
return pattern.test(permission);
|
|
1658
|
-
}
|
|
1659
|
-
var ALL_PERMISSIONS = {
|
|
1660
|
-
...GLOBAL_PERMISSIONS,
|
|
1661
|
-
...ORGANISATION_PERMISSIONS,
|
|
1662
|
-
...EVENT_APP_PERMISSIONS,
|
|
1663
|
-
...PAGE_PERMISSIONS
|
|
1664
|
-
};
|
|
1665
|
-
|
|
1666
|
-
// src/rbac/compliance/setup-validator.ts
|
|
1667
|
-
function isRBACInitialized() {
|
|
1668
|
-
try {
|
|
1669
|
-
const config = getRBACConfig();
|
|
1670
|
-
return config !== null && config.supabase !== null;
|
|
1671
|
-
} catch (error) {
|
|
1672
|
-
if (error instanceof RBACNotInitializedError) {
|
|
1673
|
-
return false;
|
|
1674
|
-
}
|
|
1675
|
-
throw error;
|
|
1676
|
-
}
|
|
1677
|
-
}
|
|
1678
|
-
function getSetupIssues() {
|
|
1679
|
-
const issues = [];
|
|
1680
|
-
const config = getRBACConfig();
|
|
1681
|
-
if (!config) {
|
|
1682
|
-
issues.push({
|
|
1683
|
-
type: "not-initialized",
|
|
1684
|
-
message: "RBAC system has not been initialized. setupRBAC() has not been called.",
|
|
1685
|
-
recommendation: "Call setupRBAC(supabase) before using any RBAC features. This should be done in your main entry point (main.tsx or App.tsx) before rendering the app."
|
|
1686
|
-
});
|
|
1687
|
-
return issues;
|
|
1688
|
-
}
|
|
1689
|
-
if (!config.supabase) {
|
|
1690
|
-
issues.push({
|
|
1691
|
-
type: "missing-config",
|
|
1692
|
-
message: "RBAC configuration is missing Supabase client.",
|
|
1693
|
-
recommendation: "Ensure setupRBAC() is called with a valid Supabase client instance."
|
|
1694
|
-
});
|
|
1695
|
-
}
|
|
1696
|
-
return issues;
|
|
1697
|
-
}
|
|
1698
|
-
function getProviderContextIssues() {
|
|
1699
|
-
const issues = [];
|
|
1700
|
-
if (!isRBACInitialized()) {
|
|
1701
|
-
issues.push({
|
|
1702
|
-
type: "not-initialized",
|
|
1703
|
-
message: "RBAC system must be initialized before provider context can be used.",
|
|
1704
|
-
recommendation: "Call setupRBAC(supabase) before rendering UnifiedAuthProvider."
|
|
1705
|
-
});
|
|
1706
|
-
}
|
|
1707
|
-
return issues;
|
|
1708
|
-
}
|
|
1709
|
-
function validateRBACSetup() {
|
|
1710
|
-
const issues = getSetupIssues();
|
|
1711
|
-
const providerIssues = getProviderContextIssues();
|
|
1712
|
-
return {
|
|
1713
|
-
isCompliant: issues.length === 0 && providerIssues.length === 0,
|
|
1714
|
-
issues: [...issues, ...providerIssues]
|
|
1715
|
-
};
|
|
1716
|
-
}
|
|
1717
|
-
|
|
1718
|
-
// src/rbac/compliance/runtime-compliance.ts
|
|
1719
|
-
function checkRuntimeCompliance() {
|
|
1720
|
-
const logger2 = getRBACLogger();
|
|
1721
|
-
const setupValidation = validateRBACSetup();
|
|
1722
|
-
const warnings = [];
|
|
1723
|
-
if (!setupValidation.isCompliant) {
|
|
1724
|
-
setupValidation.issues.forEach((issue) => {
|
|
1725
|
-
const warning = `[RBAC Compliance] ${issue.message}
|
|
1726
|
-
Recommendation: ${issue.recommendation}`;
|
|
1727
|
-
warnings.push(warning);
|
|
1728
|
-
logger2.warn(warning);
|
|
1729
|
-
});
|
|
1730
|
-
}
|
|
1731
|
-
const providerContextIssues = setupValidation.issues.filter(
|
|
1732
|
-
(issue) => issue.type === "missing-provider-context" || issue.type === "not-initialized"
|
|
1733
|
-
);
|
|
1734
|
-
const providerContext = providerContextIssues.length > 0 ? {
|
|
1735
|
-
available: false,
|
|
1736
|
-
message: "UnifiedAuthProvider context may not be available. Ensure your app is wrapped with UnifiedAuthProvider from @jmruthers/pace-core."
|
|
1737
|
-
} : {
|
|
1738
|
-
available: true
|
|
1739
|
-
};
|
|
1740
|
-
return {
|
|
1741
|
-
setup: setupValidation,
|
|
1742
|
-
warnings,
|
|
1743
|
-
providerContext
|
|
1744
|
-
};
|
|
1745
|
-
}
|
|
1746
|
-
function validateAndWarn() {
|
|
1747
|
-
if (import.meta.env.MODE === "development" || import.meta.env.DEV) {
|
|
1748
|
-
checkRuntimeCompliance();
|
|
1749
|
-
}
|
|
1750
|
-
}
|
|
1751
|
-
|
|
1752
|
-
// src/rbac/compliance/database-validator.ts
|
|
1753
|
-
async function validateDatabaseConfiguration(supabase, appName) {
|
|
1754
|
-
const issues = [];
|
|
1755
|
-
const recommendations = [];
|
|
1756
|
-
let appConfigured = false;
|
|
1757
|
-
let pagesConfigured = false;
|
|
1758
|
-
let permissionsConfigured = false;
|
|
1759
|
-
let rlsPoliciesActive = false;
|
|
1760
|
-
let rolesConfigured = false;
|
|
1761
|
-
try {
|
|
1762
|
-
const { data: app, error: appError } = await supabase.from("rbac_apps").select("id, name").eq("name", appName).single();
|
|
1763
|
-
if (appError || !app) {
|
|
1764
|
-
issues.push({
|
|
1765
|
-
type: "app-not-configured",
|
|
1766
|
-
message: `App '${appName}' not found in rbac_apps table.`,
|
|
1767
|
-
recommendation: `Register your app in the rbac_apps table with name '${appName}' (case-sensitive).`
|
|
1768
|
-
});
|
|
1769
|
-
} else {
|
|
1770
|
-
appConfigured = true;
|
|
1771
|
-
if (app.name !== appName) {
|
|
1772
|
-
issues.push({
|
|
1773
|
-
type: "app-name-mismatch",
|
|
1774
|
-
message: `App name mismatch. Database has '${app.name}', but environment variable has '${appName}'.`,
|
|
1775
|
-
recommendation: `Ensure VITE_APP_NAME (or NEXT_PUBLIC_APP_NAME) matches the app name in rbac_apps table exactly (case-sensitive).`
|
|
1776
|
-
});
|
|
1777
|
-
}
|
|
1778
|
-
const { data: pages, error: pagesError } = await supabase.from("rbac_app_pages").select("id").eq("app_id", app.id).limit(1);
|
|
1779
|
-
if (pagesError || !pages || pages.length === 0) {
|
|
1780
|
-
issues.push({
|
|
1781
|
-
type: "pages-not-configured",
|
|
1782
|
-
message: `No pages found for app '${appName}' in rbac_app_pages table.`,
|
|
1783
|
-
recommendation: "Register your app pages in the rbac_app_pages table. Each route/page should have an entry."
|
|
1784
|
-
});
|
|
1785
|
-
} else {
|
|
1786
|
-
pagesConfigured = true;
|
|
1787
|
-
const { data: permissions, error: permissionsError } = await supabase.from("rbac_page_permissions").select("id").in("page_id", pages.map((p) => p.id)).limit(1);
|
|
1788
|
-
if (permissionsError || !permissions || permissions.length === 0) {
|
|
1789
|
-
issues.push({
|
|
1790
|
-
type: "permissions-not-configured",
|
|
1791
|
-
message: `No permissions found for app '${appName}' pages in rbac_page_permissions table.`,
|
|
1792
|
-
recommendation: "Configure permissions for your app pages in the rbac_page_permissions table. Each page should have permissions for different operations (read, create, update, delete)."
|
|
1793
|
-
});
|
|
1794
|
-
} else {
|
|
1795
|
-
permissionsConfigured = true;
|
|
1796
|
-
}
|
|
1797
|
-
}
|
|
1798
|
-
}
|
|
1799
|
-
try {
|
|
1800
|
-
const { data: rbacTables, error: rlsError } = await supabase.rpc("rbac_check_rls_status");
|
|
1801
|
-
if (rlsError) {
|
|
1802
|
-
rlsPoliciesActive = true;
|
|
1803
|
-
recommendations.push("Consider adding an RLS status check function to validate RLS policies are active.");
|
|
1804
|
-
} else {
|
|
1805
|
-
rlsPoliciesActive = true;
|
|
1806
|
-
}
|
|
1807
|
-
} catch (error) {
|
|
1808
|
-
rlsPoliciesActive = true;
|
|
1809
|
-
recommendations.push("Consider adding an RLS status check function to validate RLS policies are active.");
|
|
1810
|
-
}
|
|
1811
|
-
const { data: orgRoles, error: rolesError } = await supabase.from("rbac_organisation_roles").select("id").limit(1);
|
|
1812
|
-
if (rolesError) {
|
|
1813
|
-
issues.push({
|
|
1814
|
-
type: "roles-not-configured",
|
|
1815
|
-
message: "Unable to query rbac_organisation_roles table. RLS might be blocking access or table might not exist.",
|
|
1816
|
-
recommendation: "Ensure rbac_organisation_roles table exists and RLS policies allow read access for authenticated users."
|
|
1817
|
-
});
|
|
1818
|
-
} else {
|
|
1819
|
-
rolesConfigured = true;
|
|
1820
|
-
}
|
|
1821
|
-
} catch (error) {
|
|
1822
|
-
issues.push({
|
|
1823
|
-
type: "app-not-configured",
|
|
1824
|
-
message: `Error validating database configuration: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
1825
|
-
recommendation: "Check your Supabase connection and ensure you have the necessary permissions to query RBAC tables."
|
|
1826
|
-
});
|
|
1827
|
-
}
|
|
1828
|
-
return {
|
|
1829
|
-
appConfigured,
|
|
1830
|
-
pagesConfigured,
|
|
1831
|
-
permissionsConfigured,
|
|
1832
|
-
rlsPoliciesActive,
|
|
1833
|
-
rolesConfigured,
|
|
1834
|
-
issues,
|
|
1835
|
-
recommendations
|
|
1836
|
-
};
|
|
1837
|
-
}
|
|
1838
|
-
|
|
1839
|
-
// src/rbac/compliance/quick-fix-suggestions.ts
|
|
1840
|
-
function getCustomAuthCodeFixes(customCodeName, type) {
|
|
1841
|
-
const fixes = {
|
|
1842
|
-
"useAuth": {
|
|
1843
|
-
issue: `Custom ${type} '${customCodeName}' detected`,
|
|
1844
|
-
suggestion: `Replace with useUnifiedAuth from pace-core`,
|
|
1845
|
-
codeExample: `// Before
|
|
1846
|
-
import { useAuth } from './hooks/useAuth';
|
|
1847
|
-
|
|
1848
|
-
// After
|
|
1849
|
-
import { useUnifiedAuth } from '@jmruthers/pace-core';`,
|
|
1850
|
-
migrationSteps: [
|
|
1851
|
-
"Remove custom useAuth hook",
|
|
1852
|
-
"Import useUnifiedAuth from @jmruthers/pace-core",
|
|
1853
|
-
"Update all usages to use useUnifiedAuth",
|
|
1854
|
-
"Ensure UnifiedAuthProvider wraps your app"
|
|
1855
|
-
]
|
|
1856
|
-
},
|
|
1857
|
-
"usePermissions": {
|
|
1858
|
-
issue: `Custom ${type} '${customCodeName}' detected`,
|
|
1859
|
-
suggestion: `Replace with usePermissions from pace-core`,
|
|
1860
|
-
codeExample: `// Before
|
|
1861
|
-
import { usePermissions } from './hooks/usePermissions';
|
|
1862
|
-
|
|
1863
|
-
// After
|
|
1864
|
-
import { usePermissions } from '@jmruthers/pace-core/rbac';`,
|
|
1865
|
-
migrationSteps: [
|
|
1866
|
-
"Remove custom usePermissions hook",
|
|
1867
|
-
"Import usePermissions from @jmruthers/pace-core/rbac",
|
|
1868
|
-
"Update all usages - ensure setupRBAC() has been called",
|
|
1869
|
-
"Verify provider hierarchy is correct"
|
|
1870
|
-
]
|
|
1871
|
-
},
|
|
1872
|
-
"PermissionGuard": {
|
|
1873
|
-
issue: `Custom ${type} '${customCodeName}' detected`,
|
|
1874
|
-
suggestion: `Replace with PagePermissionGuard from pace-core`,
|
|
1875
|
-
codeExample: `// Before
|
|
1876
|
-
import { PermissionGuard } from './components/PermissionGuard';
|
|
1877
|
-
|
|
1878
|
-
// After
|
|
1879
|
-
import { PagePermissionGuard } from '@jmruthers/pace-core/rbac';`,
|
|
1880
|
-
migrationSteps: [
|
|
1881
|
-
"Remove custom PermissionGuard component",
|
|
1882
|
-
"Import PagePermissionGuard from @jmruthers/pace-core/rbac",
|
|
1883
|
-
"Wrap pages with PagePermissionGuard",
|
|
1884
|
-
"Use pageName and operation props instead of custom permission strings"
|
|
1885
|
-
]
|
|
1886
|
-
},
|
|
1887
|
-
"checkPermission": {
|
|
1888
|
-
issue: `Custom ${type} '${customCodeName}' detected`,
|
|
1889
|
-
suggestion: `Replace with isPermitted from pace-core`,
|
|
1890
|
-
codeExample: `// Before
|
|
1891
|
-
import { checkPermission } from './utils/permissions';
|
|
1892
|
-
|
|
1893
|
-
// After
|
|
1894
|
-
import { isPermitted } from '@jmruthers/pace-core/rbac';`,
|
|
1895
|
-
migrationSteps: [
|
|
1896
|
-
"Remove custom checkPermission utility",
|
|
1897
|
-
"Import isPermitted from @jmruthers/pace-core/rbac",
|
|
1898
|
-
"Update all usages to use isPermitted with proper scope",
|
|
1899
|
-
"Ensure setupRBAC() has been called"
|
|
1900
|
-
]
|
|
1901
|
-
}
|
|
1902
|
-
};
|
|
1903
|
-
return fixes[customCodeName] || {
|
|
1904
|
-
issue: `Custom ${type} '${customCodeName}' detected`,
|
|
1905
|
-
suggestion: `Use pace-core's equivalent instead. Check @jmruthers/pace-core documentation for the correct import.`,
|
|
1906
|
-
migrationSteps: [
|
|
1907
|
-
`Remove custom ${customCodeName} ${type}`,
|
|
1908
|
-
"Find equivalent in pace-core",
|
|
1909
|
-
"Import from @jmruthers/pace-core or @jmruthers/pace-core/rbac",
|
|
1910
|
-
"Update all usages"
|
|
1911
|
-
]
|
|
1912
|
-
};
|
|
1913
|
-
}
|
|
1914
|
-
function getDuplicateConfigFixes() {
|
|
1915
|
-
return {
|
|
1916
|
-
issue: "Multiple Supabase client instantiations found",
|
|
1917
|
-
suggestion: "Consolidate to a single Supabase client configuration",
|
|
1918
|
-
codeExample: `// Before - Multiple createClient calls
|
|
1919
|
-
// src/lib/supabase.ts
|
|
1920
|
-
export const supabase = createClient(url, key);
|
|
1921
|
-
|
|
1922
|
-
// src/utils/api.ts
|
|
1923
|
-
export const supabase = createClient(url, key);
|
|
1924
|
-
|
|
1925
|
-
// After - Single configuration
|
|
1926
|
-
// src/lib/supabase.ts
|
|
1927
|
-
export const supabase = createClient(
|
|
1928
|
-
import.meta.env.VITE_SUPABASE_URL,
|
|
1929
|
-
import.meta.env.VITE_SUPABASE_PUBLISHABLE_KEY
|
|
1930
|
-
);
|
|
1931
|
-
|
|
1932
|
-
// src/utils/api.ts
|
|
1933
|
-
import { supabase } from '../lib/supabase';`,
|
|
1934
|
-
migrationSteps: [
|
|
1935
|
-
"Create a single supabase.ts file in a shared location (e.g., src/lib/supabase.ts)",
|
|
1936
|
-
"Move all Supabase client creation to this file",
|
|
1937
|
-
"Export the client instance",
|
|
1938
|
-
"Update all files to import from the shared location",
|
|
1939
|
-
"Remove duplicate createClient calls"
|
|
1940
|
-
]
|
|
1941
|
-
};
|
|
1942
|
-
}
|
|
1943
|
-
function getUnprotectedPageFixes() {
|
|
1944
|
-
return {
|
|
1945
|
-
issue: "Route/page found without PagePermissionGuard",
|
|
1946
|
-
suggestion: "Wrap all routes with PagePermissionGuard",
|
|
1947
|
-
codeExample: `// Before
|
|
1948
|
-
<Route path="/dashboard" element={<Dashboard />} />
|
|
1949
|
-
|
|
1950
|
-
// After
|
|
1951
|
-
<Route
|
|
1952
|
-
path="/dashboard"
|
|
1953
|
-
element={
|
|
1954
|
-
<PagePermissionGuard pageName="dashboard" operation="read">
|
|
1955
|
-
<Dashboard />
|
|
1956
|
-
</PagePermissionGuard>
|
|
1957
|
-
}
|
|
1958
|
-
/>`,
|
|
1959
|
-
migrationSteps: [
|
|
1960
|
-
"Import PagePermissionGuard from @jmruthers/pace-core/rbac",
|
|
1961
|
-
"Wrap each route/page component with PagePermissionGuard",
|
|
1962
|
-
"Set pageName prop to match your page name in rbac_app_pages table",
|
|
1963
|
-
"Set operation prop (read, create, update, or delete)",
|
|
1964
|
-
"Ensure setupRBAC() has been called and providers are set up correctly"
|
|
1965
|
-
]
|
|
1966
|
-
};
|
|
1967
|
-
}
|
|
1968
|
-
function getDirectSupabaseAuthFixes() {
|
|
1969
|
-
return {
|
|
1970
|
-
issue: "Direct Supabase auth usage detected",
|
|
1971
|
-
suggestion: "Use UnifiedAuthProvider and useUnifiedAuth from pace-core",
|
|
1972
|
-
codeExample: `// Before
|
|
1973
|
-
import { createClient } from '@supabase/supabase-js';
|
|
1974
|
-
const supabase = createClient(url, key);
|
|
1975
|
-
await supabase.auth.signInWithPassword({ email, password });
|
|
1976
|
-
|
|
1977
|
-
// After
|
|
1978
|
-
import { useUnifiedAuth } from '@jmruthers/pace-core';
|
|
1979
|
-
const { signIn } = useUnifiedAuth();
|
|
1980
|
-
await signIn({ email, password });`,
|
|
1981
|
-
migrationSteps: [
|
|
1982
|
-
"Remove direct Supabase auth calls",
|
|
1983
|
-
"Import useUnifiedAuth from @jmruthers/pace-core",
|
|
1984
|
-
"Use the auth methods from useUnifiedAuth hook",
|
|
1985
|
-
"Ensure UnifiedAuthProvider wraps your app",
|
|
1986
|
-
"Update all auth-related code to use pace-core hooks"
|
|
1987
|
-
]
|
|
1988
|
-
};
|
|
1989
|
-
}
|
|
1990
|
-
function getQuickFixes(issueType, details) {
|
|
1991
|
-
const fixes = [];
|
|
1992
|
-
switch (issueType) {
|
|
1993
|
-
case "custom-auth-code":
|
|
1994
|
-
if (details?.name && details?.type) {
|
|
1995
|
-
fixes.push(getCustomAuthCodeFixes(details.name, details.type));
|
|
1996
|
-
}
|
|
1997
|
-
break;
|
|
1998
|
-
case "duplicate-config":
|
|
1999
|
-
fixes.push(getDuplicateConfigFixes());
|
|
2000
|
-
break;
|
|
2001
|
-
case "unprotected-pages":
|
|
2002
|
-
fixes.push(getUnprotectedPageFixes());
|
|
2003
|
-
break;
|
|
2004
|
-
case "direct-supabase-auth":
|
|
2005
|
-
fixes.push(getDirectSupabaseAuthFixes());
|
|
2006
|
-
break;
|
|
2007
|
-
}
|
|
2008
|
-
return fixes;
|
|
2009
|
-
}
|
|
2010
|
-
|
|
2011
|
-
export {
|
|
2012
|
-
RPCFunction,
|
|
2013
|
-
RBACErrorCode,
|
|
2014
|
-
PagePermissionProvider,
|
|
2015
|
-
usePagePermissions,
|
|
2016
|
-
PagePermissionGuard,
|
|
2017
|
-
SecureDataProvider,
|
|
2018
|
-
useSecureData,
|
|
2019
|
-
PermissionEnforcer,
|
|
2020
|
-
RoleBasedRouter,
|
|
2021
|
-
useRoleBasedRouter,
|
|
2022
|
-
NavigationProvider,
|
|
2023
|
-
useNavigationPermissions,
|
|
2024
|
-
NavigationGuard,
|
|
2025
|
-
EnhancedNavigationMenu,
|
|
2026
|
-
PermissionGuard,
|
|
2027
|
-
AccessLevelGuard,
|
|
2028
|
-
withPermissionGuard,
|
|
2029
|
-
withAccessLevelGuard,
|
|
2030
|
-
withRoleGuard,
|
|
2031
|
-
createRBACMiddleware,
|
|
2032
|
-
createRBACExpressMiddleware,
|
|
2033
|
-
hasPermissionCached,
|
|
2034
|
-
hasAnyPermissionCached,
|
|
2035
|
-
GLOBAL_PERMISSIONS,
|
|
2036
|
-
ORGANISATION_PERMISSIONS,
|
|
2037
|
-
EVENT_APP_PERMISSIONS,
|
|
2038
|
-
PAGE_PERMISSIONS,
|
|
2039
|
-
isValidPermission,
|
|
2040
|
-
ALL_PERMISSIONS,
|
|
2041
|
-
isRBACInitialized,
|
|
2042
|
-
getSetupIssues,
|
|
2043
|
-
validateRBACSetup,
|
|
2044
|
-
checkRuntimeCompliance,
|
|
2045
|
-
validateAndWarn,
|
|
2046
|
-
validateDatabaseConfiguration,
|
|
2047
|
-
getCustomAuthCodeFixes,
|
|
2048
|
-
getDuplicateConfigFixes,
|
|
2049
|
-
getUnprotectedPageFixes,
|
|
2050
|
-
getDirectSupabaseAuthFixes,
|
|
2051
|
-
getQuickFixes
|
|
2052
|
-
};
|
|
2053
|
-
//# sourceMappingURL=chunk-G7QEZTYQ.js.map
|