@jmruthers/pace-core 0.6.4 → 0.6.6
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/core-usage-manifest.json +93 -0
- package/cursor-rules/00-pace-core-compliance.mdc +128 -26
- package/cursor-rules/01-standards-compliance.mdc +49 -8
- package/cursor-rules/02-project-structure.mdc +6 -0
- package/cursor-rules/03-solid-principles.mdc +2 -0
- package/cursor-rules/04-testing-standards.mdc +2 -0
- package/cursor-rules/05-bug-reports-and-features.mdc +2 -0
- package/cursor-rules/06-code-quality.mdc +2 -0
- package/cursor-rules/07-tech-stack-compliance.mdc +2 -0
- package/cursor-rules/08-markup-quality.mdc +52 -27
- package/cursor-rules/09-rbac-compliance.mdc +462 -0
- package/cursor-rules/10-error-handling-patterns.mdc +179 -0
- package/cursor-rules/11-performance-optimization.mdc +169 -0
- package/cursor-rules/12-ci-cd-integration.mdc +150 -0
- package/dist/{AuthService-Cb34EQs3.d.ts → AuthService-DmfO5rGS.d.ts} +10 -0
- package/dist/{DataTable-BMRU8a1j.d.ts → DataTable-2N_tqbfq.d.ts} +1 -1
- package/dist/DataTable-LRJL4IRV.js +15 -0
- package/dist/{PublicPageProvider-DEMpysFR.d.ts → PublicPageProvider-BBH6Vqg7.d.ts} +72 -139
- package/dist/UnifiedAuthProvider-ZT6TIGM7.js +7 -0
- package/dist/api-Y4MQWOFW.js +4 -0
- package/dist/audit-MYQXYZFU.js +3 -0
- package/dist/{chunk-J36DSWQK.js → chunk-2HGJFNAH.js} +8 -28
- package/dist/{chunk-OEWDTMG7.js → chunk-3O3WHILE.js} +38 -121
- package/dist/{chunk-M43Y4SSO.js → chunk-3QC3KRHK.js} +1 -14
- package/dist/{chunk-DGUM43GV.js → chunk-3RG5ZIWI.js} +1 -4
- package/dist/{chunk-QXHPKYJV.js → chunk-4SXLQIZO.js} +1 -26
- package/dist/chunk-4T7OBVTU.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-NN6WWZ5U.js → chunk-7TYHROIV.js} +579 -563
- package/dist/{chunk-M7MPQISP.js → chunk-A55DK444.js} +9 -16
- package/dist/{chunk-63FOKYGO.js → chunk-AHU7G2R5.js} +2 -11
- package/dist/{chunk-L4OXEN46.js → chunk-BVP2BCJF.js} +2 -16
- package/dist/chunk-C7NSAPTL.js +1 -0
- package/dist/{chunk-YKRAFF5K.js → chunk-FENMYN2U.js} +73 -149
- package/dist/{chunk-AVMLPIM7.js → chunk-FTCRZOG2.js} +284 -432
- package/dist/{chunk-G37KK66H.js → chunk-FYHN4DD5.js} +60 -19
- package/dist/{chunk-VBXEHIUJ.js → chunk-HF6O3O37.js} +6 -88
- package/dist/{chunk-I6DAQMWX.js → chunk-LAZMKTTF.js} +930 -891
- package/dist/{chunk-5EC5MEWX.js → chunk-MAGBIDNS.js} +77 -222
- package/dist/chunk-MBADTM7L.js +64 -0
- package/dist/chunk-OHIK3MIO.js +994 -0
- package/dist/{chunk-6SOIHG6Z.js → chunk-S7DKJPLT.js} +115 -44
- package/dist/{chunk-FMUCXFII.js → chunk-SD6WQY43.js} +1 -5
- package/dist/{chunk-PWLANIRT.js → chunk-TTRFSOKR.js} +1 -7
- package/dist/{chunk-5DRSZLL2.js → chunk-UH3NTO3F.js} +1 -6
- package/dist/{chunk-FFQEQTNW.js → chunk-UIYSCEV7.js} +134 -45
- package/dist/{chunk-3LPHPB62.js → chunk-ZFYPMX46.js} +271 -87
- package/dist/{chunk-7JPAB3T5.js → chunk-ZS5VO5JB.js} +1989 -1283
- package/dist/components.d.ts +6 -6
- package/dist/components.js +57 -267
- package/dist/{database.generated-CzIvgcPu.d.ts → database.generated-CcnC_DRc.d.ts} +4795 -3691
- package/dist/eslint-rules/index.cjs +22 -0
- package/dist/eslint-rules/rules/compliance.cjs +348 -0
- package/dist/eslint-rules/rules/components.cjs +113 -0
- package/dist/eslint-rules/rules/imports.cjs +102 -0
- package/dist/eslint-rules/rules/rbac.cjs +790 -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 +5 -5
- package/dist/hooks.js +62 -270
- package/dist/icons/index.d.ts +1 -0
- package/dist/icons/index.js +1 -0
- package/dist/index.d.ts +36 -26
- package/dist/index.js +87 -690
- 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 +124 -594
- package/dist/rbac/index.js +14 -207
- package/dist/styles/index.js +2 -12
- 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-B-K_5VnO.d.ts} +4 -0
- package/dist/types-t9H8qKRw.d.ts +55 -0
- package/dist/types.d.ts +1 -1
- package/dist/types.js +7 -94
- package/dist/{usePublicRouteParams-i3qtoBgg.d.ts → usePublicRouteParams-COZ28Mvq.d.ts} +9 -9
- package/dist/utils.d.ts +24 -117
- package/dist/utils.js +54 -392
- package/docs/README.md +16 -6
- package/docs/api/README.md +4 -402
- package/docs/api/modules.md +454 -930
- package/docs/api-reference/components.md +3 -1
- package/docs/api-reference/deprecated.md +31 -6
- package/docs/api-reference/rpc-functions.md +78 -3
- package/docs/best-practices/accessibility.md +6 -3
- package/docs/getting-started/cursor-rules.md +3 -23
- package/docs/getting-started/dependencies.md +650 -0
- package/docs/getting-started/installation-guide.md +20 -7
- package/docs/getting-started/quick-start.md +23 -12
- package/docs/implementation-guides/permission-enforcement.md +4 -0
- package/docs/rbac/MIGRATION_GUIDE.md +819 -0
- package/docs/rbac/RBAC_CONTRACT.md +724 -0
- package/docs/rbac/README.md +12 -3
- package/docs/rbac/edge-functions-guide.md +376 -0
- package/docs/rbac/secure-client-protection.md +0 -34
- package/docs/standards/00-pace-core-compliance.md +967 -0
- package/docs/standards/01-standards-compliance.md +188 -0
- package/docs/standards/02-project-structure.md +985 -0
- package/docs/standards/03-solid-principles.md +39 -0
- package/docs/standards/04-testing-standards.md +36 -0
- package/docs/standards/05-bug-reports-and-features.md +27 -0
- package/docs/standards/{04-code-style-standard.md → 06-code-quality.md} +2 -0
- package/docs/standards/07-tech-stack-compliance.md +30 -0
- package/docs/standards/08-markup-quality.md +345 -0
- package/docs/standards/{07-rbac-and-rls-standard.md → 09-rbac-compliance.md} +149 -54
- package/docs/standards/10-error-handling-patterns.md +401 -0
- package/docs/standards/11-performance-optimization.md +348 -0
- package/docs/standards/12-ci-cd-integration.md +370 -0
- package/docs/standards/ALIGNMENT_REVIEW_SUMMARY.md +192 -0
- package/docs/standards/README.md +62 -33
- package/docs/troubleshooting/organisation-context-setup.md +42 -19
- package/eslint-config-pace-core.cjs +20 -4
- package/package.json +31 -21
- package/scripts/audit/audit-compliance.cjs +1295 -0
- package/scripts/audit/audit-components.cjs +260 -0
- package/scripts/audit/audit-dependencies.cjs +395 -0
- package/scripts/audit/audit-rbac.cjs +954 -0
- package/scripts/audit/audit-standards.cjs +1268 -0
- package/scripts/audit/index.cjs +1898 -194
- package/scripts/install-cursor-rules.cjs +259 -8
- package/scripts/validate-master.js +1 -1
- package/src/__tests__/fixtures/supabase.ts +1 -1
- package/src/__tests__/helpers/__tests__/component-test-utils.test.tsx +1 -1
- 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-utils.test.tsx +3 -3
- package/src/__tests__/helpers/component-test-utils.tsx +1 -1
- package/src/__tests__/helpers/supabaseMock.ts +2 -2
- package/src/__tests__/public-recipe-view.test.ts +38 -9
- package/src/components/Button/Button.tsx +5 -1
- package/src/components/ContextSelector/ContextSelector.tsx +42 -39
- package/src/components/DataTable/__tests__/keyboard.test.tsx +15 -2
- package/src/components/DataTable/components/DataTableBody.tsx +55 -31
- package/src/components/DataTable/components/DataTableCore.tsx +186 -13
- package/src/components/DataTable/components/DataTableLayout.tsx +30 -5
- package/src/components/DataTable/components/EditFields.tsx +23 -3
- package/src/components/DataTable/components/EditableRow.tsx +7 -2
- package/src/components/DataTable/components/ImportModal.tsx +4 -6
- package/src/components/DataTable/components/RowComponent.tsx +12 -0
- package/src/components/DataTable/components/ViewRowModal.tsx +4 -4
- package/src/components/DataTable/components/__tests__/ImportModal.test.tsx +455 -96
- package/src/components/DataTable/components/__tests__/ViewRowModal.test.tsx +122 -58
- package/src/components/DataTable/components/hooks/usePermissionTracking.ts +0 -4
- package/src/components/DataTable/core/DataTableContext.tsx +1 -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 -0
- package/src/components/DateTimeField/DateTimeField.tsx +20 -20
- package/src/components/DateTimeField/README.md +5 -2
- package/src/components/Dialog/Dialog.test.tsx +361 -318
- package/src/components/Dialog/Dialog.tsx +1154 -323
- package/src/components/Dialog/index.ts +3 -3
- package/src/components/FileDisplay/FileDisplay.test.tsx +45 -2
- package/src/components/FileDisplay/FileDisplay.tsx +28 -22
- package/src/components/Form/Form.test.tsx +9 -10
- package/src/components/Form/Form.tsx +369 -9
- package/src/components/InactivityWarningModal/InactivityWarningModal.test.tsx +28 -28
- package/src/components/InactivityWarningModal/InactivityWarningModal.tsx +40 -54
- package/src/components/LoginForm/LoginForm.tsx +2 -2
- package/src/components/NavigationMenu/NavigationMenu.test.tsx +14 -13
- package/src/components/NavigationMenu/NavigationMenu.tsx +2 -2
- package/src/components/NavigationMenu/useNavigationFiltering.ts +11 -21
- package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +6 -4
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +30 -41
- package/src/components/PaceAppLayout/README.md +10 -9
- package/src/components/PaceAppLayout/test-setup.tsx +40 -31
- package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +108 -61
- package/src/components/PaceLoginPage/PaceLoginPage.tsx +27 -3
- package/src/components/PasswordChange/PasswordChangeForm.test.tsx +61 -0
- package/src/components/PasswordChange/PasswordChangeForm.tsx +20 -13
- package/src/components/PublicLayout/PublicLayout.test.tsx +7 -3
- package/src/components/PublicLayout/PublicPageLayout.tsx +5 -8
- package/src/components/Select/Select.tsx +23 -21
- package/src/components/Select/types.ts +1 -1
- package/src/components/UserMenu/UserMenu.test.tsx +38 -6
- package/src/components/UserMenu/UserMenu.tsx +39 -34
- package/src/components/index.ts +3 -4
- package/src/eslint-rules/index.cjs +22 -0
- package/src/eslint-rules/rules/compliance.cjs +348 -0
- package/src/eslint-rules/rules/components.cjs +113 -0
- package/src/eslint-rules/rules/imports.cjs +102 -0
- package/src/eslint-rules/rules/rbac.cjs +790 -0
- package/src/eslint-rules/utils/helpers.cjs +42 -0
- package/src/eslint-rules/utils/manifest-loader.cjs +75 -0
- package/src/hooks/__tests__/hooks.integration.test.tsx +6 -8
- package/src/hooks/__tests__/useAppConfig.unit.test.ts +129 -67
- 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 +62 -190
- package/src/hooks/public/usePublicEventLogo.test.ts +70 -17
- package/src/hooks/public/usePublicEventLogo.ts +19 -9
- package/src/hooks/useAppConfig.ts +26 -24
- package/src/hooks/useEventTheme.test.ts +211 -233
- package/src/hooks/useEventTheme.ts +19 -28
- package/src/hooks/useEvents.ts +11 -7
- package/src/hooks/useKeyboardShortcuts.ts +1 -1
- package/src/hooks/useOrganisationPermissions.ts +9 -11
- package/src/hooks/useOrganisations.ts +13 -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 +16 -1
- package/src/providers/OrganisationProvider.tsx +23 -14
- package/src/providers/services/EventServiceProvider.tsx +1 -24
- package/src/providers/services/UnifiedAuthProvider.tsx +5 -48
- package/src/providers/services/__tests__/UnifiedAuthProvider.integration.test.tsx +3 -0
- package/src/rbac/README.md +20 -20
- 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/useRBAC.test.ts +21 -3
- package/src/rbac/hooks/useRBAC.ts +4 -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 +241 -60
- package/src/rbac/hooks/useResourcePermissions.ts +182 -63
- 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/permissions.ts +17 -17
- package/src/rbac/utils/contextValidator.ts +45 -7
- package/src/services/AuthService.ts +132 -23
- package/src/services/EventService.ts +4 -97
- package/src/services/InactivityService.ts +155 -58
- 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 +4 -0
- package/src/types/database.generated.ts +4733 -3809
- 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/formatting/formatTime.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/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/CHANGELOG.md +0 -119
- package/cursor-rules/README.md +0 -192
- package/dist/DataTable-E7YQZD7D.js +0 -175
- package/dist/DataTable-E7YQZD7D.js.map +0 -1
- package/dist/UnifiedAuthProvider-QPXO24B4.js +0 -18
- package/dist/UnifiedAuthProvider-QPXO24B4.js.map +0 -1
- package/dist/api-6LVZTHDS.js +0 -52
- package/dist/api-6LVZTHDS.js.map +0 -1
- package/dist/audit-V53FV5AG.js +0 -17
- package/dist/audit-V53FV5AG.js.map +0 -1
- package/dist/chunk-36LVWXB2.js +0 -227
- package/dist/chunk-36LVWXB2.js.map +0 -1
- package/dist/chunk-3LPHPB62.js.map +0 -1
- package/dist/chunk-5DRSZLL2.js.map +0 -1
- package/dist/chunk-5EC5MEWX.js.map +0 -1
- package/dist/chunk-63FOKYGO.js.map +0 -1
- package/dist/chunk-6SOIHG6Z.js.map +0 -1
- package/dist/chunk-7JPAB3T5.js.map +0 -1
- package/dist/chunk-ATKZM7RX.js +0 -2053
- package/dist/chunk-ATKZM7RX.js.map +0 -1
- package/dist/chunk-AVMLPIM7.js.map +0 -1
- package/dist/chunk-DGUM43GV.js.map +0 -1
- package/dist/chunk-E66EQZE6.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-I6DAQMWX.js.map +0 -1
- package/dist/chunk-J36DSWQK.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-NN6WWZ5U.js.map +0 -1
- package/dist/chunk-OEWDTMG7.js.map +0 -1
- package/dist/chunk-PWLANIRT.js.map +0 -1
- package/dist/chunk-QXHPKYJV.js.map +0 -1
- package/dist/chunk-VBXEHIUJ.js.map +0 -1
- package/dist/chunk-YKRAFF5K.js.map +0 -1
- package/dist/chunk-ZSAAAMVR.js.map +0 -1
- package/dist/components.js.map +0 -1
- package/dist/contextValidator-OOPCLPZW.js +0 -9
- package/dist/contextValidator-OOPCLPZW.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/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/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/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/eslint-rules/pace-core-compliance.cjs +0 -510
- 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
|
@@ -1,33 +1,130 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
} from
|
|
6
|
-
import
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
} from
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
1
|
+
import { useAppConfig, useOrganisationSecurity } from './chunk-3O3WHILE.js';
|
|
2
|
+
import { useEventService, useUnifiedAuth, useOrganisations } from './chunk-FTCRZOG2.js';
|
|
3
|
+
import { OrganisationContextRequiredError, getRBACLogger, resolveAppContext, getPageScopeType, ContextValidator, getPermissionMap, getRoleContext, getAccessLevel, isPermittedCached, isPermitted, isSuperAdmin } from './chunk-ZFYPMX46.js';
|
|
4
|
+
import { cn, getCurrentAppName } from './chunk-A55DK444.js';
|
|
5
|
+
import { createLogger, logger } from './chunk-TTRFSOKR.js';
|
|
6
|
+
import * as React from 'react';
|
|
7
|
+
import { useRef, useMemo, useState, useCallback, useEffect } from 'react';
|
|
8
|
+
import * as TooltipPrimitive from '@radix-ui/react-tooltip';
|
|
9
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
10
|
+
import { Slot } from '@radix-ui/react-slot';
|
|
11
|
+
import { createClient } from '@supabase/supabase-js';
|
|
12
|
+
|
|
13
|
+
function useEvents() {
|
|
14
|
+
const eventService = useEventService();
|
|
15
|
+
const rawEvents = eventService.getEvents();
|
|
16
|
+
const selectedEvent = eventService.getSelectedEvent();
|
|
17
|
+
const isLoading = eventService.isLoading();
|
|
18
|
+
const error = eventService.getError();
|
|
19
|
+
const prevEventsRef = useRef([]);
|
|
20
|
+
const prevEventsIdsRef = useRef("");
|
|
21
|
+
const currentEventsIds = rawEvents.map((e) => e.event_id || e.id).join(",");
|
|
22
|
+
const eventsChanged = currentEventsIds !== prevEventsIdsRef.current;
|
|
23
|
+
if (eventsChanged) {
|
|
24
|
+
prevEventsRef.current = rawEvents;
|
|
25
|
+
prevEventsIdsRef.current = currentEventsIds;
|
|
26
|
+
}
|
|
27
|
+
const events = useMemo(() => {
|
|
28
|
+
return prevEventsRef.current;
|
|
29
|
+
}, [currentEventsIds]);
|
|
30
|
+
const setSelectedEventCallback = useMemo(
|
|
31
|
+
() => (event) => eventService.setSelectedEvent(event),
|
|
32
|
+
[eventService]
|
|
33
|
+
);
|
|
34
|
+
const refreshEventsCallback = useMemo(
|
|
35
|
+
() => () => eventService.refreshEvents(),
|
|
36
|
+
[eventService]
|
|
37
|
+
);
|
|
38
|
+
const clearEventSelectionCallback = useMemo(
|
|
39
|
+
() => () => eventService.clearEventSelection(),
|
|
40
|
+
[eventService]
|
|
41
|
+
);
|
|
42
|
+
return useMemo(() => ({
|
|
43
|
+
events,
|
|
44
|
+
selectedEvent,
|
|
45
|
+
isLoading,
|
|
46
|
+
error,
|
|
47
|
+
setSelectedEvent: setSelectedEventCallback,
|
|
48
|
+
refreshEvents: refreshEventsCallback,
|
|
49
|
+
clearEventSelection: clearEventSelectionCallback
|
|
50
|
+
}), [events, selectedEvent?.event_id, isLoading, error?.message, setSelectedEventCallback, refreshEventsCallback, clearEventSelectionCallback]);
|
|
51
|
+
}
|
|
52
|
+
var TooltipProvider = TooltipPrimitive.Provider;
|
|
53
|
+
var TooltipRoot = TooltipPrimitive.Root;
|
|
54
|
+
var TooltipTrigger = TooltipPrimitive.Trigger;
|
|
55
|
+
var TooltipContent = React.forwardRef(({ className, sideOffset = 4, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
56
|
+
TooltipPrimitive.Content,
|
|
57
|
+
{
|
|
58
|
+
ref,
|
|
59
|
+
sideOffset,
|
|
60
|
+
className: cn(
|
|
61
|
+
"z-50 overflow-hidden rounded-md border bg-main-500 px-3 py-1.5 text-sm text-main-50 shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
|
62
|
+
className
|
|
63
|
+
),
|
|
64
|
+
...props
|
|
65
|
+
}
|
|
66
|
+
));
|
|
67
|
+
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
|
|
68
|
+
var Tooltip = React.forwardRef(({ children, content, delayDuration = 200 }, ref) => /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsxs(TooltipRoot, { delayDuration, children: [
|
|
69
|
+
/* @__PURE__ */ jsx(TooltipTrigger, { ref, asChild: true, children: /* @__PURE__ */ jsx("span", { children }) }),
|
|
70
|
+
/* @__PURE__ */ jsx(TooltipContent, { children: content })
|
|
71
|
+
] }) }));
|
|
72
|
+
Tooltip.displayName = "Tooltip";
|
|
73
|
+
function getButtonClasses(variant = "default", size = "default") {
|
|
74
|
+
const baseClasses = "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50";
|
|
75
|
+
const variantClasses = {
|
|
76
|
+
default: "bg-main-600 text-main-50 shadow hover:bg-acc-400",
|
|
77
|
+
destructive: "bg-acc-600 text-acc-50 shadow-sm hover:bg-acc-400",
|
|
78
|
+
outline: "border border-main-300 bg-background shadow-sm hover:bg-acc-400",
|
|
79
|
+
secondary: "bg-sec-100 text-sec-900 shadow-sm hover:bg-acc-400",
|
|
80
|
+
ghost: "hover:bg-acc-400",
|
|
81
|
+
link: "text-main-700 underline-offset-4 hover:underline hover:drop-shadow-lg hover:drop-shadow-acc-400"
|
|
82
|
+
};
|
|
83
|
+
const sizeClasses = {
|
|
84
|
+
default: "h-9 px-4 py-2",
|
|
85
|
+
sm: "h-8 rounded-md px-3 text-xs",
|
|
86
|
+
lg: "h-10 rounded-md px-8",
|
|
87
|
+
icon: "size-8"
|
|
88
|
+
};
|
|
89
|
+
return `${baseClasses} ${variantClasses[variant]} ${sizeClasses[size]}`;
|
|
90
|
+
}
|
|
91
|
+
var Button = React.forwardRef(
|
|
92
|
+
({ className, variant, size, asChild = false, type = "button", disabled, ...props }, ref) => {
|
|
93
|
+
const Comp = asChild ? Slot : "button";
|
|
94
|
+
return /* @__PURE__ */ jsx(
|
|
95
|
+
Comp,
|
|
96
|
+
{
|
|
97
|
+
className: cn(getButtonClasses(variant, size), className),
|
|
98
|
+
ref,
|
|
99
|
+
type: !asChild ? type : void 0,
|
|
100
|
+
disabled,
|
|
101
|
+
"aria-disabled": disabled ? "true" : void 0,
|
|
102
|
+
...props
|
|
103
|
+
}
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
);
|
|
107
|
+
Button.displayName = "Button";
|
|
108
|
+
var IconButton = React.forwardRef(
|
|
109
|
+
({ icon, className, size = "icon", "aria-label": ariaLabel, tooltip, ...props }, ref) => {
|
|
110
|
+
const button = /* @__PURE__ */ jsx(
|
|
111
|
+
Button,
|
|
112
|
+
{
|
|
113
|
+
ref,
|
|
114
|
+
size,
|
|
115
|
+
className: cn("shrink-0", className),
|
|
116
|
+
"aria-label": ariaLabel,
|
|
117
|
+
...props,
|
|
118
|
+
children: icon
|
|
119
|
+
}
|
|
120
|
+
);
|
|
121
|
+
if (tooltip) {
|
|
122
|
+
return /* @__PURE__ */ jsx(Tooltip, { content: tooltip, children: button });
|
|
123
|
+
}
|
|
124
|
+
return button;
|
|
125
|
+
}
|
|
126
|
+
);
|
|
127
|
+
IconButton.displayName = "IconButton";
|
|
31
128
|
|
|
32
129
|
// src/rbac/utils/clientSecurity.ts
|
|
33
130
|
var SECURE_CLIENT_SYMBOL = Symbol("pace-core-secure-client");
|
|
@@ -61,11 +158,8 @@ See: https://github.com/jmruthers/pace-core/blob/main/packages/core/docs/rbac/ge
|
|
|
61
158
|
function markClientAsSecure(client) {
|
|
62
159
|
client[SECURE_CLIENT_SYMBOL] = true;
|
|
63
160
|
}
|
|
64
|
-
|
|
65
|
-
// src/rbac/secureClient.ts
|
|
66
|
-
import { createClient } from "@supabase/supabase-js";
|
|
67
161
|
var _SecureSupabaseClient = class _SecureSupabaseClient {
|
|
68
|
-
constructor(supabaseUrl, supabaseKey, organisationId, eventId, appId,
|
|
162
|
+
constructor(supabaseUrl, supabaseKey, organisationId, eventId, appId, isSuperAdmin2 = false, existingClient) {
|
|
69
163
|
this.edgeFunctionClient = null;
|
|
70
164
|
this.usesExistingClient = false;
|
|
71
165
|
this.supabaseUrl = supabaseUrl;
|
|
@@ -73,7 +167,7 @@ var _SecureSupabaseClient = class _SecureSupabaseClient {
|
|
|
73
167
|
this.organisationId = organisationId;
|
|
74
168
|
this.eventId = eventId;
|
|
75
169
|
this.appId = appId;
|
|
76
|
-
this.isSuperAdmin =
|
|
170
|
+
this.isSuperAdmin = isSuperAdmin2;
|
|
77
171
|
if (existingClient) {
|
|
78
172
|
this.supabase = existingClient;
|
|
79
173
|
this.usesExistingClient = true;
|
|
@@ -422,200 +516,12 @@ _SecureSupabaseClient.GLOBAL_RPC_ALLOWLIST = /* @__PURE__ */ new Set([
|
|
|
422
516
|
"data_rbac_apps_list"
|
|
423
517
|
]);
|
|
424
518
|
var SecureSupabaseClient = _SecureSupabaseClient;
|
|
425
|
-
function createSecureClient(supabaseUrl, supabaseKey, organisationId, eventId, appId,
|
|
426
|
-
return new SecureSupabaseClient(supabaseUrl, supabaseKey, organisationId, eventId, appId,
|
|
519
|
+
function createSecureClient(supabaseUrl, supabaseKey, organisationId, eventId, appId, isSuperAdmin2 = false) {
|
|
520
|
+
return new SecureSupabaseClient(supabaseUrl, supabaseKey, organisationId, eventId, appId, isSuperAdmin2);
|
|
427
521
|
}
|
|
428
|
-
function fromSupabaseClient(client, organisationId, eventId, appId,
|
|
429
|
-
return new SecureSupabaseClient("", "", organisationId, eventId, appId,
|
|
522
|
+
function fromSupabaseClient(client, organisationId, eventId, appId, isSuperAdmin2 = false) {
|
|
523
|
+
return new SecureSupabaseClient("", "", organisationId, eventId, appId, isSuperAdmin2, client);
|
|
430
524
|
}
|
|
431
|
-
|
|
432
|
-
// src/rbac/hooks/useResolvedScope.ts
|
|
433
|
-
import { useEffect, useState, useRef, useMemo } from "react";
|
|
434
|
-
var log = createLogger("useResolvedScope");
|
|
435
|
-
var appIdCache = /* @__PURE__ */ new Map();
|
|
436
|
-
var CACHE_TTL = 5 * 60 * 1e3;
|
|
437
|
-
function useResolvedScope({
|
|
438
|
-
supabase,
|
|
439
|
-
selectedOrganisationId,
|
|
440
|
-
selectedEventId
|
|
441
|
-
}) {
|
|
442
|
-
const [resolvedScope, setResolvedScope] = useState(null);
|
|
443
|
-
const [isLoading, setIsLoading] = useState(true);
|
|
444
|
-
const [error, setError] = useState(null);
|
|
445
|
-
const stableScopeRef = useRef({
|
|
446
|
-
organisationId: "",
|
|
447
|
-
appId: "",
|
|
448
|
-
eventId: void 0
|
|
449
|
-
});
|
|
450
|
-
useEffect(() => {
|
|
451
|
-
if (resolvedScope) {
|
|
452
|
-
const newScope = {
|
|
453
|
-
organisationId: resolvedScope.organisationId || "",
|
|
454
|
-
appId: resolvedScope.appId || "",
|
|
455
|
-
eventId: resolvedScope.eventId
|
|
456
|
-
};
|
|
457
|
-
if (stableScopeRef.current.organisationId !== newScope.organisationId || stableScopeRef.current.eventId !== newScope.eventId || stableScopeRef.current.appId !== newScope.appId) {
|
|
458
|
-
stableScopeRef.current = {
|
|
459
|
-
organisationId: newScope.organisationId,
|
|
460
|
-
appId: newScope.appId,
|
|
461
|
-
eventId: newScope.eventId
|
|
462
|
-
};
|
|
463
|
-
}
|
|
464
|
-
} else {
|
|
465
|
-
stableScopeRef.current = { organisationId: "", appId: "", eventId: void 0 };
|
|
466
|
-
}
|
|
467
|
-
}, [resolvedScope]);
|
|
468
|
-
const appName = getCurrentAppName();
|
|
469
|
-
const stableScope = stableScopeRef.current;
|
|
470
|
-
useEffect(() => {
|
|
471
|
-
let cancelled = false;
|
|
472
|
-
const resolveScope = async () => {
|
|
473
|
-
if (!supabase && !selectedOrganisationId && !selectedEventId) {
|
|
474
|
-
if (!cancelled) {
|
|
475
|
-
setResolvedScope(null);
|
|
476
|
-
setIsLoading(false);
|
|
477
|
-
setError(null);
|
|
478
|
-
}
|
|
479
|
-
return;
|
|
480
|
-
}
|
|
481
|
-
setIsLoading(true);
|
|
482
|
-
setError(null);
|
|
483
|
-
try {
|
|
484
|
-
const appName2 = getCurrentAppName();
|
|
485
|
-
let appId = void 0;
|
|
486
|
-
if (supabase && appName2) {
|
|
487
|
-
try {
|
|
488
|
-
const { data: session } = await supabase.auth.getSession();
|
|
489
|
-
if (!session?.session) {
|
|
490
|
-
log.debug(`Skipping app resolution for "${appName2}" - user not authenticated`);
|
|
491
|
-
} else {
|
|
492
|
-
const cached = appIdCache.get(appName2);
|
|
493
|
-
const now = Date.now();
|
|
494
|
-
if (cached && now - cached.timestamp < CACHE_TTL) {
|
|
495
|
-
appId = cached.appId;
|
|
496
|
-
} else {
|
|
497
|
-
const { data: app, error: error2 } = await supabase.from("rbac_apps").select("id, name, is_active").eq("name", appName2).eq("is_active", true).single();
|
|
498
|
-
if (error2) {
|
|
499
|
-
if (error2.code === "406" || error2.code === "PGRST116" || error2.message?.includes("406")) {
|
|
500
|
-
log.debug(`App resolution blocked by RLS for "${appName2}" - user may not be authenticated`);
|
|
501
|
-
appId = void 0;
|
|
502
|
-
} else {
|
|
503
|
-
const { data: inactiveApp } = await supabase.from("rbac_apps").select("id, name, is_active").eq("name", appName2).single();
|
|
504
|
-
if (inactiveApp) {
|
|
505
|
-
log.error(`App "${appName2}" exists but is inactive (is_active: ${inactiveApp.is_active})`);
|
|
506
|
-
appId = void 0;
|
|
507
|
-
} else {
|
|
508
|
-
log.error(`App "${appName2}" not found in rbac_apps table`, { error: error2 });
|
|
509
|
-
appId = void 0;
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
} else if (app) {
|
|
513
|
-
appId = app.id;
|
|
514
|
-
appIdCache.set(appName2, { appId, timestamp: now });
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
} catch (error2) {
|
|
519
|
-
const errorMessage = error2 instanceof Error ? error2.message : String(error2);
|
|
520
|
-
if (!errorMessage.includes("406") && !errorMessage.includes("PGRST116")) {
|
|
521
|
-
log.error("Unexpected error resolving app ID:", error2);
|
|
522
|
-
} else {
|
|
523
|
-
log.debug("App resolution skipped - authentication required");
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
const initialScope = {
|
|
528
|
-
organisationId: selectedOrganisationId || void 0,
|
|
529
|
-
eventId: selectedEventId || void 0,
|
|
530
|
-
appId
|
|
531
|
-
};
|
|
532
|
-
if (appName2 === "PORTAL" || appName2 === "ADMIN") {
|
|
533
|
-
if (!cancelled) {
|
|
534
|
-
const optionalContextScope = {
|
|
535
|
-
organisationId: void 0,
|
|
536
|
-
eventId: void 0,
|
|
537
|
-
appId: appId || void 0
|
|
538
|
-
};
|
|
539
|
-
setResolvedScope(optionalContextScope);
|
|
540
|
-
setError(null);
|
|
541
|
-
setIsLoading(false);
|
|
542
|
-
}
|
|
543
|
-
return;
|
|
544
|
-
}
|
|
545
|
-
const { ContextValidator: ContextValidator2 } = await import("./contextValidator-OOPCLPZW.js");
|
|
546
|
-
const validation = await ContextValidator2.resolveScopeForPage(
|
|
547
|
-
initialScope,
|
|
548
|
-
"organisation",
|
|
549
|
-
// Default to organisation scope when no page context
|
|
550
|
-
appName2 || void 0,
|
|
551
|
-
supabase
|
|
552
|
-
);
|
|
553
|
-
if (!validation.isValid) {
|
|
554
|
-
if (selectedEventId) {
|
|
555
|
-
if (!cancelled) {
|
|
556
|
-
const eventScope = {
|
|
557
|
-
organisationId: void 0,
|
|
558
|
-
// Will be derived from event during permission check
|
|
559
|
-
eventId: selectedEventId,
|
|
560
|
-
appId: appId || void 0
|
|
561
|
-
};
|
|
562
|
-
setResolvedScope(eventScope);
|
|
563
|
-
setError(null);
|
|
564
|
-
setIsLoading(false);
|
|
565
|
-
}
|
|
566
|
-
return;
|
|
567
|
-
}
|
|
568
|
-
if (!cancelled) {
|
|
569
|
-
setResolvedScope(null);
|
|
570
|
-
setError(validation.error || new Error("Context validation failed"));
|
|
571
|
-
setIsLoading(false);
|
|
572
|
-
}
|
|
573
|
-
return;
|
|
574
|
-
}
|
|
575
|
-
if (!cancelled) {
|
|
576
|
-
setResolvedScope(validation.resolvedScope);
|
|
577
|
-
setError(null);
|
|
578
|
-
setIsLoading(false);
|
|
579
|
-
}
|
|
580
|
-
} catch (err) {
|
|
581
|
-
if (!cancelled) {
|
|
582
|
-
setError(err);
|
|
583
|
-
setIsLoading(false);
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
};
|
|
587
|
-
resolveScope();
|
|
588
|
-
return () => {
|
|
589
|
-
cancelled = true;
|
|
590
|
-
};
|
|
591
|
-
}, [selectedOrganisationId, selectedEventId, supabase]);
|
|
592
|
-
const allowsOptionalContexts = appName === "PORTAL" || appName === "ADMIN";
|
|
593
|
-
const hasValidScope = allowsOptionalContexts ? true : stableScope.appId || stableScope.organisationId;
|
|
594
|
-
const finalScope = useMemo(() => {
|
|
595
|
-
if (!hasValidScope) {
|
|
596
|
-
return allowsOptionalContexts ? {} : null;
|
|
597
|
-
}
|
|
598
|
-
const scope = {};
|
|
599
|
-
if (stableScope.organisationId) {
|
|
600
|
-
scope.organisationId = stableScope.organisationId;
|
|
601
|
-
}
|
|
602
|
-
if (stableScope.eventId) {
|
|
603
|
-
scope.eventId = stableScope.eventId;
|
|
604
|
-
}
|
|
605
|
-
if (stableScope.appId) {
|
|
606
|
-
scope.appId = stableScope.appId;
|
|
607
|
-
}
|
|
608
|
-
return scope;
|
|
609
|
-
}, [hasValidScope, allowsOptionalContexts, stableScope.organisationId, stableScope.eventId, stableScope.appId]);
|
|
610
|
-
return {
|
|
611
|
-
resolvedScope: finalScope,
|
|
612
|
-
isLoading,
|
|
613
|
-
error
|
|
614
|
-
};
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
// src/rbac/hooks/useRBAC.ts
|
|
618
|
-
import { useState as useState2, useEffect as useEffect2, useCallback, useMemo as useMemo2 } from "react";
|
|
619
525
|
function mapAccessLevelToEventRole(level) {
|
|
620
526
|
switch (level) {
|
|
621
527
|
case "viewer":
|
|
@@ -645,13 +551,13 @@ function useRBAC(pageId) {
|
|
|
645
551
|
selectedEvent,
|
|
646
552
|
eventLoading
|
|
647
553
|
} = useUnifiedAuth();
|
|
648
|
-
const [globalRole, setGlobalRole] =
|
|
649
|
-
const [organisationRole, setOrganisationRole] =
|
|
650
|
-
const [eventAppRole, setEventAppRole] =
|
|
651
|
-
const [permissionMap, setPermissionMap] =
|
|
652
|
-
const [currentScope, setCurrentScope] =
|
|
653
|
-
const [isLoading, setIsLoading] =
|
|
654
|
-
const [error, setError] =
|
|
554
|
+
const [globalRole, setGlobalRole] = useState(null);
|
|
555
|
+
const [organisationRole, setOrganisationRole] = useState(null);
|
|
556
|
+
const [eventAppRole, setEventAppRole] = useState(null);
|
|
557
|
+
const [permissionMap, setPermissionMap] = useState({});
|
|
558
|
+
const [currentScope, setCurrentScope] = useState(null);
|
|
559
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
560
|
+
const [error, setError] = useState(null);
|
|
655
561
|
const resetState = useCallback(() => {
|
|
656
562
|
setGlobalRole(null);
|
|
657
563
|
setOrganisationRole(null);
|
|
@@ -737,9 +643,9 @@ function useRBAC(pageId) {
|
|
|
737
643
|
const resolvedScope = validation.resolvedScope;
|
|
738
644
|
setCurrentScope(resolvedScope);
|
|
739
645
|
const [map, roleContext, accessLevel] = await Promise.all([
|
|
740
|
-
getPermissionMap({ userId: user.id, scope: resolvedScope }),
|
|
741
|
-
getRoleContext({ userId: user.id, scope: resolvedScope }),
|
|
742
|
-
getAccessLevel({ userId: user.id, scope: resolvedScope })
|
|
646
|
+
getPermissionMap({ userId: user.id, scope: resolvedScope }, appName),
|
|
647
|
+
getRoleContext({ userId: user.id, scope: resolvedScope }, appName),
|
|
648
|
+
getAccessLevel({ userId: user.id, scope: resolvedScope }, appName)
|
|
743
649
|
]);
|
|
744
650
|
setPermissionMap(map);
|
|
745
651
|
setGlobalRole(roleContext.globalRole);
|
|
@@ -777,12 +683,12 @@ function useRBAC(pageId) {
|
|
|
777
683
|
},
|
|
778
684
|
[globalRole, organisationRole, permissionMap]
|
|
779
685
|
);
|
|
780
|
-
const
|
|
781
|
-
const isOrgAdmin =
|
|
782
|
-
const isEventAdmin =
|
|
783
|
-
const canManageOrganisation =
|
|
784
|
-
const canManageEvent =
|
|
785
|
-
|
|
686
|
+
const isSuperAdmin2 = useMemo(() => globalRole === "super_admin" || permissionMap["*"] === true, [globalRole, permissionMap]);
|
|
687
|
+
const isOrgAdmin = useMemo(() => organisationRole === "org_admin" || isSuperAdmin2, [organisationRole, isSuperAdmin2]);
|
|
688
|
+
const isEventAdmin = useMemo(() => eventAppRole === "event_admin" || isSuperAdmin2, [eventAppRole, isSuperAdmin2]);
|
|
689
|
+
const canManageOrganisation = useMemo(() => isSuperAdmin2 || organisationRole === "org_admin", [isSuperAdmin2, organisationRole]);
|
|
690
|
+
const canManageEvent = useMemo(() => isSuperAdmin2 || eventAppRole === "event_admin", [isSuperAdmin2, eventAppRole]);
|
|
691
|
+
useEffect(() => {
|
|
786
692
|
loadRBACContext();
|
|
787
693
|
}, [loadRBACContext, appName, eventLoading, selectedEvent?.event_id, user, session, selectedOrganisation?.id, orgContextReady, orgLoading]);
|
|
788
694
|
return {
|
|
@@ -791,7 +697,7 @@ function useRBAC(pageId) {
|
|
|
791
697
|
organisationRole,
|
|
792
698
|
eventAppRole,
|
|
793
699
|
hasGlobalPermission,
|
|
794
|
-
isSuperAdmin,
|
|
700
|
+
isSuperAdmin: isSuperAdmin2,
|
|
795
701
|
isOrgAdmin,
|
|
796
702
|
isEventAdmin,
|
|
797
703
|
canManageOrganisation,
|
|
@@ -800,20 +706,131 @@ function useRBAC(pageId) {
|
|
|
800
706
|
error
|
|
801
707
|
};
|
|
802
708
|
}
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
709
|
+
var log = createLogger("useResolvedScope");
|
|
710
|
+
var appIdCache = /* @__PURE__ */ new Map();
|
|
711
|
+
var CACHE_TTL = 5 * 60 * 1e3;
|
|
712
|
+
function useResolvedScope({
|
|
713
|
+
supabase,
|
|
714
|
+
selectedOrganisationId,
|
|
715
|
+
selectedEventId,
|
|
716
|
+
selectedEventOrganisationId
|
|
717
|
+
}) {
|
|
718
|
+
const immediateOrganisationId = selectedEventOrganisationId || selectedOrganisationId || void 0;
|
|
719
|
+
const immediateEventId = selectedEventId || void 0;
|
|
720
|
+
const [appId, setAppId] = useState(void 0);
|
|
721
|
+
const [isResolvingAppId, setIsResolvingAppId] = useState(false);
|
|
722
|
+
const [error, setError] = useState(null);
|
|
723
|
+
const appName = getCurrentAppName();
|
|
724
|
+
useEffect(() => {
|
|
725
|
+
let cancelled = false;
|
|
726
|
+
const resolveAppId = async () => {
|
|
727
|
+
if (!supabase && !selectedOrganisationId && !selectedEventId) {
|
|
728
|
+
if (!cancelled) {
|
|
729
|
+
setAppId(void 0);
|
|
730
|
+
setIsResolvingAppId(false);
|
|
731
|
+
setError(null);
|
|
732
|
+
}
|
|
733
|
+
return;
|
|
734
|
+
}
|
|
735
|
+
setIsResolvingAppId(true);
|
|
736
|
+
setError(null);
|
|
737
|
+
try {
|
|
738
|
+
const appName2 = getCurrentAppName();
|
|
739
|
+
let resolvedAppId = void 0;
|
|
740
|
+
if (supabase && appName2) {
|
|
741
|
+
try {
|
|
742
|
+
const { data: session } = await supabase.auth.getSession();
|
|
743
|
+
if (!session?.session) {
|
|
744
|
+
log.debug(`Skipping app resolution for "${appName2}" - user not authenticated`);
|
|
745
|
+
} else {
|
|
746
|
+
const cached = appIdCache.get(appName2);
|
|
747
|
+
const now = Date.now();
|
|
748
|
+
if (cached && now - cached.timestamp < CACHE_TTL) {
|
|
749
|
+
resolvedAppId = cached.appId;
|
|
750
|
+
} else {
|
|
751
|
+
const { data: app, error: error2 } = await supabase.from("rbac_apps").select("id, name, is_active").eq("name", appName2).eq("is_active", true).single();
|
|
752
|
+
if (error2) {
|
|
753
|
+
if (error2.code === "406" || error2.code === "PGRST116" || error2.message?.includes("406")) {
|
|
754
|
+
log.debug(`App resolution blocked by RLS for "${appName2}" - user may not be authenticated`);
|
|
755
|
+
resolvedAppId = void 0;
|
|
756
|
+
} else {
|
|
757
|
+
const { data: inactiveApp } = await supabase.from("rbac_apps").select("id, name, is_active").eq("name", appName2).single();
|
|
758
|
+
if (inactiveApp) {
|
|
759
|
+
log.error(`App "${appName2}" exists but is inactive (is_active: ${inactiveApp.is_active})`);
|
|
760
|
+
resolvedAppId = void 0;
|
|
761
|
+
} else {
|
|
762
|
+
log.error(`App "${appName2}" not found in rbac_apps table`, { error: error2 });
|
|
763
|
+
resolvedAppId = void 0;
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
} else if (app) {
|
|
767
|
+
resolvedAppId = app.id;
|
|
768
|
+
appIdCache.set(appName2, { appId: resolvedAppId, timestamp: now });
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
} catch (error2) {
|
|
773
|
+
const errorMessage = error2 instanceof Error ? error2.message : String(error2);
|
|
774
|
+
if (!errorMessage.includes("406") && !errorMessage.includes("PGRST116")) {
|
|
775
|
+
log.error("Unexpected error resolving app ID:", error2);
|
|
776
|
+
} else {
|
|
777
|
+
log.debug("App resolution skipped - authentication required");
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
if (!cancelled) {
|
|
782
|
+
setAppId(resolvedAppId);
|
|
783
|
+
setIsResolvingAppId(false);
|
|
784
|
+
setError(null);
|
|
785
|
+
}
|
|
786
|
+
} catch (err) {
|
|
787
|
+
if (!cancelled) {
|
|
788
|
+
setError(err);
|
|
789
|
+
setIsResolvingAppId(false);
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
};
|
|
793
|
+
resolveAppId();
|
|
794
|
+
return () => {
|
|
795
|
+
cancelled = true;
|
|
796
|
+
};
|
|
797
|
+
}, [supabase, selectedOrganisationId, selectedEventId]);
|
|
798
|
+
const immediateScope = useMemo(() => {
|
|
799
|
+
if (appName === "PORTAL" || appName === "ADMIN") {
|
|
800
|
+
return {
|
|
801
|
+
organisationId: void 0,
|
|
802
|
+
eventId: void 0,
|
|
803
|
+
appId: appId || void 0
|
|
804
|
+
};
|
|
805
|
+
}
|
|
806
|
+
const scope = {};
|
|
807
|
+
if (immediateOrganisationId) {
|
|
808
|
+
scope.organisationId = immediateOrganisationId;
|
|
809
|
+
}
|
|
810
|
+
if (immediateEventId) {
|
|
811
|
+
scope.eventId = immediateEventId;
|
|
812
|
+
}
|
|
813
|
+
if (appId) {
|
|
814
|
+
scope.appId = appId;
|
|
815
|
+
}
|
|
816
|
+
if (!scope.organisationId && !scope.appId) {
|
|
817
|
+
return null;
|
|
818
|
+
}
|
|
819
|
+
return scope;
|
|
820
|
+
}, [immediateOrganisationId, immediateEventId, appId, appName]);
|
|
821
|
+
return {
|
|
822
|
+
resolvedScope: immediateScope,
|
|
823
|
+
isLoading: isResolvingAppId,
|
|
824
|
+
// Only true while appId resolves
|
|
825
|
+
error
|
|
826
|
+
};
|
|
827
|
+
}
|
|
806
828
|
function useAccessLevel(userId, scope) {
|
|
807
|
-
const [accessLevel, setAccessLevel] =
|
|
808
|
-
const [isLoading, setIsLoading] =
|
|
809
|
-
const [error, setError] =
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
const { appName: contextAppName } = useAppConfig();
|
|
813
|
-
appName = contextAppName;
|
|
814
|
-
} catch {
|
|
815
|
-
}
|
|
816
|
-
const fetchAccessLevel = useCallback2(async () => {
|
|
829
|
+
const [accessLevel, setAccessLevel] = useState("viewer");
|
|
830
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
831
|
+
const [error, setError] = useState(null);
|
|
832
|
+
const { appName } = useAppConfig();
|
|
833
|
+
const fetchAccessLevel = useCallback(async () => {
|
|
817
834
|
if (!userId) {
|
|
818
835
|
setAccessLevel("viewer");
|
|
819
836
|
setIsLoading(false);
|
|
@@ -822,7 +839,7 @@ function useAccessLevel(userId, scope) {
|
|
|
822
839
|
try {
|
|
823
840
|
setIsLoading(true);
|
|
824
841
|
setError(null);
|
|
825
|
-
const { isSuperAdmin: checkSuperAdmin } = await import(
|
|
842
|
+
const { isSuperAdmin: checkSuperAdmin } = await import('./api-Y4MQWOFW.js');
|
|
826
843
|
const isSuperAdminUser = await checkSuperAdmin(userId);
|
|
827
844
|
if (isSuperAdminUser) {
|
|
828
845
|
setAccessLevel("super");
|
|
@@ -846,10 +863,10 @@ function useAccessLevel(userId, scope) {
|
|
|
846
863
|
setIsLoading(false);
|
|
847
864
|
}
|
|
848
865
|
}, [userId, scope.organisationId, scope.eventId, scope.appId, appName]);
|
|
849
|
-
|
|
866
|
+
useEffect(() => {
|
|
850
867
|
fetchAccessLevel();
|
|
851
868
|
}, [fetchAccessLevel]);
|
|
852
|
-
return
|
|
869
|
+
return useMemo(() => ({
|
|
853
870
|
accessLevel,
|
|
854
871
|
isLoading,
|
|
855
872
|
error,
|
|
@@ -857,47 +874,6 @@ function useAccessLevel(userId, scope) {
|
|
|
857
874
|
}), [accessLevel, isLoading, error, fetchAccessLevel]);
|
|
858
875
|
}
|
|
859
876
|
|
|
860
|
-
// src/rbac/hooks/permissions/useCachedPermissions.ts
|
|
861
|
-
import { useCallback as useCallback3, useEffect as useEffect4, useMemo as useMemo4, useState as useState4 } from "react";
|
|
862
|
-
function useCachedPermissions(userId, scope) {
|
|
863
|
-
const [permissions, setPermissions] = useState4({});
|
|
864
|
-
const [isLoading, setIsLoading] = useState4(true);
|
|
865
|
-
const [error, setError] = useState4(null);
|
|
866
|
-
const fetchCachedPermissions = useCallback3(async () => {
|
|
867
|
-
if (!userId) {
|
|
868
|
-
setPermissions({});
|
|
869
|
-
setIsLoading(false);
|
|
870
|
-
return;
|
|
871
|
-
}
|
|
872
|
-
try {
|
|
873
|
-
setIsLoading(true);
|
|
874
|
-
setError(null);
|
|
875
|
-
const permissionMap = await getPermissionMap({ userId, scope });
|
|
876
|
-
setPermissions(permissionMap);
|
|
877
|
-
} catch (err) {
|
|
878
|
-
setError(err instanceof Error ? err : new Error("Failed to fetch cached permissions"));
|
|
879
|
-
} finally {
|
|
880
|
-
setIsLoading(false);
|
|
881
|
-
}
|
|
882
|
-
}, [userId, scope.organisationId, scope.eventId, scope.appId]);
|
|
883
|
-
const invalidateCache = useCallback3(() => {
|
|
884
|
-
fetchCachedPermissions();
|
|
885
|
-
}, [fetchCachedPermissions]);
|
|
886
|
-
useEffect4(() => {
|
|
887
|
-
fetchCachedPermissions();
|
|
888
|
-
}, [fetchCachedPermissions]);
|
|
889
|
-
return useMemo4(() => ({
|
|
890
|
-
permissions,
|
|
891
|
-
isLoading,
|
|
892
|
-
error,
|
|
893
|
-
invalidateCache,
|
|
894
|
-
refetch: fetchCachedPermissions
|
|
895
|
-
}), [permissions, isLoading, error, invalidateCache, fetchCachedPermissions]);
|
|
896
|
-
}
|
|
897
|
-
|
|
898
|
-
// src/rbac/hooks/permissions/useCan.ts
|
|
899
|
-
import { useCallback as useCallback4, useEffect as useEffect5, useMemo as useMemo5, useRef as useRef2, useState as useState5 } from "react";
|
|
900
|
-
|
|
901
877
|
// src/rbac/utils/deep-equal.ts
|
|
902
878
|
function scopeEqual(a, b) {
|
|
903
879
|
if (a === b) {
|
|
@@ -911,27 +887,27 @@ function scopeEqual(a, b) {
|
|
|
911
887
|
|
|
912
888
|
// src/rbac/hooks/permissions/useCan.ts
|
|
913
889
|
function useCan(userId, scope, permission, pageId, useCache = true, precomputedSuperAdmin = null, appName) {
|
|
914
|
-
const [
|
|
890
|
+
const [isSuperAdmin2, setIsSuperAdmin] = useState(precomputedSuperAdmin ?? null);
|
|
915
891
|
const initialCan = precomputedSuperAdmin === true ? true : false;
|
|
916
892
|
const initialIsLoading = precomputedSuperAdmin === true ? false : true;
|
|
917
|
-
const [can, setCan] =
|
|
918
|
-
const [isLoading, setIsLoading] =
|
|
919
|
-
const [error, setError] =
|
|
893
|
+
const [can, setCan] = useState(initialCan);
|
|
894
|
+
const [isLoading, setIsLoading] = useState(initialIsLoading);
|
|
895
|
+
const [error, setError] = useState(null);
|
|
920
896
|
const isValidScope = scope && typeof scope === "object";
|
|
921
897
|
const organisationId = isValidScope ? scope.organisationId : void 0;
|
|
922
898
|
const eventId = isValidScope ? scope.eventId : void 0;
|
|
923
899
|
const appId = isValidScope ? scope.appId : void 0;
|
|
924
|
-
|
|
925
|
-
if (precomputedSuperAdmin === true &&
|
|
900
|
+
useEffect(() => {
|
|
901
|
+
if (precomputedSuperAdmin === true && isSuperAdmin2 !== true) {
|
|
926
902
|
setIsSuperAdmin(true);
|
|
927
903
|
setCan(true);
|
|
928
904
|
setIsLoading(false);
|
|
929
905
|
setError(null);
|
|
930
|
-
} else if (precomputedSuperAdmin === false &&
|
|
906
|
+
} else if (precomputedSuperAdmin === false && isSuperAdmin2 !== false) {
|
|
931
907
|
setIsSuperAdmin(false);
|
|
932
908
|
}
|
|
933
|
-
}, [precomputedSuperAdmin,
|
|
934
|
-
|
|
909
|
+
}, [precomputedSuperAdmin, isSuperAdmin2]);
|
|
910
|
+
useEffect(() => {
|
|
935
911
|
if (precomputedSuperAdmin === null) {
|
|
936
912
|
if (!userId) {
|
|
937
913
|
setIsSuperAdmin(false);
|
|
@@ -941,7 +917,7 @@ function useCan(userId, scope, permission, pageId, useCache = true, precomputedS
|
|
|
941
917
|
const checkSuperAdmin = async () => {
|
|
942
918
|
const startTime = Date.now();
|
|
943
919
|
try {
|
|
944
|
-
const { isSuperAdmin: checkSuperAdmin2 } = await import(
|
|
920
|
+
const { isSuperAdmin: checkSuperAdmin2 } = await import('./api-Y4MQWOFW.js');
|
|
945
921
|
const timeoutWarning = setTimeout(() => {
|
|
946
922
|
if (!cancelled) {
|
|
947
923
|
console.warn("[useCan] Super admin check taking longer than 5 seconds", {
|
|
@@ -985,10 +961,10 @@ function useCan(userId, scope, permission, pageId, useCache = true, precomputedS
|
|
|
985
961
|
};
|
|
986
962
|
}
|
|
987
963
|
}, [userId, precomputedSuperAdmin]);
|
|
988
|
-
|
|
964
|
+
useEffect(() => {
|
|
989
965
|
const isPagePermission = permission.includes(":page.") || !!pageId;
|
|
990
966
|
const requiresOrgId = !isPagePermission;
|
|
991
|
-
if (
|
|
967
|
+
if (isSuperAdmin2 === true) {
|
|
992
968
|
return;
|
|
993
969
|
}
|
|
994
970
|
if (requiresOrgId && (!isValidScope || !organisationId || organisationId === null || typeof organisationId === "string" && organisationId.trim() === "")) {
|
|
@@ -1002,14 +978,14 @@ function useCan(userId, scope, permission, pageId, useCache = true, precomputedS
|
|
|
1002
978
|
if (error?.message === "Organisation context is required for permission checks") {
|
|
1003
979
|
setError(null);
|
|
1004
980
|
}
|
|
1005
|
-
}, [isValidScope, organisationId, error, permission, pageId,
|
|
1006
|
-
const lastUserIdRef =
|
|
1007
|
-
|
|
1008
|
-
const lastPermissionRef =
|
|
1009
|
-
const lastPageIdRef =
|
|
1010
|
-
const lastUseCacheRef =
|
|
1011
|
-
const lastIsSuperAdminRef =
|
|
1012
|
-
const stableScope =
|
|
981
|
+
}, [isValidScope, organisationId, error, permission, pageId, isSuperAdmin2]);
|
|
982
|
+
const lastUserIdRef = useRef(null);
|
|
983
|
+
useRef(null);
|
|
984
|
+
const lastPermissionRef = useRef(null);
|
|
985
|
+
const lastPageIdRef = useRef(null);
|
|
986
|
+
const lastUseCacheRef = useRef(null);
|
|
987
|
+
const lastIsSuperAdminRef = useRef(null);
|
|
988
|
+
const stableScope = useMemo(() => {
|
|
1013
989
|
if (!isValidScope) {
|
|
1014
990
|
return null;
|
|
1015
991
|
}
|
|
@@ -1019,12 +995,12 @@ function useCan(userId, scope, permission, pageId, useCache = true, precomputedS
|
|
|
1019
995
|
appId
|
|
1020
996
|
};
|
|
1021
997
|
}, [isValidScope, organisationId, eventId, appId]);
|
|
1022
|
-
const prevScopeRef =
|
|
1023
|
-
|
|
998
|
+
const prevScopeRef = useRef(null);
|
|
999
|
+
useEffect(() => {
|
|
1024
1000
|
const scopeChanged = !scopeEqual(prevScopeRef.current, stableScope);
|
|
1025
|
-
const isSuperAdminChanged = lastIsSuperAdminRef.current !==
|
|
1001
|
+
const isSuperAdminChanged = lastIsSuperAdminRef.current !== isSuperAdmin2;
|
|
1026
1002
|
if (lastUserIdRef.current !== userId || scopeChanged || lastPermissionRef.current !== permission || lastPageIdRef.current !== pageId || lastUseCacheRef.current !== useCache || isSuperAdminChanged) {
|
|
1027
|
-
lastIsSuperAdminRef.current =
|
|
1003
|
+
lastIsSuperAdminRef.current = isSuperAdmin2;
|
|
1028
1004
|
lastUserIdRef.current = userId;
|
|
1029
1005
|
prevScopeRef.current = stableScope;
|
|
1030
1006
|
lastPermissionRef.current = permission;
|
|
@@ -1036,13 +1012,13 @@ function useCan(userId, scope, permission, pageId, useCache = true, precomputedS
|
|
|
1036
1012
|
setIsLoading(false);
|
|
1037
1013
|
return;
|
|
1038
1014
|
}
|
|
1039
|
-
if (
|
|
1015
|
+
if (isSuperAdmin2 === true) {
|
|
1040
1016
|
setCan(true);
|
|
1041
1017
|
setIsLoading(false);
|
|
1042
1018
|
setError(null);
|
|
1043
1019
|
return;
|
|
1044
1020
|
}
|
|
1045
|
-
if (
|
|
1021
|
+
if (isSuperAdmin2 === null) {
|
|
1046
1022
|
setIsLoading(true);
|
|
1047
1023
|
setCan(false);
|
|
1048
1024
|
setError(null);
|
|
@@ -1078,7 +1054,7 @@ function useCan(userId, scope, permission, pageId, useCache = true, precomputedS
|
|
|
1078
1054
|
...eventId ? { eventId } : {},
|
|
1079
1055
|
...appId ? { appId } : {}
|
|
1080
1056
|
};
|
|
1081
|
-
const result = useCache ? await isPermittedCached({ userId, scope: validScope, permission, pageId }, appName) : await isPermitted({ userId, scope: validScope, permission, pageId }, appName,
|
|
1057
|
+
const result = useCache ? await isPermittedCached({ userId, scope: validScope, permission, pageId }, appName) : await isPermitted({ userId, scope: validScope, permission, pageId }, appName, isSuperAdmin2 === false ? false : null);
|
|
1082
1058
|
setCan(result);
|
|
1083
1059
|
} catch (err) {
|
|
1084
1060
|
const logger2 = getRBACLogger();
|
|
@@ -1092,8 +1068,8 @@ function useCan(userId, scope, permission, pageId, useCache = true, precomputedS
|
|
|
1092
1068
|
};
|
|
1093
1069
|
checkPermission();
|
|
1094
1070
|
}
|
|
1095
|
-
}, [userId, stableScope, permission, pageId, useCache, appName,
|
|
1096
|
-
const refetch =
|
|
1071
|
+
}, [userId, stableScope, permission, pageId, useCache, appName, isSuperAdmin2]);
|
|
1072
|
+
const refetch = useCallback(async () => {
|
|
1097
1073
|
if (!userId) {
|
|
1098
1074
|
setCan(false);
|
|
1099
1075
|
setIsLoading(false);
|
|
@@ -1130,105 +1106,18 @@ function useCan(userId, scope, permission, pageId, useCache = true, precomputedS
|
|
|
1130
1106
|
setIsLoading(false);
|
|
1131
1107
|
}
|
|
1132
1108
|
}, [userId, isValidScope, organisationId, eventId, appId, permission, pageId, useCache, appName]);
|
|
1133
|
-
return
|
|
1109
|
+
return useMemo(() => ({
|
|
1134
1110
|
can,
|
|
1135
1111
|
isLoading,
|
|
1136
1112
|
error,
|
|
1137
1113
|
refetch
|
|
1138
1114
|
}), [can, isLoading, error, refetch]);
|
|
1139
1115
|
}
|
|
1140
|
-
|
|
1141
|
-
// src/rbac/hooks/permissions/useHasAllPermissions.ts
|
|
1142
|
-
import { useCallback as useCallback5, useEffect as useEffect6, useMemo as useMemo6, useState as useState6 } from "react";
|
|
1143
|
-
function useHasAllPermissions(userId, scope, permissions, useCache = true) {
|
|
1144
|
-
const [hasAll, setHasAll] = useState6(false);
|
|
1145
|
-
const [isLoading, setIsLoading] = useState6(true);
|
|
1146
|
-
const [error, setError] = useState6(null);
|
|
1147
|
-
const checkAllPermissions = useCallback5(async () => {
|
|
1148
|
-
if (!userId || permissions.length === 0) {
|
|
1149
|
-
setHasAll(false);
|
|
1150
|
-
setIsLoading(false);
|
|
1151
|
-
return;
|
|
1152
|
-
}
|
|
1153
|
-
try {
|
|
1154
|
-
setIsLoading(true);
|
|
1155
|
-
setError(null);
|
|
1156
|
-
let hasAllPermissions = true;
|
|
1157
|
-
for (const permission of permissions) {
|
|
1158
|
-
const result = useCache ? await isPermittedCached({ userId, scope, permission }) : await isPermitted({ userId, scope, permission });
|
|
1159
|
-
if (!result) {
|
|
1160
|
-
hasAllPermissions = false;
|
|
1161
|
-
break;
|
|
1162
|
-
}
|
|
1163
|
-
}
|
|
1164
|
-
setHasAll(hasAllPermissions);
|
|
1165
|
-
} catch (err) {
|
|
1166
|
-
setError(err instanceof Error ? err : new Error("Failed to check permissions"));
|
|
1167
|
-
setHasAll(false);
|
|
1168
|
-
} finally {
|
|
1169
|
-
setIsLoading(false);
|
|
1170
|
-
}
|
|
1171
|
-
}, [userId, scope.organisationId, scope.eventId, scope.appId, permissions, useCache]);
|
|
1172
|
-
useEffect6(() => {
|
|
1173
|
-
checkAllPermissions();
|
|
1174
|
-
}, [checkAllPermissions]);
|
|
1175
|
-
return useMemo6(() => ({
|
|
1176
|
-
hasAll,
|
|
1177
|
-
isLoading,
|
|
1178
|
-
error,
|
|
1179
|
-
refetch: checkAllPermissions
|
|
1180
|
-
}), [hasAll, isLoading, error, checkAllPermissions]);
|
|
1181
|
-
}
|
|
1182
|
-
|
|
1183
|
-
// src/rbac/hooks/permissions/useHasAnyPermission.ts
|
|
1184
|
-
import { useCallback as useCallback6, useEffect as useEffect7, useMemo as useMemo7, useState as useState7 } from "react";
|
|
1185
|
-
function useHasAnyPermission(userId, scope, permissions, useCache = true) {
|
|
1186
|
-
const [hasAny, setHasAny] = useState7(false);
|
|
1187
|
-
const [isLoading, setIsLoading] = useState7(true);
|
|
1188
|
-
const [error, setError] = useState7(null);
|
|
1189
|
-
const checkAnyPermission = useCallback6(async () => {
|
|
1190
|
-
if (!userId || permissions.length === 0) {
|
|
1191
|
-
setHasAny(false);
|
|
1192
|
-
setIsLoading(false);
|
|
1193
|
-
return;
|
|
1194
|
-
}
|
|
1195
|
-
try {
|
|
1196
|
-
setIsLoading(true);
|
|
1197
|
-
setError(null);
|
|
1198
|
-
let hasAnyPermission = false;
|
|
1199
|
-
for (const permission of permissions) {
|
|
1200
|
-
const result = useCache ? await isPermittedCached({ userId, scope, permission }) : await isPermitted({ userId, scope, permission });
|
|
1201
|
-
if (result) {
|
|
1202
|
-
hasAnyPermission = true;
|
|
1203
|
-
break;
|
|
1204
|
-
}
|
|
1205
|
-
}
|
|
1206
|
-
setHasAny(hasAnyPermission);
|
|
1207
|
-
} catch (err) {
|
|
1208
|
-
setError(err instanceof Error ? err : new Error("Failed to check permissions"));
|
|
1209
|
-
setHasAny(false);
|
|
1210
|
-
} finally {
|
|
1211
|
-
setIsLoading(false);
|
|
1212
|
-
}
|
|
1213
|
-
}, [userId, scope.organisationId, scope.eventId, scope.appId, permissions, useCache]);
|
|
1214
|
-
useEffect7(() => {
|
|
1215
|
-
checkAnyPermission();
|
|
1216
|
-
}, [checkAnyPermission]);
|
|
1217
|
-
return useMemo7(() => ({
|
|
1218
|
-
hasAny,
|
|
1219
|
-
isLoading,
|
|
1220
|
-
error,
|
|
1221
|
-
refetch: checkAnyPermission
|
|
1222
|
-
}), [hasAny, isLoading, error, checkAnyPermission]);
|
|
1223
|
-
}
|
|
1224
|
-
|
|
1225
|
-
// src/rbac/hooks/permissions/useMultiplePermissions.ts
|
|
1226
|
-
import { useCallback as useCallback7, useEffect as useEffect8, useMemo as useMemo8, useState as useState8 } from "react";
|
|
1227
1116
|
function useMultiplePermissions(userId, scope, permissions, useCache = true) {
|
|
1228
|
-
const [results, setResults] =
|
|
1229
|
-
const [isLoading, setIsLoading] =
|
|
1230
|
-
const [error, setError] =
|
|
1231
|
-
const checkPermissions =
|
|
1117
|
+
const [results, setResults] = useState({});
|
|
1118
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
1119
|
+
const [error, setError] = useState(null);
|
|
1120
|
+
const checkPermissions = useCallback(async () => {
|
|
1232
1121
|
if (!userId || permissions.length === 0) {
|
|
1233
1122
|
setResults({});
|
|
1234
1123
|
setIsLoading(false);
|
|
@@ -1250,29 +1139,26 @@ function useMultiplePermissions(userId, scope, permissions, useCache = true) {
|
|
|
1250
1139
|
setIsLoading(false);
|
|
1251
1140
|
}
|
|
1252
1141
|
}, [userId, scope.organisationId, scope.eventId, scope.appId, permissions, useCache]);
|
|
1253
|
-
|
|
1142
|
+
useEffect(() => {
|
|
1254
1143
|
checkPermissions();
|
|
1255
1144
|
}, [checkPermissions]);
|
|
1256
|
-
return
|
|
1145
|
+
return useMemo(() => ({
|
|
1257
1146
|
results,
|
|
1258
1147
|
isLoading,
|
|
1259
1148
|
error,
|
|
1260
1149
|
refetch: checkPermissions
|
|
1261
1150
|
}), [results, isLoading, error, checkPermissions]);
|
|
1262
1151
|
}
|
|
1263
|
-
|
|
1264
|
-
// src/rbac/hooks/permissions/usePermissions.ts
|
|
1265
|
-
import { useCallback as useCallback8, useEffect as useEffect9, useMemo as useMemo9, useRef as useRef3, useState as useState9 } from "react";
|
|
1266
1152
|
function usePermissions(userId, organisationId, eventId, appId) {
|
|
1267
|
-
const [permissions, setPermissions] =
|
|
1268
|
-
const [isLoading, setIsLoading] =
|
|
1269
|
-
const [error, setError] =
|
|
1270
|
-
const [fetchTrigger, setFetchTrigger] =
|
|
1271
|
-
const isFetchingRef =
|
|
1153
|
+
const [permissions, setPermissions] = useState({});
|
|
1154
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
1155
|
+
const [error, setError] = useState(null);
|
|
1156
|
+
const [fetchTrigger, setFetchTrigger] = useState(0);
|
|
1157
|
+
const isFetchingRef = useRef(false);
|
|
1272
1158
|
const logger2 = getRBACLogger();
|
|
1273
|
-
const prevValuesRef =
|
|
1159
|
+
const prevValuesRef = useRef({ userId, organisationId, eventId, appId });
|
|
1274
1160
|
const orgId = organisationId || "";
|
|
1275
|
-
|
|
1161
|
+
useEffect(() => {
|
|
1276
1162
|
if (!userId) {
|
|
1277
1163
|
return;
|
|
1278
1164
|
}
|
|
@@ -1287,16 +1173,15 @@ function usePermissions(userId, organisationId, eventId, appId) {
|
|
|
1287
1173
|
setError(null);
|
|
1288
1174
|
}
|
|
1289
1175
|
}, [userId, organisationId, error, orgId]);
|
|
1290
|
-
|
|
1176
|
+
useEffect(() => {
|
|
1291
1177
|
const paramsChanged = prevValuesRef.current.userId !== userId || prevValuesRef.current.organisationId !== organisationId || prevValuesRef.current.eventId !== eventId || prevValuesRef.current.appId !== appId;
|
|
1292
1178
|
if (paramsChanged) {
|
|
1293
|
-
if (prevValuesRef.current.appId !== appId)
|
|
1294
|
-
}
|
|
1179
|
+
if (prevValuesRef.current.appId !== appId) ;
|
|
1295
1180
|
prevValuesRef.current = { userId, organisationId, eventId, appId };
|
|
1296
1181
|
setFetchTrigger((prev) => prev + 1);
|
|
1297
1182
|
}
|
|
1298
1183
|
}, [userId, organisationId, eventId, appId, logger2]);
|
|
1299
|
-
|
|
1184
|
+
useEffect(() => {
|
|
1300
1185
|
const fetchPermissions = async () => {
|
|
1301
1186
|
if (isFetchingRef.current) {
|
|
1302
1187
|
return;
|
|
@@ -1343,25 +1228,25 @@ function usePermissions(userId, organisationId, eventId, appId) {
|
|
|
1343
1228
|
};
|
|
1344
1229
|
fetchPermissions();
|
|
1345
1230
|
}, [fetchTrigger, userId, organisationId, eventId, appId]);
|
|
1346
|
-
const hasPermission =
|
|
1231
|
+
const hasPermission = useCallback((permission) => {
|
|
1347
1232
|
if (permissions["*"]) {
|
|
1348
1233
|
return true;
|
|
1349
1234
|
}
|
|
1350
1235
|
return permissions[permission] === true;
|
|
1351
1236
|
}, [permissions]);
|
|
1352
|
-
const hasAnyPermission =
|
|
1237
|
+
const hasAnyPermission = useCallback((permissionList) => {
|
|
1353
1238
|
if (permissions["*"]) {
|
|
1354
1239
|
return true;
|
|
1355
1240
|
}
|
|
1356
1241
|
return permissionList.some((p) => permissions[p] === true);
|
|
1357
1242
|
}, [permissions]);
|
|
1358
|
-
const hasAllPermissions =
|
|
1243
|
+
const hasAllPermissions = useCallback((permissionList) => {
|
|
1359
1244
|
if (permissions["*"]) {
|
|
1360
1245
|
return true;
|
|
1361
1246
|
}
|
|
1362
1247
|
return permissionList.every((p) => permissions[p] === true);
|
|
1363
1248
|
}, [permissions]);
|
|
1364
|
-
const refetch =
|
|
1249
|
+
const refetch = useCallback(async () => {
|
|
1365
1250
|
if (isFetchingRef.current) {
|
|
1366
1251
|
return;
|
|
1367
1252
|
}
|
|
@@ -1395,7 +1280,7 @@ function usePermissions(userId, organisationId, eventId, appId) {
|
|
|
1395
1280
|
isFetchingRef.current = false;
|
|
1396
1281
|
}
|
|
1397
1282
|
}, [userId, organisationId, eventId, appId]);
|
|
1398
|
-
return
|
|
1283
|
+
return useMemo(() => ({
|
|
1399
1284
|
permissions,
|
|
1400
1285
|
isLoading,
|
|
1401
1286
|
error,
|
|
@@ -1405,12 +1290,57 @@ function usePermissions(userId, organisationId, eventId, appId) {
|
|
|
1405
1290
|
refetch
|
|
1406
1291
|
}), [permissions, isLoading, error, hasPermission, hasAnyPermission, hasAllPermissions, refetch]);
|
|
1407
1292
|
}
|
|
1408
|
-
|
|
1409
|
-
// src/rbac/hooks/useResourcePermissions.ts
|
|
1410
|
-
import { useMemo as useMemo10 } from "react";
|
|
1411
1293
|
function useResourcePermissions(resource, options = {}) {
|
|
1412
1294
|
const { enableRead = false, requireScope = true } = options;
|
|
1295
|
+
const logger2 = createLogger("ResourcePermissions");
|
|
1413
1296
|
const { user, supabase } = useUnifiedAuth();
|
|
1297
|
+
const [isSuperAdminUser, setIsSuperAdminUser] = useState(null);
|
|
1298
|
+
const [isCheckingSuperAdmin, setIsCheckingSuperAdmin] = useState(() => !!user?.id);
|
|
1299
|
+
const lastCheckedUserIdRef = useRef(null);
|
|
1300
|
+
const isCheckingRef = useRef(false);
|
|
1301
|
+
useEffect(() => {
|
|
1302
|
+
if (lastCheckedUserIdRef.current === user?.id && isSuperAdminUser !== null) {
|
|
1303
|
+
return;
|
|
1304
|
+
}
|
|
1305
|
+
if (isCheckingRef.current) {
|
|
1306
|
+
return;
|
|
1307
|
+
}
|
|
1308
|
+
const checkSuperAdminStatus = async () => {
|
|
1309
|
+
if (!user?.id) {
|
|
1310
|
+
setIsSuperAdminUser(false);
|
|
1311
|
+
setIsCheckingSuperAdmin(false);
|
|
1312
|
+
lastCheckedUserIdRef.current = null;
|
|
1313
|
+
return;
|
|
1314
|
+
}
|
|
1315
|
+
isCheckingRef.current = true;
|
|
1316
|
+
lastCheckedUserIdRef.current = user.id;
|
|
1317
|
+
const startTime = Date.now();
|
|
1318
|
+
setIsCheckingSuperAdmin(true);
|
|
1319
|
+
const timeoutId = setTimeout(() => {
|
|
1320
|
+
logger2.warn("useResourcePermissions", "Super admin check taking longer than 5 seconds", {
|
|
1321
|
+
userId: user?.id,
|
|
1322
|
+
elapsedMs: Date.now() - startTime
|
|
1323
|
+
});
|
|
1324
|
+
}, 5e3);
|
|
1325
|
+
try {
|
|
1326
|
+
const superAdminStatus = await isSuperAdmin(user.id);
|
|
1327
|
+
setIsSuperAdminUser(superAdminStatus);
|
|
1328
|
+
} catch (error2) {
|
|
1329
|
+
const elapsed = Date.now() - startTime;
|
|
1330
|
+
logger2.error("useResourcePermissions", "Error checking super admin status", {
|
|
1331
|
+
userId: user?.id,
|
|
1332
|
+
error: error2,
|
|
1333
|
+
elapsedMs: elapsed
|
|
1334
|
+
});
|
|
1335
|
+
setIsSuperAdminUser(false);
|
|
1336
|
+
} finally {
|
|
1337
|
+
clearTimeout(timeoutId);
|
|
1338
|
+
setIsCheckingSuperAdmin(false);
|
|
1339
|
+
isCheckingRef.current = false;
|
|
1340
|
+
}
|
|
1341
|
+
};
|
|
1342
|
+
checkSuperAdminStatus();
|
|
1343
|
+
}, [user?.id, logger2]);
|
|
1414
1344
|
const { selectedOrganisation } = useOrganisations();
|
|
1415
1345
|
let selectedEvent = null;
|
|
1416
1346
|
try {
|
|
@@ -1421,70 +1351,79 @@ function useResourcePermissions(resource, options = {}) {
|
|
|
1421
1351
|
const { resolvedScope, isLoading: scopeLoading, error: scopeError } = useResolvedScope({
|
|
1422
1352
|
supabase,
|
|
1423
1353
|
selectedOrganisationId: selectedOrganisation?.id || null,
|
|
1424
|
-
selectedEventId: selectedEvent?.event_id || null
|
|
1354
|
+
selectedEventId: selectedEvent?.event_id || null,
|
|
1355
|
+
selectedEventOrganisationId: selectedEvent?.organisation_id || null
|
|
1425
1356
|
});
|
|
1426
1357
|
const scope = resolvedScope || {
|
|
1427
1358
|
organisationId: selectedOrganisation?.id || "",
|
|
1428
1359
|
eventId: selectedEvent?.event_id || void 0,
|
|
1429
1360
|
appId: void 0
|
|
1430
1361
|
};
|
|
1431
|
-
const
|
|
1362
|
+
const hasAppId = !!resolvedScope?.appId;
|
|
1363
|
+
const pageId = hasAppId ? resource : void 0;
|
|
1364
|
+
const isPagePermission = hasAppId && !!pageId;
|
|
1365
|
+
const createPermission = isPagePermission ? `create:page.${resource}` : `create:${resource}`;
|
|
1366
|
+
const updatePermission = isPagePermission ? `update:page.${resource}` : `update:${resource}`;
|
|
1367
|
+
const deletePermission = isPagePermission ? `delete:page.${resource}` : `delete:${resource}`;
|
|
1368
|
+
const readPermission = isPagePermission ? `read:page.${resource}` : `read:${resource}`;
|
|
1432
1369
|
const { can: canCreateResult, isLoading: createLoading, error: createError } = useCan(
|
|
1433
1370
|
user?.id || "",
|
|
1434
1371
|
scope,
|
|
1435
|
-
|
|
1372
|
+
createPermission,
|
|
1436
1373
|
pageId,
|
|
1437
1374
|
// Pass resource name as pageId when appId is available to enable page permission checks
|
|
1438
1375
|
true,
|
|
1439
1376
|
// useCache
|
|
1440
|
-
|
|
1441
|
-
// precomputedSuperAdmin -
|
|
1377
|
+
isSuperAdminUser,
|
|
1378
|
+
// precomputedSuperAdmin - null if checking, false/true if checked
|
|
1442
1379
|
void 0
|
|
1443
1380
|
// appName
|
|
1444
1381
|
);
|
|
1445
1382
|
const { can: canUpdateResult, isLoading: updateLoading, error: updateError } = useCan(
|
|
1446
1383
|
user?.id || "",
|
|
1447
1384
|
scope,
|
|
1448
|
-
|
|
1385
|
+
updatePermission,
|
|
1449
1386
|
pageId,
|
|
1450
1387
|
// Pass resource name as pageId when appId is available to enable page permission checks
|
|
1451
1388
|
true,
|
|
1452
1389
|
// useCache
|
|
1453
|
-
|
|
1454
|
-
// precomputedSuperAdmin -
|
|
1390
|
+
isSuperAdminUser,
|
|
1391
|
+
// precomputedSuperAdmin - null if checking, false/true if checked
|
|
1455
1392
|
void 0
|
|
1456
1393
|
// appName
|
|
1457
1394
|
);
|
|
1458
1395
|
const { can: canDeleteResult, isLoading: deleteLoading, error: deleteError } = useCan(
|
|
1459
1396
|
user?.id || "",
|
|
1460
1397
|
scope,
|
|
1461
|
-
|
|
1398
|
+
deletePermission,
|
|
1462
1399
|
pageId,
|
|
1463
1400
|
// Pass resource name as pageId when appId is available to enable page permission checks
|
|
1464
1401
|
true,
|
|
1465
1402
|
// useCache
|
|
1466
|
-
|
|
1467
|
-
// precomputedSuperAdmin -
|
|
1403
|
+
isSuperAdminUser,
|
|
1404
|
+
// precomputedSuperAdmin - null if checking, false/true if checked
|
|
1468
1405
|
void 0
|
|
1469
1406
|
// appName
|
|
1470
1407
|
);
|
|
1471
1408
|
const { can: canReadResult, isLoading: readLoading, error: readError } = useCan(
|
|
1472
1409
|
user?.id || "",
|
|
1473
1410
|
scope,
|
|
1474
|
-
|
|
1411
|
+
readPermission,
|
|
1475
1412
|
pageId,
|
|
1476
1413
|
// Pass resource name as pageId when appId is available to enable page permission checks
|
|
1477
1414
|
true,
|
|
1478
1415
|
// useCache
|
|
1479
|
-
|
|
1480
|
-
// precomputedSuperAdmin -
|
|
1416
|
+
isSuperAdminUser,
|
|
1417
|
+
// precomputedSuperAdmin - null if checking, false/true if checked
|
|
1481
1418
|
void 0
|
|
1482
1419
|
// appName
|
|
1483
1420
|
);
|
|
1484
|
-
const isLoading =
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1421
|
+
const isLoading = useMemo(() => {
|
|
1422
|
+
const waitingForScope = requireScope && scopeLoading;
|
|
1423
|
+
const waitingForSuperAdmin = isSuperAdminUser === null && isCheckingSuperAdmin;
|
|
1424
|
+
return waitingForScope || waitingForSuperAdmin || createLoading || updateLoading || deleteLoading || enableRead && readLoading;
|
|
1425
|
+
}, [scopeLoading, requireScope, isSuperAdminUser, isCheckingSuperAdmin, createLoading, updateLoading, deleteLoading, readLoading, enableRead]);
|
|
1426
|
+
const error = useMemo(() => {
|
|
1488
1427
|
if (scopeError) return scopeError;
|
|
1489
1428
|
if (createError) return createError;
|
|
1490
1429
|
if (updateError) return updateError;
|
|
@@ -1492,39 +1431,49 @@ function useResourcePermissions(resource, options = {}) {
|
|
|
1492
1431
|
if (enableRead && readError) return readError;
|
|
1493
1432
|
return null;
|
|
1494
1433
|
}, [scopeError, createError, updateError, deleteError, readError, enableRead]);
|
|
1495
|
-
return
|
|
1496
|
-
|
|
1497
|
-
if (
|
|
1498
|
-
return false;
|
|
1499
|
-
}
|
|
1500
|
-
return canCreateResult;
|
|
1501
|
-
},
|
|
1502
|
-
canUpdate: (res) => {
|
|
1503
|
-
if (res !== resource) {
|
|
1504
|
-
return false;
|
|
1505
|
-
}
|
|
1506
|
-
return canUpdateResult;
|
|
1507
|
-
},
|
|
1508
|
-
canDelete: (res) => {
|
|
1509
|
-
if (res !== resource) {
|
|
1510
|
-
return false;
|
|
1511
|
-
}
|
|
1512
|
-
return canDeleteResult;
|
|
1513
|
-
},
|
|
1514
|
-
canRead: (res) => {
|
|
1515
|
-
if (!enableRead) {
|
|
1434
|
+
return useMemo(() => {
|
|
1435
|
+
const createSuperAdminAwarePermission = (result) => {
|
|
1436
|
+
if (isSuperAdminUser === true) {
|
|
1516
1437
|
return true;
|
|
1517
1438
|
}
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1439
|
+
return result;
|
|
1440
|
+
};
|
|
1441
|
+
return {
|
|
1442
|
+
canCreate: (res) => {
|
|
1443
|
+
if (res !== resource) {
|
|
1444
|
+
return false;
|
|
1445
|
+
}
|
|
1446
|
+
return createSuperAdminAwarePermission(canCreateResult);
|
|
1447
|
+
},
|
|
1448
|
+
canUpdate: (res) => {
|
|
1449
|
+
if (res !== resource) {
|
|
1450
|
+
return false;
|
|
1451
|
+
}
|
|
1452
|
+
return createSuperAdminAwarePermission(canUpdateResult);
|
|
1453
|
+
},
|
|
1454
|
+
canDelete: (res) => {
|
|
1455
|
+
if (res !== resource) {
|
|
1456
|
+
return false;
|
|
1457
|
+
}
|
|
1458
|
+
return createSuperAdminAwarePermission(canDeleteResult);
|
|
1459
|
+
},
|
|
1460
|
+
canRead: (res) => {
|
|
1461
|
+
if (!enableRead) {
|
|
1462
|
+
return true;
|
|
1463
|
+
}
|
|
1464
|
+
if (res !== resource) {
|
|
1465
|
+
return false;
|
|
1466
|
+
}
|
|
1467
|
+
return createSuperAdminAwarePermission(canReadResult);
|
|
1468
|
+
},
|
|
1469
|
+
scope,
|
|
1470
|
+
isLoading: isCheckingSuperAdmin || isLoading,
|
|
1471
|
+
error
|
|
1472
|
+
};
|
|
1473
|
+
}, [
|
|
1527
1474
|
resource,
|
|
1475
|
+
isSuperAdminUser,
|
|
1476
|
+
isCheckingSuperAdmin,
|
|
1528
1477
|
canCreateResult,
|
|
1529
1478
|
canUpdateResult,
|
|
1530
1479
|
canDeleteResult,
|
|
@@ -1535,38 +1484,102 @@ function useResourcePermissions(resource, options = {}) {
|
|
|
1535
1484
|
error
|
|
1536
1485
|
]);
|
|
1537
1486
|
}
|
|
1538
|
-
|
|
1539
|
-
// src/rbac/hooks/useRoleManagement.ts
|
|
1540
|
-
import { useState as useState10, useCallback as useCallback9 } from "react";
|
|
1541
1487
|
function useRoleManagement() {
|
|
1542
1488
|
const { user, supabase } = useUnifiedAuth();
|
|
1543
|
-
const [isLoading, setIsLoading] =
|
|
1544
|
-
const [error, setError] =
|
|
1489
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
1490
|
+
const [error, setError] = useState(null);
|
|
1545
1491
|
if (!supabase) {
|
|
1546
1492
|
throw new Error("useRoleManagement requires a Supabase client. Ensure UnifiedAuthProvider is configured.");
|
|
1547
1493
|
}
|
|
1548
|
-
const revokeEventAppRole =
|
|
1494
|
+
const revokeEventAppRole = useCallback(async (params) => {
|
|
1549
1495
|
setIsLoading(true);
|
|
1550
1496
|
setError(null);
|
|
1551
1497
|
try {
|
|
1552
|
-
const
|
|
1498
|
+
const contextId = `${params.event_id}:${params.app_id}`;
|
|
1499
|
+
const rpcParams = {
|
|
1553
1500
|
p_user_id: params.user_id,
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
p_role: params.role,
|
|
1501
|
+
p_role_type: "event_app",
|
|
1502
|
+
p_role_name: params.role,
|
|
1503
|
+
p_context_id: contextId,
|
|
1558
1504
|
p_revoked_by: params.revoked_by || user?.id || void 0
|
|
1559
|
-
}
|
|
1505
|
+
};
|
|
1506
|
+
if (import.meta.env.MODE === "development") {
|
|
1507
|
+
console.log("[useRoleManagement] revokeEventAppRole called with:", rpcParams);
|
|
1508
|
+
}
|
|
1509
|
+
const { data, error: rpcError } = await supabase.rpc("rbac_role_revoke", rpcParams);
|
|
1560
1510
|
if (rpcError) {
|
|
1561
|
-
|
|
1511
|
+
if (import.meta.env.MODE === "development") {
|
|
1512
|
+
console.error("[useRoleManagement] RPC error:", {
|
|
1513
|
+
message: rpcError.message,
|
|
1514
|
+
details: rpcError.details,
|
|
1515
|
+
hint: rpcError.hint,
|
|
1516
|
+
code: rpcError.code,
|
|
1517
|
+
fullError: rpcError
|
|
1518
|
+
});
|
|
1519
|
+
}
|
|
1520
|
+
const errorMessage = rpcError.message || "Failed to revoke role - unknown RPC error";
|
|
1521
|
+
throw new Error(errorMessage);
|
|
1522
|
+
}
|
|
1523
|
+
if (import.meta.env.MODE === "development") {
|
|
1524
|
+
console.log("[useRoleManagement] RPC response:", {
|
|
1525
|
+
data,
|
|
1526
|
+
error: rpcError,
|
|
1527
|
+
dataType: Array.isArray(data) ? "array" : typeof data,
|
|
1528
|
+
dataLength: Array.isArray(data) ? data.length : "N/A"
|
|
1529
|
+
});
|
|
1530
|
+
}
|
|
1531
|
+
if (!data || !Array.isArray(data) || data.length === 0) {
|
|
1532
|
+
const errorMsg = "No response from database - role revocation may have failed";
|
|
1533
|
+
if (import.meta.env.MODE === "development") {
|
|
1534
|
+
console.error("[useRoleManagement] Empty or null data response:", {
|
|
1535
|
+
data,
|
|
1536
|
+
dataType: typeof data,
|
|
1537
|
+
isArray: Array.isArray(data),
|
|
1538
|
+
length: Array.isArray(data) ? data.length : "N/A"
|
|
1539
|
+
});
|
|
1540
|
+
}
|
|
1541
|
+
throw new Error(errorMsg);
|
|
1542
|
+
}
|
|
1543
|
+
const result = data[0];
|
|
1544
|
+
if (import.meta.env.MODE === "development") {
|
|
1545
|
+
console.log("[useRoleManagement] RPC result:", {
|
|
1546
|
+
success: result?.success,
|
|
1547
|
+
message: result?.message,
|
|
1548
|
+
error_code: result?.error_code,
|
|
1549
|
+
revoked_count: result?.revoked_count,
|
|
1550
|
+
fullResult: result
|
|
1551
|
+
});
|
|
1552
|
+
}
|
|
1553
|
+
if (!result || result.success !== true) {
|
|
1554
|
+
const errorMessage = result?.message || result?.error_code || "Role revocation failed";
|
|
1555
|
+
if (import.meta.env.MODE === "development") {
|
|
1556
|
+
console.error("[useRoleManagement] Role revocation failed:", {
|
|
1557
|
+
result,
|
|
1558
|
+
errorMessage,
|
|
1559
|
+
fullData: data,
|
|
1560
|
+
rpcParams
|
|
1561
|
+
});
|
|
1562
|
+
}
|
|
1563
|
+
return {
|
|
1564
|
+
success: false,
|
|
1565
|
+
message: result?.message || void 0,
|
|
1566
|
+
error: errorMessage
|
|
1567
|
+
};
|
|
1562
1568
|
}
|
|
1563
1569
|
return {
|
|
1564
|
-
success:
|
|
1565
|
-
message:
|
|
1566
|
-
error:
|
|
1570
|
+
success: true,
|
|
1571
|
+
message: result.message || "Role revoked successfully",
|
|
1572
|
+
error: void 0
|
|
1567
1573
|
};
|
|
1568
1574
|
} catch (err) {
|
|
1569
1575
|
const errorMessage = err instanceof Error ? err.message : "Unknown error occurred";
|
|
1576
|
+
if (import.meta.env.MODE === "development") {
|
|
1577
|
+
console.error("[useRoleManagement] Exception in revokeEventAppRole:", {
|
|
1578
|
+
error: err,
|
|
1579
|
+
errorMessage,
|
|
1580
|
+
params
|
|
1581
|
+
});
|
|
1582
|
+
}
|
|
1570
1583
|
setError(err instanceof Error ? err : new Error(errorMessage));
|
|
1571
1584
|
return {
|
|
1572
1585
|
success: false,
|
|
@@ -1575,8 +1588,8 @@ function useRoleManagement() {
|
|
|
1575
1588
|
} finally {
|
|
1576
1589
|
setIsLoading(false);
|
|
1577
1590
|
}
|
|
1578
|
-
}, [user?.id]);
|
|
1579
|
-
const grantEventAppRole =
|
|
1591
|
+
}, [user?.id, supabase]);
|
|
1592
|
+
const grantEventAppRole = useCallback(async (params) => {
|
|
1580
1593
|
setIsLoading(true);
|
|
1581
1594
|
setError(null);
|
|
1582
1595
|
try {
|
|
@@ -1615,7 +1628,7 @@ function useRoleManagement() {
|
|
|
1615
1628
|
setIsLoading(false);
|
|
1616
1629
|
}
|
|
1617
1630
|
}, [user?.id]);
|
|
1618
|
-
const revokeRoleById =
|
|
1631
|
+
const revokeRoleById = useCallback(async (roleId) => {
|
|
1619
1632
|
setIsLoading(true);
|
|
1620
1633
|
setError(null);
|
|
1621
1634
|
try {
|
|
@@ -1632,13 +1645,28 @@ function useRoleManagement() {
|
|
|
1632
1645
|
p_revoked_by: user?.id || void 0
|
|
1633
1646
|
});
|
|
1634
1647
|
if (rpcError) {
|
|
1635
|
-
|
|
1648
|
+
const errorMessage = rpcError.message || "Failed to revoke role - unknown RPC error";
|
|
1649
|
+
throw new Error(errorMessage);
|
|
1636
1650
|
}
|
|
1637
1651
|
const result = Array.isArray(data) && data.length > 0 ? data[0] : null;
|
|
1652
|
+
if (!result) {
|
|
1653
|
+
return {
|
|
1654
|
+
success: false,
|
|
1655
|
+
error: void 0
|
|
1656
|
+
};
|
|
1657
|
+
}
|
|
1658
|
+
if (result.success === false) {
|
|
1659
|
+
const errorMessage = result.message || result.error_code || "Role revocation failed";
|
|
1660
|
+
return {
|
|
1661
|
+
success: false,
|
|
1662
|
+
message: result.message || void 0,
|
|
1663
|
+
error: errorMessage
|
|
1664
|
+
};
|
|
1665
|
+
}
|
|
1638
1666
|
return {
|
|
1639
|
-
success:
|
|
1640
|
-
message: result
|
|
1641
|
-
error:
|
|
1667
|
+
success: true,
|
|
1668
|
+
message: result.message || "Role revoked successfully",
|
|
1669
|
+
error: void 0
|
|
1642
1670
|
};
|
|
1643
1671
|
} catch (err) {
|
|
1644
1672
|
const errorMessage = err instanceof Error ? err.message : "Unknown error occurred";
|
|
@@ -1651,7 +1679,7 @@ function useRoleManagement() {
|
|
|
1651
1679
|
setIsLoading(false);
|
|
1652
1680
|
}
|
|
1653
1681
|
}, [user?.id, supabase]);
|
|
1654
|
-
const grantGlobalRole =
|
|
1682
|
+
const grantGlobalRole = useCallback(async (params) => {
|
|
1655
1683
|
setIsLoading(true);
|
|
1656
1684
|
setError(null);
|
|
1657
1685
|
try {
|
|
@@ -1690,7 +1718,7 @@ function useRoleManagement() {
|
|
|
1690
1718
|
setIsLoading(false);
|
|
1691
1719
|
}
|
|
1692
1720
|
}, [user?.id, supabase]);
|
|
1693
|
-
const revokeGlobalRole =
|
|
1721
|
+
const revokeGlobalRole = useCallback(async (params) => {
|
|
1694
1722
|
setIsLoading(true);
|
|
1695
1723
|
setError(null);
|
|
1696
1724
|
try {
|
|
@@ -1703,7 +1731,14 @@ function useRoleManagement() {
|
|
|
1703
1731
|
p_revoked_by: params.revoked_by || user?.id || void 0
|
|
1704
1732
|
});
|
|
1705
1733
|
if (rpcError) {
|
|
1706
|
-
|
|
1734
|
+
const errorParts = [
|
|
1735
|
+
rpcError.message,
|
|
1736
|
+
rpcError.details,
|
|
1737
|
+
rpcError.hint,
|
|
1738
|
+
rpcError.code ? `Error code: ${rpcError.code}` : null
|
|
1739
|
+
].filter(Boolean);
|
|
1740
|
+
const errorMessage = errorParts.length > 0 ? errorParts.join(" | ") : "Failed to revoke role - unknown RPC error";
|
|
1741
|
+
throw new Error(errorMessage);
|
|
1707
1742
|
}
|
|
1708
1743
|
const result = Array.isArray(data) && data.length > 0 ? data[0] : null;
|
|
1709
1744
|
return {
|
|
@@ -1722,7 +1757,7 @@ function useRoleManagement() {
|
|
|
1722
1757
|
setIsLoading(false);
|
|
1723
1758
|
}
|
|
1724
1759
|
}, [user?.id, supabase]);
|
|
1725
|
-
const grantOrganisationRole =
|
|
1760
|
+
const grantOrganisationRole = useCallback(async (params) => {
|
|
1726
1761
|
setIsLoading(true);
|
|
1727
1762
|
setError(null);
|
|
1728
1763
|
try {
|
|
@@ -1761,7 +1796,7 @@ function useRoleManagement() {
|
|
|
1761
1796
|
setIsLoading(false);
|
|
1762
1797
|
}
|
|
1763
1798
|
}, [user?.id, supabase]);
|
|
1764
|
-
const revokeOrganisationRole =
|
|
1799
|
+
const revokeOrganisationRole = useCallback(async (params) => {
|
|
1765
1800
|
setIsLoading(true);
|
|
1766
1801
|
setError(null);
|
|
1767
1802
|
try {
|
|
@@ -1774,7 +1809,14 @@ function useRoleManagement() {
|
|
|
1774
1809
|
p_revoked_by: params.revoked_by || user?.id || void 0
|
|
1775
1810
|
});
|
|
1776
1811
|
if (rpcError) {
|
|
1777
|
-
|
|
1812
|
+
const errorParts = [
|
|
1813
|
+
rpcError.message,
|
|
1814
|
+
rpcError.details,
|
|
1815
|
+
rpcError.hint,
|
|
1816
|
+
rpcError.code ? `Error code: ${rpcError.code}` : null
|
|
1817
|
+
].filter(Boolean);
|
|
1818
|
+
const errorMessage = errorParts.length > 0 ? errorParts.join(" | ") : "Failed to revoke role - unknown RPC error";
|
|
1819
|
+
throw new Error(errorMessage);
|
|
1778
1820
|
}
|
|
1779
1821
|
const result = Array.isArray(data) && data.length > 0 ? data[0] : null;
|
|
1780
1822
|
return {
|
|
@@ -1809,13 +1851,10 @@ function useRoleManagement() {
|
|
|
1809
1851
|
error
|
|
1810
1852
|
};
|
|
1811
1853
|
}
|
|
1812
|
-
|
|
1813
|
-
// src/rbac/hooks/useSecureSupabase.ts
|
|
1814
|
-
import { useMemo as useMemo11, useRef as useRef4 } from "react";
|
|
1815
1854
|
var secureClientCache = /* @__PURE__ */ new Map();
|
|
1816
1855
|
var MAX_CACHE_SIZE = 5;
|
|
1817
|
-
function getCacheKey(organisationId, eventId, appId,
|
|
1818
|
-
return `${organisationId || "no-org"}-${eventId || "no-event"}-${appId || "no-app"}-${
|
|
1856
|
+
function getCacheKey(organisationId, eventId, appId, isSuperAdmin2) {
|
|
1857
|
+
return `${organisationId || "no-org"}-${eventId || "no-event"}-${appId || "no-app"}-${isSuperAdmin2 ? "super" : "regular"}`;
|
|
1819
1858
|
}
|
|
1820
1859
|
function getSupabaseConfig() {
|
|
1821
1860
|
const getEnvVar = (key) => {
|
|
@@ -1841,28 +1880,26 @@ function useSecureSupabase(baseClient) {
|
|
|
1841
1880
|
const { selectedEvent } = eventsContext;
|
|
1842
1881
|
const eventLoading = "eventLoading" in eventsContext ? eventsContext.eventLoading : false;
|
|
1843
1882
|
const { superAdminContext } = useOrganisationSecurity();
|
|
1844
|
-
const
|
|
1883
|
+
const isSuperAdmin2 = superAdminContext.isSuperAdmin;
|
|
1845
1884
|
const { resolvedScope } = useResolvedScope({
|
|
1846
1885
|
supabase: authSupabase || null,
|
|
1847
1886
|
selectedOrganisationId: selectedOrganisation?.id || null,
|
|
1848
|
-
selectedEventId: selectedEvent?.event_id || null
|
|
1887
|
+
selectedEventId: selectedEvent?.event_id || null,
|
|
1888
|
+
selectedEventOrganisationId: selectedEvent?.organisation_id || null
|
|
1849
1889
|
});
|
|
1850
|
-
const prevContextRef =
|
|
1890
|
+
const prevContextRef = useRef({
|
|
1851
1891
|
organisationId: void 0,
|
|
1852
1892
|
eventId: void 0,
|
|
1853
1893
|
appId: void 0
|
|
1854
1894
|
});
|
|
1855
|
-
return
|
|
1856
|
-
if (eventLoading) {
|
|
1857
|
-
return baseClient || authSupabase || null;
|
|
1858
|
-
}
|
|
1895
|
+
return useMemo(() => {
|
|
1859
1896
|
const organisationId = resolvedScope?.organisationId;
|
|
1860
1897
|
const eventId = resolvedScope?.eventId || selectedEvent?.event_id;
|
|
1861
1898
|
const appId = resolvedScope?.appId;
|
|
1862
|
-
const canCreateSecureClient = user?.id && (
|
|
1899
|
+
const canCreateSecureClient = user?.id && (isSuperAdmin2 || organisationId);
|
|
1863
1900
|
if (canCreateSecureClient) {
|
|
1864
1901
|
prevContextRef.current = { organisationId, eventId, appId };
|
|
1865
|
-
const cacheKey = getCacheKey(organisationId, eventId, appId,
|
|
1902
|
+
const cacheKey = getCacheKey(organisationId, eventId, appId, isSuperAdmin2);
|
|
1866
1903
|
const cachedClient = secureClientCache.get(cacheKey);
|
|
1867
1904
|
if (cachedClient) {
|
|
1868
1905
|
return cachedClient.getClient();
|
|
@@ -1875,14 +1912,14 @@ function useSecureSupabase(baseClient) {
|
|
|
1875
1912
|
return baseClient || authSupabase || null;
|
|
1876
1913
|
}
|
|
1877
1914
|
try {
|
|
1878
|
-
const effectiveOrganisationId =
|
|
1915
|
+
const effectiveOrganisationId = isSuperAdmin2 ? organisationId || null : organisationId;
|
|
1879
1916
|
const baseForSecureClient = baseClient || authSupabase || null;
|
|
1880
1917
|
const secureClient = baseForSecureClient ? fromSupabaseClient(
|
|
1881
1918
|
baseForSecureClient,
|
|
1882
1919
|
effectiveOrganisationId ?? null,
|
|
1883
1920
|
eventId,
|
|
1884
1921
|
appId,
|
|
1885
|
-
|
|
1922
|
+
isSuperAdmin2
|
|
1886
1923
|
) : createSecureClient(
|
|
1887
1924
|
config.url,
|
|
1888
1925
|
config.key,
|
|
@@ -1891,7 +1928,7 @@ function useSecureSupabase(baseClient) {
|
|
|
1891
1928
|
eventId,
|
|
1892
1929
|
appId,
|
|
1893
1930
|
// appId is string | undefined, UUID is string alias
|
|
1894
|
-
|
|
1931
|
+
isSuperAdmin2
|
|
1895
1932
|
// Pass super admin status for conditional filtering
|
|
1896
1933
|
);
|
|
1897
1934
|
secureClientCache.set(cacheKey, secureClient);
|
|
@@ -1915,31 +1952,10 @@ function useSecureSupabase(baseClient) {
|
|
|
1915
1952
|
selectedEvent?.event_id,
|
|
1916
1953
|
user?.id,
|
|
1917
1954
|
eventLoading,
|
|
1918
|
-
|
|
1955
|
+
isSuperAdmin2,
|
|
1919
1956
|
baseClient,
|
|
1920
1957
|
authSupabase
|
|
1921
1958
|
]);
|
|
1922
1959
|
}
|
|
1923
1960
|
|
|
1924
|
-
export {
|
|
1925
|
-
SECURE_CLIENT_SYMBOL,
|
|
1926
|
-
isSecureClient,
|
|
1927
|
-
warnIfInsecureClient,
|
|
1928
|
-
SecureSupabaseClient,
|
|
1929
|
-
createSecureClient,
|
|
1930
|
-
fromSupabaseClient,
|
|
1931
|
-
useResolvedScope,
|
|
1932
|
-
useRBAC,
|
|
1933
|
-
useAccessLevel,
|
|
1934
|
-
useCachedPermissions,
|
|
1935
|
-
scopeEqual,
|
|
1936
|
-
useCan,
|
|
1937
|
-
useHasAllPermissions,
|
|
1938
|
-
useHasAnyPermission,
|
|
1939
|
-
useMultiplePermissions,
|
|
1940
|
-
usePermissions,
|
|
1941
|
-
useResourcePermissions,
|
|
1942
|
-
useRoleManagement,
|
|
1943
|
-
useSecureSupabase
|
|
1944
|
-
};
|
|
1945
|
-
//# sourceMappingURL=chunk-NN6WWZ5U.js.map
|
|
1961
|
+
export { Button, SECURE_CLIENT_SYMBOL, SecureSupabaseClient, Tooltip, TooltipContent, TooltipProvider, TooltipRoot, TooltipTrigger, createSecureClient, fromSupabaseClient, isSecureClient, scopeEqual, useAccessLevel, useCan, useEvents, useMultiplePermissions, usePermissions, useRBAC, useResolvedScope, useResourcePermissions, useRoleManagement, useSecureSupabase, warnIfInsecureClient };
|