@jmruthers/pace-core 0.5.193 → 0.6.2
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 +62 -0
- package/README.md +7 -1
- package/cursor-rules/00-pace-core-compliance.mdc +299 -0
- package/cursor-rules/01-standards-compliance.mdc +244 -0
- package/cursor-rules/02-project-structure.mdc +200 -0
- package/cursor-rules/03-solid-principles.mdc +222 -0
- package/cursor-rules/04-testing-standards.mdc +268 -0
- package/cursor-rules/05-bug-reports-and-features.mdc +246 -0
- package/cursor-rules/06-code-quality.mdc +309 -0
- package/cursor-rules/07-tech-stack-compliance.mdc +214 -0
- package/cursor-rules/08-markup-quality.mdc +452 -0
- package/cursor-rules/CHANGELOG.md +119 -0
- package/cursor-rules/README.md +192 -0
- package/dist/{AuthService-DjnJHDtC.d.ts → AuthService-BPvc3Ka0.d.ts} +54 -0
- package/dist/{DataTable-Be6dH_dR.d.ts → DataTable-BMRU8a1j.d.ts} +34 -2
- package/dist/{DataTable-5FU7IESH.js → DataTable-TPTKCX4D.js} +10 -9
- package/dist/{PublicPageProvider-C0Sm_e5k.d.ts → PublicPageProvider-DC6kCaqf.d.ts} +385 -261
- package/dist/{UnifiedAuthProvider-RGJTDE2C.js → UnifiedAuthProvider-CH6Z342H.js} +3 -3
- package/dist/{UnifiedAuthProvider-185Ih4dj.d.ts → UnifiedAuthProvider-CVcTjx-d.d.ts} +29 -0
- package/dist/{api-N774RPUA.js → api-MVVQZLJI.js} +2 -2
- package/dist/{chunk-KNC55RTG.js → chunk-24UVZUZG.js} +90 -54
- package/dist/chunk-24UVZUZG.js.map +1 -0
- package/dist/{chunk-HWIIPPNI.js → chunk-2UOI2FG5.js} +20 -20
- package/dist/chunk-2UOI2FG5.js.map +1 -0
- package/dist/{chunk-E3SPN4VZ 5.js → chunk-3XC4CPTD.js} +4345 -3986
- package/dist/chunk-3XC4CPTD.js.map +1 -0
- package/dist/{chunk-7EQTDTTJ.js → chunk-6J4GEEJR.js} +172 -45
- package/dist/chunk-6J4GEEJR.js.map +1 -0
- package/dist/{chunk-6C4YBBJM 5.js → chunk-6SOIHG6Z.js} +1 -1
- package/dist/chunk-6SOIHG6Z.js.map +1 -0
- package/dist/{chunk-7FLMSG37.js → chunk-EHMR7VYL.js} +25 -25
- package/dist/chunk-EHMR7VYL.js.map +1 -0
- package/dist/{chunk-I7PSE6JW.js → chunk-F2IMUDXZ.js} +2 -75
- package/dist/chunk-F2IMUDXZ.js.map +1 -0
- package/dist/{chunk-QWWZ5CAQ.js → chunk-FFQEQTNW.js} +7 -9
- package/dist/chunk-FFQEQTNW.js.map +1 -0
- package/dist/chunk-FMUCXFII.js +76 -0
- package/dist/chunk-FMUCXFII.js.map +1 -0
- package/dist/{chunk-HW3OVDUF.js → chunk-J36DSWQK.js} +1 -1
- package/dist/{chunk-HW3OVDUF.js.map → chunk-J36DSWQK.js.map} +1 -1
- package/dist/{chunk-SQGMNID3.js → chunk-L4OXEN46.js} +4 -5
- package/dist/chunk-L4OXEN46.js.map +1 -0
- package/dist/{chunk-R77UEZ4E 3.js → chunk-M43Y4SSO.js} +1 -1
- package/dist/chunk-M43Y4SSO.js.map +1 -0
- package/dist/{chunk-IIELH4DL.js → chunk-MMZ7JXPU.js} +60 -223
- package/dist/chunk-MMZ7JXPU.js.map +1 -0
- package/dist/{chunk-NOAYCWCX 5.js → chunk-NECFR5MM.js} +394 -312
- package/dist/chunk-NECFR5MM.js.map +1 -0
- package/dist/{chunk-BC4IJKSL.js → chunk-SFZUDBL5.js} +40 -4
- package/dist/chunk-SFZUDBL5.js.map +1 -0
- package/dist/{chunk-XNXXZ43G.js → chunk-XWQCNGTQ.js} +748 -364
- package/dist/chunk-XWQCNGTQ.js.map +1 -0
- package/dist/components.d.ts +6 -6
- package/dist/components.js +15 -12
- package/dist/components.js.map +1 -1
- package/dist/{functions-D_kgHktt.d.ts → functions-DHebl8-F.d.ts} +1 -1
- package/dist/hooks.d.ts +59 -126
- package/dist/hooks.js +19 -28
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +63 -16
- package/dist/index.js +23 -24
- package/dist/index.js.map +1 -1
- package/dist/providers.d.ts +21 -3
- package/dist/providers.js +2 -2
- package/dist/rbac/index.d.ts +146 -115
- package/dist/rbac/index.js +8 -11
- package/dist/theming/runtime.d.ts +1 -13
- package/dist/theming/runtime.js +1 -1
- package/dist/{timezone-_pgH8qrY.d.ts → timezone-CHhWg6b4.d.ts} +3 -10
- package/dist/{types-UU913iLA.d.ts → types-BeoeWV5I.d.ts} +8 -0
- package/dist/{types-CEpcvwwF.d.ts → types-CkbwOr4Y.d.ts} +6 -0
- package/dist/types.d.ts +2 -2
- package/dist/{usePublicRouteParams-TZe0gy-4.d.ts → usePublicRouteParams-1oMokgLF.d.ts} +34 -4
- package/dist/{useToast-C8gR5ir4.d.ts → useToast-AyaT-x7p.d.ts} +2 -2
- package/dist/utils.d.ts +4 -5
- package/dist/utils.js +15 -15
- package/dist/utils.js.map +1 -1
- package/docs/api/README.md +7 -1
- package/docs/api/classes/ColumnFactory.md +8 -8
- package/docs/api/classes/InvalidScopeError.md +4 -4
- package/docs/api/classes/Logger.md +1 -1
- package/docs/api/classes/MissingUserContextError.md +4 -4
- package/docs/api/classes/OrganisationContextRequiredError.md +4 -4
- package/docs/api/classes/PermissionDeniedError.md +4 -4
- package/docs/api/classes/RBACAuditManager.md +1 -1
- package/docs/api/classes/RBACCache.md +1 -1
- package/docs/api/classes/RBACEngine.md +1 -1
- package/docs/api/classes/RBACError.md +4 -4
- package/docs/api/classes/RBACNotInitializedError.md +4 -4
- package/docs/api/classes/SecureSupabaseClient.md +18 -15
- package/docs/api/classes/StorageUtils.md +1 -1
- package/docs/api/enums/FileCategory.md +1 -1
- package/docs/api/enums/LogLevel.md +1 -1
- package/docs/api/enums/RBACErrorCode.md +1 -1
- package/docs/api/enums/RPCFunction.md +1 -1
- package/docs/api/interfaces/AddressFieldProps.md +1 -1
- package/docs/api/interfaces/AddressFieldRef.md +1 -1
- package/docs/api/interfaces/AggregateConfig.md +4 -4
- package/docs/api/interfaces/AutocompleteOptions.md +1 -1
- package/docs/api/interfaces/AvatarProps.md +1 -1
- package/docs/api/interfaces/BadgeProps.md +9 -2
- package/docs/api/interfaces/ButtonProps.md +7 -4
- package/docs/api/interfaces/CalendarProps.md +8 -5
- package/docs/api/interfaces/CardProps.md +8 -5
- package/docs/api/interfaces/ColorPalette.md +1 -1
- package/docs/api/interfaces/ColorShade.md +1 -1
- package/docs/api/interfaces/ComplianceResult.md +1 -1
- package/docs/api/interfaces/DataAccessRecord.md +9 -9
- package/docs/api/interfaces/DataRecord.md +1 -1
- package/docs/api/interfaces/DataTableAction.md +24 -21
- package/docs/api/interfaces/DataTableColumn.md +31 -31
- package/docs/api/interfaces/DataTableProps.md +1 -1
- package/docs/api/interfaces/DataTableToolbarButton.md +7 -7
- package/docs/api/interfaces/DatabaseComplianceResult.md +1 -1
- package/docs/api/interfaces/DatabaseIssue.md +1 -1
- package/docs/api/interfaces/EmptyStateConfig.md +5 -5
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
- package/docs/api/interfaces/ErrorBoundaryProps.md +147 -0
- package/docs/api/interfaces/ErrorBoundaryProviderProps.md +36 -0
- package/docs/api/interfaces/ErrorBoundaryState.md +75 -0
- package/docs/api/interfaces/EventAppRoleData.md +1 -1
- package/docs/api/interfaces/ExportColumn.md +1 -1
- package/docs/api/interfaces/ExportOptions.md +8 -8
- package/docs/api/interfaces/FileDisplayProps.md +1 -1
- package/docs/api/interfaces/FileMetadata.md +1 -1
- package/docs/api/interfaces/FileReference.md +1 -1
- package/docs/api/interfaces/FileSizeLimits.md +1 -1
- package/docs/api/interfaces/FileUploadOptions.md +1 -1
- package/docs/api/interfaces/FileUploadProps.md +26 -23
- package/docs/api/interfaces/FooterProps.md +10 -8
- package/docs/api/interfaces/FormFieldProps.md +10 -10
- package/docs/api/interfaces/FormProps.md +1 -1
- package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
- package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
- package/docs/api/interfaces/InputProps.md +7 -4
- package/docs/api/interfaces/LabelProps.md +1 -1
- package/docs/api/interfaces/LoggerConfig.md +1 -1
- package/docs/api/interfaces/LoginFormProps.md +14 -11
- package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
- package/docs/api/interfaces/NavigationContextType.md +1 -1
- package/docs/api/interfaces/NavigationGuardProps.md +1 -1
- package/docs/api/interfaces/NavigationItem.md +11 -11
- package/docs/api/interfaces/NavigationMenuProps.md +15 -15
- package/docs/api/interfaces/NavigationProviderProps.md +1 -1
- package/docs/api/interfaces/Organisation.md +1 -1
- package/docs/api/interfaces/OrganisationContextType.md +1 -1
- package/docs/api/interfaces/OrganisationMembership.md +1 -1
- package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
- package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
- package/docs/api/interfaces/PaceAppLayoutProps.md +30 -27
- package/docs/api/interfaces/PaceLoginPageProps.md +6 -4
- package/docs/api/interfaces/PageAccessRecord.md +1 -1
- package/docs/api/interfaces/PagePermissionContextType.md +1 -1
- package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
- package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
- package/docs/api/interfaces/PaletteData.md +1 -1
- package/docs/api/interfaces/ParsedAddress.md +1 -1
- package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
- package/docs/api/interfaces/ProgressProps.md +1 -1
- package/docs/api/interfaces/ProtectedRouteProps.md +7 -26
- package/docs/api/interfaces/PublicPageFooterProps.md +9 -9
- package/docs/api/interfaces/PublicPageHeaderProps.md +10 -10
- package/docs/api/interfaces/PublicPageLayoutProps.md +7 -20
- package/docs/api/interfaces/QuickFix.md +1 -1
- package/docs/api/interfaces/RBACAccessValidateParams.md +1 -1
- package/docs/api/interfaces/RBACAccessValidateResult.md +1 -1
- package/docs/api/interfaces/RBACAuditLogParams.md +1 -1
- package/docs/api/interfaces/RBACAuditLogResult.md +1 -1
- package/docs/api/interfaces/RBACConfig.md +1 -1
- package/docs/api/interfaces/RBACContext.md +1 -1
- package/docs/api/interfaces/RBACLogger.md +1 -1
- package/docs/api/interfaces/RBACPageAccessCheckParams.md +1 -1
- package/docs/api/interfaces/RBACPerformanceMetrics.md +1 -1
- package/docs/api/interfaces/RBACPermissionCheckParams.md +1 -1
- package/docs/api/interfaces/RBACPermissionCheckResult.md +1 -1
- package/docs/api/interfaces/RBACPermissionsGetParams.md +1 -1
- package/docs/api/interfaces/RBACPermissionsGetResult.md +1 -1
- package/docs/api/interfaces/RBACResult.md +1 -1
- package/docs/api/interfaces/RBACRoleGrantParams.md +1 -1
- package/docs/api/interfaces/RBACRoleGrantResult.md +1 -1
- package/docs/api/interfaces/RBACRoleRevokeParams.md +1 -1
- package/docs/api/interfaces/RBACRoleRevokeResult.md +1 -1
- package/docs/api/interfaces/RBACRoleValidateParams.md +1 -1
- package/docs/api/interfaces/RBACRoleValidateResult.md +1 -1
- package/docs/api/interfaces/RBACRolesListParams.md +1 -1
- package/docs/api/interfaces/RBACRolesListResult.md +1 -1
- package/docs/api/interfaces/RBACSessionTrackParams.md +1 -1
- package/docs/api/interfaces/RBACSessionTrackResult.md +1 -1
- package/docs/api/interfaces/ResourcePermissions.md +1 -1
- package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
- package/docs/api/interfaces/RoleManagementResult.md +1 -1
- package/docs/api/interfaces/RouteAccessRecord.md +1 -1
- package/docs/api/interfaces/RouteConfig.md +1 -1
- package/docs/api/interfaces/RuntimeComplianceResult.md +1 -1
- package/docs/api/interfaces/SecureDataContextType.md +9 -9
- package/docs/api/interfaces/SecureDataProviderProps.md +8 -8
- package/docs/api/interfaces/SessionRestorationLoaderProps.md +3 -3
- package/docs/api/interfaces/SetupIssue.md +1 -1
- package/docs/api/interfaces/StorageConfig.md +1 -1
- package/docs/api/interfaces/StorageFileInfo.md +1 -1
- package/docs/api/interfaces/StorageFileMetadata.md +1 -1
- package/docs/api/interfaces/StorageListOptions.md +1 -1
- package/docs/api/interfaces/StorageListResult.md +1 -1
- package/docs/api/interfaces/StorageUploadOptions.md +1 -1
- package/docs/api/interfaces/StorageUploadResult.md +1 -1
- package/docs/api/interfaces/StorageUrlOptions.md +1 -1
- package/docs/api/interfaces/StyleImport.md +1 -1
- package/docs/api/interfaces/SwitchProps.md +1 -1
- package/docs/api/interfaces/TabsContentProps.md +1 -1
- package/docs/api/interfaces/TabsListProps.md +1 -1
- package/docs/api/interfaces/TabsProps.md +1 -1
- package/docs/api/interfaces/TabsTriggerProps.md +3 -3
- package/docs/api/interfaces/TextareaProps.md +1 -1
- package/docs/api/interfaces/ToastActionElement.md +4 -1
- package/docs/api/interfaces/ToastProps.md +1 -1
- package/docs/api/interfaces/UnifiedAuthContextType.md +58 -55
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +15 -13
- package/docs/api/interfaces/UseFormDialogOptions.md +1 -1
- package/docs/api/interfaces/UseFormDialogReturn.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +11 -9
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +8 -8
- package/docs/api/interfaces/UsePublicEventLogoOptions.md +6 -6
- package/docs/api/interfaces/UsePublicEventLogoReturn.md +9 -6
- package/docs/api/interfaces/UsePublicEventOptions.md +3 -3
- package/docs/api/interfaces/UsePublicEventReturn.md +8 -5
- package/docs/api/interfaces/UsePublicFileDisplayOptions.md +4 -4
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +12 -9
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +10 -7
- package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
- package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
- package/docs/api/interfaces/UserEventAccess.md +14 -11
- package/docs/api/interfaces/UserMenuProps.md +8 -6
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +575 -634
- package/docs/architecture/database-schema-requirements.md +161 -0
- package/docs/core-concepts/rbac-system.md +3 -3
- package/docs/documentation-index.md +2 -4
- package/docs/getting-started/cursor-rules.md +263 -0
- package/docs/getting-started/installation-guide.md +6 -1
- package/docs/getting-started/quick-start.md +6 -1
- package/docs/migration/DOCUMENTATION_STRUCTURE.md +441 -0
- package/docs/migration/MIGRATION_GUIDE.md +6 -28
- package/docs/migration/README.md +52 -6
- package/docs/migration/V0.5.190_TO_V0.6.1_MIGRATION.md +1153 -0
- package/docs/migration/V0.6.0_REACT_19_MIGRATION.md +227 -0
- package/docs/migration/database-changes-december-2025.md +3 -3
- package/docs/rbac/event-based-apps.md +1 -1
- package/docs/rbac/getting-started.md +1 -1
- package/docs/rbac/quick-start.md +1 -1
- package/docs/standards/README.md +40 -0
- package/docs/troubleshooting/migration.md +4 -4
- package/examples/PublicPages/PublicEventPage.tsx +1 -1
- package/package.json +12 -6
- package/scripts/audit/core/checks/accessibility.cjs +197 -0
- package/scripts/audit/core/checks/api-usage.cjs +191 -0
- package/scripts/audit/core/checks/bundle.cjs +142 -0
- package/scripts/{check-pace-core-compliance.cjs → audit/core/checks/compliance.cjs} +737 -691
- package/scripts/audit/core/checks/config.cjs +54 -0
- package/scripts/audit/core/checks/coverage.cjs +84 -0
- package/scripts/audit/core/checks/dependencies.cjs +454 -0
- package/scripts/audit/core/checks/documentation.cjs +203 -0
- package/scripts/audit/core/checks/environment.cjs +128 -0
- package/scripts/audit/core/checks/error-handling.cjs +299 -0
- package/scripts/audit/core/checks/forms.cjs +172 -0
- package/scripts/audit/core/checks/heuristics.cjs +68 -0
- package/scripts/audit/core/checks/hooks.cjs +334 -0
- package/scripts/audit/core/checks/imports.cjs +244 -0
- package/scripts/audit/core/checks/performance.cjs +325 -0
- package/scripts/audit/core/checks/routes.cjs +117 -0
- package/scripts/audit/core/checks/state.cjs +130 -0
- package/scripts/audit/core/checks/structure.cjs +65 -0
- package/scripts/audit/core/checks/style.cjs +584 -0
- package/scripts/audit/core/checks/testing.cjs +122 -0
- package/scripts/audit/core/checks/typescript.cjs +61 -0
- package/scripts/audit/core/scanner.cjs +199 -0
- package/scripts/audit/core/utils.cjs +137 -0
- package/scripts/audit/index.cjs +223 -0
- package/scripts/audit/reporters/console.cjs +151 -0
- package/scripts/audit/reporters/json.cjs +54 -0
- package/scripts/audit/reporters/markdown.cjs +124 -0
- package/scripts/audit-consuming-app.cjs +86 -0
- package/scripts/build-docs/build-decision.js +240 -0
- package/scripts/build-docs/cache-utils.js +105 -0
- package/scripts/build-docs/content-normalization.js +150 -0
- package/scripts/build-docs/file-utils.js +105 -0
- package/scripts/build-docs/git-utils.js +86 -0
- package/scripts/build-docs/hash-utils.js +116 -0
- package/scripts/build-docs/typedoc-runner.js +220 -0
- package/scripts/build-docs-incremental.js +77 -913
- package/scripts/install-cursor-rules.cjs +236 -0
- package/scripts/utils/command-runner.js +16 -11
- package/scripts/validate-formats.js +61 -56
- package/scripts/validate-master.js +74 -69
- package/scripts/validate-pre-publish.js +70 -65
- package/src/__tests__/helpers/test-providers.tsx +1 -1
- package/src/__tests__/helpers/test-utils.tsx +1 -1
- package/src/__tests__/hooks/usePermissions.test.ts +2 -2
- package/src/components/Alert/Alert.test.tsx +12 -18
- package/src/components/Alert/Alert.tsx +5 -7
- package/src/components/Avatar/Avatar.test.tsx +4 -4
- package/src/components/Badge/Badge.tsx +16 -4
- package/src/components/Button/Button.tsx +27 -4
- package/src/components/Calendar/Calendar.tsx +9 -3
- package/src/components/Card/Card.tsx +4 -0
- package/src/components/Checkbox/Checkbox.test.tsx +12 -12
- package/src/components/Checkbox/Checkbox.tsx +2 -2
- package/src/components/DataTable/DataTable.test.tsx +57 -93
- package/src/components/DataTable/DataTable.tsx +40 -6
- package/src/components/DataTable/__tests__/DataTableCore.test-setup.ts +5 -6
- package/src/components/DataTable/__tests__/pagination.modes.test.tsx +29 -7
- package/src/components/DataTable/__tests__/ssr.strict-mode.test.tsx +12 -12
- package/src/components/DataTable/__tests__/test-utils/sharedTestUtils.tsx +2 -3
- package/src/components/DataTable/components/AccessDeniedPage.tsx +17 -26
- package/src/components/DataTable/components/ActionButtons.tsx +10 -7
- package/src/components/DataTable/components/BulkOperationsDropdown.tsx +2 -2
- package/src/components/DataTable/components/ColumnFilter.tsx +10 -0
- package/src/components/DataTable/components/ColumnVisibilityDropdown.tsx +12 -0
- package/src/components/DataTable/components/DataTableBody.tsx +8 -0
- package/src/components/DataTable/components/DataTableCore.tsx +200 -561
- package/src/components/DataTable/components/DataTableErrorBoundary.tsx +11 -0
- package/src/components/DataTable/components/DataTableLayout.tsx +559 -0
- package/src/components/DataTable/components/DataTableModals.tsx +9 -1
- package/src/components/DataTable/components/DataTableToolbar.tsx +8 -0
- package/src/components/DataTable/components/DraggableColumnHeader.tsx +12 -0
- package/src/components/DataTable/components/EditFields.tsx +307 -0
- package/src/components/DataTable/components/EditableRow.tsx +9 -1
- package/src/components/DataTable/components/EmptyState.tsx +10 -0
- package/src/components/DataTable/components/FilterRow.tsx +12 -0
- package/src/components/DataTable/components/GroupHeader.tsx +12 -0
- package/src/components/DataTable/components/GroupingDropdown.tsx +12 -0
- package/src/components/DataTable/components/ImportModal.tsx +7 -0
- package/src/components/DataTable/components/LoadingState.tsx +6 -0
- package/src/components/DataTable/components/PaginationControls.tsx +16 -1
- package/src/components/DataTable/components/RowComponent.tsx +391 -0
- package/src/components/DataTable/components/UnifiedTableBody.tsx +62 -852
- package/src/components/DataTable/components/VirtualizedDataTable.tsx +16 -4
- package/src/components/DataTable/components/__tests__/AccessDeniedPage.test.tsx +4 -2
- package/src/components/DataTable/components/__tests__/DataTableModals.test.tsx +23 -23
- package/src/components/DataTable/components/__tests__/EditableRow.test.tsx +11 -11
- package/src/components/DataTable/components/__tests__/ExpandButton.test.tsx +36 -36
- package/src/components/DataTable/components/__tests__/GroupHeader.test.tsx +27 -27
- package/src/components/DataTable/components/__tests__/ImportModal.test.tsx +39 -39
- package/src/components/DataTable/components/__tests__/UnifiedTableBody.test.tsx +33 -33
- package/src/components/DataTable/components/__tests__/ViewRowModal.test.tsx +29 -29
- package/src/components/DataTable/components/cellValueUtils.ts +40 -0
- package/src/components/DataTable/components/hooks/useImportModalFocus.ts +53 -0
- package/src/components/DataTable/components/hooks/usePermissionTracking.ts +126 -0
- package/src/components/DataTable/context/DataTableContext.tsx +50 -0
- package/src/components/DataTable/core/ColumnFactory.ts +31 -0
- package/src/components/DataTable/core/DataTableContext.tsx +32 -1
- package/src/components/DataTable/hooks/useColumnOrderPersistence.ts +10 -0
- package/src/components/DataTable/hooks/useColumnReordering.ts +14 -2
- package/src/components/DataTable/hooks/useColumnVisibilityPersistence.ts +10 -0
- package/src/components/DataTable/hooks/useDataTableDataPipeline.ts +16 -0
- package/src/components/DataTable/hooks/useDataTablePermissions.ts +124 -32
- package/src/components/DataTable/hooks/useDataTableState.ts +35 -1
- package/src/components/DataTable/hooks/useEffectiveColumnOrder.ts +12 -0
- package/src/components/DataTable/hooks/useKeyboardNavigation.ts +2 -2
- package/src/components/DataTable/hooks/useServerSideDataEffect.ts +11 -0
- package/src/components/DataTable/hooks/useTableColumns.ts +8 -0
- package/src/components/DataTable/hooks/useTableHandlers.ts +14 -0
- package/src/components/DataTable/styles.ts +6 -6
- package/src/components/DataTable/types.ts +6 -10
- package/src/components/DataTable/utils/a11yUtils.ts +7 -0
- package/src/components/DataTable/utils/debugTools.ts +18 -113
- package/src/components/DataTable/utils/errorHandling.ts +12 -0
- package/src/components/DataTable/utils/exportUtils.ts +9 -0
- package/src/components/DataTable/utils/flexibleImport.ts +12 -48
- package/src/components/DataTable/utils/paginationUtils.ts +8 -0
- package/src/components/DataTable/utils/performanceUtils.ts +5 -1
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +8 -14
- package/src/components/Dialog/Dialog.tsx +8 -7
- package/src/components/ErrorBoundary/ErrorBoundary.test.tsx +180 -1
- package/src/components/ErrorBoundary/ErrorBoundary.tsx +46 -6
- package/src/components/ErrorBoundary/ErrorBoundaryContext.tsx +129 -0
- package/src/components/ErrorBoundary/index.ts +27 -2
- package/src/components/EventSelector/EventSelector.tsx +4 -1
- package/src/components/FileDisplay/FileDisplay.test.tsx +2 -2
- package/src/components/FileDisplay/FileDisplay.tsx +32 -18
- package/src/components/FileUpload/FileUpload.tsx +22 -2
- package/src/components/Footer/Footer.test.tsx +16 -16
- package/src/components/Footer/Footer.tsx +15 -12
- package/src/components/Form/Form.test.tsx +36 -15
- package/src/components/Form/Form.tsx +31 -26
- package/src/components/Header/Header.tsx +22 -11
- package/src/components/InactivityWarningModal/InactivityWarningModal.test.tsx +40 -40
- package/src/components/InactivityWarningModal/InactivityWarningModal.tsx +1 -1
- package/src/components/Input/Input.test.tsx +2 -2
- package/src/components/Input/Input.tsx +36 -34
- package/src/components/Label/Label.tsx +1 -1
- package/src/components/LoadingSpinner/LoadingSpinner.test.tsx +4 -4
- package/src/components/LoadingSpinner/LoadingSpinner.tsx +1 -1
- package/src/components/LoginForm/LoginForm.test.tsx +42 -42
- package/src/components/LoginForm/LoginForm.tsx +12 -8
- package/src/components/NavigationMenu/NavigationMenu.tsx +15 -514
- package/src/components/NavigationMenu/types.ts +56 -0
- package/src/components/NavigationMenu/useNavigationFiltering.ts +390 -0
- package/src/components/OrganisationSelector/OrganisationSelector.tsx +3 -0
- package/src/components/PaceAppLayout/PaceAppLayout.performance.test.tsx +1 -1
- package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +54 -52
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +33 -12
- package/src/components/PaceAppLayout/README.md +1 -1
- package/src/components/PaceAppLayout/test-setup.tsx +1 -2
- package/src/components/PaceLoginPage/PaceLoginPage.tsx +4 -1
- package/src/components/PasswordChange/PasswordChangeForm.test.tsx +33 -33
- package/src/components/PasswordChange/PasswordChangeForm.tsx +10 -1
- package/src/components/Progress/Progress.tsx +1 -1
- package/src/components/ProtectedRoute/ProtectedRoute.tsx +3 -9
- package/src/components/PublicLayout/PublicPageLayout.tsx +3 -6
- package/src/components/PublicLayout/PublicPageProvider.tsx +4 -0
- package/src/components/Select/Select.tsx +95 -438
- package/src/components/Select/context.ts +23 -0
- package/src/components/Select/hooks/useSelectEvents.ts +87 -0
- package/src/components/Select/hooks/useSelectSearch.ts +91 -0
- package/src/components/Select/hooks/useSelectState.ts +104 -0
- package/src/components/Select/index.ts +9 -1
- package/src/components/Select/types.ts +123 -0
- package/src/components/Select/utils/text.ts +26 -0
- package/src/components/SessionRestorationLoader/SessionRestorationLoader.tsx +5 -6
- package/src/components/Switch/Switch.tsx +4 -4
- package/src/components/Table/Table.tsx +1 -1
- package/src/components/Tabs/Tabs.tsx +1 -1
- package/src/components/Textarea/Textarea.tsx +27 -29
- package/src/components/Toast/Toast.tsx +5 -1
- package/src/components/Tooltip/Tooltip.tsx +3 -3
- package/src/components/UserMenu/UserMenu.test.tsx +24 -11
- package/src/components/UserMenu/UserMenu.tsx +22 -19
- package/src/components/index.ts +2 -2
- package/src/hooks/__tests__/hooks.integration.test.tsx +80 -55
- package/src/hooks/__tests__/index.unit.test.ts +2 -5
- package/src/hooks/__tests__/useStorage.unit.test.ts +36 -36
- package/src/hooks/index.ts +1 -2
- package/src/hooks/public/usePublicEvent.ts +5 -1
- package/src/hooks/public/usePublicEventLogo.ts +5 -1
- package/src/hooks/public/usePublicFileDisplay.ts +4 -0
- package/src/hooks/public/usePublicRouteParams.ts +5 -1
- package/src/hooks/services/useAuth.ts +32 -0
- package/src/hooks/services/useCurrentEvent.ts +6 -0
- package/src/hooks/services/useCurrentOrganisation.ts +6 -0
- package/src/hooks/useDataTableState.ts +8 -18
- package/src/hooks/useDebounce.ts +9 -0
- package/src/hooks/useEventTheme.ts +6 -0
- package/src/hooks/useFileDisplay.ts +4 -0
- package/src/hooks/useFileReference.ts +25 -7
- package/src/hooks/useFileUrl.ts +11 -1
- package/src/hooks/useFocusManagement.ts +16 -2
- package/src/hooks/useFocusTrap.ts +7 -4
- package/src/hooks/useFormDialog.ts +8 -7
- package/src/hooks/useInactivityTracker.ts +4 -1
- package/src/hooks/useKeyboardShortcuts.ts +4 -0
- package/src/hooks/useOrganisationPermissions.ts +4 -0
- package/src/hooks/useOrganisationSecurity.ts +4 -0
- package/src/hooks/usePerformanceMonitor.ts +4 -0
- package/src/hooks/usePermissionCache.ts +8 -1
- package/src/hooks/useQueryCache.ts +12 -1
- package/src/hooks/useSessionRestoration.ts +4 -0
- package/src/hooks/useStorage.ts +4 -0
- package/src/hooks/useToast.ts +3 -3
- package/src/index.ts +2 -1
- package/src/providers/__tests__/OrganisationProvider.test.tsx +115 -49
- package/src/providers/__tests__/ProviderLifecycle.test.tsx +21 -6
- package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +10 -10
- package/src/providers/services/AuthServiceProvider.tsx +18 -0
- package/src/providers/services/EventServiceProvider.tsx +18 -0
- package/src/providers/services/InactivityServiceProvider.tsx +18 -0
- package/src/providers/services/OrganisationServiceProvider.tsx +18 -0
- package/src/providers/services/UnifiedAuthProvider.tsx +58 -22
- package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +33 -7
- package/src/rbac/README.md +1 -1
- package/src/rbac/__tests__/adapters.comprehensive.test.tsx +26 -26
- package/src/rbac/__tests__/scenarios.user-role.test.tsx +4 -5
- package/src/rbac/adapters.tsx +14 -5
- package/src/rbac/api.ts +100 -67
- package/src/rbac/components/EnhancedNavigationMenu.tsx +1 -1
- package/src/rbac/components/NavigationGuard.tsx +1 -1
- package/src/rbac/components/NavigationProvider.tsx +5 -2
- package/src/rbac/components/PagePermissionGuard.tsx +158 -18
- package/src/rbac/components/PagePermissionProvider.tsx +1 -1
- package/src/rbac/components/PermissionEnforcer.tsx +1 -1
- package/src/rbac/components/RoleBasedRouter.tsx +6 -2
- package/src/rbac/components/SecureDataProvider.test.tsx +84 -49
- package/src/rbac/components/SecureDataProvider.tsx +21 -6
- package/src/rbac/components/__tests__/PagePermissionGuard.race-condition.test.tsx +24 -14
- package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +7 -0
- package/src/rbac/components/__tests__/PagePermissionGuard.verification.test.tsx +14 -6
- package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +15 -4
- package/src/rbac/components/__tests__/SecureDataProvider.fixed.test.tsx +148 -24
- package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +81 -15
- package/src/rbac/engine.ts +38 -14
- package/src/rbac/hooks/permissions/index.ts +7 -0
- package/src/rbac/hooks/permissions/useAccessLevel.ts +105 -0
- package/src/rbac/hooks/permissions/useCachedPermissions.ts +79 -0
- package/src/rbac/hooks/permissions/useCan.ts +347 -0
- package/src/rbac/hooks/permissions/useHasAllPermissions.ts +90 -0
- package/src/rbac/hooks/permissions/useHasAnyPermission.ts +90 -0
- package/src/rbac/hooks/permissions/useMultiplePermissions.ts +93 -0
- package/src/rbac/hooks/permissions/usePermissions.ts +253 -0
- package/src/rbac/hooks/useCan.test.ts +71 -64
- package/src/rbac/hooks/usePermissions.ts +14 -995
- package/src/rbac/hooks/useResourcePermissions.test.ts +54 -18
- package/src/rbac/hooks/useResourcePermissions.ts +14 -4
- package/src/rbac/hooks/useSecureSupabase.ts +33 -13
- package/src/rbac/permissions.ts +0 -30
- package/src/rbac/secureClient.ts +212 -61
- package/src/rbac/types.ts +8 -0
- package/src/theming/__tests__/parseEventColours.test.ts +6 -9
- package/src/theming/parseEventColours.ts +5 -19
- package/src/types/vitest-globals.d.ts +51 -26
- package/src/utils/__mocks__/supabaseMock.ts +1 -3
- package/src/utils/__tests__/formatting.unit.test.ts +4 -4
- package/src/utils/__tests__/index.unit.test.ts +2 -2
- package/src/utils/audit/audit.ts +0 -3
- package/src/utils/core/cn.ts +1 -1
- package/src/utils/file-reference/index.ts +53 -1
- package/src/utils/formatting/formatting.ts +8 -18
- package/src/utils/index.ts +0 -1
- package/src/utils/security/secureDataAccess.test.ts +31 -20
- package/src/utils/security/secureDataAccess.ts +4 -3
- package/dist/chunk-6C4YBBJM.js +0 -628
- package/dist/chunk-6C4YBBJM.js.map +0 -1
- package/dist/chunk-7D4SUZUM.js 2.map +0 -1
- package/dist/chunk-7EQTDTTJ.js 2.map +0 -1
- package/dist/chunk-7EQTDTTJ.js.map +0 -1
- package/dist/chunk-7FLMSG37.js 2.map +0 -1
- package/dist/chunk-7FLMSG37.js.map +0 -1
- package/dist/chunk-BC4IJKSL.js.map +0 -1
- package/dist/chunk-E3SPN4VZ.js +0 -12917
- package/dist/chunk-E3SPN4VZ.js.map +0 -1
- package/dist/chunk-E66EQZE6 5.js +0 -37
- package/dist/chunk-E66EQZE6.js 2.map +0 -1
- package/dist/chunk-HWIIPPNI.js.map +0 -1
- package/dist/chunk-I7PSE6JW 5.js +0 -191
- package/dist/chunk-I7PSE6JW.js 2.map +0 -1
- package/dist/chunk-I7PSE6JW.js.map +0 -1
- package/dist/chunk-IIELH4DL.js.map +0 -1
- package/dist/chunk-KNC55RTG.js 5.map +0 -1
- package/dist/chunk-KNC55RTG.js.map +0 -1
- package/dist/chunk-KQCRWDSA.js 5.map +0 -1
- package/dist/chunk-LFNCN2SP.js +0 -412
- package/dist/chunk-LFNCN2SP.js 2.map +0 -1
- package/dist/chunk-LFNCN2SP.js.map +0 -1
- package/dist/chunk-LMC26NLJ 2.js +0 -84
- package/dist/chunk-NOAYCWCX.js +0 -4993
- package/dist/chunk-NOAYCWCX.js.map +0 -1
- package/dist/chunk-QWWZ5CAQ.js 3.map +0 -1
- package/dist/chunk-QWWZ5CAQ.js.map +0 -1
- package/dist/chunk-QXHPKYJV 3.js +0 -113
- package/dist/chunk-R77UEZ4E.js +0 -68
- package/dist/chunk-R77UEZ4E.js.map +0 -1
- package/dist/chunk-SQGMNID3.js.map +0 -1
- package/dist/chunk-VBXEHIUJ.js 6.map +0 -1
- package/dist/chunk-XNXXZ43G.js.map +0 -1
- package/dist/chunk-ZSAAAMVR 6.js +0 -25
- package/dist/components.js 5.map +0 -1
- package/dist/styles/index 2.js +0 -12
- package/dist/styles/index.js 5.map +0 -1
- package/dist/theming/runtime 5.js +0 -19
- package/dist/theming/runtime.js 5.map +0 -1
- package/docs/api/classes/ErrorBoundary.md +0 -144
- package/docs/migration/quick-migration-guide.md +0 -356
- package/docs/migration/service-architecture.md +0 -281
- package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +0 -680
- package/src/hooks/useSecureDataAccess.test.ts +0 -559
- package/src/hooks/useSecureDataAccess.ts +0 -666
- /package/dist/{DataTable-5FU7IESH.js.map → DataTable-TPTKCX4D.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-RGJTDE2C.js.map → UnifiedAuthProvider-CH6Z342H.js.map} +0 -0
- /package/dist/{api-N774RPUA.js.map → api-MVVQZLJI.js.map} +0 -0
- /package/docs/migration/{organisation-context-timing-fix.md → V0.3.44_organisation-context-timing-fix.md} +0 -0
- /package/docs/migration/{rbac-migration.md → V0.4.0_rbac-migration.md} +0 -0
- /package/docs/migration/{person-scoped-profiles-migration-guide.md → V0.5.190_person-scoped-profiles-migration-guide.md} +0 -0
- /package/examples/{rbac → RBAC}/CompleteRBACExample.tsx +0 -0
- /package/examples/{rbac → RBAC}/EventBasedApp.tsx +0 -0
- /package/examples/{rbac → RBAC}/PermissionExample.tsx +0 -0
- /package/examples/{rbac → RBAC}/index.ts +0 -0
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
useAppConfig,
|
|
3
3
|
useEvents,
|
|
4
|
-
useOrganisationSecurity
|
|
5
|
-
|
|
6
|
-
} from "./chunk-IIELH4DL.js";
|
|
4
|
+
useOrganisationSecurity
|
|
5
|
+
} from "./chunk-MMZ7JXPU.js";
|
|
7
6
|
import {
|
|
8
7
|
useOrganisations,
|
|
9
8
|
useUnifiedAuth
|
|
10
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-EHMR7VYL.js";
|
|
11
10
|
import {
|
|
12
11
|
ContextValidator,
|
|
13
12
|
OrganisationContextRequiredError,
|
|
@@ -18,31 +17,41 @@ import {
|
|
|
18
17
|
isPermitted,
|
|
19
18
|
isPermittedCached,
|
|
20
19
|
resolveAppContext
|
|
21
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-24UVZUZG.js";
|
|
22
21
|
import {
|
|
22
|
+
getCurrentAppName
|
|
23
|
+
} from "./chunk-F2IMUDXZ.js";
|
|
24
|
+
import {
|
|
25
|
+
createLogger,
|
|
23
26
|
logger
|
|
24
27
|
} from "./chunk-PWLANIRT.js";
|
|
25
28
|
|
|
26
29
|
// src/rbac/secureClient.ts
|
|
27
30
|
import { createClient } from "@supabase/supabase-js";
|
|
28
|
-
var
|
|
29
|
-
constructor(supabaseUrl, supabaseKey, organisationId, eventId, appId, isSuperAdmin = false) {
|
|
31
|
+
var _SecureSupabaseClient = class _SecureSupabaseClient {
|
|
32
|
+
constructor(supabaseUrl, supabaseKey, organisationId, eventId, appId, isSuperAdmin = false, existingClient) {
|
|
30
33
|
this.edgeFunctionClient = null;
|
|
34
|
+
this.usesExistingClient = false;
|
|
31
35
|
this.supabaseUrl = supabaseUrl;
|
|
32
36
|
this.supabaseKey = supabaseKey;
|
|
33
37
|
this.organisationId = organisationId;
|
|
34
38
|
this.eventId = eventId;
|
|
35
39
|
this.appId = appId;
|
|
36
40
|
this.isSuperAdmin = isSuperAdmin;
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
if (existingClient) {
|
|
42
|
+
this.supabase = existingClient;
|
|
43
|
+
this.usesExistingClient = true;
|
|
44
|
+
} else {
|
|
45
|
+
this.supabase = createClient(supabaseUrl, supabaseKey, {
|
|
46
|
+
global: {
|
|
47
|
+
headers: {
|
|
48
|
+
"x-organisation-id": organisationId || "",
|
|
49
|
+
"x-event-id": eventId || "",
|
|
50
|
+
"x-app-id": appId || ""
|
|
51
|
+
}
|
|
43
52
|
}
|
|
44
|
-
}
|
|
45
|
-
}
|
|
53
|
+
});
|
|
54
|
+
}
|
|
46
55
|
this.setupContextInjection();
|
|
47
56
|
this.setupEdgeFunctionHandling();
|
|
48
57
|
}
|
|
@@ -52,19 +61,20 @@ var SecureSupabaseClient = class _SecureSupabaseClient {
|
|
|
52
61
|
setupContextInjection() {
|
|
53
62
|
const originalFrom = this.supabase.from.bind(this.supabase);
|
|
54
63
|
this.supabase.from = (table) => {
|
|
55
|
-
this.
|
|
64
|
+
this.validateContextForTable(table);
|
|
56
65
|
const query = originalFrom(table);
|
|
57
66
|
query._tableName = table;
|
|
58
67
|
return this.injectContext(query, table);
|
|
59
68
|
};
|
|
60
69
|
const originalRpc = this.supabase.rpc.bind(this.supabase);
|
|
61
70
|
this.supabase.rpc = (fn, args, options) => {
|
|
62
|
-
this.
|
|
71
|
+
this.validateContextForRpc(fn);
|
|
72
|
+
const safeArgs = args ?? {};
|
|
63
73
|
const contextArgs = {
|
|
64
|
-
...
|
|
65
|
-
p_organisation_id: this.organisationId,
|
|
66
|
-
p_event_id: this.eventId,
|
|
67
|
-
p_app_id: this.appId
|
|
74
|
+
...safeArgs,
|
|
75
|
+
...this.organisationId && safeArgs.p_organisation_id === void 0 ? { p_organisation_id: this.organisationId } : {},
|
|
76
|
+
...this.eventId && safeArgs.p_event_id === void 0 ? { p_event_id: this.eventId } : {},
|
|
77
|
+
...this.appId && safeArgs.p_app_id === void 0 ? { p_app_id: this.appId } : {}
|
|
68
78
|
};
|
|
69
79
|
return originalRpc(fn, contextArgs, options);
|
|
70
80
|
};
|
|
@@ -79,6 +89,10 @@ var SecureSupabaseClient = class _SecureSupabaseClient {
|
|
|
79
89
|
* This avoids interfering with the main client's operations.
|
|
80
90
|
*/
|
|
81
91
|
setupEdgeFunctionHandling() {
|
|
92
|
+
if (this.usesExistingClient) {
|
|
93
|
+
this.edgeFunctionClient = null;
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
82
96
|
this.edgeFunctionClient = createClient(this.supabaseUrl, this.supabaseKey);
|
|
83
97
|
}
|
|
84
98
|
/**
|
|
@@ -119,9 +133,18 @@ var SecureSupabaseClient = class _SecureSupabaseClient {
|
|
|
119
133
|
if (this.isSuperAdmin) {
|
|
120
134
|
return originalInsert(values);
|
|
121
135
|
}
|
|
136
|
+
if (!this.organisationId) {
|
|
137
|
+
throw new OrganisationContextRequiredError();
|
|
138
|
+
}
|
|
122
139
|
const contextValues2 = Array.isArray(values) ? values.map((v) => ({ ...v, organisation_id: this.organisationId })) : { ...values, organisation_id: this.organisationId };
|
|
123
140
|
return originalInsert(contextValues2);
|
|
124
141
|
}
|
|
142
|
+
if (this.isSuperAdmin && !this.organisationId) {
|
|
143
|
+
return originalInsert(values);
|
|
144
|
+
}
|
|
145
|
+
if (!this.organisationId) {
|
|
146
|
+
throw new OrganisationContextRequiredError();
|
|
147
|
+
}
|
|
125
148
|
const contextValues = Array.isArray(values) ? values.map((v) => ({ ...v, organisation_id: this.organisationId })) : { ...values, organisation_id: this.organisationId };
|
|
126
149
|
return originalInsert(contextValues);
|
|
127
150
|
};
|
|
@@ -146,6 +169,10 @@ var SecureSupabaseClient = class _SecureSupabaseClient {
|
|
|
146
169
|
* - Super admins: No org filter (see all users) - RLS will allow access
|
|
147
170
|
* - Non-super-admins: Apply org filter as defense in depth - RLS will also filter
|
|
148
171
|
*
|
|
172
|
+
* For system-wide tables (like core_organisations):
|
|
173
|
+
* - Super admins: No org filter (see all records) - RLS will allow access
|
|
174
|
+
* - Non-super-admins: Apply org filter as defense in depth - RLS will also filter
|
|
175
|
+
*
|
|
149
176
|
* For other tables:
|
|
150
177
|
* - Always apply org filter unless super admin bypasses it
|
|
151
178
|
*/
|
|
@@ -157,8 +184,31 @@ var SecureSupabaseClient = class _SecureSupabaseClient {
|
|
|
157
184
|
// App configuration table - no organisation scope
|
|
158
185
|
"rbac_app_pages",
|
|
159
186
|
// Page configuration table - scoped by app_id, not organisation_id
|
|
160
|
-
"rbac_global_roles"
|
|
187
|
+
"rbac_global_roles",
|
|
161
188
|
// Global roles - no organisation scope
|
|
189
|
+
// Person-scoped tables (organisation_id was removed in person-scoped profiles migration)
|
|
190
|
+
"core_person",
|
|
191
|
+
// Person records - person-scoped, no organisation_id
|
|
192
|
+
"core_member",
|
|
193
|
+
// Member profiles - person-scoped, no organisation_id
|
|
194
|
+
"core_contact",
|
|
195
|
+
// Contact profiles - person-scoped, no organisation_id
|
|
196
|
+
"core_consent",
|
|
197
|
+
// Consent records - person-scoped, no organisation_id
|
|
198
|
+
"core_identification",
|
|
199
|
+
// Identification records - person-scoped, no organisation_id
|
|
200
|
+
"core_qualification",
|
|
201
|
+
// Qualification records - person-scoped, no organisation_id
|
|
202
|
+
"medi_profile",
|
|
203
|
+
// Medical profiles - person-scoped, no organisation_id
|
|
204
|
+
"medi_condition",
|
|
205
|
+
// Medical conditions - person-scoped via medi_profile, no organisation_id
|
|
206
|
+
"medi_diet",
|
|
207
|
+
// Medical diets - person-scoped via medi_profile, no organisation_id
|
|
208
|
+
"medi_action_plan",
|
|
209
|
+
// Medical action plans - person-scoped via medi_profile, no organisation_id
|
|
210
|
+
"medi_profile_versions"
|
|
211
|
+
// Medical profile versions - person-scoped via medi_profile, no organisation_id
|
|
162
212
|
];
|
|
163
213
|
if (tablesWithoutOrganisationId.includes(tableName)) {
|
|
164
214
|
return query;
|
|
@@ -166,6 +216,13 @@ var SecureSupabaseClient = class _SecureSupabaseClient {
|
|
|
166
216
|
if (!this.organisationId) {
|
|
167
217
|
return query;
|
|
168
218
|
}
|
|
219
|
+
const systemWideTablesForSuperAdmins = [
|
|
220
|
+
"core_organisations"
|
|
221
|
+
// Super-admins need to see all organisations
|
|
222
|
+
];
|
|
223
|
+
if (systemWideTablesForSuperAdmins.includes(tableName) && this.isSuperAdmin) {
|
|
224
|
+
return query;
|
|
225
|
+
}
|
|
169
226
|
if (tableName === "rbac_user_profiles") {
|
|
170
227
|
if (this.isSuperAdmin) {
|
|
171
228
|
return query;
|
|
@@ -179,12 +236,57 @@ var SecureSupabaseClient = class _SecureSupabaseClient {
|
|
|
179
236
|
}
|
|
180
237
|
/**
|
|
181
238
|
* Validate that required context is present
|
|
239
|
+
* Super-admins can operate without organisation context
|
|
182
240
|
*/
|
|
183
241
|
validateContext() {
|
|
242
|
+
if (this.isSuperAdmin) {
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
184
245
|
if (!this.organisationId) {
|
|
185
246
|
throw new OrganisationContextRequiredError();
|
|
186
247
|
}
|
|
187
248
|
}
|
|
249
|
+
/**
|
|
250
|
+
* Determine whether a table requires organisation context.
|
|
251
|
+
* Tables without an organisation_id column (or global configuration tables) are safe without org context.
|
|
252
|
+
*/
|
|
253
|
+
tableRequiresOrganisationContext(tableName) {
|
|
254
|
+
const tablesWithoutOrganisationId = /* @__PURE__ */ new Set([
|
|
255
|
+
"core_organisations",
|
|
256
|
+
"rbac_apps",
|
|
257
|
+
"rbac_app_pages",
|
|
258
|
+
"rbac_global_roles",
|
|
259
|
+
"core_person",
|
|
260
|
+
"core_member",
|
|
261
|
+
"core_contact",
|
|
262
|
+
"core_consent",
|
|
263
|
+
"core_identification",
|
|
264
|
+
"core_qualification",
|
|
265
|
+
"medi_profile",
|
|
266
|
+
"medi_condition",
|
|
267
|
+
"medi_diet",
|
|
268
|
+
"medi_action_plan",
|
|
269
|
+
"medi_profile_versions"
|
|
270
|
+
]);
|
|
271
|
+
return !tablesWithoutOrganisationId.has(tableName);
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Validate context for a specific table operation.
|
|
275
|
+
*/
|
|
276
|
+
validateContextForTable(tableName) {
|
|
277
|
+
if (this.isSuperAdmin) return;
|
|
278
|
+
if (!this.organisationId && this.tableRequiresOrganisationContext(tableName)) {
|
|
279
|
+
throw new OrganisationContextRequiredError();
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Validate context for a specific RPC call.
|
|
284
|
+
*/
|
|
285
|
+
validateContextForRpc(fn) {
|
|
286
|
+
if (this.isSuperAdmin) return;
|
|
287
|
+
if (_SecureSupabaseClient.GLOBAL_RPC_ALLOWLIST.has(fn)) return;
|
|
288
|
+
this.validateContext();
|
|
289
|
+
}
|
|
188
290
|
/**
|
|
189
291
|
* Get the current organisation ID
|
|
190
292
|
*/
|
|
@@ -210,7 +312,7 @@ var SecureSupabaseClient = class _SecureSupabaseClient {
|
|
|
210
312
|
return new _SecureSupabaseClient(
|
|
211
313
|
this.supabaseUrl,
|
|
212
314
|
this.supabaseKey,
|
|
213
|
-
updates.organisationId
|
|
315
|
+
updates.organisationId !== void 0 ? updates.organisationId : this.organisationId,
|
|
214
316
|
updates.eventId !== void 0 ? updates.eventId : this.eventId,
|
|
215
317
|
updates.appId !== void 0 ? updates.appId : this.appId,
|
|
216
318
|
updates.isSuperAdmin !== void 0 ? updates.isSuperAdmin : this.isSuperAdmin
|
|
@@ -231,15 +333,216 @@ var SecureSupabaseClient = class _SecureSupabaseClient {
|
|
|
231
333
|
});
|
|
232
334
|
}
|
|
233
335
|
};
|
|
336
|
+
/**
|
|
337
|
+
* RPC functions that are safe to call without organisation context.
|
|
338
|
+
*
|
|
339
|
+
* These functions must:
|
|
340
|
+
* - rely on JWT context (auth.uid()) for authentication
|
|
341
|
+
* - not read or write organisation-scoped data
|
|
342
|
+
*
|
|
343
|
+
* This allowlist enables compliant consuming apps to use `secureSupabase.rpc(...)`
|
|
344
|
+
* even before an organisation is selected (common during initial page load/refresh).
|
|
345
|
+
*/
|
|
346
|
+
_SecureSupabaseClient.GLOBAL_RPC_ALLOWLIST = /* @__PURE__ */ new Set([
|
|
347
|
+
"data_rbac_apps_list"
|
|
348
|
+
]);
|
|
349
|
+
var SecureSupabaseClient = _SecureSupabaseClient;
|
|
234
350
|
function createSecureClient(supabaseUrl, supabaseKey, organisationId, eventId, appId, isSuperAdmin = false) {
|
|
235
351
|
return new SecureSupabaseClient(supabaseUrl, supabaseKey, organisationId, eventId, appId, isSuperAdmin);
|
|
236
352
|
}
|
|
237
|
-
function fromSupabaseClient(client, organisationId, eventId, appId) {
|
|
238
|
-
|
|
353
|
+
function fromSupabaseClient(client, organisationId, eventId, appId, isSuperAdmin = false) {
|
|
354
|
+
return new SecureSupabaseClient("", "", organisationId, eventId, appId, isSuperAdmin, client);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// src/rbac/hooks/useResolvedScope.ts
|
|
358
|
+
import { useEffect, useState, useRef, useMemo } from "react";
|
|
359
|
+
var log = createLogger("useResolvedScope");
|
|
360
|
+
var appConfigCache = /* @__PURE__ */ new Map();
|
|
361
|
+
var CACHE_TTL = 5 * 60 * 1e3;
|
|
362
|
+
function useResolvedScope({
|
|
363
|
+
supabase,
|
|
364
|
+
selectedOrganisationId,
|
|
365
|
+
selectedEventId
|
|
366
|
+
}) {
|
|
367
|
+
const [resolvedScope, setResolvedScope] = useState(null);
|
|
368
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
369
|
+
const [error, setError] = useState(null);
|
|
370
|
+
const stableScopeRef = useRef({
|
|
371
|
+
organisationId: "",
|
|
372
|
+
appId: "",
|
|
373
|
+
eventId: void 0
|
|
374
|
+
});
|
|
375
|
+
useEffect(() => {
|
|
376
|
+
if (resolvedScope) {
|
|
377
|
+
const newScope = {
|
|
378
|
+
organisationId: resolvedScope.organisationId || "",
|
|
379
|
+
appId: resolvedScope.appId || "",
|
|
380
|
+
eventId: resolvedScope.eventId
|
|
381
|
+
};
|
|
382
|
+
if (stableScopeRef.current.organisationId !== newScope.organisationId || stableScopeRef.current.eventId !== newScope.eventId || stableScopeRef.current.appId !== newScope.appId) {
|
|
383
|
+
stableScopeRef.current = {
|
|
384
|
+
organisationId: newScope.organisationId,
|
|
385
|
+
appId: newScope.appId,
|
|
386
|
+
eventId: newScope.eventId
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
} else {
|
|
390
|
+
stableScopeRef.current = { organisationId: "", appId: "", eventId: void 0 };
|
|
391
|
+
}
|
|
392
|
+
}, [resolvedScope]);
|
|
393
|
+
const appName = getCurrentAppName();
|
|
394
|
+
const stableScope = stableScopeRef.current;
|
|
395
|
+
useEffect(() => {
|
|
396
|
+
let cancelled = false;
|
|
397
|
+
const resolveScope = async () => {
|
|
398
|
+
if (!supabase && !selectedOrganisationId && !selectedEventId) {
|
|
399
|
+
if (!cancelled) {
|
|
400
|
+
setResolvedScope(null);
|
|
401
|
+
setIsLoading(false);
|
|
402
|
+
setError(null);
|
|
403
|
+
}
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
setIsLoading(true);
|
|
407
|
+
setError(null);
|
|
408
|
+
try {
|
|
409
|
+
const appName2 = getCurrentAppName();
|
|
410
|
+
let appId = void 0;
|
|
411
|
+
let appConfig = null;
|
|
412
|
+
if (supabase && appName2) {
|
|
413
|
+
try {
|
|
414
|
+
const { data: session } = await supabase.auth.getSession();
|
|
415
|
+
if (!session?.session) {
|
|
416
|
+
log.debug(`Skipping app resolution for "${appName2}" - user not authenticated`);
|
|
417
|
+
} else {
|
|
418
|
+
const cached = appConfigCache.get(appName2);
|
|
419
|
+
const now = Date.now();
|
|
420
|
+
if (cached && now - cached.timestamp < CACHE_TTL) {
|
|
421
|
+
appId = cached.appId;
|
|
422
|
+
appConfig = cached.appConfig;
|
|
423
|
+
} else {
|
|
424
|
+
const { data: app, error: error2 } = await supabase.from("rbac_apps").select("id, name, requires_event, is_active").eq("name", appName2).eq("is_active", true).single();
|
|
425
|
+
if (error2) {
|
|
426
|
+
if (error2.code === "406" || error2.code === "PGRST116" || error2.message?.includes("406")) {
|
|
427
|
+
log.debug(`App resolution blocked by RLS for "${appName2}" - user may not be authenticated`);
|
|
428
|
+
appId = void 0;
|
|
429
|
+
} else {
|
|
430
|
+
const { data: inactiveApp } = await supabase.from("rbac_apps").select("id, name, is_active").eq("name", appName2).single();
|
|
431
|
+
if (inactiveApp) {
|
|
432
|
+
log.error(`App "${appName2}" exists but is inactive (is_active: ${inactiveApp.is_active})`);
|
|
433
|
+
appId = void 0;
|
|
434
|
+
} else {
|
|
435
|
+
log.error(`App "${appName2}" not found in rbac_apps table`, { error: error2 });
|
|
436
|
+
appId = void 0;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
} else if (app) {
|
|
440
|
+
appId = app.id;
|
|
441
|
+
appConfig = { requires_event: app.requires_event ?? false };
|
|
442
|
+
appConfigCache.set(appName2, { appId, appConfig, timestamp: now });
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
} catch (error2) {
|
|
447
|
+
const errorMessage = error2 instanceof Error ? error2.message : String(error2);
|
|
448
|
+
if (!errorMessage.includes("406") && !errorMessage.includes("PGRST116")) {
|
|
449
|
+
log.error("Unexpected error resolving app config:", error2);
|
|
450
|
+
} else {
|
|
451
|
+
log.debug("App resolution skipped - authentication required");
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
const initialScope = {
|
|
456
|
+
organisationId: appConfig?.requires_event ? void 0 : selectedOrganisationId || void 0,
|
|
457
|
+
eventId: selectedEventId || void 0,
|
|
458
|
+
appId
|
|
459
|
+
};
|
|
460
|
+
const validation = await ContextValidator.resolveRequiredContext(
|
|
461
|
+
initialScope,
|
|
462
|
+
appConfig,
|
|
463
|
+
appName2 || void 0,
|
|
464
|
+
supabase
|
|
465
|
+
);
|
|
466
|
+
if (!validation.isValid) {
|
|
467
|
+
if (appName2 === "PORTAL" || appName2 === "ADMIN") {
|
|
468
|
+
if (!cancelled) {
|
|
469
|
+
const optionalContextScope = {
|
|
470
|
+
organisationId: void 0,
|
|
471
|
+
eventId: void 0,
|
|
472
|
+
appId: appId || void 0
|
|
473
|
+
// appId might be undefined if query failed, that's OK
|
|
474
|
+
};
|
|
475
|
+
setResolvedScope(optionalContextScope);
|
|
476
|
+
setError(null);
|
|
477
|
+
setIsLoading(false);
|
|
478
|
+
}
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
if (appConfig?.requires_event && selectedEventId) {
|
|
482
|
+
if (!cancelled) {
|
|
483
|
+
const eventScope = {
|
|
484
|
+
organisationId: void 0,
|
|
485
|
+
// Will be derived from event during permission check
|
|
486
|
+
eventId: selectedEventId,
|
|
487
|
+
appId: appId || void 0
|
|
488
|
+
};
|
|
489
|
+
setResolvedScope(eventScope);
|
|
490
|
+
setError(null);
|
|
491
|
+
setIsLoading(false);
|
|
492
|
+
}
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
if (!cancelled) {
|
|
496
|
+
setResolvedScope(null);
|
|
497
|
+
setError(validation.error || new Error("Context validation failed"));
|
|
498
|
+
setIsLoading(false);
|
|
499
|
+
}
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
if (!cancelled) {
|
|
503
|
+
setResolvedScope(validation.resolvedScope);
|
|
504
|
+
setError(null);
|
|
505
|
+
setIsLoading(false);
|
|
506
|
+
}
|
|
507
|
+
} catch (err) {
|
|
508
|
+
if (!cancelled) {
|
|
509
|
+
setError(err);
|
|
510
|
+
setIsLoading(false);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
};
|
|
514
|
+
resolveScope();
|
|
515
|
+
return () => {
|
|
516
|
+
cancelled = true;
|
|
517
|
+
};
|
|
518
|
+
}, [selectedOrganisationId, selectedEventId, supabase]);
|
|
519
|
+
const allowsOptionalContexts = appName === "PORTAL" || appName === "ADMIN";
|
|
520
|
+
const hasValidScope = allowsOptionalContexts ? true : stableScope.appId || stableScope.organisationId;
|
|
521
|
+
const finalScope = useMemo(() => {
|
|
522
|
+
if (!hasValidScope) {
|
|
523
|
+
return allowsOptionalContexts ? {} : null;
|
|
524
|
+
}
|
|
525
|
+
const scope = {};
|
|
526
|
+
if (stableScope.organisationId) {
|
|
527
|
+
scope.organisationId = stableScope.organisationId;
|
|
528
|
+
}
|
|
529
|
+
if (stableScope.eventId) {
|
|
530
|
+
scope.eventId = stableScope.eventId;
|
|
531
|
+
}
|
|
532
|
+
if (stableScope.appId) {
|
|
533
|
+
scope.appId = stableScope.appId;
|
|
534
|
+
}
|
|
535
|
+
return scope;
|
|
536
|
+
}, [hasValidScope, allowsOptionalContexts, stableScope.organisationId, stableScope.eventId, stableScope.appId]);
|
|
537
|
+
return {
|
|
538
|
+
resolvedScope: finalScope,
|
|
539
|
+
isLoading,
|
|
540
|
+
error
|
|
541
|
+
};
|
|
239
542
|
}
|
|
240
543
|
|
|
241
544
|
// src/rbac/hooks/useRBAC.ts
|
|
242
|
-
import { useState, useEffect, useCallback, useMemo } from "react";
|
|
545
|
+
import { useState as useState2, useEffect as useEffect2, useCallback, useMemo as useMemo2 } from "react";
|
|
243
546
|
function mapAccessLevelToEventRole(level) {
|
|
244
547
|
switch (level) {
|
|
245
548
|
case "viewer":
|
|
@@ -270,13 +573,13 @@ function useRBAC(pageId) {
|
|
|
270
573
|
selectedEvent,
|
|
271
574
|
eventLoading
|
|
272
575
|
} = useUnifiedAuth();
|
|
273
|
-
const [globalRole, setGlobalRole] =
|
|
274
|
-
const [organisationRole, setOrganisationRole] =
|
|
275
|
-
const [eventAppRole, setEventAppRole] =
|
|
276
|
-
const [permissionMap, setPermissionMap] =
|
|
277
|
-
const [currentScope, setCurrentScope] =
|
|
278
|
-
const [isLoading, setIsLoading] =
|
|
279
|
-
const [error, setError] =
|
|
576
|
+
const [globalRole, setGlobalRole] = useState2(null);
|
|
577
|
+
const [organisationRole, setOrganisationRole] = useState2(null);
|
|
578
|
+
const [eventAppRole, setEventAppRole] = useState2(null);
|
|
579
|
+
const [permissionMap, setPermissionMap] = useState2({});
|
|
580
|
+
const [currentScope, setCurrentScope] = useState2(null);
|
|
581
|
+
const [isLoading, setIsLoading] = useState2(false);
|
|
582
|
+
const [error, setError] = useState2(null);
|
|
280
583
|
const resetState = useCallback(() => {
|
|
281
584
|
setGlobalRole(null);
|
|
282
585
|
setOrganisationRole(null);
|
|
@@ -316,7 +619,7 @@ function useRBAC(pageId) {
|
|
|
316
619
|
if (!resolved) {
|
|
317
620
|
if (appName === "PORTAL" || appName === "ADMIN") {
|
|
318
621
|
try {
|
|
319
|
-
const { getAppConfigByName } = await import("./api-
|
|
622
|
+
const { getAppConfigByName } = await import("./api-MVVQZLJI.js");
|
|
320
623
|
await getAppConfigByName(appName);
|
|
321
624
|
} catch (err) {
|
|
322
625
|
}
|
|
@@ -401,12 +704,12 @@ function useRBAC(pageId) {
|
|
|
401
704
|
},
|
|
402
705
|
[globalRole, organisationRole, permissionMap]
|
|
403
706
|
);
|
|
404
|
-
const isSuperAdmin =
|
|
405
|
-
const isOrgAdmin =
|
|
406
|
-
const isEventAdmin =
|
|
407
|
-
const canManageOrganisation =
|
|
408
|
-
const canManageEvent =
|
|
409
|
-
|
|
707
|
+
const isSuperAdmin = useMemo2(() => globalRole === "super_admin" || permissionMap["*"] === true, [globalRole, permissionMap]);
|
|
708
|
+
const isOrgAdmin = useMemo2(() => organisationRole === "org_admin" || isSuperAdmin, [organisationRole, isSuperAdmin]);
|
|
709
|
+
const isEventAdmin = useMemo2(() => eventAppRole === "event_admin" || isSuperAdmin, [eventAppRole, isSuperAdmin]);
|
|
710
|
+
const canManageOrganisation = useMemo2(() => isSuperAdmin || organisationRole === "org_admin", [isSuperAdmin, organisationRole]);
|
|
711
|
+
const canManageEvent = useMemo2(() => isSuperAdmin || eventAppRole === "event_admin", [isSuperAdmin, eventAppRole]);
|
|
712
|
+
useEffect2(() => {
|
|
410
713
|
loadRBACContext();
|
|
411
714
|
}, [loadRBACContext, appName, appConfig, eventLoading, selectedEvent?.event_id, user, session, selectedOrganisation?.id, orgContextReady, orgLoading]);
|
|
412
715
|
return {
|
|
@@ -425,197 +728,176 @@ function useRBAC(pageId) {
|
|
|
425
728
|
};
|
|
426
729
|
}
|
|
427
730
|
|
|
428
|
-
// src/rbac/hooks/
|
|
429
|
-
import {
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
731
|
+
// src/rbac/hooks/permissions/useAccessLevel.ts
|
|
732
|
+
import { useCallback as useCallback2, useEffect as useEffect3, useMemo as useMemo3, useState as useState3 } from "react";
|
|
733
|
+
function useAccessLevel(userId, scope) {
|
|
734
|
+
const [accessLevel, setAccessLevel] = useState3("viewer");
|
|
735
|
+
const [isLoading, setIsLoading] = useState3(true);
|
|
736
|
+
const [error, setError] = useState3(null);
|
|
737
|
+
let appName;
|
|
738
|
+
try {
|
|
739
|
+
const { appName: contextAppName } = useAppConfig();
|
|
740
|
+
appName = contextAppName;
|
|
741
|
+
} catch {
|
|
438
742
|
}
|
|
439
|
-
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
// src/rbac/hooks/usePermissions.ts
|
|
443
|
-
function usePermissions(userId, organisationId, eventId, appId) {
|
|
444
|
-
const [permissions, setPermissions] = useState2({});
|
|
445
|
-
const [isLoading, setIsLoading] = useState2(true);
|
|
446
|
-
const [error, setError] = useState2(null);
|
|
447
|
-
const [fetchTrigger, setFetchTrigger] = useState2(0);
|
|
448
|
-
const isFetchingRef = useRef(false);
|
|
449
|
-
const logger2 = getRBACLogger();
|
|
450
|
-
const prevValuesRef = useRef({ userId, organisationId, eventId, appId });
|
|
451
|
-
const orgId = organisationId || "";
|
|
452
|
-
useEffect2(() => {
|
|
743
|
+
const fetchAccessLevel = useCallback2(async () => {
|
|
453
744
|
if (!userId) {
|
|
745
|
+
setAccessLevel("viewer");
|
|
746
|
+
setIsLoading(false);
|
|
454
747
|
return;
|
|
455
748
|
}
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
setError(new Error("Organisation context is required for permission checks"));
|
|
459
|
-
setIsLoading(false);
|
|
460
|
-
}, 3e3);
|
|
461
|
-
return () => clearTimeout(timeoutId);
|
|
462
|
-
}
|
|
463
|
-
if (error?.message === "Organisation context is required for permission checks") {
|
|
749
|
+
try {
|
|
750
|
+
setIsLoading(true);
|
|
464
751
|
setError(null);
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
if (paramsChanged) {
|
|
470
|
-
if (prevValuesRef.current.appId !== appId) {
|
|
471
|
-
}
|
|
472
|
-
prevValuesRef.current = { userId, organisationId, eventId, appId };
|
|
473
|
-
setFetchTrigger((prev) => prev + 1);
|
|
474
|
-
}
|
|
475
|
-
}, [userId, organisationId, eventId, appId, logger2]);
|
|
476
|
-
useEffect2(() => {
|
|
477
|
-
const fetchPermissions = async () => {
|
|
478
|
-
if (isFetchingRef.current) {
|
|
479
|
-
return;
|
|
480
|
-
}
|
|
481
|
-
if (!userId) {
|
|
482
|
-
setPermissions({});
|
|
752
|
+
const { isSuperAdmin: checkSuperAdmin } = await import("./api-MVVQZLJI.js");
|
|
753
|
+
const isSuperAdminUser = await checkSuperAdmin(userId);
|
|
754
|
+
if (isSuperAdminUser) {
|
|
755
|
+
setAccessLevel("super");
|
|
483
756
|
setIsLoading(false);
|
|
484
757
|
return;
|
|
485
758
|
}
|
|
486
|
-
if (!
|
|
487
|
-
|
|
759
|
+
if (appName !== "PORTAL" && appName !== "ADMIN" && !scope.organisationId && !scope.eventId) {
|
|
760
|
+
const orgError = new OrganisationContextRequiredError();
|
|
761
|
+
setError(orgError);
|
|
762
|
+
setAccessLevel("viewer");
|
|
488
763
|
setIsLoading(false);
|
|
489
764
|
return;
|
|
490
765
|
}
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
setError(null);
|
|
500
|
-
const scope = {
|
|
501
|
-
organisationId: orgId,
|
|
502
|
-
eventId,
|
|
503
|
-
appId
|
|
504
|
-
};
|
|
505
|
-
const permissionMap = await getPermissionMap({ userId, scope });
|
|
506
|
-
const permissionCount = Object.keys(permissionMap).length;
|
|
507
|
-
if (permissionCount === 0 && Object.keys(permissions).length > 0) {
|
|
508
|
-
logger2.warn("[usePermissions] Permissions fetched but returned empty map", {
|
|
509
|
-
scope: { organisationId: orgId, eventId, appId }
|
|
510
|
-
});
|
|
511
|
-
}
|
|
512
|
-
setPermissions(permissionMap);
|
|
513
|
-
} catch (err) {
|
|
514
|
-
logger2.error("[usePermissions] Failed to fetch permissions:", err);
|
|
515
|
-
setError(err instanceof Error ? err : new Error("Failed to fetch permissions"));
|
|
516
|
-
} finally {
|
|
517
|
-
setIsLoading(false);
|
|
518
|
-
isFetchingRef.current = false;
|
|
519
|
-
}
|
|
520
|
-
};
|
|
521
|
-
fetchPermissions();
|
|
522
|
-
}, [fetchTrigger, userId, organisationId, eventId, appId]);
|
|
523
|
-
const hasPermission = useCallback2((permission) => {
|
|
524
|
-
if (permissions["*"]) {
|
|
525
|
-
return true;
|
|
526
|
-
}
|
|
527
|
-
return permissions[permission] === true;
|
|
528
|
-
}, [permissions]);
|
|
529
|
-
const hasAnyPermission = useCallback2((permissionList) => {
|
|
530
|
-
if (permissions["*"]) {
|
|
531
|
-
return true;
|
|
532
|
-
}
|
|
533
|
-
return permissionList.some((p) => permissions[p] === true);
|
|
534
|
-
}, [permissions]);
|
|
535
|
-
const hasAllPermissions = useCallback2((permissionList) => {
|
|
536
|
-
if (permissions["*"]) {
|
|
537
|
-
return true;
|
|
538
|
-
}
|
|
539
|
-
return permissionList.every((p) => permissions[p] === true);
|
|
540
|
-
}, [permissions]);
|
|
541
|
-
const refetch = useCallback2(async () => {
|
|
542
|
-
if (isFetchingRef.current) {
|
|
543
|
-
return;
|
|
766
|
+
const level = await getAccessLevel({ userId, scope }, null, appName);
|
|
767
|
+
setAccessLevel(level);
|
|
768
|
+
} catch (err) {
|
|
769
|
+
const error2 = err instanceof Error ? err : new Error("Failed to fetch access level");
|
|
770
|
+
setError(error2);
|
|
771
|
+
setAccessLevel("viewer");
|
|
772
|
+
} finally {
|
|
773
|
+
setIsLoading(false);
|
|
544
774
|
}
|
|
775
|
+
}, [userId, scope.organisationId, scope.eventId, scope.appId, appName]);
|
|
776
|
+
useEffect3(() => {
|
|
777
|
+
fetchAccessLevel();
|
|
778
|
+
}, [fetchAccessLevel]);
|
|
779
|
+
return useMemo3(() => ({
|
|
780
|
+
accessLevel,
|
|
781
|
+
isLoading,
|
|
782
|
+
error,
|
|
783
|
+
refetch: fetchAccessLevel
|
|
784
|
+
}), [accessLevel, isLoading, error, fetchAccessLevel]);
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
// src/rbac/hooks/permissions/useCachedPermissions.ts
|
|
788
|
+
import { useCallback as useCallback3, useEffect as useEffect4, useMemo as useMemo4, useState as useState4 } from "react";
|
|
789
|
+
function useCachedPermissions(userId, scope) {
|
|
790
|
+
const [permissions, setPermissions] = useState4({});
|
|
791
|
+
const [isLoading, setIsLoading] = useState4(true);
|
|
792
|
+
const [error, setError] = useState4(null);
|
|
793
|
+
const fetchCachedPermissions = useCallback3(async () => {
|
|
545
794
|
if (!userId) {
|
|
546
795
|
setPermissions({});
|
|
547
796
|
setIsLoading(false);
|
|
548
797
|
return;
|
|
549
798
|
}
|
|
550
|
-
if (!orgId || orgId === null || typeof orgId === "string" && orgId.trim() === "") {
|
|
551
|
-
setIsLoading(true);
|
|
552
|
-
setError(null);
|
|
553
|
-
return;
|
|
554
|
-
}
|
|
555
799
|
try {
|
|
556
|
-
isFetchingRef.current = true;
|
|
557
800
|
setIsLoading(true);
|
|
558
801
|
setError(null);
|
|
559
|
-
const scope = {
|
|
560
|
-
organisationId: orgId,
|
|
561
|
-
eventId,
|
|
562
|
-
appId
|
|
563
|
-
};
|
|
564
802
|
const permissionMap = await getPermissionMap({ userId, scope });
|
|
565
803
|
setPermissions(permissionMap);
|
|
566
804
|
} catch (err) {
|
|
567
|
-
|
|
568
|
-
logger3.error("Failed to refetch permissions:", err);
|
|
569
|
-
setError(err instanceof Error ? err : new Error("Failed to fetch permissions"));
|
|
805
|
+
setError(err instanceof Error ? err : new Error("Failed to fetch cached permissions"));
|
|
570
806
|
} finally {
|
|
571
807
|
setIsLoading(false);
|
|
572
|
-
isFetchingRef.current = false;
|
|
573
808
|
}
|
|
574
|
-
}, [userId, organisationId, eventId, appId]);
|
|
575
|
-
|
|
809
|
+
}, [userId, scope.organisationId, scope.eventId, scope.appId]);
|
|
810
|
+
const invalidateCache = useCallback3(() => {
|
|
811
|
+
fetchCachedPermissions();
|
|
812
|
+
}, [fetchCachedPermissions]);
|
|
813
|
+
useEffect4(() => {
|
|
814
|
+
fetchCachedPermissions();
|
|
815
|
+
}, [fetchCachedPermissions]);
|
|
816
|
+
return useMemo4(() => ({
|
|
576
817
|
permissions,
|
|
577
818
|
isLoading,
|
|
578
819
|
error,
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
refetch
|
|
583
|
-
}), [permissions, isLoading, error, hasPermission, hasAnyPermission, hasAllPermissions, refetch]);
|
|
820
|
+
invalidateCache,
|
|
821
|
+
refetch: fetchCachedPermissions
|
|
822
|
+
}), [permissions, isLoading, error, invalidateCache, fetchCachedPermissions]);
|
|
584
823
|
}
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
824
|
+
|
|
825
|
+
// src/rbac/hooks/permissions/useCan.ts
|
|
826
|
+
import { useCallback as useCallback4, useEffect as useEffect5, useMemo as useMemo5, useRef as useRef2, useState as useState5 } from "react";
|
|
827
|
+
|
|
828
|
+
// src/rbac/utils/deep-equal.ts
|
|
829
|
+
function scopeEqual(a, b) {
|
|
830
|
+
if (a === b) {
|
|
831
|
+
return true;
|
|
832
|
+
}
|
|
833
|
+
if (a == null || b == null) {
|
|
834
|
+
return a === b;
|
|
835
|
+
}
|
|
836
|
+
return a.organisationId === b.organisationId && a.eventId === b.eventId && a.appId === b.appId;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
// src/rbac/hooks/permissions/useCan.ts
|
|
840
|
+
function useCan(userId, scope, permission, pageId, useCache = true, precomputedSuperAdmin = null, appName) {
|
|
841
|
+
const [can, setCan] = useState5(false);
|
|
842
|
+
const [isLoading, setIsLoading] = useState5(true);
|
|
843
|
+
const [error, setError] = useState5(null);
|
|
844
|
+
const [isSuperAdmin, setIsSuperAdmin] = useState5(precomputedSuperAdmin ?? null);
|
|
590
845
|
const isValidScope = scope && typeof scope === "object";
|
|
591
846
|
const organisationId = isValidScope ? scope.organisationId : void 0;
|
|
592
847
|
const eventId = isValidScope ? scope.eventId : void 0;
|
|
593
848
|
const appId = isValidScope ? scope.appId : void 0;
|
|
594
|
-
|
|
595
|
-
if (
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
let cancelled = false;
|
|
600
|
-
const checkSuperAdmin = async () => {
|
|
601
|
-
try {
|
|
602
|
-
const { isSuperAdmin: checkSuperAdmin2 } = await import("./api-N774RPUA.js");
|
|
603
|
-
const isSuper = await checkSuperAdmin2(userId);
|
|
604
|
-
if (!cancelled) {
|
|
605
|
-
setIsSuperAdmin(isSuper);
|
|
606
|
-
}
|
|
607
|
-
} catch (err) {
|
|
608
|
-
if (!cancelled) {
|
|
609
|
-
setIsSuperAdmin(false);
|
|
610
|
-
}
|
|
849
|
+
useEffect5(() => {
|
|
850
|
+
if (precomputedSuperAdmin === null) {
|
|
851
|
+
if (!userId) {
|
|
852
|
+
setIsSuperAdmin(false);
|
|
853
|
+
return;
|
|
611
854
|
}
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
855
|
+
let cancelled = false;
|
|
856
|
+
const checkSuperAdmin = async () => {
|
|
857
|
+
const startTime = Date.now();
|
|
858
|
+
try {
|
|
859
|
+
const { isSuperAdmin: checkSuperAdmin2 } = await import("./api-MVVQZLJI.js");
|
|
860
|
+
const timeoutWarning = setTimeout(() => {
|
|
861
|
+
if (!cancelled) {
|
|
862
|
+
console.warn("[useCan] Super admin check taking longer than 5 seconds", {
|
|
863
|
+
userId,
|
|
864
|
+
elapsedMs: Date.now() - startTime
|
|
865
|
+
});
|
|
866
|
+
}
|
|
867
|
+
}, 5e3);
|
|
868
|
+
const isSuper = await checkSuperAdmin2(userId);
|
|
869
|
+
clearTimeout(timeoutWarning);
|
|
870
|
+
if (!cancelled) {
|
|
871
|
+
const elapsed = Date.now() - startTime;
|
|
872
|
+
if (elapsed > 1e3) {
|
|
873
|
+
console.warn("[useCan] Super admin check took longer than expected", {
|
|
874
|
+
userId,
|
|
875
|
+
elapsedMs: elapsed
|
|
876
|
+
});
|
|
877
|
+
}
|
|
878
|
+
setIsSuperAdmin(isSuper);
|
|
879
|
+
}
|
|
880
|
+
} catch (err) {
|
|
881
|
+
if (!cancelled) {
|
|
882
|
+
const elapsed = Date.now() - startTime;
|
|
883
|
+
console.error("[useCan] Error checking super admin", {
|
|
884
|
+
userId,
|
|
885
|
+
error: err,
|
|
886
|
+
elapsedMs: elapsed
|
|
887
|
+
});
|
|
888
|
+
setIsSuperAdmin(false);
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
};
|
|
892
|
+
checkSuperAdmin();
|
|
893
|
+
return () => {
|
|
894
|
+
cancelled = true;
|
|
895
|
+
};
|
|
896
|
+
} else {
|
|
897
|
+
setIsSuperAdmin(precomputedSuperAdmin);
|
|
898
|
+
}
|
|
899
|
+
}, [userId, precomputedSuperAdmin]);
|
|
900
|
+
useEffect5(() => {
|
|
619
901
|
const isPagePermission = permission.includes(":page.") || !!pageId;
|
|
620
902
|
const requiresOrgId = !isPagePermission;
|
|
621
903
|
if (isSuperAdmin === true) {
|
|
@@ -633,12 +915,12 @@ function useCan(userId, scope, permission, pageId, useCache = true, appName) {
|
|
|
633
915
|
setError(null);
|
|
634
916
|
}
|
|
635
917
|
}, [isValidScope, organisationId, error, permission, pageId, isSuperAdmin]);
|
|
636
|
-
const lastUserIdRef =
|
|
637
|
-
const lastScopeRef =
|
|
638
|
-
const lastPermissionRef =
|
|
639
|
-
const lastPageIdRef =
|
|
640
|
-
const lastUseCacheRef =
|
|
641
|
-
const stableScope =
|
|
918
|
+
const lastUserIdRef = useRef2(null);
|
|
919
|
+
const lastScopeRef = useRef2(null);
|
|
920
|
+
const lastPermissionRef = useRef2(null);
|
|
921
|
+
const lastPageIdRef = useRef2(null);
|
|
922
|
+
const lastUseCacheRef = useRef2(null);
|
|
923
|
+
const stableScope = useMemo5(() => {
|
|
642
924
|
if (!isValidScope) {
|
|
643
925
|
return null;
|
|
644
926
|
}
|
|
@@ -648,8 +930,8 @@ function useCan(userId, scope, permission, pageId, useCache = true, appName) {
|
|
|
648
930
|
appId
|
|
649
931
|
};
|
|
650
932
|
}, [isValidScope, organisationId, eventId, appId]);
|
|
651
|
-
const prevScopeRef =
|
|
652
|
-
|
|
933
|
+
const prevScopeRef = useRef2(null);
|
|
934
|
+
useEffect5(() => {
|
|
653
935
|
const scopeChanged = !scopeEqual(prevScopeRef.current, stableScope);
|
|
654
936
|
if (lastUserIdRef.current !== userId || scopeChanged || lastPermissionRef.current !== permission || lastPageIdRef.current !== pageId || lastUseCacheRef.current !== useCache) {
|
|
655
937
|
lastUserIdRef.current = userId;
|
|
@@ -660,7 +942,19 @@ function useCan(userId, scope, permission, pageId, useCache = true, appName) {
|
|
|
660
942
|
const checkPermission = async () => {
|
|
661
943
|
if (!userId) {
|
|
662
944
|
setCan(false);
|
|
663
|
-
setIsLoading(false);
|
|
945
|
+
setIsLoading(false);
|
|
946
|
+
return;
|
|
947
|
+
}
|
|
948
|
+
if (isSuperAdmin === true) {
|
|
949
|
+
setCan(true);
|
|
950
|
+
setIsLoading(false);
|
|
951
|
+
setError(null);
|
|
952
|
+
return;
|
|
953
|
+
}
|
|
954
|
+
if (isSuperAdmin === null) {
|
|
955
|
+
setIsLoading(true);
|
|
956
|
+
setCan(false);
|
|
957
|
+
setError(null);
|
|
664
958
|
return;
|
|
665
959
|
}
|
|
666
960
|
if (!isValidScope) {
|
|
@@ -674,13 +968,10 @@ function useCan(userId, scope, permission, pageId, useCache = true, appName) {
|
|
|
674
968
|
const isPageName = pageId && typeof pageId === "string" && !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(pageId);
|
|
675
969
|
const needsAppIdForPageName = isPagePermission && isPageName;
|
|
676
970
|
if (requiresOrgId && (!organisationId || organisationId === null || typeof organisationId === "string" && organisationId.trim() === "")) {
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
setError(null);
|
|
682
|
-
return;
|
|
683
|
-
}
|
|
971
|
+
setIsLoading(true);
|
|
972
|
+
setCan(false);
|
|
973
|
+
setError(null);
|
|
974
|
+
return;
|
|
684
975
|
}
|
|
685
976
|
if (needsAppIdForPageName && (!appId || appId === null || typeof appId === "string" && appId.trim() === "")) {
|
|
686
977
|
setIsLoading(true);
|
|
@@ -696,11 +987,12 @@ function useCan(userId, scope, permission, pageId, useCache = true, appName) {
|
|
|
696
987
|
...eventId ? { eventId } : {},
|
|
697
988
|
...appId ? { appId } : {}
|
|
698
989
|
};
|
|
699
|
-
const result = useCache ? await isPermittedCached({ userId, scope: validScope, permission, pageId }, void 0, appName) : await isPermitted({ userId, scope: validScope, permission, pageId }, void 0, appName);
|
|
990
|
+
const result = useCache ? await isPermittedCached({ userId, scope: validScope, permission, pageId }, void 0, appName) : await isPermitted({ userId, scope: validScope, permission, pageId }, void 0, appName, isSuperAdmin === false ? false : null);
|
|
700
991
|
setCan(result);
|
|
701
992
|
} catch (err) {
|
|
702
993
|
const logger2 = getRBACLogger();
|
|
703
994
|
logger2.error("Permission check error:", { permission, error: err });
|
|
995
|
+
console.error("[useCan] Permission check error", { userId, permission, error: err });
|
|
704
996
|
setError(err instanceof Error ? err : new Error("Failed to check permission"));
|
|
705
997
|
setCan(false);
|
|
706
998
|
} finally {
|
|
@@ -710,7 +1002,7 @@ function useCan(userId, scope, permission, pageId, useCache = true, appName) {
|
|
|
710
1002
|
checkPermission();
|
|
711
1003
|
}
|
|
712
1004
|
}, [userId, stableScope, permission, pageId, useCache, appName, isSuperAdmin]);
|
|
713
|
-
const refetch =
|
|
1005
|
+
const refetch = useCallback4(async () => {
|
|
714
1006
|
if (!userId) {
|
|
715
1007
|
setCan(false);
|
|
716
1008
|
setIsLoading(false);
|
|
@@ -738,7 +1030,7 @@ function useCan(userId, scope, permission, pageId, useCache = true, appName) {
|
|
|
738
1030
|
...eventId ? { eventId } : {},
|
|
739
1031
|
...appId ? { appId } : {}
|
|
740
1032
|
};
|
|
741
|
-
const result = useCache ? await isPermittedCached({ userId, scope: validScope, permission, pageId }, void 0, appName) : await isPermitted({ userId, scope: validScope, permission, pageId }, void 0, appName);
|
|
1033
|
+
const result = useCache ? await isPermittedCached({ userId, scope: validScope, permission, pageId }, void 0, appName) : await isPermitted({ userId, scope: validScope, permission, pageId }, void 0, appName, null);
|
|
742
1034
|
setCan(result);
|
|
743
1035
|
} catch (err) {
|
|
744
1036
|
setError(err instanceof Error ? err : new Error("Failed to check permission"));
|
|
@@ -747,107 +1039,63 @@ function useCan(userId, scope, permission, pageId, useCache = true, appName) {
|
|
|
747
1039
|
setIsLoading(false);
|
|
748
1040
|
}
|
|
749
1041
|
}, [userId, isValidScope, organisationId, eventId, appId, permission, pageId, useCache, appName]);
|
|
750
|
-
return
|
|
1042
|
+
return useMemo5(() => ({
|
|
751
1043
|
can,
|
|
752
1044
|
isLoading,
|
|
753
1045
|
error,
|
|
754
1046
|
refetch
|
|
755
1047
|
}), [can, isLoading, error, refetch]);
|
|
756
1048
|
}
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
} catch {
|
|
766
|
-
}
|
|
767
|
-
const fetchAccessLevel = useCallback2(async () => {
|
|
768
|
-
if (!userId) {
|
|
769
|
-
setAccessLevel("viewer");
|
|
770
|
-
setIsLoading(false);
|
|
771
|
-
return;
|
|
772
|
-
}
|
|
773
|
-
try {
|
|
774
|
-
setIsLoading(true);
|
|
775
|
-
setError(null);
|
|
776
|
-
const { isSuperAdmin: checkSuperAdmin } = await import("./api-N774RPUA.js");
|
|
777
|
-
const isSuperAdminUser = await checkSuperAdmin(userId);
|
|
778
|
-
if (isSuperAdminUser) {
|
|
779
|
-
setAccessLevel("super");
|
|
780
|
-
setIsLoading(false);
|
|
781
|
-
return;
|
|
782
|
-
}
|
|
783
|
-
if (appName !== "PORTAL" && appName !== "ADMIN" && !scope.organisationId && !scope.eventId) {
|
|
784
|
-
const orgError = new OrganisationContextRequiredError();
|
|
785
|
-
setError(orgError);
|
|
786
|
-
setAccessLevel("viewer");
|
|
787
|
-
setIsLoading(false);
|
|
788
|
-
return;
|
|
789
|
-
}
|
|
790
|
-
const level = await getAccessLevel({ userId, scope }, null, appName);
|
|
791
|
-
setAccessLevel(level);
|
|
792
|
-
} catch (err) {
|
|
793
|
-
const error2 = err instanceof Error ? err : new Error("Failed to fetch access level");
|
|
794
|
-
setError(error2);
|
|
795
|
-
setAccessLevel("viewer");
|
|
796
|
-
} finally {
|
|
797
|
-
setIsLoading(false);
|
|
798
|
-
}
|
|
799
|
-
}, [userId, scope.organisationId, scope.eventId, scope.appId, appName]);
|
|
800
|
-
useEffect2(() => {
|
|
801
|
-
fetchAccessLevel();
|
|
802
|
-
}, [fetchAccessLevel]);
|
|
803
|
-
return useMemo2(() => ({
|
|
804
|
-
accessLevel,
|
|
805
|
-
isLoading,
|
|
806
|
-
error,
|
|
807
|
-
refetch: fetchAccessLevel
|
|
808
|
-
}), [accessLevel, isLoading, error, fetchAccessLevel]);
|
|
809
|
-
}
|
|
810
|
-
function useMultiplePermissions(userId, scope, permissions, useCache = true) {
|
|
811
|
-
const [results, setResults] = useState2({});
|
|
812
|
-
const [isLoading, setIsLoading] = useState2(true);
|
|
813
|
-
const [error, setError] = useState2(null);
|
|
814
|
-
const checkPermissions = useCallback2(async () => {
|
|
1049
|
+
|
|
1050
|
+
// src/rbac/hooks/permissions/useHasAllPermissions.ts
|
|
1051
|
+
import { useCallback as useCallback5, useEffect as useEffect6, useMemo as useMemo6, useState as useState6 } from "react";
|
|
1052
|
+
function useHasAllPermissions(userId, scope, permissions, useCache = true) {
|
|
1053
|
+
const [hasAll, setHasAll] = useState6(false);
|
|
1054
|
+
const [isLoading, setIsLoading] = useState6(true);
|
|
1055
|
+
const [error, setError] = useState6(null);
|
|
1056
|
+
const checkAllPermissions = useCallback5(async () => {
|
|
815
1057
|
if (!userId || permissions.length === 0) {
|
|
816
|
-
|
|
1058
|
+
setHasAll(false);
|
|
817
1059
|
setIsLoading(false);
|
|
818
1060
|
return;
|
|
819
1061
|
}
|
|
820
1062
|
try {
|
|
821
1063
|
setIsLoading(true);
|
|
822
1064
|
setError(null);
|
|
823
|
-
|
|
1065
|
+
let hasAllPermissions = true;
|
|
824
1066
|
for (const permission of permissions) {
|
|
825
|
-
const result = useCache ? await isPermittedCached({ userId, scope, permission }) : await isPermitted({ userId, scope, permission });
|
|
826
|
-
|
|
1067
|
+
const result = useCache ? await isPermittedCached({ userId, scope, permission }) : await isPermitted({ userId, scope, permission }, null, void 0, null);
|
|
1068
|
+
if (!result) {
|
|
1069
|
+
hasAllPermissions = false;
|
|
1070
|
+
break;
|
|
1071
|
+
}
|
|
827
1072
|
}
|
|
828
|
-
|
|
1073
|
+
setHasAll(hasAllPermissions);
|
|
829
1074
|
} catch (err) {
|
|
830
1075
|
setError(err instanceof Error ? err : new Error("Failed to check permissions"));
|
|
831
|
-
|
|
1076
|
+
setHasAll(false);
|
|
832
1077
|
} finally {
|
|
833
1078
|
setIsLoading(false);
|
|
834
1079
|
}
|
|
835
1080
|
}, [userId, scope.organisationId, scope.eventId, scope.appId, permissions, useCache]);
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
}, [
|
|
839
|
-
return
|
|
840
|
-
|
|
1081
|
+
useEffect6(() => {
|
|
1082
|
+
checkAllPermissions();
|
|
1083
|
+
}, [checkAllPermissions]);
|
|
1084
|
+
return useMemo6(() => ({
|
|
1085
|
+
hasAll,
|
|
841
1086
|
isLoading,
|
|
842
1087
|
error,
|
|
843
|
-
refetch:
|
|
844
|
-
}), [
|
|
1088
|
+
refetch: checkAllPermissions
|
|
1089
|
+
}), [hasAll, isLoading, error, checkAllPermissions]);
|
|
845
1090
|
}
|
|
1091
|
+
|
|
1092
|
+
// src/rbac/hooks/permissions/useHasAnyPermission.ts
|
|
1093
|
+
import { useCallback as useCallback6, useEffect as useEffect7, useMemo as useMemo7, useState as useState7 } from "react";
|
|
846
1094
|
function useHasAnyPermission(userId, scope, permissions, useCache = true) {
|
|
847
|
-
const [hasAny, setHasAny] =
|
|
848
|
-
const [isLoading, setIsLoading] =
|
|
849
|
-
const [error, setError] =
|
|
850
|
-
const checkAnyPermission =
|
|
1095
|
+
const [hasAny, setHasAny] = useState7(false);
|
|
1096
|
+
const [isLoading, setIsLoading] = useState7(true);
|
|
1097
|
+
const [error, setError] = useState7(null);
|
|
1098
|
+
const checkAnyPermission = useCallback6(async () => {
|
|
851
1099
|
if (!userId || permissions.length === 0) {
|
|
852
1100
|
setHasAny(false);
|
|
853
1101
|
setIsLoading(false);
|
|
@@ -858,7 +1106,7 @@ function useHasAnyPermission(userId, scope, permissions, useCache = true) {
|
|
|
858
1106
|
setError(null);
|
|
859
1107
|
let hasAnyPermission = false;
|
|
860
1108
|
for (const permission of permissions) {
|
|
861
|
-
const result = useCache ? await isPermittedCached({ userId, scope, permission }) : await isPermitted({ userId, scope, permission });
|
|
1109
|
+
const result = useCache ? await isPermittedCached({ userId, scope, permission }) : await isPermitted({ userId, scope, permission }, null, void 0, null);
|
|
862
1110
|
if (result) {
|
|
863
1111
|
hasAnyPermission = true;
|
|
864
1112
|
break;
|
|
@@ -872,93 +1120,203 @@ function useHasAnyPermission(userId, scope, permissions, useCache = true) {
|
|
|
872
1120
|
setIsLoading(false);
|
|
873
1121
|
}
|
|
874
1122
|
}, [userId, scope.organisationId, scope.eventId, scope.appId, permissions, useCache]);
|
|
875
|
-
|
|
1123
|
+
useEffect7(() => {
|
|
876
1124
|
checkAnyPermission();
|
|
877
1125
|
}, [checkAnyPermission]);
|
|
878
|
-
return
|
|
1126
|
+
return useMemo7(() => ({
|
|
879
1127
|
hasAny,
|
|
880
1128
|
isLoading,
|
|
881
1129
|
error,
|
|
882
1130
|
refetch: checkAnyPermission
|
|
883
1131
|
}), [hasAny, isLoading, error, checkAnyPermission]);
|
|
884
1132
|
}
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
const
|
|
1133
|
+
|
|
1134
|
+
// src/rbac/hooks/permissions/useMultiplePermissions.ts
|
|
1135
|
+
import { useCallback as useCallback7, useEffect as useEffect8, useMemo as useMemo8, useState as useState8 } from "react";
|
|
1136
|
+
function useMultiplePermissions(userId, scope, permissions, useCache = true) {
|
|
1137
|
+
const [results, setResults] = useState8({});
|
|
1138
|
+
const [isLoading, setIsLoading] = useState8(true);
|
|
1139
|
+
const [error, setError] = useState8(null);
|
|
1140
|
+
const checkPermissions = useCallback7(async () => {
|
|
890
1141
|
if (!userId || permissions.length === 0) {
|
|
891
|
-
|
|
1142
|
+
setResults({});
|
|
892
1143
|
setIsLoading(false);
|
|
893
1144
|
return;
|
|
894
1145
|
}
|
|
895
1146
|
try {
|
|
896
1147
|
setIsLoading(true);
|
|
897
1148
|
setError(null);
|
|
898
|
-
|
|
1149
|
+
const permissionResults = {};
|
|
899
1150
|
for (const permission of permissions) {
|
|
900
|
-
const result = useCache ? await isPermittedCached({ userId, scope, permission }) : await isPermitted({ userId, scope, permission });
|
|
901
|
-
|
|
902
|
-
hasAllPermissions = false;
|
|
903
|
-
break;
|
|
904
|
-
}
|
|
1151
|
+
const result = useCache ? await isPermittedCached({ userId, scope, permission }) : await isPermitted({ userId, scope, permission }, null, void 0, null);
|
|
1152
|
+
permissionResults[permission] = result;
|
|
905
1153
|
}
|
|
906
|
-
|
|
1154
|
+
setResults(permissionResults);
|
|
907
1155
|
} catch (err) {
|
|
908
1156
|
setError(err instanceof Error ? err : new Error("Failed to check permissions"));
|
|
909
|
-
|
|
1157
|
+
setResults({});
|
|
910
1158
|
} finally {
|
|
911
1159
|
setIsLoading(false);
|
|
912
1160
|
}
|
|
913
1161
|
}, [userId, scope.organisationId, scope.eventId, scope.appId, permissions, useCache]);
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
}, [
|
|
917
|
-
return
|
|
918
|
-
|
|
1162
|
+
useEffect8(() => {
|
|
1163
|
+
checkPermissions();
|
|
1164
|
+
}, [checkPermissions]);
|
|
1165
|
+
return useMemo8(() => ({
|
|
1166
|
+
results,
|
|
919
1167
|
isLoading,
|
|
920
1168
|
error,
|
|
921
|
-
refetch:
|
|
922
|
-
}), [
|
|
1169
|
+
refetch: checkPermissions
|
|
1170
|
+
}), [results, isLoading, error, checkPermissions]);
|
|
923
1171
|
}
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
const
|
|
1172
|
+
|
|
1173
|
+
// src/rbac/hooks/permissions/usePermissions.ts
|
|
1174
|
+
import { useCallback as useCallback8, useEffect as useEffect9, useMemo as useMemo9, useRef as useRef3, useState as useState9 } from "react";
|
|
1175
|
+
function usePermissions(userId, organisationId, eventId, appId) {
|
|
1176
|
+
const [permissions, setPermissions] = useState9({});
|
|
1177
|
+
const [isLoading, setIsLoading] = useState9(true);
|
|
1178
|
+
const [error, setError] = useState9(null);
|
|
1179
|
+
const [fetchTrigger, setFetchTrigger] = useState9(0);
|
|
1180
|
+
const isFetchingRef = useRef3(false);
|
|
1181
|
+
const logger2 = getRBACLogger();
|
|
1182
|
+
const prevValuesRef = useRef3({ userId, organisationId, eventId, appId });
|
|
1183
|
+
const orgId = organisationId || "";
|
|
1184
|
+
useEffect9(() => {
|
|
1185
|
+
if (!userId) {
|
|
1186
|
+
return;
|
|
1187
|
+
}
|
|
1188
|
+
if (!orgId || orgId === null || typeof orgId === "string" && orgId.trim() === "") {
|
|
1189
|
+
const timeoutId = setTimeout(() => {
|
|
1190
|
+
setError(new Error("Organisation context is required for permission checks"));
|
|
1191
|
+
setIsLoading(false);
|
|
1192
|
+
}, 3e3);
|
|
1193
|
+
return () => clearTimeout(timeoutId);
|
|
1194
|
+
}
|
|
1195
|
+
if (error?.message === "Organisation context is required for permission checks") {
|
|
1196
|
+
setError(null);
|
|
1197
|
+
}
|
|
1198
|
+
}, [userId, organisationId, error, orgId]);
|
|
1199
|
+
useEffect9(() => {
|
|
1200
|
+
const paramsChanged = prevValuesRef.current.userId !== userId || prevValuesRef.current.organisationId !== organisationId || prevValuesRef.current.eventId !== eventId || prevValuesRef.current.appId !== appId;
|
|
1201
|
+
if (paramsChanged) {
|
|
1202
|
+
if (prevValuesRef.current.appId !== appId) {
|
|
1203
|
+
}
|
|
1204
|
+
prevValuesRef.current = { userId, organisationId, eventId, appId };
|
|
1205
|
+
setFetchTrigger((prev) => prev + 1);
|
|
1206
|
+
}
|
|
1207
|
+
}, [userId, organisationId, eventId, appId, logger2]);
|
|
1208
|
+
useEffect9(() => {
|
|
1209
|
+
const fetchPermissions = async () => {
|
|
1210
|
+
if (isFetchingRef.current) {
|
|
1211
|
+
return;
|
|
1212
|
+
}
|
|
1213
|
+
if (!userId) {
|
|
1214
|
+
setPermissions({});
|
|
1215
|
+
setIsLoading(false);
|
|
1216
|
+
return;
|
|
1217
|
+
}
|
|
1218
|
+
if (!userId) {
|
|
1219
|
+
setPermissions({});
|
|
1220
|
+
setIsLoading(false);
|
|
1221
|
+
return;
|
|
1222
|
+
}
|
|
1223
|
+
if (!orgId || orgId === null || typeof orgId === "string" && orgId.trim() === "") {
|
|
1224
|
+
setIsLoading(true);
|
|
1225
|
+
setError(null);
|
|
1226
|
+
return;
|
|
1227
|
+
}
|
|
1228
|
+
try {
|
|
1229
|
+
isFetchingRef.current = true;
|
|
1230
|
+
setIsLoading(true);
|
|
1231
|
+
setError(null);
|
|
1232
|
+
const scope = {
|
|
1233
|
+
organisationId: orgId,
|
|
1234
|
+
eventId,
|
|
1235
|
+
appId
|
|
1236
|
+
};
|
|
1237
|
+
const permissionMap = await getPermissionMap({ userId, scope });
|
|
1238
|
+
const permissionCount = Object.keys(permissionMap).length;
|
|
1239
|
+
if (permissionCount === 0 && Object.keys(permissions).length > 0) {
|
|
1240
|
+
logger2.warn("[usePermissions] Permissions fetched but returned empty map", {
|
|
1241
|
+
scope: { organisationId: orgId, eventId, appId }
|
|
1242
|
+
});
|
|
1243
|
+
}
|
|
1244
|
+
setPermissions(permissionMap);
|
|
1245
|
+
} catch (err) {
|
|
1246
|
+
logger2.error("[usePermissions] Failed to fetch permissions:", err);
|
|
1247
|
+
setError(err instanceof Error ? err : new Error("Failed to fetch permissions"));
|
|
1248
|
+
} finally {
|
|
1249
|
+
setIsLoading(false);
|
|
1250
|
+
isFetchingRef.current = false;
|
|
1251
|
+
}
|
|
1252
|
+
};
|
|
1253
|
+
fetchPermissions();
|
|
1254
|
+
}, [fetchTrigger, userId, organisationId, eventId, appId]);
|
|
1255
|
+
const hasPermission = useCallback8((permission) => {
|
|
1256
|
+
if (permissions["*"]) {
|
|
1257
|
+
return true;
|
|
1258
|
+
}
|
|
1259
|
+
return permissions[permission] === true;
|
|
1260
|
+
}, [permissions]);
|
|
1261
|
+
const hasAnyPermission = useCallback8((permissionList) => {
|
|
1262
|
+
if (permissions["*"]) {
|
|
1263
|
+
return true;
|
|
1264
|
+
}
|
|
1265
|
+
return permissionList.some((p) => permissions[p] === true);
|
|
1266
|
+
}, [permissions]);
|
|
1267
|
+
const hasAllPermissions = useCallback8((permissionList) => {
|
|
1268
|
+
if (permissions["*"]) {
|
|
1269
|
+
return true;
|
|
1270
|
+
}
|
|
1271
|
+
return permissionList.every((p) => permissions[p] === true);
|
|
1272
|
+
}, [permissions]);
|
|
1273
|
+
const refetch = useCallback8(async () => {
|
|
1274
|
+
if (isFetchingRef.current) {
|
|
1275
|
+
return;
|
|
1276
|
+
}
|
|
929
1277
|
if (!userId) {
|
|
930
1278
|
setPermissions({});
|
|
931
1279
|
setIsLoading(false);
|
|
932
1280
|
return;
|
|
933
1281
|
}
|
|
1282
|
+
if (!orgId || orgId === null || typeof orgId === "string" && orgId.trim() === "") {
|
|
1283
|
+
setIsLoading(true);
|
|
1284
|
+
setError(null);
|
|
1285
|
+
return;
|
|
1286
|
+
}
|
|
934
1287
|
try {
|
|
1288
|
+
isFetchingRef.current = true;
|
|
935
1289
|
setIsLoading(true);
|
|
936
1290
|
setError(null);
|
|
1291
|
+
const scope = {
|
|
1292
|
+
organisationId: orgId,
|
|
1293
|
+
eventId,
|
|
1294
|
+
appId
|
|
1295
|
+
};
|
|
937
1296
|
const permissionMap = await getPermissionMap({ userId, scope });
|
|
938
1297
|
setPermissions(permissionMap);
|
|
939
1298
|
} catch (err) {
|
|
940
|
-
|
|
1299
|
+
const logger3 = getRBACLogger();
|
|
1300
|
+
logger3.error("Failed to refetch permissions:", err);
|
|
1301
|
+
setError(err instanceof Error ? err : new Error("Failed to fetch permissions"));
|
|
941
1302
|
} finally {
|
|
942
1303
|
setIsLoading(false);
|
|
1304
|
+
isFetchingRef.current = false;
|
|
943
1305
|
}
|
|
944
|
-
}, [userId,
|
|
945
|
-
|
|
946
|
-
fetchCachedPermissions();
|
|
947
|
-
}, [fetchCachedPermissions]);
|
|
948
|
-
useEffect2(() => {
|
|
949
|
-
fetchCachedPermissions();
|
|
950
|
-
}, [fetchCachedPermissions]);
|
|
951
|
-
return useMemo2(() => ({
|
|
1306
|
+
}, [userId, organisationId, eventId, appId]);
|
|
1307
|
+
return useMemo9(() => ({
|
|
952
1308
|
permissions,
|
|
953
1309
|
isLoading,
|
|
954
1310
|
error,
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
1311
|
+
hasPermission,
|
|
1312
|
+
hasAnyPermission,
|
|
1313
|
+
hasAllPermissions,
|
|
1314
|
+
refetch
|
|
1315
|
+
}), [permissions, isLoading, error, hasPermission, hasAnyPermission, hasAllPermissions, refetch]);
|
|
958
1316
|
}
|
|
959
1317
|
|
|
960
1318
|
// src/rbac/hooks/useResourcePermissions.ts
|
|
961
|
-
import { useMemo as
|
|
1319
|
+
import { useMemo as useMemo10 } from "react";
|
|
962
1320
|
function useResourcePermissions(resource, options = {}) {
|
|
963
1321
|
const { enableRead = false, requireScope = true } = options;
|
|
964
1322
|
const { user, supabase } = useUnifiedAuth();
|
|
@@ -986,8 +1344,12 @@ function useResourcePermissions(resource, options = {}) {
|
|
|
986
1344
|
`create:${resource}`,
|
|
987
1345
|
pageId,
|
|
988
1346
|
// Pass resource name as pageId when appId is available to enable page permission checks
|
|
989
|
-
true
|
|
1347
|
+
true,
|
|
990
1348
|
// useCache
|
|
1349
|
+
null,
|
|
1350
|
+
// precomputedSuperAdmin - not checked yet
|
|
1351
|
+
void 0
|
|
1352
|
+
// appName
|
|
991
1353
|
);
|
|
992
1354
|
const { can: canUpdateResult, isLoading: updateLoading, error: updateError } = useCan(
|
|
993
1355
|
user?.id || "",
|
|
@@ -995,8 +1357,12 @@ function useResourcePermissions(resource, options = {}) {
|
|
|
995
1357
|
`update:${resource}`,
|
|
996
1358
|
pageId,
|
|
997
1359
|
// Pass resource name as pageId when appId is available to enable page permission checks
|
|
998
|
-
true
|
|
1360
|
+
true,
|
|
999
1361
|
// useCache
|
|
1362
|
+
null,
|
|
1363
|
+
// precomputedSuperAdmin - not checked yet
|
|
1364
|
+
void 0
|
|
1365
|
+
// appName
|
|
1000
1366
|
);
|
|
1001
1367
|
const { can: canDeleteResult, isLoading: deleteLoading, error: deleteError } = useCan(
|
|
1002
1368
|
user?.id || "",
|
|
@@ -1004,8 +1370,12 @@ function useResourcePermissions(resource, options = {}) {
|
|
|
1004
1370
|
`delete:${resource}`,
|
|
1005
1371
|
pageId,
|
|
1006
1372
|
// Pass resource name as pageId when appId is available to enable page permission checks
|
|
1007
|
-
true
|
|
1373
|
+
true,
|
|
1008
1374
|
// useCache
|
|
1375
|
+
null,
|
|
1376
|
+
// precomputedSuperAdmin - not checked yet
|
|
1377
|
+
void 0
|
|
1378
|
+
// appName
|
|
1009
1379
|
);
|
|
1010
1380
|
const { can: canReadResult, isLoading: readLoading, error: readError } = useCan(
|
|
1011
1381
|
user?.id || "",
|
|
@@ -1013,13 +1383,17 @@ function useResourcePermissions(resource, options = {}) {
|
|
|
1013
1383
|
`read:${resource}`,
|
|
1014
1384
|
pageId,
|
|
1015
1385
|
// Pass resource name as pageId when appId is available to enable page permission checks
|
|
1016
|
-
true
|
|
1386
|
+
true,
|
|
1017
1387
|
// useCache
|
|
1388
|
+
null,
|
|
1389
|
+
// precomputedSuperAdmin - not checked yet
|
|
1390
|
+
void 0
|
|
1391
|
+
// appName
|
|
1018
1392
|
);
|
|
1019
|
-
const isLoading =
|
|
1393
|
+
const isLoading = useMemo10(() => {
|
|
1020
1394
|
return scopeLoading || createLoading || updateLoading || deleteLoading || enableRead && readLoading;
|
|
1021
1395
|
}, [scopeLoading, createLoading, updateLoading, deleteLoading, readLoading, enableRead]);
|
|
1022
|
-
const error =
|
|
1396
|
+
const error = useMemo10(() => {
|
|
1023
1397
|
if (scopeError) return scopeError;
|
|
1024
1398
|
if (createError) return createError;
|
|
1025
1399
|
if (updateError) return updateError;
|
|
@@ -1027,7 +1401,7 @@ function useResourcePermissions(resource, options = {}) {
|
|
|
1027
1401
|
if (enableRead && readError) return readError;
|
|
1028
1402
|
return null;
|
|
1029
1403
|
}, [scopeError, createError, updateError, deleteError, readError, enableRead]);
|
|
1030
|
-
return
|
|
1404
|
+
return useMemo10(() => ({
|
|
1031
1405
|
canCreate: (res) => {
|
|
1032
1406
|
if (res !== resource) {
|
|
1033
1407
|
return false;
|
|
@@ -1072,15 +1446,15 @@ function useResourcePermissions(resource, options = {}) {
|
|
|
1072
1446
|
}
|
|
1073
1447
|
|
|
1074
1448
|
// src/rbac/hooks/useRoleManagement.ts
|
|
1075
|
-
import { useState as
|
|
1449
|
+
import { useState as useState10, useCallback as useCallback9 } from "react";
|
|
1076
1450
|
function useRoleManagement() {
|
|
1077
1451
|
const { user, supabase } = useUnifiedAuth();
|
|
1078
|
-
const [isLoading, setIsLoading] =
|
|
1079
|
-
const [error, setError] =
|
|
1452
|
+
const [isLoading, setIsLoading] = useState10(false);
|
|
1453
|
+
const [error, setError] = useState10(null);
|
|
1080
1454
|
if (!supabase) {
|
|
1081
1455
|
throw new Error("useRoleManagement requires a Supabase client. Ensure UnifiedAuthProvider is configured.");
|
|
1082
1456
|
}
|
|
1083
|
-
const revokeEventAppRole =
|
|
1457
|
+
const revokeEventAppRole = useCallback9(async (params) => {
|
|
1084
1458
|
setIsLoading(true);
|
|
1085
1459
|
setError(null);
|
|
1086
1460
|
try {
|
|
@@ -1111,7 +1485,7 @@ function useRoleManagement() {
|
|
|
1111
1485
|
setIsLoading(false);
|
|
1112
1486
|
}
|
|
1113
1487
|
}, [user?.id]);
|
|
1114
|
-
const grantEventAppRole =
|
|
1488
|
+
const grantEventAppRole = useCallback9(async (params) => {
|
|
1115
1489
|
setIsLoading(true);
|
|
1116
1490
|
setError(null);
|
|
1117
1491
|
try {
|
|
@@ -1150,7 +1524,7 @@ function useRoleManagement() {
|
|
|
1150
1524
|
setIsLoading(false);
|
|
1151
1525
|
}
|
|
1152
1526
|
}, [user?.id]);
|
|
1153
|
-
const revokeRoleById =
|
|
1527
|
+
const revokeRoleById = useCallback9(async (roleId) => {
|
|
1154
1528
|
setIsLoading(true);
|
|
1155
1529
|
setError(null);
|
|
1156
1530
|
try {
|
|
@@ -1186,7 +1560,7 @@ function useRoleManagement() {
|
|
|
1186
1560
|
setIsLoading(false);
|
|
1187
1561
|
}
|
|
1188
1562
|
}, [user?.id, supabase]);
|
|
1189
|
-
const grantGlobalRole =
|
|
1563
|
+
const grantGlobalRole = useCallback9(async (params) => {
|
|
1190
1564
|
setIsLoading(true);
|
|
1191
1565
|
setError(null);
|
|
1192
1566
|
try {
|
|
@@ -1225,7 +1599,7 @@ function useRoleManagement() {
|
|
|
1225
1599
|
setIsLoading(false);
|
|
1226
1600
|
}
|
|
1227
1601
|
}, [user?.id, supabase]);
|
|
1228
|
-
const revokeGlobalRole =
|
|
1602
|
+
const revokeGlobalRole = useCallback9(async (params) => {
|
|
1229
1603
|
setIsLoading(true);
|
|
1230
1604
|
setError(null);
|
|
1231
1605
|
try {
|
|
@@ -1257,7 +1631,7 @@ function useRoleManagement() {
|
|
|
1257
1631
|
setIsLoading(false);
|
|
1258
1632
|
}
|
|
1259
1633
|
}, [user?.id, supabase]);
|
|
1260
|
-
const grantOrganisationRole =
|
|
1634
|
+
const grantOrganisationRole = useCallback9(async (params) => {
|
|
1261
1635
|
setIsLoading(true);
|
|
1262
1636
|
setError(null);
|
|
1263
1637
|
try {
|
|
@@ -1296,7 +1670,7 @@ function useRoleManagement() {
|
|
|
1296
1670
|
setIsLoading(false);
|
|
1297
1671
|
}
|
|
1298
1672
|
}, [user?.id, supabase]);
|
|
1299
|
-
const revokeOrganisationRole =
|
|
1673
|
+
const revokeOrganisationRole = useCallback9(async (params) => {
|
|
1300
1674
|
setIsLoading(true);
|
|
1301
1675
|
setError(null);
|
|
1302
1676
|
try {
|
|
@@ -1346,11 +1720,11 @@ function useRoleManagement() {
|
|
|
1346
1720
|
}
|
|
1347
1721
|
|
|
1348
1722
|
// src/rbac/hooks/useSecureSupabase.ts
|
|
1349
|
-
import { useMemo as
|
|
1723
|
+
import { useMemo as useMemo11, useRef as useRef4 } from "react";
|
|
1350
1724
|
var secureClientCache = /* @__PURE__ */ new Map();
|
|
1351
1725
|
var MAX_CACHE_SIZE = 5;
|
|
1352
1726
|
function getCacheKey(organisationId, eventId, appId, isSuperAdmin) {
|
|
1353
|
-
return `${organisationId}-${eventId || "no-event"}-${appId || "no-app"}-${isSuperAdmin ? "super" : "regular"}`;
|
|
1727
|
+
return `${organisationId || "no-org"}-${eventId || "no-event"}-${appId || "no-app"}-${isSuperAdmin ? "super" : "regular"}`;
|
|
1354
1728
|
}
|
|
1355
1729
|
function getSupabaseConfig() {
|
|
1356
1730
|
const getEnvVar = (key) => {
|
|
@@ -1382,19 +1756,20 @@ function useSecureSupabase(baseClient) {
|
|
|
1382
1756
|
selectedOrganisationId: selectedOrganisation?.id || null,
|
|
1383
1757
|
selectedEventId: selectedEvent?.event_id || null
|
|
1384
1758
|
});
|
|
1385
|
-
const prevContextRef =
|
|
1759
|
+
const prevContextRef = useRef4({
|
|
1386
1760
|
organisationId: void 0,
|
|
1387
1761
|
eventId: void 0,
|
|
1388
1762
|
appId: void 0
|
|
1389
1763
|
});
|
|
1390
|
-
return
|
|
1764
|
+
return useMemo11(() => {
|
|
1391
1765
|
if (eventLoading) {
|
|
1392
1766
|
return baseClient || authSupabase || null;
|
|
1393
1767
|
}
|
|
1394
1768
|
const organisationId = resolvedScope?.organisationId;
|
|
1395
1769
|
const eventId = resolvedScope?.eventId || selectedEvent?.event_id;
|
|
1396
1770
|
const appId = resolvedScope?.appId;
|
|
1397
|
-
|
|
1771
|
+
const canCreateSecureClient = user?.id && (isSuperAdmin || organisationId);
|
|
1772
|
+
if (canCreateSecureClient) {
|
|
1398
1773
|
prevContextRef.current = { organisationId, eventId, appId };
|
|
1399
1774
|
const cacheKey = getCacheKey(organisationId, eventId, appId, isSuperAdmin);
|
|
1400
1775
|
const cachedClient = secureClientCache.get(cacheKey);
|
|
@@ -1409,11 +1784,19 @@ function useSecureSupabase(baseClient) {
|
|
|
1409
1784
|
return baseClient || authSupabase || null;
|
|
1410
1785
|
}
|
|
1411
1786
|
try {
|
|
1412
|
-
const
|
|
1787
|
+
const effectiveOrganisationId = isSuperAdmin ? organisationId || null : organisationId;
|
|
1788
|
+
const baseForSecureClient = baseClient || authSupabase || null;
|
|
1789
|
+
const secureClient = baseForSecureClient ? fromSupabaseClient(
|
|
1790
|
+
baseForSecureClient,
|
|
1791
|
+
effectiveOrganisationId ?? null,
|
|
1792
|
+
eventId,
|
|
1793
|
+
appId,
|
|
1794
|
+
isSuperAdmin
|
|
1795
|
+
) : createSecureClient(
|
|
1413
1796
|
config.url,
|
|
1414
1797
|
config.key,
|
|
1415
|
-
|
|
1416
|
-
// organisationId is string, UUID is string alias
|
|
1798
|
+
effectiveOrganisationId,
|
|
1799
|
+
// organisationId is string | null, UUID is string alias
|
|
1417
1800
|
eventId,
|
|
1418
1801
|
appId,
|
|
1419
1802
|
// appId is string | undefined, UUID is string alias
|
|
@@ -1451,17 +1834,18 @@ export {
|
|
|
1451
1834
|
SecureSupabaseClient,
|
|
1452
1835
|
createSecureClient,
|
|
1453
1836
|
fromSupabaseClient,
|
|
1837
|
+
useResolvedScope,
|
|
1454
1838
|
useRBAC,
|
|
1839
|
+
useAccessLevel,
|
|
1840
|
+
useCachedPermissions,
|
|
1455
1841
|
scopeEqual,
|
|
1456
|
-
usePermissions,
|
|
1457
1842
|
useCan,
|
|
1458
|
-
useAccessLevel,
|
|
1459
|
-
useMultiplePermissions,
|
|
1460
|
-
useHasAnyPermission,
|
|
1461
1843
|
useHasAllPermissions,
|
|
1462
|
-
|
|
1844
|
+
useHasAnyPermission,
|
|
1845
|
+
useMultiplePermissions,
|
|
1846
|
+
usePermissions,
|
|
1463
1847
|
useResourcePermissions,
|
|
1464
1848
|
useRoleManagement,
|
|
1465
1849
|
useSecureSupabase
|
|
1466
1850
|
};
|
|
1467
|
-
//# sourceMappingURL=chunk-
|
|
1851
|
+
//# sourceMappingURL=chunk-XWQCNGTQ.js.map
|