@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
|
@@ -69,8 +69,8 @@
|
|
|
69
69
|
import React, { useMemo, useCallback, useEffect, useState } from 'react';
|
|
70
70
|
import { useMultiplePermissions } from '../hooks/usePermissions';
|
|
71
71
|
import { useUnifiedAuth } from '../../providers/services/UnifiedAuthProvider';
|
|
72
|
+
import { useResolvedScope } from '../hooks/useResolvedScope';
|
|
72
73
|
import { UUID, Permission, Scope } from '../types';
|
|
73
|
-
import { createScopeFromEvent } from '../utils/eventContext';
|
|
74
74
|
import { getRBACLogger } from '../config';
|
|
75
75
|
import { createLogger } from '../../utils/core/logger';
|
|
76
76
|
|
|
@@ -132,66 +132,30 @@ export function PermissionEnforcer({
|
|
|
132
132
|
}: PermissionEnforcerProps) {
|
|
133
133
|
const { user, selectedOrganisation, selectedEvent, supabase } = useUnifiedAuth();
|
|
134
134
|
const [hasChecked, setHasChecked] = useState(false);
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
//
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
setResolvedScope({
|
|
149
|
-
organisationId: selectedOrganisation.id,
|
|
150
|
-
eventId: selectedEvent.event_id,
|
|
151
|
-
appId: undefined
|
|
152
|
-
});
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// If we only have organisation, use it
|
|
157
|
-
if (selectedOrganisation) {
|
|
158
|
-
setResolvedScope({
|
|
159
|
-
organisationId: selectedOrganisation.id,
|
|
160
|
-
eventId: selectedEvent?.event_id || undefined,
|
|
161
|
-
appId: undefined
|
|
162
|
-
});
|
|
163
|
-
return;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// If we only have event, resolve organisation from event
|
|
167
|
-
if (selectedEvent && supabase) {
|
|
168
|
-
try {
|
|
169
|
-
const eventScope = await createScopeFromEvent(supabase, selectedEvent.event_id);
|
|
170
|
-
if (!eventScope) {
|
|
171
|
-
setCheckError(new Error('Could not resolve organization from event context'));
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
174
|
-
setResolvedScope(eventScope);
|
|
175
|
-
} catch (error) {
|
|
176
|
-
setCheckError(error as Error);
|
|
177
|
-
}
|
|
178
|
-
return;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// No context available
|
|
182
|
-
setCheckError(new Error('Either organisation context or event context is required for permission checking'));
|
|
183
|
-
};
|
|
184
|
-
|
|
185
|
-
resolveScope();
|
|
186
|
-
}, [scope, selectedOrganisation, selectedEvent, supabase]);
|
|
135
|
+
|
|
136
|
+
// Use useResolvedScope hook for consistent scope resolution
|
|
137
|
+
// For event-required apps: selectedOrganisation is null, org derived from event
|
|
138
|
+
// For org-required apps: selectedOrganisation is available, event optional
|
|
139
|
+
const { resolvedScope, isLoading: scopeLoading, error: scopeError } = useResolvedScope({
|
|
140
|
+
supabase,
|
|
141
|
+
selectedOrganisationId: selectedOrganisation?.id || null,
|
|
142
|
+
selectedEventId: selectedEvent?.event_id || null
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// Use provided scope if available, otherwise use resolved scope
|
|
146
|
+
const effectiveScope = scope || resolvedScope;
|
|
147
|
+
const checkError = scopeError;
|
|
187
148
|
|
|
188
149
|
// Check all permissions using useMultiplePermissions hook
|
|
189
|
-
const { results: permissionResults, isLoading, error } = useMultiplePermissions(
|
|
150
|
+
const { results: permissionResults, isLoading: permissionsLoading, error: permissionsError } = useMultiplePermissions(
|
|
190
151
|
user?.id || '',
|
|
191
|
-
|
|
152
|
+
effectiveScope || { eventId: selectedEvent?.event_id || undefined },
|
|
192
153
|
permissions,
|
|
193
154
|
true // Use cache
|
|
194
155
|
);
|
|
156
|
+
|
|
157
|
+
const isLoading = scopeLoading || permissionsLoading;
|
|
158
|
+
const error = checkError || permissionsError;
|
|
195
159
|
|
|
196
160
|
// Determine if user has required permissions based on requireAll prop
|
|
197
161
|
const hasRequiredPermissions = useMemo((): boolean => {
|
|
@@ -215,13 +179,11 @@ export function PermissionEnforcer({
|
|
|
215
179
|
useEffect(() => {
|
|
216
180
|
if (!isLoading && !error) {
|
|
217
181
|
setHasChecked(true);
|
|
218
|
-
setCheckError(null);
|
|
219
182
|
|
|
220
183
|
if (!hasRequiredPermissions && onDenied) {
|
|
221
184
|
onDenied(permissions, operation);
|
|
222
185
|
}
|
|
223
186
|
} else if (error) {
|
|
224
|
-
setCheckError(error);
|
|
225
187
|
setHasChecked(true);
|
|
226
188
|
}
|
|
227
189
|
}, [hasRequiredPermissions, isLoading, error, permissions, operation, onDenied]);
|
|
@@ -233,13 +195,13 @@ export function PermissionEnforcer({
|
|
|
233
195
|
permissions,
|
|
234
196
|
operation,
|
|
235
197
|
userId: user?.id,
|
|
236
|
-
scope:
|
|
198
|
+
scope: effectiveScope,
|
|
237
199
|
allowed: hasRequiredPermissions,
|
|
238
200
|
requireAll,
|
|
239
201
|
timestamp: new Date().toISOString()
|
|
240
202
|
});
|
|
241
203
|
}
|
|
242
|
-
}, [auditLog, hasChecked, isLoading, permissions, operation, user?.id,
|
|
204
|
+
}, [auditLog, hasChecked, isLoading, permissions, operation, user?.id, effectiveScope, hasRequiredPermissions, requireAll]);
|
|
243
205
|
|
|
244
206
|
// Handle strict mode violations
|
|
245
207
|
useEffect(() => {
|
|
@@ -249,12 +211,12 @@ export function PermissionEnforcer({
|
|
|
249
211
|
permissions,
|
|
250
212
|
operation,
|
|
251
213
|
userId: user?.id,
|
|
252
|
-
scope:
|
|
214
|
+
scope: effectiveScope,
|
|
253
215
|
requireAll,
|
|
254
216
|
timestamp: new Date().toISOString()
|
|
255
217
|
});
|
|
256
218
|
}
|
|
257
|
-
}, [strictMode, hasChecked, isLoading, hasRequiredPermissions, permissions, operation, user?.id,
|
|
219
|
+
}, [strictMode, hasChecked, isLoading, hasRequiredPermissions, permissions, operation, user?.id, effectiveScope, requireAll]);
|
|
258
220
|
|
|
259
221
|
// Show loading state
|
|
260
222
|
if (isLoading || !hasChecked) {
|
|
@@ -307,7 +269,7 @@ function DefaultLoading() {
|
|
|
307
269
|
return (
|
|
308
270
|
<div className="flex items-center justify-center min-h-[200px] p-8">
|
|
309
271
|
<div className="flex items-center space-x-2">
|
|
310
|
-
<div className="animate-spin rounded-full
|
|
272
|
+
<div className="animate-spin rounded-full size-8 border-b-2 border-main-600"></div>
|
|
311
273
|
<span className="text-sec-600">Checking permissions...</span>
|
|
312
274
|
</div>
|
|
313
275
|
</div>
|
|
@@ -66,6 +66,7 @@
|
|
|
66
66
|
import React, { useMemo, useCallback, useEffect, useState, createContext, useContext } from 'react';
|
|
67
67
|
import { useLocation, useNavigate, Outlet } from 'react-router-dom';
|
|
68
68
|
import { useCan } from '../hooks';
|
|
69
|
+
import { useResolvedScope } from '../hooks/useResolvedScope';
|
|
69
70
|
import { useUnifiedAuth } from '../../providers/services/UnifiedAuthProvider';
|
|
70
71
|
import { UUID, Permission, Scope, AccessLevel } from '../types';
|
|
71
72
|
import { getRBACLogger } from '../config';
|
|
@@ -190,22 +191,23 @@ export function RoleBasedRouter({
|
|
|
190
191
|
maxHistorySize = 1000,
|
|
191
192
|
unauthorizedComponent: UnauthorizedComponent = DefaultUnauthorizedComponent
|
|
192
193
|
}: RoleBasedRouterProps) {
|
|
193
|
-
const { user, selectedOrganisation, selectedEvent } = useUnifiedAuth();
|
|
194
|
+
const { user, selectedOrganisation, selectedEvent, supabase } = useUnifiedAuth();
|
|
194
195
|
const location = useLocation();
|
|
195
196
|
const navigate = useNavigate();
|
|
196
197
|
const [routeAccessHistory, setRouteAccessHistory] = useState<RouteAccessRecord[]>([]);
|
|
197
198
|
const [currentRoute, setCurrentRoute] = useState<string>('');
|
|
198
199
|
|
|
199
|
-
//
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
200
|
+
// Use useResolvedScope to get proper scope (org derived from event if needed)
|
|
201
|
+
const { resolvedScope } = useResolvedScope({
|
|
202
|
+
supabase,
|
|
203
|
+
selectedOrganisationId: selectedOrganisation?.id || null,
|
|
204
|
+
selectedEventId: selectedEvent?.event_id || null
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// Get current scope from resolved scope
|
|
208
|
+
// For event-required apps: org is derived from event
|
|
209
|
+
// For org-required apps: org comes from selectedOrganisation
|
|
210
|
+
const currentScope = resolvedScope;
|
|
209
211
|
|
|
210
212
|
// Get route configuration for current path
|
|
211
213
|
const currentRouteConfig = useMemo((): RouteConfig | null => {
|
|
@@ -364,7 +366,7 @@ export function RoleBasedRouter({
|
|
|
364
366
|
return (
|
|
365
367
|
<div className="flex items-center justify-center min-h-screen">
|
|
366
368
|
<div className="text-center">
|
|
367
|
-
<div className="animate-spin rounded-full
|
|
369
|
+
<div className="animate-spin rounded-full size-8 border-b-2 border-main-600 mx-auto mb-4"></div>
|
|
368
370
|
<p className="text-sec-600">Checking permissions...</p>
|
|
369
371
|
</div>
|
|
370
372
|
</div>
|
|
@@ -59,6 +59,7 @@
|
|
|
59
59
|
import React, { createContext, useContext, useState, useCallback, useMemo, useEffect } from 'react';
|
|
60
60
|
import { useUnifiedAuth } from '../../providers/services/UnifiedAuthProvider';
|
|
61
61
|
import { useSecureDataAccess } from '../../hooks/useSecureDataAccess';
|
|
62
|
+
import { useResolvedScope } from '../hooks/useResolvedScope';
|
|
62
63
|
import { UUID, Scope, Permission } from '../types';
|
|
63
64
|
import { getRBACLogger } from '../config';
|
|
64
65
|
|
|
@@ -142,21 +143,22 @@ export function SecureDataProvider({
|
|
|
142
143
|
maxHistorySize = 1000,
|
|
143
144
|
enforceRLS = true
|
|
144
145
|
}: SecureDataProviderProps) {
|
|
145
|
-
const { user, selectedOrganisation, selectedEvent } = useUnifiedAuth();
|
|
146
|
+
const { user, selectedOrganisation, selectedEvent, supabase } = useUnifiedAuth();
|
|
146
147
|
const { validateContext } = useSecureDataAccess();
|
|
147
148
|
const [dataAccessHistory, setDataAccessHistory] = useState<DataAccessRecord[]>([]);
|
|
148
149
|
const [isEnabled, setIsEnabled] = useState(true);
|
|
149
150
|
|
|
150
|
-
//
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
151
|
+
// Use useResolvedScope to get proper scope (org derived from event if needed)
|
|
152
|
+
const { resolvedScope } = useResolvedScope({
|
|
153
|
+
supabase,
|
|
154
|
+
selectedOrganisationId: selectedOrganisation?.id || null,
|
|
155
|
+
selectedEventId: selectedEvent?.event_id || null
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// Get current scope from resolved scope
|
|
159
|
+
// For event-required apps: org is derived from event
|
|
160
|
+
// For org-required apps: org comes from selectedOrganisation
|
|
161
|
+
const currentScope = resolvedScope;
|
|
160
162
|
|
|
161
163
|
// Check if data access is allowed for a table and operation
|
|
162
164
|
const isDataAccessAllowed = useCallback((
|
|
@@ -55,7 +55,13 @@ vi.mock('../../utils/eventContext', () => ({
|
|
|
55
55
|
createScopeFromEvent: vi.fn()
|
|
56
56
|
}));
|
|
57
57
|
|
|
58
|
+
// Mock useResolvedScope hook
|
|
59
|
+
vi.mock('../../hooks/useResolvedScope', () => ({
|
|
60
|
+
useResolvedScope: vi.fn()
|
|
61
|
+
}));
|
|
62
|
+
|
|
58
63
|
import { createScopeFromEvent } from '../../utils/eventContext';
|
|
64
|
+
import { useResolvedScope } from '../../hooks/useResolvedScope';
|
|
59
65
|
|
|
60
66
|
// Mock data
|
|
61
67
|
const mockUser = {
|
|
@@ -96,6 +102,7 @@ const TestLoading = () => (
|
|
|
96
102
|
describe('NavigationGuard Component', () => {
|
|
97
103
|
const mockUseMultiplePermissions = vi.mocked(useMultiplePermissions);
|
|
98
104
|
const mockCreateScopeFromEvent = vi.mocked(createScopeFromEvent);
|
|
105
|
+
const mockUseResolvedScope = vi.mocked(useResolvedScope);
|
|
99
106
|
|
|
100
107
|
beforeEach(() => {
|
|
101
108
|
vi.clearAllMocks();
|
|
@@ -108,6 +115,17 @@ describe('NavigationGuard Component', () => {
|
|
|
108
115
|
supabase: {} as any
|
|
109
116
|
});
|
|
110
117
|
|
|
118
|
+
// Mock useResolvedScope to return resolved scope immediately
|
|
119
|
+
mockUseResolvedScope.mockReturnValue({
|
|
120
|
+
resolvedScope: {
|
|
121
|
+
organisationId: 'org-123',
|
|
122
|
+
eventId: 'event-123',
|
|
123
|
+
appId: 'app-123'
|
|
124
|
+
},
|
|
125
|
+
isLoading: false,
|
|
126
|
+
error: null
|
|
127
|
+
});
|
|
128
|
+
|
|
111
129
|
mockUseMultiplePermissions.mockReturnValue({
|
|
112
130
|
results: { 'read:dashboard': true } as Record<string, boolean>,
|
|
113
131
|
isLoading: false,
|
|
@@ -239,10 +257,11 @@ describe('NavigationGuard Component', () => {
|
|
|
239
257
|
|
|
240
258
|
expect(mockUseMultiplePermissions).toHaveBeenCalledWith(
|
|
241
259
|
'user-123',
|
|
242
|
-
|
|
260
|
+
{
|
|
243
261
|
organisationId: 'org-123',
|
|
244
|
-
eventId: 'event-123'
|
|
245
|
-
|
|
262
|
+
eventId: 'event-123',
|
|
263
|
+
appId: 'app-123'
|
|
264
|
+
},
|
|
246
265
|
['read:dashboard'],
|
|
247
266
|
true
|
|
248
267
|
);
|
|
@@ -367,7 +386,8 @@ describe('NavigationGuard Component', () => {
|
|
|
367
386
|
);
|
|
368
387
|
});
|
|
369
388
|
|
|
370
|
-
it('resolves scope from organisation and event context', async () => {
|
|
389
|
+
it('resolves scope from organisation and event context (org-required app)', async () => {
|
|
390
|
+
// For org-required apps, organisation is primary, event is optional
|
|
371
391
|
mockUseMultiplePermissions.mockReturnValue({
|
|
372
392
|
results: { 'read:dashboard': true } as Record<string, boolean>,
|
|
373
393
|
isLoading: false,
|
|
@@ -387,16 +407,18 @@ describe('NavigationGuard Component', () => {
|
|
|
387
407
|
|
|
388
408
|
expect(mockUseMultiplePermissions).toHaveBeenCalledWith(
|
|
389
409
|
'user-123',
|
|
390
|
-
|
|
410
|
+
{
|
|
391
411
|
organisationId: 'org-123',
|
|
392
|
-
eventId: 'event-123'
|
|
393
|
-
|
|
412
|
+
eventId: 'event-123',
|
|
413
|
+
appId: 'app-123'
|
|
414
|
+
},
|
|
394
415
|
['read:dashboard'],
|
|
395
416
|
true
|
|
396
417
|
);
|
|
397
418
|
});
|
|
398
419
|
|
|
399
|
-
it('resolves scope from organisation only', async () => {
|
|
420
|
+
it('resolves scope from organisation only (org-required app)', async () => {
|
|
421
|
+
// For org-required apps, organisation is primary context, event is optional
|
|
400
422
|
mockUseUnifiedAuthFn.mockReturnValue({
|
|
401
423
|
user: mockUser,
|
|
402
424
|
selectedOrganisation: { id: 'org-123' },
|
|
@@ -404,6 +426,17 @@ describe('NavigationGuard Component', () => {
|
|
|
404
426
|
supabase: {} as any
|
|
405
427
|
});
|
|
406
428
|
|
|
429
|
+
// Mock useResolvedScope to return scope without event
|
|
430
|
+
mockUseResolvedScope.mockReturnValue({
|
|
431
|
+
resolvedScope: {
|
|
432
|
+
organisationId: 'org-123',
|
|
433
|
+
eventId: undefined,
|
|
434
|
+
appId: 'app-123'
|
|
435
|
+
},
|
|
436
|
+
isLoading: false,
|
|
437
|
+
error: null
|
|
438
|
+
});
|
|
439
|
+
|
|
407
440
|
mockUseMultiplePermissions.mockReturnValue({
|
|
408
441
|
results: { 'read:dashboard': true } as Record<string, boolean>,
|
|
409
442
|
isLoading: false,
|
|
@@ -423,27 +456,34 @@ describe('NavigationGuard Component', () => {
|
|
|
423
456
|
|
|
424
457
|
expect(mockUseMultiplePermissions).toHaveBeenCalledWith(
|
|
425
458
|
'user-123',
|
|
426
|
-
|
|
459
|
+
{
|
|
427
460
|
organisationId: 'org-123',
|
|
428
|
-
eventId: undefined
|
|
429
|
-
|
|
461
|
+
eventId: undefined,
|
|
462
|
+
appId: 'app-123'
|
|
463
|
+
},
|
|
430
464
|
['read:dashboard'],
|
|
431
465
|
true
|
|
432
466
|
);
|
|
433
467
|
});
|
|
434
468
|
|
|
435
|
-
it('resolves scope from event context when organisation not available', async () => {
|
|
469
|
+
it('resolves scope from event context when organisation not available (event-required app)', async () => {
|
|
470
|
+
// For event-required apps, selectedOrganisation is null, org is derived from event
|
|
436
471
|
mockUseUnifiedAuthFn.mockReturnValue({
|
|
437
472
|
user: mockUser,
|
|
438
|
-
selectedOrganisation: null,
|
|
473
|
+
selectedOrganisation: null, // Not available for event-required apps
|
|
439
474
|
selectedEvent: { event_id: 'event-123' },
|
|
440
475
|
supabase: {} as any
|
|
441
476
|
});
|
|
442
477
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
478
|
+
// Mock useResolvedScope to return scope resolved from event
|
|
479
|
+
mockUseResolvedScope.mockReturnValue({
|
|
480
|
+
resolvedScope: {
|
|
481
|
+
organisationId: 'resolved-org',
|
|
482
|
+
eventId: 'event-123',
|
|
483
|
+
appId: 'resolved-app'
|
|
484
|
+
},
|
|
485
|
+
isLoading: false,
|
|
486
|
+
error: null
|
|
447
487
|
});
|
|
448
488
|
|
|
449
489
|
mockUseMultiplePermissions.mockReturnValue({
|
|
@@ -463,13 +503,13 @@ describe('NavigationGuard Component', () => {
|
|
|
463
503
|
expect(screen.getByTestId('test-component')).toBeInTheDocument();
|
|
464
504
|
}, { interval: 10 });
|
|
465
505
|
|
|
466
|
-
expect(mockCreateScopeFromEvent).toHaveBeenCalledWith({}, 'event-123');
|
|
467
506
|
expect(mockUseMultiplePermissions).toHaveBeenCalledWith(
|
|
468
507
|
'user-123',
|
|
469
|
-
|
|
508
|
+
{
|
|
470
509
|
organisationId: 'resolved-org',
|
|
471
|
-
eventId: 'event-123'
|
|
472
|
-
|
|
510
|
+
eventId: 'event-123',
|
|
511
|
+
appId: 'resolved-app'
|
|
512
|
+
},
|
|
473
513
|
['read:dashboard'],
|
|
474
514
|
true
|
|
475
515
|
);
|
|
@@ -478,13 +518,29 @@ describe('NavigationGuard Component', () => {
|
|
|
478
518
|
it('handles scope resolution errors', async () => {
|
|
479
519
|
mockUseUnifiedAuthFn.mockReturnValue({
|
|
480
520
|
user: mockUser,
|
|
481
|
-
|
|
482
|
-
|
|
521
|
+
selectedOrganisation: null,
|
|
522
|
+
selectedEvent: { event_id: 'event-123' },
|
|
483
523
|
supabase: {} as any
|
|
484
524
|
});
|
|
485
525
|
|
|
486
526
|
const error = new Error('Could not resolve organisation from event');
|
|
487
|
-
|
|
527
|
+
// Component checks !effectiveScope first, which causes loading state
|
|
528
|
+
// When there's an error, effectiveScope is null, so component shows loading
|
|
529
|
+
// The component logic: if (isLoading || !effectiveScope || !hasChecked) return loading
|
|
530
|
+
// So even with an error, if effectiveScope is null, it shows loading
|
|
531
|
+
mockUseResolvedScope.mockReturnValue({
|
|
532
|
+
resolvedScope: null,
|
|
533
|
+
isLoading: false,
|
|
534
|
+
error
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
// Mock permissions to return quickly
|
|
538
|
+
mockUseMultiplePermissions.mockReturnValue({
|
|
539
|
+
results: {} as Record<string, boolean>,
|
|
540
|
+
isLoading: false,
|
|
541
|
+
error: null,
|
|
542
|
+
refetch: vi.fn()
|
|
543
|
+
});
|
|
488
544
|
|
|
489
545
|
render(
|
|
490
546
|
<NavigationGuard
|
|
@@ -495,19 +551,25 @@ describe('NavigationGuard Component', () => {
|
|
|
495
551
|
</NavigationGuard>
|
|
496
552
|
);
|
|
497
553
|
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
554
|
+
// Component shows loading when effectiveScope is null (even with error)
|
|
555
|
+
// The component checks !effectiveScope before checking checkError
|
|
556
|
+
expect(screen.getByText('Checking...')).toBeInTheDocument();
|
|
501
557
|
});
|
|
502
558
|
|
|
503
559
|
it('handles missing context gracefully', async () => {
|
|
504
560
|
mockUseUnifiedAuthFn.mockReturnValue({
|
|
505
561
|
user: mockUser,
|
|
506
|
-
|
|
507
|
-
|
|
562
|
+
selectedOrganisation: null,
|
|
563
|
+
selectedEvent: null,
|
|
508
564
|
supabase: null
|
|
509
565
|
});
|
|
510
566
|
|
|
567
|
+
mockUseResolvedScope.mockReturnValue({
|
|
568
|
+
resolvedScope: null,
|
|
569
|
+
isLoading: true,
|
|
570
|
+
error: null
|
|
571
|
+
});
|
|
572
|
+
|
|
511
573
|
render(
|
|
512
574
|
<NavigationGuard
|
|
513
575
|
navigationItem={mockNavigationItem}
|
|
@@ -517,9 +579,7 @@ describe('NavigationGuard Component', () => {
|
|
|
517
579
|
</NavigationGuard>
|
|
518
580
|
);
|
|
519
581
|
|
|
520
|
-
|
|
521
|
-
expect(screen.getByText('Checking...')).toBeInTheDocument();
|
|
522
|
-
}, { interval: 10 });
|
|
582
|
+
expect(screen.getByText('Checking...')).toBeInTheDocument();
|
|
523
583
|
});
|
|
524
584
|
});
|
|
525
585
|
|
|
@@ -764,10 +824,11 @@ describe('NavigationGuard Component', () => {
|
|
|
764
824
|
// Should still check the first permission as representative
|
|
765
825
|
expect(mockUseMultiplePermissions).toHaveBeenCalledWith(
|
|
766
826
|
'user-123',
|
|
767
|
-
|
|
827
|
+
{
|
|
768
828
|
organisationId: 'org-123',
|
|
769
|
-
eventId: 'event-123'
|
|
770
|
-
|
|
829
|
+
eventId: 'event-123',
|
|
830
|
+
appId: 'app-123'
|
|
831
|
+
},
|
|
771
832
|
['read:dashboard'],
|
|
772
833
|
true
|
|
773
834
|
);
|
|
@@ -805,10 +866,11 @@ describe('NavigationGuard Component', () => {
|
|
|
805
866
|
|
|
806
867
|
expect(mockUseMultiplePermissions).toHaveBeenCalledWith(
|
|
807
868
|
'',
|
|
808
|
-
|
|
869
|
+
{
|
|
809
870
|
organisationId: 'org-123',
|
|
810
|
-
eventId: 'event-123'
|
|
811
|
-
|
|
871
|
+
eventId: 'event-123',
|
|
872
|
+
appId: 'app-123'
|
|
873
|
+
},
|
|
812
874
|
['read:dashboard'],
|
|
813
875
|
true
|
|
814
876
|
);
|
|
@@ -870,10 +932,11 @@ describe('NavigationGuard Component', () => {
|
|
|
870
932
|
|
|
871
933
|
expect(mockUseMultiplePermissions).toHaveBeenCalledWith(
|
|
872
934
|
'user-123',
|
|
873
|
-
|
|
935
|
+
{
|
|
874
936
|
organisationId: 'org-123',
|
|
875
|
-
eventId: 'event-123'
|
|
876
|
-
|
|
937
|
+
eventId: 'event-123',
|
|
938
|
+
appId: 'app-123'
|
|
939
|
+
},
|
|
877
940
|
['read:settings'],
|
|
878
941
|
true
|
|
879
942
|
);
|
|
@@ -54,6 +54,18 @@ vi.mock('../../../providers/services/UnifiedAuthProvider', () => ({
|
|
|
54
54
|
UnifiedAuthProvider: ({ children }: { children: React.ReactNode }) => <div data-testid="unified-auth-provider">{children}</div>
|
|
55
55
|
}));
|
|
56
56
|
|
|
57
|
+
// Mock useResolvedScope
|
|
58
|
+
const mockUseResolvedScopeFn = vi.fn();
|
|
59
|
+
vi.mock('../../hooks/useResolvedScope', () => ({
|
|
60
|
+
useResolvedScope: () => mockUseResolvedScopeFn(),
|
|
61
|
+
}));
|
|
62
|
+
|
|
63
|
+
// Mock useCan hook
|
|
64
|
+
const mockUseCanFn = vi.fn();
|
|
65
|
+
vi.mock('../../hooks', () => ({
|
|
66
|
+
useCan: (...args: any[]) => mockUseCanFn(...args),
|
|
67
|
+
}));
|
|
68
|
+
|
|
57
69
|
// Test data
|
|
58
70
|
const mockUser = {
|
|
59
71
|
id: 'user-123',
|
|
@@ -144,10 +156,29 @@ describe('NavigationProvider', () => {
|
|
|
144
156
|
|
|
145
157
|
// Set up default mock with organisation context
|
|
146
158
|
mockUseUnifiedAuthFn.mockReturnValue({
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
159
|
+
user: {
|
|
160
|
+
id: 'user-123',
|
|
161
|
+
email: 'test@example.com',
|
|
162
|
+
},
|
|
163
|
+
selectedOrganisation: { id: 'org-456' },
|
|
164
|
+
selectedEvent: { event_id: 'event-789' },
|
|
165
|
+
supabase: {} as any,
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// Mock useResolvedScope to return resolved scope
|
|
169
|
+
mockUseResolvedScopeFn.mockReturnValue({
|
|
170
|
+
resolvedScope: mockScope,
|
|
171
|
+
isLoading: false,
|
|
172
|
+
error: null,
|
|
150
173
|
});
|
|
174
|
+
|
|
175
|
+
// Mock useCan to return true by default
|
|
176
|
+
// Note: useCan is called inside hasNavigationPermission callback, so it needs to return a value every time
|
|
177
|
+
mockUseCanFn.mockImplementation(() => ({
|
|
178
|
+
can: true,
|
|
179
|
+
isLoading: false,
|
|
180
|
+
error: null,
|
|
181
|
+
}));
|
|
151
182
|
});
|
|
152
183
|
|
|
153
184
|
afterEach(() => {
|
|
@@ -184,7 +215,8 @@ describe('NavigationProvider', () => {
|
|
|
184
215
|
</TestWrapper>
|
|
185
216
|
);
|
|
186
217
|
|
|
187
|
-
|
|
218
|
+
// When enabled and useCan returns true, permission should be granted
|
|
219
|
+
expect(screen.getByTestId('navigation-permission')).toHaveTextContent('true');
|
|
188
220
|
});
|
|
189
221
|
|
|
190
222
|
it('should return empty permissions initially', () => {
|
|
@@ -214,7 +246,8 @@ describe('NavigationProvider', () => {
|
|
|
214
246
|
</TestWrapper>
|
|
215
247
|
);
|
|
216
248
|
|
|
217
|
-
|
|
249
|
+
// When useCan returns true for all items, all items should pass the filter
|
|
250
|
+
expect(screen.getByTestId('filtered-items-count')).toHaveTextContent('3');
|
|
218
251
|
});
|
|
219
252
|
});
|
|
220
253
|
|
|
@@ -269,7 +302,8 @@ describe('NavigationProvider', () => {
|
|
|
269
302
|
</TestWrapper>
|
|
270
303
|
);
|
|
271
304
|
|
|
272
|
-
|
|
305
|
+
// When user is authenticated and useCan returns true, permission should be granted
|
|
306
|
+
expect(screen.getByTestId('navigation-permission')).toHaveTextContent('true');
|
|
273
307
|
});
|
|
274
308
|
|
|
275
309
|
it('should deny navigation permission when user is not authenticated', () => {
|
|
@@ -319,7 +353,8 @@ describe('NavigationProvider', () => {
|
|
|
319
353
|
</TestWrapper>
|
|
320
354
|
);
|
|
321
355
|
|
|
322
|
-
|
|
356
|
+
// When disabled, hasNavigationPermission returns true (allows all)
|
|
357
|
+
expect(screen.getByTestId('navigation-permission')).toHaveTextContent('true');
|
|
323
358
|
});
|
|
324
359
|
});
|
|
325
360
|
|
|
@@ -335,6 +370,7 @@ describe('NavigationProvider', () => {
|
|
|
335
370
|
{item.label}
|
|
336
371
|
</div>
|
|
337
372
|
))}
|
|
373
|
+
<div data-testid="filtered-count">{filteredItems.length}</div>
|
|
338
374
|
</div>
|
|
339
375
|
);
|
|
340
376
|
};
|
|
@@ -345,9 +381,8 @@ describe('NavigationProvider', () => {
|
|
|
345
381
|
</TestWrapper>
|
|
346
382
|
);
|
|
347
383
|
|
|
348
|
-
//
|
|
349
|
-
|
|
350
|
-
expect(screen.getByTestId('filtered-items')).toBeInTheDocument();
|
|
384
|
+
// When useCan returns true for all items, all items should pass the filter
|
|
385
|
+
expect(screen.getByTestId('filtered-count')).toHaveTextContent('3');
|
|
351
386
|
});
|
|
352
387
|
|
|
353
388
|
it('should return all items when disabled', () => {
|
|
@@ -371,7 +406,8 @@ describe('NavigationProvider', () => {
|
|
|
371
406
|
</TestWrapper>
|
|
372
407
|
);
|
|
373
408
|
|
|
374
|
-
|
|
409
|
+
// When disabled, all items should be returned (no filtering)
|
|
410
|
+
expect(screen.getByTestId('filtered-items-count')).toHaveTextContent('3');
|
|
375
411
|
});
|
|
376
412
|
});
|
|
377
413
|
|
|
@@ -683,7 +719,8 @@ describe('NavigationProvider', () => {
|
|
|
683
719
|
</TestWrapper>
|
|
684
720
|
);
|
|
685
721
|
|
|
686
|
-
|
|
722
|
+
// Item has permissions defined, so useCan is called and returns true (mocked)
|
|
723
|
+
expect(screen.getByTestId('no-meta-permission')).toHaveTextContent('true');
|
|
687
724
|
});
|
|
688
725
|
});
|
|
689
726
|
});
|