@jmruthers/pace-core 0.5.189 → 0.5.190

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (420) hide show
  1. package/core-usage-manifest.json +0 -4
  2. package/dist/{AuthService-B-cd2MA4.d.ts → AuthService-CbP_utw2.d.ts} +7 -3
  3. package/dist/{DataTable-GUFUNZ3N.js → DataTable-ON3IXISJ.js} +8 -8
  4. package/dist/{PublicPageProvider-B8HaLe69.d.ts → PublicPageProvider-C4uxosp6.d.ts} +83 -24
  5. package/dist/{UnifiedAuthProvider-BG0AL5eE.d.ts → UnifiedAuthProvider-BYA9qB-o.d.ts} +4 -3
  6. package/dist/{UnifiedAuthProvider-643PUAIM.js → UnifiedAuthProvider-X5NXANVI.js} +4 -2
  7. package/dist/{api-YP7XD5L6.js → api-I6UCQ5S6.js} +4 -2
  8. package/dist/{chunk-DDM4CCYT.js → chunk-4QYC5L4K.js} +60 -35
  9. package/dist/chunk-4QYC5L4K.js.map +1 -0
  10. package/dist/{chunk-IM4QE42D.js → chunk-73HSNNOQ.js} +141 -326
  11. package/dist/chunk-73HSNNOQ.js.map +1 -0
  12. package/dist/{chunk-YHCN776L.js → chunk-DZWK57KZ.js} +2 -75
  13. package/dist/chunk-DZWK57KZ.js.map +1 -0
  14. package/dist/{chunk-3GOZZZYH.js → chunk-HQVPB5MZ.js} +238 -301
  15. package/dist/chunk-HQVPB5MZ.js.map +1 -0
  16. package/dist/{chunk-THRPYOFK.js → chunk-HW3OVDUF.js} +5 -5
  17. package/dist/chunk-HW3OVDUF.js.map +1 -0
  18. package/dist/{chunk-F2IMUDXZ.js → chunk-I7PSE6JW.js} +75 -2
  19. package/dist/chunk-I7PSE6JW.js.map +1 -0
  20. package/dist/{chunk-VGZZXKBR.js → chunk-J2XXC7R5.js} +280 -52
  21. package/dist/chunk-J2XXC7R5.js.map +1 -0
  22. package/dist/{chunk-UCQSRW7Z.js → chunk-NIU6J6OX.js} +425 -378
  23. package/dist/chunk-NIU6J6OX.js.map +1 -0
  24. package/dist/{chunk-HESYZWZW.js → chunk-QWWZ5CAQ.js} +2 -2
  25. package/dist/{chunk-HEHYGYOX.js → chunk-RUYZKXOD.js} +401 -46
  26. package/dist/chunk-RUYZKXOD.js.map +1 -0
  27. package/dist/{chunk-2UUZZJFT.js → chunk-SDMHPX3X.js} +176 -160
  28. package/dist/{chunk-2UUZZJFT.js.map → chunk-SDMHPX3X.js.map} +1 -1
  29. package/dist/{chunk-MX64ZF6I.js → chunk-STYK4OH2.js} +11 -11
  30. package/dist/chunk-STYK4OH2.js.map +1 -0
  31. package/dist/{chunk-YGPFYGA6.js → chunk-VVBAW5A5.js} +822 -498
  32. package/dist/chunk-VVBAW5A5.js.map +1 -0
  33. package/dist/chunk-Y4BUBBHD.js +614 -0
  34. package/dist/chunk-Y4BUBBHD.js.map +1 -0
  35. package/dist/{chunk-SAUPYVLF.js → chunk-ZSAAAMVR.js} +1 -1
  36. package/dist/chunk-ZSAAAMVR.js.map +1 -0
  37. package/dist/components.d.ts +3 -4
  38. package/dist/components.js +19 -19
  39. package/dist/components.js.map +1 -1
  40. package/dist/eslint-rules/pace-core-compliance.cjs +0 -2
  41. package/dist/{file-reference-D037xOFK.d.ts → file-reference-BavO2eQj.d.ts} +13 -10
  42. package/dist/hooks.d.ts +10 -5
  43. package/dist/hooks.js +14 -8
  44. package/dist/hooks.js.map +1 -1
  45. package/dist/index.d.ts +13 -11
  46. package/dist/index.js +79 -69
  47. package/dist/index.js.map +1 -1
  48. package/dist/providers.d.ts +3 -3
  49. package/dist/providers.js +3 -1
  50. package/dist/rbac/index.d.ts +76 -12
  51. package/dist/rbac/index.js +12 -9
  52. package/dist/types.d.ts +1 -1
  53. package/dist/types.js +1 -1
  54. package/dist/{usePublicRouteParams-CTDELQ7H.d.ts → usePublicRouteParams-DxIDS4bC.d.ts} +16 -9
  55. package/dist/utils.js +16 -16
  56. package/docs/README.md +2 -2
  57. package/docs/api/classes/ColumnFactory.md +1 -1
  58. package/docs/api/classes/ErrorBoundary.md +1 -1
  59. package/docs/api/classes/InvalidScopeError.md +2 -2
  60. package/docs/api/classes/Logger.md +1 -1
  61. package/docs/api/classes/MissingUserContextError.md +2 -2
  62. package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
  63. package/docs/api/classes/PermissionDeniedError.md +1 -1
  64. package/docs/api/classes/RBACAuditManager.md +1 -1
  65. package/docs/api/classes/RBACCache.md +1 -1
  66. package/docs/api/classes/RBACEngine.md +4 -4
  67. package/docs/api/classes/RBACError.md +1 -1
  68. package/docs/api/classes/RBACNotInitializedError.md +2 -2
  69. package/docs/api/classes/SecureSupabaseClient.md +21 -16
  70. package/docs/api/classes/StorageUtils.md +7 -4
  71. package/docs/api/enums/FileCategory.md +1 -1
  72. package/docs/api/enums/LogLevel.md +1 -1
  73. package/docs/api/enums/RBACErrorCode.md +1 -1
  74. package/docs/api/enums/RPCFunction.md +1 -1
  75. package/docs/api/interfaces/AddressFieldProps.md +1 -1
  76. package/docs/api/interfaces/AddressFieldRef.md +1 -1
  77. package/docs/api/interfaces/AggregateConfig.md +1 -1
  78. package/docs/api/interfaces/AutocompleteOptions.md +1 -1
  79. package/docs/api/interfaces/AvatarProps.md +1 -1
  80. package/docs/api/interfaces/BadgeProps.md +1 -1
  81. package/docs/api/interfaces/ButtonProps.md +1 -1
  82. package/docs/api/interfaces/CalendarProps.md +20 -6
  83. package/docs/api/interfaces/CardProps.md +1 -1
  84. package/docs/api/interfaces/ColorPalette.md +1 -1
  85. package/docs/api/interfaces/ColorShade.md +1 -1
  86. package/docs/api/interfaces/ComplianceResult.md +1 -1
  87. package/docs/api/interfaces/DataAccessRecord.md +9 -9
  88. package/docs/api/interfaces/DataRecord.md +1 -1
  89. package/docs/api/interfaces/DataTableAction.md +1 -1
  90. package/docs/api/interfaces/DataTableColumn.md +1 -1
  91. package/docs/api/interfaces/DataTableProps.md +1 -1
  92. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  93. package/docs/api/interfaces/DatabaseComplianceResult.md +1 -1
  94. package/docs/api/interfaces/DatabaseIssue.md +1 -1
  95. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  96. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  97. package/docs/api/interfaces/EventAppRoleData.md +1 -1
  98. package/docs/api/interfaces/ExportColumn.md +1 -1
  99. package/docs/api/interfaces/ExportOptions.md +1 -1
  100. package/docs/api/interfaces/FileDisplayProps.md +62 -16
  101. package/docs/api/interfaces/FileMetadata.md +1 -1
  102. package/docs/api/interfaces/FileReference.md +2 -2
  103. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  104. package/docs/api/interfaces/FileUploadOptions.md +26 -12
  105. package/docs/api/interfaces/FileUploadProps.md +30 -19
  106. package/docs/api/interfaces/FooterProps.md +1 -1
  107. package/docs/api/interfaces/FormFieldProps.md +1 -1
  108. package/docs/api/interfaces/FormProps.md +1 -1
  109. package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
  110. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  111. package/docs/api/interfaces/InputProps.md +1 -1
  112. package/docs/api/interfaces/LabelProps.md +1 -1
  113. package/docs/api/interfaces/LoggerConfig.md +1 -1
  114. package/docs/api/interfaces/LoginFormProps.md +1 -1
  115. package/docs/api/interfaces/NavigationAccessRecord.md +10 -10
  116. package/docs/api/interfaces/NavigationContextType.md +9 -9
  117. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  118. package/docs/api/interfaces/NavigationItem.md +1 -1
  119. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  120. package/docs/api/interfaces/NavigationProviderProps.md +7 -7
  121. package/docs/api/interfaces/Organisation.md +1 -1
  122. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  123. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  124. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  125. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  126. package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
  127. package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
  128. package/docs/api/interfaces/PageAccessRecord.md +8 -8
  129. package/docs/api/interfaces/PagePermissionContextType.md +8 -8
  130. package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
  131. package/docs/api/interfaces/PagePermissionProviderProps.md +7 -7
  132. package/docs/api/interfaces/PaletteData.md +1 -1
  133. package/docs/api/interfaces/ParsedAddress.md +1 -1
  134. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  135. package/docs/api/interfaces/ProgressProps.md +3 -11
  136. package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
  137. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  138. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  139. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  140. package/docs/api/interfaces/QuickFix.md +1 -1
  141. package/docs/api/interfaces/RBACAccessValidateParams.md +1 -1
  142. package/docs/api/interfaces/RBACAccessValidateResult.md +1 -1
  143. package/docs/api/interfaces/RBACAuditLogParams.md +1 -1
  144. package/docs/api/interfaces/RBACAuditLogResult.md +1 -1
  145. package/docs/api/interfaces/RBACConfig.md +1 -1
  146. package/docs/api/interfaces/RBACContext.md +1 -1
  147. package/docs/api/interfaces/RBACLogger.md +1 -1
  148. package/docs/api/interfaces/RBACPageAccessCheckParams.md +1 -1
  149. package/docs/api/interfaces/RBACPerformanceMetrics.md +1 -1
  150. package/docs/api/interfaces/RBACPermissionCheckParams.md +1 -1
  151. package/docs/api/interfaces/RBACPermissionCheckResult.md +1 -1
  152. package/docs/api/interfaces/RBACPermissionsGetParams.md +1 -1
  153. package/docs/api/interfaces/RBACPermissionsGetResult.md +1 -1
  154. package/docs/api/interfaces/RBACResult.md +1 -1
  155. package/docs/api/interfaces/RBACRoleGrantParams.md +1 -1
  156. package/docs/api/interfaces/RBACRoleGrantResult.md +1 -1
  157. package/docs/api/interfaces/RBACRoleRevokeParams.md +1 -1
  158. package/docs/api/interfaces/RBACRoleRevokeResult.md +1 -1
  159. package/docs/api/interfaces/RBACRoleValidateParams.md +1 -1
  160. package/docs/api/interfaces/RBACRoleValidateResult.md +1 -1
  161. package/docs/api/interfaces/RBACRolesListParams.md +1 -1
  162. package/docs/api/interfaces/RBACRolesListResult.md +1 -1
  163. package/docs/api/interfaces/RBACSessionTrackParams.md +1 -1
  164. package/docs/api/interfaces/RBACSessionTrackResult.md +1 -1
  165. package/docs/api/interfaces/ResourcePermissions.md +1 -1
  166. package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
  167. package/docs/api/interfaces/RoleBasedRouterContextType.md +8 -8
  168. package/docs/api/interfaces/RoleBasedRouterProps.md +10 -10
  169. package/docs/api/interfaces/RoleManagementResult.md +1 -1
  170. package/docs/api/interfaces/RouteAccessRecord.md +10 -10
  171. package/docs/api/interfaces/RouteConfig.md +10 -10
  172. package/docs/api/interfaces/RuntimeComplianceResult.md +1 -1
  173. package/docs/api/interfaces/SecureDataContextType.md +9 -9
  174. package/docs/api/interfaces/SecureDataProviderProps.md +8 -8
  175. package/docs/api/interfaces/SessionRestorationLoaderProps.md +1 -1
  176. package/docs/api/interfaces/SetupIssue.md +1 -1
  177. package/docs/api/interfaces/StorageConfig.md +4 -4
  178. package/docs/api/interfaces/StorageFileInfo.md +7 -7
  179. package/docs/api/interfaces/StorageFileMetadata.md +25 -14
  180. package/docs/api/interfaces/StorageListOptions.md +22 -9
  181. package/docs/api/interfaces/StorageListResult.md +4 -4
  182. package/docs/api/interfaces/StorageUploadOptions.md +21 -8
  183. package/docs/api/interfaces/StorageUploadResult.md +6 -6
  184. package/docs/api/interfaces/StorageUrlOptions.md +19 -6
  185. package/docs/api/interfaces/StyleImport.md +1 -1
  186. package/docs/api/interfaces/SwitchProps.md +1 -1
  187. package/docs/api/interfaces/TabsContentProps.md +1 -1
  188. package/docs/api/interfaces/TabsListProps.md +1 -1
  189. package/docs/api/interfaces/TabsProps.md +1 -1
  190. package/docs/api/interfaces/TabsTriggerProps.md +1 -1
  191. package/docs/api/interfaces/TextareaProps.md +1 -1
  192. package/docs/api/interfaces/ToastActionElement.md +1 -1
  193. package/docs/api/interfaces/ToastProps.md +1 -1
  194. package/docs/api/interfaces/UnifiedAuthContextType.md +53 -53
  195. package/docs/api/interfaces/UnifiedAuthProviderProps.md +13 -13
  196. package/docs/api/interfaces/UseFormDialogOptions.md +1 -1
  197. package/docs/api/interfaces/UseFormDialogReturn.md +1 -1
  198. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  199. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  200. package/docs/api/interfaces/UsePublicEventLogoOptions.md +1 -1
  201. package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
  202. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  203. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  204. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +1 -1
  205. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
  206. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  207. package/docs/api/interfaces/UseResolvedScopeOptions.md +4 -4
  208. package/docs/api/interfaces/UseResolvedScopeReturn.md +4 -4
  209. package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
  210. package/docs/api/interfaces/UserEventAccess.md +11 -11
  211. package/docs/api/interfaces/UserMenuProps.md +1 -1
  212. package/docs/api/interfaces/UserProfile.md +1 -1
  213. package/docs/api/modules.md +151 -92
  214. package/docs/api-reference/components.md +15 -7
  215. package/docs/api-reference/providers.md +2 -2
  216. package/docs/api-reference/rpc-functions.md +1 -0
  217. package/docs/best-practices/README.md +1 -1
  218. package/docs/best-practices/deployment.md +8 -8
  219. package/docs/getting-started/examples/README.md +2 -2
  220. package/docs/getting-started/installation-guide.md +4 -4
  221. package/docs/getting-started/quick-start.md +3 -3
  222. package/docs/migration/MIGRATION_GUIDE.md +3 -3
  223. package/docs/rbac/compliance/compliance-guide.md +2 -2
  224. package/docs/rbac/event-based-apps.md +2 -2
  225. package/docs/rbac/getting-started.md +2 -2
  226. package/docs/rbac/quick-start.md +2 -2
  227. package/docs/security/README.md +4 -4
  228. package/docs/standards/07-rbac-and-rls-standard.md +430 -7
  229. package/docs/troubleshooting/README.md +2 -2
  230. package/docs/troubleshooting/migration.md +3 -3
  231. package/package.json +1 -3
  232. package/scripts/check-pace-core-compliance.cjs +1 -1
  233. package/scripts/check-pace-core-compliance.js +1 -1
  234. package/src/__tests__/fixtures/supabase.ts +301 -0
  235. package/src/__tests__/public-recipe-view.test.ts +9 -9
  236. package/src/__tests__/rls-policies.test.ts +197 -61
  237. package/src/components/AddressField/AddressField.test.tsx +42 -0
  238. package/src/components/AddressField/AddressField.tsx +71 -60
  239. package/src/components/AddressField/README.md +1 -0
  240. package/src/components/Alert/Alert.test.tsx +50 -10
  241. package/src/components/Alert/Alert.tsx +5 -3
  242. package/src/components/Avatar/Avatar.test.tsx +95 -43
  243. package/src/components/Avatar/Avatar.tsx +16 -16
  244. package/src/components/Button/Button.test.tsx +2 -1
  245. package/src/components/Button/Button.tsx +3 -3
  246. package/src/components/Calendar/Calendar.test.tsx +53 -37
  247. package/src/components/Calendar/Calendar.tsx +409 -82
  248. package/src/components/Card/Card.test.tsx +7 -4
  249. package/src/components/Card/Card.tsx +3 -6
  250. package/src/components/Checkbox/Checkbox.tsx +2 -2
  251. package/src/components/DataTable/components/ActionButtons.tsx +5 -5
  252. package/src/components/DataTable/components/BulkOperationsDropdown.tsx +2 -2
  253. package/src/components/DataTable/components/ColumnFilter.tsx +1 -1
  254. package/src/components/DataTable/components/ColumnVisibilityDropdown.tsx +3 -3
  255. package/src/components/DataTable/components/DataTableBody.tsx +12 -12
  256. package/src/components/DataTable/components/DataTableCore.tsx +3 -3
  257. package/src/components/DataTable/components/DataTableToolbar.tsx +5 -5
  258. package/src/components/DataTable/components/DraggableColumnHeader.tsx +3 -3
  259. package/src/components/DataTable/components/EditableRow.tsx +2 -2
  260. package/src/components/DataTable/components/EmptyState.tsx +3 -3
  261. package/src/components/DataTable/components/GroupHeader.tsx +2 -2
  262. package/src/components/DataTable/components/GroupingDropdown.tsx +1 -1
  263. package/src/components/DataTable/components/ImportModal.tsx +4 -4
  264. package/src/components/DataTable/components/LoadingState.tsx +1 -1
  265. package/src/components/DataTable/components/PaginationControls.tsx +11 -11
  266. package/src/components/DataTable/components/UnifiedTableBody.tsx +9 -9
  267. package/src/components/DataTable/components/ViewRowModal.tsx +2 -2
  268. package/src/components/DataTable/components/__tests__/AccessDeniedPage.test.tsx +11 -37
  269. package/src/components/DataTable/components/__tests__/DataTableToolbar.test.tsx +157 -0
  270. package/src/components/DataTable/components/__tests__/LoadingState.test.tsx +2 -1
  271. package/src/components/DataTable/components/__tests__/VirtualizedDataTable.test.tsx +128 -0
  272. package/src/components/DataTable/core/__tests__/ActionManager.test.ts +19 -0
  273. package/src/components/DataTable/core/__tests__/ColumnFactory.test.ts +51 -0
  274. package/src/components/DataTable/core/__tests__/ColumnManager.test.ts +84 -0
  275. package/src/components/DataTable/core/__tests__/DataManager.test.ts +14 -0
  276. package/src/components/DataTable/core/__tests__/DataTableContext.test.tsx +136 -0
  277. package/src/components/DataTable/core/__tests__/LocalDataAdapter.test.ts +16 -0
  278. package/src/components/DataTable/core/__tests__/PluginRegistry.test.ts +18 -0
  279. package/src/components/DataTable/hooks/useDataTablePermissions.ts +28 -7
  280. package/src/components/DataTable/utils/__tests__/hierarchicalUtils.test.ts +30 -1
  281. package/src/components/DataTable/utils/hierarchicalUtils.ts +38 -10
  282. package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +8 -3
  283. package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.tsx +4 -4
  284. package/src/components/Dialog/Dialog.tsx +2 -2
  285. package/src/components/EventSelector/EventSelector.tsx +7 -7
  286. package/src/components/FileDisplay/FileDisplay.tsx +291 -179
  287. package/src/components/FileUpload/FileUpload.tsx +7 -4
  288. package/src/components/Header/Header.test.tsx +28 -0
  289. package/src/components/Header/Header.tsx +22 -9
  290. package/src/components/InactivityWarningModal/InactivityWarningModal.tsx +2 -2
  291. package/src/components/LoadingSpinner/LoadingSpinner.test.tsx +19 -14
  292. package/src/components/LoadingSpinner/LoadingSpinner.tsx +5 -5
  293. package/src/components/NavigationMenu/NavigationMenu.test.tsx +127 -1
  294. package/src/components/OrganisationSelector/OrganisationSelector.tsx +8 -8
  295. package/src/components/PaceAppLayout/PaceAppLayout.integration.test.tsx +4 -0
  296. package/src/components/PaceAppLayout/PaceAppLayout.performance.test.tsx +3 -0
  297. package/src/components/PaceAppLayout/PaceAppLayout.security.test.tsx +3 -0
  298. package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +16 -6
  299. package/src/components/PaceAppLayout/PaceAppLayout.tsx +37 -3
  300. package/src/components/PaceAppLayout/test-setup.tsx +1 -0
  301. package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +66 -45
  302. package/src/components/PaceLoginPage/PaceLoginPage.tsx +6 -4
  303. package/src/components/Progress/Progress.test.tsx +18 -19
  304. package/src/components/Progress/Progress.tsx +31 -32
  305. package/src/components/PublicLayout/PublicLayout.test.tsx +6 -6
  306. package/src/components/PublicLayout/PublicPageProvider.tsx +5 -3
  307. package/src/components/Select/Select.tsx +5 -5
  308. package/src/components/Switch/Switch.test.tsx +2 -1
  309. package/src/components/Switch/Switch.tsx +1 -1
  310. package/src/components/Toast/Toast.tsx +1 -1
  311. package/src/components/Tooltip/Tooltip.test.tsx +8 -2
  312. package/src/components/UserMenu/UserMenu.tsx +3 -3
  313. package/src/eslint-rules/pace-core-compliance.cjs +0 -2
  314. package/src/eslint-rules/pace-core-compliance.js +0 -2
  315. package/src/hooks/__tests__/hooks.integration.test.tsx +4 -1
  316. package/src/hooks/__tests__/useAppConfig.unit.test.ts +76 -5
  317. package/src/hooks/__tests__/useDataTableState.test.ts +76 -0
  318. package/src/hooks/__tests__/useFileUrl.unit.test.ts +25 -69
  319. package/src/hooks/__tests__/useFileUrlCache.test.ts +129 -0
  320. package/src/hooks/__tests__/usePreventTabReload.test.ts +88 -0
  321. package/src/hooks/__tests__/{usePublicEvent.unit.test.ts → usePublicEvent.test.ts} +28 -1
  322. package/src/hooks/__tests__/useQueryCache.test.ts +144 -0
  323. package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +58 -16
  324. package/src/hooks/index.ts +1 -1
  325. package/src/hooks/public/usePublicEvent.ts +2 -2
  326. package/src/hooks/public/usePublicFileDisplay.ts +173 -87
  327. package/src/hooks/useAppConfig.ts +24 -5
  328. package/src/hooks/useFileDisplay.ts +297 -34
  329. package/src/hooks/useFileReference.ts +56 -11
  330. package/src/hooks/useFileUrl.ts +1 -1
  331. package/src/hooks/useInactivityTracker.ts +16 -7
  332. package/src/hooks/usePermissionCache.test.ts +85 -8
  333. package/src/hooks/useQueryCache.ts +21 -0
  334. package/src/hooks/useSecureDataAccess.test.ts +80 -35
  335. package/src/hooks/useSecureDataAccess.ts +80 -37
  336. package/src/providers/services/EventServiceProvider.tsx +37 -17
  337. package/src/providers/services/InactivityServiceProvider.tsx +4 -4
  338. package/src/providers/services/OrganisationServiceProvider.tsx +8 -1
  339. package/src/providers/services/UnifiedAuthProvider.tsx +115 -29
  340. package/src/rbac/__tests__/auth-rbac.e2e.test.tsx +451 -0
  341. package/src/rbac/__tests__/engine.comprehensive.test.ts +12 -0
  342. package/src/rbac/__tests__/rbac-engine-core-logic.test.ts +8 -0
  343. package/src/rbac/__tests__/rbac-engine-simplified.test.ts +4 -0
  344. package/src/rbac/api.ts +240 -36
  345. package/src/rbac/cache-invalidation.ts +21 -7
  346. package/src/rbac/compliance/quick-fix-suggestions.ts +1 -1
  347. package/src/rbac/components/NavigationGuard.tsx +23 -63
  348. package/src/rbac/components/NavigationProvider.test.tsx +52 -23
  349. package/src/rbac/components/NavigationProvider.tsx +13 -11
  350. package/src/rbac/components/PagePermissionGuard.tsx +77 -203
  351. package/src/rbac/components/PagePermissionProvider.tsx +13 -11
  352. package/src/rbac/components/PermissionEnforcer.tsx +24 -62
  353. package/src/rbac/components/RoleBasedRouter.tsx +14 -12
  354. package/src/rbac/components/SecureDataProvider.tsx +13 -11
  355. package/src/rbac/components/__tests__/NavigationGuard.test.tsx +104 -41
  356. package/src/rbac/components/__tests__/NavigationProvider.test.tsx +49 -12
  357. package/src/rbac/components/__tests__/PagePermissionGuard.race-condition.test.tsx +22 -1
  358. package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +161 -82
  359. package/src/rbac/components/__tests__/PagePermissionGuard.verification.test.tsx +22 -1
  360. package/src/rbac/components/__tests__/PermissionEnforcer.test.tsx +77 -30
  361. package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +39 -5
  362. package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +47 -4
  363. package/src/rbac/engine.ts +4 -2
  364. package/src/rbac/hooks/__tests__/useSecureSupabase.test.ts +144 -52
  365. package/src/rbac/hooks/index.ts +3 -0
  366. package/src/rbac/hooks/useCan.test.ts +101 -53
  367. package/src/rbac/hooks/usePermissions.ts +108 -41
  368. package/src/rbac/hooks/useRBAC.test.ts +11 -3
  369. package/src/rbac/hooks/useRBAC.ts +83 -40
  370. package/src/rbac/hooks/useResolvedScope.test.ts +189 -63
  371. package/src/rbac/hooks/useResolvedScope.ts +128 -70
  372. package/src/rbac/hooks/useSecureSupabase.ts +36 -19
  373. package/src/rbac/hooks/useSuperAdminBypass.ts +126 -0
  374. package/src/rbac/request-deduplication.ts +1 -1
  375. package/src/rbac/secureClient.ts +72 -12
  376. package/src/rbac/security.ts +29 -23
  377. package/src/rbac/types.ts +10 -0
  378. package/src/rbac/utils/__tests__/contextValidator.test.ts +150 -0
  379. package/src/rbac/utils/__tests__/deep-equal.test.ts +53 -0
  380. package/src/rbac/utils/__tests__/eventContext.test.ts +6 -1
  381. package/src/rbac/utils/contextValidator.ts +288 -0
  382. package/src/rbac/utils/eventContext.ts +48 -2
  383. package/src/services/EventService.ts +165 -21
  384. package/src/services/OrganisationService.ts +37 -2
  385. package/src/services/__tests__/EventService.test.ts +26 -21
  386. package/src/types/file-reference.ts +13 -10
  387. package/src/utils/app/appNameResolver.test.ts +346 -73
  388. package/src/utils/context/superAdminOverride.ts +58 -0
  389. package/src/utils/file-reference/index.ts +61 -33
  390. package/src/utils/google-places/googlePlacesUtils.test.ts +98 -0
  391. package/src/utils/google-places/loadGoogleMapsScript.test.ts +83 -0
  392. package/src/utils/storage/helpers.test.ts +1 -1
  393. package/src/utils/storage/helpers.ts +38 -19
  394. package/src/utils/storage/types.ts +15 -8
  395. package/src/utils/validation/__tests__/csrf.test.ts +105 -0
  396. package/src/utils/validation/__tests__/sqlInjectionProtection.test.ts +92 -0
  397. package/src/vite-env.d.ts +2 -2
  398. package/dist/chunk-3GOZZZYH.js.map +0 -1
  399. package/dist/chunk-DDM4CCYT.js.map +0 -1
  400. package/dist/chunk-E7UAOUMY.js +0 -75
  401. package/dist/chunk-E7UAOUMY.js.map +0 -1
  402. package/dist/chunk-F2IMUDXZ.js.map +0 -1
  403. package/dist/chunk-HEHYGYOX.js.map +0 -1
  404. package/dist/chunk-IM4QE42D.js.map +0 -1
  405. package/dist/chunk-MX64ZF6I.js.map +0 -1
  406. package/dist/chunk-SAUPYVLF.js.map +0 -1
  407. package/dist/chunk-THRPYOFK.js.map +0 -1
  408. package/dist/chunk-UCQSRW7Z.js.map +0 -1
  409. package/dist/chunk-VGZZXKBR.js.map +0 -1
  410. package/dist/chunk-YGPFYGA6.js.map +0 -1
  411. package/dist/chunk-YHCN776L.js.map +0 -1
  412. package/src/hooks/__tests__/usePermissionCache.simple.test.ts +0 -192
  413. package/src/hooks/__tests__/usePermissionCache.unit.test.ts +0 -741
  414. package/src/hooks/__tests__/usePublicEvent.simple.test.ts +0 -703
  415. package/src/rbac/hooks/useRBAC.simple.test.ts +0 -95
  416. package/src/rbac/utils/__tests__/eventContext.unit.test.ts +0 -428
  417. /package/dist/{DataTable-GUFUNZ3N.js.map → DataTable-ON3IXISJ.js.map} +0 -0
  418. /package/dist/{UnifiedAuthProvider-643PUAIM.js.map → UnifiedAuthProvider-X5NXANVI.js.map} +0 -0
  419. /package/dist/{api-YP7XD5L6.js.map → api-I6UCQ5S6.js.map} +0 -0
  420. /package/dist/{chunk-HESYZWZW.js.map → chunk-QWWZ5CAQ.js.map} +0 -0
