@jmruthers/pace-core 0.5.188 → 0.5.190
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/core-usage-manifest.json +0 -4
- package/dist/{AuthService-B-cd2MA4.d.ts → AuthService-CbP_utw2.d.ts} +7 -3
- package/dist/{DataTable-GUFUNZ3N.js → DataTable-ON3IXISJ.js} +8 -8
- package/dist/{PublicPageProvider-DrLDztHt.d.ts → PublicPageProvider-C4uxosp6.d.ts} +129 -40
- package/dist/{UnifiedAuthProvider-BG0AL5eE.d.ts → UnifiedAuthProvider-BYA9qB-o.d.ts} +4 -3
- package/dist/{UnifiedAuthProvider-643PUAIM.js → UnifiedAuthProvider-X5NXANVI.js} +4 -2
- package/dist/{api-YP7XD5L6.js → api-I6UCQ5S6.js} +4 -2
- package/dist/{chunk-DDM4CCYT.js → chunk-4QYC5L4K.js} +60 -35
- package/dist/chunk-4QYC5L4K.js.map +1 -0
- package/dist/{chunk-IM4QE42D.js → chunk-73HSNNOQ.js} +141 -326
- package/dist/chunk-73HSNNOQ.js.map +1 -0
- package/dist/{chunk-YHCN776L.js → chunk-DZWK57KZ.js} +2 -75
- package/dist/chunk-DZWK57KZ.js.map +1 -0
- package/dist/{chunk-3GOZZZYH.js → chunk-HQVPB5MZ.js} +238 -301
- package/dist/chunk-HQVPB5MZ.js.map +1 -0
- package/dist/{chunk-THRPYOFK.js → chunk-HW3OVDUF.js} +5 -5
- package/dist/chunk-HW3OVDUF.js.map +1 -0
- package/dist/{chunk-F2IMUDXZ.js → chunk-I7PSE6JW.js} +75 -2
- package/dist/chunk-I7PSE6JW.js.map +1 -0
- package/dist/{chunk-VGZZXKBR.js → chunk-J2XXC7R5.js} +280 -52
- package/dist/chunk-J2XXC7R5.js.map +1 -0
- package/dist/{chunk-UNOTYLQF.js → chunk-NIU6J6OX.js} +772 -725
- package/dist/chunk-NIU6J6OX.js.map +1 -0
- package/dist/{chunk-HESYZWZW.js → chunk-QWWZ5CAQ.js} +2 -2
- package/dist/{chunk-HEHYGYOX.js → chunk-RUYZKXOD.js} +401 -46
- package/dist/chunk-RUYZKXOD.js.map +1 -0
- package/dist/{chunk-2UUZZJFT.js → chunk-SDMHPX3X.js} +176 -160
- package/dist/{chunk-2UUZZJFT.js.map → chunk-SDMHPX3X.js.map} +1 -1
- package/dist/{chunk-IPCH26AG.js → chunk-STYK4OH2.js} +11 -11
- package/dist/chunk-STYK4OH2.js.map +1 -0
- package/dist/{chunk-EFCLXK7F.js → chunk-VVBAW5A5.js} +4201 -3809
- package/dist/chunk-VVBAW5A5.js.map +1 -0
- package/dist/chunk-Y4BUBBHD.js +614 -0
- package/dist/chunk-Y4BUBBHD.js.map +1 -0
- package/dist/{chunk-SAUPYVLF.js → chunk-ZSAAAMVR.js} +1 -1
- package/dist/chunk-ZSAAAMVR.js.map +1 -0
- package/dist/components.d.ts +3 -5
- package/dist/components.js +19 -23
- package/dist/components.js.map +1 -1
- package/dist/eslint-rules/pace-core-compliance.cjs +0 -2
- package/dist/{file-reference-D037xOFK.d.ts → file-reference-BavO2eQj.d.ts} +13 -10
- package/dist/hooks.d.ts +10 -5
- package/dist/hooks.js +14 -8
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +13 -12
- package/dist/index.js +79 -73
- package/dist/index.js.map +1 -1
- package/dist/providers.d.ts +3 -3
- package/dist/providers.js +3 -1
- package/dist/rbac/index.d.ts +76 -12
- package/dist/rbac/index.js +12 -9
- package/dist/types.d.ts +1 -1
- package/dist/types.js +1 -1
- package/dist/{usePublicRouteParams-CTDELQ7H.d.ts → usePublicRouteParams-DxIDS4bC.d.ts} +16 -9
- package/dist/utils.js +16 -16
- package/docs/README.md +2 -2
- package/docs/api/classes/ColumnFactory.md +1 -1
- package/docs/api/classes/ErrorBoundary.md +1 -1
- package/docs/api/classes/InvalidScopeError.md +2 -2
- package/docs/api/classes/Logger.md +1 -1
- package/docs/api/classes/MissingUserContextError.md +2 -2
- package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
- package/docs/api/classes/PermissionDeniedError.md +1 -1
- package/docs/api/classes/RBACAuditManager.md +1 -1
- package/docs/api/classes/RBACCache.md +1 -1
- package/docs/api/classes/RBACEngine.md +4 -4
- package/docs/api/classes/RBACError.md +1 -1
- package/docs/api/classes/RBACNotInitializedError.md +2 -2
- package/docs/api/classes/SecureSupabaseClient.md +21 -16
- package/docs/api/classes/StorageUtils.md +7 -4
- package/docs/api/enums/FileCategory.md +1 -1
- package/docs/api/enums/LogLevel.md +1 -1
- package/docs/api/enums/RBACErrorCode.md +1 -1
- package/docs/api/enums/RPCFunction.md +1 -1
- package/docs/api/interfaces/AddressFieldProps.md +1 -1
- package/docs/api/interfaces/AddressFieldRef.md +1 -1
- package/docs/api/interfaces/AggregateConfig.md +1 -1
- package/docs/api/interfaces/AutocompleteOptions.md +1 -1
- package/docs/api/interfaces/AvatarProps.md +128 -0
- package/docs/api/interfaces/BadgeProps.md +1 -1
- package/docs/api/interfaces/ButtonProps.md +1 -1
- package/docs/api/interfaces/CalendarProps.md +20 -6
- package/docs/api/interfaces/CardProps.md +1 -1
- package/docs/api/interfaces/ColorPalette.md +1 -1
- package/docs/api/interfaces/ColorShade.md +1 -1
- package/docs/api/interfaces/ComplianceResult.md +1 -1
- package/docs/api/interfaces/DataAccessRecord.md +9 -9
- package/docs/api/interfaces/DataRecord.md +1 -1
- package/docs/api/interfaces/DataTableAction.md +1 -1
- package/docs/api/interfaces/DataTableColumn.md +1 -1
- package/docs/api/interfaces/DataTableProps.md +1 -1
- package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
- package/docs/api/interfaces/DatabaseComplianceResult.md +1 -1
- package/docs/api/interfaces/DatabaseIssue.md +1 -1
- package/docs/api/interfaces/EmptyStateConfig.md +1 -1
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
- package/docs/api/interfaces/EventAppRoleData.md +1 -1
- package/docs/api/interfaces/ExportColumn.md +1 -1
- package/docs/api/interfaces/ExportOptions.md +1 -1
- package/docs/api/interfaces/FileDisplayProps.md +62 -16
- package/docs/api/interfaces/FileMetadata.md +1 -1
- package/docs/api/interfaces/FileReference.md +2 -2
- package/docs/api/interfaces/FileSizeLimits.md +1 -1
- package/docs/api/interfaces/FileUploadOptions.md +26 -12
- package/docs/api/interfaces/FileUploadProps.md +30 -19
- package/docs/api/interfaces/FooterProps.md +1 -1
- package/docs/api/interfaces/FormFieldProps.md +1 -1
- package/docs/api/interfaces/FormProps.md +1 -1
- package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
- package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
- package/docs/api/interfaces/InputProps.md +1 -1
- package/docs/api/interfaces/LabelProps.md +1 -1
- package/docs/api/interfaces/LoggerConfig.md +1 -1
- package/docs/api/interfaces/LoginFormProps.md +1 -1
- package/docs/api/interfaces/NavigationAccessRecord.md +10 -10
- package/docs/api/interfaces/NavigationContextType.md +9 -9
- package/docs/api/interfaces/NavigationGuardProps.md +1 -1
- package/docs/api/interfaces/NavigationItem.md +1 -1
- package/docs/api/interfaces/NavigationMenuProps.md +1 -1
- package/docs/api/interfaces/NavigationProviderProps.md +7 -7
- package/docs/api/interfaces/Organisation.md +1 -1
- package/docs/api/interfaces/OrganisationContextType.md +1 -1
- package/docs/api/interfaces/OrganisationMembership.md +1 -1
- package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
- package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
- package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
- package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
- package/docs/api/interfaces/PageAccessRecord.md +8 -8
- package/docs/api/interfaces/PagePermissionContextType.md +8 -8
- package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
- package/docs/api/interfaces/PagePermissionProviderProps.md +7 -7
- package/docs/api/interfaces/PaletteData.md +1 -1
- package/docs/api/interfaces/ParsedAddress.md +1 -1
- package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
- package/docs/api/interfaces/ProgressProps.md +3 -11
- package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
- package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
- package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
- package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
- package/docs/api/interfaces/QuickFix.md +1 -1
- package/docs/api/interfaces/RBACAccessValidateParams.md +1 -1
- package/docs/api/interfaces/RBACAccessValidateResult.md +1 -1
- package/docs/api/interfaces/RBACAuditLogParams.md +1 -1
- package/docs/api/interfaces/RBACAuditLogResult.md +1 -1
- package/docs/api/interfaces/RBACConfig.md +1 -1
- package/docs/api/interfaces/RBACContext.md +1 -1
- package/docs/api/interfaces/RBACLogger.md +1 -1
- package/docs/api/interfaces/RBACPageAccessCheckParams.md +1 -1
- package/docs/api/interfaces/RBACPerformanceMetrics.md +1 -1
- package/docs/api/interfaces/RBACPermissionCheckParams.md +1 -1
- package/docs/api/interfaces/RBACPermissionCheckResult.md +1 -1
- package/docs/api/interfaces/RBACPermissionsGetParams.md +1 -1
- package/docs/api/interfaces/RBACPermissionsGetResult.md +1 -1
- package/docs/api/interfaces/RBACResult.md +1 -1
- package/docs/api/interfaces/RBACRoleGrantParams.md +1 -1
- package/docs/api/interfaces/RBACRoleGrantResult.md +1 -1
- package/docs/api/interfaces/RBACRoleRevokeParams.md +1 -1
- package/docs/api/interfaces/RBACRoleRevokeResult.md +1 -1
- package/docs/api/interfaces/RBACRoleValidateParams.md +1 -1
- package/docs/api/interfaces/RBACRoleValidateResult.md +1 -1
- package/docs/api/interfaces/RBACRolesListParams.md +1 -1
- package/docs/api/interfaces/RBACRolesListResult.md +1 -1
- package/docs/api/interfaces/RBACSessionTrackParams.md +1 -1
- package/docs/api/interfaces/RBACSessionTrackResult.md +1 -1
- package/docs/api/interfaces/ResourcePermissions.md +1 -1
- package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterContextType.md +8 -8
- package/docs/api/interfaces/RoleBasedRouterProps.md +10 -10
- package/docs/api/interfaces/RoleManagementResult.md +1 -1
- package/docs/api/interfaces/RouteAccessRecord.md +10 -10
- package/docs/api/interfaces/RouteConfig.md +10 -10
- package/docs/api/interfaces/RuntimeComplianceResult.md +1 -1
- package/docs/api/interfaces/SecureDataContextType.md +9 -9
- package/docs/api/interfaces/SecureDataProviderProps.md +8 -8
- package/docs/api/interfaces/SessionRestorationLoaderProps.md +1 -1
- package/docs/api/interfaces/SetupIssue.md +1 -1
- package/docs/api/interfaces/StorageConfig.md +4 -4
- package/docs/api/interfaces/StorageFileInfo.md +7 -7
- package/docs/api/interfaces/StorageFileMetadata.md +25 -14
- package/docs/api/interfaces/StorageListOptions.md +22 -9
- package/docs/api/interfaces/StorageListResult.md +4 -4
- package/docs/api/interfaces/StorageUploadOptions.md +21 -8
- package/docs/api/interfaces/StorageUploadResult.md +6 -6
- package/docs/api/interfaces/StorageUrlOptions.md +19 -6
- package/docs/api/interfaces/StyleImport.md +1 -1
- package/docs/api/interfaces/SwitchProps.md +1 -1
- package/docs/api/interfaces/TabsContentProps.md +1 -1
- package/docs/api/interfaces/TabsListProps.md +1 -1
- package/docs/api/interfaces/TabsProps.md +1 -1
- package/docs/api/interfaces/TabsTriggerProps.md +1 -1
- package/docs/api/interfaces/TextareaProps.md +1 -1
- package/docs/api/interfaces/ToastActionElement.md +1 -1
- package/docs/api/interfaces/ToastProps.md +1 -1
- package/docs/api/interfaces/UnifiedAuthContextType.md +53 -53
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +13 -13
- package/docs/api/interfaces/UseFormDialogOptions.md +1 -1
- package/docs/api/interfaces/UseFormDialogReturn.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventLogoOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
- package/docs/api/interfaces/UsePublicFileDisplayOptions.md +1 -1
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeOptions.md +4 -4
- package/docs/api/interfaces/UseResolvedScopeReturn.md +4 -4
- package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
- package/docs/api/interfaces/UserEventAccess.md +11 -11
- package/docs/api/interfaces/UserMenuProps.md +1 -1
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +155 -135
- package/docs/api-reference/components.md +72 -29
- package/docs/api-reference/providers.md +2 -2
- package/docs/api-reference/rpc-functions.md +1 -0
- package/docs/best-practices/README.md +1 -1
- package/docs/best-practices/deployment.md +8 -8
- package/docs/getting-started/examples/README.md +2 -2
- package/docs/getting-started/installation-guide.md +4 -4
- package/docs/getting-started/quick-start.md +3 -3
- package/docs/migration/MIGRATION_GUIDE.md +3 -3
- package/docs/rbac/compliance/compliance-guide.md +2 -2
- package/docs/rbac/event-based-apps.md +2 -2
- package/docs/rbac/getting-started.md +2 -2
- package/docs/rbac/quick-start.md +2 -2
- package/docs/security/README.md +4 -4
- package/docs/standards/07-rbac-and-rls-standard.md +430 -7
- package/docs/troubleshooting/README.md +2 -2
- package/docs/troubleshooting/migration.md +3 -3
- package/package.json +1 -4
- package/scripts/check-pace-core-compliance.cjs +1 -1
- package/scripts/check-pace-core-compliance.js +1 -1
- package/src/__tests__/fixtures/supabase.ts +301 -0
- package/src/__tests__/public-recipe-view.test.ts +9 -9
- package/src/__tests__/rls-policies.test.ts +197 -61
- package/src/components/AddressField/AddressField.test.tsx +42 -0
- package/src/components/AddressField/AddressField.tsx +71 -60
- package/src/components/AddressField/README.md +1 -0
- package/src/components/Alert/Alert.test.tsx +50 -10
- package/src/components/Alert/Alert.tsx +5 -3
- package/src/components/Avatar/Avatar.test.tsx +252 -226
- package/src/components/Avatar/Avatar.tsx +179 -53
- package/src/components/Avatar/index.ts +1 -1
- package/src/components/Button/Button.test.tsx +2 -1
- package/src/components/Button/Button.tsx +3 -3
- package/src/components/Calendar/Calendar.test.tsx +53 -37
- package/src/components/Calendar/Calendar.tsx +409 -82
- package/src/components/Card/Card.test.tsx +7 -4
- package/src/components/Card/Card.tsx +3 -6
- package/src/components/Checkbox/Checkbox.tsx +2 -2
- package/src/components/DataTable/components/ActionButtons.tsx +5 -5
- package/src/components/DataTable/components/BulkOperationsDropdown.tsx +2 -2
- package/src/components/DataTable/components/ColumnFilter.tsx +1 -1
- package/src/components/DataTable/components/ColumnVisibilityDropdown.tsx +3 -3
- package/src/components/DataTable/components/DataTableBody.tsx +12 -12
- package/src/components/DataTable/components/DataTableCore.tsx +3 -3
- package/src/components/DataTable/components/DataTableToolbar.tsx +5 -5
- package/src/components/DataTable/components/DraggableColumnHeader.tsx +3 -3
- package/src/components/DataTable/components/EditableRow.tsx +2 -2
- package/src/components/DataTable/components/EmptyState.tsx +3 -3
- package/src/components/DataTable/components/GroupHeader.tsx +2 -2
- package/src/components/DataTable/components/GroupingDropdown.tsx +1 -1
- package/src/components/DataTable/components/ImportModal.tsx +4 -4
- package/src/components/DataTable/components/LoadingState.tsx +1 -1
- package/src/components/DataTable/components/PaginationControls.tsx +11 -11
- package/src/components/DataTable/components/UnifiedTableBody.tsx +9 -9
- package/src/components/DataTable/components/ViewRowModal.tsx +2 -2
- package/src/components/DataTable/components/__tests__/AccessDeniedPage.test.tsx +11 -37
- package/src/components/DataTable/components/__tests__/DataTableToolbar.test.tsx +157 -0
- package/src/components/DataTable/components/__tests__/LoadingState.test.tsx +2 -1
- package/src/components/DataTable/components/__tests__/VirtualizedDataTable.test.tsx +128 -0
- package/src/components/DataTable/core/__tests__/ActionManager.test.ts +19 -0
- package/src/components/DataTable/core/__tests__/ColumnFactory.test.ts +51 -0
- package/src/components/DataTable/core/__tests__/ColumnManager.test.ts +84 -0
- package/src/components/DataTable/core/__tests__/DataManager.test.ts +14 -0
- package/src/components/DataTable/core/__tests__/DataTableContext.test.tsx +136 -0
- package/src/components/DataTable/core/__tests__/LocalDataAdapter.test.ts +16 -0
- package/src/components/DataTable/core/__tests__/PluginRegistry.test.ts +18 -0
- package/src/components/DataTable/hooks/useDataTablePermissions.ts +28 -7
- package/src/components/DataTable/utils/__tests__/hierarchicalUtils.test.ts +30 -1
- package/src/components/DataTable/utils/hierarchicalUtils.ts +38 -10
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +8 -3
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.tsx +4 -4
- package/src/components/Dialog/Dialog.tsx +2 -2
- package/src/components/EventSelector/EventSelector.tsx +7 -7
- package/src/components/FileDisplay/FileDisplay.tsx +291 -179
- package/src/components/FileUpload/FileUpload.tsx +7 -4
- package/src/components/Header/Header.test.tsx +28 -0
- package/src/components/Header/Header.tsx +22 -9
- package/src/components/InactivityWarningModal/InactivityWarningModal.tsx +2 -2
- package/src/components/LoadingSpinner/LoadingSpinner.test.tsx +19 -14
- package/src/components/LoadingSpinner/LoadingSpinner.tsx +5 -5
- package/src/components/NavigationMenu/NavigationMenu.test.tsx +127 -1
- package/src/components/OrganisationSelector/OrganisationSelector.tsx +8 -8
- package/src/components/PaceAppLayout/PaceAppLayout.integration.test.tsx +4 -0
- package/src/components/PaceAppLayout/PaceAppLayout.performance.test.tsx +3 -0
- package/src/components/PaceAppLayout/PaceAppLayout.security.test.tsx +3 -0
- package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +16 -6
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +37 -3
- package/src/components/PaceAppLayout/test-setup.tsx +1 -0
- package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +66 -45
- package/src/components/PaceLoginPage/PaceLoginPage.tsx +6 -4
- package/src/components/Progress/Progress.test.tsx +18 -19
- package/src/components/Progress/Progress.tsx +31 -32
- package/src/components/PublicLayout/PublicLayout.test.tsx +6 -6
- package/src/components/PublicLayout/PublicPageProvider.tsx +5 -3
- package/src/components/Select/Select.tsx +5 -5
- package/src/components/Switch/Switch.test.tsx +2 -1
- package/src/components/Switch/Switch.tsx +1 -1
- package/src/components/Toast/Toast.tsx +1 -1
- package/src/components/Tooltip/Tooltip.test.tsx +8 -2
- package/src/components/UserMenu/UserMenu.test.tsx +7 -9
- package/src/components/UserMenu/UserMenu.tsx +10 -8
- package/src/components/index.ts +2 -1
- package/src/eslint-rules/pace-core-compliance.cjs +0 -2
- package/src/eslint-rules/pace-core-compliance.js +0 -2
- package/src/hooks/__tests__/hooks.integration.test.tsx +4 -1
- package/src/hooks/__tests__/useAppConfig.unit.test.ts +76 -5
- package/src/hooks/__tests__/useDataTableState.test.ts +76 -0
- package/src/hooks/__tests__/useFileUrl.unit.test.ts +25 -69
- package/src/hooks/__tests__/useFileUrlCache.test.ts +129 -0
- package/src/hooks/__tests__/usePreventTabReload.test.ts +88 -0
- package/src/hooks/__tests__/{usePublicEvent.unit.test.ts → usePublicEvent.test.ts} +28 -1
- package/src/hooks/__tests__/useQueryCache.test.ts +144 -0
- package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +58 -16
- package/src/hooks/index.ts +1 -1
- package/src/hooks/public/usePublicEvent.ts +2 -2
- package/src/hooks/public/usePublicFileDisplay.ts +173 -87
- package/src/hooks/useAppConfig.ts +24 -5
- package/src/hooks/useFileDisplay.ts +297 -34
- package/src/hooks/useFileReference.ts +56 -11
- package/src/hooks/useFileUrl.ts +1 -1
- package/src/hooks/useInactivityTracker.ts +16 -7
- package/src/hooks/usePermissionCache.test.ts +85 -8
- package/src/hooks/useQueryCache.ts +21 -0
- package/src/hooks/useSecureDataAccess.test.ts +80 -35
- package/src/hooks/useSecureDataAccess.ts +80 -37
- package/src/index.ts +2 -1
- package/src/providers/services/EventServiceProvider.tsx +37 -17
- package/src/providers/services/InactivityServiceProvider.tsx +4 -4
- package/src/providers/services/OrganisationServiceProvider.tsx +8 -1
- package/src/providers/services/UnifiedAuthProvider.tsx +115 -29
- package/src/rbac/__tests__/auth-rbac.e2e.test.tsx +451 -0
- package/src/rbac/__tests__/engine.comprehensive.test.ts +12 -0
- package/src/rbac/__tests__/rbac-engine-core-logic.test.ts +8 -0
- package/src/rbac/__tests__/rbac-engine-simplified.test.ts +4 -0
- package/src/rbac/api.ts +240 -36
- package/src/rbac/cache-invalidation.ts +21 -7
- package/src/rbac/compliance/quick-fix-suggestions.ts +1 -1
- package/src/rbac/components/NavigationGuard.tsx +23 -63
- package/src/rbac/components/NavigationProvider.test.tsx +52 -23
- package/src/rbac/components/NavigationProvider.tsx +13 -11
- package/src/rbac/components/PagePermissionGuard.tsx +77 -203
- package/src/rbac/components/PagePermissionProvider.tsx +13 -11
- package/src/rbac/components/PermissionEnforcer.tsx +24 -62
- package/src/rbac/components/RoleBasedRouter.tsx +14 -12
- package/src/rbac/components/SecureDataProvider.tsx +13 -11
- package/src/rbac/components/__tests__/NavigationGuard.test.tsx +104 -41
- package/src/rbac/components/__tests__/NavigationProvider.test.tsx +49 -12
- package/src/rbac/components/__tests__/PagePermissionGuard.race-condition.test.tsx +22 -1
- package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +161 -82
- package/src/rbac/components/__tests__/PagePermissionGuard.verification.test.tsx +22 -1
- package/src/rbac/components/__tests__/PermissionEnforcer.test.tsx +77 -30
- package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +39 -5
- package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +47 -4
- package/src/rbac/engine.ts +4 -2
- package/src/rbac/hooks/__tests__/useSecureSupabase.test.ts +144 -52
- package/src/rbac/hooks/index.ts +3 -0
- package/src/rbac/hooks/useCan.test.ts +101 -53
- package/src/rbac/hooks/usePermissions.ts +108 -41
- package/src/rbac/hooks/useRBAC.test.ts +11 -3
- package/src/rbac/hooks/useRBAC.ts +83 -40
- package/src/rbac/hooks/useResolvedScope.test.ts +189 -63
- package/src/rbac/hooks/useResolvedScope.ts +128 -70
- package/src/rbac/hooks/useSecureSupabase.ts +36 -19
- package/src/rbac/hooks/useSuperAdminBypass.ts +126 -0
- package/src/rbac/request-deduplication.ts +1 -1
- package/src/rbac/secureClient.ts +72 -12
- package/src/rbac/security.ts +29 -23
- package/src/rbac/types.ts +10 -0
- package/src/rbac/utils/__tests__/contextValidator.test.ts +150 -0
- package/src/rbac/utils/__tests__/deep-equal.test.ts +53 -0
- package/src/rbac/utils/__tests__/eventContext.test.ts +6 -1
- package/src/rbac/utils/contextValidator.ts +288 -0
- package/src/rbac/utils/eventContext.ts +48 -2
- package/src/services/EventService.ts +165 -21
- package/src/services/OrganisationService.ts +37 -2
- package/src/services/__tests__/EventService.test.ts +26 -21
- package/src/types/file-reference.ts +13 -10
- package/src/utils/app/appNameResolver.test.ts +346 -73
- package/src/utils/context/superAdminOverride.ts +58 -0
- package/src/utils/file-reference/index.ts +61 -33
- package/src/utils/google-places/googlePlacesUtils.test.ts +98 -0
- package/src/utils/google-places/loadGoogleMapsScript.test.ts +83 -0
- package/src/utils/storage/helpers.test.ts +1 -1
- package/src/utils/storage/helpers.ts +38 -19
- package/src/utils/storage/types.ts +15 -8
- package/src/utils/validation/__tests__/csrf.test.ts +105 -0
- package/src/utils/validation/__tests__/sqlInjectionProtection.test.ts +92 -0
- package/src/vite-env.d.ts +2 -2
- package/dist/chunk-3GOZZZYH.js.map +0 -1
- package/dist/chunk-DDM4CCYT.js.map +0 -1
- package/dist/chunk-E7UAOUMY.js +0 -75
- package/dist/chunk-E7UAOUMY.js.map +0 -1
- package/dist/chunk-EFCLXK7F.js.map +0 -1
- package/dist/chunk-F2IMUDXZ.js.map +0 -1
- package/dist/chunk-HEHYGYOX.js.map +0 -1
- package/dist/chunk-IM4QE42D.js.map +0 -1
- package/dist/chunk-IPCH26AG.js.map +0 -1
- package/dist/chunk-SAUPYVLF.js.map +0 -1
- package/dist/chunk-THRPYOFK.js.map +0 -1
- package/dist/chunk-UNOTYLQF.js.map +0 -1
- package/dist/chunk-VGZZXKBR.js.map +0 -1
- package/dist/chunk-YHCN776L.js.map +0 -1
- package/src/hooks/__tests__/usePermissionCache.simple.test.ts +0 -192
- package/src/hooks/__tests__/usePermissionCache.unit.test.ts +0 -741
- package/src/hooks/__tests__/usePublicEvent.simple.test.ts +0 -703
- package/src/rbac/hooks/useRBAC.simple.test.ts +0 -95
- package/src/rbac/utils/__tests__/eventContext.unit.test.ts +0 -428
- /package/dist/{DataTable-GUFUNZ3N.js.map → DataTable-ON3IXISJ.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-643PUAIM.js.map → UnifiedAuthProvider-X5NXANVI.js.map} +0 -0
- /package/dist/{api-YP7XD5L6.js.map → api-I6UCQ5S6.js.map} +0 -0
- /package/dist/{chunk-HESYZWZW.js.map → chunk-QWWZ5CAQ.js.map} +0 -0
package/src/rbac/secureClient.ts
CHANGED
|
@@ -18,6 +18,9 @@ import { OrganisationContextRequiredError } from './types';
|
|
|
18
18
|
*
|
|
19
19
|
* This client automatically injects organisation context into all requests
|
|
20
20
|
* and prevents queries that don't have the required context.
|
|
21
|
+
*
|
|
22
|
+
* Note: Callers should derive organisationId from eventId before creating this client
|
|
23
|
+
* if working with event-required apps. The client requires organisationId.
|
|
21
24
|
*/
|
|
22
25
|
export class SecureSupabaseClient {
|
|
23
26
|
private supabase: SupabaseClient<Database>;
|
|
@@ -27,19 +30,22 @@ export class SecureSupabaseClient {
|
|
|
27
30
|
private organisationId: UUID;
|
|
28
31
|
private eventId?: string;
|
|
29
32
|
private appId?: UUID;
|
|
33
|
+
private isSuperAdmin: boolean;
|
|
30
34
|
|
|
31
35
|
constructor(
|
|
32
36
|
supabaseUrl: string,
|
|
33
37
|
supabaseKey: string,
|
|
34
38
|
organisationId: UUID,
|
|
35
39
|
eventId?: string,
|
|
36
|
-
appId?: UUID
|
|
40
|
+
appId?: UUID,
|
|
41
|
+
isSuperAdmin: boolean = false
|
|
37
42
|
) {
|
|
38
43
|
this.supabaseUrl = supabaseUrl;
|
|
39
44
|
this.supabaseKey = supabaseKey;
|
|
40
45
|
this.organisationId = organisationId;
|
|
41
46
|
this.eventId = eventId;
|
|
42
47
|
this.appId = appId;
|
|
48
|
+
this.isSuperAdmin = isSuperAdmin;
|
|
43
49
|
|
|
44
50
|
// Create the base Supabase client with context headers
|
|
45
51
|
// Note: We'll override functions.invoke to exclude headers for Edge Functions
|
|
@@ -75,8 +81,11 @@ export class SecureSupabaseClient {
|
|
|
75
81
|
// Type assertion needed because table is a string but Supabase expects specific table names
|
|
76
82
|
const query = originalFrom(table as any);
|
|
77
83
|
|
|
84
|
+
// Store table name on query object so we can access it in filter methods
|
|
85
|
+
(query as any)._tableName = table;
|
|
86
|
+
|
|
78
87
|
// Inject organisation context into all queries
|
|
79
|
-
return this.injectContext(query);
|
|
88
|
+
return this.injectContext(query, table);
|
|
80
89
|
};
|
|
81
90
|
|
|
82
91
|
const originalRpc = this.supabase.rpc.bind(this.supabase);
|
|
@@ -129,7 +138,7 @@ export class SecureSupabaseClient {
|
|
|
129
138
|
/**
|
|
130
139
|
* Inject organisation context into a query
|
|
131
140
|
*/
|
|
132
|
-
private injectContext(query: any) {
|
|
141
|
+
private injectContext(query: any, tableName: string) {
|
|
133
142
|
const originalSelect = query.select.bind(query);
|
|
134
143
|
const originalInsert = query.insert.bind(query);
|
|
135
144
|
const originalUpdate = query.update.bind(query);
|
|
@@ -138,11 +147,26 @@ export class SecureSupabaseClient {
|
|
|
138
147
|
// Override select to add organisation filter
|
|
139
148
|
query.select = (columns?: string) => {
|
|
140
149
|
const result = originalSelect(columns);
|
|
141
|
-
return this.addOrganisationFilter(result);
|
|
150
|
+
return this.addOrganisationFilter(result, tableName);
|
|
142
151
|
};
|
|
143
152
|
|
|
144
153
|
// Override insert to add organisation context
|
|
145
154
|
query.insert = (values: any) => {
|
|
155
|
+
// For rbac_user_profiles, only add organisation_id if not super admin
|
|
156
|
+
// Super admins can create users in any org, non-super-admins are restricted
|
|
157
|
+
if (tableName === 'rbac_user_profiles') {
|
|
158
|
+
if (this.isSuperAdmin) {
|
|
159
|
+
// Super admin: Don't force organisation_id (can be set explicitly if needed)
|
|
160
|
+
return originalInsert(values);
|
|
161
|
+
}
|
|
162
|
+
// Non-super-admin: Add organisation_id as defense in depth
|
|
163
|
+
const contextValues = Array.isArray(values)
|
|
164
|
+
? values.map(v => ({ ...v, organisation_id: this.organisationId }))
|
|
165
|
+
: { ...values, organisation_id: this.organisationId };
|
|
166
|
+
return originalInsert(contextValues);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// For other tables, always add organisation_id
|
|
146
170
|
const contextValues = Array.isArray(values)
|
|
147
171
|
? values.map(v => ({ ...v, organisation_id: this.organisationId }))
|
|
148
172
|
: { ...values, organisation_id: this.organisationId };
|
|
@@ -153,13 +177,13 @@ export class SecureSupabaseClient {
|
|
|
153
177
|
// Override update to add organisation filter
|
|
154
178
|
query.update = (values: any) => {
|
|
155
179
|
const result = originalUpdate(values);
|
|
156
|
-
return this.addOrganisationFilter(result);
|
|
180
|
+
return this.addOrganisationFilter(result, tableName);
|
|
157
181
|
};
|
|
158
182
|
|
|
159
183
|
// Override delete to add organisation filter
|
|
160
184
|
query.delete = () => {
|
|
161
185
|
const result = originalDelete();
|
|
162
|
-
return this.addOrganisationFilter(result);
|
|
186
|
+
return this.addOrganisationFilter(result, tableName);
|
|
163
187
|
};
|
|
164
188
|
|
|
165
189
|
return query;
|
|
@@ -167,9 +191,40 @@ export class SecureSupabaseClient {
|
|
|
167
191
|
|
|
168
192
|
/**
|
|
169
193
|
* Add organisation filter to a query
|
|
194
|
+
*
|
|
195
|
+
* Defense in depth strategy:
|
|
196
|
+
* - RLS policies are the primary security layer (cannot be bypassed)
|
|
197
|
+
* - Application-level filtering adds an additional layer of protection
|
|
198
|
+
*
|
|
199
|
+
* For rbac_user_profiles:
|
|
200
|
+
* - Super admins: No org filter (see all users) - RLS will allow access
|
|
201
|
+
* - Non-super-admins: Apply org filter as defense in depth - RLS will also filter
|
|
202
|
+
*
|
|
203
|
+
* For other tables:
|
|
204
|
+
* - Always apply org filter unless super admin bypasses it
|
|
170
205
|
*/
|
|
171
|
-
private addOrganisationFilter(query: any) {
|
|
172
|
-
//
|
|
206
|
+
private addOrganisationFilter(query: any, tableName: string) {
|
|
207
|
+
// For rbac_user_profiles, use conditional filtering based on super admin status
|
|
208
|
+
if (tableName === 'rbac_user_profiles') {
|
|
209
|
+
// Super admins: No org filter (see all users via RLS)
|
|
210
|
+
// Non-super-admins: Apply org filter as defense in depth (RLS also filters)
|
|
211
|
+
if (this.isSuperAdmin) {
|
|
212
|
+
return query; // No filter - RLS handles access control
|
|
213
|
+
}
|
|
214
|
+
// Apply org filter for non-super-admins as additional security layer
|
|
215
|
+
return query.eq('organisation_id', this.organisationId);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// For all other tables, apply organisation filter
|
|
219
|
+
// Super admins can still bypass via RLS if needed, but we filter at app level too
|
|
220
|
+
if (this.isSuperAdmin) {
|
|
221
|
+
// Super admins might want to see cross-org data, but for most tables
|
|
222
|
+
// we still apply the filter as a safety measure (they can override if needed)
|
|
223
|
+
// For now, we'll apply it - super admins can use direct queries if they need cross-org access
|
|
224
|
+
return query.eq('organisation_id', this.organisationId);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Non-super-admins: Always apply org filter
|
|
173
228
|
return query.eq('organisation_id', this.organisationId);
|
|
174
229
|
}
|
|
175
230
|
|
|
@@ -210,13 +265,15 @@ export class SecureSupabaseClient {
|
|
|
210
265
|
organisationId?: UUID;
|
|
211
266
|
eventId?: string;
|
|
212
267
|
appId?: UUID;
|
|
268
|
+
isSuperAdmin?: boolean;
|
|
213
269
|
}): SecureSupabaseClient {
|
|
214
270
|
return new SecureSupabaseClient(
|
|
215
271
|
this.supabaseUrl,
|
|
216
272
|
this.supabaseKey,
|
|
217
273
|
updates.organisationId || this.organisationId,
|
|
218
274
|
updates.eventId !== undefined ? updates.eventId : this.eventId,
|
|
219
|
-
updates.appId !== undefined ? updates.appId : this.appId
|
|
275
|
+
updates.appId !== undefined ? updates.appId : this.appId,
|
|
276
|
+
updates.isSuperAdmin !== undefined ? updates.isSuperAdmin : this.isSuperAdmin
|
|
220
277
|
);
|
|
221
278
|
}
|
|
222
279
|
|
|
@@ -249,6 +306,7 @@ export class SecureSupabaseClient {
|
|
|
249
306
|
* @param organisationId - Required organisation ID
|
|
250
307
|
* @param eventId - Optional event ID
|
|
251
308
|
* @param appId - Optional app ID
|
|
309
|
+
* @param isSuperAdmin - Optional super admin flag (defaults to false)
|
|
252
310
|
* @returns SecureSupabaseClient instance
|
|
253
311
|
*
|
|
254
312
|
* @example
|
|
@@ -258,7 +316,8 @@ export class SecureSupabaseClient {
|
|
|
258
316
|
* 'your-publishable-key-or-anon-key',
|
|
259
317
|
* 'org-123',
|
|
260
318
|
* 'event-456',
|
|
261
|
-
* 'app-789'
|
|
319
|
+
* 'app-789',
|
|
320
|
+
* false // isSuperAdmin
|
|
262
321
|
* );
|
|
263
322
|
* ```
|
|
264
323
|
*/
|
|
@@ -267,9 +326,10 @@ export function createSecureClient(
|
|
|
267
326
|
supabaseKey: string,
|
|
268
327
|
organisationId: UUID,
|
|
269
328
|
eventId?: string,
|
|
270
|
-
appId?: UUID
|
|
329
|
+
appId?: UUID,
|
|
330
|
+
isSuperAdmin: boolean = false
|
|
271
331
|
): SecureSupabaseClient {
|
|
272
|
-
return new SecureSupabaseClient(supabaseUrl, supabaseKey, organisationId, eventId, appId);
|
|
332
|
+
return new SecureSupabaseClient(supabaseUrl, supabaseKey, organisationId, eventId, appId, isSuperAdmin);
|
|
273
333
|
}
|
|
274
334
|
|
|
275
335
|
/**
|
package/src/rbac/security.ts
CHANGED
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
|
|
10
10
|
import { UUID, Permission, Scope } from './types';
|
|
11
11
|
import { createLogger } from '../utils/core/logger';
|
|
12
|
+
import { ContextValidator } from './utils/contextValidator';
|
|
13
|
+
import type { AppConfig } from './utils/contextValidator';
|
|
12
14
|
|
|
13
15
|
const log = createLogger('RBACSecurity');
|
|
14
16
|
|
|
@@ -162,25 +164,17 @@ export class RBACSecurityValidator {
|
|
|
162
164
|
/**
|
|
163
165
|
* Validate context requirements for security
|
|
164
166
|
* @param scope - Scope object
|
|
165
|
-
* @param
|
|
167
|
+
* @param appConfig - App configuration
|
|
168
|
+
* @param appName - App name (for PORTAL special case)
|
|
166
169
|
* @returns True if context is valid, false otherwise
|
|
167
170
|
*/
|
|
168
|
-
static validateContextRequirements(
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
if (appId && scope.appId === appId) {
|
|
176
|
-
// This would need to check app configuration
|
|
177
|
-
// For now, we'll assume event context is required if appId is provided
|
|
178
|
-
if (!scope.eventId) {
|
|
179
|
-
return false;
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
return true;
|
|
171
|
+
static async validateContextRequirements(
|
|
172
|
+
scope: Scope,
|
|
173
|
+
appConfig?: AppConfig | null,
|
|
174
|
+
appName?: string
|
|
175
|
+
): Promise<boolean> {
|
|
176
|
+
const validation = await ContextValidator.validateScope(scope, appConfig || null, appName);
|
|
177
|
+
return validation.isValid;
|
|
184
178
|
}
|
|
185
179
|
|
|
186
180
|
/**
|
|
@@ -309,7 +303,7 @@ export const DEFAULT_SECURITY_CONFIG: RBACSecurityConfig = {
|
|
|
309
303
|
*/
|
|
310
304
|
export interface SecurityContext {
|
|
311
305
|
userId: UUID;
|
|
312
|
-
organisationId: UUID; // Required -
|
|
306
|
+
organisationId: UUID | null; // Required for resource-level permissions, null for page-level permissions (database handles NULL)
|
|
313
307
|
ipAddress?: string;
|
|
314
308
|
userAgent?: string;
|
|
315
309
|
timestamp: Date;
|
|
@@ -353,11 +347,23 @@ export class RBACSecurityMiddleware {
|
|
|
353
347
|
errors.push('Invalid user ID format');
|
|
354
348
|
}
|
|
355
349
|
|
|
356
|
-
//
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
350
|
+
// For page-level permissions, organisationId can be null (database handles it)
|
|
351
|
+
// For resource-level permissions, organisationId is required
|
|
352
|
+
const isPagePermission = input.permission?.includes(':page.') || !!input.pageId;
|
|
353
|
+
const requiresOrgId = !isPagePermission;
|
|
354
|
+
|
|
355
|
+
// OrganisationId validation - only required for resource-level permissions
|
|
356
|
+
if (requiresOrgId) {
|
|
357
|
+
if (!context.organisationId) {
|
|
358
|
+
errors.push('Organisation ID is required for resource-level permissions');
|
|
359
|
+
} else if (!RBACSecurityValidator.validateUUID(context.organisationId)) {
|
|
360
|
+
errors.push('Invalid organisation ID format');
|
|
361
|
+
}
|
|
362
|
+
} else {
|
|
363
|
+
// For page-level permissions, organisationId can be null, but if provided, it must be valid
|
|
364
|
+
if (context.organisationId && !RBACSecurityValidator.validateUUID(context.organisationId)) {
|
|
365
|
+
errors.push('Invalid organisation ID format');
|
|
366
|
+
}
|
|
361
367
|
}
|
|
362
368
|
|
|
363
369
|
if (input.permission && !RBACSecurityValidator.validatePermission(input.permission)) {
|
package/src/rbac/types.ts
CHANGED
|
@@ -322,6 +322,16 @@ export class OrganisationContextRequiredError extends RBACError {
|
|
|
322
322
|
}
|
|
323
323
|
}
|
|
324
324
|
|
|
325
|
+
export class EventContextRequiredError extends RBACError {
|
|
326
|
+
constructor() {
|
|
327
|
+
super(
|
|
328
|
+
'Event context is required for this operation',
|
|
329
|
+
'EVENT_CONTEXT_REQUIRED'
|
|
330
|
+
);
|
|
331
|
+
this.name = 'EventContextRequiredError';
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
325
335
|
export class RBACNotInitializedError extends RBACError {
|
|
326
336
|
constructor() {
|
|
327
337
|
super(
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import type { SupabaseClient } from '@supabase/supabase-js';
|
|
3
|
+
import { ContextValidator, type AppConfig } from '../contextValidator';
|
|
4
|
+
import { EventContextRequiredError, OrganisationContextRequiredError, type Scope } from '../../types';
|
|
5
|
+
import type { Database } from '../../../types/database';
|
|
6
|
+
|
|
7
|
+
describe('ContextValidator', () => {
|
|
8
|
+
const eventRequiredConfig: AppConfig = { requires_event: true };
|
|
9
|
+
const orgRequiredConfig: AppConfig = { requires_event: false };
|
|
10
|
+
const scopeWithOrg: Scope = { organisationId: 'org-1', eventId: 'event-1', appId: 'app-1' };
|
|
11
|
+
|
|
12
|
+
const createSupabaseMock = () => ({}) as SupabaseClient<Database>;
|
|
13
|
+
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
vi.restoreAllMocks();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
vi.clearAllMocks();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe('validateScope', () => {
|
|
23
|
+
it('treats PORTAL and ADMIN apps as always valid', async () => {
|
|
24
|
+
const baseScope: Scope = { appId: 'portal-app' };
|
|
25
|
+
const result = await ContextValidator.validateScope(baseScope, eventRequiredConfig, 'PORTAL');
|
|
26
|
+
|
|
27
|
+
expect(result).toEqual({
|
|
28
|
+
isValid: true,
|
|
29
|
+
resolvedScope: { organisationId: undefined, eventId: undefined, appId: 'portal-app' },
|
|
30
|
+
error: null
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('requires organisation context when no app config exists', async () => {
|
|
35
|
+
const baseScope: Scope = { eventId: 'event-1' };
|
|
36
|
+
const result = await ContextValidator.validateScope(baseScope, null);
|
|
37
|
+
|
|
38
|
+
expect(result.isValid).toBe(false);
|
|
39
|
+
expect(result.error).toBeInstanceOf(OrganisationContextRequiredError);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('requires event context for event-based apps', async () => {
|
|
43
|
+
const baseScope: Scope = { organisationId: 'org-1' };
|
|
44
|
+
const result = await ContextValidator.validateScope(baseScope, eventRequiredConfig);
|
|
45
|
+
|
|
46
|
+
expect(result.isValid).toBe(false);
|
|
47
|
+
expect(result.error).toBeInstanceOf(EventContextRequiredError);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('accepts scopes that satisfy event-based requirements', async () => {
|
|
51
|
+
const result = await ContextValidator.validateScope({ eventId: 'event-1' }, eventRequiredConfig);
|
|
52
|
+
|
|
53
|
+
expect(result.isValid).toBe(true);
|
|
54
|
+
expect(result.resolvedScope?.eventId).toBe('event-1');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('enforces organisation requirements for org-based apps', async () => {
|
|
58
|
+
const result = await ContextValidator.validateScope({ eventId: 'event-1' }, orgRequiredConfig);
|
|
59
|
+
|
|
60
|
+
expect(result.isValid).toBe(false);
|
|
61
|
+
expect(result.error).toBeInstanceOf(OrganisationContextRequiredError);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
describe('resolveRequiredContext', () => {
|
|
66
|
+
it('passes through scope for optional contexts', async () => {
|
|
67
|
+
const baseScope: Scope = { appId: 'admin-app' };
|
|
68
|
+
const result = await ContextValidator.resolveRequiredContext(baseScope, eventRequiredConfig, 'ADMIN');
|
|
69
|
+
|
|
70
|
+
expect(result).toEqual({
|
|
71
|
+
isValid: true,
|
|
72
|
+
resolvedScope: { organisationId: undefined, eventId: undefined, appId: 'admin-app' },
|
|
73
|
+
error: null
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('returns organisation error when no app config and org is missing', async () => {
|
|
78
|
+
const result = await ContextValidator.resolveRequiredContext({ eventId: 'event-1' }, null);
|
|
79
|
+
|
|
80
|
+
expect(result.isValid).toBe(false);
|
|
81
|
+
expect(result.error).toBeInstanceOf(OrganisationContextRequiredError);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('derives organisation id for event-required apps when missing', async () => {
|
|
85
|
+
const supabase = createSupabaseMock();
|
|
86
|
+
const deriveSpy = vi.spyOn(ContextValidator, 'deriveOrgFromEvent').mockResolvedValue('derived-org');
|
|
87
|
+
|
|
88
|
+
const result = await ContextValidator.resolveRequiredContext({ eventId: 'event-1' }, eventRequiredConfig, undefined, supabase);
|
|
89
|
+
|
|
90
|
+
expect(deriveSpy).toHaveBeenCalledWith(supabase, 'event-1');
|
|
91
|
+
expect(result).toEqual({
|
|
92
|
+
isValid: true,
|
|
93
|
+
resolvedScope: { organisationId: 'derived-org', eventId: 'event-1', appId: undefined },
|
|
94
|
+
error: null
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('surfaces derivation failures when supabase is unavailable', async () => {
|
|
99
|
+
const result = await ContextValidator.resolveRequiredContext({ eventId: 'event-1' }, eventRequiredConfig, undefined, null);
|
|
100
|
+
|
|
101
|
+
expect(result.isValid).toBe(false);
|
|
102
|
+
expect(result.error?.message).toContain('supabase client not available');
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('returns derivation error messages when fetch fails', async () => {
|
|
106
|
+
const supabase = createSupabaseMock();
|
|
107
|
+
vi.spyOn(ContextValidator, 'deriveOrgFromEvent').mockRejectedValue(new Error('network down'));
|
|
108
|
+
|
|
109
|
+
const result = await ContextValidator.resolveRequiredContext({ eventId: 'event-1' }, eventRequiredConfig, undefined, supabase);
|
|
110
|
+
|
|
111
|
+
expect(result.isValid).toBe(false);
|
|
112
|
+
expect(result.error).toBeInstanceOf(Error);
|
|
113
|
+
expect(result.error?.message).toContain('network down');
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('enforces organisation requirement for org-based apps', async () => {
|
|
117
|
+
const result = await ContextValidator.resolveRequiredContext({ eventId: 'event-1' }, orgRequiredConfig);
|
|
118
|
+
|
|
119
|
+
expect(result.isValid).toBe(false);
|
|
120
|
+
expect(result.error).toBeInstanceOf(OrganisationContextRequiredError);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('returns valid result when org context is present', async () => {
|
|
124
|
+
const result = await ContextValidator.resolveRequiredContext(scopeWithOrg, orgRequiredConfig);
|
|
125
|
+
|
|
126
|
+
expect(result).toEqual({ isValid: true, resolvedScope: scopeWithOrg, error: null });
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
describe('isContextReady', () => {
|
|
131
|
+
it('is always ready for PORTAL/ADMIN apps', () => {
|
|
132
|
+
expect(ContextValidator.isContextReady({}, eventRequiredConfig, 'PORTAL')).toBe(true);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('checks event selection for event-required apps', () => {
|
|
136
|
+
expect(ContextValidator.isContextReady({}, eventRequiredConfig, undefined, true)).toBe(true);
|
|
137
|
+
expect(ContextValidator.isContextReady({}, eventRequiredConfig, undefined, false)).toBe(false);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('checks organisation selection for org-required apps', () => {
|
|
141
|
+
expect(ContextValidator.isContextReady({}, orgRequiredConfig, undefined, false, true)).toBe(true);
|
|
142
|
+
expect(ContextValidator.isContextReady({}, orgRequiredConfig, undefined, false, false)).toBe(false);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('defaults to organisation requirement when no config', () => {
|
|
146
|
+
expect(ContextValidator.isContextReady({}, null, undefined, false, true)).toBe(true);
|
|
147
|
+
expect(ContextValidator.isContextReady({}, null, undefined, false, false)).toBe(false);
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { deepEqual, scopeEqual } from '../deep-equal';
|
|
3
|
+
import type { Scope } from '../../types';
|
|
4
|
+
|
|
5
|
+
describe('deepEqual', () => {
|
|
6
|
+
it('returns true for identical primitives and references', () => {
|
|
7
|
+
expect(deepEqual(5, 5)).toBe(true);
|
|
8
|
+
expect(deepEqual('test', 'test')).toBe(true);
|
|
9
|
+
const obj = { value: 1 };
|
|
10
|
+
expect(deepEqual(obj, obj)).toBe(true);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('returns false for different primitive values or types', () => {
|
|
14
|
+
expect(deepEqual(5, 6)).toBe(false);
|
|
15
|
+
expect(deepEqual(5, '5')).toBe(false);
|
|
16
|
+
expect(deepEqual(null, undefined)).toBe(false);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('compares arrays by value and order', () => {
|
|
20
|
+
expect(deepEqual([1, 2, 3], [1, 2, 3])).toBe(true);
|
|
21
|
+
expect(deepEqual([1, 2, 3], [3, 2, 1])).toBe(false);
|
|
22
|
+
expect(deepEqual([1, 2], [1, 2, 3])).toBe(false);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('performs deep comparisons on nested objects', () => {
|
|
26
|
+
const left = { id: 1, nested: { active: true, tags: ['a', 'b'] } };
|
|
27
|
+
const right = { nested: { tags: ['a', 'b'], active: true }, id: 1 };
|
|
28
|
+
expect(deepEqual(left, right)).toBe(true);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('detects differences in object structure or values', () => {
|
|
32
|
+
const base = { id: 1, nested: { active: true } };
|
|
33
|
+
expect(deepEqual(base, { id: 1, nested: { active: false } })).toBe(false);
|
|
34
|
+
expect(deepEqual(base, { id: 1, nested: { active: true }, extra: 'field' })).toBe(false);
|
|
35
|
+
expect(deepEqual(base, { id: 1, other: { active: true } })).toBe(false);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe('scopeEqual', () => {
|
|
40
|
+
it('returns true for identical scopes including nulls', () => {
|
|
41
|
+
const scope: Scope = { organisationId: 'org', eventId: 'event', appId: 'app' };
|
|
42
|
+
expect(scopeEqual(scope, { ...scope })).toBe(true);
|
|
43
|
+
expect(scopeEqual(null, null)).toBe(true);
|
|
44
|
+
expect(scopeEqual(undefined, undefined)).toBe(true);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('returns false when any scope property differs', () => {
|
|
48
|
+
const base: Scope = { organisationId: 'org', eventId: 'event', appId: 'app' };
|
|
49
|
+
expect(scopeEqual(base, { ...base, eventId: 'other' })).toBe(false);
|
|
50
|
+
expect(scopeEqual(base, { organisationId: 'org', eventId: 'event' })).toBe(false);
|
|
51
|
+
expect(scopeEqual(base, null)).toBe(false);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
@@ -15,7 +15,8 @@ import {
|
|
|
15
15
|
getOrganisationFromEvent,
|
|
16
16
|
createScopeFromEvent,
|
|
17
17
|
isEventBasedScope,
|
|
18
|
-
isValidEventBasedScope
|
|
18
|
+
isValidEventBasedScope,
|
|
19
|
+
clearAllOrgDerivationCache
|
|
19
20
|
} from '../eventContext';
|
|
20
21
|
|
|
21
22
|
// Mock Supabase client
|
|
@@ -37,11 +38,15 @@ describe('Event Context Utilities', () => {
|
|
|
37
38
|
let mockQuery: any;
|
|
38
39
|
|
|
39
40
|
beforeEach(() => {
|
|
41
|
+
// Clear cache before each test to prevent test interference
|
|
42
|
+
clearAllOrgDerivationCache();
|
|
40
43
|
mockSupabase = createMockSupabaseClient();
|
|
41
44
|
mockQuery = mockSupabase.from('event');
|
|
42
45
|
});
|
|
43
46
|
|
|
44
47
|
afterEach(() => {
|
|
48
|
+
// Clear cache after each test
|
|
49
|
+
clearAllOrgDerivationCache();
|
|
45
50
|
vi.clearAllMocks();
|
|
46
51
|
});
|
|
47
52
|
|