@jmruthers/pace-core 0.5.189 → 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-B8HaLe69.d.ts → PublicPageProvider-C4uxosp6.d.ts} +83 -24
- 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-UCQSRW7Z.js → chunk-NIU6J6OX.js} +425 -378
- 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-MX64ZF6I.js → chunk-STYK4OH2.js} +11 -11
- package/dist/chunk-STYK4OH2.js.map +1 -0
- package/dist/{chunk-YGPFYGA6.js → chunk-VVBAW5A5.js} +822 -498
- 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 -4
- package/dist/components.js +19 -19
- 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 -11
- package/dist/index.js +79 -69
- 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 +1 -1
- 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 +151 -92
- package/docs/api-reference/components.md +15 -7
- 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 -3
- 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 +95 -43
- package/src/components/Avatar/Avatar.tsx +16 -16
- 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.tsx +3 -3
- 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/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-F2IMUDXZ.js.map +0 -1
- package/dist/chunk-HEHYGYOX.js.map +0 -1
- package/dist/chunk-IM4QE42D.js.map +0 -1
- package/dist/chunk-MX64ZF6I.js.map +0 -1
- package/dist/chunk-SAUPYVLF.js.map +0 -1
- package/dist/chunk-THRPYOFK.js.map +0 -1
- package/dist/chunk-UCQSRW7Z.js.map +0 -1
- package/dist/chunk-VGZZXKBR.js.map +0 -1
- package/dist/chunk-YGPFYGA6.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
|
@@ -12,7 +12,8 @@ import {
|
|
|
12
12
|
UUID,
|
|
13
13
|
Scope,
|
|
14
14
|
Permission,
|
|
15
|
-
PermissionMap
|
|
15
|
+
PermissionMap,
|
|
16
|
+
OrganisationContextRequiredError
|
|
16
17
|
} from '../types';
|
|
17
18
|
import { AccessLevel as AccessLevelType } from '../types';
|
|
18
19
|
import {
|
|
@@ -23,6 +24,7 @@ import {
|
|
|
23
24
|
} from '../api';
|
|
24
25
|
import { getRBACLogger } from '../config';
|
|
25
26
|
import { scopeEqual } from '../utils/deep-equal';
|
|
27
|
+
import { useAppConfig } from '../../hooks/useAppConfig';
|
|
26
28
|
|
|
27
29
|
/**
|
|
28
30
|
* Hook to get user's permissions in a scope
|
|
@@ -97,28 +99,30 @@ export function usePermissions(
|
|
|
97
99
|
if (error?.message === 'Organisation context is required for permission checks') {
|
|
98
100
|
setError(null);
|
|
99
101
|
}
|
|
100
|
-
}, [userId, organisationId, error]);
|
|
101
|
-
|
|
102
|
-
// CRITICAL: Detect parameter changes
|
|
103
|
-
//
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
102
|
+
}, [userId, organisationId, error, orgId]);
|
|
103
|
+
|
|
104
|
+
// CRITICAL: Detect parameter changes and trigger fetch
|
|
105
|
+
// Moved to useEffect to prevent render-time state updates that could cause render loops
|
|
106
|
+
useEffect(() => {
|
|
107
|
+
const paramsChanged =
|
|
108
|
+
prevValuesRef.current.userId !== userId ||
|
|
109
|
+
prevValuesRef.current.organisationId !== organisationId ||
|
|
110
|
+
prevValuesRef.current.eventId !== eventId ||
|
|
111
|
+
prevValuesRef.current.appId !== appId;
|
|
112
|
+
|
|
113
|
+
if (paramsChanged) {
|
|
114
|
+
// Only log significant changes (appId changes are most important)
|
|
115
|
+
if (prevValuesRef.current.appId !== appId) {
|
|
116
|
+
logger.debug('[usePermissions] AppId changed - triggering fetch', {
|
|
117
|
+
prevAppId: prevValuesRef.current.appId,
|
|
118
|
+
newAppId: appId
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
prevValuesRef.current = { userId, organisationId, eventId, appId };
|
|
122
|
+
// Increment counter to force fetch useEffect to run
|
|
123
|
+
setFetchTrigger(prev => prev + 1);
|
|
117
124
|
}
|
|
118
|
-
|
|
119
|
-
// Increment counter to force useEffect to run
|
|
120
|
-
setFetchTrigger(prev => prev + 1);
|
|
121
|
-
}
|
|
125
|
+
}, [userId, organisationId, eventId, appId, logger]);
|
|
122
126
|
|
|
123
127
|
useEffect(() => {
|
|
124
128
|
const fetchPermissions = async () => {
|
|
@@ -283,6 +287,7 @@ export function usePermissions(
|
|
|
283
287
|
* @param permission - Permission to check
|
|
284
288
|
* @param pageId - Optional page ID
|
|
285
289
|
* @param useCache - Whether to use cached results
|
|
290
|
+
* @param appName - Optional app name (for PORTAL/ADMIN special case)
|
|
286
291
|
* @returns Permission check state and methods
|
|
287
292
|
*
|
|
288
293
|
* @example
|
|
@@ -302,7 +307,8 @@ export function useCan(
|
|
|
302
307
|
scope: Scope,
|
|
303
308
|
permission: Permission,
|
|
304
309
|
pageId?: UUID,
|
|
305
|
-
useCache: boolean = true
|
|
310
|
+
useCache: boolean = true,
|
|
311
|
+
appName?: string
|
|
306
312
|
) {
|
|
307
313
|
const [can, setCan] = useState<boolean>(false);
|
|
308
314
|
const [isLoading, setIsLoading] = useState(true);
|
|
@@ -315,8 +321,12 @@ export function useCan(
|
|
|
315
321
|
const appId = isValidScope ? scope.appId : undefined;
|
|
316
322
|
|
|
317
323
|
// Add timeout for missing organisation context (3 seconds)
|
|
324
|
+
// Only apply timeout for resource-level permissions, not page-level (which can handle null orgs)
|
|
318
325
|
useEffect(() => {
|
|
319
|
-
|
|
326
|
+
const isPagePermission = permission.includes(':page.') || !!pageId;
|
|
327
|
+
const requiresOrgId = !isPagePermission;
|
|
328
|
+
|
|
329
|
+
if (requiresOrgId && (!isValidScope || !organisationId || organisationId === null || (typeof organisationId === 'string' && organisationId.trim() === ''))) {
|
|
320
330
|
const timeoutId = setTimeout(() => {
|
|
321
331
|
setError(new Error('Organisation context is required for permission checks'));
|
|
322
332
|
setIsLoading(false);
|
|
@@ -329,7 +339,7 @@ export function useCan(
|
|
|
329
339
|
if (error?.message === 'Organisation context is required for permission checks') {
|
|
330
340
|
setError(null);
|
|
331
341
|
}
|
|
332
|
-
}, [isValidScope, organisationId, error]);
|
|
342
|
+
}, [isValidScope, organisationId, error, permission, pageId]);
|
|
333
343
|
|
|
334
344
|
// Use refs to track the last values to prevent unnecessary re-runs
|
|
335
345
|
const lastUserIdRef = useRef<UUID | null>(null);
|
|
@@ -388,30 +398,50 @@ export function useCan(
|
|
|
388
398
|
return;
|
|
389
399
|
}
|
|
390
400
|
|
|
391
|
-
//
|
|
392
|
-
//
|
|
393
|
-
|
|
401
|
+
// For page-level permissions, allow undefined/null organisationId (database function handles it)
|
|
402
|
+
// For resource-level permissions, organisationId is required
|
|
403
|
+
const isPagePermission = permission.includes(':page.') || !!pageId;
|
|
404
|
+
const requiresOrgId = !isPagePermission;
|
|
405
|
+
|
|
406
|
+
// Check if pageId is a pageName (not a UUID) - if so, we need appId to resolve it
|
|
407
|
+
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);
|
|
408
|
+
const needsAppIdForPageName = isPagePermission && isPageName;
|
|
409
|
+
|
|
410
|
+
// Don't check permissions if scope is invalid and orgId is required
|
|
411
|
+
// Wait for organisation context to resolve (unless it's a page permission that can handle null orgs)
|
|
412
|
+
if (requiresOrgId && (!organisationId || organisationId === null || (typeof organisationId === 'string' && organisationId.trim() === ''))) {
|
|
394
413
|
setIsLoading(true);
|
|
395
414
|
setCan(false);
|
|
396
415
|
setError(null);
|
|
397
416
|
// Timeout is handled in separate useEffect (Phase 1.4)
|
|
398
417
|
return;
|
|
399
418
|
}
|
|
419
|
+
|
|
420
|
+
// For page-level permissions with pageName (not UUID), we need appId to resolve the pageName to pageId
|
|
421
|
+
// Wait for appId to be available before checking permissions
|
|
422
|
+
if (needsAppIdForPageName && (!appId || appId === null || (typeof appId === 'string' && appId.trim() === ''))) {
|
|
423
|
+
setIsLoading(true);
|
|
424
|
+
setCan(false);
|
|
425
|
+
setError(null);
|
|
426
|
+
// Will re-run when appId becomes available (via scope change detection)
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
400
429
|
|
|
401
430
|
try {
|
|
402
431
|
setIsLoading(true);
|
|
403
432
|
setError(null);
|
|
404
433
|
|
|
405
434
|
// Create a valid scope object for the API call
|
|
435
|
+
// For page-level permissions, organisationId can be undefined (database handles it)
|
|
406
436
|
const validScope: Scope = {
|
|
407
|
-
organisationId,
|
|
437
|
+
...(organisationId ? { organisationId } : {}),
|
|
408
438
|
...(eventId ? { eventId } : {}),
|
|
409
439
|
...(appId ? { appId } : {})
|
|
410
440
|
};
|
|
411
441
|
|
|
412
442
|
const result = useCache
|
|
413
|
-
? await isPermittedCached({ userId, scope: validScope, permission, pageId })
|
|
414
|
-
: await isPermitted({ userId, scope: validScope, permission, pageId });
|
|
443
|
+
? await isPermittedCached({ userId, scope: validScope, permission, pageId }, undefined, appName)
|
|
444
|
+
: await isPermitted({ userId, scope: validScope, permission, pageId }, undefined, appName);
|
|
415
445
|
|
|
416
446
|
setCan(result);
|
|
417
447
|
} catch (err) {
|
|
@@ -426,7 +456,7 @@ export function useCan(
|
|
|
426
456
|
|
|
427
457
|
checkPermission();
|
|
428
458
|
}
|
|
429
|
-
}, [userId, stableScope, permission, pageId, useCache]);
|
|
459
|
+
}, [userId, stableScope, permission, pageId, useCache, appName]);
|
|
430
460
|
|
|
431
461
|
const refetch = useCallback(async () => {
|
|
432
462
|
if (!userId) {
|
|
@@ -443,8 +473,13 @@ export function useCan(
|
|
|
443
473
|
return;
|
|
444
474
|
}
|
|
445
475
|
|
|
446
|
-
//
|
|
447
|
-
|
|
476
|
+
// For page-level permissions, allow undefined/null organisationId (database function handles it)
|
|
477
|
+
// For resource-level permissions, organisationId is required
|
|
478
|
+
const isPagePermission = permission.includes(':page.') || !!pageId;
|
|
479
|
+
const requiresOrgId = !isPagePermission;
|
|
480
|
+
|
|
481
|
+
// Don't check permissions if scope is invalid and orgId is required
|
|
482
|
+
if (requiresOrgId && (!organisationId || organisationId === null || (typeof organisationId === 'string' && organisationId.trim() === ''))) {
|
|
448
483
|
setCan(false);
|
|
449
484
|
setIsLoading(true);
|
|
450
485
|
setError(null);
|
|
@@ -456,15 +491,16 @@ export function useCan(
|
|
|
456
491
|
setError(null);
|
|
457
492
|
|
|
458
493
|
// Create a valid scope object for the API call
|
|
494
|
+
// For page-level permissions, organisationId can be undefined (database handles it)
|
|
459
495
|
const validScope: Scope = {
|
|
460
|
-
organisationId,
|
|
496
|
+
...(organisationId ? { organisationId } : {}),
|
|
461
497
|
...(eventId ? { eventId } : {}),
|
|
462
498
|
...(appId ? { appId } : {})
|
|
463
499
|
};
|
|
464
500
|
|
|
465
501
|
const result = useCache
|
|
466
|
-
? await isPermittedCached({ userId, scope: validScope, permission, pageId })
|
|
467
|
-
: await isPermitted({ userId, scope: validScope, permission, pageId });
|
|
502
|
+
? await isPermittedCached({ userId, scope: validScope, permission, pageId }, undefined, appName)
|
|
503
|
+
: await isPermitted({ userId, scope: validScope, permission, pageId }, undefined, appName);
|
|
468
504
|
|
|
469
505
|
setCan(result);
|
|
470
506
|
} catch (err) {
|
|
@@ -473,7 +509,7 @@ export function useCan(
|
|
|
473
509
|
} finally {
|
|
474
510
|
setIsLoading(false);
|
|
475
511
|
}
|
|
476
|
-
}, [userId, isValidScope, organisationId, eventId, appId, permission, pageId, useCache]);
|
|
512
|
+
}, [userId, isValidScope, organisationId, eventId, appId, permission, pageId, useCache, appName]);
|
|
477
513
|
|
|
478
514
|
// Memoize the return object to prevent unnecessary re-renders
|
|
479
515
|
return useMemo(() => ({
|
|
@@ -518,6 +554,15 @@ export function useAccessLevel(userId: UUID, scope: Scope): {
|
|
|
518
554
|
const [isLoading, setIsLoading] = useState(true);
|
|
519
555
|
const [error, setError] = useState<Error | null>(null);
|
|
520
556
|
|
|
557
|
+
// Get appName from context if available (safely handles missing context)
|
|
558
|
+
let appName: string | undefined;
|
|
559
|
+
try {
|
|
560
|
+
const { appName: contextAppName } = useAppConfig();
|
|
561
|
+
appName = contextAppName;
|
|
562
|
+
} catch {
|
|
563
|
+
// Not available, will use undefined
|
|
564
|
+
}
|
|
565
|
+
|
|
521
566
|
const fetchAccessLevel = useCallback(async () => {
|
|
522
567
|
if (!userId) {
|
|
523
568
|
setAccessLevel('viewer');
|
|
@@ -529,15 +574,37 @@ export function useAccessLevel(userId: UUID, scope: Scope): {
|
|
|
529
574
|
setIsLoading(true);
|
|
530
575
|
setError(null);
|
|
531
576
|
|
|
532
|
-
|
|
577
|
+
// Check super admin status first - super admins bypass context requirements
|
|
578
|
+
// This allows super admins to check their access level without organisation context
|
|
579
|
+
const { isSuperAdmin: checkSuperAdmin } = await import('../api');
|
|
580
|
+
const isSuperAdminUser = await checkSuperAdmin(userId);
|
|
581
|
+
|
|
582
|
+
if (isSuperAdminUser) {
|
|
583
|
+
setAccessLevel('super');
|
|
584
|
+
setIsLoading(false);
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// Early validation: check if scope has required context
|
|
589
|
+
// PORTAL/ADMIN apps allow both contexts to be optional
|
|
590
|
+
if (appName !== 'PORTAL' && appName !== 'ADMIN' && !scope.organisationId && !scope.eventId) {
|
|
591
|
+
const orgError = new OrganisationContextRequiredError();
|
|
592
|
+
setError(orgError);
|
|
593
|
+
setAccessLevel('viewer');
|
|
594
|
+
setIsLoading(false);
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
const level = await getAccessLevel({ userId, scope }, null, appName);
|
|
533
599
|
setAccessLevel(level);
|
|
534
600
|
} catch (err) {
|
|
535
|
-
|
|
601
|
+
const error = err instanceof Error ? err : new Error('Failed to fetch access level');
|
|
602
|
+
setError(error);
|
|
536
603
|
setAccessLevel('viewer');
|
|
537
604
|
} finally {
|
|
538
605
|
setIsLoading(false);
|
|
539
606
|
}
|
|
540
|
-
}, [userId, scope.organisationId, scope.eventId, scope.appId]);
|
|
607
|
+
}, [userId, scope.organisationId, scope.eventId, scope.appId, appName]);
|
|
541
608
|
|
|
542
609
|
useEffect(() => {
|
|
543
610
|
fetchAccessLevel();
|
|
@@ -108,10 +108,18 @@ describe('useRBAC', () => {
|
|
|
108
108
|
|
|
109
109
|
expect(mockResolveAppContext).toHaveBeenCalledWith({ userId: 'user-1', appName: 'test-app' });
|
|
110
110
|
expect(mockGetPermissionMap).toHaveBeenCalledWith(
|
|
111
|
-
|
|
111
|
+
{
|
|
112
112
|
userId: 'user-1',
|
|
113
|
-
scope:
|
|
114
|
-
|
|
113
|
+
scope: {
|
|
114
|
+
organisationId: 'org-1',
|
|
115
|
+
eventId: undefined,
|
|
116
|
+
appId: 'app-123'
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
requires_event: false,
|
|
121
|
+
},
|
|
122
|
+
'test-app'
|
|
115
123
|
);
|
|
116
124
|
});
|
|
117
125
|
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
getRoleContext,
|
|
22
22
|
} from '../api';
|
|
23
23
|
import { getRBACLogger } from '../config';
|
|
24
|
+
import { ContextValidator } from '../utils/contextValidator';
|
|
24
25
|
import type {
|
|
25
26
|
UserRBACContext,
|
|
26
27
|
GlobalRole,
|
|
@@ -55,8 +56,10 @@ export function useRBAC(pageId?: string): UserRBACContext {
|
|
|
55
56
|
const {
|
|
56
57
|
user,
|
|
57
58
|
session,
|
|
59
|
+
supabase,
|
|
58
60
|
appName,
|
|
59
61
|
appConfig,
|
|
62
|
+
appId: contextAppId,
|
|
60
63
|
selectedOrganisation,
|
|
61
64
|
isContextReady: orgContextReady,
|
|
62
65
|
organisationLoading: orgLoading,
|
|
@@ -64,11 +67,6 @@ export function useRBAC(pageId?: string): UserRBACContext {
|
|
|
64
67
|
eventLoading
|
|
65
68
|
} = useUnifiedAuth();
|
|
66
69
|
|
|
67
|
-
// Check if app requires event context
|
|
68
|
-
// IMPORTANT: If appConfig is null initially, default to true (safer for event-based apps)
|
|
69
|
-
// This prevents premature loading when appConfig hasn't loaded yet
|
|
70
|
-
const requiresEvent = appConfig?.requires_event ?? (appConfig === null ? true : false);
|
|
71
|
-
|
|
72
70
|
// Removed excessive logging - hook initialization logged only on first mount or significant changes
|
|
73
71
|
|
|
74
72
|
const [globalRole, setGlobalRole] = useState<GlobalRole | null>(null);
|
|
@@ -95,55 +93,79 @@ export function useRBAC(pageId?: string): UserRBACContext {
|
|
|
95
93
|
return;
|
|
96
94
|
}
|
|
97
95
|
|
|
98
|
-
//
|
|
99
|
-
//
|
|
100
|
-
|
|
96
|
+
// Build initial scope from available context
|
|
97
|
+
// For event-required apps: use organisation_id from selectedEvent if available (faster than deriving)
|
|
98
|
+
// For org-required apps: use selectedOrganisation.id
|
|
99
|
+
const initialScope: Scope = {
|
|
100
|
+
organisationId: appConfig?.requires_event
|
|
101
|
+
? (selectedEvent?.organisation_id || selectedOrganisation?.id)
|
|
102
|
+
: selectedOrganisation?.id,
|
|
103
|
+
eventId: selectedEvent?.event_id || undefined,
|
|
104
|
+
appId: undefined
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// Check if context is ready using ContextValidator
|
|
108
|
+
const contextReady = ContextValidator.isContextReady(
|
|
109
|
+
initialScope,
|
|
110
|
+
appConfig,
|
|
111
|
+
appName,
|
|
112
|
+
!!selectedEvent,
|
|
113
|
+
!!selectedOrganisation
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
// PORTAL/ADMIN special case: context is always ready
|
|
117
|
+
if (appName !== 'PORTAL' && appName !== 'ADMIN' && !contextReady) {
|
|
118
|
+
// Wait for appropriate context based on app config
|
|
101
119
|
setIsLoading(true);
|
|
102
120
|
return;
|
|
103
121
|
}
|
|
104
122
|
|
|
105
|
-
// For event-based apps, wait for event context to be ready before loading RBAC
|
|
106
|
-
// This prevents NetworkError when RPC calls are made before event context is available
|
|
107
|
-
if (requiresEvent) {
|
|
108
|
-
if (eventLoading || !selectedEvent) {
|
|
109
|
-
// Event context not ready yet - don't load RBAC yet
|
|
110
|
-
// This prevents premature RPC calls that can cause NetworkError
|
|
111
|
-
// Set loading state so React knows we're waiting
|
|
112
|
-
setIsLoading(true);
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
123
|
setIsLoading(true);
|
|
118
124
|
setError(null);
|
|
119
125
|
|
|
120
126
|
// Only log at debug level - loading RBAC context is normal operation
|
|
121
|
-
// Changed from warn to debug to reduce console noise
|
|
122
127
|
logger.debug('[useRBAC] Loading RBAC context', {
|
|
123
128
|
appName,
|
|
124
|
-
|
|
129
|
+
appConfig,
|
|
125
130
|
hasSelectedEvent: !!selectedEvent,
|
|
126
131
|
selectedEventId: selectedEvent?.event_id,
|
|
127
132
|
organisationId: selectedOrganisation?.id
|
|
128
133
|
});
|
|
129
134
|
|
|
130
135
|
try {
|
|
131
|
-
let appId: UUID | undefined;
|
|
132
|
-
|
|
136
|
+
let appId: UUID | undefined = contextAppId; // Use contextAppId as default (already resolved in UnifiedAuthProvider)
|
|
137
|
+
|
|
138
|
+
if (appName && !appId) {
|
|
133
139
|
// Wrap RPC call in try-catch to handle NetworkError gracefully
|
|
134
140
|
try {
|
|
135
141
|
const resolved = await resolveAppContext({ userId: user.id as UUID, appName });
|
|
136
|
-
if (
|
|
142
|
+
// For PORTAL/ADMIN apps, allow access even if hasAccess is false (users can view their own profile, super admins have global access)
|
|
143
|
+
if (!resolved) {
|
|
144
|
+
if (appName === 'PORTAL' || appName === 'ADMIN') {
|
|
145
|
+
// For PORTAL/ADMIN, try to get appId directly from database
|
|
146
|
+
logger.debug(`[useRBAC] ${appName} app context not resolved, attempting direct lookup`);
|
|
147
|
+
try {
|
|
148
|
+
const { getAppConfigByName } = await import('../api');
|
|
149
|
+
const config = await getAppConfigByName(appName);
|
|
150
|
+
// We can't get appId from config, but that's OK - use contextAppId or proceed without
|
|
151
|
+
logger.debug(`[useRBAC] ${appName} app - proceeding without appId for page-level permissions`);
|
|
152
|
+
} catch (err) {
|
|
153
|
+
logger.debug(`[useRBAC] ${appName} app - proceeding without appId for page-level permissions`);
|
|
154
|
+
}
|
|
155
|
+
} else {
|
|
156
|
+
throw new Error(`User does not have access to app "${appName}"`);
|
|
157
|
+
}
|
|
158
|
+
} else if (!resolved.hasAccess && appName !== 'PORTAL' && appName !== 'ADMIN') {
|
|
137
159
|
throw new Error(`User does not have access to app "${appName}"`);
|
|
160
|
+
} else {
|
|
161
|
+
appId = resolved.appId;
|
|
138
162
|
}
|
|
139
|
-
appId = resolved.appId;
|
|
140
163
|
} catch (rpcError: any) {
|
|
141
164
|
// Handle NetworkError - might be due to timing issue
|
|
142
165
|
if (rpcError?.message?.includes('NetworkError') || rpcError?.message?.includes('fetch')) {
|
|
143
166
|
logger.warn('[useRBAC] NetworkError resolving app context - may be timing issue, will retry when context is ready', {
|
|
144
167
|
appName,
|
|
145
168
|
error: rpcError.message,
|
|
146
|
-
requiresEvent,
|
|
147
169
|
eventLoading,
|
|
148
170
|
hasSelectedEvent: !!selectedEvent
|
|
149
171
|
});
|
|
@@ -151,23 +173,44 @@ export function useRBAC(pageId?: string): UserRBACContext {
|
|
|
151
173
|
setIsLoading(false);
|
|
152
174
|
return;
|
|
153
175
|
}
|
|
154
|
-
//
|
|
155
|
-
|
|
176
|
+
// For PORTAL/ADMIN, allow proceeding without app access check (for profile pages, super admin access)
|
|
177
|
+
if (appName === 'PORTAL' || appName === 'ADMIN') {
|
|
178
|
+
logger.debug(`[useRBAC] ${appName} app - allowing access despite app context resolution failure`);
|
|
179
|
+
// appId will use contextAppId or be undefined, which is OK for PORTAL/ADMIN page-level permissions
|
|
180
|
+
} else {
|
|
181
|
+
// Re-throw other errors for non-PORTAL apps
|
|
182
|
+
throw rpcError;
|
|
183
|
+
}
|
|
156
184
|
}
|
|
157
185
|
}
|
|
158
186
|
|
|
187
|
+
// Update scope with appId (use contextAppId as fallback for PORTAL)
|
|
159
188
|
const scope: Scope = {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
appId,
|
|
189
|
+
...initialScope,
|
|
190
|
+
appId: appId || contextAppId,
|
|
163
191
|
};
|
|
164
192
|
|
|
165
|
-
|
|
193
|
+
// Resolve required context using ContextValidator
|
|
194
|
+
// Pass supabase client to allow deriving organisation from event for event-required apps
|
|
195
|
+
const validation = await ContextValidator.resolveRequiredContext(
|
|
196
|
+
scope,
|
|
197
|
+
appConfig,
|
|
198
|
+
appName,
|
|
199
|
+
supabase || null
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
if (!validation.isValid || !validation.resolvedScope) {
|
|
203
|
+
throw validation.error || new Error('Context validation failed');
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const resolvedScope = validation.resolvedScope;
|
|
207
|
+
setCurrentScope(resolvedScope);
|
|
166
208
|
|
|
209
|
+
// Pass appConfig and appName to API calls for context validation
|
|
167
210
|
const [map, roleContext, accessLevel] = await Promise.all([
|
|
168
|
-
getPermissionMap({ userId: user.id as UUID, scope }),
|
|
169
|
-
getRoleContext({ userId: user.id as UUID, scope }),
|
|
170
|
-
getAccessLevel({ userId: user.id as UUID, scope }),
|
|
211
|
+
getPermissionMap({ userId: user.id as UUID, scope: resolvedScope }, appConfig, appName),
|
|
212
|
+
getRoleContext({ userId: user.id as UUID, scope: resolvedScope }, appConfig, appName),
|
|
213
|
+
getAccessLevel({ userId: user.id as UUID, scope: resolvedScope }, appConfig, appName),
|
|
171
214
|
]);
|
|
172
215
|
|
|
173
216
|
setPermissionMap(map);
|
|
@@ -180,8 +223,8 @@ export function useRBAC(pageId?: string): UserRBACContext {
|
|
|
180
223
|
if (permissionCount === 0) {
|
|
181
224
|
logger.warn('[useRBAC] RBAC context loaded but returned 0 permissions', {
|
|
182
225
|
appName,
|
|
183
|
-
organisationId:
|
|
184
|
-
eventId:
|
|
226
|
+
organisationId: resolvedScope.organisationId,
|
|
227
|
+
eventId: resolvedScope.eventId
|
|
185
228
|
});
|
|
186
229
|
}
|
|
187
230
|
} catch (err) {
|
|
@@ -192,7 +235,7 @@ export function useRBAC(pageId?: string): UserRBACContext {
|
|
|
192
235
|
} finally {
|
|
193
236
|
setIsLoading(false);
|
|
194
237
|
}
|
|
195
|
-
}, [appName, logger, resetState, selectedEvent?.event_id, selectedOrganisation?.id, session, user,
|
|
238
|
+
}, [appName, logger, resetState, selectedEvent?.event_id, selectedOrganisation?.id, session, user, eventLoading, appConfig, orgContextReady, orgLoading]);
|
|
196
239
|
|
|
197
240
|
const hasGlobalPermission = useCallback(
|
|
198
241
|
(permission: string): boolean => {
|
|
@@ -221,7 +264,7 @@ export function useRBAC(pageId?: string): UserRBACContext {
|
|
|
221
264
|
|
|
222
265
|
useEffect(() => {
|
|
223
266
|
loadRBACContext();
|
|
224
|
-
}, [loadRBACContext, appName,
|
|
267
|
+
}, [loadRBACContext, appName, appConfig, eventLoading, selectedEvent?.event_id, user, session, selectedOrganisation?.id, orgContextReady, orgLoading]);
|
|
225
268
|
|
|
226
269
|
return {
|
|
227
270
|
user,
|