@jmruthers/pace-core 0.5.54 → 0.5.55
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/README.md +0 -4
- package/dist/{DataTable-7FMFXA7A.js → DataTable-4T627QFJ.js} +11 -11
- package/dist/{PublicLoadingSpinner-Bq_-BeK-.d.ts → PublicLoadingSpinner-SL8WaQN7.d.ts} +2 -21
- package/dist/{api-H5A3H4IR.js → api-LUNF5O6M.js} +3 -3
- package/dist/{appConfig-BVGyuvI7.d.ts → appConfig-DjpeG6P-.d.ts} +9 -1
- package/dist/{appNameResolver-7GHF5ED2.js → appNameResolver-UURKN7NF.js} +2 -2
- package/dist/{audit-BUW3LMJB.js → audit-6TOCAMKO.js} +2 -2
- package/dist/{chunk-MZBUOP4P.js → chunk-5BSLGBYI.js} +4 -3
- package/dist/chunk-5BSLGBYI.js.map +1 -0
- package/dist/{chunk-I5Z3QH5X.js → chunk-66C4BSAY.js} +2 -2
- package/dist/{chunk-I5Z3QH5X.js.map → chunk-66C4BSAY.js.map} +1 -1
- package/dist/{chunk-MYP2EGHX.js → chunk-AJ2KMES7.js} +21 -14
- package/dist/chunk-AJ2KMES7.js.map +1 -0
- package/dist/{chunk-EL2O4IUX.js → chunk-AQFRLC7K.js} +16 -24
- package/dist/{chunk-EL2O4IUX.js.map → chunk-AQFRLC7K.js.map} +1 -1
- package/dist/{chunk-7BNPOCLL.js → chunk-B2WTCLCV.js} +6 -2
- package/dist/chunk-B2WTCLCV.js.map +1 -0
- package/dist/{chunk-WJARTBCT.js → chunk-D7ARGIA3.js} +16 -7
- package/dist/chunk-D7ARGIA3.js.map +1 -0
- package/dist/{chunk-NRK4AIHQ.js → chunk-KBRACSJI.js} +3 -3
- package/dist/{chunk-NYUJ4FJR.js → chunk-KJDPSM64.js} +7 -7
- package/dist/chunk-KJDPSM64.js.map +1 -0
- package/dist/{chunk-GWSBHC4J.js → chunk-KLPVOPRI.js} +261 -38
- package/dist/chunk-KLPVOPRI.js.map +1 -0
- package/dist/{chunk-TRIZ7IB7.js → chunk-MPQDF75X.js} +148 -288
- package/dist/chunk-MPQDF75X.js.map +1 -0
- package/dist/{chunk-MSFACPQQ.js → chunk-PAEM3OWN.js} +11 -11
- package/dist/{chunk-MSFACPQQ.js.map → chunk-PAEM3OWN.js.map} +1 -1
- package/dist/{chunk-GIO7BFE7.js → chunk-RQD3D2CO.js} +66 -169
- package/dist/{chunk-GIO7BFE7.js.map → chunk-RQD3D2CO.js.map} +1 -1
- package/dist/{chunk-YDJW5XTN.js → chunk-STT7INZR.js} +25 -1
- package/dist/chunk-STT7INZR.js.map +1 -0
- package/dist/{chunk-6MTY77WU.js → chunk-TNMXZLDR.js} +3 -3
- package/dist/{chunk-BC3S53OZ.js → chunk-UQE2Y64H.js} +30 -14
- package/dist/chunk-UQE2Y64H.js.map +1 -0
- package/dist/{chunk-22KLBHPS.js → chunk-W66AZIOH.js} +2 -2
- package/dist/chunk-W66AZIOH.js.map +1 -0
- package/dist/{chunk-SS3E6QLB.js → chunk-YNUBMSMV.js} +2 -2
- package/dist/chunk-YNUBMSMV.js.map +1 -0
- package/dist/{chunk-NZ655MWE.js → chunk-ZOD2ZY6X.js} +5 -4
- package/dist/chunk-ZOD2ZY6X.js.map +1 -0
- package/dist/{chunk-74C6SNEC.js → chunk-ZPK5656W.js} +3 -3
- package/dist/{chunk-74C6SNEC.js.map → chunk-ZPK5656W.js.map} +1 -1
- package/dist/components.d.ts +22 -899
- package/dist/components.js +436 -3118
- package/dist/components.js.map +1 -1
- package/dist/file-reference-9xUOnwyt.d.ts +70 -0
- package/dist/hooks.d.ts +2 -2
- package/dist/hooks.js +10 -10
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +49 -9
- package/dist/index.js +190 -25
- package/dist/index.js.map +1 -1
- package/dist/{organisation-CO3Sh3_D.d.ts → organisation-t-vvQC3g.d.ts} +1 -8
- package/dist/providers.d.ts +2 -2
- package/dist/providers.js +5 -5
- package/dist/rbac/index.d.ts +65 -46
- package/dist/rbac/index.js +10 -12
- package/dist/styles/core.css +0 -125
- package/dist/types.d.ts +2 -1
- package/dist/types.js +3 -1
- package/dist/types.js.map +1 -1
- package/dist/{usePublicRouteParams-B2OcAsur.d.ts → usePublicRouteParams-CdoFxnJK.d.ts} +1 -1
- package/dist/utils.d.ts +3 -4
- package/dist/utils.js +44 -13
- package/dist/utils.js.map +1 -1
- package/docs/FILE_REFERENCE_SYSTEM.md +440 -0
- package/docs/INDEX.md +7 -5
- package/docs/README.md +0 -1
- package/docs/api/README.md +0 -4
- package/docs/api/classes/ErrorBoundary.md +1 -1
- package/docs/api/classes/InvalidScopeError.md +1 -1
- package/docs/api/classes/MissingUserContextError.md +1 -1
- package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
- package/docs/api/classes/PermissionDeniedError.md +2 -2
- package/docs/api/classes/PublicErrorBoundary.md +1 -1
- package/docs/api/classes/RBACAuditManager.md +12 -12
- package/docs/api/classes/RBACCache.md +1 -1
- package/docs/api/classes/RBACEngine.md +6 -6
- package/docs/api/classes/RBACError.md +1 -1
- package/docs/api/classes/RBACNotInitializedError.md +1 -1
- package/docs/api/classes/SecureSupabaseClient.md +1 -1
- package/docs/api/classes/StorageUtils.md +281 -0
- package/docs/api/interfaces/AggregateConfig.md +1 -1
- package/docs/api/interfaces/ButtonProps.md +1 -1
- 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/DataAccessRecord.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/EmptyStateConfig.md +1 -1
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
- package/docs/api/interfaces/EventContextType.md +1 -1
- package/docs/api/interfaces/EventLogoProps.md +1 -1
- package/docs/api/interfaces/EventProviderProps.md +1 -1
- package/docs/api/interfaces/FileSizeLimits.md +1 -1
- package/docs/api/interfaces/FileUploadProps.md +1 -1
- package/docs/api/interfaces/FooterProps.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/LoginFormProps.md +1 -1
- package/docs/api/interfaces/NavigationAccessRecord.md +2 -2
- package/docs/api/interfaces/NavigationContextType.md +1 -1
- 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 +1 -1
- package/docs/api/interfaces/Organisation.md +1 -1
- package/docs/api/interfaces/OrganisationContextType.md +1 -1
- package/docs/api/interfaces/OrganisationMembership.md +1 -1
- package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
- package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
- package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
- package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
- package/docs/api/interfaces/PageAccessRecord.md +1 -1
- package/docs/api/interfaces/PagePermissionContextType.md +1 -1
- package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
- package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
- package/docs/api/interfaces/PaletteData.md +1 -1
- package/docs/api/interfaces/PermissionEnforcerProps.md +4 -4
- package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
- package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
- package/docs/api/interfaces/PublicLoadingSpinnerProps.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/RBACConfig.md +1 -1
- package/docs/api/interfaces/RBACContextType.md +1 -1
- package/docs/api/interfaces/RBACLogger.md +1 -1
- package/docs/api/interfaces/RBACProviderProps.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
- package/docs/api/interfaces/RouteAccessRecord.md +2 -2
- package/docs/api/interfaces/RouteConfig.md +2 -2
- package/docs/api/interfaces/SecureDataContextType.md +1 -1
- package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
- package/docs/api/interfaces/StorageConfig.md +1 -1
- package/docs/api/interfaces/StorageFileInfo.md +1 -1
- package/docs/api/interfaces/StorageFileMetadata.md +1 -1
- package/docs/api/interfaces/StorageListOptions.md +1 -1
- package/docs/api/interfaces/StorageListResult.md +1 -1
- package/docs/api/interfaces/StorageUploadOptions.md +1 -1
- package/docs/api/interfaces/StorageUploadResult.md +1 -1
- package/docs/api/interfaces/StorageUrlOptions.md +1 -1
- package/docs/api/interfaces/StyleImport.md +1 -1
- package/docs/api/interfaces/ToastActionElement.md +1 -1
- package/docs/api/interfaces/ToastProps.md +1 -1
- package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
- package/docs/api/interfaces/UnifiedAuthProviderProps.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/UsePublicRouteParamsReturn.md +1 -1
- package/docs/api/interfaces/UserEventAccess.md +1 -1
- package/docs/api/interfaces/UserMenuProps.md +1 -1
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +204 -200
- package/docs/api-reference/components.md +141 -163
- package/docs/api-reference/hooks.md +347 -0
- package/docs/core-concepts/rbac-system.md +69 -16
- package/docs/getting-started/examples/basic-auth-app.md +0 -1
- package/docs/implementation-guides/datatable-rbac-usage.md +12 -11
- package/docs/implementation-guides/file-upload-storage.md +733 -0
- package/docs/implementation-guides/inactivity-tracking.md +779 -0
- package/docs/implementation-guides/organisation-security.md +748 -0
- package/docs/implementation-guides/public-pages-advanced.md +1022 -0
- package/docs/migration/MIGRATION_GUIDE.md +684 -0
- package/docs/migration/README.md +13 -2
- package/docs/migration/rbac-migration.md +73 -0
- package/docs/rbac/examples/rbac-rls-integration-example.md +11 -13
- package/docs/style-guide.md +269 -1
- package/package.json +1 -1
- package/src/__tests__/TESTING_GUIDELINES.md +331 -18
- package/src/__tests__/helpers/supabaseMock.ts +99 -0
- package/src/__tests__/rbac/PagePermissionGuard.test.tsx +10 -7
- package/src/__tests__/shared.ts +6 -0
- package/src/components/DataTable/components/ActionButtons.tsx +2 -2
- package/src/components/DataTable/components/DataTableCore.tsx +2 -2
- package/src/components/DataTable/components/UnifiedTableBody.tsx +1 -1
- package/src/components/DataTable/utils/debugTools.ts +2 -2
- package/src/components/Dialog/Dialog.test.tsx +12 -2
- package/src/components/ErrorBoundary/ErrorBoundary.test.tsx +6 -6
- package/src/components/ErrorBoundary/ErrorBoundary.tsx +2 -2
- package/src/components/FileDisplay.tsx +233 -0
- package/src/components/FileUpload.tsx +176 -0
- package/src/components/Footer/Footer.test.tsx +7 -7
- package/src/components/NavigationMenu/NavigationMenu.test.tsx +13 -6
- package/src/components/OrganisationSelector/OrganisationSelector.test.tsx +30 -3
- package/src/components/OrganisationSelector/OrganisationSelector.tsx +1 -1
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.rbac.test.tsx +558 -0
- package/src/components/PublicLayout/PublicErrorBoundary.tsx +1 -1
- package/src/components/PublicLayout/PublicPageDebugger.tsx +2 -2
- package/src/components/PublicLayout/PublicPageDiagnostic.tsx +2 -2
- package/src/components/PublicLayout/PublicPageProvider.tsx +2 -2
- package/src/components/Select/Select.test.tsx +50 -15
- package/src/components/SuperAdminGuard.tsx +2 -2
- package/src/components/__tests__/SuperAdminGuard.test.tsx +559 -0
- package/src/components/index.ts +0 -183
- package/src/hooks/__tests__/useOrganisationPermissions.unit.test.tsx +2 -2
- package/src/hooks/__tests__/usePermissionCache.unit.test.ts +1 -1
- package/src/hooks/__tests__/useRBAC.unit.test.ts +191 -138
- package/src/hooks/public/usePublicEvent.ts +2 -2
- package/src/hooks/useAppConfig.ts +3 -3
- package/src/hooks/useComponentPerformance.ts +1 -1
- package/src/hooks/useDataTablePerformance.ts +1 -1
- package/src/hooks/useFileReference.ts +232 -0
- package/src/hooks/useOrganisationPermissions.test.ts +254 -344
- package/src/hooks/useOrganisationPermissions.ts +15 -7
- package/src/hooks/useOrganisationSecurity.test.ts +390 -402
- package/src/hooks/usePerformanceMonitor.ts +1 -1
- package/src/hooks/usePermissionCache.test.ts +264 -395
- package/src/hooks/usePermissionCache.ts +34 -4
- package/src/hooks/useSecureDataAccess.test.ts +486 -0
- package/src/hooks/useSecureDataAccess.ts +4 -1
- package/src/providers/InactivityProvider.tsx +2 -2
- package/src/providers/OrganisationProvider.test.simple.tsx +168 -0
- package/src/providers/OrganisationProvider.test.tsx +168 -0
- package/src/providers/OrganisationProvider.tsx +18 -31
- package/src/providers/UnifiedAuthProvider.test.simple.tsx +205 -0
- package/src/providers/UnifiedAuthProvider.test.tsx +128 -0
- package/src/providers/__tests__/InactivityProvider.test.tsx +3 -4
- package/src/providers/__tests__/OrganisationProvider.test.tsx +19 -14
- package/src/rbac/__tests__/integration.authflow.test.tsx +123 -0
- package/src/rbac/__tests__/integration.navigation.test.tsx +72 -0
- package/src/rbac/__tests__/integration.securedata.test.tsx +92 -0
- package/src/rbac/__tests__/integration.smoke.test.tsx +73 -0
- package/src/rbac/__tests__/rbac-core.test.tsx +26 -22
- package/src/rbac/__tests__/rbac-engine-core-logic.test.ts +411 -0
- package/src/rbac/__tests__/rbac-engine-simplified.test.ts +285 -0
- package/src/rbac/__tests__/rbac-functions.test.ts +655 -0
- package/src/rbac/__tests__/rbac-integration.test.ts +532 -0
- package/src/rbac/__tests__/scenarios.user-role.test.tsx +196 -0
- package/src/rbac/api.test.ts +6 -6
- package/src/rbac/api.ts +2 -2
- package/src/rbac/audit.test.ts +485 -0
- package/src/rbac/audit.ts +7 -1
- package/src/rbac/cache-invalidation.ts +318 -0
- package/src/rbac/cache.test.ts +286 -0
- package/src/rbac/components/EnhancedNavigationMenu.test.tsx +559 -0
- package/src/rbac/components/EnhancedNavigationMenu.tsx +29 -23
- package/src/rbac/components/NavigationProvider.test.tsx +449 -0
- package/src/rbac/components/PagePermissionGuard.tsx +4 -4
- package/src/rbac/components/PagePermissionProvider.test.tsx +479 -0
- package/src/rbac/components/SecureDataProvider.test.tsx +511 -0
- package/src/rbac/components/__tests__/PagePermissionGuard.race-condition.test.tsx +159 -430
- package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +4 -5
- package/src/rbac/components/__tests__/PagePermissionGuard.verification.test.tsx +112 -118
- package/src/rbac/config.test.ts +410 -0
- package/src/rbac/engine.test.simple.ts +237 -0
- package/src/rbac/engine.test.ts +233 -0
- package/src/rbac/engine.ts +37 -41
- package/src/rbac/examples/CompleteRBACExample.tsx +3 -3
- package/src/rbac/examples/EventBasedApp.tsx +4 -4
- package/src/rbac/hooks/useRBAC.simple.test.ts +16 -0
- package/src/rbac/hooks/useRBAC.test.ts +207 -455
- package/src/rbac/hooks/useRBAC.ts +30 -22
- package/src/rbac/permissions.test.ts +128 -0
- package/src/rbac/permissions.ts +56 -141
- package/src/rbac/providers/RBACProvider.tsx +1 -1
- package/src/rbac/secureClient.test.ts +444 -0
- package/src/rbac/security.test.ts +390 -0
- package/src/rbac/security.ts +1 -1
- package/src/rbac/types.test.ts +382 -0
- package/src/rbac/types.ts +2 -2
- package/src/styles/core.css +0 -125
- package/src/types/file-reference.ts +77 -0
- package/src/types/rbac-functions.ts +290 -0
- package/src/types/supabase.ts +10 -28
- package/src/types/unified.ts +4 -1
- package/src/utils/__tests__/bundleAnalysis.unit.test.ts +81 -55
- package/src/utils/__tests__/lazyLoad.unit.test.tsx +21 -12
- package/src/utils/__tests__/organisationContext.unit.test.ts +13 -7
- package/src/utils/__tests__/performanceBudgets.unit.test.ts +3 -3
- package/src/utils/__tests__/sessionTracking.unit.test.ts +32 -12
- package/src/utils/appConfig.ts +1 -1
- package/src/utils/appIdResolver.test.ts +503 -0
- package/src/utils/appIdResolver.ts +1 -1
- package/src/utils/appNameResolver.test.ts +494 -0
- package/src/utils/appNameResolver.ts +3 -2
- package/src/utils/bundleAnalysis.ts +3 -3
- package/src/utils/debugLogger.ts +1 -1
- package/src/utils/file-reference.ts +263 -0
- package/src/utils/formatDate.test.ts +2 -2
- package/src/utils/organisationContext.test.ts +340 -0
- package/src/utils/organisationContext.ts +19 -6
- package/src/utils/performanceBudgets.ts +2 -2
- package/src/utils/permissionUtils.test.ts +393 -0
- package/src/utils/permissionUtils.ts +5 -2
- package/src/utils/secureDataAccess.test.ts +715 -0
- package/src/utils/secureDataAccess.ts +21 -5
- package/src/utils/sessionTracking.ts +34 -4
- package/src/utils/storage/__tests__/helpers.unit.test.ts +328 -0
- package/src/utils/storage/__tests__/index.unit.test.ts +16 -0
- package/src/utils/storage/helpers.ts +20 -25
- package/src/utils/storage/index.ts +29 -1
- package/src/vite-env.d.ts +17 -0
- package/dist/chunk-22KLBHPS.js.map +0 -1
- package/dist/chunk-7BNPOCLL.js.map +0 -1
- package/dist/chunk-BC3S53OZ.js.map +0 -1
- package/dist/chunk-GWSBHC4J.js.map +0 -1
- package/dist/chunk-MYP2EGHX.js.map +0 -1
- package/dist/chunk-MZBUOP4P.js.map +0 -1
- package/dist/chunk-NYUJ4FJR.js.map +0 -1
- package/dist/chunk-NZ655MWE.js.map +0 -1
- package/dist/chunk-SS3E6QLB.js.map +0 -1
- package/dist/chunk-TRIZ7IB7.js.map +0 -1
- package/dist/chunk-WJARTBCT.js.map +0 -1
- package/dist/chunk-YDJW5XTN.js.map +0 -1
- package/docs/print-components/README.md +0 -258
- package/docs/print-components/api-reference.md +0 -636
- package/docs/print-components/examples/README.md +0 -204
- package/docs/print-components/examples/basic-report.tsx +0 -92
- package/docs/print-components/examples/card-catalog.tsx +0 -149
- package/docs/print-components/examples/cover-page-report.tsx +0 -163
- package/docs/print-components/quick-start.md +0 -363
- package/src/components/PrintButton/PrintButton.tsx +0 -321
- package/src/components/PrintButton/PrintButtonGroup.tsx +0 -84
- package/src/components/PrintButton/PrintToolbar.tsx +0 -94
- package/src/components/PrintButton/__tests__/PrintButton.test.tsx +0 -271
- package/src/components/PrintButton/examples/PrintButtonShowcase.tsx +0 -438
- package/src/components/PrintButton/index.ts +0 -33
- package/src/components/PrintButton/types.ts +0 -173
- package/src/components/PrintCard/PrintCard.tsx +0 -154
- package/src/components/PrintCard/PrintCardContent.tsx +0 -57
- package/src/components/PrintCard/PrintCardFooter.tsx +0 -60
- package/src/components/PrintCard/PrintCardGrid.tsx +0 -91
- package/src/components/PrintCard/PrintCardHeader.tsx +0 -78
- package/src/components/PrintCard/PrintCardImage.tsx +0 -81
- package/src/components/PrintCard/examples/PrintCardShowcase.tsx +0 -239
- package/src/components/PrintCard/index.ts +0 -34
- package/src/components/PrintCard/types.ts +0 -171
- package/src/components/PrintDataTable/PrintDataTable.tsx +0 -215
- package/src/components/PrintDataTable/PrintTableGroup.tsx +0 -90
- package/src/components/PrintDataTable/PrintTableRow.tsx +0 -76
- package/src/components/PrintDataTable/index.ts +0 -25
- package/src/components/PrintDataTable/types.ts +0 -67
- package/src/components/PrintFooter/PrintFooter.tsx +0 -183
- package/src/components/PrintFooter/PrintFooterContent.tsx +0 -71
- package/src/components/PrintFooter/PrintFooterInfo.tsx +0 -86
- package/src/components/PrintFooter/PrintPageNumber.tsx +0 -90
- package/src/components/PrintFooter/examples/PrintFooterShowcase.tsx +0 -390
- package/src/components/PrintFooter/index.ts +0 -30
- package/src/components/PrintFooter/types.ts +0 -149
- package/src/components/PrintGrid/PrintGrid.tsx +0 -180
- package/src/components/PrintGrid/PrintGridBreakpoint.tsx +0 -109
- package/src/components/PrintGrid/PrintGridContainer.tsx +0 -128
- package/src/components/PrintGrid/PrintGridItem.tsx +0 -220
- package/src/components/PrintGrid/examples/PrintGridShowcase.tsx +0 -359
- package/src/components/PrintGrid/index.ts +0 -31
- package/src/components/PrintGrid/types.ts +0 -159
- package/src/components/PrintHeader/PrintCoverHeader.tsx +0 -230
- package/src/components/PrintHeader/PrintHeader.tsx +0 -150
- package/src/components/PrintHeader/index.ts +0 -17
- package/src/components/PrintHeader/types.ts +0 -42
- package/src/components/PrintLayout/PrintLayout.tsx +0 -122
- package/src/components/PrintLayout/PrintLayoutContext.tsx +0 -66
- package/src/components/PrintLayout/PrintPageBreak.tsx +0 -52
- package/src/components/PrintLayout/examples/PrintShowcase.tsx +0 -230
- package/src/components/PrintLayout/index.ts +0 -19
- package/src/components/PrintLayout/types.ts +0 -37
- package/src/components/PrintPageBreak/PrintPageBreak.tsx +0 -120
- package/src/components/PrintPageBreak/PrintPageBreakGroup.tsx +0 -90
- package/src/components/PrintPageBreak/PrintPageBreakIndicator.tsx +0 -112
- package/src/components/PrintPageBreak/examples/PrintPageBreakShowcase.tsx +0 -279
- package/src/components/PrintPageBreak/index.ts +0 -23
- package/src/components/PrintPageBreak/types.ts +0 -94
- package/src/components/PrintSection/PrintColumn.tsx +0 -104
- package/src/components/PrintSection/PrintDivider.tsx +0 -101
- package/src/components/PrintSection/PrintSection.tsx +0 -129
- package/src/components/PrintSection/PrintSectionContent.tsx +0 -75
- package/src/components/PrintSection/PrintSectionHeader.tsx +0 -97
- package/src/components/PrintSection/examples/PrintSectionShowcase.tsx +0 -258
- package/src/components/PrintSection/index.ts +0 -33
- package/src/components/PrintSection/types.ts +0 -155
- package/src/components/PrintText/PrintText.tsx +0 -116
- package/src/components/PrintText/index.ts +0 -16
- package/src/components/PrintText/types.ts +0 -24
- package/src/rbac/__tests__/integration.test.tsx +0 -218
- package/src/utils/print/PrintDataProcessor.ts +0 -390
- package/src/utils/print/examples/PrintUtilitiesShowcase.tsx +0 -397
- package/src/utils/print/index.ts +0 -29
- package/src/utils/print/types.ts +0 -196
- package/src/utils/print/usePrintOptimization.ts +0 -272
- /package/dist/{DataTable-7FMFXA7A.js.map → DataTable-4T627QFJ.js.map} +0 -0
- /package/dist/{api-H5A3H4IR.js.map → api-LUNF5O6M.js.map} +0 -0
- /package/dist/{appNameResolver-7GHF5ED2.js.map → appNameResolver-UURKN7NF.js.map} +0 -0
- /package/dist/{audit-BUW3LMJB.js.map → audit-6TOCAMKO.js.map} +0 -0
- /package/dist/{chunk-NRK4AIHQ.js.map → chunk-KBRACSJI.js.map} +0 -0
- /package/dist/{chunk-6MTY77WU.js.map → chunk-TNMXZLDR.js.map} +0 -0
|
@@ -92,7 +92,7 @@ interface CacheConfig {
|
|
|
92
92
|
const DEFAULT_CONFIG: CacheConfig = {
|
|
93
93
|
defaultTTL: 5 * 60 * 1000, // 5 minutes
|
|
94
94
|
maxCacheSize: 1000,
|
|
95
|
-
enableLogging:
|
|
95
|
+
enableLogging: import.meta.env.MODE === 'development',
|
|
96
96
|
enableAuditTrail: true
|
|
97
97
|
};
|
|
98
98
|
|
|
@@ -111,7 +111,7 @@ export function usePermissionCache(config: Partial<CacheConfig> = {}) {
|
|
|
111
111
|
cacheMisses: 0,
|
|
112
112
|
totalChecks: 0,
|
|
113
113
|
totalResponseTime: 0,
|
|
114
|
-
lastInvalidation:
|
|
114
|
+
lastInvalidation: 0
|
|
115
115
|
});
|
|
116
116
|
|
|
117
117
|
// Audit trail
|
|
@@ -125,7 +125,8 @@ export function usePermissionCache(config: Partial<CacheConfig> = {}) {
|
|
|
125
125
|
}>>([]);
|
|
126
126
|
|
|
127
127
|
// Get base RBAC hook
|
|
128
|
-
const
|
|
128
|
+
const rbacHook = useRBAC();
|
|
129
|
+
const { hasPermission: baseHasPermission, user } = rbacHook || {};
|
|
129
130
|
|
|
130
131
|
// Generate cache key
|
|
131
132
|
const getCacheKey = useCallback((operation: Operation, pageId: string): string => {
|
|
@@ -197,6 +198,17 @@ export function usePermissionCache(config: Partial<CacheConfig> = {}) {
|
|
|
197
198
|
pageId: string,
|
|
198
199
|
ttl?: number
|
|
199
200
|
): Promise<boolean> => {
|
|
201
|
+
// Validate parameters
|
|
202
|
+
if (!operation || !pageId) {
|
|
203
|
+
console.warn('[PermissionCache] Invalid parameters: operation and pageId are required');
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (!baseHasPermission) {
|
|
208
|
+
console.warn('[PermissionCache] RBAC not available - permission check failed');
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
211
|
+
|
|
200
212
|
const startTime = Date.now();
|
|
201
213
|
const cacheKey = getCacheKey(operation, pageId);
|
|
202
214
|
const entry = cache.current.get(cacheKey);
|
|
@@ -262,6 +274,24 @@ export function usePermissionCache(config: Partial<CacheConfig> = {}) {
|
|
|
262
274
|
permissions: Array<[Operation, string]>,
|
|
263
275
|
ttl?: number
|
|
264
276
|
): Promise<PermissionResult[]> => {
|
|
277
|
+
// Validate parameters
|
|
278
|
+
if (!permissions || !Array.isArray(permissions) || permissions.length === 0) {
|
|
279
|
+
console.warn('[PermissionCache] Invalid permissions array');
|
|
280
|
+
return [];
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (!baseHasPermission) {
|
|
284
|
+
console.warn('[PermissionCache] RBAC not available - permission checks failed');
|
|
285
|
+
return permissions.map(([operation, pageId]) => ({
|
|
286
|
+
operation,
|
|
287
|
+
pageId,
|
|
288
|
+
hasPermission: false,
|
|
289
|
+
cached: false,
|
|
290
|
+
responseTime: 0,
|
|
291
|
+
timestamp: Date.now()
|
|
292
|
+
}));
|
|
293
|
+
}
|
|
294
|
+
|
|
265
295
|
const results: PermissionResult[] = [];
|
|
266
296
|
const startTime = Date.now();
|
|
267
297
|
|
|
@@ -433,7 +463,7 @@ export function usePermissionCache(config: Partial<CacheConfig> = {}) {
|
|
|
433
463
|
};
|
|
434
464
|
|
|
435
465
|
// Only set up interval in non-test environments to prevent memory leaks during testing
|
|
436
|
-
const interval =
|
|
466
|
+
const interval = import.meta.env.MODE !== 'test'
|
|
437
467
|
? setInterval(cleanup, 1000)
|
|
438
468
|
: null;
|
|
439
469
|
|
|
@@ -0,0 +1,486 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Secure Data Access Hook Tests
|
|
3
|
+
* @package @jmruthers/pace-core
|
|
4
|
+
* @module Hooks/useSecureDataAccess
|
|
5
|
+
* @since 0.4.0
|
|
6
|
+
*
|
|
7
|
+
* Comprehensive tests for the useSecureDataAccess hook covering all critical functionality.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { renderHook, waitFor } from '@testing-library/react';
|
|
11
|
+
import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
12
|
+
import { useSecureDataAccess } from './useSecureDataAccess';
|
|
13
|
+
import { useUnifiedAuth } from '../providers/UnifiedAuthProvider';
|
|
14
|
+
import { useOrganisations } from '../providers/OrganisationProvider';
|
|
15
|
+
import { setOrganisationContext } from '../utils/organisationContext';
|
|
16
|
+
import { createMockSupabaseClient, createMockQueryBuilderWithData } from '../__tests__/helpers/supabaseMock';
|
|
17
|
+
|
|
18
|
+
// Mock the providers
|
|
19
|
+
vi.mock('../providers/UnifiedAuthProvider', () => ({
|
|
20
|
+
useUnifiedAuth: vi.fn()
|
|
21
|
+
}));
|
|
22
|
+
|
|
23
|
+
vi.mock('../providers/OrganisationProvider', () => ({
|
|
24
|
+
useOrganisations: vi.fn()
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
// Mock the organisation context utility
|
|
28
|
+
vi.mock('../utils/organisationContext', () => ({
|
|
29
|
+
setOrganisationContext: vi.fn()
|
|
30
|
+
}));
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
describe('useSecureDataAccess', () => {
|
|
34
|
+
const mockUseUnifiedAuth = vi.mocked(useUnifiedAuth);
|
|
35
|
+
const mockUseOrganisations = vi.mocked(useOrganisations);
|
|
36
|
+
const mockSetOrganisationContext = vi.mocked(setOrganisationContext);
|
|
37
|
+
|
|
38
|
+
const mockUser = {
|
|
39
|
+
id: 'user-123',
|
|
40
|
+
email: 'test@example.com'
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const mockSession = {
|
|
44
|
+
access_token: 'token-123',
|
|
45
|
+
refresh_token: 'refresh-123'
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// Create proper thenable mock query builder
|
|
49
|
+
const mockQueryBuilder = createMockQueryBuilderWithData([{ id: 'record-123' }]);
|
|
50
|
+
const mockSupabase = createMockSupabaseClient([{ id: 'record-123' }]);
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
const mockSelectedOrganisation = {
|
|
54
|
+
id: 'org-123',
|
|
55
|
+
name: 'Test Organisation'
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
let freshMockQueryBuilder: any;
|
|
59
|
+
let freshMockSupabase: any;
|
|
60
|
+
|
|
61
|
+
beforeEach(() => {
|
|
62
|
+
vi.clearAllMocks();
|
|
63
|
+
|
|
64
|
+
// Recreate the mock to ensure it's fresh
|
|
65
|
+
freshMockQueryBuilder = createMockQueryBuilderWithData([{ id: 'record-123' }]);
|
|
66
|
+
freshMockSupabase = createMockSupabaseClient([{ id: 'record-123' }]);
|
|
67
|
+
freshMockSupabase.from.mockReturnValue(freshMockQueryBuilder);
|
|
68
|
+
|
|
69
|
+
mockUseUnifiedAuth.mockReturnValue({
|
|
70
|
+
user: mockUser,
|
|
71
|
+
session: mockSession,
|
|
72
|
+
supabase: freshMockSupabase,
|
|
73
|
+
isAuthenticated: true,
|
|
74
|
+
signOut: vi.fn(),
|
|
75
|
+
// Add other required properties
|
|
76
|
+
} as any);
|
|
77
|
+
|
|
78
|
+
mockUseOrganisations.mockReturnValue({
|
|
79
|
+
selectedOrganisation: mockSelectedOrganisation,
|
|
80
|
+
getUserRole: vi.fn().mockReturnValue('member'),
|
|
81
|
+
validateOrganisationAccess: vi.fn().mockResolvedValue(true),
|
|
82
|
+
ensureOrganisationContext: vi.fn().mockReturnValue(mockSelectedOrganisation),
|
|
83
|
+
// Add other required properties
|
|
84
|
+
} as any);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
afterEach(() => {
|
|
88
|
+
vi.restoreAllMocks();
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe('Hook Initialization', () => {
|
|
92
|
+
it('initializes with required dependencies', () => {
|
|
93
|
+
const { result } = renderHook(() => useSecureDataAccess());
|
|
94
|
+
|
|
95
|
+
expect(result.current).toBeDefined();
|
|
96
|
+
expect(result.current.secureQuery).toBeInstanceOf(Function);
|
|
97
|
+
expect(result.current.secureInsert).toBeInstanceOf(Function);
|
|
98
|
+
expect(result.current.secureUpdate).toBeInstanceOf(Function);
|
|
99
|
+
expect(result.current.secureDelete).toBeInstanceOf(Function);
|
|
100
|
+
expect(result.current.secureRpc).toBeInstanceOf(Function);
|
|
101
|
+
expect(result.current.getCurrentOrganisationId()).toBe('org-123');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('depends on useUnifiedAuth hook', () => {
|
|
105
|
+
renderHook(() => useSecureDataAccess());
|
|
106
|
+
expect(mockUseUnifiedAuth).toHaveBeenCalled();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('depends on useOrganisations hook', () => {
|
|
110
|
+
renderHook(() => useSecureDataAccess());
|
|
111
|
+
expect(mockUseOrganisations).toHaveBeenCalled();
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe('Secure Query Operations', () => {
|
|
116
|
+
it('executes secure query with organisation filtering', async () => {
|
|
117
|
+
const { result } = renderHook(() => useSecureDataAccess());
|
|
118
|
+
|
|
119
|
+
const data = await result.current.secureQuery('event', '*', { active: true });
|
|
120
|
+
|
|
121
|
+
expect(freshMockSupabase.from).toHaveBeenCalledWith('event');
|
|
122
|
+
expect(freshMockQueryBuilder.select).toHaveBeenCalledWith('*');
|
|
123
|
+
expect(freshMockQueryBuilder.eq).toHaveBeenCalledWith('organisation_id', 'org-123');
|
|
124
|
+
expect(freshMockQueryBuilder.eq).toHaveBeenCalledWith('active', true);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('executes secure query with custom filters', async () => {
|
|
128
|
+
const { result } = renderHook(() => useSecureDataAccess());
|
|
129
|
+
|
|
130
|
+
const data = await result.current.secureQuery('users', '*', { active: true });
|
|
131
|
+
|
|
132
|
+
expect(freshMockSupabase.from).toHaveBeenCalledWith('users');
|
|
133
|
+
expect(freshMockQueryBuilder.select).toHaveBeenCalledWith('*');
|
|
134
|
+
expect(freshMockQueryBuilder.eq).toHaveBeenCalledWith('active', true);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('executes secure query with ordering', async () => {
|
|
138
|
+
const { result } = renderHook(() => useSecureDataAccess());
|
|
139
|
+
|
|
140
|
+
const data = await result.current.secureQuery('users', '*', {}, {
|
|
141
|
+
orderBy: 'created_at',
|
|
142
|
+
ascending: false
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
expect(freshMockQueryBuilder.select).toHaveBeenCalledWith('*');
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('executes secure query with pagination', async () => {
|
|
149
|
+
const { result } = renderHook(() => useSecureDataAccess());
|
|
150
|
+
|
|
151
|
+
const data = await result.current.secureQuery('users', '*', {}, {
|
|
152
|
+
limit: 10,
|
|
153
|
+
offset: 20
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
expect(freshMockQueryBuilder.select).toHaveBeenCalledWith('*');
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('handles query errors gracefully', async () => {
|
|
160
|
+
// Mock error response - the query builder should reject with an error
|
|
161
|
+
freshMockQueryBuilder.then.mockImplementation((resolve, reject) => {
|
|
162
|
+
reject(new Error('Query failed'));
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
const { result } = renderHook(() => useSecureDataAccess());
|
|
166
|
+
|
|
167
|
+
await expect(result.current.secureQuery('users', '*')).rejects.toThrow('Query failed');
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
describe('Secure Insert Operations', () => {
|
|
172
|
+
it('executes secure insert with organisation context', async () => {
|
|
173
|
+
const { result } = renderHook(() => useSecureDataAccess());
|
|
174
|
+
|
|
175
|
+
const data = await result.current.secureInsert('users', { name: 'Test User' });
|
|
176
|
+
|
|
177
|
+
expect(freshMockSupabase.from).toHaveBeenCalledWith('users');
|
|
178
|
+
expect(freshMockQueryBuilder.insert).toHaveBeenCalledWith({
|
|
179
|
+
name: 'Test User',
|
|
180
|
+
organisation_id: 'org-123'
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('handles insert errors gracefully', async () => {
|
|
185
|
+
// Mock error response - the entire chain should reject
|
|
186
|
+
freshMockQueryBuilder.single.mockRejectedValue(new Error('Insert failed'));
|
|
187
|
+
|
|
188
|
+
const { result } = renderHook(() => useSecureDataAccess());
|
|
189
|
+
|
|
190
|
+
await expect(result.current.secureInsert('users', { name: 'Test User' })).rejects.toThrow('Insert failed');
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('prevents organisation_id override', async () => {
|
|
194
|
+
const { result } = renderHook(() => useSecureDataAccess());
|
|
195
|
+
|
|
196
|
+
const data = await result.current.secureInsert('users', {
|
|
197
|
+
name: 'Test User',
|
|
198
|
+
organisation_id: 'malicious-org-id'
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
expect(freshMockQueryBuilder.insert).toHaveBeenCalledWith({
|
|
202
|
+
name: 'Test User',
|
|
203
|
+
organisation_id: 'org-123' // Should override malicious value
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
describe('Secure Update Operations', () => {
|
|
209
|
+
it('executes secure update with organisation filtering', async () => {
|
|
210
|
+
const { result } = renderHook(() => useSecureDataAccess());
|
|
211
|
+
|
|
212
|
+
const data = await result.current.secureUpdate('event', { event_name: 'Updated Event' }, { id: 'event-123' });
|
|
213
|
+
|
|
214
|
+
expect(freshMockSupabase.from).toHaveBeenCalledWith('event');
|
|
215
|
+
expect(freshMockQueryBuilder.update).toHaveBeenCalledWith({ event_name: 'Updated Event' });
|
|
216
|
+
expect(freshMockQueryBuilder.eq).toHaveBeenCalledWith('id', 'event-123');
|
|
217
|
+
expect(freshMockQueryBuilder.eq).toHaveBeenCalledWith('organisation_id', 'org-123');
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('handles update errors gracefully', async () => {
|
|
221
|
+
// Mock error response - mock the entire chain to throw an error
|
|
222
|
+
const errorQueryBuilder = {
|
|
223
|
+
...freshMockQueryBuilder,
|
|
224
|
+
then: vi.fn().mockImplementation(() => {
|
|
225
|
+
throw new Error('Update failed');
|
|
226
|
+
})
|
|
227
|
+
};
|
|
228
|
+
freshMockSupabase.from.mockReturnValue(errorQueryBuilder);
|
|
229
|
+
|
|
230
|
+
const { result } = renderHook(() => useSecureDataAccess());
|
|
231
|
+
|
|
232
|
+
await expect(result.current.secureUpdate('users', { name: 'Updated User' }, { id: 'user-123' })).rejects.toThrow('Update failed');
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it('prevents organisation_id updates', async () => {
|
|
236
|
+
const { result } = renderHook(() => useSecureDataAccess());
|
|
237
|
+
|
|
238
|
+
const data = await result.current.secureUpdate('users', {
|
|
239
|
+
name: 'Updated User',
|
|
240
|
+
organisation_id: 'malicious-org-id'
|
|
241
|
+
}, { id: 'user-123' });
|
|
242
|
+
|
|
243
|
+
expect(freshMockQueryBuilder.update).toHaveBeenCalledWith({
|
|
244
|
+
name: 'Updated User'
|
|
245
|
+
// organisation_id should be filtered out
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
describe('Secure Delete Operations', () => {
|
|
251
|
+
it('executes secure delete with organisation filtering', async () => {
|
|
252
|
+
const { result } = renderHook(() => useSecureDataAccess());
|
|
253
|
+
|
|
254
|
+
await result.current.secureDelete('event', { id: 'event-123' });
|
|
255
|
+
|
|
256
|
+
expect(freshMockSupabase.from).toHaveBeenCalledWith('event');
|
|
257
|
+
expect(freshMockQueryBuilder.delete).toHaveBeenCalled();
|
|
258
|
+
expect(freshMockQueryBuilder.eq).toHaveBeenCalledWith('id', 'event-123');
|
|
259
|
+
expect(freshMockQueryBuilder.eq).toHaveBeenCalledWith('organisation_id', 'org-123');
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it('handles delete errors gracefully', async () => {
|
|
263
|
+
// Mock error response - mock the entire chain to throw an error
|
|
264
|
+
const errorQueryBuilder = {
|
|
265
|
+
...freshMockQueryBuilder,
|
|
266
|
+
then: vi.fn().mockImplementation(() => {
|
|
267
|
+
throw new Error('Delete failed');
|
|
268
|
+
})
|
|
269
|
+
};
|
|
270
|
+
freshMockSupabase.from.mockReturnValue(errorQueryBuilder);
|
|
271
|
+
|
|
272
|
+
const { result } = renderHook(() => useSecureDataAccess());
|
|
273
|
+
|
|
274
|
+
await expect(result.current.secureDelete('users', { id: 'user-123' })).rejects.toThrow('Delete failed');
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
describe('Secure RPC Operations', () => {
|
|
279
|
+
it('executes secure RPC with organisation context', async () => {
|
|
280
|
+
const { result } = renderHook(() => useSecureDataAccess());
|
|
281
|
+
|
|
282
|
+
const data = await result.current.secureRpc('get_user_data', { user_id: 'user-123' });
|
|
283
|
+
|
|
284
|
+
expect(freshMockSupabase.rpc).toHaveBeenCalledWith('get_user_data', {
|
|
285
|
+
user_id: 'user-123',
|
|
286
|
+
organisation_id: 'org-123'
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it('handles RPC errors gracefully', async () => {
|
|
291
|
+
// Mock error response - the RPC should reject with an error
|
|
292
|
+
freshMockSupabase.rpc.mockRejectedValue(new Error('RPC failed'));
|
|
293
|
+
|
|
294
|
+
const { result } = renderHook(() => useSecureDataAccess());
|
|
295
|
+
|
|
296
|
+
await expect(result.current.secureRpc('get_user_data')).rejects.toThrow('RPC failed');
|
|
297
|
+
});
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
describe('Organisation Context Management', () => {
|
|
301
|
+
it('sets organisation context before operations', async () => {
|
|
302
|
+
const { result } = renderHook(() => useSecureDataAccess());
|
|
303
|
+
|
|
304
|
+
await result.current.secureQuery('users', '*');
|
|
305
|
+
|
|
306
|
+
expect(mockSetOrganisationContext).toHaveBeenCalledWith(freshMockSupabase, 'org-123');
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it('handles missing organisation context', () => {
|
|
310
|
+
mockUseOrganisations.mockReturnValue({
|
|
311
|
+
selectedOrganisation: null,
|
|
312
|
+
getUserRole: vi.fn().mockReturnValue('no_access'),
|
|
313
|
+
validateOrganisationAccess: vi.fn().mockResolvedValue(false),
|
|
314
|
+
ensureOrganisationContext: vi.fn().mockImplementation(() => {
|
|
315
|
+
throw new Error('Organisation context is required but not available');
|
|
316
|
+
}),
|
|
317
|
+
// Add other required properties
|
|
318
|
+
} as any);
|
|
319
|
+
|
|
320
|
+
const { result } = renderHook(() => useSecureDataAccess());
|
|
321
|
+
|
|
322
|
+
expect(() => result.current.getCurrentOrganisationId()).toThrow('Organisation context is required for data access');
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
it('validates organisation access before operations', async () => {
|
|
326
|
+
const mockValidateAccess = vi.fn().mockResolvedValue(true);
|
|
327
|
+
mockUseOrganisations.mockReturnValue({
|
|
328
|
+
selectedOrganisation: mockSelectedOrganisation,
|
|
329
|
+
getUserRole: vi.fn().mockReturnValue('member'),
|
|
330
|
+
validateOrganisationAccess: mockValidateAccess,
|
|
331
|
+
ensureOrganisationContext: vi.fn().mockReturnValue(mockSelectedOrganisation),
|
|
332
|
+
// Add other required properties
|
|
333
|
+
} as any);
|
|
334
|
+
|
|
335
|
+
const { result } = renderHook(() => useSecureDataAccess());
|
|
336
|
+
|
|
337
|
+
await result.current.secureQuery('users', '*');
|
|
338
|
+
|
|
339
|
+
// The hook doesn't call validateOrganisationAccess, it calls ensureOrganisationContext
|
|
340
|
+
expect(mockUseOrganisations().ensureOrganisationContext).toHaveBeenCalled();
|
|
341
|
+
});
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
describe('Error Handling', () => {
|
|
345
|
+
it('handles missing user context', () => {
|
|
346
|
+
mockUseUnifiedAuth.mockReturnValue({
|
|
347
|
+
user: null,
|
|
348
|
+
session: null,
|
|
349
|
+
supabase: null,
|
|
350
|
+
isAuthenticated: false,
|
|
351
|
+
signOut: vi.fn(),
|
|
352
|
+
// Add other required properties
|
|
353
|
+
} as any);
|
|
354
|
+
|
|
355
|
+
const { result } = renderHook(() => useSecureDataAccess());
|
|
356
|
+
|
|
357
|
+
expect(() => result.current.getCurrentOrganisationId()).toThrow('No Supabase client available');
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
it('handles missing supabase client', () => {
|
|
361
|
+
mockUseUnifiedAuth.mockReturnValue({
|
|
362
|
+
user: mockUser,
|
|
363
|
+
session: mockSession,
|
|
364
|
+
supabase: null,
|
|
365
|
+
isAuthenticated: true,
|
|
366
|
+
signOut: vi.fn(),
|
|
367
|
+
// Add other required properties
|
|
368
|
+
} as any);
|
|
369
|
+
|
|
370
|
+
const { result } = renderHook(() => useSecureDataAccess());
|
|
371
|
+
|
|
372
|
+
expect(() => result.current.getCurrentOrganisationId()).toThrow('No Supabase client available');
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
it('handles organisation access validation failures', async () => {
|
|
376
|
+
mockUseOrganisations.mockReturnValue({
|
|
377
|
+
selectedOrganisation: mockSelectedOrganisation,
|
|
378
|
+
getUserRole: vi.fn().mockReturnValue('no_access'),
|
|
379
|
+
validateOrganisationAccess: vi.fn().mockResolvedValue(false),
|
|
380
|
+
// Add other required properties
|
|
381
|
+
} as any);
|
|
382
|
+
|
|
383
|
+
const { result } = renderHook(() => useSecureDataAccess());
|
|
384
|
+
|
|
385
|
+
await expect(result.current.secureQuery('users', '*')).rejects.toThrow();
|
|
386
|
+
});
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
describe('Security Features', () => {
|
|
390
|
+
it('prevents data leaks between organisations', async () => {
|
|
391
|
+
const { result } = renderHook(() => useSecureDataAccess());
|
|
392
|
+
|
|
393
|
+
await result.current.secureQuery('event', '*');
|
|
394
|
+
|
|
395
|
+
// Verify organisation_id filter is always applied
|
|
396
|
+
expect(freshMockQueryBuilder.eq).toHaveBeenCalledWith('organisation_id', 'org-123');
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
it('prevents organisation_id manipulation in inserts', async () => {
|
|
400
|
+
const { result } = renderHook(() => useSecureDataAccess());
|
|
401
|
+
|
|
402
|
+
await result.current.secureInsert('users', {
|
|
403
|
+
name: 'Test User',
|
|
404
|
+
organisation_id: 'malicious-org-id'
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
expect(freshMockQueryBuilder.insert).toHaveBeenCalledWith({
|
|
408
|
+
name: 'Test User',
|
|
409
|
+
organisation_id: 'org-123' // Should override malicious value
|
|
410
|
+
});
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
it('prevents organisation_id manipulation in updates', async () => {
|
|
414
|
+
const { result } = renderHook(() => useSecureDataAccess());
|
|
415
|
+
|
|
416
|
+
await result.current.secureUpdate('users', {
|
|
417
|
+
name: 'Updated User',
|
|
418
|
+
organisation_id: 'malicious-org-id'
|
|
419
|
+
}, { id: 'user-123' });
|
|
420
|
+
|
|
421
|
+
expect(freshMockQueryBuilder.update).toHaveBeenCalledWith({
|
|
422
|
+
name: 'Updated User'
|
|
423
|
+
// organisation_id should be filtered out
|
|
424
|
+
});
|
|
425
|
+
});
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
describe('Performance', () => {
|
|
429
|
+
it('memoizes results to prevent unnecessary re-renders', () => {
|
|
430
|
+
const { result, rerender } = renderHook(() => useSecureDataAccess());
|
|
431
|
+
const initialSecureQuery = result.current.secureQuery;
|
|
432
|
+
const initialGetCurrentOrganisationId = result.current.getCurrentOrganisationId;
|
|
433
|
+
|
|
434
|
+
// Re-render with same props
|
|
435
|
+
rerender();
|
|
436
|
+
|
|
437
|
+
// Should not cause additional renders due to memoization
|
|
438
|
+
expect(result.current.secureQuery).toBe(initialSecureQuery);
|
|
439
|
+
expect(result.current.getCurrentOrganisationId).toBe(initialGetCurrentOrganisationId);
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
it('handles organisation changes efficiently', () => {
|
|
443
|
+
const { result, rerender } = renderHook(() => useSecureDataAccess());
|
|
444
|
+
|
|
445
|
+
expect(result.current.getCurrentOrganisationId()).toBe('org-123');
|
|
446
|
+
|
|
447
|
+
// Change organisation
|
|
448
|
+
const newOrganisation = { id: 'org-456', name: 'New Organisation' };
|
|
449
|
+
mockUseOrganisations.mockReturnValue({
|
|
450
|
+
selectedOrganisation: newOrganisation,
|
|
451
|
+
getUserRole: vi.fn().mockReturnValue('member'),
|
|
452
|
+
validateOrganisationAccess: vi.fn().mockResolvedValue(true),
|
|
453
|
+
ensureOrganisationContext: vi.fn().mockReturnValue(newOrganisation),
|
|
454
|
+
// Add other required properties
|
|
455
|
+
} as any);
|
|
456
|
+
|
|
457
|
+
rerender();
|
|
458
|
+
|
|
459
|
+
expect(result.current.getCurrentOrganisationId()).toBe('org-456');
|
|
460
|
+
});
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
describe('Integration with Providers', () => {
|
|
464
|
+
it('integrates with useUnifiedAuth hook correctly', () => {
|
|
465
|
+
renderHook(() => useSecureDataAccess());
|
|
466
|
+
expect(mockUseUnifiedAuth).toHaveBeenCalled();
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
it('integrates with useOrganisations hook correctly', () => {
|
|
470
|
+
renderHook(() => useSecureDataAccess());
|
|
471
|
+
expect(mockUseOrganisations).toHaveBeenCalled();
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
it('uses organisation context from provider', () => {
|
|
475
|
+
const { result } = renderHook(() => useSecureDataAccess());
|
|
476
|
+
|
|
477
|
+
expect(result.current.getCurrentOrganisationId()).toBe('org-123');
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
it('validates organisation access through provider', () => {
|
|
481
|
+
// This test is removed as validateOrganisationAccess is not used in useSecureDataAccess
|
|
482
|
+
// The hook only calls ensureOrganisationContext during operations
|
|
483
|
+
expect(true).toBe(true);
|
|
484
|
+
});
|
|
485
|
+
});
|
|
486
|
+
});
|
|
@@ -335,10 +335,13 @@ export function useSecureDataAccess(): SecureDataAccessReturn {
|
|
|
335
335
|
// Set organisation context in database session
|
|
336
336
|
await setOrganisationContextInSession(organisationId);
|
|
337
337
|
|
|
338
|
+
// Filter out organisation_id from data to prevent manipulation
|
|
339
|
+
const { organisation_id, ...secureData } = data;
|
|
340
|
+
|
|
338
341
|
// Build update query with organisation filter
|
|
339
342
|
let query = supabase!
|
|
340
343
|
.from(table)
|
|
341
|
-
.update(
|
|
344
|
+
.update(secureData);
|
|
342
345
|
|
|
343
346
|
// Add organisation filter only if table has organisation_id column
|
|
344
347
|
const tablesWithOrganisation = [
|
|
@@ -75,7 +75,7 @@ export function InactivityProvider({
|
|
|
75
75
|
// Production safety check for inactivity feature
|
|
76
76
|
useEffect(() => {
|
|
77
77
|
if (typeof window !== 'undefined') {
|
|
78
|
-
const isProduction =
|
|
78
|
+
const isProduction = import.meta.env.MODE === 'production';
|
|
79
79
|
|
|
80
80
|
if (isProduction && dangerouslyDisableInactivity) {
|
|
81
81
|
console.error('[InactivityProvider] CRITICAL: dangerouslyDisableInactivity is not allowed in production! Auto-enabling inactivity feature.');
|
|
@@ -89,7 +89,7 @@ export function InactivityProvider({
|
|
|
89
89
|
|
|
90
90
|
// Inactivity tracking
|
|
91
91
|
const isInactivityEnabled = typeof window !== 'undefined' &&
|
|
92
|
-
(
|
|
92
|
+
(import.meta.env.MODE !== 'production' ? !dangerouslyDisableInactivity : true);
|
|
93
93
|
|
|
94
94
|
const {
|
|
95
95
|
isIdle,
|