@jmruthers/pace-core 0.6.1 → 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 +43 -10
- package/cursor-rules/00-pace-core-compliance.mdc +18 -91
- package/cursor-rules/01-standards-compliance.mdc +16 -47
- package/cursor-rules/02-project-structure.mdc +4 -4
- package/cursor-rules/03-solid-principles.mdc +45 -164
- package/cursor-rules/04-testing-standards.mdc +22 -69
- package/cursor-rules/05-bug-reports-and-features.mdc +2 -2
- package/cursor-rules/06-code-quality.mdc +42 -125
- package/cursor-rules/07-tech-stack-compliance.mdc +33 -128
- package/cursor-rules/08-markup-quality.mdc +452 -0
- package/cursor-rules/CHANGELOG.md +18 -0
- package/cursor-rules/README.md +2 -1
- package/dist/{AuthService-DjnJHDtC.d.ts → AuthService-BPvc3Ka0.d.ts} +54 -0
- package/dist/{DataTable-CH1U5Tpy.d.ts → DataTable-BMRU8a1j.d.ts} +33 -1
- package/dist/{DataTable-DQ7RSOHE.js → DataTable-TPTKCX4D.js} +10 -9
- package/dist/{PublicPageProvider-ce4xlHYA.d.ts → PublicPageProvider-DC6kCaqf.d.ts} +356 -111
- package/dist/{UnifiedAuthProvider-ATAP5UTR.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-4N5C5XZU.js → chunk-2UOI2FG5.js} +4 -4
- package/dist/chunk-2UOI2FG5.js.map +1 -0
- package/dist/{chunk-T33XF5ZC.js → chunk-3XC4CPTD.js} +4317 -3963
- package/dist/chunk-3XC4CPTD.js.map +1 -0
- package/dist/{chunk-4ZC4GX36.js → chunk-6J4GEEJR.js} +172 -45
- package/dist/chunk-6J4GEEJR.js.map +1 -0
- package/dist/{chunk-3QRJFVBR.js → chunk-6SOIHG6Z.js} +1 -1
- package/dist/chunk-6SOIHG6Z.js.map +1 -0
- package/dist/{chunk-BYFSK72L.js → chunk-EHMR7VYL.js} +4 -4
- 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-LXQLPRQ2.js → chunk-FFQEQTNW.js} +6 -8
- 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-SQGMNID3.js → chunk-L4OXEN46.js} +4 -5
- package/dist/chunk-L4OXEN46.js.map +1 -0
- package/dist/{chunk-R77UEZ4E.js → chunk-M43Y4SSO.js} +1 -1
- package/dist/chunk-M43Y4SSO.js.map +1 -0
- package/dist/{chunk-3XTALGJF.js → chunk-MMZ7JXPU.js} +60 -223
- package/dist/chunk-MMZ7JXPU.js.map +1 -0
- package/dist/{chunk-GLK6VM3F.js → chunk-NECFR5MM.js} +254 -170
- package/dist/chunk-NECFR5MM.js.map +1 -0
- package/dist/{chunk-JBKQ3SAO.js → chunk-SFZUDBL5.js} +40 -4
- package/dist/chunk-SFZUDBL5.js.map +1 -0
- package/dist/{chunk-XM25TVIE.js → chunk-XWQCNGTQ.js} +724 -363
- package/dist/chunk-XWQCNGTQ.js.map +1 -0
- package/dist/components.d.ts +5 -5
- package/dist/components.js +14 -11
- 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 +55 -122
- package/dist/hooks.js +8 -12
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +60 -13
- package/dist/index.js +19 -19
- 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 +145 -114
- 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-BJAlWfuJ.d.ts → usePublicRouteParams-1oMokgLF.d.ts} +31 -1
- package/dist/utils.d.ts +4 -5
- package/dist/utils.js +14 -14
- 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 +2 -1
- package/docs/migration/DOCUMENTATION_STRUCTURE.md +441 -0
- package/docs/migration/MIGRATION_GUIDE.md +2 -24
- package/docs/migration/README.md +52 -6
- package/docs/migration/V0.5.190_TO_V0.6.1_MIGRATION.md +1153 -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 +1 -0
- package/package.json +2 -1
- 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} +714 -687
- 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 +61 -936
- 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/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__/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 +14 -0
- package/src/components/Button/Button.tsx +22 -0
- package/src/components/Calendar/Calendar.tsx +8 -2
- 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.tsx +38 -4
- package/src/components/DataTable/__tests__/DataTableCore.test-setup.ts +5 -6
- package/src/components/DataTable/__tests__/pagination.modes.test.tsx +18 -4
- package/src/components/DataTable/__tests__/test-utils/sharedTestUtils.tsx +2 -3
- package/src/components/DataTable/components/AccessDeniedPage.tsx +16 -25
- package/src/components/DataTable/components/ActionButtons.tsx +10 -7
- package/src/components/DataTable/components/BulkOperationsDropdown.tsx +1 -1
- 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 +196 -554
- 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 +8 -0
- 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 +8 -0
- 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 +61 -849
- 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/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 +12 -0
- 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/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/Dialog/Dialog.tsx +2 -2
- package/src/components/ErrorBoundary/ErrorBoundary.test.tsx +180 -1
- package/src/components/ErrorBoundary/ErrorBoundary.tsx +45 -5
- package/src/components/ErrorBoundary/ErrorBoundaryContext.tsx +129 -0
- package/src/components/ErrorBoundary/index.ts +27 -2
- package/src/components/EventSelector/EventSelector.tsx +3 -0
- 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 +14 -11
- package/src/components/Form/Form.tsx +1 -0
- package/src/components/Header/Header.tsx +21 -10
- package/src/components/Input/Input.test.tsx +2 -2
- package/src/components/Input/Input.tsx +8 -4
- package/src/components/LoadingSpinner/LoadingSpinner.test.tsx +4 -4
- package/src/components/LoginForm/LoginForm.tsx +4 -0
- package/src/components/NavigationMenu/NavigationMenu.tsx +14 -513
- 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.test.tsx +4 -2
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +32 -11
- package/src/components/PaceAppLayout/test-setup.tsx +1 -2
- package/src/components/PaceLoginPage/PaceLoginPage.tsx +3 -0
- package/src/components/PasswordChange/PasswordChangeForm.tsx +9 -0
- package/src/components/ProtectedRoute/ProtectedRoute.tsx +3 -9
- package/src/components/PublicLayout/PublicPageLayout.tsx +2 -5
- package/src/components/PublicLayout/PublicPageProvider.tsx +4 -0
- package/src/components/Select/Select.tsx +80 -434
- 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 +4 -5
- package/src/components/Switch/Switch.tsx +4 -4
- package/src/components/Tabs/Tabs.tsx +1 -1
- package/src/components/Toast/Toast.tsx +4 -0
- package/src/components/Tooltip/Tooltip.tsx +2 -2
- package/src/components/UserMenu/UserMenu.test.tsx +24 -11
- package/src/components/UserMenu/UserMenu.tsx +21 -18
- package/src/components/index.ts +2 -2
- package/src/hooks/__tests__/index.unit.test.ts +2 -5
- package/src/hooks/index.ts +1 -2
- package/src/hooks/public/usePublicEvent.ts +4 -0
- package/src/hooks/public/usePublicEventLogo.ts +4 -0
- package/src/hooks/public/usePublicFileDisplay.ts +4 -0
- package/src/hooks/public/usePublicRouteParams.ts +4 -0
- 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/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 +14 -0
- package/src/hooks/useFocusTrap.ts +3 -0
- package/src/hooks/useInactivityTracker.ts +3 -0
- 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 +7 -0
- 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 +1 -1
- package/src/index.ts +2 -1
- package/src/providers/__tests__/OrganisationProvider.test.tsx +92 -70
- 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 +36 -0
- package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +29 -13
- package/src/rbac/README.md +1 -1
- package/src/rbac/__tests__/adapters.comprehensive.test.tsx +2 -2
- 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/NavigationProvider.tsx +4 -1
- package/src/rbac/components/PagePermissionGuard.tsx +157 -17
- package/src/rbac/components/RoleBasedRouter.tsx +5 -1
- package/src/rbac/components/SecureDataProvider.test.tsx +84 -49
- package/src/rbac/components/SecureDataProvider.tsx +20 -5
- 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 +200 -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/dist/chunk-3QRJFVBR.js.map +0 -1
- package/dist/chunk-3XTALGJF.js.map +0 -1
- package/dist/chunk-4N5C5XZU.js.map +0 -1
- package/dist/chunk-4ZC4GX36.js.map +0 -1
- package/dist/chunk-BYFSK72L.js.map +0 -1
- package/dist/chunk-EXUD6RNJ.js +0 -451
- package/dist/chunk-EXUD6RNJ.js.map +0 -1
- package/dist/chunk-GLK6VM3F.js.map +0 -1
- package/dist/chunk-I7PSE6JW.js.map +0 -1
- package/dist/chunk-JBKQ3SAO.js.map +0 -1
- package/dist/chunk-KNC55RTG.js.map +0 -1
- package/dist/chunk-LXQLPRQ2.js.map +0 -1
- package/dist/chunk-R77UEZ4E.js.map +0 -1
- package/dist/chunk-SQGMNID3.js.map +0 -1
- package/dist/chunk-T33XF5ZC.js.map +0 -1
- package/dist/chunk-XM25TVIE.js.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 -681
- /package/dist/{DataTable-DQ7RSOHE.js.map → DataTable-TPTKCX4D.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-ATAP5UTR.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/docs/migration/{REACT_19_MIGRATION.md → V0.6.0_REACT_19_MIGRATION.md} +0 -0
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
useAppConfig,
|
|
3
3
|
useEvents,
|
|
4
|
-
useOrganisationSecurity
|
|
5
|
-
|
|
6
|
-
} from "./chunk-3XTALGJF.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
|
*/
|
|
@@ -189,6 +216,13 @@ var SecureSupabaseClient = class _SecureSupabaseClient {
|
|
|
189
216
|
if (!this.organisationId) {
|
|
190
217
|
return query;
|
|
191
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
|
+
}
|
|
192
226
|
if (tableName === "rbac_user_profiles") {
|
|
193
227
|
if (this.isSuperAdmin) {
|
|
194
228
|
return query;
|
|
@@ -202,12 +236,57 @@ var SecureSupabaseClient = class _SecureSupabaseClient {
|
|
|
202
236
|
}
|
|
203
237
|
/**
|
|
204
238
|
* Validate that required context is present
|
|
239
|
+
* Super-admins can operate without organisation context
|
|
205
240
|
*/
|
|
206
241
|
validateContext() {
|
|
242
|
+
if (this.isSuperAdmin) {
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
207
245
|
if (!this.organisationId) {
|
|
208
246
|
throw new OrganisationContextRequiredError();
|
|
209
247
|
}
|
|
210
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
|
+
}
|
|
211
290
|
/**
|
|
212
291
|
* Get the current organisation ID
|
|
213
292
|
*/
|
|
@@ -233,7 +312,7 @@ var SecureSupabaseClient = class _SecureSupabaseClient {
|
|
|
233
312
|
return new _SecureSupabaseClient(
|
|
234
313
|
this.supabaseUrl,
|
|
235
314
|
this.supabaseKey,
|
|
236
|
-
updates.organisationId
|
|
315
|
+
updates.organisationId !== void 0 ? updates.organisationId : this.organisationId,
|
|
237
316
|
updates.eventId !== void 0 ? updates.eventId : this.eventId,
|
|
238
317
|
updates.appId !== void 0 ? updates.appId : this.appId,
|
|
239
318
|
updates.isSuperAdmin !== void 0 ? updates.isSuperAdmin : this.isSuperAdmin
|
|
@@ -254,15 +333,216 @@ var SecureSupabaseClient = class _SecureSupabaseClient {
|
|
|
254
333
|
});
|
|
255
334
|
}
|
|
256
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;
|
|
257
350
|
function createSecureClient(supabaseUrl, supabaseKey, organisationId, eventId, appId, isSuperAdmin = false) {
|
|
258
351
|
return new SecureSupabaseClient(supabaseUrl, supabaseKey, organisationId, eventId, appId, isSuperAdmin);
|
|
259
352
|
}
|
|
260
|
-
function fromSupabaseClient(client, organisationId, eventId, appId) {
|
|
261
|
-
|
|
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
|
+
};
|
|
262
542
|
}
|
|
263
543
|
|
|
264
544
|
// src/rbac/hooks/useRBAC.ts
|
|
265
|
-
import { useState, useEffect, useCallback, useMemo } from "react";
|
|
545
|
+
import { useState as useState2, useEffect as useEffect2, useCallback, useMemo as useMemo2 } from "react";
|
|
266
546
|
function mapAccessLevelToEventRole(level) {
|
|
267
547
|
switch (level) {
|
|
268
548
|
case "viewer":
|
|
@@ -293,13 +573,13 @@ function useRBAC(pageId) {
|
|
|
293
573
|
selectedEvent,
|
|
294
574
|
eventLoading
|
|
295
575
|
} = useUnifiedAuth();
|
|
296
|
-
const [globalRole, setGlobalRole] =
|
|
297
|
-
const [organisationRole, setOrganisationRole] =
|
|
298
|
-
const [eventAppRole, setEventAppRole] =
|
|
299
|
-
const [permissionMap, setPermissionMap] =
|
|
300
|
-
const [currentScope, setCurrentScope] =
|
|
301
|
-
const [isLoading, setIsLoading] =
|
|
302
|
-
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);
|
|
303
583
|
const resetState = useCallback(() => {
|
|
304
584
|
setGlobalRole(null);
|
|
305
585
|
setOrganisationRole(null);
|
|
@@ -339,7 +619,7 @@ function useRBAC(pageId) {
|
|
|
339
619
|
if (!resolved) {
|
|
340
620
|
if (appName === "PORTAL" || appName === "ADMIN") {
|
|
341
621
|
try {
|
|
342
|
-
const { getAppConfigByName } = await import("./api-
|
|
622
|
+
const { getAppConfigByName } = await import("./api-MVVQZLJI.js");
|
|
343
623
|
await getAppConfigByName(appName);
|
|
344
624
|
} catch (err) {
|
|
345
625
|
}
|
|
@@ -424,12 +704,12 @@ function useRBAC(pageId) {
|
|
|
424
704
|
},
|
|
425
705
|
[globalRole, organisationRole, permissionMap]
|
|
426
706
|
);
|
|
427
|
-
const isSuperAdmin =
|
|
428
|
-
const isOrgAdmin =
|
|
429
|
-
const isEventAdmin =
|
|
430
|
-
const canManageOrganisation =
|
|
431
|
-
const canManageEvent =
|
|
432
|
-
|
|
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(() => {
|
|
433
713
|
loadRBACContext();
|
|
434
714
|
}, [loadRBACContext, appName, appConfig, eventLoading, selectedEvent?.event_id, user, session, selectedOrganisation?.id, orgContextReady, orgLoading]);
|
|
435
715
|
return {
|
|
@@ -448,197 +728,176 @@ function useRBAC(pageId) {
|
|
|
448
728
|
};
|
|
449
729
|
}
|
|
450
730
|
|
|
451
|
-
// src/rbac/hooks/
|
|
452
|
-
import {
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
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 {
|
|
461
742
|
}
|
|
462
|
-
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
// src/rbac/hooks/usePermissions.ts
|
|
466
|
-
function usePermissions(userId, organisationId, eventId, appId) {
|
|
467
|
-
const [permissions, setPermissions] = useState2({});
|
|
468
|
-
const [isLoading, setIsLoading] = useState2(true);
|
|
469
|
-
const [error, setError] = useState2(null);
|
|
470
|
-
const [fetchTrigger, setFetchTrigger] = useState2(0);
|
|
471
|
-
const isFetchingRef = useRef(false);
|
|
472
|
-
const logger2 = getRBACLogger();
|
|
473
|
-
const prevValuesRef = useRef({ userId, organisationId, eventId, appId });
|
|
474
|
-
const orgId = organisationId || "";
|
|
475
|
-
useEffect2(() => {
|
|
743
|
+
const fetchAccessLevel = useCallback2(async () => {
|
|
476
744
|
if (!userId) {
|
|
745
|
+
setAccessLevel("viewer");
|
|
746
|
+
setIsLoading(false);
|
|
477
747
|
return;
|
|
478
748
|
}
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
setError(new Error("Organisation context is required for permission checks"));
|
|
482
|
-
setIsLoading(false);
|
|
483
|
-
}, 3e3);
|
|
484
|
-
return () => clearTimeout(timeoutId);
|
|
485
|
-
}
|
|
486
|
-
if (error?.message === "Organisation context is required for permission checks") {
|
|
749
|
+
try {
|
|
750
|
+
setIsLoading(true);
|
|
487
751
|
setError(null);
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
if (paramsChanged) {
|
|
493
|
-
if (prevValuesRef.current.appId !== appId) {
|
|
494
|
-
}
|
|
495
|
-
prevValuesRef.current = { userId, organisationId, eventId, appId };
|
|
496
|
-
setFetchTrigger((prev) => prev + 1);
|
|
497
|
-
}
|
|
498
|
-
}, [userId, organisationId, eventId, appId, logger2]);
|
|
499
|
-
useEffect2(() => {
|
|
500
|
-
const fetchPermissions = async () => {
|
|
501
|
-
if (isFetchingRef.current) {
|
|
502
|
-
return;
|
|
503
|
-
}
|
|
504
|
-
if (!userId) {
|
|
505
|
-
setPermissions({});
|
|
752
|
+
const { isSuperAdmin: checkSuperAdmin } = await import("./api-MVVQZLJI.js");
|
|
753
|
+
const isSuperAdminUser = await checkSuperAdmin(userId);
|
|
754
|
+
if (isSuperAdminUser) {
|
|
755
|
+
setAccessLevel("super");
|
|
506
756
|
setIsLoading(false);
|
|
507
757
|
return;
|
|
508
758
|
}
|
|
509
|
-
if (!
|
|
510
|
-
|
|
759
|
+
if (appName !== "PORTAL" && appName !== "ADMIN" && !scope.organisationId && !scope.eventId) {
|
|
760
|
+
const orgError = new OrganisationContextRequiredError();
|
|
761
|
+
setError(orgError);
|
|
762
|
+
setAccessLevel("viewer");
|
|
511
763
|
setIsLoading(false);
|
|
512
764
|
return;
|
|
513
765
|
}
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
setError(null);
|
|
523
|
-
const scope = {
|
|
524
|
-
organisationId: orgId,
|
|
525
|
-
eventId,
|
|
526
|
-
appId
|
|
527
|
-
};
|
|
528
|
-
const permissionMap = await getPermissionMap({ userId, scope });
|
|
529
|
-
const permissionCount = Object.keys(permissionMap).length;
|
|
530
|
-
if (permissionCount === 0 && Object.keys(permissions).length > 0) {
|
|
531
|
-
logger2.warn("[usePermissions] Permissions fetched but returned empty map", {
|
|
532
|
-
scope: { organisationId: orgId, eventId, appId }
|
|
533
|
-
});
|
|
534
|
-
}
|
|
535
|
-
setPermissions(permissionMap);
|
|
536
|
-
} catch (err) {
|
|
537
|
-
logger2.error("[usePermissions] Failed to fetch permissions:", err);
|
|
538
|
-
setError(err instanceof Error ? err : new Error("Failed to fetch permissions"));
|
|
539
|
-
} finally {
|
|
540
|
-
setIsLoading(false);
|
|
541
|
-
isFetchingRef.current = false;
|
|
542
|
-
}
|
|
543
|
-
};
|
|
544
|
-
fetchPermissions();
|
|
545
|
-
}, [fetchTrigger, userId, organisationId, eventId, appId]);
|
|
546
|
-
const hasPermission = useCallback2((permission) => {
|
|
547
|
-
if (permissions["*"]) {
|
|
548
|
-
return true;
|
|
549
|
-
}
|
|
550
|
-
return permissions[permission] === true;
|
|
551
|
-
}, [permissions]);
|
|
552
|
-
const hasAnyPermission = useCallback2((permissionList) => {
|
|
553
|
-
if (permissions["*"]) {
|
|
554
|
-
return true;
|
|
555
|
-
}
|
|
556
|
-
return permissionList.some((p) => permissions[p] === true);
|
|
557
|
-
}, [permissions]);
|
|
558
|
-
const hasAllPermissions = useCallback2((permissionList) => {
|
|
559
|
-
if (permissions["*"]) {
|
|
560
|
-
return true;
|
|
561
|
-
}
|
|
562
|
-
return permissionList.every((p) => permissions[p] === true);
|
|
563
|
-
}, [permissions]);
|
|
564
|
-
const refetch = useCallback2(async () => {
|
|
565
|
-
if (isFetchingRef.current) {
|
|
566
|
-
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);
|
|
567
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 () => {
|
|
568
794
|
if (!userId) {
|
|
569
795
|
setPermissions({});
|
|
570
796
|
setIsLoading(false);
|
|
571
797
|
return;
|
|
572
798
|
}
|
|
573
|
-
if (!orgId || orgId === null || typeof orgId === "string" && orgId.trim() === "") {
|
|
574
|
-
setIsLoading(true);
|
|
575
|
-
setError(null);
|
|
576
|
-
return;
|
|
577
|
-
}
|
|
578
799
|
try {
|
|
579
|
-
isFetchingRef.current = true;
|
|
580
800
|
setIsLoading(true);
|
|
581
801
|
setError(null);
|
|
582
|
-
const scope = {
|
|
583
|
-
organisationId: orgId,
|
|
584
|
-
eventId,
|
|
585
|
-
appId
|
|
586
|
-
};
|
|
587
802
|
const permissionMap = await getPermissionMap({ userId, scope });
|
|
588
803
|
setPermissions(permissionMap);
|
|
589
804
|
} catch (err) {
|
|
590
|
-
|
|
591
|
-
logger3.error("Failed to refetch permissions:", err);
|
|
592
|
-
setError(err instanceof Error ? err : new Error("Failed to fetch permissions"));
|
|
805
|
+
setError(err instanceof Error ? err : new Error("Failed to fetch cached permissions"));
|
|
593
806
|
} finally {
|
|
594
807
|
setIsLoading(false);
|
|
595
|
-
isFetchingRef.current = false;
|
|
596
808
|
}
|
|
597
|
-
}, [userId, organisationId, eventId, appId]);
|
|
598
|
-
|
|
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(() => ({
|
|
599
817
|
permissions,
|
|
600
818
|
isLoading,
|
|
601
819
|
error,
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
refetch
|
|
606
|
-
}), [permissions, isLoading, error, hasPermission, hasAnyPermission, hasAllPermissions, refetch]);
|
|
820
|
+
invalidateCache,
|
|
821
|
+
refetch: fetchCachedPermissions
|
|
822
|
+
}), [permissions, isLoading, error, invalidateCache, fetchCachedPermissions]);
|
|
607
823
|
}
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
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);
|
|
613
845
|
const isValidScope = scope && typeof scope === "object";
|
|
614
846
|
const organisationId = isValidScope ? scope.organisationId : void 0;
|
|
615
847
|
const eventId = isValidScope ? scope.eventId : void 0;
|
|
616
848
|
const appId = isValidScope ? scope.appId : void 0;
|
|
617
|
-
|
|
618
|
-
if (
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
let cancelled = false;
|
|
623
|
-
const checkSuperAdmin = async () => {
|
|
624
|
-
try {
|
|
625
|
-
const { isSuperAdmin: checkSuperAdmin2 } = await import("./api-N774RPUA.js");
|
|
626
|
-
const isSuper = await checkSuperAdmin2(userId);
|
|
627
|
-
if (!cancelled) {
|
|
628
|
-
setIsSuperAdmin(isSuper);
|
|
629
|
-
}
|
|
630
|
-
} catch (err) {
|
|
631
|
-
if (!cancelled) {
|
|
632
|
-
setIsSuperAdmin(false);
|
|
633
|
-
}
|
|
849
|
+
useEffect5(() => {
|
|
850
|
+
if (precomputedSuperAdmin === null) {
|
|
851
|
+
if (!userId) {
|
|
852
|
+
setIsSuperAdmin(false);
|
|
853
|
+
return;
|
|
634
854
|
}
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
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(() => {
|
|
642
901
|
const isPagePermission = permission.includes(":page.") || !!pageId;
|
|
643
902
|
const requiresOrgId = !isPagePermission;
|
|
644
903
|
if (isSuperAdmin === true) {
|
|
@@ -656,12 +915,12 @@ function useCan(userId, scope, permission, pageId, useCache = true, appName) {
|
|
|
656
915
|
setError(null);
|
|
657
916
|
}
|
|
658
917
|
}, [isValidScope, organisationId, error, permission, pageId, isSuperAdmin]);
|
|
659
|
-
const lastUserIdRef =
|
|
660
|
-
const lastScopeRef =
|
|
661
|
-
const lastPermissionRef =
|
|
662
|
-
const lastPageIdRef =
|
|
663
|
-
const lastUseCacheRef =
|
|
664
|
-
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(() => {
|
|
665
924
|
if (!isValidScope) {
|
|
666
925
|
return null;
|
|
667
926
|
}
|
|
@@ -671,8 +930,8 @@ function useCan(userId, scope, permission, pageId, useCache = true, appName) {
|
|
|
671
930
|
appId
|
|
672
931
|
};
|
|
673
932
|
}, [isValidScope, organisationId, eventId, appId]);
|
|
674
|
-
const prevScopeRef =
|
|
675
|
-
|
|
933
|
+
const prevScopeRef = useRef2(null);
|
|
934
|
+
useEffect5(() => {
|
|
676
935
|
const scopeChanged = !scopeEqual(prevScopeRef.current, stableScope);
|
|
677
936
|
if (lastUserIdRef.current !== userId || scopeChanged || lastPermissionRef.current !== permission || lastPageIdRef.current !== pageId || lastUseCacheRef.current !== useCache) {
|
|
678
937
|
lastUserIdRef.current = userId;
|
|
@@ -683,7 +942,19 @@ function useCan(userId, scope, permission, pageId, useCache = true, appName) {
|
|
|
683
942
|
const checkPermission = async () => {
|
|
684
943
|
if (!userId) {
|
|
685
944
|
setCan(false);
|
|
686
|
-
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);
|
|
687
958
|
return;
|
|
688
959
|
}
|
|
689
960
|
if (!isValidScope) {
|
|
@@ -697,13 +968,10 @@ function useCan(userId, scope, permission, pageId, useCache = true, appName) {
|
|
|
697
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);
|
|
698
969
|
const needsAppIdForPageName = isPagePermission && isPageName;
|
|
699
970
|
if (requiresOrgId && (!organisationId || organisationId === null || typeof organisationId === "string" && organisationId.trim() === "")) {
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
setError(null);
|
|
705
|
-
return;
|
|
706
|
-
}
|
|
971
|
+
setIsLoading(true);
|
|
972
|
+
setCan(false);
|
|
973
|
+
setError(null);
|
|
974
|
+
return;
|
|
707
975
|
}
|
|
708
976
|
if (needsAppIdForPageName && (!appId || appId === null || typeof appId === "string" && appId.trim() === "")) {
|
|
709
977
|
setIsLoading(true);
|
|
@@ -719,11 +987,12 @@ function useCan(userId, scope, permission, pageId, useCache = true, appName) {
|
|
|
719
987
|
...eventId ? { eventId } : {},
|
|
720
988
|
...appId ? { appId } : {}
|
|
721
989
|
};
|
|
722
|
-
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);
|
|
723
991
|
setCan(result);
|
|
724
992
|
} catch (err) {
|
|
725
993
|
const logger2 = getRBACLogger();
|
|
726
994
|
logger2.error("Permission check error:", { permission, error: err });
|
|
995
|
+
console.error("[useCan] Permission check error", { userId, permission, error: err });
|
|
727
996
|
setError(err instanceof Error ? err : new Error("Failed to check permission"));
|
|
728
997
|
setCan(false);
|
|
729
998
|
} finally {
|
|
@@ -733,7 +1002,7 @@ function useCan(userId, scope, permission, pageId, useCache = true, appName) {
|
|
|
733
1002
|
checkPermission();
|
|
734
1003
|
}
|
|
735
1004
|
}, [userId, stableScope, permission, pageId, useCache, appName, isSuperAdmin]);
|
|
736
|
-
const refetch =
|
|
1005
|
+
const refetch = useCallback4(async () => {
|
|
737
1006
|
if (!userId) {
|
|
738
1007
|
setCan(false);
|
|
739
1008
|
setIsLoading(false);
|
|
@@ -761,7 +1030,7 @@ function useCan(userId, scope, permission, pageId, useCache = true, appName) {
|
|
|
761
1030
|
...eventId ? { eventId } : {},
|
|
762
1031
|
...appId ? { appId } : {}
|
|
763
1032
|
};
|
|
764
|
-
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);
|
|
765
1034
|
setCan(result);
|
|
766
1035
|
} catch (err) {
|
|
767
1036
|
setError(err instanceof Error ? err : new Error("Failed to check permission"));
|
|
@@ -770,107 +1039,63 @@ function useCan(userId, scope, permission, pageId, useCache = true, appName) {
|
|
|
770
1039
|
setIsLoading(false);
|
|
771
1040
|
}
|
|
772
1041
|
}, [userId, isValidScope, organisationId, eventId, appId, permission, pageId, useCache, appName]);
|
|
773
|
-
return
|
|
1042
|
+
return useMemo5(() => ({
|
|
774
1043
|
can,
|
|
775
1044
|
isLoading,
|
|
776
1045
|
error,
|
|
777
1046
|
refetch
|
|
778
1047
|
}), [can, isLoading, error, refetch]);
|
|
779
1048
|
}
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
} catch {
|
|
789
|
-
}
|
|
790
|
-
const fetchAccessLevel = useCallback2(async () => {
|
|
791
|
-
if (!userId) {
|
|
792
|
-
setAccessLevel("viewer");
|
|
793
|
-
setIsLoading(false);
|
|
794
|
-
return;
|
|
795
|
-
}
|
|
796
|
-
try {
|
|
797
|
-
setIsLoading(true);
|
|
798
|
-
setError(null);
|
|
799
|
-
const { isSuperAdmin: checkSuperAdmin } = await import("./api-N774RPUA.js");
|
|
800
|
-
const isSuperAdminUser = await checkSuperAdmin(userId);
|
|
801
|
-
if (isSuperAdminUser) {
|
|
802
|
-
setAccessLevel("super");
|
|
803
|
-
setIsLoading(false);
|
|
804
|
-
return;
|
|
805
|
-
}
|
|
806
|
-
if (appName !== "PORTAL" && appName !== "ADMIN" && !scope.organisationId && !scope.eventId) {
|
|
807
|
-
const orgError = new OrganisationContextRequiredError();
|
|
808
|
-
setError(orgError);
|
|
809
|
-
setAccessLevel("viewer");
|
|
810
|
-
setIsLoading(false);
|
|
811
|
-
return;
|
|
812
|
-
}
|
|
813
|
-
const level = await getAccessLevel({ userId, scope }, null, appName);
|
|
814
|
-
setAccessLevel(level);
|
|
815
|
-
} catch (err) {
|
|
816
|
-
const error2 = err instanceof Error ? err : new Error("Failed to fetch access level");
|
|
817
|
-
setError(error2);
|
|
818
|
-
setAccessLevel("viewer");
|
|
819
|
-
} finally {
|
|
820
|
-
setIsLoading(false);
|
|
821
|
-
}
|
|
822
|
-
}, [userId, scope.organisationId, scope.eventId, scope.appId, appName]);
|
|
823
|
-
useEffect2(() => {
|
|
824
|
-
fetchAccessLevel();
|
|
825
|
-
}, [fetchAccessLevel]);
|
|
826
|
-
return useMemo2(() => ({
|
|
827
|
-
accessLevel,
|
|
828
|
-
isLoading,
|
|
829
|
-
error,
|
|
830
|
-
refetch: fetchAccessLevel
|
|
831
|
-
}), [accessLevel, isLoading, error, fetchAccessLevel]);
|
|
832
|
-
}
|
|
833
|
-
function useMultiplePermissions(userId, scope, permissions, useCache = true) {
|
|
834
|
-
const [results, setResults] = useState2({});
|
|
835
|
-
const [isLoading, setIsLoading] = useState2(true);
|
|
836
|
-
const [error, setError] = useState2(null);
|
|
837
|
-
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 () => {
|
|
838
1057
|
if (!userId || permissions.length === 0) {
|
|
839
|
-
|
|
1058
|
+
setHasAll(false);
|
|
840
1059
|
setIsLoading(false);
|
|
841
1060
|
return;
|
|
842
1061
|
}
|
|
843
1062
|
try {
|
|
844
1063
|
setIsLoading(true);
|
|
845
1064
|
setError(null);
|
|
846
|
-
|
|
1065
|
+
let hasAllPermissions = true;
|
|
847
1066
|
for (const permission of permissions) {
|
|
848
|
-
const result = useCache ? await isPermittedCached({ userId, scope, permission }) : await isPermitted({ userId, scope, permission });
|
|
849
|
-
|
|
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
|
+
}
|
|
850
1072
|
}
|
|
851
|
-
|
|
1073
|
+
setHasAll(hasAllPermissions);
|
|
852
1074
|
} catch (err) {
|
|
853
1075
|
setError(err instanceof Error ? err : new Error("Failed to check permissions"));
|
|
854
|
-
|
|
1076
|
+
setHasAll(false);
|
|
855
1077
|
} finally {
|
|
856
1078
|
setIsLoading(false);
|
|
857
1079
|
}
|
|
858
1080
|
}, [userId, scope.organisationId, scope.eventId, scope.appId, permissions, useCache]);
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
}, [
|
|
862
|
-
return
|
|
863
|
-
|
|
1081
|
+
useEffect6(() => {
|
|
1082
|
+
checkAllPermissions();
|
|
1083
|
+
}, [checkAllPermissions]);
|
|
1084
|
+
return useMemo6(() => ({
|
|
1085
|
+
hasAll,
|
|
864
1086
|
isLoading,
|
|
865
1087
|
error,
|
|
866
|
-
refetch:
|
|
867
|
-
}), [
|
|
1088
|
+
refetch: checkAllPermissions
|
|
1089
|
+
}), [hasAll, isLoading, error, checkAllPermissions]);
|
|
868
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";
|
|
869
1094
|
function useHasAnyPermission(userId, scope, permissions, useCache = true) {
|
|
870
|
-
const [hasAny, setHasAny] =
|
|
871
|
-
const [isLoading, setIsLoading] =
|
|
872
|
-
const [error, setError] =
|
|
873
|
-
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 () => {
|
|
874
1099
|
if (!userId || permissions.length === 0) {
|
|
875
1100
|
setHasAny(false);
|
|
876
1101
|
setIsLoading(false);
|
|
@@ -881,7 +1106,7 @@ function useHasAnyPermission(userId, scope, permissions, useCache = true) {
|
|
|
881
1106
|
setError(null);
|
|
882
1107
|
let hasAnyPermission = false;
|
|
883
1108
|
for (const permission of permissions) {
|
|
884
|
-
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);
|
|
885
1110
|
if (result) {
|
|
886
1111
|
hasAnyPermission = true;
|
|
887
1112
|
break;
|
|
@@ -895,93 +1120,203 @@ function useHasAnyPermission(userId, scope, permissions, useCache = true) {
|
|
|
895
1120
|
setIsLoading(false);
|
|
896
1121
|
}
|
|
897
1122
|
}, [userId, scope.organisationId, scope.eventId, scope.appId, permissions, useCache]);
|
|
898
|
-
|
|
1123
|
+
useEffect7(() => {
|
|
899
1124
|
checkAnyPermission();
|
|
900
1125
|
}, [checkAnyPermission]);
|
|
901
|
-
return
|
|
1126
|
+
return useMemo7(() => ({
|
|
902
1127
|
hasAny,
|
|
903
1128
|
isLoading,
|
|
904
1129
|
error,
|
|
905
1130
|
refetch: checkAnyPermission
|
|
906
1131
|
}), [hasAny, isLoading, error, checkAnyPermission]);
|
|
907
1132
|
}
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
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 () => {
|
|
913
1141
|
if (!userId || permissions.length === 0) {
|
|
914
|
-
|
|
1142
|
+
setResults({});
|
|
915
1143
|
setIsLoading(false);
|
|
916
1144
|
return;
|
|
917
1145
|
}
|
|
918
1146
|
try {
|
|
919
1147
|
setIsLoading(true);
|
|
920
1148
|
setError(null);
|
|
921
|
-
|
|
1149
|
+
const permissionResults = {};
|
|
922
1150
|
for (const permission of permissions) {
|
|
923
|
-
const result = useCache ? await isPermittedCached({ userId, scope, permission }) : await isPermitted({ userId, scope, permission });
|
|
924
|
-
|
|
925
|
-
hasAllPermissions = false;
|
|
926
|
-
break;
|
|
927
|
-
}
|
|
1151
|
+
const result = useCache ? await isPermittedCached({ userId, scope, permission }) : await isPermitted({ userId, scope, permission }, null, void 0, null);
|
|
1152
|
+
permissionResults[permission] = result;
|
|
928
1153
|
}
|
|
929
|
-
|
|
1154
|
+
setResults(permissionResults);
|
|
930
1155
|
} catch (err) {
|
|
931
1156
|
setError(err instanceof Error ? err : new Error("Failed to check permissions"));
|
|
932
|
-
|
|
1157
|
+
setResults({});
|
|
933
1158
|
} finally {
|
|
934
1159
|
setIsLoading(false);
|
|
935
1160
|
}
|
|
936
1161
|
}, [userId, scope.organisationId, scope.eventId, scope.appId, permissions, useCache]);
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
}, [
|
|
940
|
-
return
|
|
941
|
-
|
|
1162
|
+
useEffect8(() => {
|
|
1163
|
+
checkPermissions();
|
|
1164
|
+
}, [checkPermissions]);
|
|
1165
|
+
return useMemo8(() => ({
|
|
1166
|
+
results,
|
|
942
1167
|
isLoading,
|
|
943
1168
|
error,
|
|
944
|
-
refetch:
|
|
945
|
-
}), [
|
|
1169
|
+
refetch: checkPermissions
|
|
1170
|
+
}), [results, isLoading, error, checkPermissions]);
|
|
946
1171
|
}
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
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
|
+
}
|
|
952
1277
|
if (!userId) {
|
|
953
1278
|
setPermissions({});
|
|
954
1279
|
setIsLoading(false);
|
|
955
1280
|
return;
|
|
956
1281
|
}
|
|
1282
|
+
if (!orgId || orgId === null || typeof orgId === "string" && orgId.trim() === "") {
|
|
1283
|
+
setIsLoading(true);
|
|
1284
|
+
setError(null);
|
|
1285
|
+
return;
|
|
1286
|
+
}
|
|
957
1287
|
try {
|
|
1288
|
+
isFetchingRef.current = true;
|
|
958
1289
|
setIsLoading(true);
|
|
959
1290
|
setError(null);
|
|
1291
|
+
const scope = {
|
|
1292
|
+
organisationId: orgId,
|
|
1293
|
+
eventId,
|
|
1294
|
+
appId
|
|
1295
|
+
};
|
|
960
1296
|
const permissionMap = await getPermissionMap({ userId, scope });
|
|
961
1297
|
setPermissions(permissionMap);
|
|
962
1298
|
} catch (err) {
|
|
963
|
-
|
|
1299
|
+
const logger3 = getRBACLogger();
|
|
1300
|
+
logger3.error("Failed to refetch permissions:", err);
|
|
1301
|
+
setError(err instanceof Error ? err : new Error("Failed to fetch permissions"));
|
|
964
1302
|
} finally {
|
|
965
1303
|
setIsLoading(false);
|
|
1304
|
+
isFetchingRef.current = false;
|
|
966
1305
|
}
|
|
967
|
-
}, [userId,
|
|
968
|
-
|
|
969
|
-
fetchCachedPermissions();
|
|
970
|
-
}, [fetchCachedPermissions]);
|
|
971
|
-
useEffect2(() => {
|
|
972
|
-
fetchCachedPermissions();
|
|
973
|
-
}, [fetchCachedPermissions]);
|
|
974
|
-
return useMemo2(() => ({
|
|
1306
|
+
}, [userId, organisationId, eventId, appId]);
|
|
1307
|
+
return useMemo9(() => ({
|
|
975
1308
|
permissions,
|
|
976
1309
|
isLoading,
|
|
977
1310
|
error,
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
1311
|
+
hasPermission,
|
|
1312
|
+
hasAnyPermission,
|
|
1313
|
+
hasAllPermissions,
|
|
1314
|
+
refetch
|
|
1315
|
+
}), [permissions, isLoading, error, hasPermission, hasAnyPermission, hasAllPermissions, refetch]);
|
|
981
1316
|
}
|
|
982
1317
|
|
|
983
1318
|
// src/rbac/hooks/useResourcePermissions.ts
|
|
984
|
-
import { useMemo as
|
|
1319
|
+
import { useMemo as useMemo10 } from "react";
|
|
985
1320
|
function useResourcePermissions(resource, options = {}) {
|
|
986
1321
|
const { enableRead = false, requireScope = true } = options;
|
|
987
1322
|
const { user, supabase } = useUnifiedAuth();
|
|
@@ -1009,8 +1344,12 @@ function useResourcePermissions(resource, options = {}) {
|
|
|
1009
1344
|
`create:${resource}`,
|
|
1010
1345
|
pageId,
|
|
1011
1346
|
// Pass resource name as pageId when appId is available to enable page permission checks
|
|
1012
|
-
true
|
|
1347
|
+
true,
|
|
1013
1348
|
// useCache
|
|
1349
|
+
null,
|
|
1350
|
+
// precomputedSuperAdmin - not checked yet
|
|
1351
|
+
void 0
|
|
1352
|
+
// appName
|
|
1014
1353
|
);
|
|
1015
1354
|
const { can: canUpdateResult, isLoading: updateLoading, error: updateError } = useCan(
|
|
1016
1355
|
user?.id || "",
|
|
@@ -1018,8 +1357,12 @@ function useResourcePermissions(resource, options = {}) {
|
|
|
1018
1357
|
`update:${resource}`,
|
|
1019
1358
|
pageId,
|
|
1020
1359
|
// Pass resource name as pageId when appId is available to enable page permission checks
|
|
1021
|
-
true
|
|
1360
|
+
true,
|
|
1022
1361
|
// useCache
|
|
1362
|
+
null,
|
|
1363
|
+
// precomputedSuperAdmin - not checked yet
|
|
1364
|
+
void 0
|
|
1365
|
+
// appName
|
|
1023
1366
|
);
|
|
1024
1367
|
const { can: canDeleteResult, isLoading: deleteLoading, error: deleteError } = useCan(
|
|
1025
1368
|
user?.id || "",
|
|
@@ -1027,8 +1370,12 @@ function useResourcePermissions(resource, options = {}) {
|
|
|
1027
1370
|
`delete:${resource}`,
|
|
1028
1371
|
pageId,
|
|
1029
1372
|
// Pass resource name as pageId when appId is available to enable page permission checks
|
|
1030
|
-
true
|
|
1373
|
+
true,
|
|
1031
1374
|
// useCache
|
|
1375
|
+
null,
|
|
1376
|
+
// precomputedSuperAdmin - not checked yet
|
|
1377
|
+
void 0
|
|
1378
|
+
// appName
|
|
1032
1379
|
);
|
|
1033
1380
|
const { can: canReadResult, isLoading: readLoading, error: readError } = useCan(
|
|
1034
1381
|
user?.id || "",
|
|
@@ -1036,13 +1383,17 @@ function useResourcePermissions(resource, options = {}) {
|
|
|
1036
1383
|
`read:${resource}`,
|
|
1037
1384
|
pageId,
|
|
1038
1385
|
// Pass resource name as pageId when appId is available to enable page permission checks
|
|
1039
|
-
true
|
|
1386
|
+
true,
|
|
1040
1387
|
// useCache
|
|
1388
|
+
null,
|
|
1389
|
+
// precomputedSuperAdmin - not checked yet
|
|
1390
|
+
void 0
|
|
1391
|
+
// appName
|
|
1041
1392
|
);
|
|
1042
|
-
const isLoading =
|
|
1393
|
+
const isLoading = useMemo10(() => {
|
|
1043
1394
|
return scopeLoading || createLoading || updateLoading || deleteLoading || enableRead && readLoading;
|
|
1044
1395
|
}, [scopeLoading, createLoading, updateLoading, deleteLoading, readLoading, enableRead]);
|
|
1045
|
-
const error =
|
|
1396
|
+
const error = useMemo10(() => {
|
|
1046
1397
|
if (scopeError) return scopeError;
|
|
1047
1398
|
if (createError) return createError;
|
|
1048
1399
|
if (updateError) return updateError;
|
|
@@ -1050,7 +1401,7 @@ function useResourcePermissions(resource, options = {}) {
|
|
|
1050
1401
|
if (enableRead && readError) return readError;
|
|
1051
1402
|
return null;
|
|
1052
1403
|
}, [scopeError, createError, updateError, deleteError, readError, enableRead]);
|
|
1053
|
-
return
|
|
1404
|
+
return useMemo10(() => ({
|
|
1054
1405
|
canCreate: (res) => {
|
|
1055
1406
|
if (res !== resource) {
|
|
1056
1407
|
return false;
|
|
@@ -1095,15 +1446,15 @@ function useResourcePermissions(resource, options = {}) {
|
|
|
1095
1446
|
}
|
|
1096
1447
|
|
|
1097
1448
|
// src/rbac/hooks/useRoleManagement.ts
|
|
1098
|
-
import { useState as
|
|
1449
|
+
import { useState as useState10, useCallback as useCallback9 } from "react";
|
|
1099
1450
|
function useRoleManagement() {
|
|
1100
1451
|
const { user, supabase } = useUnifiedAuth();
|
|
1101
|
-
const [isLoading, setIsLoading] =
|
|
1102
|
-
const [error, setError] =
|
|
1452
|
+
const [isLoading, setIsLoading] = useState10(false);
|
|
1453
|
+
const [error, setError] = useState10(null);
|
|
1103
1454
|
if (!supabase) {
|
|
1104
1455
|
throw new Error("useRoleManagement requires a Supabase client. Ensure UnifiedAuthProvider is configured.");
|
|
1105
1456
|
}
|
|
1106
|
-
const revokeEventAppRole =
|
|
1457
|
+
const revokeEventAppRole = useCallback9(async (params) => {
|
|
1107
1458
|
setIsLoading(true);
|
|
1108
1459
|
setError(null);
|
|
1109
1460
|
try {
|
|
@@ -1134,7 +1485,7 @@ function useRoleManagement() {
|
|
|
1134
1485
|
setIsLoading(false);
|
|
1135
1486
|
}
|
|
1136
1487
|
}, [user?.id]);
|
|
1137
|
-
const grantEventAppRole =
|
|
1488
|
+
const grantEventAppRole = useCallback9(async (params) => {
|
|
1138
1489
|
setIsLoading(true);
|
|
1139
1490
|
setError(null);
|
|
1140
1491
|
try {
|
|
@@ -1173,7 +1524,7 @@ function useRoleManagement() {
|
|
|
1173
1524
|
setIsLoading(false);
|
|
1174
1525
|
}
|
|
1175
1526
|
}, [user?.id]);
|
|
1176
|
-
const revokeRoleById =
|
|
1527
|
+
const revokeRoleById = useCallback9(async (roleId) => {
|
|
1177
1528
|
setIsLoading(true);
|
|
1178
1529
|
setError(null);
|
|
1179
1530
|
try {
|
|
@@ -1209,7 +1560,7 @@ function useRoleManagement() {
|
|
|
1209
1560
|
setIsLoading(false);
|
|
1210
1561
|
}
|
|
1211
1562
|
}, [user?.id, supabase]);
|
|
1212
|
-
const grantGlobalRole =
|
|
1563
|
+
const grantGlobalRole = useCallback9(async (params) => {
|
|
1213
1564
|
setIsLoading(true);
|
|
1214
1565
|
setError(null);
|
|
1215
1566
|
try {
|
|
@@ -1248,7 +1599,7 @@ function useRoleManagement() {
|
|
|
1248
1599
|
setIsLoading(false);
|
|
1249
1600
|
}
|
|
1250
1601
|
}, [user?.id, supabase]);
|
|
1251
|
-
const revokeGlobalRole =
|
|
1602
|
+
const revokeGlobalRole = useCallback9(async (params) => {
|
|
1252
1603
|
setIsLoading(true);
|
|
1253
1604
|
setError(null);
|
|
1254
1605
|
try {
|
|
@@ -1280,7 +1631,7 @@ function useRoleManagement() {
|
|
|
1280
1631
|
setIsLoading(false);
|
|
1281
1632
|
}
|
|
1282
1633
|
}, [user?.id, supabase]);
|
|
1283
|
-
const grantOrganisationRole =
|
|
1634
|
+
const grantOrganisationRole = useCallback9(async (params) => {
|
|
1284
1635
|
setIsLoading(true);
|
|
1285
1636
|
setError(null);
|
|
1286
1637
|
try {
|
|
@@ -1319,7 +1670,7 @@ function useRoleManagement() {
|
|
|
1319
1670
|
setIsLoading(false);
|
|
1320
1671
|
}
|
|
1321
1672
|
}, [user?.id, supabase]);
|
|
1322
|
-
const revokeOrganisationRole =
|
|
1673
|
+
const revokeOrganisationRole = useCallback9(async (params) => {
|
|
1323
1674
|
setIsLoading(true);
|
|
1324
1675
|
setError(null);
|
|
1325
1676
|
try {
|
|
@@ -1369,11 +1720,11 @@ function useRoleManagement() {
|
|
|
1369
1720
|
}
|
|
1370
1721
|
|
|
1371
1722
|
// src/rbac/hooks/useSecureSupabase.ts
|
|
1372
|
-
import { useMemo as
|
|
1723
|
+
import { useMemo as useMemo11, useRef as useRef4 } from "react";
|
|
1373
1724
|
var secureClientCache = /* @__PURE__ */ new Map();
|
|
1374
1725
|
var MAX_CACHE_SIZE = 5;
|
|
1375
1726
|
function getCacheKey(organisationId, eventId, appId, isSuperAdmin) {
|
|
1376
|
-
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"}`;
|
|
1377
1728
|
}
|
|
1378
1729
|
function getSupabaseConfig() {
|
|
1379
1730
|
const getEnvVar = (key) => {
|
|
@@ -1405,19 +1756,20 @@ function useSecureSupabase(baseClient) {
|
|
|
1405
1756
|
selectedOrganisationId: selectedOrganisation?.id || null,
|
|
1406
1757
|
selectedEventId: selectedEvent?.event_id || null
|
|
1407
1758
|
});
|
|
1408
|
-
const prevContextRef =
|
|
1759
|
+
const prevContextRef = useRef4({
|
|
1409
1760
|
organisationId: void 0,
|
|
1410
1761
|
eventId: void 0,
|
|
1411
1762
|
appId: void 0
|
|
1412
1763
|
});
|
|
1413
|
-
return
|
|
1764
|
+
return useMemo11(() => {
|
|
1414
1765
|
if (eventLoading) {
|
|
1415
1766
|
return baseClient || authSupabase || null;
|
|
1416
1767
|
}
|
|
1417
1768
|
const organisationId = resolvedScope?.organisationId;
|
|
1418
1769
|
const eventId = resolvedScope?.eventId || selectedEvent?.event_id;
|
|
1419
1770
|
const appId = resolvedScope?.appId;
|
|
1420
|
-
|
|
1771
|
+
const canCreateSecureClient = user?.id && (isSuperAdmin || organisationId);
|
|
1772
|
+
if (canCreateSecureClient) {
|
|
1421
1773
|
prevContextRef.current = { organisationId, eventId, appId };
|
|
1422
1774
|
const cacheKey = getCacheKey(organisationId, eventId, appId, isSuperAdmin);
|
|
1423
1775
|
const cachedClient = secureClientCache.get(cacheKey);
|
|
@@ -1432,11 +1784,19 @@ function useSecureSupabase(baseClient) {
|
|
|
1432
1784
|
return baseClient || authSupabase || null;
|
|
1433
1785
|
}
|
|
1434
1786
|
try {
|
|
1435
|
-
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(
|
|
1436
1796
|
config.url,
|
|
1437
1797
|
config.key,
|
|
1438
|
-
|
|
1439
|
-
// organisationId is string, UUID is string alias
|
|
1798
|
+
effectiveOrganisationId,
|
|
1799
|
+
// organisationId is string | null, UUID is string alias
|
|
1440
1800
|
eventId,
|
|
1441
1801
|
appId,
|
|
1442
1802
|
// appId is string | undefined, UUID is string alias
|
|
@@ -1474,17 +1834,18 @@ export {
|
|
|
1474
1834
|
SecureSupabaseClient,
|
|
1475
1835
|
createSecureClient,
|
|
1476
1836
|
fromSupabaseClient,
|
|
1837
|
+
useResolvedScope,
|
|
1477
1838
|
useRBAC,
|
|
1839
|
+
useAccessLevel,
|
|
1840
|
+
useCachedPermissions,
|
|
1478
1841
|
scopeEqual,
|
|
1479
|
-
usePermissions,
|
|
1480
1842
|
useCan,
|
|
1481
|
-
useAccessLevel,
|
|
1482
|
-
useMultiplePermissions,
|
|
1483
|
-
useHasAnyPermission,
|
|
1484
1843
|
useHasAllPermissions,
|
|
1485
|
-
|
|
1844
|
+
useHasAnyPermission,
|
|
1845
|
+
useMultiplePermissions,
|
|
1846
|
+
usePermissions,
|
|
1486
1847
|
useResourcePermissions,
|
|
1487
1848
|
useRoleManagement,
|
|
1488
1849
|
useSecureSupabase
|
|
1489
1850
|
};
|
|
1490
|
-
//# sourceMappingURL=chunk-
|
|
1851
|
+
//# sourceMappingURL=chunk-XWQCNGTQ.js.map
|