@jmruthers/pace-core 0.5.188 → 0.5.190
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/core-usage-manifest.json +0 -4
- package/dist/{AuthService-B-cd2MA4.d.ts → AuthService-CbP_utw2.d.ts} +7 -3
- package/dist/{DataTable-GUFUNZ3N.js → DataTable-ON3IXISJ.js} +8 -8
- package/dist/{PublicPageProvider-DrLDztHt.d.ts → PublicPageProvider-C4uxosp6.d.ts} +129 -40
- package/dist/{UnifiedAuthProvider-BG0AL5eE.d.ts → UnifiedAuthProvider-BYA9qB-o.d.ts} +4 -3
- package/dist/{UnifiedAuthProvider-643PUAIM.js → UnifiedAuthProvider-X5NXANVI.js} +4 -2
- package/dist/{api-YP7XD5L6.js → api-I6UCQ5S6.js} +4 -2
- package/dist/{chunk-DDM4CCYT.js → chunk-4QYC5L4K.js} +60 -35
- package/dist/chunk-4QYC5L4K.js.map +1 -0
- package/dist/{chunk-IM4QE42D.js → chunk-73HSNNOQ.js} +141 -326
- package/dist/chunk-73HSNNOQ.js.map +1 -0
- package/dist/{chunk-YHCN776L.js → chunk-DZWK57KZ.js} +2 -75
- package/dist/chunk-DZWK57KZ.js.map +1 -0
- package/dist/{chunk-3GOZZZYH.js → chunk-HQVPB5MZ.js} +238 -301
- package/dist/chunk-HQVPB5MZ.js.map +1 -0
- package/dist/{chunk-THRPYOFK.js → chunk-HW3OVDUF.js} +5 -5
- package/dist/chunk-HW3OVDUF.js.map +1 -0
- package/dist/{chunk-F2IMUDXZ.js → chunk-I7PSE6JW.js} +75 -2
- package/dist/chunk-I7PSE6JW.js.map +1 -0
- package/dist/{chunk-VGZZXKBR.js → chunk-J2XXC7R5.js} +280 -52
- package/dist/chunk-J2XXC7R5.js.map +1 -0
- package/dist/{chunk-UNOTYLQF.js → chunk-NIU6J6OX.js} +772 -725
- package/dist/chunk-NIU6J6OX.js.map +1 -0
- package/dist/{chunk-HESYZWZW.js → chunk-QWWZ5CAQ.js} +2 -2
- package/dist/{chunk-HEHYGYOX.js → chunk-RUYZKXOD.js} +401 -46
- package/dist/chunk-RUYZKXOD.js.map +1 -0
- package/dist/{chunk-2UUZZJFT.js → chunk-SDMHPX3X.js} +176 -160
- package/dist/{chunk-2UUZZJFT.js.map → chunk-SDMHPX3X.js.map} +1 -1
- package/dist/{chunk-IPCH26AG.js → chunk-STYK4OH2.js} +11 -11
- package/dist/chunk-STYK4OH2.js.map +1 -0
- package/dist/{chunk-EFCLXK7F.js → chunk-VVBAW5A5.js} +4201 -3809
- package/dist/chunk-VVBAW5A5.js.map +1 -0
- package/dist/chunk-Y4BUBBHD.js +614 -0
- package/dist/chunk-Y4BUBBHD.js.map +1 -0
- package/dist/{chunk-SAUPYVLF.js → chunk-ZSAAAMVR.js} +1 -1
- package/dist/chunk-ZSAAAMVR.js.map +1 -0
- package/dist/components.d.ts +3 -5
- package/dist/components.js +19 -23
- package/dist/components.js.map +1 -1
- package/dist/eslint-rules/pace-core-compliance.cjs +0 -2
- package/dist/{file-reference-D037xOFK.d.ts → file-reference-BavO2eQj.d.ts} +13 -10
- package/dist/hooks.d.ts +10 -5
- package/dist/hooks.js +14 -8
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +13 -12
- package/dist/index.js +79 -73
- package/dist/index.js.map +1 -1
- package/dist/providers.d.ts +3 -3
- package/dist/providers.js +3 -1
- package/dist/rbac/index.d.ts +76 -12
- package/dist/rbac/index.js +12 -9
- package/dist/types.d.ts +1 -1
- package/dist/types.js +1 -1
- package/dist/{usePublicRouteParams-CTDELQ7H.d.ts → usePublicRouteParams-DxIDS4bC.d.ts} +16 -9
- package/dist/utils.js +16 -16
- package/docs/README.md +2 -2
- package/docs/api/classes/ColumnFactory.md +1 -1
- package/docs/api/classes/ErrorBoundary.md +1 -1
- package/docs/api/classes/InvalidScopeError.md +2 -2
- package/docs/api/classes/Logger.md +1 -1
- package/docs/api/classes/MissingUserContextError.md +2 -2
- package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
- package/docs/api/classes/PermissionDeniedError.md +1 -1
- package/docs/api/classes/RBACAuditManager.md +1 -1
- package/docs/api/classes/RBACCache.md +1 -1
- package/docs/api/classes/RBACEngine.md +4 -4
- package/docs/api/classes/RBACError.md +1 -1
- package/docs/api/classes/RBACNotInitializedError.md +2 -2
- package/docs/api/classes/SecureSupabaseClient.md +21 -16
- package/docs/api/classes/StorageUtils.md +7 -4
- 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 +1 -1
- package/docs/api/interfaces/AutocompleteOptions.md +1 -1
- package/docs/api/interfaces/AvatarProps.md +128 -0
- package/docs/api/interfaces/BadgeProps.md +1 -1
- package/docs/api/interfaces/ButtonProps.md +1 -1
- package/docs/api/interfaces/CalendarProps.md +20 -6
- package/docs/api/interfaces/CardProps.md +1 -1
- package/docs/api/interfaces/ColorPalette.md +1 -1
- package/docs/api/interfaces/ColorShade.md +1 -1
- package/docs/api/interfaces/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 +1 -1
- package/docs/api/interfaces/DataTableColumn.md +1 -1
- package/docs/api/interfaces/DataTableProps.md +1 -1
- package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
- package/docs/api/interfaces/DatabaseComplianceResult.md +1 -1
- package/docs/api/interfaces/DatabaseIssue.md +1 -1
- package/docs/api/interfaces/EmptyStateConfig.md +1 -1
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
- package/docs/api/interfaces/EventAppRoleData.md +1 -1
- package/docs/api/interfaces/ExportColumn.md +1 -1
- package/docs/api/interfaces/ExportOptions.md +1 -1
- package/docs/api/interfaces/FileDisplayProps.md +62 -16
- package/docs/api/interfaces/FileMetadata.md +1 -1
- package/docs/api/interfaces/FileReference.md +2 -2
- package/docs/api/interfaces/FileSizeLimits.md +1 -1
- package/docs/api/interfaces/FileUploadOptions.md +26 -12
- package/docs/api/interfaces/FileUploadProps.md +30 -19
- package/docs/api/interfaces/FooterProps.md +1 -1
- package/docs/api/interfaces/FormFieldProps.md +1 -1
- 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 +1 -1
- package/docs/api/interfaces/LabelProps.md +1 -1
- package/docs/api/interfaces/LoggerConfig.md +1 -1
- package/docs/api/interfaces/LoginFormProps.md +1 -1
- package/docs/api/interfaces/NavigationAccessRecord.md +10 -10
- package/docs/api/interfaces/NavigationContextType.md +9 -9
- package/docs/api/interfaces/NavigationGuardProps.md +1 -1
- package/docs/api/interfaces/NavigationItem.md +1 -1
- package/docs/api/interfaces/NavigationMenuProps.md +1 -1
- package/docs/api/interfaces/NavigationProviderProps.md +7 -7
- 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 +1 -1
- package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
- package/docs/api/interfaces/PageAccessRecord.md +8 -8
- package/docs/api/interfaces/PagePermissionContextType.md +8 -8
- package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
- package/docs/api/interfaces/PagePermissionProviderProps.md +7 -7
- 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 +3 -11
- package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
- package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
- package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
- package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
- package/docs/api/interfaces/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 +8 -8
- package/docs/api/interfaces/RoleBasedRouterProps.md +10 -10
- package/docs/api/interfaces/RoleManagementResult.md +1 -1
- package/docs/api/interfaces/RouteAccessRecord.md +10 -10
- package/docs/api/interfaces/RouteConfig.md +10 -10
- 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 +1 -1
- package/docs/api/interfaces/SetupIssue.md +1 -1
- package/docs/api/interfaces/StorageConfig.md +4 -4
- package/docs/api/interfaces/StorageFileInfo.md +7 -7
- package/docs/api/interfaces/StorageFileMetadata.md +25 -14
- package/docs/api/interfaces/StorageListOptions.md +22 -9
- package/docs/api/interfaces/StorageListResult.md +4 -4
- package/docs/api/interfaces/StorageUploadOptions.md +21 -8
- package/docs/api/interfaces/StorageUploadResult.md +6 -6
- package/docs/api/interfaces/StorageUrlOptions.md +19 -6
- 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 +1 -1
- package/docs/api/interfaces/TextareaProps.md +1 -1
- package/docs/api/interfaces/ToastActionElement.md +1 -1
- package/docs/api/interfaces/ToastProps.md +1 -1
- package/docs/api/interfaces/UnifiedAuthContextType.md +53 -53
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +13 -13
- package/docs/api/interfaces/UseFormDialogOptions.md +1 -1
- package/docs/api/interfaces/UseFormDialogReturn.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventLogoOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
- package/docs/api/interfaces/UsePublicFileDisplayOptions.md +1 -1
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeOptions.md +4 -4
- package/docs/api/interfaces/UseResolvedScopeReturn.md +4 -4
- package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
- package/docs/api/interfaces/UserEventAccess.md +11 -11
- package/docs/api/interfaces/UserMenuProps.md +1 -1
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +155 -135
- package/docs/api-reference/components.md +72 -29
- package/docs/api-reference/providers.md +2 -2
- package/docs/api-reference/rpc-functions.md +1 -0
- package/docs/best-practices/README.md +1 -1
- package/docs/best-practices/deployment.md +8 -8
- package/docs/getting-started/examples/README.md +2 -2
- package/docs/getting-started/installation-guide.md +4 -4
- package/docs/getting-started/quick-start.md +3 -3
- package/docs/migration/MIGRATION_GUIDE.md +3 -3
- package/docs/rbac/compliance/compliance-guide.md +2 -2
- package/docs/rbac/event-based-apps.md +2 -2
- package/docs/rbac/getting-started.md +2 -2
- package/docs/rbac/quick-start.md +2 -2
- package/docs/security/README.md +4 -4
- package/docs/standards/07-rbac-and-rls-standard.md +430 -7
- package/docs/troubleshooting/README.md +2 -2
- package/docs/troubleshooting/migration.md +3 -3
- package/package.json +1 -4
- package/scripts/check-pace-core-compliance.cjs +1 -1
- package/scripts/check-pace-core-compliance.js +1 -1
- package/src/__tests__/fixtures/supabase.ts +301 -0
- package/src/__tests__/public-recipe-view.test.ts +9 -9
- package/src/__tests__/rls-policies.test.ts +197 -61
- package/src/components/AddressField/AddressField.test.tsx +42 -0
- package/src/components/AddressField/AddressField.tsx +71 -60
- package/src/components/AddressField/README.md +1 -0
- package/src/components/Alert/Alert.test.tsx +50 -10
- package/src/components/Alert/Alert.tsx +5 -3
- package/src/components/Avatar/Avatar.test.tsx +252 -226
- package/src/components/Avatar/Avatar.tsx +179 -53
- package/src/components/Avatar/index.ts +1 -1
- package/src/components/Button/Button.test.tsx +2 -1
- package/src/components/Button/Button.tsx +3 -3
- package/src/components/Calendar/Calendar.test.tsx +53 -37
- package/src/components/Calendar/Calendar.tsx +409 -82
- package/src/components/Card/Card.test.tsx +7 -4
- package/src/components/Card/Card.tsx +3 -6
- package/src/components/Checkbox/Checkbox.tsx +2 -2
- package/src/components/DataTable/components/ActionButtons.tsx +5 -5
- package/src/components/DataTable/components/BulkOperationsDropdown.tsx +2 -2
- package/src/components/DataTable/components/ColumnFilter.tsx +1 -1
- package/src/components/DataTable/components/ColumnVisibilityDropdown.tsx +3 -3
- package/src/components/DataTable/components/DataTableBody.tsx +12 -12
- package/src/components/DataTable/components/DataTableCore.tsx +3 -3
- package/src/components/DataTable/components/DataTableToolbar.tsx +5 -5
- package/src/components/DataTable/components/DraggableColumnHeader.tsx +3 -3
- package/src/components/DataTable/components/EditableRow.tsx +2 -2
- package/src/components/DataTable/components/EmptyState.tsx +3 -3
- package/src/components/DataTable/components/GroupHeader.tsx +2 -2
- package/src/components/DataTable/components/GroupingDropdown.tsx +1 -1
- package/src/components/DataTable/components/ImportModal.tsx +4 -4
- package/src/components/DataTable/components/LoadingState.tsx +1 -1
- package/src/components/DataTable/components/PaginationControls.tsx +11 -11
- package/src/components/DataTable/components/UnifiedTableBody.tsx +9 -9
- package/src/components/DataTable/components/ViewRowModal.tsx +2 -2
- package/src/components/DataTable/components/__tests__/AccessDeniedPage.test.tsx +11 -37
- package/src/components/DataTable/components/__tests__/DataTableToolbar.test.tsx +157 -0
- package/src/components/DataTable/components/__tests__/LoadingState.test.tsx +2 -1
- package/src/components/DataTable/components/__tests__/VirtualizedDataTable.test.tsx +128 -0
- package/src/components/DataTable/core/__tests__/ActionManager.test.ts +19 -0
- package/src/components/DataTable/core/__tests__/ColumnFactory.test.ts +51 -0
- package/src/components/DataTable/core/__tests__/ColumnManager.test.ts +84 -0
- package/src/components/DataTable/core/__tests__/DataManager.test.ts +14 -0
- package/src/components/DataTable/core/__tests__/DataTableContext.test.tsx +136 -0
- package/src/components/DataTable/core/__tests__/LocalDataAdapter.test.ts +16 -0
- package/src/components/DataTable/core/__tests__/PluginRegistry.test.ts +18 -0
- package/src/components/DataTable/hooks/useDataTablePermissions.ts +28 -7
- package/src/components/DataTable/utils/__tests__/hierarchicalUtils.test.ts +30 -1
- package/src/components/DataTable/utils/hierarchicalUtils.ts +38 -10
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +8 -3
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.tsx +4 -4
- package/src/components/Dialog/Dialog.tsx +2 -2
- package/src/components/EventSelector/EventSelector.tsx +7 -7
- package/src/components/FileDisplay/FileDisplay.tsx +291 -179
- package/src/components/FileUpload/FileUpload.tsx +7 -4
- package/src/components/Header/Header.test.tsx +28 -0
- package/src/components/Header/Header.tsx +22 -9
- package/src/components/InactivityWarningModal/InactivityWarningModal.tsx +2 -2
- package/src/components/LoadingSpinner/LoadingSpinner.test.tsx +19 -14
- package/src/components/LoadingSpinner/LoadingSpinner.tsx +5 -5
- package/src/components/NavigationMenu/NavigationMenu.test.tsx +127 -1
- package/src/components/OrganisationSelector/OrganisationSelector.tsx +8 -8
- package/src/components/PaceAppLayout/PaceAppLayout.integration.test.tsx +4 -0
- package/src/components/PaceAppLayout/PaceAppLayout.performance.test.tsx +3 -0
- package/src/components/PaceAppLayout/PaceAppLayout.security.test.tsx +3 -0
- package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +16 -6
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +37 -3
- package/src/components/PaceAppLayout/test-setup.tsx +1 -0
- package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +66 -45
- package/src/components/PaceLoginPage/PaceLoginPage.tsx +6 -4
- package/src/components/Progress/Progress.test.tsx +18 -19
- package/src/components/Progress/Progress.tsx +31 -32
- package/src/components/PublicLayout/PublicLayout.test.tsx +6 -6
- package/src/components/PublicLayout/PublicPageProvider.tsx +5 -3
- package/src/components/Select/Select.tsx +5 -5
- package/src/components/Switch/Switch.test.tsx +2 -1
- package/src/components/Switch/Switch.tsx +1 -1
- package/src/components/Toast/Toast.tsx +1 -1
- package/src/components/Tooltip/Tooltip.test.tsx +8 -2
- package/src/components/UserMenu/UserMenu.test.tsx +7 -9
- package/src/components/UserMenu/UserMenu.tsx +10 -8
- package/src/components/index.ts +2 -1
- package/src/eslint-rules/pace-core-compliance.cjs +0 -2
- package/src/eslint-rules/pace-core-compliance.js +0 -2
- package/src/hooks/__tests__/hooks.integration.test.tsx +4 -1
- package/src/hooks/__tests__/useAppConfig.unit.test.ts +76 -5
- package/src/hooks/__tests__/useDataTableState.test.ts +76 -0
- package/src/hooks/__tests__/useFileUrl.unit.test.ts +25 -69
- package/src/hooks/__tests__/useFileUrlCache.test.ts +129 -0
- package/src/hooks/__tests__/usePreventTabReload.test.ts +88 -0
- package/src/hooks/__tests__/{usePublicEvent.unit.test.ts → usePublicEvent.test.ts} +28 -1
- package/src/hooks/__tests__/useQueryCache.test.ts +144 -0
- package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +58 -16
- package/src/hooks/index.ts +1 -1
- package/src/hooks/public/usePublicEvent.ts +2 -2
- package/src/hooks/public/usePublicFileDisplay.ts +173 -87
- package/src/hooks/useAppConfig.ts +24 -5
- package/src/hooks/useFileDisplay.ts +297 -34
- package/src/hooks/useFileReference.ts +56 -11
- package/src/hooks/useFileUrl.ts +1 -1
- package/src/hooks/useInactivityTracker.ts +16 -7
- package/src/hooks/usePermissionCache.test.ts +85 -8
- package/src/hooks/useQueryCache.ts +21 -0
- package/src/hooks/useSecureDataAccess.test.ts +80 -35
- package/src/hooks/useSecureDataAccess.ts +80 -37
- package/src/index.ts +2 -1
- package/src/providers/services/EventServiceProvider.tsx +37 -17
- package/src/providers/services/InactivityServiceProvider.tsx +4 -4
- package/src/providers/services/OrganisationServiceProvider.tsx +8 -1
- package/src/providers/services/UnifiedAuthProvider.tsx +115 -29
- package/src/rbac/__tests__/auth-rbac.e2e.test.tsx +451 -0
- package/src/rbac/__tests__/engine.comprehensive.test.ts +12 -0
- package/src/rbac/__tests__/rbac-engine-core-logic.test.ts +8 -0
- package/src/rbac/__tests__/rbac-engine-simplified.test.ts +4 -0
- package/src/rbac/api.ts +240 -36
- package/src/rbac/cache-invalidation.ts +21 -7
- package/src/rbac/compliance/quick-fix-suggestions.ts +1 -1
- package/src/rbac/components/NavigationGuard.tsx +23 -63
- package/src/rbac/components/NavigationProvider.test.tsx +52 -23
- package/src/rbac/components/NavigationProvider.tsx +13 -11
- package/src/rbac/components/PagePermissionGuard.tsx +77 -203
- package/src/rbac/components/PagePermissionProvider.tsx +13 -11
- package/src/rbac/components/PermissionEnforcer.tsx +24 -62
- package/src/rbac/components/RoleBasedRouter.tsx +14 -12
- package/src/rbac/components/SecureDataProvider.tsx +13 -11
- package/src/rbac/components/__tests__/NavigationGuard.test.tsx +104 -41
- package/src/rbac/components/__tests__/NavigationProvider.test.tsx +49 -12
- package/src/rbac/components/__tests__/PagePermissionGuard.race-condition.test.tsx +22 -1
- package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +161 -82
- package/src/rbac/components/__tests__/PagePermissionGuard.verification.test.tsx +22 -1
- package/src/rbac/components/__tests__/PermissionEnforcer.test.tsx +77 -30
- package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +39 -5
- package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +47 -4
- package/src/rbac/engine.ts +4 -2
- package/src/rbac/hooks/__tests__/useSecureSupabase.test.ts +144 -52
- package/src/rbac/hooks/index.ts +3 -0
- package/src/rbac/hooks/useCan.test.ts +101 -53
- package/src/rbac/hooks/usePermissions.ts +108 -41
- package/src/rbac/hooks/useRBAC.test.ts +11 -3
- package/src/rbac/hooks/useRBAC.ts +83 -40
- package/src/rbac/hooks/useResolvedScope.test.ts +189 -63
- package/src/rbac/hooks/useResolvedScope.ts +128 -70
- package/src/rbac/hooks/useSecureSupabase.ts +36 -19
- package/src/rbac/hooks/useSuperAdminBypass.ts +126 -0
- package/src/rbac/request-deduplication.ts +1 -1
- package/src/rbac/secureClient.ts +72 -12
- package/src/rbac/security.ts +29 -23
- package/src/rbac/types.ts +10 -0
- package/src/rbac/utils/__tests__/contextValidator.test.ts +150 -0
- package/src/rbac/utils/__tests__/deep-equal.test.ts +53 -0
- package/src/rbac/utils/__tests__/eventContext.test.ts +6 -1
- package/src/rbac/utils/contextValidator.ts +288 -0
- package/src/rbac/utils/eventContext.ts +48 -2
- package/src/services/EventService.ts +165 -21
- package/src/services/OrganisationService.ts +37 -2
- package/src/services/__tests__/EventService.test.ts +26 -21
- package/src/types/file-reference.ts +13 -10
- package/src/utils/app/appNameResolver.test.ts +346 -73
- package/src/utils/context/superAdminOverride.ts +58 -0
- package/src/utils/file-reference/index.ts +61 -33
- package/src/utils/google-places/googlePlacesUtils.test.ts +98 -0
- package/src/utils/google-places/loadGoogleMapsScript.test.ts +83 -0
- package/src/utils/storage/helpers.test.ts +1 -1
- package/src/utils/storage/helpers.ts +38 -19
- package/src/utils/storage/types.ts +15 -8
- package/src/utils/validation/__tests__/csrf.test.ts +105 -0
- package/src/utils/validation/__tests__/sqlInjectionProtection.test.ts +92 -0
- package/src/vite-env.d.ts +2 -2
- package/dist/chunk-3GOZZZYH.js.map +0 -1
- package/dist/chunk-DDM4CCYT.js.map +0 -1
- package/dist/chunk-E7UAOUMY.js +0 -75
- package/dist/chunk-E7UAOUMY.js.map +0 -1
- package/dist/chunk-EFCLXK7F.js.map +0 -1
- package/dist/chunk-F2IMUDXZ.js.map +0 -1
- package/dist/chunk-HEHYGYOX.js.map +0 -1
- package/dist/chunk-IM4QE42D.js.map +0 -1
- package/dist/chunk-IPCH26AG.js.map +0 -1
- package/dist/chunk-SAUPYVLF.js.map +0 -1
- package/dist/chunk-THRPYOFK.js.map +0 -1
- package/dist/chunk-UNOTYLQF.js.map +0 -1
- package/dist/chunk-VGZZXKBR.js.map +0 -1
- package/dist/chunk-YHCN776L.js.map +0 -1
- package/src/hooks/__tests__/usePermissionCache.simple.test.ts +0 -192
- package/src/hooks/__tests__/usePermissionCache.unit.test.ts +0 -741
- package/src/hooks/__tests__/usePublicEvent.simple.test.ts +0 -703
- package/src/rbac/hooks/useRBAC.simple.test.ts +0 -95
- package/src/rbac/utils/__tests__/eventContext.unit.test.ts +0 -428
- /package/dist/{DataTable-GUFUNZ3N.js.map → DataTable-ON3IXISJ.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-643PUAIM.js.map → UnifiedAuthProvider-X5NXANVI.js.map} +0 -0
- /package/dist/{api-YP7XD5L6.js.map → api-I6UCQ5S6.js.map} +0 -0
- /package/dist/{chunk-HESYZWZW.js.map → chunk-QWWZ5CAQ.js.map} +0 -0
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
|
+
useAppConfig,
|
|
3
|
+
useEvents,
|
|
4
|
+
useResolvedScope,
|
|
5
|
+
useSuperAdminBypass
|
|
6
|
+
} from "./chunk-Y4BUBBHD.js";
|
|
7
|
+
import {
|
|
8
|
+
useOrganisations,
|
|
9
|
+
useUnifiedAuth
|
|
10
|
+
} from "./chunk-J2XXC7R5.js";
|
|
11
|
+
import {
|
|
12
|
+
ContextValidator,
|
|
2
13
|
OrganisationContextRequiredError,
|
|
3
14
|
getAccessLevel,
|
|
4
15
|
getPermissionMap,
|
|
@@ -7,32 +18,22 @@ import {
|
|
|
7
18
|
isPermitted,
|
|
8
19
|
isPermittedCached,
|
|
9
20
|
resolveAppContext
|
|
10
|
-
} from "./chunk-
|
|
11
|
-
import {
|
|
12
|
-
useEvents,
|
|
13
|
-
useOrganisations
|
|
14
|
-
} from "./chunk-E7UAOUMY.js";
|
|
15
|
-
import {
|
|
16
|
-
useUnifiedAuth
|
|
17
|
-
} from "./chunk-VGZZXKBR.js";
|
|
18
|
-
import {
|
|
19
|
-
getCurrentAppName
|
|
20
|
-
} from "./chunk-F2IMUDXZ.js";
|
|
21
|
+
} from "./chunk-RUYZKXOD.js";
|
|
21
22
|
import {
|
|
22
|
-
createLogger,
|
|
23
23
|
logger
|
|
24
24
|
} from "./chunk-PWLANIRT.js";
|
|
25
25
|
|
|
26
26
|
// src/rbac/secureClient.ts
|
|
27
27
|
import { createClient } from "@supabase/supabase-js";
|
|
28
28
|
var SecureSupabaseClient = class _SecureSupabaseClient {
|
|
29
|
-
constructor(supabaseUrl, supabaseKey, organisationId, eventId, appId) {
|
|
29
|
+
constructor(supabaseUrl, supabaseKey, organisationId, eventId, appId, isSuperAdmin = false) {
|
|
30
30
|
this.edgeFunctionClient = null;
|
|
31
31
|
this.supabaseUrl = supabaseUrl;
|
|
32
32
|
this.supabaseKey = supabaseKey;
|
|
33
33
|
this.organisationId = organisationId;
|
|
34
34
|
this.eventId = eventId;
|
|
35
35
|
this.appId = appId;
|
|
36
|
+
this.isSuperAdmin = isSuperAdmin;
|
|
36
37
|
this.supabase = createClient(supabaseUrl, supabaseKey, {
|
|
37
38
|
global: {
|
|
38
39
|
headers: {
|
|
@@ -53,7 +54,8 @@ var SecureSupabaseClient = class _SecureSupabaseClient {
|
|
|
53
54
|
this.supabase.from = (table) => {
|
|
54
55
|
this.validateContext();
|
|
55
56
|
const query = originalFrom(table);
|
|
56
|
-
|
|
57
|
+
query._tableName = table;
|
|
58
|
+
return this.injectContext(query, table);
|
|
57
59
|
};
|
|
58
60
|
const originalRpc = this.supabase.rpc.bind(this.supabase);
|
|
59
61
|
this.supabase.rpc = (fn, args, options) => {
|
|
@@ -90,33 +92,60 @@ var SecureSupabaseClient = class _SecureSupabaseClient {
|
|
|
90
92
|
/**
|
|
91
93
|
* Inject organisation context into a query
|
|
92
94
|
*/
|
|
93
|
-
injectContext(query) {
|
|
95
|
+
injectContext(query, tableName) {
|
|
94
96
|
const originalSelect = query.select.bind(query);
|
|
95
97
|
const originalInsert = query.insert.bind(query);
|
|
96
98
|
const originalUpdate = query.update.bind(query);
|
|
97
99
|
const originalDelete = query.delete.bind(query);
|
|
98
100
|
query.select = (columns) => {
|
|
99
101
|
const result = originalSelect(columns);
|
|
100
|
-
return this.addOrganisationFilter(result);
|
|
102
|
+
return this.addOrganisationFilter(result, tableName);
|
|
101
103
|
};
|
|
102
104
|
query.insert = (values) => {
|
|
105
|
+
if (tableName === "rbac_user_profiles") {
|
|
106
|
+
if (this.isSuperAdmin) {
|
|
107
|
+
return originalInsert(values);
|
|
108
|
+
}
|
|
109
|
+
const contextValues2 = Array.isArray(values) ? values.map((v) => ({ ...v, organisation_id: this.organisationId })) : { ...values, organisation_id: this.organisationId };
|
|
110
|
+
return originalInsert(contextValues2);
|
|
111
|
+
}
|
|
103
112
|
const contextValues = Array.isArray(values) ? values.map((v) => ({ ...v, organisation_id: this.organisationId })) : { ...values, organisation_id: this.organisationId };
|
|
104
113
|
return originalInsert(contextValues);
|
|
105
114
|
};
|
|
106
115
|
query.update = (values) => {
|
|
107
116
|
const result = originalUpdate(values);
|
|
108
|
-
return this.addOrganisationFilter(result);
|
|
117
|
+
return this.addOrganisationFilter(result, tableName);
|
|
109
118
|
};
|
|
110
119
|
query.delete = () => {
|
|
111
120
|
const result = originalDelete();
|
|
112
|
-
return this.addOrganisationFilter(result);
|
|
121
|
+
return this.addOrganisationFilter(result, tableName);
|
|
113
122
|
};
|
|
114
123
|
return query;
|
|
115
124
|
}
|
|
116
125
|
/**
|
|
117
126
|
* Add organisation filter to a query
|
|
127
|
+
*
|
|
128
|
+
* Defense in depth strategy:
|
|
129
|
+
* - RLS policies are the primary security layer (cannot be bypassed)
|
|
130
|
+
* - Application-level filtering adds an additional layer of protection
|
|
131
|
+
*
|
|
132
|
+
* For rbac_user_profiles:
|
|
133
|
+
* - Super admins: No org filter (see all users) - RLS will allow access
|
|
134
|
+
* - Non-super-admins: Apply org filter as defense in depth - RLS will also filter
|
|
135
|
+
*
|
|
136
|
+
* For other tables:
|
|
137
|
+
* - Always apply org filter unless super admin bypasses it
|
|
118
138
|
*/
|
|
119
|
-
addOrganisationFilter(query) {
|
|
139
|
+
addOrganisationFilter(query, tableName) {
|
|
140
|
+
if (tableName === "rbac_user_profiles") {
|
|
141
|
+
if (this.isSuperAdmin) {
|
|
142
|
+
return query;
|
|
143
|
+
}
|
|
144
|
+
return query.eq("organisation_id", this.organisationId);
|
|
145
|
+
}
|
|
146
|
+
if (this.isSuperAdmin) {
|
|
147
|
+
return query.eq("organisation_id", this.organisationId);
|
|
148
|
+
}
|
|
120
149
|
return query.eq("organisation_id", this.organisationId);
|
|
121
150
|
}
|
|
122
151
|
/**
|
|
@@ -154,7 +183,8 @@ var SecureSupabaseClient = class _SecureSupabaseClient {
|
|
|
154
183
|
this.supabaseKey,
|
|
155
184
|
updates.organisationId || this.organisationId,
|
|
156
185
|
updates.eventId !== void 0 ? updates.eventId : this.eventId,
|
|
157
|
-
updates.appId !== void 0 ? updates.appId : this.appId
|
|
186
|
+
updates.appId !== void 0 ? updates.appId : this.appId,
|
|
187
|
+
updates.isSuperAdmin !== void 0 ? updates.isSuperAdmin : this.isSuperAdmin
|
|
158
188
|
);
|
|
159
189
|
}
|
|
160
190
|
/**
|
|
@@ -172,8 +202,8 @@ var SecureSupabaseClient = class _SecureSupabaseClient {
|
|
|
172
202
|
});
|
|
173
203
|
}
|
|
174
204
|
};
|
|
175
|
-
function createSecureClient(supabaseUrl, supabaseKey, organisationId, eventId, appId) {
|
|
176
|
-
return new SecureSupabaseClient(supabaseUrl, supabaseKey, organisationId, eventId, appId);
|
|
205
|
+
function createSecureClient(supabaseUrl, supabaseKey, organisationId, eventId, appId, isSuperAdmin = false) {
|
|
206
|
+
return new SecureSupabaseClient(supabaseUrl, supabaseKey, organisationId, eventId, appId, isSuperAdmin);
|
|
177
207
|
}
|
|
178
208
|
function fromSupabaseClient(client, organisationId, eventId, appId) {
|
|
179
209
|
throw new Error("fromSupabaseClient is not supported. Use createSecureClient instead.");
|
|
@@ -201,15 +231,16 @@ function useRBAC(pageId) {
|
|
|
201
231
|
const {
|
|
202
232
|
user,
|
|
203
233
|
session,
|
|
234
|
+
supabase,
|
|
204
235
|
appName,
|
|
205
236
|
appConfig,
|
|
237
|
+
appId: contextAppId,
|
|
206
238
|
selectedOrganisation,
|
|
207
239
|
isContextReady: orgContextReady,
|
|
208
240
|
organisationLoading: orgLoading,
|
|
209
241
|
selectedEvent,
|
|
210
242
|
eventLoading
|
|
211
243
|
} = useUnifiedAuth();
|
|
212
|
-
const requiresEvent = appConfig?.requires_event ?? (appConfig === null ? true : false);
|
|
213
244
|
const [globalRole, setGlobalRole] = useState(null);
|
|
214
245
|
const [organisationRole, setOrganisationRole] = useState(null);
|
|
215
246
|
const [eventAppRole, setEventAppRole] = useState(null);
|
|
@@ -230,59 +261,91 @@ function useRBAC(pageId) {
|
|
|
230
261
|
setIsLoading(false);
|
|
231
262
|
return;
|
|
232
263
|
}
|
|
233
|
-
|
|
264
|
+
const initialScope = {
|
|
265
|
+
organisationId: appConfig?.requires_event ? selectedEvent?.organisation_id || selectedOrganisation?.id : selectedOrganisation?.id,
|
|
266
|
+
eventId: selectedEvent?.event_id || void 0,
|
|
267
|
+
appId: void 0
|
|
268
|
+
};
|
|
269
|
+
const contextReady = ContextValidator.isContextReady(
|
|
270
|
+
initialScope,
|
|
271
|
+
appConfig,
|
|
272
|
+
appName,
|
|
273
|
+
!!selectedEvent,
|
|
274
|
+
!!selectedOrganisation
|
|
275
|
+
);
|
|
276
|
+
if (appName !== "PORTAL" && appName !== "ADMIN" && !contextReady) {
|
|
234
277
|
setIsLoading(true);
|
|
235
278
|
return;
|
|
236
279
|
}
|
|
237
|
-
if (requiresEvent) {
|
|
238
|
-
if (eventLoading || !selectedEvent) {
|
|
239
|
-
setIsLoading(true);
|
|
240
|
-
return;
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
280
|
setIsLoading(true);
|
|
244
281
|
setError(null);
|
|
245
282
|
logger2.debug("[useRBAC] Loading RBAC context", {
|
|
246
283
|
appName,
|
|
247
|
-
|
|
284
|
+
appConfig,
|
|
248
285
|
hasSelectedEvent: !!selectedEvent,
|
|
249
286
|
selectedEventId: selectedEvent?.event_id,
|
|
250
287
|
organisationId: selectedOrganisation?.id
|
|
251
288
|
});
|
|
252
289
|
try {
|
|
253
|
-
let appId;
|
|
254
|
-
if (appName) {
|
|
290
|
+
let appId = contextAppId;
|
|
291
|
+
if (appName && !appId) {
|
|
255
292
|
try {
|
|
256
293
|
const resolved = await resolveAppContext({ userId: user.id, appName });
|
|
257
|
-
if (!resolved
|
|
294
|
+
if (!resolved) {
|
|
295
|
+
if (appName === "PORTAL" || appName === "ADMIN") {
|
|
296
|
+
logger2.debug(`[useRBAC] ${appName} app context not resolved, attempting direct lookup`);
|
|
297
|
+
try {
|
|
298
|
+
const { getAppConfigByName } = await import("./api-I6UCQ5S6.js");
|
|
299
|
+
const config = await getAppConfigByName(appName);
|
|
300
|
+
logger2.debug(`[useRBAC] ${appName} app - proceeding without appId for page-level permissions`);
|
|
301
|
+
} catch (err) {
|
|
302
|
+
logger2.debug(`[useRBAC] ${appName} app - proceeding without appId for page-level permissions`);
|
|
303
|
+
}
|
|
304
|
+
} else {
|
|
305
|
+
throw new Error(`User does not have access to app "${appName}"`);
|
|
306
|
+
}
|
|
307
|
+
} else if (!resolved.hasAccess && appName !== "PORTAL" && appName !== "ADMIN") {
|
|
258
308
|
throw new Error(`User does not have access to app "${appName}"`);
|
|
309
|
+
} else {
|
|
310
|
+
appId = resolved.appId;
|
|
259
311
|
}
|
|
260
|
-
appId = resolved.appId;
|
|
261
312
|
} catch (rpcError) {
|
|
262
313
|
if (rpcError?.message?.includes("NetworkError") || rpcError?.message?.includes("fetch")) {
|
|
263
314
|
logger2.warn("[useRBAC] NetworkError resolving app context - may be timing issue, will retry when context is ready", {
|
|
264
315
|
appName,
|
|
265
316
|
error: rpcError.message,
|
|
266
|
-
requiresEvent,
|
|
267
317
|
eventLoading,
|
|
268
318
|
hasSelectedEvent: !!selectedEvent
|
|
269
319
|
});
|
|
270
320
|
setIsLoading(false);
|
|
271
321
|
return;
|
|
272
322
|
}
|
|
273
|
-
|
|
323
|
+
if (appName === "PORTAL" || appName === "ADMIN") {
|
|
324
|
+
logger2.debug(`[useRBAC] ${appName} app - allowing access despite app context resolution failure`);
|
|
325
|
+
} else {
|
|
326
|
+
throw rpcError;
|
|
327
|
+
}
|
|
274
328
|
}
|
|
275
329
|
}
|
|
276
330
|
const scope = {
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
appId
|
|
331
|
+
...initialScope,
|
|
332
|
+
appId: appId || contextAppId
|
|
280
333
|
};
|
|
281
|
-
|
|
334
|
+
const validation = await ContextValidator.resolveRequiredContext(
|
|
335
|
+
scope,
|
|
336
|
+
appConfig,
|
|
337
|
+
appName,
|
|
338
|
+
supabase || null
|
|
339
|
+
);
|
|
340
|
+
if (!validation.isValid || !validation.resolvedScope) {
|
|
341
|
+
throw validation.error || new Error("Context validation failed");
|
|
342
|
+
}
|
|
343
|
+
const resolvedScope = validation.resolvedScope;
|
|
344
|
+
setCurrentScope(resolvedScope);
|
|
282
345
|
const [map, roleContext, accessLevel] = await Promise.all([
|
|
283
|
-
getPermissionMap({ userId: user.id, scope }),
|
|
284
|
-
getRoleContext({ userId: user.id, scope }),
|
|
285
|
-
getAccessLevel({ userId: user.id, scope })
|
|
346
|
+
getPermissionMap({ userId: user.id, scope: resolvedScope }, appConfig, appName),
|
|
347
|
+
getRoleContext({ userId: user.id, scope: resolvedScope }, appConfig, appName),
|
|
348
|
+
getAccessLevel({ userId: user.id, scope: resolvedScope }, appConfig, appName)
|
|
286
349
|
]);
|
|
287
350
|
setPermissionMap(map);
|
|
288
351
|
setGlobalRole(roleContext.globalRole);
|
|
@@ -292,8 +355,8 @@ function useRBAC(pageId) {
|
|
|
292
355
|
if (permissionCount === 0) {
|
|
293
356
|
logger2.warn("[useRBAC] RBAC context loaded but returned 0 permissions", {
|
|
294
357
|
appName,
|
|
295
|
-
organisationId:
|
|
296
|
-
eventId:
|
|
358
|
+
organisationId: resolvedScope.organisationId,
|
|
359
|
+
eventId: resolvedScope.eventId
|
|
297
360
|
});
|
|
298
361
|
}
|
|
299
362
|
} catch (err) {
|
|
@@ -304,7 +367,7 @@ function useRBAC(pageId) {
|
|
|
304
367
|
} finally {
|
|
305
368
|
setIsLoading(false);
|
|
306
369
|
}
|
|
307
|
-
}, [appName, logger2, resetState, selectedEvent?.event_id, selectedOrganisation?.id, session, user,
|
|
370
|
+
}, [appName, logger2, resetState, selectedEvent?.event_id, selectedOrganisation?.id, session, user, eventLoading, appConfig, orgContextReady, orgLoading]);
|
|
308
371
|
const hasGlobalPermission = useCallback(
|
|
309
372
|
(permission) => {
|
|
310
373
|
if (globalRole === "super_admin" || permissionMap["*"]) {
|
|
@@ -327,7 +390,7 @@ function useRBAC(pageId) {
|
|
|
327
390
|
const canManageEvent = useMemo(() => isSuperAdmin || eventAppRole === "event_admin", [isSuperAdmin, eventAppRole]);
|
|
328
391
|
useEffect(() => {
|
|
329
392
|
loadRBACContext();
|
|
330
|
-
}, [loadRBACContext, appName,
|
|
393
|
+
}, [loadRBACContext, appName, appConfig, eventLoading, selectedEvent?.event_id, user, session, selectedOrganisation?.id, orgContextReady, orgLoading]);
|
|
331
394
|
return {
|
|
332
395
|
user,
|
|
333
396
|
globalRole,
|
|
@@ -344,176 +407,8 @@ function useRBAC(pageId) {
|
|
|
344
407
|
};
|
|
345
408
|
}
|
|
346
409
|
|
|
347
|
-
// src/rbac/hooks/useResolvedScope.ts
|
|
348
|
-
import { useEffect as useEffect2, useState as useState2, useRef } from "react";
|
|
349
|
-
|
|
350
|
-
// src/rbac/utils/eventContext.ts
|
|
351
|
-
async function getOrganisationFromEvent(supabase, eventId) {
|
|
352
|
-
const { data, error } = await supabase.from("event").select("organisation_id").eq("event_id", eventId).single();
|
|
353
|
-
if (error || !data) {
|
|
354
|
-
return null;
|
|
355
|
-
}
|
|
356
|
-
return data.organisation_id;
|
|
357
|
-
}
|
|
358
|
-
async function createScopeFromEvent(supabase, eventId, appId) {
|
|
359
|
-
const organisationId = await getOrganisationFromEvent(supabase, eventId);
|
|
360
|
-
if (!organisationId) {
|
|
361
|
-
return null;
|
|
362
|
-
}
|
|
363
|
-
return {
|
|
364
|
-
organisationId,
|
|
365
|
-
eventId,
|
|
366
|
-
appId
|
|
367
|
-
};
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
// src/rbac/hooks/useResolvedScope.ts
|
|
371
|
-
var log = createLogger("useResolvedScope");
|
|
372
|
-
function useResolvedScope({
|
|
373
|
-
supabase,
|
|
374
|
-
selectedOrganisationId,
|
|
375
|
-
selectedEventId
|
|
376
|
-
}) {
|
|
377
|
-
const [resolvedScope, setResolvedScope] = useState2(null);
|
|
378
|
-
const [isLoading, setIsLoading] = useState2(true);
|
|
379
|
-
const [error, setError] = useState2(null);
|
|
380
|
-
const stableScopeRef = useRef({
|
|
381
|
-
organisationId: "",
|
|
382
|
-
appId: "",
|
|
383
|
-
eventId: void 0
|
|
384
|
-
});
|
|
385
|
-
useEffect2(() => {
|
|
386
|
-
if (resolvedScope && resolvedScope.organisationId) {
|
|
387
|
-
const newScope = {
|
|
388
|
-
organisationId: resolvedScope.organisationId,
|
|
389
|
-
appId: resolvedScope.appId,
|
|
390
|
-
eventId: resolvedScope.eventId
|
|
391
|
-
};
|
|
392
|
-
if (stableScopeRef.current.organisationId !== newScope.organisationId || stableScopeRef.current.eventId !== newScope.eventId || stableScopeRef.current.appId !== newScope.appId) {
|
|
393
|
-
stableScopeRef.current = {
|
|
394
|
-
organisationId: newScope.organisationId,
|
|
395
|
-
appId: newScope.appId || "",
|
|
396
|
-
eventId: newScope.eventId
|
|
397
|
-
};
|
|
398
|
-
}
|
|
399
|
-
} else if (!resolvedScope) {
|
|
400
|
-
stableScopeRef.current = { organisationId: "", appId: "", eventId: void 0 };
|
|
401
|
-
}
|
|
402
|
-
}, [resolvedScope]);
|
|
403
|
-
const stableScope = stableScopeRef.current;
|
|
404
|
-
useEffect2(() => {
|
|
405
|
-
let cancelled = false;
|
|
406
|
-
const resolveScope = async () => {
|
|
407
|
-
if (!supabase && !selectedOrganisationId && !selectedEventId) {
|
|
408
|
-
if (!cancelled) {
|
|
409
|
-
setResolvedScope(null);
|
|
410
|
-
setIsLoading(false);
|
|
411
|
-
setError(null);
|
|
412
|
-
}
|
|
413
|
-
return;
|
|
414
|
-
}
|
|
415
|
-
setIsLoading(true);
|
|
416
|
-
setError(null);
|
|
417
|
-
try {
|
|
418
|
-
let appId = void 0;
|
|
419
|
-
if (supabase) {
|
|
420
|
-
const appName = getCurrentAppName();
|
|
421
|
-
if (appName) {
|
|
422
|
-
try {
|
|
423
|
-
const { data: app, error: error2 } = await supabase.from("rbac_apps").select("id, name, is_active").eq("name", appName).eq("is_active", true).single();
|
|
424
|
-
if (error2) {
|
|
425
|
-
const { data: inactiveApp } = await supabase.from("rbac_apps").select("id, name, is_active").eq("name", appName).single();
|
|
426
|
-
if (inactiveApp) {
|
|
427
|
-
log.error(`App "${appName}" exists but is inactive (is_active: ${inactiveApp.is_active})`);
|
|
428
|
-
} else {
|
|
429
|
-
log.error(`App "${appName}" not found in rbac_apps table`);
|
|
430
|
-
}
|
|
431
|
-
} else if (app) {
|
|
432
|
-
appId = app.id;
|
|
433
|
-
}
|
|
434
|
-
} catch (error2) {
|
|
435
|
-
log.error("Unexpected error resolving app ID:", error2);
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
if (selectedOrganisationId && selectedEventId) {
|
|
440
|
-
if (!cancelled) {
|
|
441
|
-
setResolvedScope({
|
|
442
|
-
organisationId: selectedOrganisationId,
|
|
443
|
-
eventId: selectedEventId,
|
|
444
|
-
appId
|
|
445
|
-
});
|
|
446
|
-
setIsLoading(false);
|
|
447
|
-
}
|
|
448
|
-
return;
|
|
449
|
-
}
|
|
450
|
-
if (selectedOrganisationId) {
|
|
451
|
-
if (!cancelled) {
|
|
452
|
-
setResolvedScope({
|
|
453
|
-
organisationId: selectedOrganisationId,
|
|
454
|
-
eventId: selectedEventId || void 0,
|
|
455
|
-
appId
|
|
456
|
-
});
|
|
457
|
-
setIsLoading(false);
|
|
458
|
-
}
|
|
459
|
-
return;
|
|
460
|
-
}
|
|
461
|
-
if (selectedEventId && supabase) {
|
|
462
|
-
try {
|
|
463
|
-
const eventScope = await createScopeFromEvent(supabase, selectedEventId, appId);
|
|
464
|
-
if (!eventScope) {
|
|
465
|
-
log.error("Could not resolve organization from event context");
|
|
466
|
-
if (!cancelled) {
|
|
467
|
-
setResolvedScope(null);
|
|
468
|
-
setError(new Error("Could not resolve organisation from event context"));
|
|
469
|
-
setIsLoading(false);
|
|
470
|
-
}
|
|
471
|
-
return;
|
|
472
|
-
}
|
|
473
|
-
if (!cancelled) {
|
|
474
|
-
setResolvedScope({
|
|
475
|
-
...eventScope,
|
|
476
|
-
appId: appId || eventScope.appId
|
|
477
|
-
});
|
|
478
|
-
setIsLoading(false);
|
|
479
|
-
}
|
|
480
|
-
} catch (err) {
|
|
481
|
-
log.error("Error resolving scope from event:", err);
|
|
482
|
-
if (!cancelled) {
|
|
483
|
-
setResolvedScope(null);
|
|
484
|
-
setError(err);
|
|
485
|
-
setIsLoading(false);
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
return;
|
|
489
|
-
}
|
|
490
|
-
log.error("No organisation or event context available");
|
|
491
|
-
if (!cancelled) {
|
|
492
|
-
setResolvedScope(null);
|
|
493
|
-
setError(new Error("No organisation or event context available"));
|
|
494
|
-
setIsLoading(false);
|
|
495
|
-
}
|
|
496
|
-
} catch (err) {
|
|
497
|
-
if (!cancelled) {
|
|
498
|
-
setError(err);
|
|
499
|
-
setIsLoading(false);
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
};
|
|
503
|
-
resolveScope();
|
|
504
|
-
return () => {
|
|
505
|
-
cancelled = true;
|
|
506
|
-
};
|
|
507
|
-
}, [selectedOrganisationId, selectedEventId, supabase]);
|
|
508
|
-
return {
|
|
509
|
-
resolvedScope: stableScope.organisationId ? stableScope : null,
|
|
510
|
-
isLoading,
|
|
511
|
-
error
|
|
512
|
-
};
|
|
513
|
-
}
|
|
514
|
-
|
|
515
410
|
// src/rbac/hooks/usePermissions.ts
|
|
516
|
-
import { useState as
|
|
411
|
+
import { useState as useState2, useEffect as useEffect2, useCallback as useCallback2, useMemo as useMemo2, useRef } from "react";
|
|
517
412
|
|
|
518
413
|
// src/rbac/utils/deep-equal.ts
|
|
519
414
|
function scopeEqual(a, b) {
|
|
@@ -528,15 +423,15 @@ function scopeEqual(a, b) {
|
|
|
528
423
|
|
|
529
424
|
// src/rbac/hooks/usePermissions.ts
|
|
530
425
|
function usePermissions(userId, organisationId, eventId, appId) {
|
|
531
|
-
const [permissions, setPermissions] =
|
|
532
|
-
const [isLoading, setIsLoading] =
|
|
533
|
-
const [error, setError] =
|
|
534
|
-
const [fetchTrigger, setFetchTrigger] =
|
|
535
|
-
const isFetchingRef =
|
|
426
|
+
const [permissions, setPermissions] = useState2({});
|
|
427
|
+
const [isLoading, setIsLoading] = useState2(true);
|
|
428
|
+
const [error, setError] = useState2(null);
|
|
429
|
+
const [fetchTrigger, setFetchTrigger] = useState2(0);
|
|
430
|
+
const isFetchingRef = useRef(false);
|
|
536
431
|
const logger2 = getRBACLogger();
|
|
537
|
-
const prevValuesRef =
|
|
432
|
+
const prevValuesRef = useRef({ userId, organisationId, eventId, appId });
|
|
538
433
|
const orgId = organisationId || "";
|
|
539
|
-
|
|
434
|
+
useEffect2(() => {
|
|
540
435
|
if (!userId) {
|
|
541
436
|
return;
|
|
542
437
|
}
|
|
@@ -550,19 +445,21 @@ function usePermissions(userId, organisationId, eventId, appId) {
|
|
|
550
445
|
if (error?.message === "Organisation context is required for permission checks") {
|
|
551
446
|
setError(null);
|
|
552
447
|
}
|
|
553
|
-
}, [userId, organisationId, error]);
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
if (
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
448
|
+
}, [userId, organisationId, error, orgId]);
|
|
449
|
+
useEffect2(() => {
|
|
450
|
+
const paramsChanged = prevValuesRef.current.userId !== userId || prevValuesRef.current.organisationId !== organisationId || prevValuesRef.current.eventId !== eventId || prevValuesRef.current.appId !== appId;
|
|
451
|
+
if (paramsChanged) {
|
|
452
|
+
if (prevValuesRef.current.appId !== appId) {
|
|
453
|
+
logger2.debug("[usePermissions] AppId changed - triggering fetch", {
|
|
454
|
+
prevAppId: prevValuesRef.current.appId,
|
|
455
|
+
newAppId: appId
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
prevValuesRef.current = { userId, organisationId, eventId, appId };
|
|
459
|
+
setFetchTrigger((prev) => prev + 1);
|
|
561
460
|
}
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
}
|
|
565
|
-
useEffect3(() => {
|
|
461
|
+
}, [userId, organisationId, eventId, appId, logger2]);
|
|
462
|
+
useEffect2(() => {
|
|
566
463
|
const fetchPermissions = async () => {
|
|
567
464
|
if (isFetchingRef.current) {
|
|
568
465
|
return;
|
|
@@ -671,16 +568,18 @@ function usePermissions(userId, organisationId, eventId, appId) {
|
|
|
671
568
|
refetch
|
|
672
569
|
}), [permissions, isLoading, error, hasPermission, hasAnyPermission, hasAllPermissions, refetch]);
|
|
673
570
|
}
|
|
674
|
-
function useCan(userId, scope, permission, pageId, useCache = true) {
|
|
675
|
-
const [can, setCan] =
|
|
676
|
-
const [isLoading, setIsLoading] =
|
|
677
|
-
const [error, setError] =
|
|
571
|
+
function useCan(userId, scope, permission, pageId, useCache = true, appName) {
|
|
572
|
+
const [can, setCan] = useState2(false);
|
|
573
|
+
const [isLoading, setIsLoading] = useState2(true);
|
|
574
|
+
const [error, setError] = useState2(null);
|
|
678
575
|
const isValidScope = scope && typeof scope === "object";
|
|
679
576
|
const organisationId = isValidScope ? scope.organisationId : void 0;
|
|
680
577
|
const eventId = isValidScope ? scope.eventId : void 0;
|
|
681
578
|
const appId = isValidScope ? scope.appId : void 0;
|
|
682
|
-
|
|
683
|
-
|
|
579
|
+
useEffect2(() => {
|
|
580
|
+
const isPagePermission = permission.includes(":page.") || !!pageId;
|
|
581
|
+
const requiresOrgId = !isPagePermission;
|
|
582
|
+
if (requiresOrgId && (!isValidScope || !organisationId || organisationId === null || typeof organisationId === "string" && organisationId.trim() === "")) {
|
|
684
583
|
const timeoutId = setTimeout(() => {
|
|
685
584
|
setError(new Error("Organisation context is required for permission checks"));
|
|
686
585
|
setIsLoading(false);
|
|
@@ -691,12 +590,12 @@ function useCan(userId, scope, permission, pageId, useCache = true) {
|
|
|
691
590
|
if (error?.message === "Organisation context is required for permission checks") {
|
|
692
591
|
setError(null);
|
|
693
592
|
}
|
|
694
|
-
}, [isValidScope, organisationId, error]);
|
|
695
|
-
const lastUserIdRef =
|
|
696
|
-
const lastScopeRef =
|
|
697
|
-
const lastPermissionRef =
|
|
698
|
-
const lastPageIdRef =
|
|
699
|
-
const lastUseCacheRef =
|
|
593
|
+
}, [isValidScope, organisationId, error, permission, pageId]);
|
|
594
|
+
const lastUserIdRef = useRef(null);
|
|
595
|
+
const lastScopeRef = useRef(null);
|
|
596
|
+
const lastPermissionRef = useRef(null);
|
|
597
|
+
const lastPageIdRef = useRef(null);
|
|
598
|
+
const lastUseCacheRef = useRef(null);
|
|
700
599
|
const stableScope = useMemo2(() => {
|
|
701
600
|
if (!isValidScope) {
|
|
702
601
|
return null;
|
|
@@ -707,8 +606,8 @@ function useCan(userId, scope, permission, pageId, useCache = true) {
|
|
|
707
606
|
appId
|
|
708
607
|
};
|
|
709
608
|
}, [isValidScope, organisationId, eventId, appId]);
|
|
710
|
-
const prevScopeRef =
|
|
711
|
-
|
|
609
|
+
const prevScopeRef = useRef(null);
|
|
610
|
+
useEffect2(() => {
|
|
712
611
|
const scopeChanged = !scopeEqual(prevScopeRef.current, stableScope);
|
|
713
612
|
if (lastUserIdRef.current !== userId || scopeChanged || lastPermissionRef.current !== permission || lastPageIdRef.current !== pageId || lastUseCacheRef.current !== useCache) {
|
|
714
613
|
lastUserIdRef.current = userId;
|
|
@@ -728,7 +627,17 @@ function useCan(userId, scope, permission, pageId, useCache = true) {
|
|
|
728
627
|
setError(null);
|
|
729
628
|
return;
|
|
730
629
|
}
|
|
731
|
-
|
|
630
|
+
const isPagePermission = permission.includes(":page.") || !!pageId;
|
|
631
|
+
const requiresOrgId = !isPagePermission;
|
|
632
|
+
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);
|
|
633
|
+
const needsAppIdForPageName = isPagePermission && isPageName;
|
|
634
|
+
if (requiresOrgId && (!organisationId || organisationId === null || typeof organisationId === "string" && organisationId.trim() === "")) {
|
|
635
|
+
setIsLoading(true);
|
|
636
|
+
setCan(false);
|
|
637
|
+
setError(null);
|
|
638
|
+
return;
|
|
639
|
+
}
|
|
640
|
+
if (needsAppIdForPageName && (!appId || appId === null || typeof appId === "string" && appId.trim() === "")) {
|
|
732
641
|
setIsLoading(true);
|
|
733
642
|
setCan(false);
|
|
734
643
|
setError(null);
|
|
@@ -738,11 +647,11 @@ function useCan(userId, scope, permission, pageId, useCache = true) {
|
|
|
738
647
|
setIsLoading(true);
|
|
739
648
|
setError(null);
|
|
740
649
|
const validScope = {
|
|
741
|
-
organisationId,
|
|
650
|
+
...organisationId ? { organisationId } : {},
|
|
742
651
|
...eventId ? { eventId } : {},
|
|
743
652
|
...appId ? { appId } : {}
|
|
744
653
|
};
|
|
745
|
-
const result = useCache ? await isPermittedCached({ userId, scope: validScope, permission, pageId }) : await isPermitted({ userId, scope: validScope, permission, pageId });
|
|
654
|
+
const result = useCache ? await isPermittedCached({ userId, scope: validScope, permission, pageId }, void 0, appName) : await isPermitted({ userId, scope: validScope, permission, pageId }, void 0, appName);
|
|
746
655
|
setCan(result);
|
|
747
656
|
} catch (err) {
|
|
748
657
|
const logger2 = getRBACLogger();
|
|
@@ -755,7 +664,7 @@ function useCan(userId, scope, permission, pageId, useCache = true) {
|
|
|
755
664
|
};
|
|
756
665
|
checkPermission();
|
|
757
666
|
}
|
|
758
|
-
}, [userId, stableScope, permission, pageId, useCache]);
|
|
667
|
+
}, [userId, stableScope, permission, pageId, useCache, appName]);
|
|
759
668
|
const refetch = useCallback2(async () => {
|
|
760
669
|
if (!userId) {
|
|
761
670
|
setCan(false);
|
|
@@ -768,7 +677,9 @@ function useCan(userId, scope, permission, pageId, useCache = true) {
|
|
|
768
677
|
setError(null);
|
|
769
678
|
return;
|
|
770
679
|
}
|
|
771
|
-
|
|
680
|
+
const isPagePermission = permission.includes(":page.") || !!pageId;
|
|
681
|
+
const requiresOrgId = !isPagePermission;
|
|
682
|
+
if (requiresOrgId && (!organisationId || organisationId === null || typeof organisationId === "string" && organisationId.trim() === "")) {
|
|
772
683
|
setCan(false);
|
|
773
684
|
setIsLoading(true);
|
|
774
685
|
setError(null);
|
|
@@ -778,11 +689,11 @@ function useCan(userId, scope, permission, pageId, useCache = true) {
|
|
|
778
689
|
setIsLoading(true);
|
|
779
690
|
setError(null);
|
|
780
691
|
const validScope = {
|
|
781
|
-
organisationId,
|
|
692
|
+
...organisationId ? { organisationId } : {},
|
|
782
693
|
...eventId ? { eventId } : {},
|
|
783
694
|
...appId ? { appId } : {}
|
|
784
695
|
};
|
|
785
|
-
const result = useCache ? await isPermittedCached({ userId, scope: validScope, permission, pageId }) : await isPermitted({ userId, scope: validScope, permission, pageId });
|
|
696
|
+
const result = useCache ? await isPermittedCached({ userId, scope: validScope, permission, pageId }, void 0, appName) : await isPermitted({ userId, scope: validScope, permission, pageId }, void 0, appName);
|
|
786
697
|
setCan(result);
|
|
787
698
|
} catch (err) {
|
|
788
699
|
setError(err instanceof Error ? err : new Error("Failed to check permission"));
|
|
@@ -790,7 +701,7 @@ function useCan(userId, scope, permission, pageId, useCache = true) {
|
|
|
790
701
|
} finally {
|
|
791
702
|
setIsLoading(false);
|
|
792
703
|
}
|
|
793
|
-
}, [userId, isValidScope, organisationId, eventId, appId, permission, pageId, useCache]);
|
|
704
|
+
}, [userId, isValidScope, organisationId, eventId, appId, permission, pageId, useCache, appName]);
|
|
794
705
|
return useMemo2(() => ({
|
|
795
706
|
can,
|
|
796
707
|
isLoading,
|
|
@@ -799,9 +710,15 @@ function useCan(userId, scope, permission, pageId, useCache = true) {
|
|
|
799
710
|
}), [can, isLoading, error, refetch]);
|
|
800
711
|
}
|
|
801
712
|
function useAccessLevel(userId, scope) {
|
|
802
|
-
const [accessLevel, setAccessLevel] =
|
|
803
|
-
const [isLoading, setIsLoading] =
|
|
804
|
-
const [error, setError] =
|
|
713
|
+
const [accessLevel, setAccessLevel] = useState2("viewer");
|
|
714
|
+
const [isLoading, setIsLoading] = useState2(true);
|
|
715
|
+
const [error, setError] = useState2(null);
|
|
716
|
+
let appName;
|
|
717
|
+
try {
|
|
718
|
+
const { appName: contextAppName } = useAppConfig();
|
|
719
|
+
appName = contextAppName;
|
|
720
|
+
} catch {
|
|
721
|
+
}
|
|
805
722
|
const fetchAccessLevel = useCallback2(async () => {
|
|
806
723
|
if (!userId) {
|
|
807
724
|
setAccessLevel("viewer");
|
|
@@ -811,16 +728,31 @@ function useAccessLevel(userId, scope) {
|
|
|
811
728
|
try {
|
|
812
729
|
setIsLoading(true);
|
|
813
730
|
setError(null);
|
|
814
|
-
const
|
|
731
|
+
const { isSuperAdmin: checkSuperAdmin } = await import("./api-I6UCQ5S6.js");
|
|
732
|
+
const isSuperAdminUser = await checkSuperAdmin(userId);
|
|
733
|
+
if (isSuperAdminUser) {
|
|
734
|
+
setAccessLevel("super");
|
|
735
|
+
setIsLoading(false);
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
if (appName !== "PORTAL" && appName !== "ADMIN" && !scope.organisationId && !scope.eventId) {
|
|
739
|
+
const orgError = new OrganisationContextRequiredError();
|
|
740
|
+
setError(orgError);
|
|
741
|
+
setAccessLevel("viewer");
|
|
742
|
+
setIsLoading(false);
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
745
|
+
const level = await getAccessLevel({ userId, scope }, null, appName);
|
|
815
746
|
setAccessLevel(level);
|
|
816
747
|
} catch (err) {
|
|
817
|
-
|
|
748
|
+
const error2 = err instanceof Error ? err : new Error("Failed to fetch access level");
|
|
749
|
+
setError(error2);
|
|
818
750
|
setAccessLevel("viewer");
|
|
819
751
|
} finally {
|
|
820
752
|
setIsLoading(false);
|
|
821
753
|
}
|
|
822
|
-
}, [userId, scope.organisationId, scope.eventId, scope.appId]);
|
|
823
|
-
|
|
754
|
+
}, [userId, scope.organisationId, scope.eventId, scope.appId, appName]);
|
|
755
|
+
useEffect2(() => {
|
|
824
756
|
fetchAccessLevel();
|
|
825
757
|
}, [fetchAccessLevel]);
|
|
826
758
|
return useMemo2(() => ({
|
|
@@ -831,9 +763,9 @@ function useAccessLevel(userId, scope) {
|
|
|
831
763
|
}), [accessLevel, isLoading, error, fetchAccessLevel]);
|
|
832
764
|
}
|
|
833
765
|
function useMultiplePermissions(userId, scope, permissions, useCache = true) {
|
|
834
|
-
const [results, setResults] =
|
|
835
|
-
const [isLoading, setIsLoading] =
|
|
836
|
-
const [error, setError] =
|
|
766
|
+
const [results, setResults] = useState2({});
|
|
767
|
+
const [isLoading, setIsLoading] = useState2(true);
|
|
768
|
+
const [error, setError] = useState2(null);
|
|
837
769
|
const checkPermissions = useCallback2(async () => {
|
|
838
770
|
if (!userId || permissions.length === 0) {
|
|
839
771
|
setResults({});
|
|
@@ -856,7 +788,7 @@ function useMultiplePermissions(userId, scope, permissions, useCache = true) {
|
|
|
856
788
|
setIsLoading(false);
|
|
857
789
|
}
|
|
858
790
|
}, [userId, scope.organisationId, scope.eventId, scope.appId, permissions, useCache]);
|
|
859
|
-
|
|
791
|
+
useEffect2(() => {
|
|
860
792
|
checkPermissions();
|
|
861
793
|
}, [checkPermissions]);
|
|
862
794
|
return useMemo2(() => ({
|
|
@@ -867,9 +799,9 @@ function useMultiplePermissions(userId, scope, permissions, useCache = true) {
|
|
|
867
799
|
}), [results, isLoading, error, checkPermissions]);
|
|
868
800
|
}
|
|
869
801
|
function useHasAnyPermission(userId, scope, permissions, useCache = true) {
|
|
870
|
-
const [hasAny, setHasAny] =
|
|
871
|
-
const [isLoading, setIsLoading] =
|
|
872
|
-
const [error, setError] =
|
|
802
|
+
const [hasAny, setHasAny] = useState2(false);
|
|
803
|
+
const [isLoading, setIsLoading] = useState2(true);
|
|
804
|
+
const [error, setError] = useState2(null);
|
|
873
805
|
const checkAnyPermission = useCallback2(async () => {
|
|
874
806
|
if (!userId || permissions.length === 0) {
|
|
875
807
|
setHasAny(false);
|
|
@@ -895,7 +827,7 @@ function useHasAnyPermission(userId, scope, permissions, useCache = true) {
|
|
|
895
827
|
setIsLoading(false);
|
|
896
828
|
}
|
|
897
829
|
}, [userId, scope.organisationId, scope.eventId, scope.appId, permissions, useCache]);
|
|
898
|
-
|
|
830
|
+
useEffect2(() => {
|
|
899
831
|
checkAnyPermission();
|
|
900
832
|
}, [checkAnyPermission]);
|
|
901
833
|
return useMemo2(() => ({
|
|
@@ -906,9 +838,9 @@ function useHasAnyPermission(userId, scope, permissions, useCache = true) {
|
|
|
906
838
|
}), [hasAny, isLoading, error, checkAnyPermission]);
|
|
907
839
|
}
|
|
908
840
|
function useHasAllPermissions(userId, scope, permissions, useCache = true) {
|
|
909
|
-
const [hasAll, setHasAll] =
|
|
910
|
-
const [isLoading, setIsLoading] =
|
|
911
|
-
const [error, setError] =
|
|
841
|
+
const [hasAll, setHasAll] = useState2(false);
|
|
842
|
+
const [isLoading, setIsLoading] = useState2(true);
|
|
843
|
+
const [error, setError] = useState2(null);
|
|
912
844
|
const checkAllPermissions = useCallback2(async () => {
|
|
913
845
|
if (!userId || permissions.length === 0) {
|
|
914
846
|
setHasAll(false);
|
|
@@ -934,7 +866,7 @@ function useHasAllPermissions(userId, scope, permissions, useCache = true) {
|
|
|
934
866
|
setIsLoading(false);
|
|
935
867
|
}
|
|
936
868
|
}, [userId, scope.organisationId, scope.eventId, scope.appId, permissions, useCache]);
|
|
937
|
-
|
|
869
|
+
useEffect2(() => {
|
|
938
870
|
checkAllPermissions();
|
|
939
871
|
}, [checkAllPermissions]);
|
|
940
872
|
return useMemo2(() => ({
|
|
@@ -945,9 +877,9 @@ function useHasAllPermissions(userId, scope, permissions, useCache = true) {
|
|
|
945
877
|
}), [hasAll, isLoading, error, checkAllPermissions]);
|
|
946
878
|
}
|
|
947
879
|
function useCachedPermissions(userId, scope) {
|
|
948
|
-
const [permissions, setPermissions] =
|
|
949
|
-
const [isLoading, setIsLoading] =
|
|
950
|
-
const [error, setError] =
|
|
880
|
+
const [permissions, setPermissions] = useState2({});
|
|
881
|
+
const [isLoading, setIsLoading] = useState2(true);
|
|
882
|
+
const [error, setError] = useState2(null);
|
|
951
883
|
const fetchCachedPermissions = useCallback2(async () => {
|
|
952
884
|
if (!userId) {
|
|
953
885
|
setPermissions({});
|
|
@@ -968,7 +900,7 @@ function useCachedPermissions(userId, scope) {
|
|
|
968
900
|
const invalidateCache = useCallback2(() => {
|
|
969
901
|
fetchCachedPermissions();
|
|
970
902
|
}, [fetchCachedPermissions]);
|
|
971
|
-
|
|
903
|
+
useEffect2(() => {
|
|
972
904
|
fetchCachedPermissions();
|
|
973
905
|
}, [fetchCachedPermissions]);
|
|
974
906
|
return useMemo2(() => ({
|
|
@@ -1095,11 +1027,11 @@ function useResourcePermissions(resource, options = {}) {
|
|
|
1095
1027
|
}
|
|
1096
1028
|
|
|
1097
1029
|
// src/rbac/hooks/useRoleManagement.ts
|
|
1098
|
-
import { useState as
|
|
1030
|
+
import { useState as useState3, useCallback as useCallback3 } from "react";
|
|
1099
1031
|
function useRoleManagement() {
|
|
1100
1032
|
const { user, supabase } = useUnifiedAuth();
|
|
1101
|
-
const [isLoading, setIsLoading] =
|
|
1102
|
-
const [error, setError] =
|
|
1033
|
+
const [isLoading, setIsLoading] = useState3(false);
|
|
1034
|
+
const [error, setError] = useState3(null);
|
|
1103
1035
|
if (!supabase) {
|
|
1104
1036
|
throw new Error("useRoleManagement requires a Supabase client. Ensure UnifiedAuthProvider is configured.");
|
|
1105
1037
|
}
|
|
@@ -1369,11 +1301,11 @@ function useRoleManagement() {
|
|
|
1369
1301
|
}
|
|
1370
1302
|
|
|
1371
1303
|
// src/rbac/hooks/useSecureSupabase.ts
|
|
1372
|
-
import { useMemo as useMemo4, useRef as
|
|
1304
|
+
import { useMemo as useMemo4, useRef as useRef2 } from "react";
|
|
1373
1305
|
var secureClientCache = /* @__PURE__ */ new Map();
|
|
1374
1306
|
var MAX_CACHE_SIZE = 5;
|
|
1375
|
-
function getCacheKey(organisationId, eventId, appId) {
|
|
1376
|
-
return `${organisationId}-${eventId || "no-event"}-${appId || "no-app"}`;
|
|
1307
|
+
function getCacheKey(organisationId, eventId, appId, isSuperAdmin) {
|
|
1308
|
+
return `${organisationId}-${eventId || "no-event"}-${appId || "no-app"}-${isSuperAdmin ? "super" : "regular"}`;
|
|
1377
1309
|
}
|
|
1378
1310
|
function getSupabaseConfig() {
|
|
1379
1311
|
const getEnvVar = (key) => {
|
|
@@ -1386,7 +1318,7 @@ function getSupabaseConfig() {
|
|
|
1386
1318
|
return void 0;
|
|
1387
1319
|
};
|
|
1388
1320
|
const supabaseUrl = getEnvVar("VITE_SUPABASE_URL") || getEnvVar("NEXT_PUBLIC_SUPABASE_URL") || null;
|
|
1389
|
-
const supabaseKey = getEnvVar("
|
|
1321
|
+
const supabaseKey = getEnvVar("VITE_SUPABASE_PUBLISHABLE_KEY") || getEnvVar("NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY") || null;
|
|
1390
1322
|
if (!supabaseUrl || !supabaseKey) {
|
|
1391
1323
|
return null;
|
|
1392
1324
|
}
|
|
@@ -1398,12 +1330,15 @@ function useSecureSupabase(baseClient) {
|
|
|
1398
1330
|
const eventsContext = useEvents();
|
|
1399
1331
|
const { selectedEvent } = eventsContext;
|
|
1400
1332
|
const eventLoading = "eventLoading" in eventsContext ? eventsContext.eventLoading : false;
|
|
1333
|
+
const { isSuperAdmin: verifiedIsSuperAdmin, isLoading: isVerifyingSuperAdmin } = useSuperAdminBypass();
|
|
1334
|
+
const metadataHint = Boolean(user?.app_metadata?.is_super_admin) || Boolean(user?.user_metadata?.is_super_admin);
|
|
1335
|
+
const isSuperAdmin = verifiedIsSuperAdmin || isVerifyingSuperAdmin && metadataHint;
|
|
1401
1336
|
const { resolvedScope } = useResolvedScope({
|
|
1402
1337
|
supabase: authSupabase || null,
|
|
1403
1338
|
selectedOrganisationId: selectedOrganisation?.id || null,
|
|
1404
1339
|
selectedEventId: selectedEvent?.event_id || null
|
|
1405
1340
|
});
|
|
1406
|
-
const prevContextRef =
|
|
1341
|
+
const prevContextRef = useRef2({
|
|
1407
1342
|
organisationId: void 0,
|
|
1408
1343
|
eventId: void 0,
|
|
1409
1344
|
appId: void 0
|
|
@@ -1412,12 +1347,12 @@ function useSecureSupabase(baseClient) {
|
|
|
1412
1347
|
if (eventLoading) {
|
|
1413
1348
|
return baseClient || authSupabase || null;
|
|
1414
1349
|
}
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1350
|
+
const organisationId = resolvedScope?.organisationId;
|
|
1351
|
+
const eventId = resolvedScope?.eventId || selectedEvent?.event_id;
|
|
1352
|
+
const appId = resolvedScope?.appId;
|
|
1353
|
+
if (organisationId && user?.id) {
|
|
1419
1354
|
prevContextRef.current = { organisationId, eventId, appId };
|
|
1420
|
-
const cacheKey = getCacheKey(organisationId, eventId, appId);
|
|
1355
|
+
const cacheKey = getCacheKey(organisationId, eventId, appId, isSuperAdmin);
|
|
1421
1356
|
const cachedClient = secureClientCache.get(cacheKey);
|
|
1422
1357
|
if (cachedClient) {
|
|
1423
1358
|
return cachedClient.getClient();
|
|
@@ -1425,7 +1360,7 @@ function useSecureSupabase(baseClient) {
|
|
|
1425
1360
|
const config = getSupabaseConfig();
|
|
1426
1361
|
if (!config || !config.url || !config.key) {
|
|
1427
1362
|
logger.warn("useSecureSupabase", "Missing Supabase environment variables. Falling back to base client.", {
|
|
1428
|
-
note: "Ensure VITE_SUPABASE_URL and
|
|
1363
|
+
note: "Ensure VITE_SUPABASE_URL and VITE_SUPABASE_PUBLISHABLE_KEY are set in your environment."
|
|
1429
1364
|
});
|
|
1430
1365
|
return baseClient || authSupabase || null;
|
|
1431
1366
|
}
|
|
@@ -1436,8 +1371,10 @@ function useSecureSupabase(baseClient) {
|
|
|
1436
1371
|
organisationId,
|
|
1437
1372
|
// organisationId is string, UUID is string alias
|
|
1438
1373
|
eventId,
|
|
1439
|
-
appId
|
|
1374
|
+
appId,
|
|
1440
1375
|
// appId is string | undefined, UUID is string alias
|
|
1376
|
+
isSuperAdmin
|
|
1377
|
+
// Pass super admin status for conditional filtering
|
|
1441
1378
|
);
|
|
1442
1379
|
secureClientCache.set(cacheKey, secureClient);
|
|
1443
1380
|
if (secureClientCache.size > MAX_CACHE_SIZE) {
|
|
@@ -1454,11 +1391,13 @@ function useSecureSupabase(baseClient) {
|
|
|
1454
1391
|
}
|
|
1455
1392
|
return baseClient || authSupabase || null;
|
|
1456
1393
|
}, [
|
|
1457
|
-
|
|
1394
|
+
resolvedScope?.organisationId,
|
|
1395
|
+
resolvedScope?.eventId,
|
|
1396
|
+
resolvedScope?.appId,
|
|
1458
1397
|
selectedEvent?.event_id,
|
|
1459
1398
|
user?.id,
|
|
1460
1399
|
eventLoading,
|
|
1461
|
-
|
|
1400
|
+
isSuperAdmin,
|
|
1462
1401
|
baseClient,
|
|
1463
1402
|
authSupabase
|
|
1464
1403
|
]);
|
|
@@ -1469,8 +1408,6 @@ export {
|
|
|
1469
1408
|
createSecureClient,
|
|
1470
1409
|
fromSupabaseClient,
|
|
1471
1410
|
useRBAC,
|
|
1472
|
-
createScopeFromEvent,
|
|
1473
|
-
useResolvedScope,
|
|
1474
1411
|
scopeEqual,
|
|
1475
1412
|
usePermissions,
|
|
1476
1413
|
useCan,
|
|
@@ -1483,4 +1420,4 @@ export {
|
|
|
1483
1420
|
useRoleManagement,
|
|
1484
1421
|
useSecureSupabase
|
|
1485
1422
|
};
|
|
1486
|
-
//# sourceMappingURL=chunk-
|
|
1423
|
+
//# sourceMappingURL=chunk-HQVPB5MZ.js.map
|