@jmruthers/pace-core 0.5.190 → 0.5.193
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/dist/{AuthService-CbP_utw2.d.ts → AuthService-DjnJHDtC.d.ts} +1 -0
- package/dist/{DataTable-ON3IXISJ.js → DataTable-5FU7IESH.js} +7 -6
- package/dist/{DataTable-IVYljGJ6.d.ts → DataTable-Be6dH_dR.d.ts} +1 -1
- package/dist/{PublicPageProvider-C4uxosp6.d.ts → PublicPageProvider-C0Sm_e5k.d.ts} +4 -2
- package/dist/{UnifiedAuthProvider-BYA9qB-o.d.ts → UnifiedAuthProvider-185Ih4dj.d.ts} +2 -0
- package/dist/{UnifiedAuthProvider-X5NXANVI.js → UnifiedAuthProvider-RGJTDE2C.js} +3 -3
- package/dist/{api-I6UCQ5S6.js → api-N774RPUA.js} +2 -2
- package/dist/chunk-6C4YBBJM 5.js +628 -0
- package/dist/chunk-7D4SUZUM.js 2.map +1 -0
- package/dist/{chunk-73HSNNOQ.js → chunk-7EQTDTTJ.js} +47 -74
- package/dist/chunk-7EQTDTTJ.js 2.map +1 -0
- package/dist/chunk-7EQTDTTJ.js.map +1 -0
- package/dist/{chunk-J2XXC7R5.js → chunk-7FLMSG37.js} +409 -244
- package/dist/chunk-7FLMSG37.js 2.map +1 -0
- package/dist/chunk-7FLMSG37.js.map +1 -0
- package/dist/{chunk-NIU6J6OX.js → chunk-BC4IJKSL.js} +23 -32
- package/dist/chunk-BC4IJKSL.js.map +1 -0
- package/dist/{chunk-SDMHPX3X.js → chunk-E3SPN4VZ 5.js } +198 -53
- package/dist/chunk-E3SPN4VZ.js +12917 -0
- package/dist/{chunk-SDMHPX3X.js.map → chunk-E3SPN4VZ.js.map} +1 -1
- package/dist/chunk-E66EQZE6 5.js +37 -0
- package/dist/chunk-E66EQZE6.js 2.map +1 -0
- package/dist/{chunk-DZWK57KZ.js → chunk-G37KK66H.js} +1 -1
- package/dist/{chunk-DZWK57KZ.js.map → chunk-G37KK66H.js.map} +1 -1
- package/dist/{chunk-STYK4OH2.js → chunk-HWIIPPNI.js} +44 -225
- package/dist/chunk-HWIIPPNI.js.map +1 -0
- package/dist/chunk-I7PSE6JW 5.js +191 -0
- package/dist/chunk-I7PSE6JW.js 2.map +1 -0
- package/dist/{chunk-Y4BUBBHD.js → chunk-IIELH4DL.js} +211 -136
- package/dist/chunk-IIELH4DL.js.map +1 -0
- package/dist/{chunk-RUYZKXOD.js → chunk-KNC55RTG.js} +17 -5
- package/dist/chunk-KNC55RTG.js 5.map +1 -0
- package/dist/chunk-KNC55RTG.js.map +1 -0
- package/dist/chunk-KQCRWDSA.js 5.map +1 -0
- package/dist/{chunk-4QYC5L4K.js → chunk-LFNCN2SP.js} +26 -30
- package/dist/chunk-LFNCN2SP.js 2.map +1 -0
- package/dist/chunk-LFNCN2SP.js.map +1 -0
- package/dist/chunk-LMC26NLJ 2.js +84 -0
- package/dist/{chunk-VVBAW5A5.js → chunk-NOAYCWCX 5.js } +118 -110
- package/dist/chunk-NOAYCWCX.js +4993 -0
- package/dist/chunk-NOAYCWCX.js.map +1 -0
- package/dist/chunk-QWWZ5CAQ.js 3.map +1 -0
- package/dist/chunk-QXHPKYJV 3.js +113 -0
- package/dist/chunk-R77UEZ4E 3.js +68 -0
- package/dist/chunk-VBXEHIUJ.js 6.map +1 -0
- package/dist/{chunk-HQVPB5MZ.js → chunk-XNXXZ43G.js} +77 -33
- package/dist/chunk-XNXXZ43G.js.map +1 -0
- package/dist/chunk-ZSAAAMVR 6.js +25 -0
- package/dist/components.d.ts +4 -4
- package/dist/components.js +8 -8
- package/dist/components.js 5.map +1 -0
- package/dist/{database.generated-DI89OQeI.d.ts → database.generated-CzIvgcPu.d.ts} +165 -201
- package/dist/hooks.d.ts +12 -12
- package/dist/hooks.js +9 -9
- package/dist/index.d.ts +11 -11
- package/dist/index.js +20 -27
- package/dist/index.js.map +1 -1
- package/dist/providers.d.ts +3 -3
- package/dist/providers.js +2 -2
- package/dist/rbac/index.d.ts +2 -20
- package/dist/rbac/index.js +7 -9
- package/dist/styles/index 2.js +12 -0
- package/dist/styles/index.js 5.map +1 -0
- package/dist/theming/runtime 5.js +19 -0
- package/dist/theming/runtime.js 5.map +1 -0
- package/dist/{types-Bwgl--Xo.d.ts → types-CEpcvwwF.d.ts} +1 -1
- package/dist/types.d.ts +2 -2
- package/dist/{usePublicRouteParams-DxIDS4bC.d.ts → usePublicRouteParams-TZe0gy-4.d.ts} +1 -1
- package/dist/utils.d.ts +8 -8
- package/dist/utils.js +2 -2
- package/docs/api/classes/ColumnFactory.md +1 -1
- package/docs/api/classes/ErrorBoundary.md +1 -1
- package/docs/api/classes/InvalidScopeError.md +1 -1
- package/docs/api/classes/Logger.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/RBACAuditManager.md +2 -2
- package/docs/api/classes/RBACCache.md +1 -1
- package/docs/api/classes/RBACEngine.md +2 -2
- package/docs/api/classes/RBACError.md +1 -1
- package/docs/api/classes/RBACNotInitializedError.md +1 -1
- package/docs/api/classes/SecureSupabaseClient.md +10 -10
- package/docs/api/classes/StorageUtils.md +1 -1
- 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 +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/ComplianceResult.md +1 -1
- package/docs/api/interfaces/DataAccessRecord.md +1 -1
- 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 +24 -11
- package/docs/api/interfaces/FileMetadata.md +1 -1
- package/docs/api/interfaces/FileReference.md +1 -1
- package/docs/api/interfaces/FileSizeLimits.md +1 -1
- package/docs/api/interfaces/FileUploadOptions.md +1 -1
- package/docs/api/interfaces/FileUploadProps.md +1 -1
- 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 +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 +2 -2
- package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
- package/docs/api/interfaces/PaletteData.md +1 -1
- package/docs/api/interfaces/ParsedAddress.md +2 -2
- package/docs/api/interfaces/PermissionEnforcerProps.md +4 -4
- package/docs/api/interfaces/ProgressProps.md +1 -1
- 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 +2 -2
- 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 +2 -2
- package/docs/api/interfaces/RBACRoleGrantResult.md +1 -1
- package/docs/api/interfaces/RBACRoleRevokeParams.md +2 -2
- package/docs/api/interfaces/RBACRoleRevokeResult.md +1 -1
- package/docs/api/interfaces/RBACRoleValidateParams.md +2 -2
- package/docs/api/interfaces/RBACRoleValidateResult.md +1 -1
- package/docs/api/interfaces/RBACRolesListParams.md +1 -1
- package/docs/api/interfaces/RBACRolesListResult.md +2 -2
- 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 +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
- package/docs/api/interfaces/RoleManagementResult.md +1 -1
- package/docs/api/interfaces/RouteAccessRecord.md +2 -2
- package/docs/api/interfaces/RouteConfig.md +2 -2
- package/docs/api/interfaces/RuntimeComplianceResult.md +1 -1
- package/docs/api/interfaces/SecureDataContextType.md +1 -1
- package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
- package/docs/api/interfaces/SessionRestorationLoaderProps.md +1 -1
- package/docs/api/interfaces/SetupIssue.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/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 +60 -38
- 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 +2 -2
- package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
- package/docs/api/interfaces/UseResourcePermissionsOptions.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 +202 -217
- package/docs/migration/README.md +18 -0
- package/docs/migration/database-changes-december-2025.md +768 -0
- package/docs/migration/person-scoped-profiles-migration-guide.md +472 -0
- package/docs/rbac/event-based-apps.md +124 -6
- package/package.json +1 -1
- package/scripts/check-pace-core-compliance.cjs +292 -57
- package/src/__tests__/public-recipe-view.test.ts +10 -10
- package/src/__tests__/rls-policies.test.ts +16 -14
- package/src/components/AddressField/README.md +6 -6
- package/src/components/DataTable/__tests__/DataTable.default-state.test.tsx +172 -45
- package/src/components/DataTable/__tests__/DataTable.grouping-aggregation.test.tsx +121 -28
- package/src/components/DataTable/__tests__/DataTableCore.test-setup.ts +9 -8
- package/src/components/DataTable/__tests__/DataTableCore.test.tsx +20 -52
- package/src/components/DataTable/__tests__/a11y.basic.test.tsx +170 -34
- package/src/components/DataTable/__tests__/keyboard.test.tsx +75 -12
- package/src/components/DataTable/__tests__/pagination.modes.test.tsx +75 -11
- package/src/components/DataTable/components/UnifiedTableBody.tsx +85 -14
- package/src/components/DataTable/hooks/useDataTablePermissions.ts +75 -10
- package/src/components/FileDisplay/FileDisplay.test.tsx +2 -1
- package/src/components/FileDisplay/FileDisplay.tsx +16 -4
- package/src/components/NavigationMenu/NavigationMenu.test.tsx +6 -4
- package/src/components/NavigationMenu/NavigationMenu.tsx +1 -10
- package/src/components/OrganisationSelector/OrganisationSelector.tsx +35 -16
- package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +25 -2
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +97 -68
- package/src/components/PaceLoginPage/PaceLoginPage.tsx +0 -7
- package/src/components/ProtectedRoute/ProtectedRoute.test.tsx +5 -9
- package/src/components/ProtectedRoute/ProtectedRoute.tsx +0 -1
- package/src/components/PublicLayout/PublicPageProvider.tsx +0 -1
- package/src/components/Select/Select.test.tsx +4 -1
- package/src/components/Select/Select.tsx +60 -15
- package/src/hooks/__tests__/usePermissionCache.simple.test.ts +192 -0
- package/src/hooks/__tests__/usePermissionCache.unit.test.ts +741 -0
- package/src/hooks/__tests__/usePublicEvent.simple.test.ts +703 -0
- package/src/hooks/__tests__/usePublicEvent.unit.test.ts +581 -0
- package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +23 -15
- package/src/hooks/public/usePublicEvent.ts +8 -8
- package/src/hooks/public/usePublicFileDisplay.ts +2 -2
- package/src/hooks/services/useAuthService.ts +21 -3
- package/src/hooks/services/useEventService.ts +21 -3
- package/src/hooks/services/useInactivityService.ts +21 -3
- package/src/hooks/services/useOrganisationService.ts +21 -3
- package/src/hooks/useFileDisplay.ts +18 -26
- package/src/hooks/useQueryCache.ts +6 -6
- package/src/hooks/useSecureDataAccess.test.ts +24 -17
- package/src/hooks/useSecureDataAccess.ts +18 -13
- package/src/providers/__tests__/OrganisationProvider.test.tsx +27 -21
- package/src/providers/services/EventServiceProvider.tsx +0 -8
- package/src/providers/services/UnifiedAuthProvider.tsx +174 -24
- package/src/rbac/__tests__/adapters.comprehensive.test.tsx +10 -16
- package/src/rbac/__tests__/isSuperAdmin.real.test.ts +82 -0
- package/src/rbac/adapters.tsx +3 -22
- package/src/rbac/api.test.ts +2 -2
- package/src/rbac/api.ts +7 -1
- package/src/rbac/components/EnhancedNavigationMenu.tsx +2 -15
- package/src/rbac/components/NavigationGuard.tsx +1 -10
- package/src/rbac/components/NavigationProvider.tsx +0 -1
- package/src/rbac/components/PermissionEnforcer.tsx +45 -12
- package/src/rbac/components/SecureDataProvider.tsx +0 -1
- package/src/rbac/components/__tests__/EnhancedNavigationMenu.test.tsx +7 -43
- package/src/rbac/components/__tests__/NavigationGuard.test.tsx +4 -11
- package/src/rbac/components/__tests__/NavigationProvider.test.tsx +3 -3
- package/src/rbac/components/__tests__/SecureDataProvider.fixed.test.tsx +1 -1
- package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +1 -1
- package/src/rbac/engine.ts +14 -2
- package/src/rbac/hooks/index.ts +0 -3
- package/src/rbac/hooks/usePermissions.ts +51 -11
- package/src/rbac/hooks/useRBAC.simple.test.ts +95 -0
- package/src/rbac/hooks/useRBAC.ts +3 -13
- package/src/rbac/hooks/useResolvedScope.test.ts +75 -54
- package/src/rbac/hooks/useResolvedScope.ts +58 -33
- package/src/rbac/hooks/useSecureSupabase.ts +4 -9
- package/src/rbac/secureClient.ts +31 -0
- package/src/rbac/utils/__tests__/eventContext.test.ts +2 -2
- package/src/rbac/utils/__tests__/eventContext.unit.test.ts +490 -0
- package/src/rbac/utils/eventContext.ts +5 -2
- package/src/services/AuthService.ts +37 -8
- package/src/services/EventService.ts +4 -57
- package/src/services/InactivityService.ts +127 -34
- package/src/services/OrganisationService.ts +160 -149
- 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/supabase.ts +2 -2
- package/src/utils/__tests__/secureDataAccess.unit.test.ts +3 -2
- package/src/utils/file-reference/index.ts +4 -4
- package/src/utils/google-places/googlePlacesUtils.ts +1 -1
- 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/dist/chunk-4QYC5L4K.js.map +0 -1
- package/dist/chunk-73HSNNOQ.js.map +0 -1
- package/dist/chunk-HQVPB5MZ.js.map +0 -1
- package/dist/chunk-J2XXC7R5.js.map +0 -1
- package/dist/chunk-NIU6J6OX.js.map +0 -1
- package/dist/chunk-RUYZKXOD.js.map +0 -1
- package/dist/chunk-STYK4OH2.js.map +0 -1
- package/dist/chunk-VVBAW5A5.js.map +0 -1
- package/dist/chunk-Y4BUBBHD.js.map +0 -1
- package/scripts/check-pace-core-compliance.js +0 -512
- package/src/rbac/hooks/useSuperAdminBypass.ts +0 -126
- package/src/utils/context/superAdminOverride.ts +0 -58
- /package/dist/{DataTable-ON3IXISJ.js.map → DataTable-5FU7IESH.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-X5NXANVI.js.map → UnifiedAuthProvider-RGJTDE2C.js.map} +0 -0
- /package/dist/{api-I6UCQ5S6.js.map → api-N774RPUA.js.map} +0 -0
|
@@ -210,7 +210,7 @@ export function usePublicEvent(
|
|
|
210
210
|
|
|
211
211
|
// Fallback: Direct table access with public RLS policy
|
|
212
212
|
const tableResponse2 = await (supabase as any)
|
|
213
|
-
.from('
|
|
213
|
+
.from('core_events')
|
|
214
214
|
.select(`
|
|
215
215
|
event_id,
|
|
216
216
|
event_name,
|
|
@@ -247,11 +247,11 @@ export function usePublicEvent(
|
|
|
247
247
|
return;
|
|
248
248
|
}
|
|
249
249
|
|
|
250
|
-
// Get event logo from
|
|
250
|
+
// Get event logo from core_file_references
|
|
251
251
|
const logoResponse = await (supabase as any)
|
|
252
|
-
.from('
|
|
252
|
+
.from('core_file_references')
|
|
253
253
|
.select('file_path')
|
|
254
|
-
.eq('table_name', '
|
|
254
|
+
.eq('table_name', 'core_events')
|
|
255
255
|
.eq('record_id', tableData.event_id)
|
|
256
256
|
.eq('is_public', true)
|
|
257
257
|
.eq('file_metadata->>category', 'event_logos')
|
|
@@ -286,7 +286,7 @@ export function usePublicEvent(
|
|
|
286
286
|
logger.warn('usePublicEvent', 'RPC call failed, falling back to direct table access:', rpcError);
|
|
287
287
|
|
|
288
288
|
const tableResponse = await (supabase as any)
|
|
289
|
-
.from('
|
|
289
|
+
.from('core_events')
|
|
290
290
|
.select(`
|
|
291
291
|
event_id,
|
|
292
292
|
event_name,
|
|
@@ -323,11 +323,11 @@ export function usePublicEvent(
|
|
|
323
323
|
return;
|
|
324
324
|
}
|
|
325
325
|
|
|
326
|
-
// Get event logo from
|
|
326
|
+
// Get event logo from core_file_references
|
|
327
327
|
const logoResponse = await (supabase as any)
|
|
328
|
-
.from('
|
|
328
|
+
.from('core_file_references')
|
|
329
329
|
.select('file_path')
|
|
330
|
-
.eq('table_name', '
|
|
330
|
+
.eq('table_name', 'core_events')
|
|
331
331
|
.eq('record_id', tableData.event_id)
|
|
332
332
|
.eq('is_public', true)
|
|
333
333
|
.eq('file_metadata->>category', 'event_logos')
|
|
@@ -202,7 +202,7 @@ export function usePublicFileDisplay(
|
|
|
202
202
|
const ids = userData.map((item: any) => item.id);
|
|
203
203
|
if (ids.length > 0) {
|
|
204
204
|
const { data: fullData } = await supabase
|
|
205
|
-
.from('
|
|
205
|
+
.from('core_file_references')
|
|
206
206
|
.select('id, table_name, record_id, file_path, file_metadata, organisation_id, app_id, is_public, created_at, updated_at')
|
|
207
207
|
.in('id', ids)
|
|
208
208
|
.eq('is_public', true);
|
|
@@ -320,7 +320,7 @@ export function usePublicFileDisplay(
|
|
|
320
320
|
// Fetch full file reference data for each ID, but only public files
|
|
321
321
|
const ids = fileIds.map((item: any) => item.id);
|
|
322
322
|
const { data: fullData, error: fetchError } = await supabase
|
|
323
|
-
.from('
|
|
323
|
+
.from('core_file_references')
|
|
324
324
|
.select('id, table_name, record_id, file_path, file_metadata, organisation_id, app_id, is_public, created_at, updated_at')
|
|
325
325
|
.in('id', ids)
|
|
326
326
|
.eq('is_public', true); // Only public files in public context
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* Provides authentication service with reactive state updates.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { useContext, useReducer, useEffect } from 'react';
|
|
11
|
+
import { useContext, useReducer, useEffect, useRef } from 'react';
|
|
12
12
|
import { AuthServiceContext } from '../../providers/services/AuthServiceProvider';
|
|
13
13
|
import { AuthService } from '../../services/AuthService';
|
|
14
14
|
|
|
@@ -19,11 +19,29 @@ export function useAuthService(): AuthService {
|
|
|
19
19
|
throw new Error('useAuthService must be used within AuthServiceProvider');
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
// Subscribe to service state changes
|
|
22
|
+
// Subscribe to service state changes with debouncing to prevent excessive re-renders
|
|
23
23
|
const [, forceUpdate] = useReducer(x => x + 1, 0);
|
|
24
|
+
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
24
25
|
|
|
25
26
|
useEffect(() => {
|
|
26
|
-
|
|
27
|
+
const debouncedUpdate = () => {
|
|
28
|
+
if (timeoutRef.current) {
|
|
29
|
+
clearTimeout(timeoutRef.current);
|
|
30
|
+
}
|
|
31
|
+
timeoutRef.current = setTimeout(() => {
|
|
32
|
+
forceUpdate();
|
|
33
|
+
timeoutRef.current = null;
|
|
34
|
+
}, 50); // 50ms debounce to batch rapid updates
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const unsubscribe = context.authService.subscribe(debouncedUpdate);
|
|
38
|
+
|
|
39
|
+
return () => {
|
|
40
|
+
unsubscribe();
|
|
41
|
+
if (timeoutRef.current) {
|
|
42
|
+
clearTimeout(timeoutRef.current);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
27
45
|
}, [context.authService]);
|
|
28
46
|
|
|
29
47
|
return context.authService;
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* Provides event service with reactive state updates.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { useContext, useReducer, useEffect } from 'react';
|
|
11
|
+
import { useContext, useReducer, useEffect, useRef } from 'react';
|
|
12
12
|
import { EventServiceContext } from '../../providers/services/EventServiceProvider';
|
|
13
13
|
import { EventService } from '../../services/EventService';
|
|
14
14
|
|
|
@@ -19,11 +19,29 @@ export function useEventService(): EventService {
|
|
|
19
19
|
throw new Error('useEventService must be used within EventServiceProvider');
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
// Subscribe to service state changes
|
|
22
|
+
// Subscribe to service state changes with debouncing to prevent excessive re-renders
|
|
23
23
|
const [, forceUpdate] = useReducer(x => x + 1, 0);
|
|
24
|
+
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
24
25
|
|
|
25
26
|
useEffect(() => {
|
|
26
|
-
|
|
27
|
+
const debouncedUpdate = () => {
|
|
28
|
+
if (timeoutRef.current) {
|
|
29
|
+
clearTimeout(timeoutRef.current);
|
|
30
|
+
}
|
|
31
|
+
timeoutRef.current = setTimeout(() => {
|
|
32
|
+
forceUpdate();
|
|
33
|
+
timeoutRef.current = null;
|
|
34
|
+
}, 50); // 50ms debounce to batch rapid updates
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const unsubscribe = context.eventService.subscribe(debouncedUpdate);
|
|
38
|
+
|
|
39
|
+
return () => {
|
|
40
|
+
unsubscribe();
|
|
41
|
+
if (timeoutRef.current) {
|
|
42
|
+
clearTimeout(timeoutRef.current);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
27
45
|
}, [context.eventService]);
|
|
28
46
|
|
|
29
47
|
return context.eventService;
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* Provides inactivity service with reactive state updates.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { useContext, useReducer, useEffect } from 'react';
|
|
11
|
+
import { useContext, useReducer, useEffect, useRef } from 'react';
|
|
12
12
|
import { InactivityServiceContext } from '../../providers/services/InactivityServiceProvider';
|
|
13
13
|
import { InactivityService } from '../../services/InactivityService';
|
|
14
14
|
|
|
@@ -19,11 +19,29 @@ export function useInactivityService(): InactivityService {
|
|
|
19
19
|
throw new Error('useInactivityService must be used within InactivityServiceProvider');
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
// Subscribe to service state changes
|
|
22
|
+
// Subscribe to service state changes with debouncing to prevent excessive re-renders
|
|
23
23
|
const [, forceUpdate] = useReducer(x => x + 1, 0);
|
|
24
|
+
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
24
25
|
|
|
25
26
|
useEffect(() => {
|
|
26
|
-
|
|
27
|
+
const debouncedUpdate = () => {
|
|
28
|
+
if (timeoutRef.current) {
|
|
29
|
+
clearTimeout(timeoutRef.current);
|
|
30
|
+
}
|
|
31
|
+
timeoutRef.current = setTimeout(() => {
|
|
32
|
+
forceUpdate();
|
|
33
|
+
timeoutRef.current = null;
|
|
34
|
+
}, 50); // 50ms debounce to batch rapid updates
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const unsubscribe = context.inactivityService.subscribe(debouncedUpdate);
|
|
38
|
+
|
|
39
|
+
return () => {
|
|
40
|
+
unsubscribe();
|
|
41
|
+
if (timeoutRef.current) {
|
|
42
|
+
clearTimeout(timeoutRef.current);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
27
45
|
}, [context.inactivityService]);
|
|
28
46
|
|
|
29
47
|
return context.inactivityService;
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* Provides organisation service with reactive state updates.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { useContext, useReducer, useEffect } from 'react';
|
|
11
|
+
import { useContext, useReducer, useEffect, useRef } from 'react';
|
|
12
12
|
import { OrganisationServiceContext } from '../../providers/services/OrganisationServiceProvider';
|
|
13
13
|
import { OrganisationService } from '../../services/OrganisationService';
|
|
14
14
|
|
|
@@ -19,11 +19,29 @@ export function useOrganisationService(): OrganisationService {
|
|
|
19
19
|
throw new Error('useOrganisationService must be used within OrganisationServiceProvider');
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
// Subscribe to service state changes
|
|
22
|
+
// Subscribe to service state changes with debouncing to prevent excessive re-renders
|
|
23
23
|
const [, forceUpdate] = useReducer(x => x + 1, 0);
|
|
24
|
+
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
24
25
|
|
|
25
26
|
useEffect(() => {
|
|
26
|
-
|
|
27
|
+
const debouncedUpdate = () => {
|
|
28
|
+
if (timeoutRef.current) {
|
|
29
|
+
clearTimeout(timeoutRef.current);
|
|
30
|
+
}
|
|
31
|
+
timeoutRef.current = setTimeout(() => {
|
|
32
|
+
forceUpdate();
|
|
33
|
+
timeoutRef.current = null;
|
|
34
|
+
}, 50); // 50ms debounce to batch rapid updates
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const unsubscribe = context.organisationService.subscribe(debouncedUpdate);
|
|
38
|
+
|
|
39
|
+
return () => {
|
|
40
|
+
unsubscribe();
|
|
41
|
+
if (timeoutRef.current) {
|
|
42
|
+
clearTimeout(timeoutRef.current);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
27
45
|
}, [context.organisationService]);
|
|
28
46
|
|
|
29
47
|
return context.organisationService;
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* @module Hooks
|
|
5
5
|
*
|
|
6
6
|
* A React hook for accessing file references in authenticated contexts.
|
|
7
|
-
* Can handle both public and private files using the
|
|
7
|
+
* Can handle both public and private files using the core_file_references system.
|
|
8
8
|
*
|
|
9
9
|
* Features:
|
|
10
10
|
* - Works in authenticated contexts
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
*
|
|
21
21
|
* function FileView() {
|
|
22
22
|
* const { fileUrl, fileReference, isLoading, error } = useFileDisplay(
|
|
23
|
-
* '
|
|
23
|
+
* 'core_events',
|
|
24
24
|
* eventId,
|
|
25
25
|
* organisationId,
|
|
26
26
|
* FileCategory.EVENT_LOGOS
|
|
@@ -247,10 +247,9 @@ export function useFileDisplay(
|
|
|
247
247
|
if (user) {
|
|
248
248
|
// Query user's active organisation memberships
|
|
249
249
|
const { data: memberships, error: membershipError } = await supabase
|
|
250
|
-
.from('
|
|
250
|
+
.from('core_organisation_memberships')
|
|
251
251
|
.select('organisation_id')
|
|
252
|
-
.eq('user_id', user.id)
|
|
253
|
-
.or('status.is.null,status.eq.active');
|
|
252
|
+
.eq('user_id', user.id);
|
|
254
253
|
|
|
255
254
|
if (membershipError) {
|
|
256
255
|
logger.warn('useFileDisplay', 'Error querying organisation memberships:', membershipError);
|
|
@@ -292,7 +291,7 @@ export function useFileDisplay(
|
|
|
292
291
|
try {
|
|
293
292
|
// Try querying without organisation_id filter - let RLS handle security
|
|
294
293
|
let fallbackQuery = supabase
|
|
295
|
-
.from('
|
|
294
|
+
.from('core_file_references')
|
|
296
295
|
.select('id, table_name, record_id, file_path, file_metadata, organisation_id, app_id, is_public, created_at, updated_at')
|
|
297
296
|
.eq('table_name', table_name)
|
|
298
297
|
.eq('record_id', record_id)
|
|
@@ -350,11 +349,11 @@ export function useFileDisplay(
|
|
|
350
349
|
|
|
351
350
|
// If no files found through RPC, try a direct query as fallback
|
|
352
351
|
// This handles cases where RLS policy allows access but RPC security check is too strict
|
|
353
|
-
// (e.g.,
|
|
352
|
+
// (e.g., core_person files where user owns the person record but record_id != user_id)
|
|
354
353
|
if (files.length === 0) {
|
|
355
354
|
try {
|
|
356
355
|
let directQuery = supabase
|
|
357
|
-
.from('
|
|
356
|
+
.from('core_file_references')
|
|
358
357
|
.select('id, table_name, record_id, file_path, file_metadata, organisation_id, app_id, is_public, created_at, updated_at')
|
|
359
358
|
.eq('table_name', table_name)
|
|
360
359
|
.eq('record_id', record_id)
|
|
@@ -392,12 +391,7 @@ export function useFileDisplay(
|
|
|
392
391
|
// Category is stored in file_metadata JSONB field, not a direct column
|
|
393
392
|
if (category) {
|
|
394
393
|
// Single file mode - get files by category using RPC
|
|
395
|
-
|
|
396
|
-
table_name,
|
|
397
|
-
record_id,
|
|
398
|
-
category,
|
|
399
|
-
organisation_id
|
|
400
|
-
});
|
|
394
|
+
// Removed verbose debug log - only log on errors
|
|
401
395
|
files = await service.getFilesByCategory(
|
|
402
396
|
table_name,
|
|
403
397
|
record_id,
|
|
@@ -444,7 +438,7 @@ export function useFileDisplay(
|
|
|
444
438
|
const { data: { user } } = await supabase.auth.getUser();
|
|
445
439
|
if (user) {
|
|
446
440
|
const { data: memberships } = await supabase
|
|
447
|
-
.from('
|
|
441
|
+
.from('core_organisation_memberships')
|
|
448
442
|
.select('organisation_id')
|
|
449
443
|
.eq('user_id', user.id)
|
|
450
444
|
.or('status.is.null,status.eq.active');
|
|
@@ -511,21 +505,13 @@ export function useFileDisplay(
|
|
|
511
505
|
if (category && files.length > 0) {
|
|
512
506
|
// Single file mode - get first file
|
|
513
507
|
const firstFile = files[0];
|
|
514
|
-
|
|
515
|
-
id: firstFile.id,
|
|
516
|
-
file_path: firstFile.file_path,
|
|
517
|
-
is_public: firstFile.is_public,
|
|
518
|
-
has_file_metadata: !!firstFile.file_metadata,
|
|
519
|
-
category_in_metadata: firstFile.file_metadata?.category
|
|
520
|
-
});
|
|
521
|
-
|
|
508
|
+
// Removed verbose debug logs - only log on errors
|
|
522
509
|
setFileReference(firstFile);
|
|
523
510
|
|
|
524
511
|
// Generate URL based on file visibility
|
|
525
512
|
let url: string | null = null;
|
|
526
513
|
if (firstFile.is_public) {
|
|
527
514
|
url = getPublicUrl(supabase, firstFile.file_path, true);
|
|
528
|
-
logger.debug('useFileDisplay', 'Generated public URL:', url);
|
|
529
515
|
} else {
|
|
530
516
|
const signedUrlResult = await getSignedUrl(supabase, firstFile.file_path, {
|
|
531
517
|
appName: 'pace-core',
|
|
@@ -534,9 +520,15 @@ export function useFileDisplay(
|
|
|
534
520
|
expiresIn: 3600
|
|
535
521
|
});
|
|
536
522
|
url = signedUrlResult?.url || null;
|
|
537
|
-
|
|
523
|
+
// Only log if URL generation fails
|
|
524
|
+
if (!url) {
|
|
525
|
+
logger.warn('useFileDisplay', 'Failed to generate signed URL for file:', {
|
|
526
|
+
file_path: firstFile.file_path,
|
|
527
|
+
record_id,
|
|
528
|
+
table_name
|
|
529
|
+
});
|
|
530
|
+
}
|
|
538
531
|
}
|
|
539
|
-
logger.debug('useFileDisplay', 'Setting file URL:', url ? 'URL set' : 'URL is null');
|
|
540
532
|
setFileUrl(url);
|
|
541
533
|
} else {
|
|
542
534
|
// Multiple files mode - generate URLs for all files in batch
|
|
@@ -112,12 +112,12 @@ export interface UseQueryCacheReturn {
|
|
|
112
112
|
* const { getCachedQuery } = useQueryCache(supabase);
|
|
113
113
|
*
|
|
114
114
|
* const person = await getCachedQuery(
|
|
115
|
-
* '
|
|
115
|
+
* 'core_person',
|
|
116
116
|
* 'user_id',
|
|
117
117
|
* userId,
|
|
118
118
|
* async () => {
|
|
119
119
|
* const { data } = await supabase
|
|
120
|
-
* .from('
|
|
120
|
+
* .from('core_person')
|
|
121
121
|
* .select('id, first_name, last_name, email')
|
|
122
122
|
* .eq('user_id', userId)
|
|
123
123
|
* .single();
|
|
@@ -220,7 +220,7 @@ export function useQueryCache(supabase?: SupabaseClient<Database>): UseQueryCach
|
|
|
220
220
|
*/
|
|
221
221
|
export const queryCacheHelpers = {
|
|
222
222
|
/**
|
|
223
|
-
* Cache
|
|
223
|
+
* Cache core_person queries by user_id
|
|
224
224
|
* TTL: 5 minutes
|
|
225
225
|
*/
|
|
226
226
|
pacePersonByUserId: <T>(
|
|
@@ -228,7 +228,7 @@ export const queryCacheHelpers = {
|
|
|
228
228
|
userId: string,
|
|
229
229
|
fetchFn: () => Promise<T>
|
|
230
230
|
): Promise<T> => {
|
|
231
|
-
const cacheKey = `
|
|
231
|
+
const cacheKey = `core_person:user_id:${userId}`;
|
|
232
232
|
const now = Date.now();
|
|
233
233
|
const ttl = 300 * 1000; // 5 minutes
|
|
234
234
|
|
|
@@ -258,7 +258,7 @@ export const queryCacheHelpers = {
|
|
|
258
258
|
},
|
|
259
259
|
|
|
260
260
|
/**
|
|
261
|
-
* Cache
|
|
261
|
+
* Cache core_member queries by person_id
|
|
262
262
|
* TTL: 5 minutes
|
|
263
263
|
*/
|
|
264
264
|
paceMemberByPersonId: <T>(
|
|
@@ -266,7 +266,7 @@ export const queryCacheHelpers = {
|
|
|
266
266
|
personId: string,
|
|
267
267
|
fetchFn: () => Promise<T>
|
|
268
268
|
): Promise<T> => {
|
|
269
|
-
const cacheKey = `
|
|
269
|
+
const cacheKey = `core_member:person_id:${personId}`;
|
|
270
270
|
const now = Date.now();
|
|
271
271
|
const ttl = 300 * 1000; // 5 minutes
|
|
272
272
|
|
|
@@ -15,7 +15,7 @@ import { useOrganisations } from './useOrganisations';
|
|
|
15
15
|
import { setOrganisationContext } from '../utils/context/organisationContext';
|
|
16
16
|
import { createMockSupabaseClient, createMockQueryBuilderWithData } from '../__tests__/helpers/supabaseMock';
|
|
17
17
|
import { useResolvedScope } from '../rbac/hooks/useResolvedScope';
|
|
18
|
-
import {
|
|
18
|
+
import { useOrganisationSecurity } from './useOrganisationSecurity';
|
|
19
19
|
|
|
20
20
|
// Mock the providers
|
|
21
21
|
vi.mock('../providers', () => ({
|
|
@@ -36,9 +36,9 @@ vi.mock('../rbac/hooks/useResolvedScope', () => ({
|
|
|
36
36
|
useResolvedScope: vi.fn()
|
|
37
37
|
}));
|
|
38
38
|
|
|
39
|
-
// Mock
|
|
40
|
-
vi.mock('
|
|
41
|
-
|
|
39
|
+
// Mock useOrganisationSecurity
|
|
40
|
+
vi.mock('./useOrganisationSecurity', () => ({
|
|
41
|
+
useOrganisationSecurity: vi.fn()
|
|
42
42
|
}));
|
|
43
43
|
|
|
44
44
|
|
|
@@ -47,7 +47,7 @@ describe('useSecureDataAccess', () => {
|
|
|
47
47
|
const mockUseOrganisations = vi.mocked(useOrganisations);
|
|
48
48
|
const mockSetOrganisationContext = vi.mocked(setOrganisationContext);
|
|
49
49
|
const mockUseResolvedScope = vi.mocked(useResolvedScope);
|
|
50
|
-
const
|
|
50
|
+
const mockUseOrganisationSecurity = vi.mocked(useOrganisationSecurity);
|
|
51
51
|
|
|
52
52
|
const mockUser = {
|
|
53
53
|
id: 'user-123',
|
|
@@ -119,10 +119,17 @@ describe('useSecureDataAccess', () => {
|
|
|
119
119
|
error: null,
|
|
120
120
|
});
|
|
121
121
|
|
|
122
|
-
// Mock
|
|
123
|
-
|
|
124
|
-
isSuperAdmin: false,
|
|
125
|
-
|
|
122
|
+
// Mock useOrganisationSecurity to return not super admin
|
|
123
|
+
mockUseOrganisationSecurity.mockReturnValue({
|
|
124
|
+
superAdminContext: { isSuperAdmin: false, hasGlobalAccess: false, canManageAllOrganisations: false },
|
|
125
|
+
validateOrganisationAccess: vi.fn(),
|
|
126
|
+
hasMinimumRole: vi.fn(),
|
|
127
|
+
canAccessChildOrganisations: vi.fn(),
|
|
128
|
+
checkPermission: vi.fn(),
|
|
129
|
+
getPermissions: vi.fn(),
|
|
130
|
+
logOrganisationAccess: vi.fn(),
|
|
131
|
+
canManageOrganisation: vi.fn(),
|
|
132
|
+
} as any);
|
|
126
133
|
});
|
|
127
134
|
|
|
128
135
|
describe('Hook Initialization', () => {
|
|
@@ -154,9 +161,9 @@ describe('useSecureDataAccess', () => {
|
|
|
154
161
|
it('executes secure query with organisation filtering', async () => {
|
|
155
162
|
const { result } = renderHook(() => useSecureDataAccess());
|
|
156
163
|
|
|
157
|
-
const data = await result.current.secureQuery('
|
|
164
|
+
const data = await result.current.secureQuery('core_events', '*', { active: true });
|
|
158
165
|
|
|
159
|
-
expect(freshMockSupabase.from).toHaveBeenCalledWith('
|
|
166
|
+
expect(freshMockSupabase.from).toHaveBeenCalledWith('core_events');
|
|
160
167
|
expect(freshMockQueryBuilder.select).toHaveBeenCalledWith('*');
|
|
161
168
|
expect(freshMockQueryBuilder.eq).toHaveBeenCalledWith('organisation_id', 'org-123');
|
|
162
169
|
expect(freshMockQueryBuilder.eq).toHaveBeenCalledWith('active', true);
|
|
@@ -270,9 +277,9 @@ describe('useSecureDataAccess', () => {
|
|
|
270
277
|
it('executes secure update with organisation filtering', async () => {
|
|
271
278
|
const { result } = renderHook(() => useSecureDataAccess());
|
|
272
279
|
|
|
273
|
-
const data = await result.current.secureUpdate('
|
|
280
|
+
const data = await result.current.secureUpdate('core_events', { event_name: 'Updated Event' }, { id: 'event-123' });
|
|
274
281
|
|
|
275
|
-
expect(freshMockSupabase.from).toHaveBeenCalledWith('
|
|
282
|
+
expect(freshMockSupabase.from).toHaveBeenCalledWith('core_events');
|
|
276
283
|
expect(freshMockQueryBuilder.update).toHaveBeenCalledWith({ event_name: 'Updated Event' });
|
|
277
284
|
expect(freshMockQueryBuilder.eq).toHaveBeenCalledWith('id', 'event-123');
|
|
278
285
|
expect(freshMockQueryBuilder.eq).toHaveBeenCalledWith('organisation_id', 'org-123');
|
|
@@ -307,9 +314,9 @@ describe('useSecureDataAccess', () => {
|
|
|
307
314
|
it('executes secure delete with organisation filtering', async () => {
|
|
308
315
|
const { result } = renderHook(() => useSecureDataAccess());
|
|
309
316
|
|
|
310
|
-
await result.current.secureDelete('
|
|
317
|
+
await result.current.secureDelete('core_events', { id: 'event-123' });
|
|
311
318
|
|
|
312
|
-
expect(freshMockSupabase.from).toHaveBeenCalledWith('
|
|
319
|
+
expect(freshMockSupabase.from).toHaveBeenCalledWith('core_events');
|
|
313
320
|
expect(freshMockQueryBuilder.delete).toHaveBeenCalled();
|
|
314
321
|
expect(freshMockQueryBuilder.eq).toHaveBeenCalledWith('id', 'event-123');
|
|
315
322
|
expect(freshMockQueryBuilder.eq).toHaveBeenCalledWith('organisation_id', 'org-123');
|
|
@@ -387,7 +394,7 @@ describe('useSecureDataAccess', () => {
|
|
|
387
394
|
const { result } = renderHook(() => useSecureDataAccess());
|
|
388
395
|
|
|
389
396
|
// Use 'event' table which is in the tablesWithOrganisation list
|
|
390
|
-
await result.current.secureQuery('
|
|
397
|
+
await result.current.secureQuery('core_events', '*');
|
|
391
398
|
|
|
392
399
|
// Verify that the query was executed with organisation filter
|
|
393
400
|
expect(freshMockQueryBuilder.eq).toHaveBeenCalledWith('organisation_id', 'org-123');
|
|
@@ -443,7 +450,7 @@ describe('useSecureDataAccess', () => {
|
|
|
443
450
|
it('prevents data leaks between organisations', async () => {
|
|
444
451
|
const { result } = renderHook(() => useSecureDataAccess());
|
|
445
452
|
|
|
446
|
-
await result.current.secureQuery('
|
|
453
|
+
await result.current.secureQuery('core_events', '*');
|
|
447
454
|
|
|
448
455
|
// Verify organisation_id filter is always applied
|
|
449
456
|
expect(freshMockQueryBuilder.eq).toHaveBeenCalledWith('organisation_id', 'org-123');
|
|
@@ -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,7 +59,7 @@ 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 {
|
|
62
|
+
import { useOrganisationSecurity } from './useOrganisationSecurity';
|
|
63
63
|
import { useResolvedScope } from '../rbac/hooks/useResolvedScope';
|
|
64
64
|
|
|
65
65
|
export interface SecureDataAccessReturn {
|
|
@@ -158,7 +158,8 @@ export function useSecureDataAccess(): SecureDataAccessReturn {
|
|
|
158
158
|
const eventServiceContext = useContext(EventServiceContext);
|
|
159
159
|
const eventFromContext = eventServiceContext?.eventService?.getSelectedEvent() || null;
|
|
160
160
|
const effectiveSelectedEvent = selectedEvent || eventFromContext;
|
|
161
|
-
const {
|
|
161
|
+
const { superAdminContext } = useOrganisationSecurity();
|
|
162
|
+
const isSuperAdmin = superAdminContext.isSuperAdmin;
|
|
162
163
|
|
|
163
164
|
// Use resolved scope to get organisationId (derived from event if needed)
|
|
164
165
|
const { resolvedScope } = useResolvedScope({
|
|
@@ -231,15 +232,19 @@ export function useSecureDataAccess(): SecureDataAccessReturn {
|
|
|
231
232
|
// - RLS policies are the primary security layer (cannot be bypassed)
|
|
232
233
|
// - Application-level filtering adds an additional layer of protection
|
|
233
234
|
const tablesWithOrganisation = [
|
|
234
|
-
'
|
|
235
|
+
'core_events', 'organisation_settings',
|
|
235
236
|
'rbac_event_app_roles', 'rbac_organisation_roles',
|
|
236
237
|
// SECURITY: Phase 2 additions - complete organisation table mapping
|
|
237
238
|
'organisation_audit_log', 'organisation_invitations', 'organisation_app_access',
|
|
238
239
|
// SECURITY: Emergency additions for Phase 1 fixes
|
|
239
|
-
'cake_meal', 'cake_mealtype', '
|
|
240
|
+
'cake_meal', 'cake_mealtype', 'core_person',
|
|
241
|
+
// NOTE: core_member, medi_profile, core_contact, core_consent, core_identification, core_qualification
|
|
242
|
+
// are now person-scoped (not organisation-scoped) - removed from this list
|
|
240
243
|
// SECURITY: Phase 3A additions - medical and personal data
|
|
241
|
-
|
|
242
|
-
|
|
244
|
+
// NOTE: medi_condition, medi_diet, medi_action_plan, medi_profile_versions are now person-scoped
|
|
245
|
+
// (via medi_profile) - removed from this list
|
|
246
|
+
// core_identification_type remains organisation-scoped (lookup table)
|
|
247
|
+
'core_identification_type',
|
|
243
248
|
'form_responses', 'form_response_values', 'forms',
|
|
244
249
|
// SECURITY: Phase 3B additions - remaining critical tables
|
|
245
250
|
'invoice', 'line_item', 'credit_balance', 'payment_method',
|
|
@@ -360,12 +365,12 @@ export function useSecureDataAccess(): SecureDataAccessReturn {
|
|
|
360
365
|
|
|
361
366
|
// Add organisation filter only if table has organisation_id column
|
|
362
367
|
const tablesWithOrganisation = [
|
|
363
|
-
'
|
|
368
|
+
'core_events', 'organisation_settings',
|
|
364
369
|
'rbac_event_app_roles', 'rbac_organisation_roles',
|
|
365
370
|
// SECURITY: Phase 2 additions - complete organisation table mapping
|
|
366
371
|
'organisation_audit_log', 'organisation_invitations', 'organisation_app_access',
|
|
367
372
|
// SECURITY: Emergency additions for Phase 1 fixes
|
|
368
|
-
'cake_meal', 'cake_mealtype', '
|
|
373
|
+
'cake_meal', 'cake_mealtype', 'core_person', 'core_member'
|
|
369
374
|
];
|
|
370
375
|
|
|
371
376
|
if (!bypassOrganisationFilter && organisationId && tablesWithOrganisation.includes(table)) {
|
|
@@ -407,15 +412,15 @@ export function useSecureDataAccess(): SecureDataAccessReturn {
|
|
|
407
412
|
|
|
408
413
|
// Add organisation filter only if table has organisation_id column
|
|
409
414
|
const tablesWithOrganisation = [
|
|
410
|
-
'
|
|
415
|
+
'core_events', 'organisation_settings',
|
|
411
416
|
'rbac_event_app_roles', 'rbac_organisation_roles',
|
|
412
417
|
// SECURITY: Phase 2 additions - complete organisation table mapping
|
|
413
418
|
'organisation_audit_log', 'organisation_invitations', 'organisation_app_access',
|
|
414
419
|
// SECURITY: Emergency additions for Phase 1 fixes
|
|
415
|
-
'cake_meal', 'cake_mealtype', '
|
|
420
|
+
'cake_meal', 'cake_mealtype', 'core_person', 'core_member',
|
|
416
421
|
// SECURITY: Phase 3A additions - medical and personal data
|
|
417
422
|
'medi_profile', 'medi_condition', 'medi_diet', 'medi_action_plan', 'medi_profile_versions',
|
|
418
|
-
'
|
|
423
|
+
'core_consent', 'core_contact', 'core_identification', 'core_identification_type', 'core_qualification',
|
|
419
424
|
'form_responses', 'form_response_values', 'forms',
|
|
420
425
|
// SECURITY: Phase 3B additions - remaining critical tables
|
|
421
426
|
'invoice', 'line_item', 'credit_balance', 'payment_method',
|
|
@@ -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 }),
|