@jmruthers/pace-core 0.5.189 → 0.5.191
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-IVYljGJ6.d.ts → DataTable-Be6dH_dR.d.ts} +1 -1
- package/dist/{DataTable-GUFUNZ3N.js → DataTable-WKRZD47S.js} +8 -8
- package/dist/{PublicPageProvider-B8HaLe69.d.ts → PublicPageProvider-ULXC_u6U.d.ts} +84 -25
- package/dist/{UnifiedAuthProvider-BG0AL5eE.d.ts → UnifiedAuthProvider-BYA9qB-o.d.ts} +4 -3
- package/dist/{UnifiedAuthProvider-643PUAIM.js → UnifiedAuthProvider-FTSG5XH7.js} +4 -2
- package/dist/{api-YP7XD5L6.js → api-IHKALJZD.js} +4 -2
- package/dist/{chunk-VGZZXKBR.js → chunk-6LTQQAT6.js} +351 -157
- package/dist/chunk-6LTQQAT6.js.map +1 -0
- package/dist/{chunk-MX64ZF6I.js → chunk-6TQDD426.js} +15 -15
- package/dist/chunk-6TQDD426.js.map +1 -0
- package/dist/{chunk-YHCN776L.js → chunk-G37KK66H.js} +2 -75
- package/dist/chunk-G37KK66H.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-IM4QE42D.js → chunk-LOMZXPSN.js} +141 -326
- package/dist/chunk-LOMZXPSN.js.map +1 -0
- package/dist/chunk-OETXORNB.js +614 -0
- package/dist/chunk-OETXORNB.js.map +1 -0
- package/dist/{chunk-HESYZWZW.js → chunk-QWWZ5CAQ.js} +2 -2
- package/dist/{chunk-HEHYGYOX.js → chunk-ROXMHMY2.js} +403 -46
- package/dist/chunk-ROXMHMY2.js.map +1 -0
- package/dist/{chunk-2UUZZJFT.js → chunk-ULHIJK66.js} +228 -177
- package/dist/{chunk-2UUZZJFT.js.map → chunk-ULHIJK66.js.map} +1 -1
- package/dist/{chunk-YGPFYGA6.js → chunk-VKB2CO4Z.js} +838 -503
- package/dist/chunk-VKB2CO4Z.js.map +1 -0
- package/dist/{chunk-3GOZZZYH.js → chunk-VRGWKHDB.js} +238 -301
- package/dist/chunk-VRGWKHDB.js.map +1 -0
- package/dist/{chunk-UCQSRW7Z.js → chunk-XNYQOL3Z.js} +431 -384
- package/dist/chunk-XNYQOL3Z.js.map +1 -0
- package/dist/{chunk-DDM4CCYT.js → chunk-XYXSXPUK.js} +79 -59
- package/dist/chunk-XYXSXPUK.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 +5 -6
- package/dist/components.js +19 -19
- package/dist/components.js.map +1 -1
- package/dist/{database.generated-DI89OQeI.d.ts → database.generated-CzIvgcPu.d.ts} +165 -201
- 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 +20 -15
- package/dist/hooks.js +14 -8
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +17 -15
- package/dist/index.js +86 -81
- 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 +77 -13
- package/dist/rbac/index.js +12 -9
- package/dist/{types-Bwgl--Xo.d.ts → types-CEpcvwwF.d.ts} +1 -1
- package/dist/types.d.ts +3 -3
- package/dist/types.js +1 -1
- package/dist/{usePublicRouteParams-CTDELQ7H.d.ts → usePublicRouteParams-TZe0gy-4.d.ts} +17 -10
- package/dist/utils.d.ts +8 -8
- 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 +2 -2
- package/docs/api/classes/RBACCache.md +1 -1
- package/docs/api/classes/RBACEngine.md +5 -5
- package/docs/api/classes/RBACError.md +1 -1
- package/docs/api/classes/RBACNotInitializedError.md +2 -2
- package/docs/api/classes/SecureSupabaseClient.md +25 -20
- package/docs/api/classes/StorageUtils.md +7 -4
- package/docs/api/enums/FileCategory.md +1 -1
- package/docs/api/enums/LogLevel.md +1 -1
- package/docs/api/enums/RBACErrorCode.md +1 -1
- package/docs/api/enums/RPCFunction.md +1 -1
- package/docs/api/interfaces/AddressFieldProps.md +1 -1
- package/docs/api/interfaces/AddressFieldRef.md +1 -1
- package/docs/api/interfaces/AggregateConfig.md +1 -1
- package/docs/api/interfaces/AutocompleteOptions.md +1 -1
- package/docs/api/interfaces/AvatarProps.md +1 -1
- package/docs/api/interfaces/BadgeProps.md +1 -1
- package/docs/api/interfaces/ButtonProps.md +1 -1
- package/docs/api/interfaces/CalendarProps.md +20 -6
- package/docs/api/interfaces/CardProps.md +1 -1
- package/docs/api/interfaces/ColorPalette.md +1 -1
- package/docs/api/interfaces/ColorShade.md +1 -1
- package/docs/api/interfaces/ComplianceResult.md +1 -1
- package/docs/api/interfaces/DataAccessRecord.md +9 -9
- package/docs/api/interfaces/DataRecord.md +1 -1
- package/docs/api/interfaces/DataTableAction.md +1 -1
- package/docs/api/interfaces/DataTableColumn.md +1 -1
- package/docs/api/interfaces/DataTableProps.md +1 -1
- package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
- package/docs/api/interfaces/DatabaseComplianceResult.md +1 -1
- package/docs/api/interfaces/DatabaseIssue.md +1 -1
- package/docs/api/interfaces/EmptyStateConfig.md +1 -1
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
- package/docs/api/interfaces/EventAppRoleData.md +1 -1
- package/docs/api/interfaces/ExportColumn.md +1 -1
- package/docs/api/interfaces/ExportOptions.md +1 -1
- package/docs/api/interfaces/FileDisplayProps.md +62 -16
- package/docs/api/interfaces/FileMetadata.md +1 -1
- package/docs/api/interfaces/FileReference.md +2 -2
- package/docs/api/interfaces/FileSizeLimits.md +1 -1
- package/docs/api/interfaces/FileUploadOptions.md +26 -12
- package/docs/api/interfaces/FileUploadProps.md +30 -19
- package/docs/api/interfaces/FooterProps.md +1 -1
- package/docs/api/interfaces/FormFieldProps.md +1 -1
- package/docs/api/interfaces/FormProps.md +1 -1
- package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
- package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
- package/docs/api/interfaces/InputProps.md +1 -1
- package/docs/api/interfaces/LabelProps.md +1 -1
- package/docs/api/interfaces/LoggerConfig.md +1 -1
- package/docs/api/interfaces/LoginFormProps.md +1 -1
- package/docs/api/interfaces/NavigationAccessRecord.md +10 -10
- package/docs/api/interfaces/NavigationContextType.md +9 -9
- package/docs/api/interfaces/NavigationGuardProps.md +1 -1
- package/docs/api/interfaces/NavigationItem.md +1 -1
- package/docs/api/interfaces/NavigationMenuProps.md +1 -1
- package/docs/api/interfaces/NavigationProviderProps.md +7 -7
- package/docs/api/interfaces/Organisation.md +1 -1
- package/docs/api/interfaces/OrganisationContextType.md +1 -1
- package/docs/api/interfaces/OrganisationMembership.md +1 -1
- package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
- package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
- package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
- package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
- package/docs/api/interfaces/PageAccessRecord.md +8 -8
- package/docs/api/interfaces/PagePermissionContextType.md +8 -8
- package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
- package/docs/api/interfaces/PagePermissionProviderProps.md +7 -7
- package/docs/api/interfaces/PaletteData.md +1 -1
- package/docs/api/interfaces/ParsedAddress.md +2 -2
- 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 +2 -2
- 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 +2 -2
- 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 +2 -2
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeOptions.md +5 -5
- 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 +165 -106
- package/docs/api-reference/components.md +15 -7
- package/docs/api-reference/providers.md +2 -2
- package/docs/api-reference/rpc-functions.md +1 -0
- package/docs/best-practices/README.md +1 -1
- package/docs/best-practices/deployment.md +8 -8
- package/docs/getting-started/examples/README.md +2 -2
- package/docs/getting-started/installation-guide.md +4 -4
- package/docs/getting-started/quick-start.md +3 -3
- package/docs/migration/MIGRATION_GUIDE.md +3 -3
- package/docs/migration/README.md +18 -0
- package/docs/migration/database-changes-december-2025.md +767 -0
- package/docs/migration/person-scoped-profiles-migration-guide.md +472 -0
- package/docs/rbac/compliance/compliance-guide.md +2 -2
- package/docs/rbac/event-based-apps.md +2 -2
- package/docs/rbac/getting-started.md +2 -2
- package/docs/rbac/quick-start.md +2 -2
- package/docs/security/README.md +4 -4
- package/docs/standards/07-rbac-and-rls-standard.md +430 -7
- package/docs/troubleshooting/README.md +2 -2
- package/docs/troubleshooting/migration.md +3 -3
- package/package.json +1 -3
- package/scripts/check-pace-core-compliance.cjs +1 -1
- package/scripts/check-pace-core-compliance.js +1 -1
- package/src/__tests__/fixtures/supabase.ts +301 -0
- package/src/__tests__/public-recipe-view.test.ts +19 -19
- package/src/__tests__/rls-policies.test.ts +210 -74
- package/src/components/AddressField/AddressField.test.tsx +42 -0
- package/src/components/AddressField/AddressField.tsx +71 -60
- package/src/components/AddressField/README.md +7 -6
- package/src/components/Alert/Alert.test.tsx +50 -10
- package/src/components/Alert/Alert.tsx +5 -3
- package/src/components/Avatar/Avatar.test.tsx +95 -43
- package/src/components/Avatar/Avatar.tsx +16 -16
- package/src/components/Button/Button.test.tsx +2 -1
- package/src/components/Button/Button.tsx +3 -3
- package/src/components/Calendar/Calendar.test.tsx +53 -37
- package/src/components/Calendar/Calendar.tsx +409 -82
- package/src/components/Card/Card.test.tsx +7 -4
- package/src/components/Card/Card.tsx +3 -6
- package/src/components/Checkbox/Checkbox.tsx +2 -2
- package/src/components/DataTable/components/ActionButtons.tsx +5 -5
- package/src/components/DataTable/components/BulkOperationsDropdown.tsx +2 -2
- package/src/components/DataTable/components/ColumnFilter.tsx +1 -1
- package/src/components/DataTable/components/ColumnVisibilityDropdown.tsx +3 -3
- package/src/components/DataTable/components/DataTableBody.tsx +12 -12
- package/src/components/DataTable/components/DataTableCore.tsx +3 -3
- package/src/components/DataTable/components/DataTableToolbar.tsx +5 -5
- package/src/components/DataTable/components/DraggableColumnHeader.tsx +3 -3
- package/src/components/DataTable/components/EditableRow.tsx +2 -2
- package/src/components/DataTable/components/EmptyState.tsx +3 -3
- package/src/components/DataTable/components/GroupHeader.tsx +2 -2
- package/src/components/DataTable/components/GroupingDropdown.tsx +1 -1
- package/src/components/DataTable/components/ImportModal.tsx +4 -4
- package/src/components/DataTable/components/LoadingState.tsx +1 -1
- package/src/components/DataTable/components/PaginationControls.tsx +11 -11
- package/src/components/DataTable/components/UnifiedTableBody.tsx +9 -9
- package/src/components/DataTable/components/ViewRowModal.tsx +2 -2
- package/src/components/DataTable/components/__tests__/AccessDeniedPage.test.tsx +11 -37
- package/src/components/DataTable/components/__tests__/DataTableToolbar.test.tsx +157 -0
- package/src/components/DataTable/components/__tests__/LoadingState.test.tsx +2 -1
- package/src/components/DataTable/components/__tests__/VirtualizedDataTable.test.tsx +128 -0
- package/src/components/DataTable/core/__tests__/ActionManager.test.ts +19 -0
- package/src/components/DataTable/core/__tests__/ColumnFactory.test.ts +51 -0
- package/src/components/DataTable/core/__tests__/ColumnManager.test.ts +84 -0
- package/src/components/DataTable/core/__tests__/DataManager.test.ts +14 -0
- package/src/components/DataTable/core/__tests__/DataTableContext.test.tsx +136 -0
- package/src/components/DataTable/core/__tests__/LocalDataAdapter.test.ts +16 -0
- package/src/components/DataTable/core/__tests__/PluginRegistry.test.ts +18 -0
- package/src/components/DataTable/hooks/useDataTablePermissions.ts +28 -7
- package/src/components/DataTable/utils/__tests__/hierarchicalUtils.test.ts +30 -1
- package/src/components/DataTable/utils/hierarchicalUtils.ts +38 -10
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +8 -3
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.tsx +4 -4
- package/src/components/Dialog/Dialog.tsx +2 -2
- package/src/components/EventSelector/EventSelector.tsx +7 -7
- package/src/components/FileDisplay/FileDisplay.tsx +291 -179
- package/src/components/FileUpload/FileUpload.tsx +7 -4
- package/src/components/Header/Header.test.tsx +28 -0
- package/src/components/Header/Header.tsx +22 -9
- package/src/components/InactivityWarningModal/InactivityWarningModal.tsx +2 -2
- package/src/components/LoadingSpinner/LoadingSpinner.test.tsx +19 -14
- package/src/components/LoadingSpinner/LoadingSpinner.tsx +5 -5
- package/src/components/NavigationMenu/NavigationMenu.test.tsx +127 -1
- package/src/components/OrganisationSelector/OrganisationSelector.tsx +42 -22
- 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.test.tsx +4 -1
- package/src/components/Select/Select.tsx +65 -20
- package/src/components/Switch/Switch.test.tsx +2 -1
- package/src/components/Switch/Switch.tsx +1 -1
- package/src/components/Toast/Toast.tsx +1 -1
- package/src/components/Tooltip/Tooltip.test.tsx +8 -2
- package/src/components/UserMenu/UserMenu.tsx +3 -3
- package/src/eslint-rules/pace-core-compliance.cjs +0 -2
- package/src/eslint-rules/pace-core-compliance.js +0 -2
- package/src/hooks/__tests__/hooks.integration.test.tsx +4 -1
- package/src/hooks/__tests__/useAppConfig.unit.test.ts +76 -5
- package/src/hooks/__tests__/useDataTableState.test.ts +76 -0
- package/src/hooks/__tests__/useFileUrl.unit.test.ts +25 -69
- package/src/hooks/__tests__/useFileUrlCache.test.ts +129 -0
- package/src/hooks/__tests__/usePreventTabReload.test.ts +88 -0
- package/src/hooks/__tests__/usePublicEvent.simple.test.ts +1 -1
- package/src/hooks/__tests__/usePublicEvent.test.ts +608 -0
- package/src/hooks/__tests__/useQueryCache.test.ts +144 -0
- package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +67 -24
- package/src/hooks/index.ts +1 -1
- package/src/hooks/public/usePublicEvent.ts +10 -10
- package/src/hooks/public/usePublicFileDisplay.ts +173 -87
- package/src/hooks/useAppConfig.ts +24 -5
- package/src/hooks/useFileDisplay.ts +298 -36
- 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 +27 -6
- package/src/hooks/useSecureDataAccess.test.ts +87 -42
- package/src/hooks/useSecureDataAccess.ts +95 -48
- package/src/providers/__tests__/OrganisationProvider.test.tsx +27 -21
- 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 +8 -3
- package/src/rbac/utils/__tests__/eventContext.unit.test.ts +74 -12
- package/src/rbac/utils/contextValidator.ts +288 -0
- package/src/rbac/utils/eventContext.ts +52 -3
- package/src/services/AuthService.ts +37 -8
- package/src/services/EventService.ts +165 -21
- package/src/services/OrganisationService.ts +125 -137
- package/src/services/__tests__/EventService.test.ts +26 -21
- package/src/services/__tests__/OrganisationService.pagination.test.ts +34 -8
- package/src/services/__tests__/OrganisationService.test.ts +218 -86
- package/src/types/database.generated.ts +166 -201
- package/src/types/file-reference.ts +13 -10
- package/src/types/supabase.ts +2 -2
- package/src/utils/__tests__/secureDataAccess.unit.test.ts +3 -2
- package/src/utils/app/appNameResolver.test.ts +346 -73
- package/src/utils/context/superAdminOverride.ts +58 -0
- package/src/utils/file-reference/index.ts +65 -37
- package/src/utils/google-places/googlePlacesUtils.test.ts +98 -0
- package/src/utils/google-places/googlePlacesUtils.ts +1 -1
- package/src/utils/google-places/loadGoogleMapsScript.test.ts +83 -0
- package/src/utils/google-places/types.ts +1 -1
- package/src/utils/request-deduplication.ts +4 -4
- package/src/utils/security/secureDataAccess.test.ts +1 -1
- package/src/utils/security/secureDataAccess.ts +7 -4
- package/src/utils/storage/README.md +1 -1
- package/src/utils/storage/helpers.test.ts +1 -1
- package/src/utils/storage/helpers.ts +38 -19
- package/src/utils/storage/types.ts +15 -8
- package/src/utils/validation/__tests__/csrf.test.ts +105 -0
- package/src/utils/validation/__tests__/sqlInjectionProtection.test.ts +92 -0
- package/src/vite-env.d.ts +2 -2
- package/dist/chunk-3GOZZZYH.js.map +0 -1
- package/dist/chunk-DDM4CCYT.js.map +0 -1
- package/dist/chunk-E7UAOUMY.js +0 -75
- package/dist/chunk-E7UAOUMY.js.map +0 -1
- package/dist/chunk-F2IMUDXZ.js.map +0 -1
- package/dist/chunk-HEHYGYOX.js.map +0 -1
- package/dist/chunk-IM4QE42D.js.map +0 -1
- package/dist/chunk-MX64ZF6I.js.map +0 -1
- package/dist/chunk-SAUPYVLF.js.map +0 -1
- package/dist/chunk-THRPYOFK.js.map +0 -1
- package/dist/chunk-UCQSRW7Z.js.map +0 -1
- package/dist/chunk-VGZZXKBR.js.map +0 -1
- package/dist/chunk-YGPFYGA6.js.map +0 -1
- package/dist/chunk-YHCN776L.js.map +0 -1
- /package/dist/{DataTable-GUFUNZ3N.js.map → DataTable-WKRZD47S.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-643PUAIM.js.map → UnifiedAuthProvider-FTSG5XH7.js.map} +0 -0
- /package/dist/{api-YP7XD5L6.js.map → api-IHKALJZD.js.map} +0 -0
- /package/dist/{chunk-HESYZWZW.js.map → chunk-QWWZ5CAQ.js.map} +0 -0
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* const loadData = async () => {
|
|
16
16
|
* try {
|
|
17
17
|
* // Automatically includes organisation_id filter
|
|
18
|
-
* const events = await secureQuery('
|
|
18
|
+
* const events = await secureQuery('core_events', '*', { is_visible: true });
|
|
19
19
|
* console.log('Organisation events:', events);
|
|
20
20
|
* } catch (error) {
|
|
21
21
|
* console.error('Failed to load data:', error);
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
* const createEvent = async (eventData) => {
|
|
26
26
|
* try {
|
|
27
27
|
* // Automatically sets organisation_id
|
|
28
|
-
* const newEvent = await secureInsert('
|
|
28
|
+
* const newEvent = await secureInsert('core_events', eventData);
|
|
29
29
|
* console.log('Created event:', newEvent);
|
|
30
30
|
* } catch (error) {
|
|
31
31
|
* console.error('Failed to create event:', error);
|
|
@@ -59,6 +59,8 @@ import { setOrganisationContext } from '../utils/context/organisationContext';
|
|
|
59
59
|
import { logger } from '../utils/core/logger';
|
|
60
60
|
import type { Permission } from '../rbac/types';
|
|
61
61
|
import type { OrganisationSecurityError } from '../types/organisation';
|
|
62
|
+
import { useSuperAdminBypass } from '../rbac/hooks/useSuperAdminBypass';
|
|
63
|
+
import { useResolvedScope } from '../rbac/hooks/useResolvedScope';
|
|
62
64
|
|
|
63
65
|
export interface SecureDataAccessReturn {
|
|
64
66
|
/** Execute a secure query with organisation filtering */
|
|
@@ -149,13 +151,21 @@ export interface DataAccessRecord {
|
|
|
149
151
|
* - RPC calls include organisation_id parameter
|
|
150
152
|
*/
|
|
151
153
|
export function useSecureDataAccess(): SecureDataAccessReturn {
|
|
152
|
-
const { supabase, user, session } = useUnifiedAuth();
|
|
153
|
-
const { ensureOrganisationContext } = useOrganisations();
|
|
154
|
+
const { supabase, user, session, selectedOrganisation, selectedEvent } = useUnifiedAuth();
|
|
154
155
|
|
|
155
156
|
// Get selected event for event-scoped RPC calls
|
|
156
157
|
// Use useContext directly to safely check if EventServiceProvider is available
|
|
157
158
|
const eventServiceContext = useContext(EventServiceContext);
|
|
158
|
-
const
|
|
159
|
+
const eventFromContext = eventServiceContext?.eventService?.getSelectedEvent() || null;
|
|
160
|
+
const effectiveSelectedEvent = selectedEvent || eventFromContext;
|
|
161
|
+
const { isSuperAdmin } = useSuperAdminBypass();
|
|
162
|
+
|
|
163
|
+
// Use resolved scope to get organisationId (derived from event if needed)
|
|
164
|
+
const { resolvedScope } = useResolvedScope({
|
|
165
|
+
supabase,
|
|
166
|
+
selectedOrganisationId: selectedOrganisation?.id || null,
|
|
167
|
+
selectedEventId: effectiveSelectedEvent?.event_id || null
|
|
168
|
+
});
|
|
159
169
|
|
|
160
170
|
const validateContext = useCallback((): void => {
|
|
161
171
|
if (!supabase) {
|
|
@@ -165,23 +175,29 @@ export function useSecureDataAccess(): SecureDataAccessReturn {
|
|
|
165
175
|
throw new Error('User must be authenticated with valid session') as OrganisationSecurityError;
|
|
166
176
|
}
|
|
167
177
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
}
|
|
178
|
+
if (isSuperAdmin) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (!resolvedScope?.organisationId) {
|
|
171
183
|
throw new Error('Organisation context is required for data access') as OrganisationSecurityError;
|
|
172
184
|
}
|
|
173
|
-
}, [supabase, user, session,
|
|
185
|
+
}, [supabase, user, session, resolvedScope, isSuperAdmin]);
|
|
174
186
|
|
|
175
187
|
const getCurrentOrganisationId = useCallback((): string => {
|
|
188
|
+
if (isSuperAdmin) {
|
|
189
|
+
// For super admins, try to get org from resolved scope or selectedOrganisation
|
|
190
|
+
return resolvedScope?.organisationId || selectedOrganisation?.id || '';
|
|
191
|
+
}
|
|
192
|
+
|
|
176
193
|
validateContext();
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
}, [validateContext, ensureOrganisationContext]);
|
|
194
|
+
return resolvedScope?.organisationId || '';
|
|
195
|
+
}, [validateContext, resolvedScope, selectedOrganisation, isSuperAdmin]);
|
|
180
196
|
|
|
181
197
|
// Set organisation context in database session
|
|
182
|
-
const setOrganisationContextInSession = useCallback(async (organisationId
|
|
183
|
-
if (!supabase) {
|
|
184
|
-
|
|
198
|
+
const setOrganisationContextInSession = useCallback(async (organisationId?: string): Promise<void> => {
|
|
199
|
+
if (!supabase || !organisationId) {
|
|
200
|
+
return;
|
|
185
201
|
}
|
|
186
202
|
|
|
187
203
|
await setOrganisationContext(supabase, organisationId);
|
|
@@ -199,7 +215,8 @@ export function useSecureDataAccess(): SecureDataAccessReturn {
|
|
|
199
215
|
} = {}
|
|
200
216
|
): Promise<T[]> => {
|
|
201
217
|
validateContext();
|
|
202
|
-
const
|
|
218
|
+
const bypassOrganisationFilter = isSuperAdmin;
|
|
219
|
+
const organisationId = bypassOrganisationFilter ? undefined : getCurrentOrganisationId();
|
|
203
220
|
|
|
204
221
|
// Set organisation context in database session
|
|
205
222
|
await setOrganisationContextInSession(organisationId);
|
|
@@ -210,27 +227,45 @@ export function useSecureDataAccess(): SecureDataAccessReturn {
|
|
|
210
227
|
.select(columns);
|
|
211
228
|
|
|
212
229
|
// Add organisation filter only if table has organisation_id column
|
|
230
|
+
// Defense in depth strategy:
|
|
231
|
+
// - RLS policies are the primary security layer (cannot be bypassed)
|
|
232
|
+
// - Application-level filtering adds an additional layer of protection
|
|
213
233
|
const tablesWithOrganisation = [
|
|
214
|
-
'
|
|
234
|
+
'core_events', 'organisation_settings',
|
|
215
235
|
'rbac_event_app_roles', 'rbac_organisation_roles',
|
|
216
236
|
// SECURITY: Phase 2 additions - complete organisation table mapping
|
|
217
237
|
'organisation_audit_log', 'organisation_invitations', 'organisation_app_access',
|
|
218
238
|
// SECURITY: Emergency additions for Phase 1 fixes
|
|
219
|
-
'cake_meal', 'cake_mealtype', '
|
|
239
|
+
'cake_meal', 'cake_mealtype', 'core_person',
|
|
240
|
+
// NOTE: core_member, medi_profile, core_contact, core_consent, core_identification, core_qualification
|
|
241
|
+
// are now person-scoped (not organisation-scoped) - removed from this list
|
|
220
242
|
// SECURITY: Phase 3A additions - medical and personal data
|
|
221
|
-
|
|
222
|
-
|
|
243
|
+
// NOTE: medi_condition, medi_diet, medi_action_plan, medi_profile_versions are now person-scoped
|
|
244
|
+
// (via medi_profile) - removed from this list
|
|
245
|
+
// core_identification_type remains organisation-scoped (lookup table)
|
|
246
|
+
'core_identification_type',
|
|
223
247
|
'form_responses', 'form_response_values', 'forms',
|
|
224
248
|
// SECURITY: Phase 3B additions - remaining critical tables
|
|
225
249
|
'invoice', 'line_item', 'credit_balance', 'payment_method',
|
|
226
250
|
'form_contexts', 'form_field_config', 'form_fields',
|
|
227
251
|
'cake_delivery', 'cake_diettype', 'cake_diner', 'cake_dish', 'cake_item',
|
|
228
252
|
'cake_logistics', 'cake_mealplan', 'cake_package', 'cake_recipe', 'cake_supplier',
|
|
229
|
-
'cake_supply', 'cake_unit', 'event_app_access', 'base_application', 'base_questions'
|
|
253
|
+
'cake_supply', 'cake_unit', 'event_app_access', 'base_application', 'base_questions',
|
|
254
|
+
// rbac_user_profiles has organisation_id but uses conditional filtering
|
|
255
|
+
'rbac_user_profiles'
|
|
230
256
|
];
|
|
231
257
|
|
|
232
|
-
|
|
233
|
-
|
|
258
|
+
// Apply organisation filtering based on table and super admin status
|
|
259
|
+
if (!bypassOrganisationFilter && organisationId && tablesWithOrganisation.includes(table)) {
|
|
260
|
+
// For rbac_user_profiles: Super admins see all (no filter), non-super-admins get filtered (defense in depth)
|
|
261
|
+
// For other tables: Always apply filter
|
|
262
|
+
if (table === 'rbac_user_profiles' && isSuperAdmin) {
|
|
263
|
+
// Super admin: No org filter - RLS handles access control
|
|
264
|
+
// This allows super admins to see all users across all organisations
|
|
265
|
+
} else {
|
|
266
|
+
// Non-super-admin OR other tables: Apply org filter as defense in depth
|
|
267
|
+
query = query.eq('organisation_id', organisationId);
|
|
268
|
+
}
|
|
234
269
|
}
|
|
235
270
|
|
|
236
271
|
// Apply additional filters
|
|
@@ -272,23 +307,26 @@ export function useSecureDataAccess(): SecureDataAccessReturn {
|
|
|
272
307
|
recordDataAccess(table, 'read', true, `SELECT ${columns} FROM ${table}`, filters);
|
|
273
308
|
|
|
274
309
|
return (data as T[]) || [];
|
|
275
|
-
}, [validateContext, getCurrentOrganisationId, setOrganisationContextInSession, supabase]);
|
|
310
|
+
}, [validateContext, getCurrentOrganisationId, setOrganisationContextInSession, supabase, isSuperAdmin]);
|
|
276
311
|
|
|
277
312
|
const secureInsert = useCallback(async <T = any>(
|
|
278
313
|
table: string,
|
|
279
314
|
data: Record<string, any>
|
|
280
315
|
): Promise<T> => {
|
|
281
316
|
validateContext();
|
|
282
|
-
const
|
|
317
|
+
const bypassOrganisationFilter = isSuperAdmin;
|
|
318
|
+
const organisationId = bypassOrganisationFilter ? undefined : getCurrentOrganisationId();
|
|
283
319
|
|
|
284
320
|
// Set organisation context in database session
|
|
285
321
|
await setOrganisationContextInSession(organisationId);
|
|
286
322
|
|
|
287
323
|
// Ensure organisation_id is set
|
|
288
|
-
const secureData =
|
|
289
|
-
...data
|
|
290
|
-
|
|
291
|
-
|
|
324
|
+
const secureData = bypassOrganisationFilter
|
|
325
|
+
? { ...data }
|
|
326
|
+
: {
|
|
327
|
+
...data,
|
|
328
|
+
organisation_id: organisationId
|
|
329
|
+
};
|
|
292
330
|
|
|
293
331
|
const { data: insertData, error } = await supabase!
|
|
294
332
|
.from(table)
|
|
@@ -302,7 +340,7 @@ export function useSecureDataAccess(): SecureDataAccessReturn {
|
|
|
302
340
|
}
|
|
303
341
|
|
|
304
342
|
return insertData as T;
|
|
305
|
-
}, [validateContext, getCurrentOrganisationId, setOrganisationContextInSession, supabase]);
|
|
343
|
+
}, [validateContext, getCurrentOrganisationId, setOrganisationContextInSession, supabase, isSuperAdmin]);
|
|
306
344
|
|
|
307
345
|
const secureUpdate = useCallback(async <T = any>(
|
|
308
346
|
table: string,
|
|
@@ -310,7 +348,8 @@ export function useSecureDataAccess(): SecureDataAccessReturn {
|
|
|
310
348
|
filters: Record<string, any>
|
|
311
349
|
): Promise<T[]> => {
|
|
312
350
|
validateContext();
|
|
313
|
-
const
|
|
351
|
+
const bypassOrganisationFilter = isSuperAdmin;
|
|
352
|
+
const organisationId = bypassOrganisationFilter ? undefined : getCurrentOrganisationId();
|
|
314
353
|
|
|
315
354
|
// Set organisation context in database session
|
|
316
355
|
await setOrganisationContextInSession(organisationId);
|
|
@@ -325,15 +364,15 @@ export function useSecureDataAccess(): SecureDataAccessReturn {
|
|
|
325
364
|
|
|
326
365
|
// Add organisation filter only if table has organisation_id column
|
|
327
366
|
const tablesWithOrganisation = [
|
|
328
|
-
'
|
|
367
|
+
'core_events', 'organisation_settings',
|
|
329
368
|
'rbac_event_app_roles', 'rbac_organisation_roles',
|
|
330
369
|
// SECURITY: Phase 2 additions - complete organisation table mapping
|
|
331
370
|
'organisation_audit_log', 'organisation_invitations', 'organisation_app_access',
|
|
332
371
|
// SECURITY: Emergency additions for Phase 1 fixes
|
|
333
|
-
'cake_meal', 'cake_mealtype', '
|
|
372
|
+
'cake_meal', 'cake_mealtype', 'core_person', 'core_member'
|
|
334
373
|
];
|
|
335
374
|
|
|
336
|
-
if (tablesWithOrganisation.includes(table)) {
|
|
375
|
+
if (!bypassOrganisationFilter && organisationId && tablesWithOrganisation.includes(table)) {
|
|
337
376
|
query = query.eq('organisation_id', organisationId);
|
|
338
377
|
}
|
|
339
378
|
|
|
@@ -352,14 +391,15 @@ export function useSecureDataAccess(): SecureDataAccessReturn {
|
|
|
352
391
|
}
|
|
353
392
|
|
|
354
393
|
return (updateData as T[]) || [];
|
|
355
|
-
}, [validateContext, getCurrentOrganisationId, setOrganisationContextInSession, supabase]);
|
|
394
|
+
}, [validateContext, getCurrentOrganisationId, setOrganisationContextInSession, supabase, isSuperAdmin]);
|
|
356
395
|
|
|
357
396
|
const secureDelete = useCallback(async (
|
|
358
397
|
table: string,
|
|
359
398
|
filters: Record<string, any>
|
|
360
399
|
): Promise<void> => {
|
|
361
400
|
validateContext();
|
|
362
|
-
const
|
|
401
|
+
const bypassOrganisationFilter = isSuperAdmin;
|
|
402
|
+
const organisationId = bypassOrganisationFilter ? undefined : getCurrentOrganisationId();
|
|
363
403
|
|
|
364
404
|
// Set organisation context in database session
|
|
365
405
|
await setOrganisationContextInSession(organisationId);
|
|
@@ -371,15 +411,15 @@ export function useSecureDataAccess(): SecureDataAccessReturn {
|
|
|
371
411
|
|
|
372
412
|
// Add organisation filter only if table has organisation_id column
|
|
373
413
|
const tablesWithOrganisation = [
|
|
374
|
-
'
|
|
414
|
+
'core_events', 'organisation_settings',
|
|
375
415
|
'rbac_event_app_roles', 'rbac_organisation_roles',
|
|
376
416
|
// SECURITY: Phase 2 additions - complete organisation table mapping
|
|
377
417
|
'organisation_audit_log', 'organisation_invitations', 'organisation_app_access',
|
|
378
418
|
// SECURITY: Emergency additions for Phase 1 fixes
|
|
379
|
-
'cake_meal', 'cake_mealtype', '
|
|
419
|
+
'cake_meal', 'cake_mealtype', 'core_person', 'core_member',
|
|
380
420
|
// SECURITY: Phase 3A additions - medical and personal data
|
|
381
421
|
'medi_profile', 'medi_condition', 'medi_diet', 'medi_action_plan', 'medi_profile_versions',
|
|
382
|
-
'
|
|
422
|
+
'core_consent', 'core_contact', 'core_identification', 'core_identification_type', 'core_qualification',
|
|
383
423
|
'form_responses', 'form_response_values', 'forms',
|
|
384
424
|
// SECURITY: Phase 3B additions - remaining critical tables
|
|
385
425
|
'invoice', 'line_item', 'credit_balance', 'payment_method',
|
|
@@ -389,7 +429,7 @@ export function useSecureDataAccess(): SecureDataAccessReturn {
|
|
|
389
429
|
'cake_supply', 'cake_unit', 'event_app_access', 'base_application', 'base_questions'
|
|
390
430
|
];
|
|
391
431
|
|
|
392
|
-
if (tablesWithOrganisation.includes(table)) {
|
|
432
|
+
if (!bypassOrganisationFilter && organisationId && tablesWithOrganisation.includes(table)) {
|
|
393
433
|
query = query.eq('organisation_id', organisationId);
|
|
394
434
|
}
|
|
395
435
|
|
|
@@ -406,14 +446,15 @@ export function useSecureDataAccess(): SecureDataAccessReturn {
|
|
|
406
446
|
logger.error('useSecureDataAccess', 'Delete failed', { table, filters, error });
|
|
407
447
|
throw error;
|
|
408
448
|
}
|
|
409
|
-
}, [validateContext, getCurrentOrganisationId, setOrganisationContextInSession, supabase]);
|
|
449
|
+
}, [validateContext, getCurrentOrganisationId, setOrganisationContextInSession, supabase, isSuperAdmin]);
|
|
410
450
|
|
|
411
451
|
const secureRpc = useCallback(async <T = any>(
|
|
412
452
|
functionName: string,
|
|
413
453
|
params: Record<string, any> = {}
|
|
414
454
|
): Promise<T> => {
|
|
415
455
|
validateContext();
|
|
416
|
-
const
|
|
456
|
+
const bypassOrganisationFilter = isSuperAdmin;
|
|
457
|
+
const organisationId = bypassOrganisationFilter ? undefined : getCurrentOrganisationId();
|
|
417
458
|
|
|
418
459
|
// Set organisation context in database session
|
|
419
460
|
await setOrganisationContextInSession(organisationId);
|
|
@@ -477,8 +518,13 @@ export function useSecureDataAccess(): SecureDataAccessReturn {
|
|
|
477
518
|
secureParams.p_user_id = user.id;
|
|
478
519
|
}
|
|
479
520
|
|
|
480
|
-
// Add organisation_id parameter
|
|
481
|
-
|
|
521
|
+
// Add organisation_id parameter when needed
|
|
522
|
+
if (!bypassOrganisationFilter && organisationId) {
|
|
523
|
+
secureParams[paramName] = organisationId;
|
|
524
|
+
} else if (organisationId && !(paramName in params)) {
|
|
525
|
+
// Default to the current organisation if caller didn't specify one
|
|
526
|
+
secureParams[paramName] = organisationId;
|
|
527
|
+
}
|
|
482
528
|
|
|
483
529
|
// Add p_event_id if function needs it and event is selected
|
|
484
530
|
// CRITICAL: This must be added AFTER organisation_id but BEFORE caller params
|
|
@@ -505,7 +551,7 @@ export function useSecureDataAccess(): SecureDataAccessReturn {
|
|
|
505
551
|
}
|
|
506
552
|
|
|
507
553
|
return data as T;
|
|
508
|
-
}, [validateContext, getCurrentOrganisationId, setOrganisationContextInSession, supabase, selectedEvent?.event_id, user?.id]);
|
|
554
|
+
}, [validateContext, getCurrentOrganisationId, setOrganisationContextInSession, supabase, selectedEvent?.event_id, user?.id, isSuperAdmin]);
|
|
509
555
|
|
|
510
556
|
// NEW: Phase 1 - Enhanced Security Features
|
|
511
557
|
const [dataAccessHistory, setDataAccessHistory] = useState<DataAccessRecord[]>([]);
|
|
@@ -570,12 +616,13 @@ export function useSecureDataAccess(): SecureDataAccessReturn {
|
|
|
570
616
|
filters?: Record<string, any>
|
|
571
617
|
) => {
|
|
572
618
|
if (!isAuditLogEnabled || !user?.id) return;
|
|
573
|
-
|
|
619
|
+
const auditOrganisationId = getCurrentOrganisationId() || 'super-admin-bypass';
|
|
620
|
+
|
|
574
621
|
const record: DataAccessRecord = {
|
|
575
622
|
table,
|
|
576
623
|
operation,
|
|
577
624
|
userId: user.id,
|
|
578
|
-
organisationId:
|
|
625
|
+
organisationId: auditOrganisationId,
|
|
579
626
|
allowed,
|
|
580
627
|
timestamp: new Date().toISOString(),
|
|
581
628
|
query,
|
|
@@ -592,7 +639,7 @@ export function useSecureDataAccess(): SecureDataAccessReturn {
|
|
|
592
639
|
table,
|
|
593
640
|
operation,
|
|
594
641
|
userId: user.id,
|
|
595
|
-
organisationId:
|
|
642
|
+
organisationId: auditOrganisationId,
|
|
596
643
|
timestamp: new Date().toISOString()
|
|
597
644
|
});
|
|
598
645
|
}
|
|
@@ -40,24 +40,7 @@ const createMockSupabaseClient = () => {
|
|
|
40
40
|
const orgId = '123e4567-e89b-12d3-a456-426614174000'; // Valid UUID format
|
|
41
41
|
const userId = '123e4567-e89b-12d3-a456-426614174001'; // Valid UUID format
|
|
42
42
|
|
|
43
|
-
|
|
44
|
-
rpc: vi.fn().mockResolvedValue({
|
|
45
|
-
data: [
|
|
46
|
-
{
|
|
47
|
-
user_id: userId,
|
|
48
|
-
organisation_id: orgId,
|
|
49
|
-
role: 'org_admin',
|
|
50
|
-
status: 'active',
|
|
51
|
-
},
|
|
52
|
-
],
|
|
53
|
-
error: null,
|
|
54
|
-
}),
|
|
55
|
-
from: vi.fn((table: string) => {
|
|
56
|
-
if (table === 'organisations') {
|
|
57
|
-
return {
|
|
58
|
-
select: vi.fn().mockResolvedValue({
|
|
59
|
-
data: [
|
|
60
|
-
{
|
|
43
|
+
const mockOrganisation = {
|
|
61
44
|
id: orgId,
|
|
62
45
|
name: 'Test Organisation 1',
|
|
63
46
|
display_name: 'Test Organisation 1',
|
|
@@ -67,11 +50,34 @@ const createMockSupabaseClient = () => {
|
|
|
67
50
|
parent_id: null,
|
|
68
51
|
created_at: '2023-01-01T00:00:00Z',
|
|
69
52
|
updated_at: '2023-01-01T00:00:00Z',
|
|
70
|
-
|
|
71
|
-
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const mockQueryBuilder = {
|
|
56
|
+
select: vi.fn().mockReturnThis(),
|
|
57
|
+
eq: vi.fn().mockReturnThis(),
|
|
58
|
+
is: vi.fn().mockResolvedValue({
|
|
59
|
+
data: [{
|
|
60
|
+
id: 'membership-1',
|
|
61
|
+
user_id: userId,
|
|
62
|
+
organisation_id: orgId,
|
|
63
|
+
role: 'org_admin',
|
|
64
|
+
status: 'active',
|
|
65
|
+
granted_at: '2023-01-01T00:00:00Z',
|
|
66
|
+
revoked_at: null,
|
|
67
|
+
core_organisations: mockOrganisation
|
|
68
|
+
}],
|
|
69
|
+
error: null
|
|
70
|
+
})
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
rpc: vi.fn().mockResolvedValue({
|
|
75
|
+
data: [],
|
|
72
76
|
error: null,
|
|
73
77
|
}),
|
|
74
|
-
|
|
78
|
+
from: vi.fn((table: string) => {
|
|
79
|
+
if (table === 'rbac_organisation_roles') {
|
|
80
|
+
return mockQueryBuilder;
|
|
75
81
|
}
|
|
76
82
|
return {
|
|
77
83
|
select: vi.fn().mockResolvedValue({ data: [], error: null }),
|
|
@@ -31,17 +31,18 @@ export interface EventServiceProviderProps {
|
|
|
31
31
|
setSelectedEventId: (eventId: string | null) => void;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
export function EventServiceProvider({
|
|
35
|
-
children,
|
|
36
|
-
supabaseClient,
|
|
37
|
-
user,
|
|
38
|
-
session,
|
|
34
|
+
export function EventServiceProvider({
|
|
35
|
+
children,
|
|
36
|
+
supabaseClient,
|
|
37
|
+
user,
|
|
38
|
+
session,
|
|
39
39
|
appName,
|
|
40
40
|
selectedOrganisation,
|
|
41
41
|
setSelectedEventId
|
|
42
42
|
}: EventServiceProviderProps) {
|
|
43
43
|
// Create service instance once with useRef to avoid recreation on dependency changes
|
|
44
44
|
const eventServiceRef = useRef<EventService | null>(null);
|
|
45
|
+
const initializingRef = useRef(false);
|
|
45
46
|
|
|
46
47
|
if (!eventServiceRef.current) {
|
|
47
48
|
eventServiceRef.current = new EventService(supabaseClient, user, session, appName, selectedOrganisation, setSelectedEventId);
|
|
@@ -53,25 +54,44 @@ export function EventServiceProvider({
|
|
|
53
54
|
// Note: eventService is a ref and never changes, so we don't include it in dependencies
|
|
54
55
|
useEffect(() => {
|
|
55
56
|
let isMounted = true;
|
|
56
|
-
|
|
57
|
+
|
|
58
|
+
if (initializingRef.current) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
57
62
|
const updateAndInitialize = async () => {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
68
|
-
|
|
63
|
+
initializingRef.current = true;
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
logger.debug('EventServiceProvider', 'Updating dependencies and initializing', {
|
|
67
|
+
hasUser: !!user,
|
|
68
|
+
hasSession: !!session,
|
|
69
|
+
appName,
|
|
70
|
+
hasSelectedOrganisation: !!selectedOrganisation,
|
|
71
|
+
organisationId: selectedOrganisation?.id
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Update dependencies (now async to handle user change cleanup)
|
|
75
|
+
await eventService.updateDependencies(supabaseClient, user, session, appName, selectedOrganisation, setSelectedEventId);
|
|
76
|
+
|
|
77
|
+
if (!isMounted) return;
|
|
78
|
+
|
|
79
|
+
// Re-initialize service when dependencies change
|
|
80
|
+
await eventService.initialize().catch(error => {
|
|
81
|
+
if (isMounted) {
|
|
82
|
+
logger.error('EventServiceProvider', 'Failed to initialize event service:', error);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
} finally {
|
|
86
|
+
initializingRef.current = false;
|
|
87
|
+
}
|
|
69
88
|
};
|
|
70
89
|
|
|
71
90
|
updateAndInitialize();
|
|
72
91
|
|
|
73
92
|
return () => {
|
|
74
93
|
isMounted = false;
|
|
94
|
+
initializingRef.current = false;
|
|
75
95
|
};
|
|
76
96
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
77
97
|
// eventService is a ref and never changes, so we exclude it from dependencies
|
|
@@ -25,8 +25,8 @@ export interface InactivityServiceProviderProps {
|
|
|
25
25
|
supabaseClient: SupabaseClient;
|
|
26
26
|
user: User | null;
|
|
27
27
|
session: Session | null;
|
|
28
|
-
idleTimeoutMs
|
|
29
|
-
warnBeforeMs
|
|
28
|
+
idleTimeoutMs: number; // REQUIRED: No default - must be explicitly provided
|
|
29
|
+
warnBeforeMs: number; // REQUIRED: No default - must be explicitly provided
|
|
30
30
|
onIdleLogout: (reason: 'inactivity') => void;
|
|
31
31
|
}
|
|
32
32
|
|
|
@@ -35,8 +35,8 @@ export function InactivityServiceProvider({
|
|
|
35
35
|
supabaseClient,
|
|
36
36
|
user,
|
|
37
37
|
session,
|
|
38
|
-
idleTimeoutMs
|
|
39
|
-
warnBeforeMs
|
|
38
|
+
idleTimeoutMs, // REQUIRED: No default - must be explicitly provided
|
|
39
|
+
warnBeforeMs, // REQUIRED: No default - must be explicitly provided
|
|
40
40
|
onIdleLogout
|
|
41
41
|
}: InactivityServiceProviderProps) {
|
|
42
42
|
// Create service instance once with useRef to avoid recreation on dependency changes
|
|
@@ -52,7 +52,14 @@ export function OrganisationServiceProvider({
|
|
|
52
52
|
organisationService.initialize()
|
|
53
53
|
.catch(error => {
|
|
54
54
|
if (isMounted) {
|
|
55
|
-
|
|
55
|
+
// "User has no access to active organisations" is a valid state for users without orgs (e.g., profile pages)
|
|
56
|
+
// Log it as a warning instead of an error to reduce noise
|
|
57
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
58
|
+
if (errorMessage === 'User has no access to active organisations') {
|
|
59
|
+
logger.warn('OrganisationServiceProvider', 'User has no active organisations (this is expected for users without organisation access):', error);
|
|
60
|
+
} else {
|
|
61
|
+
logger.error('OrganisationServiceProvider', 'Failed to initialize organisation service:', error);
|
|
62
|
+
}
|
|
56
63
|
}
|
|
57
64
|
});
|
|
58
65
|
|