@@ -7,8 +7,8 @@
7
7
  * Comprehensive tests for the usePermissionCache hook covering all critical functionality.
8
8
  */
9
9
 
10
- import { renderHook, waitFor, act } from '@testing-library/react';
11
- import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
10
+ import { renderHook, act } from '@testing-library/react';
11
+ import { vi, describe, it, expect, beforeEach } from 'vitest';
12
12
  import { usePermissionCache } from './usePermissionCache';
13
13
 
14
14
  // Mock the dependencies
@@ -29,6 +29,16 @@ vi.mock('../rbac/api', () => ({
29
29
  isPermittedCached: vi.fn()
30
30
  }));
31
31
 
32
+ const loggerSpy = vi.hoisted(() => ({
33
+ warn: vi.fn(),
34
+ error: vi.fn()
35
+ }));
36
+
37
+ vi.mock('../utils/core/logger', () => ({
38
+ logger: loggerSpy,
39
+ createLogger: () => loggerSpy
40
+ }));
41
+
32
42
  import { useUnifiedAuth } from '../providers/services/UnifiedAuthProvider';
33
43
  import { useOrganisations } from './useOrganisations';
34
44
  import { useEvents } from './useEvents';
@@ -41,7 +51,7 @@ const mockIsPermittedCached = vi.mocked(isPermittedCached);
41
51
  describe('usePermissionCache', () => {
42
52
  beforeEach(() => {
43
53
  vi.clearAllMocks();
44
-
54
+
45
55
  // Setup default mocks
46
56
  mockUseUnifiedAuthFn.mockReturnValue({
47
57
  user: { id: 'user-123' },
@@ -50,19 +60,19 @@ describe('usePermissionCache', () => {
50
60
  selectedOrganisationId: 'org-123',
51
61
  selectedEventId: undefined
52
62
  } as any);
53
-
63
+
54
64
  mockUseOrganisations.mockReturnValue({
55
65
  selectedOrganisation: { id: 'org-123' }
56
66
  } as any);
57
-
67
+
58
68
  mockUseEvents.mockReturnValue({
59
69
  selectedEvent: null
60
70
  } as any);
61
-
71
+
62
72
  mockIsPermittedCached.mockResolvedValue(true);
63
73
  });
64
74
 
65
- describe('Hook Initialization', () => {
75
+ describe('Hook Initialization', () => {
66
76
  it('initializes with default configuration', () => {
67
77
  const { result } = renderHook(() => usePermissionCache());
68
78
 
@@ -130,6 +140,16 @@ describe('usePermissionCache', () => {
130
140
  expect(results.every(r => r.hasPermission)).toBe(true);
131
141
  });
132
142
 
143
+ it('handles empty permission arrays gracefully', async () => {
144
+ const { result } = renderHook(() => usePermissionCache());
145
+
146
+ const results = await result.current.checkMultiplePermissions([]);
147
+
148
+ expect(results).toEqual([]);
149
+ expect(mockIsPermittedCached).not.toHaveBeenCalled();
150
+ expect(loggerSpy.warn).toHaveBeenCalled();
151
+ });
152
+
133
153
  it('handles permission check errors gracefully', async () => {
134
154
  mockIsPermittedCached.mockRejectedValueOnce(new Error('Permission check failed'));
135
155
 
@@ -137,6 +157,7 @@ describe('usePermissionCache', () => {
137
157
 
138
158
  const hasPermission = await result.current.checkPermission('read', 'users');
139
159
  expect(hasPermission).toBe(false);
160
+ expect(loggerSpy.error).toHaveBeenCalled();
140
161
  });
141
162
 
142
163
  it('handles missing user context gracefully', () => {
@@ -153,6 +174,23 @@ describe('usePermissionCache', () => {
153
174
  expect(result.current).toBeDefined();
154
175
  expect(result.current.checkPermission).toBeInstanceOf(Function);
155
176
  });
177
+
178
+ it('deduplicates concurrent permission checks', async () => {
179
+ mockIsPermittedCached.mockImplementation(
180
+ () => new Promise(resolve => setTimeout(() => resolve(true), 50))
181
+ );
182
+
183
+ const { result } = renderHook(() => usePermissionCache());
184
+
185
+ const [first, second] = await Promise.all([
186
+ result.current.checkPermission('read', 'users'),
187
+ result.current.checkPermission('read', 'users')
188
+ ]);
189
+
190
+ expect(first).toBe(true);
191
+ expect(second).toBe(true);
192
+ expect(mockIsPermittedCached).toHaveBeenCalledTimes(1);
193
+ });
156
194
  });
157
195
 
158
196
  describe('Cache Management', () => {
@@ -196,10 +234,23 @@ describe('usePermissionCache', () => {
196
234
 
197
235
  // Check again - should not use cache
198
236
  await result.current.checkPermission('read', 'users');
199
-
237
+
200
238
  expect(mockIsPermittedCached).toHaveBeenCalledTimes(2);
201
239
  });
202
240
 
241
+ it('invalidates cache entries by pattern', async () => {
242
+ const { result } = renderHook(() => usePermissionCache());
243
+
244
+ await result.current.checkPermission('read', 'dashboard');
245
+ await result.current.checkPermission('read', 'admin');
246
+
247
+ result.current.invalidateCache('dashboard');
248
+
249
+ await result.current.checkPermission('read', 'dashboard');
250
+
251
+ expect(mockIsPermittedCached).toHaveBeenCalledTimes(3);
252
+ });
253
+
203
254
  it('respects max cache size', async () => {
204
255
  const { result } = renderHook(() => usePermissionCache({
205
256
  maxCacheSize: 2
@@ -246,6 +297,32 @@ describe('usePermissionCache', () => {
246
297
  expect(debugInfo.totalChecks).toBe(2);
247
298
  });
248
299
 
300
+ it('returns cached permissions for a page', async () => {
301
+ mockIsPermittedCached
302
+ .mockResolvedValueOnce(true)
303
+ .mockResolvedValueOnce(false)
304
+ .mockResolvedValueOnce(true)
305
+ .mockResolvedValueOnce(false);
306
+
307
+ const { result } = renderHook(() => usePermissionCache());
308
+
309
+ await result.current.checkMultiplePermissions([
310
+ ['read', 'dashboard'],
311
+ ['create', 'dashboard'],
312
+ ['update', 'dashboard'],
313
+ ['delete', 'dashboard']
314
+ ]);
315
+
316
+ const cachedPermissions = result.current.getCachedPermissions('dashboard');
317
+
318
+ expect(cachedPermissions).toEqual([
319
+ { operation: 'read', hasPermission: true },
320
+ { operation: 'create', hasPermission: false },
321
+ { operation: 'update', hasPermission: true },
322
+ { operation: 'delete', hasPermission: false }
323
+ ]);
324
+ });
325
+
249
326
  it('tracks average response time', async () => {
250
327
  const { result } = renderHook(() => usePermissionCache());
251
328
 
@@ -51,6 +51,27 @@ function runCacheCleanup() {
51
51
  if (typeof window !== 'undefined' && !cleanupTimer) {
52
52
  cleanupTimer = setInterval(runCacheCleanup, CLEANUP_INTERVAL_MS);
53
53
  log.debug('Query cache cleanup initialized.');
54
+
55
+ // Cleanup on page unload to prevent memory leaks
56
+ window.addEventListener('beforeunload', () => {
57
+ if (cleanupTimer) {
58
+ clearInterval(cleanupTimer);
59
+ cleanupTimer = null;
60
+ log.debug('Query cache cleanup timer cleared on page unload.');
61
+ }
62
+ });
63
+ }
64
+
65
+ /**
66
+ * Manually cleanup the query cache cleanup timer
67
+ * Useful for testing or when the module is reloaded
68
+ */
69
+ export function cleanupQueryCache(): void {
70
+ if (cleanupTimer) {
71
+ clearInterval(cleanupTimer);
72
+ cleanupTimer = null;
73
+ log.debug('Query cache cleanup timer cleared.');
74
+ }
54
75
  }
55
76
 
56
77
  export interface UseQueryCacheOptions {
@@ -14,6 +14,8 @@ import { useUnifiedAuth } from '../providers';
14
14
  import { useOrganisations } from './useOrganisations';
15
15
  import { setOrganisationContext } from '../utils/context/organisationContext';
16
16
  import { createMockSupabaseClient, createMockQueryBuilderWithData } from '../__tests__/helpers/supabaseMock';
17
+ import { useResolvedScope } from '../rbac/hooks/useResolvedScope';
18
+ import { useSuperAdminBypass } from '../rbac/hooks/useSuperAdminBypass';
17
19
 
18
20
  // Mock the providers
19
21
  vi.mock('../providers', () => ({
@@ -29,11 +31,23 @@ vi.mock('../utils/context/organisationContext', () => ({
29
31
  setOrganisationContext: vi.fn()
30
32
  }));
31
33
 
34
+ // Mock useResolvedScope
35
+ vi.mock('../rbac/hooks/useResolvedScope', () => ({
36
+ useResolvedScope: vi.fn()
37
+ }));
38
+
39
+ // Mock useSuperAdminBypass
40
+ vi.mock('../rbac/hooks/useSuperAdminBypass', () => ({
41
+ useSuperAdminBypass: vi.fn()
42
+ }));
43
+
32
44
 
33
45
  describe('useSecureDataAccess', () => {
34
46
  const mockUseUnifiedAuth = vi.mocked(useUnifiedAuth);
35
47
  const mockUseOrganisations = vi.mocked(useOrganisations);
36
48
  const mockSetOrganisationContext = vi.mocked(setOrganisationContext);
49
+ const mockUseResolvedScope = vi.mocked(useResolvedScope);
50
+ const mockUseSuperAdminBypass = vi.mocked(useSuperAdminBypass);
37
51
 
38
52
  const mockUser = {
39
53
  id: 'user-123',
@@ -93,6 +107,22 @@ describe('useSecureDataAccess', () => {
93
107
  ensureOrganisationContext: vi.fn().mockReturnValue(mockSelectedOrganisation),
94
108
  // Add other required properties
95
109
  } as any);
110
+
111
+ // Mock useResolvedScope to return resolved scope with organisationId
112
+ mockUseResolvedScope.mockReturnValue({
113
+ resolvedScope: {
114
+ organisationId: 'org-123',
115
+ eventId: undefined,
116
+ appId: undefined,
117
+ },
118
+ isLoading: false,
119
+ error: null,
120
+ });
121
+
122
+ // Mock useSuperAdminBypass to return not super admin
123
+ mockUseSuperAdminBypass.mockReturnValue({
124
+ isSuperAdmin: false,
125
+ });
96
126
  });
97
127
 
98
128
  describe('Hook Initialization', () => {
@@ -113,9 +143,10 @@ describe('useSecureDataAccess', () => {
113
143
  expect(mockUseUnifiedAuth).toHaveBeenCalled();
114
144
  });
115
145
 
116
- it('depends on useOrganisations hook', () => {
146
+ it('depends on useResolvedScope hook', () => {
117
147
  renderHook(() => useSecureDataAccess());
118
- expect(mockUseOrganisations).toHaveBeenCalled();
148
+ // useSecureDataAccess now uses useResolvedScope instead of useOrganisations
149
+ expect(mockUseResolvedScope).toHaveBeenCalled();
119
150
  });
120
151
  });
121
152
 
@@ -328,15 +359,22 @@ describe('useSecureDataAccess', () => {
328
359
  });
329
360
 
330
361
  it('handles missing organisation context', () => {
331
- mockUseOrganisations.mockReturnValue({
362
+ mockUseUnifiedAuth.mockReturnValue({
363
+ user: mockUser,
364
+ session: mockSession,
365
+ supabase: freshMockSupabase,
366
+ isAuthenticated: true,
367
+ signOut: vi.fn(),
332
368
  selectedOrganisation: null,
333
- getUserRole: vi.fn().mockReturnValue('no_access'),
334
- validateOrganisationAccess: vi.fn().mockResolvedValue(false),
335
- ensureOrganisationContext: vi.fn().mockImplementation(() => {
336
- throw new Error('Organisation context is required but not available');
337
- }),
338
- // Add other required properties
369
+ selectedEvent: null,
339
370
  } as any);
371
+
372
+ // Mock useResolvedScope to return null scope when no context available
373
+ mockUseResolvedScope.mockReturnValue({
374
+ resolvedScope: null,
375
+ isLoading: false,
376
+ error: null,
377
+ });
340
378
 
341
379
  const { result } = renderHook(() => useSecureDataAccess());
342
380
 
@@ -344,21 +382,15 @@ describe('useSecureDataAccess', () => {
344
382
  });
345
383
 
346
384
  it('validates organisation access before operations', async () => {
347
- const mockValidateAccess = vi.fn().mockResolvedValue(true);
348
- mockUseOrganisations.mockReturnValue({
349
- selectedOrganisation: mockSelectedOrganisation,
350
- getUserRole: vi.fn().mockReturnValue('member'),
351
- validateOrganisationAccess: mockValidateAccess,
352
- ensureOrganisationContext: vi.fn().mockReturnValue(mockSelectedOrganisation),
353
- // Add other required properties
354
- } as any);
355
-
385
+ // The hook uses useResolvedScope to get organisationId, which is already mocked in beforeEach
386
+ // The validation happens in validateContext which checks resolvedScope.organisationId
356
387
  const { result } = renderHook(() => useSecureDataAccess());
357
388
 
358
- await result.current.secureQuery('users', '*');
389
+ // Use 'event' table which is in the tablesWithOrganisation list
390
+ await result.current.secureQuery('event', '*');
359
391
 
360
- // The hook doesn't call validateOrganisationAccess, it calls ensureOrganisationContext
361
- expect(mockUseOrganisations().ensureOrganisationContext).toHaveBeenCalled();
392
+ // Verify that the query was executed with organisation filter
393
+ expect(freshMockQueryBuilder.eq).toHaveBeenCalledWith('organisation_id', 'org-123');
362
394
  });
363
395
  });
364
396
 
@@ -394,16 +426,16 @@ describe('useSecureDataAccess', () => {
394
426
  });
395
427
 
396
428
  it('handles organisation access validation failures', async () => {
397
- mockUseOrganisations.mockReturnValue({
398
- selectedOrganisation: mockSelectedOrganisation,
399
- getUserRole: vi.fn().mockReturnValue('no_access'),
400
- validateOrganisationAccess: vi.fn().mockResolvedValue(false),
401
- // Add other required properties
402
- } as any);
429
+ // Mock useResolvedScope to return null scope to simulate missing context
430
+ mockUseResolvedScope.mockReturnValue({
431
+ resolvedScope: null,
432
+ isLoading: false,
433
+ error: null,
434
+ });
403
435
 
404
436
  const { result } = renderHook(() => useSecureDataAccess());
405
437
 
406
- await expect(result.current.secureQuery('users', '*')).rejects.toThrow();
438
+ await expect(result.current.secureQuery('users', '*')).rejects.toThrow('Organisation context is required for data access');
407
439
  });
408
440
  });
409
441
 
@@ -467,13 +499,25 @@ describe('useSecureDataAccess', () => {
467
499
 
468
500
  // Change organisation
469
501
  const newOrganisation = { id: 'org-456', name: 'New Organisation' };
470
- mockUseOrganisations.mockReturnValue({
502
+ mockUseUnifiedAuth.mockReturnValue({
503
+ user: mockUser,
504
+ session: mockSession,
505
+ supabase: freshMockSupabase,
506
+ isAuthenticated: true,
507
+ signOut: vi.fn(),
471
508
  selectedOrganisation: newOrganisation,
472
- getUserRole: vi.fn().mockReturnValue('member'),
473
- validateOrganisationAccess: vi.fn().mockResolvedValue(true),
474
- ensureOrganisationContext: vi.fn().mockReturnValue(newOrganisation),
475
- // Add other required properties
476
509
  } as any);
510
+
511
+ // Update useResolvedScope mock to return new organisation
512
+ mockUseResolvedScope.mockReturnValue({
513
+ resolvedScope: {
514
+ organisationId: 'org-456',
515
+ eventId: undefined,
516
+ appId: undefined,
517
+ },
518
+ isLoading: false,
519
+ error: null,
520
+ });
477
521
 
478
522
  rerender();
479
523
 
@@ -487,9 +531,10 @@ describe('useSecureDataAccess', () => {
487
531
  expect(mockUseUnifiedAuth).toHaveBeenCalled();
488
532
  });
489
533
 
490
- it('integrates with useOrganisations hook correctly', () => {
534
+ it('integrates with useResolvedScope hook correctly', () => {
491
535
  renderHook(() => useSecureDataAccess());
492
- expect(mockUseOrganisations).toHaveBeenCalled();
536
+ // useSecureDataAccess now uses useResolvedScope instead of useOrganisations
537
+ expect(mockUseResolvedScope).toHaveBeenCalled();
493
538
  });
494
539
 
495
540
  it('uses organisation context from provider', () => {
@@ -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 selectedEvent = eventServiceContext?.eventService?.getSelectedEvent() || null;
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
- try {
169
- ensureOrganisationContext();
170
- } catch (error) {
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, ensureOrganisationContext]);
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
- const currentOrg = ensureOrganisationContext();
178
- return currentOrg.id;
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: string): Promise<void> => {
183
- if (!supabase) {
184
- throw new Error('No Supabase client available') as OrganisationSecurityError;
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 organisationId = getCurrentOrganisationId();
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,6 +227,9 @@ 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
  'event', 'organisation_settings',
215
235
  'rbac_event_app_roles', 'rbac_organisation_roles',
@@ -226,11 +246,22 @@ export function useSecureDataAccess(): SecureDataAccessReturn {
226
246
  'form_contexts', 'form_field_config', 'form_fields',
227
247
  'cake_delivery', 'cake_diettype', 'cake_diner', 'cake_dish', 'cake_item',
228
248
  'cake_logistics', 'cake_mealplan', 'cake_package', 'cake_recipe', 'cake_supplier',
229
- 'cake_supply', 'cake_unit', 'event_app_access', 'base_application', 'base_questions'
249
+ 'cake_supply', 'cake_unit', 'event_app_access', 'base_application', 'base_questions',
250
+ // rbac_user_profiles has organisation_id but uses conditional filtering
251
+ 'rbac_user_profiles'
230
252
  ];
231
253
 
232
- if (tablesWithOrganisation.includes(table)) {
233
- query = query.eq('organisation_id', organisationId);
254
+ // Apply organisation filtering based on table and super admin status
255
+ if (!bypassOrganisationFilter && organisationId && tablesWithOrganisation.includes(table)) {
256
+ // For rbac_user_profiles: Super admins see all (no filter), non-super-admins get filtered (defense in depth)
257
+ // For other tables: Always apply filter
258
+ if (table === 'rbac_user_profiles' && isSuperAdmin) {
259
+ // Super admin: No org filter - RLS handles access control
260
+ // This allows super admins to see all users across all organisations
261
+ } else {
262
+ // Non-super-admin OR other tables: Apply org filter as defense in depth
263
+ query = query.eq('organisation_id', organisationId);
264
+ }
234
265
  }
235
266
 
236
267
  // Apply additional filters
@@ -272,23 +303,26 @@ export function useSecureDataAccess(): SecureDataAccessReturn {
272
303
  recordDataAccess(table, 'read', true, `SELECT ${columns} FROM ${table}`, filters);
273
304
 
274
305
  return (data as T[]) || [];
275
- }, [validateContext, getCurrentOrganisationId, setOrganisationContextInSession, supabase]);
306
+ }, [validateContext, getCurrentOrganisationId, setOrganisationContextInSession, supabase, isSuperAdmin]);
276
307
 
277
308
  const secureInsert = useCallback(async <T = any>(
278
309
  table: string,
279
310
  data: Record<string, any>
280
311
  ): Promise<T> => {
281
312
  validateContext();
282
- const organisationId = getCurrentOrganisationId();
313
+ const bypassOrganisationFilter = isSuperAdmin;
314
+ const organisationId = bypassOrganisationFilter ? undefined : getCurrentOrganisationId();
283
315
 
284
316
  // Set organisation context in database session
285
317
  await setOrganisationContextInSession(organisationId);
286
318
 
287
319
  // Ensure organisation_id is set
288
- const secureData = {
289
- ...data,
290
- organisation_id: organisationId
291
- };
320
+ const secureData = bypassOrganisationFilter
321
+ ? { ...data }
322
+ : {
323
+ ...data,
324
+ organisation_id: organisationId
325
+ };
292
326
 
293
327
  const { data: insertData, error } = await supabase!
294
328
  .from(table)
@@ -302,7 +336,7 @@ export function useSecureDataAccess(): SecureDataAccessReturn {
302
336
  }
303
337
 
304
338
  return insertData as T;
305
- }, [validateContext, getCurrentOrganisationId, setOrganisationContextInSession, supabase]);
339
+ }, [validateContext, getCurrentOrganisationId, setOrganisationContextInSession, supabase, isSuperAdmin]);
306
340
 
307
341
  const secureUpdate = useCallback(async <T = any>(
308
342
  table: string,
@@ -310,7 +344,8 @@ export function useSecureDataAccess(): SecureDataAccessReturn {
310
344
  filters: Record<string, any>
311
345
  ): Promise<T[]> => {
312
346
  validateContext();
313
- const organisationId = getCurrentOrganisationId();
347
+ const bypassOrganisationFilter = isSuperAdmin;
348
+ const organisationId = bypassOrganisationFilter ? undefined : getCurrentOrganisationId();
314
349
 
315
350
  // Set organisation context in database session
316
351
  await setOrganisationContextInSession(organisationId);
@@ -333,7 +368,7 @@ export function useSecureDataAccess(): SecureDataAccessReturn {
333
368
  'cake_meal', 'cake_mealtype', 'pace_person', 'pace_member'
334
369
  ];
335
370
 
336
- if (tablesWithOrganisation.includes(table)) {
371
+ if (!bypassOrganisationFilter && organisationId && tablesWithOrganisation.includes(table)) {
337
372
  query = query.eq('organisation_id', organisationId);
338
373
  }
339
374
 
@@ -352,14 +387,15 @@ export function useSecureDataAccess(): SecureDataAccessReturn {
352
387
  }
353
388
 
354
389
  return (updateData as T[]) || [];
355
- }, [validateContext, getCurrentOrganisationId, setOrganisationContextInSession, supabase]);
390
+ }, [validateContext, getCurrentOrganisationId, setOrganisationContextInSession, supabase, isSuperAdmin]);
356
391
 
357
392
  const secureDelete = useCallback(async (
358
393
  table: string,
359
394
  filters: Record<string, any>
360
395
  ): Promise<void> => {
361
396
  validateContext();
362
- const organisationId = getCurrentOrganisationId();
397
+ const bypassOrganisationFilter = isSuperAdmin;
398
+ const organisationId = bypassOrganisationFilter ? undefined : getCurrentOrganisationId();
363
399
 
364
400
  // Set organisation context in database session
365
401
  await setOrganisationContextInSession(organisationId);
@@ -389,7 +425,7 @@ export function useSecureDataAccess(): SecureDataAccessReturn {
389
425
  'cake_supply', 'cake_unit', 'event_app_access', 'base_application', 'base_questions'
390
426
  ];
391
427
 
392
- if (tablesWithOrganisation.includes(table)) {
428
+ if (!bypassOrganisationFilter && organisationId && tablesWithOrganisation.includes(table)) {
393
429
  query = query.eq('organisation_id', organisationId);
394
430
  }
395
431
 
@@ -406,14 +442,15 @@ export function useSecureDataAccess(): SecureDataAccessReturn {
406
442
  logger.error('useSecureDataAccess', 'Delete failed', { table, filters, error });
407
443
  throw error;
408
444
  }
409
- }, [validateContext, getCurrentOrganisationId, setOrganisationContextInSession, supabase]);
445
+ }, [validateContext, getCurrentOrganisationId, setOrganisationContextInSession, supabase, isSuperAdmin]);
410
446
 
411
447
  const secureRpc = useCallback(async <T = any>(
412
448
  functionName: string,
413
449
  params: Record<string, any> = {}
414
450
  ): Promise<T> => {
415
451
  validateContext();
416
- const organisationId = getCurrentOrganisationId();
452
+ const bypassOrganisationFilter = isSuperAdmin;
453
+ const organisationId = bypassOrganisationFilter ? undefined : getCurrentOrganisationId();
417
454
 
418
455
  // Set organisation context in database session
419
456
  await setOrganisationContextInSession(organisationId);
@@ -477,8 +514,13 @@ export function useSecureDataAccess(): SecureDataAccessReturn {
477
514
  secureParams.p_user_id = user.id;
478
515
  }
479
516
 
480
- // Add organisation_id parameter
481
- secureParams[paramName] = organisationId;
517
+ // Add organisation_id parameter when needed
518
+ if (!bypassOrganisationFilter && organisationId) {
519
+ secureParams[paramName] = organisationId;
520
+ } else if (organisationId && !(paramName in params)) {
521
+ // Default to the current organisation if caller didn't specify one
522
+ secureParams[paramName] = organisationId;
523
+ }
482
524
 
483
525
  // Add p_event_id if function needs it and event is selected
484
526
  // CRITICAL: This must be added AFTER organisation_id but BEFORE caller params
@@ -505,7 +547,7 @@ export function useSecureDataAccess(): SecureDataAccessReturn {
505
547
  }
506
548
 
507
549
  return data as T;
508
- }, [validateContext, getCurrentOrganisationId, setOrganisationContextInSession, supabase, selectedEvent?.event_id, user?.id]);
550
+ }, [validateContext, getCurrentOrganisationId, setOrganisationContextInSession, supabase, selectedEvent?.event_id, user?.id, isSuperAdmin]);
509
551
 
510
552
  // NEW: Phase 1 - Enhanced Security Features
511
553
  const [dataAccessHistory, setDataAccessHistory] = useState<DataAccessRecord[]>([]);
@@ -570,12 +612,13 @@ export function useSecureDataAccess(): SecureDataAccessReturn {
570
612
  filters?: Record<string, any>
571
613
  ) => {
572
614
  if (!isAuditLogEnabled || !user?.id) return;
573
-
615
+ const auditOrganisationId = getCurrentOrganisationId() || 'super-admin-bypass';
616
+
574
617
  const record: DataAccessRecord = {
575
618
  table,
576
619
  operation,
577
620
  userId: user.id,
578
- organisationId: getCurrentOrganisationId(),
621
+ organisationId: auditOrganisationId,
579
622
  allowed,
580
623
  timestamp: new Date().toISOString(),
581
624
  query,
@@ -592,7 +635,7 @@ export function useSecureDataAccess(): SecureDataAccessReturn {
592
635
  table,
593
636
  operation,
594
637
  userId: user.id,
595
- organisationId: getCurrentOrganisationId(),
638
+ organisationId: auditOrganisationId,
596
639
  timestamp: new Date().toISOString()
597
640
  });
598
641
  }