@jmruthers/pace-core 0.6.1 → 0.6.2

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 (502) hide show
  1. package/CHANGELOG.md +43 -10
  2. package/cursor-rules/00-pace-core-compliance.mdc +18 -91
  3. package/cursor-rules/01-standards-compliance.mdc +16 -47
  4. package/cursor-rules/02-project-structure.mdc +4 -4
  5. package/cursor-rules/03-solid-principles.mdc +45 -164
  6. package/cursor-rules/04-testing-standards.mdc +22 -69
  7. package/cursor-rules/05-bug-reports-and-features.mdc +2 -2
  8. package/cursor-rules/06-code-quality.mdc +42 -125
  9. package/cursor-rules/07-tech-stack-compliance.mdc +33 -128
  10. package/cursor-rules/08-markup-quality.mdc +452 -0
  11. package/cursor-rules/CHANGELOG.md +18 -0
  12. package/cursor-rules/README.md +2 -1
  13. package/dist/{AuthService-DjnJHDtC.d.ts → AuthService-BPvc3Ka0.d.ts} +54 -0
  14. package/dist/{DataTable-CH1U5Tpy.d.ts → DataTable-BMRU8a1j.d.ts} +33 -1
  15. package/dist/{DataTable-DQ7RSOHE.js → DataTable-TPTKCX4D.js} +10 -9
  16. package/dist/{PublicPageProvider-ce4xlHYA.d.ts → PublicPageProvider-DC6kCaqf.d.ts} +356 -111
  17. package/dist/{UnifiedAuthProvider-ATAP5UTR.js → UnifiedAuthProvider-CH6Z342H.js} +3 -3
  18. package/dist/{UnifiedAuthProvider-185Ih4dj.d.ts → UnifiedAuthProvider-CVcTjx-d.d.ts} +29 -0
  19. package/dist/{api-N774RPUA.js → api-MVVQZLJI.js} +2 -2
  20. package/dist/{chunk-KNC55RTG.js → chunk-24UVZUZG.js} +90 -54
  21. package/dist/chunk-24UVZUZG.js.map +1 -0
  22. package/dist/{chunk-4N5C5XZU.js → chunk-2UOI2FG5.js} +4 -4
  23. package/dist/chunk-2UOI2FG5.js.map +1 -0
  24. package/dist/{chunk-T33XF5ZC.js → chunk-3XC4CPTD.js} +4317 -3963
  25. package/dist/chunk-3XC4CPTD.js.map +1 -0
  26. package/dist/{chunk-4ZC4GX36.js → chunk-6J4GEEJR.js} +172 -45
  27. package/dist/chunk-6J4GEEJR.js.map +1 -0
  28. package/dist/{chunk-3QRJFVBR.js → chunk-6SOIHG6Z.js} +1 -1
  29. package/dist/chunk-6SOIHG6Z.js.map +1 -0
  30. package/dist/{chunk-BYFSK72L.js → chunk-EHMR7VYL.js} +4 -4
  31. package/dist/chunk-EHMR7VYL.js.map +1 -0
  32. package/dist/{chunk-I7PSE6JW.js → chunk-F2IMUDXZ.js} +2 -75
  33. package/dist/chunk-F2IMUDXZ.js.map +1 -0
  34. package/dist/{chunk-LXQLPRQ2.js → chunk-FFQEQTNW.js} +6 -8
  35. package/dist/chunk-FFQEQTNW.js.map +1 -0
  36. package/dist/chunk-FMUCXFII.js +76 -0
  37. package/dist/chunk-FMUCXFII.js.map +1 -0
  38. package/dist/{chunk-SQGMNID3.js → chunk-L4OXEN46.js} +4 -5
  39. package/dist/chunk-L4OXEN46.js.map +1 -0
  40. package/dist/{chunk-R77UEZ4E.js → chunk-M43Y4SSO.js} +1 -1
  41. package/dist/chunk-M43Y4SSO.js.map +1 -0
  42. package/dist/{chunk-3XTALGJF.js → chunk-MMZ7JXPU.js} +60 -223
  43. package/dist/chunk-MMZ7JXPU.js.map +1 -0
  44. package/dist/{chunk-GLK6VM3F.js → chunk-NECFR5MM.js} +254 -170
  45. package/dist/chunk-NECFR5MM.js.map +1 -0
  46. package/dist/{chunk-JBKQ3SAO.js → chunk-SFZUDBL5.js} +40 -4
  47. package/dist/chunk-SFZUDBL5.js.map +1 -0
  48. package/dist/{chunk-XM25TVIE.js → chunk-XWQCNGTQ.js} +724 -363
  49. package/dist/chunk-XWQCNGTQ.js.map +1 -0
  50. package/dist/components.d.ts +5 -5
  51. package/dist/components.js +14 -11
  52. package/dist/components.js.map +1 -1
  53. package/dist/{functions-D_kgHktt.d.ts → functions-DHebl8-F.d.ts} +1 -1
  54. package/dist/hooks.d.ts +55 -122
  55. package/dist/hooks.js +8 -12
  56. package/dist/hooks.js.map +1 -1
  57. package/dist/index.d.ts +60 -13
  58. package/dist/index.js +19 -19
  59. package/dist/index.js.map +1 -1
  60. package/dist/providers.d.ts +21 -3
  61. package/dist/providers.js +2 -2
  62. package/dist/rbac/index.d.ts +145 -114
  63. package/dist/rbac/index.js +8 -11
  64. package/dist/theming/runtime.d.ts +1 -13
  65. package/dist/theming/runtime.js +1 -1
  66. package/dist/{timezone-_pgH8qrY.d.ts → timezone-CHhWg6b4.d.ts} +3 -10
  67. package/dist/{types-UU913iLA.d.ts → types-BeoeWV5I.d.ts} +8 -0
  68. package/dist/{types-CEpcvwwF.d.ts → types-CkbwOr4Y.d.ts} +6 -0
  69. package/dist/types.d.ts +2 -2
  70. package/dist/{usePublicRouteParams-BJAlWfuJ.d.ts → usePublicRouteParams-1oMokgLF.d.ts} +31 -1
  71. package/dist/utils.d.ts +4 -5
  72. package/dist/utils.js +14 -14
  73. package/dist/utils.js.map +1 -1
  74. package/docs/api/README.md +7 -1
  75. package/docs/api/classes/ColumnFactory.md +8 -8
  76. package/docs/api/classes/InvalidScopeError.md +4 -4
  77. package/docs/api/classes/Logger.md +1 -1
  78. package/docs/api/classes/MissingUserContextError.md +4 -4
  79. package/docs/api/classes/OrganisationContextRequiredError.md +4 -4
  80. package/docs/api/classes/PermissionDeniedError.md +4 -4
  81. package/docs/api/classes/RBACAuditManager.md +1 -1
  82. package/docs/api/classes/RBACCache.md +1 -1
  83. package/docs/api/classes/RBACEngine.md +1 -1
  84. package/docs/api/classes/RBACError.md +4 -4
  85. package/docs/api/classes/RBACNotInitializedError.md +4 -4
  86. package/docs/api/classes/SecureSupabaseClient.md +18 -15
  87. package/docs/api/classes/StorageUtils.md +1 -1
  88. package/docs/api/enums/FileCategory.md +1 -1
  89. package/docs/api/enums/LogLevel.md +1 -1
  90. package/docs/api/enums/RBACErrorCode.md +1 -1
  91. package/docs/api/enums/RPCFunction.md +1 -1
  92. package/docs/api/interfaces/AddressFieldProps.md +1 -1
  93. package/docs/api/interfaces/AddressFieldRef.md +1 -1
  94. package/docs/api/interfaces/AggregateConfig.md +4 -4
  95. package/docs/api/interfaces/AutocompleteOptions.md +1 -1
  96. package/docs/api/interfaces/AvatarProps.md +1 -1
  97. package/docs/api/interfaces/BadgeProps.md +9 -2
  98. package/docs/api/interfaces/ButtonProps.md +7 -4
  99. package/docs/api/interfaces/CalendarProps.md +8 -5
  100. package/docs/api/interfaces/CardProps.md +8 -5
  101. package/docs/api/interfaces/ColorPalette.md +1 -1
  102. package/docs/api/interfaces/ColorShade.md +1 -1
  103. package/docs/api/interfaces/ComplianceResult.md +1 -1
  104. package/docs/api/interfaces/DataAccessRecord.md +9 -9
  105. package/docs/api/interfaces/DataRecord.md +1 -1
  106. package/docs/api/interfaces/DataTableAction.md +24 -21
  107. package/docs/api/interfaces/DataTableColumn.md +31 -31
  108. package/docs/api/interfaces/DataTableProps.md +1 -1
  109. package/docs/api/interfaces/DataTableToolbarButton.md +7 -7
  110. package/docs/api/interfaces/DatabaseComplianceResult.md +1 -1
  111. package/docs/api/interfaces/DatabaseIssue.md +1 -1
  112. package/docs/api/interfaces/EmptyStateConfig.md +5 -5
  113. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  114. package/docs/api/interfaces/ErrorBoundaryProps.md +147 -0
  115. package/docs/api/interfaces/ErrorBoundaryProviderProps.md +36 -0
  116. package/docs/api/interfaces/ErrorBoundaryState.md +75 -0
  117. package/docs/api/interfaces/EventAppRoleData.md +1 -1
  118. package/docs/api/interfaces/ExportColumn.md +1 -1
  119. package/docs/api/interfaces/ExportOptions.md +8 -8
  120. package/docs/api/interfaces/FileDisplayProps.md +1 -1
  121. package/docs/api/interfaces/FileMetadata.md +1 -1
  122. package/docs/api/interfaces/FileReference.md +1 -1
  123. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  124. package/docs/api/interfaces/FileUploadOptions.md +1 -1
  125. package/docs/api/interfaces/FileUploadProps.md +26 -23
  126. package/docs/api/interfaces/FooterProps.md +10 -8
  127. package/docs/api/interfaces/FormFieldProps.md +10 -10
  128. package/docs/api/interfaces/FormProps.md +1 -1
  129. package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
  130. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  131. package/docs/api/interfaces/InputProps.md +7 -4
  132. package/docs/api/interfaces/LabelProps.md +1 -1
  133. package/docs/api/interfaces/LoggerConfig.md +1 -1
  134. package/docs/api/interfaces/LoginFormProps.md +14 -11
  135. package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
  136. package/docs/api/interfaces/NavigationContextType.md +1 -1
  137. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  138. package/docs/api/interfaces/NavigationItem.md +11 -11
  139. package/docs/api/interfaces/NavigationMenuProps.md +15 -15
  140. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  141. package/docs/api/interfaces/Organisation.md +1 -1
  142. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  143. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  144. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  145. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  146. package/docs/api/interfaces/PaceAppLayoutProps.md +30 -27
  147. package/docs/api/interfaces/PaceLoginPageProps.md +6 -4
  148. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  149. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  150. package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
  151. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  152. package/docs/api/interfaces/PaletteData.md +1 -1
  153. package/docs/api/interfaces/ParsedAddress.md +1 -1
  154. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  155. package/docs/api/interfaces/ProgressProps.md +1 -1
  156. package/docs/api/interfaces/ProtectedRouteProps.md +7 -26
  157. package/docs/api/interfaces/PublicPageFooterProps.md +9 -9
  158. package/docs/api/interfaces/PublicPageHeaderProps.md +10 -10
  159. package/docs/api/interfaces/PublicPageLayoutProps.md +7 -20
  160. package/docs/api/interfaces/QuickFix.md +1 -1
  161. package/docs/api/interfaces/RBACAccessValidateParams.md +1 -1
  162. package/docs/api/interfaces/RBACAccessValidateResult.md +1 -1
  163. package/docs/api/interfaces/RBACAuditLogParams.md +1 -1
  164. package/docs/api/interfaces/RBACAuditLogResult.md +1 -1
  165. package/docs/api/interfaces/RBACConfig.md +1 -1
  166. package/docs/api/interfaces/RBACContext.md +1 -1
  167. package/docs/api/interfaces/RBACLogger.md +1 -1
  168. package/docs/api/interfaces/RBACPageAccessCheckParams.md +1 -1
  169. package/docs/api/interfaces/RBACPerformanceMetrics.md +1 -1
  170. package/docs/api/interfaces/RBACPermissionCheckParams.md +1 -1
  171. package/docs/api/interfaces/RBACPermissionCheckResult.md +1 -1
  172. package/docs/api/interfaces/RBACPermissionsGetParams.md +1 -1
  173. package/docs/api/interfaces/RBACPermissionsGetResult.md +1 -1
  174. package/docs/api/interfaces/RBACResult.md +1 -1
  175. package/docs/api/interfaces/RBACRoleGrantParams.md +1 -1
  176. package/docs/api/interfaces/RBACRoleGrantResult.md +1 -1
  177. package/docs/api/interfaces/RBACRoleRevokeParams.md +1 -1
  178. package/docs/api/interfaces/RBACRoleRevokeResult.md +1 -1
  179. package/docs/api/interfaces/RBACRoleValidateParams.md +1 -1
  180. package/docs/api/interfaces/RBACRoleValidateResult.md +1 -1
  181. package/docs/api/interfaces/RBACRolesListParams.md +1 -1
  182. package/docs/api/interfaces/RBACRolesListResult.md +1 -1
  183. package/docs/api/interfaces/RBACSessionTrackParams.md +1 -1
  184. package/docs/api/interfaces/RBACSessionTrackResult.md +1 -1
  185. package/docs/api/interfaces/ResourcePermissions.md +1 -1
  186. package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
  187. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  188. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  189. package/docs/api/interfaces/RoleManagementResult.md +1 -1
  190. package/docs/api/interfaces/RouteAccessRecord.md +1 -1
  191. package/docs/api/interfaces/RouteConfig.md +1 -1
  192. package/docs/api/interfaces/RuntimeComplianceResult.md +1 -1
  193. package/docs/api/interfaces/SecureDataContextType.md +9 -9
  194. package/docs/api/interfaces/SecureDataProviderProps.md +8 -8
  195. package/docs/api/interfaces/SessionRestorationLoaderProps.md +3 -3
  196. package/docs/api/interfaces/SetupIssue.md +1 -1
  197. package/docs/api/interfaces/StorageConfig.md +1 -1
  198. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  199. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  200. package/docs/api/interfaces/StorageListOptions.md +1 -1
  201. package/docs/api/interfaces/StorageListResult.md +1 -1
  202. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  203. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  204. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  205. package/docs/api/interfaces/StyleImport.md +1 -1
  206. package/docs/api/interfaces/SwitchProps.md +1 -1
  207. package/docs/api/interfaces/TabsContentProps.md +1 -1
  208. package/docs/api/interfaces/TabsListProps.md +1 -1
  209. package/docs/api/interfaces/TabsProps.md +1 -1
  210. package/docs/api/interfaces/TabsTriggerProps.md +3 -3
  211. package/docs/api/interfaces/TextareaProps.md +1 -1
  212. package/docs/api/interfaces/ToastActionElement.md +4 -1
  213. package/docs/api/interfaces/ToastProps.md +1 -1
  214. package/docs/api/interfaces/UnifiedAuthContextType.md +58 -55
  215. package/docs/api/interfaces/UnifiedAuthProviderProps.md +15 -13
  216. package/docs/api/interfaces/UseFormDialogOptions.md +1 -1
  217. package/docs/api/interfaces/UseFormDialogReturn.md +1 -1
  218. package/docs/api/interfaces/UseInactivityTrackerOptions.md +11 -9
  219. package/docs/api/interfaces/UseInactivityTrackerReturn.md +8 -8
  220. package/docs/api/interfaces/UsePublicEventLogoOptions.md +6 -6
  221. package/docs/api/interfaces/UsePublicEventLogoReturn.md +9 -6
  222. package/docs/api/interfaces/UsePublicEventOptions.md +3 -3
  223. package/docs/api/interfaces/UsePublicEventReturn.md +8 -5
  224. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +4 -4
  225. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +12 -9
  226. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +10 -7
  227. package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
  228. package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
  229. package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
  230. package/docs/api/interfaces/UserEventAccess.md +14 -11
  231. package/docs/api/interfaces/UserMenuProps.md +8 -6
  232. package/docs/api/interfaces/UserProfile.md +1 -1
  233. package/docs/api/modules.md +575 -634
  234. package/docs/architecture/database-schema-requirements.md +161 -0
  235. package/docs/core-concepts/rbac-system.md +3 -3
  236. package/docs/documentation-index.md +2 -4
  237. package/docs/getting-started/cursor-rules.md +2 -1
  238. package/docs/migration/DOCUMENTATION_STRUCTURE.md +441 -0
  239. package/docs/migration/MIGRATION_GUIDE.md +2 -24
  240. package/docs/migration/README.md +52 -6
  241. package/docs/migration/V0.5.190_TO_V0.6.1_MIGRATION.md +1153 -0
  242. package/docs/migration/database-changes-december-2025.md +3 -3
  243. package/docs/rbac/event-based-apps.md +1 -1
  244. package/docs/rbac/getting-started.md +1 -1
  245. package/docs/rbac/quick-start.md +1 -1
  246. package/docs/standards/README.md +1 -0
  247. package/package.json +2 -1
  248. package/scripts/audit/core/checks/accessibility.cjs +197 -0
  249. package/scripts/audit/core/checks/api-usage.cjs +191 -0
  250. package/scripts/audit/core/checks/bundle.cjs +142 -0
  251. package/scripts/{check-pace-core-compliance.cjs → audit/core/checks/compliance.cjs} +714 -687
  252. package/scripts/audit/core/checks/config.cjs +54 -0
  253. package/scripts/audit/core/checks/coverage.cjs +84 -0
  254. package/scripts/audit/core/checks/dependencies.cjs +454 -0
  255. package/scripts/audit/core/checks/documentation.cjs +203 -0
  256. package/scripts/audit/core/checks/environment.cjs +128 -0
  257. package/scripts/audit/core/checks/error-handling.cjs +299 -0
  258. package/scripts/audit/core/checks/forms.cjs +172 -0
  259. package/scripts/audit/core/checks/heuristics.cjs +68 -0
  260. package/scripts/audit/core/checks/hooks.cjs +334 -0
  261. package/scripts/audit/core/checks/imports.cjs +244 -0
  262. package/scripts/audit/core/checks/performance.cjs +325 -0
  263. package/scripts/audit/core/checks/routes.cjs +117 -0
  264. package/scripts/audit/core/checks/state.cjs +130 -0
  265. package/scripts/audit/core/checks/structure.cjs +65 -0
  266. package/scripts/audit/core/checks/style.cjs +584 -0
  267. package/scripts/audit/core/checks/testing.cjs +122 -0
  268. package/scripts/audit/core/checks/typescript.cjs +61 -0
  269. package/scripts/audit/core/scanner.cjs +199 -0
  270. package/scripts/audit/core/utils.cjs +137 -0
  271. package/scripts/audit/index.cjs +223 -0
  272. package/scripts/audit/reporters/console.cjs +151 -0
  273. package/scripts/audit/reporters/json.cjs +54 -0
  274. package/scripts/audit/reporters/markdown.cjs +124 -0
  275. package/scripts/audit-consuming-app.cjs +61 -936
  276. package/scripts/build-docs/build-decision.js +240 -0
  277. package/scripts/build-docs/cache-utils.js +105 -0
  278. package/scripts/build-docs/content-normalization.js +150 -0
  279. package/scripts/build-docs/file-utils.js +105 -0
  280. package/scripts/build-docs/git-utils.js +86 -0
  281. package/scripts/build-docs/hash-utils.js +116 -0
  282. package/scripts/build-docs/typedoc-runner.js +220 -0
  283. package/scripts/build-docs-incremental.js +77 -913
  284. package/scripts/utils/command-runner.js +16 -11
  285. package/scripts/validate-formats.js +61 -56
  286. package/scripts/validate-master.js +74 -69
  287. package/scripts/validate-pre-publish.js +70 -65
  288. package/src/__tests__/hooks/usePermissions.test.ts +2 -2
  289. package/src/components/Alert/Alert.test.tsx +12 -18
  290. package/src/components/Alert/Alert.tsx +5 -7
  291. package/src/components/Avatar/Avatar.test.tsx +4 -4
  292. package/src/components/Badge/Badge.tsx +14 -0
  293. package/src/components/Button/Button.tsx +22 -0
  294. package/src/components/Calendar/Calendar.tsx +8 -2
  295. package/src/components/Card/Card.tsx +4 -0
  296. package/src/components/Checkbox/Checkbox.test.tsx +12 -12
  297. package/src/components/Checkbox/Checkbox.tsx +2 -2
  298. package/src/components/DataTable/DataTable.tsx +38 -4
  299. package/src/components/DataTable/__tests__/DataTableCore.test-setup.ts +5 -6
  300. package/src/components/DataTable/__tests__/pagination.modes.test.tsx +18 -4
  301. package/src/components/DataTable/__tests__/test-utils/sharedTestUtils.tsx +2 -3
  302. package/src/components/DataTable/components/AccessDeniedPage.tsx +16 -25
  303. package/src/components/DataTable/components/ActionButtons.tsx +10 -7
  304. package/src/components/DataTable/components/BulkOperationsDropdown.tsx +1 -1
  305. package/src/components/DataTable/components/ColumnFilter.tsx +10 -0
  306. package/src/components/DataTable/components/ColumnVisibilityDropdown.tsx +12 -0
  307. package/src/components/DataTable/components/DataTableBody.tsx +8 -0
  308. package/src/components/DataTable/components/DataTableCore.tsx +196 -554
  309. package/src/components/DataTable/components/DataTableErrorBoundary.tsx +11 -0
  310. package/src/components/DataTable/components/DataTableLayout.tsx +559 -0
  311. package/src/components/DataTable/components/DataTableModals.tsx +8 -0
  312. package/src/components/DataTable/components/DataTableToolbar.tsx +8 -0
  313. package/src/components/DataTable/components/DraggableColumnHeader.tsx +12 -0
  314. package/src/components/DataTable/components/EditFields.tsx +307 -0
  315. package/src/components/DataTable/components/EditableRow.tsx +8 -0
  316. package/src/components/DataTable/components/EmptyState.tsx +10 -0
  317. package/src/components/DataTable/components/FilterRow.tsx +12 -0
  318. package/src/components/DataTable/components/GroupHeader.tsx +12 -0
  319. package/src/components/DataTable/components/GroupingDropdown.tsx +12 -0
  320. package/src/components/DataTable/components/ImportModal.tsx +7 -0
  321. package/src/components/DataTable/components/LoadingState.tsx +6 -0
  322. package/src/components/DataTable/components/PaginationControls.tsx +16 -1
  323. package/src/components/DataTable/components/RowComponent.tsx +391 -0
  324. package/src/components/DataTable/components/UnifiedTableBody.tsx +61 -849
  325. package/src/components/DataTable/components/VirtualizedDataTable.tsx +16 -4
  326. package/src/components/DataTable/components/__tests__/AccessDeniedPage.test.tsx +4 -2
  327. package/src/components/DataTable/components/cellValueUtils.ts +40 -0
  328. package/src/components/DataTable/components/hooks/useImportModalFocus.ts +53 -0
  329. package/src/components/DataTable/components/hooks/usePermissionTracking.ts +126 -0
  330. package/src/components/DataTable/context/DataTableContext.tsx +50 -0
  331. package/src/components/DataTable/core/ColumnFactory.ts +31 -0
  332. package/src/components/DataTable/core/DataTableContext.tsx +32 -1
  333. package/src/components/DataTable/hooks/useColumnOrderPersistence.ts +10 -0
  334. package/src/components/DataTable/hooks/useColumnReordering.ts +12 -0
  335. package/src/components/DataTable/hooks/useColumnVisibilityPersistence.ts +10 -0
  336. package/src/components/DataTable/hooks/useDataTableDataPipeline.ts +16 -0
  337. package/src/components/DataTable/hooks/useDataTablePermissions.ts +124 -32
  338. package/src/components/DataTable/hooks/useDataTableState.ts +35 -1
  339. package/src/components/DataTable/hooks/useEffectiveColumnOrder.ts +12 -0
  340. package/src/components/DataTable/hooks/useServerSideDataEffect.ts +11 -0
  341. package/src/components/DataTable/hooks/useTableColumns.ts +8 -0
  342. package/src/components/DataTable/hooks/useTableHandlers.ts +14 -0
  343. package/src/components/DataTable/styles.ts +6 -6
  344. package/src/components/DataTable/types.ts +6 -10
  345. package/src/components/DataTable/utils/a11yUtils.ts +7 -0
  346. package/src/components/DataTable/utils/debugTools.ts +18 -113
  347. package/src/components/DataTable/utils/errorHandling.ts +12 -0
  348. package/src/components/DataTable/utils/exportUtils.ts +9 -0
  349. package/src/components/DataTable/utils/flexibleImport.ts +12 -48
  350. package/src/components/DataTable/utils/paginationUtils.ts +8 -0
  351. package/src/components/DataTable/utils/performanceUtils.ts +5 -1
  352. package/src/components/Dialog/Dialog.tsx +2 -2
  353. package/src/components/ErrorBoundary/ErrorBoundary.test.tsx +180 -1
  354. package/src/components/ErrorBoundary/ErrorBoundary.tsx +45 -5
  355. package/src/components/ErrorBoundary/ErrorBoundaryContext.tsx +129 -0
  356. package/src/components/ErrorBoundary/index.ts +27 -2
  357. package/src/components/EventSelector/EventSelector.tsx +3 -0
  358. package/src/components/FileDisplay/FileDisplay.tsx +32 -18
  359. package/src/components/FileUpload/FileUpload.tsx +22 -2
  360. package/src/components/Footer/Footer.test.tsx +16 -16
  361. package/src/components/Footer/Footer.tsx +14 -11
  362. package/src/components/Form/Form.tsx +1 -0
  363. package/src/components/Header/Header.tsx +21 -10
  364. package/src/components/Input/Input.test.tsx +2 -2
  365. package/src/components/Input/Input.tsx +8 -4
  366. package/src/components/LoadingSpinner/LoadingSpinner.test.tsx +4 -4
  367. package/src/components/LoginForm/LoginForm.tsx +4 -0
  368. package/src/components/NavigationMenu/NavigationMenu.tsx +14 -513
  369. package/src/components/NavigationMenu/types.ts +56 -0
  370. package/src/components/NavigationMenu/useNavigationFiltering.ts +390 -0
  371. package/src/components/OrganisationSelector/OrganisationSelector.tsx +3 -0
  372. package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +4 -2
  373. package/src/components/PaceAppLayout/PaceAppLayout.tsx +32 -11
  374. package/src/components/PaceAppLayout/test-setup.tsx +1 -2
  375. package/src/components/PaceLoginPage/PaceLoginPage.tsx +3 -0
  376. package/src/components/PasswordChange/PasswordChangeForm.tsx +9 -0
  377. package/src/components/ProtectedRoute/ProtectedRoute.tsx +3 -9
  378. package/src/components/PublicLayout/PublicPageLayout.tsx +2 -5
  379. package/src/components/PublicLayout/PublicPageProvider.tsx +4 -0
  380. package/src/components/Select/Select.tsx +80 -434
  381. package/src/components/Select/context.ts +23 -0
  382. package/src/components/Select/hooks/useSelectEvents.ts +87 -0
  383. package/src/components/Select/hooks/useSelectSearch.ts +91 -0
  384. package/src/components/Select/hooks/useSelectState.ts +104 -0
  385. package/src/components/Select/index.ts +9 -1
  386. package/src/components/Select/types.ts +123 -0
  387. package/src/components/Select/utils/text.ts +26 -0
  388. package/src/components/SessionRestorationLoader/SessionRestorationLoader.tsx +4 -5
  389. package/src/components/Switch/Switch.tsx +4 -4
  390. package/src/components/Tabs/Tabs.tsx +1 -1
  391. package/src/components/Toast/Toast.tsx +4 -0
  392. package/src/components/Tooltip/Tooltip.tsx +2 -2
  393. package/src/components/UserMenu/UserMenu.test.tsx +24 -11
  394. package/src/components/UserMenu/UserMenu.tsx +21 -18
  395. package/src/components/index.ts +2 -2
  396. package/src/hooks/__tests__/index.unit.test.ts +2 -5
  397. package/src/hooks/index.ts +1 -2
  398. package/src/hooks/public/usePublicEvent.ts +4 -0
  399. package/src/hooks/public/usePublicEventLogo.ts +4 -0
  400. package/src/hooks/public/usePublicFileDisplay.ts +4 -0
  401. package/src/hooks/public/usePublicRouteParams.ts +4 -0
  402. package/src/hooks/services/useAuth.ts +32 -0
  403. package/src/hooks/services/useCurrentEvent.ts +6 -0
  404. package/src/hooks/services/useCurrentOrganisation.ts +6 -0
  405. package/src/hooks/useDebounce.ts +9 -0
  406. package/src/hooks/useEventTheme.ts +6 -0
  407. package/src/hooks/useFileDisplay.ts +4 -0
  408. package/src/hooks/useFileReference.ts +25 -7
  409. package/src/hooks/useFileUrl.ts +11 -1
  410. package/src/hooks/useFocusManagement.ts +14 -0
  411. package/src/hooks/useFocusTrap.ts +3 -0
  412. package/src/hooks/useInactivityTracker.ts +3 -0
  413. package/src/hooks/useKeyboardShortcuts.ts +4 -0
  414. package/src/hooks/useOrganisationPermissions.ts +4 -0
  415. package/src/hooks/useOrganisationSecurity.ts +4 -0
  416. package/src/hooks/usePerformanceMonitor.ts +4 -0
  417. package/src/hooks/usePermissionCache.ts +7 -0
  418. package/src/hooks/useQueryCache.ts +12 -1
  419. package/src/hooks/useSessionRestoration.ts +4 -0
  420. package/src/hooks/useStorage.ts +4 -0
  421. package/src/hooks/useToast.ts +1 -1
  422. package/src/index.ts +2 -1
  423. package/src/providers/__tests__/OrganisationProvider.test.tsx +92 -70
  424. package/src/providers/services/AuthServiceProvider.tsx +18 -0
  425. package/src/providers/services/EventServiceProvider.tsx +18 -0
  426. package/src/providers/services/InactivityServiceProvider.tsx +18 -0
  427. package/src/providers/services/OrganisationServiceProvider.tsx +18 -0
  428. package/src/providers/services/UnifiedAuthProvider.tsx +36 -0
  429. package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +29 -13
  430. package/src/rbac/README.md +1 -1
  431. package/src/rbac/__tests__/adapters.comprehensive.test.tsx +2 -2
  432. package/src/rbac/__tests__/scenarios.user-role.test.tsx +4 -5
  433. package/src/rbac/adapters.tsx +14 -5
  434. package/src/rbac/api.ts +100 -67
  435. package/src/rbac/components/NavigationProvider.tsx +4 -1
  436. package/src/rbac/components/PagePermissionGuard.tsx +157 -17
  437. package/src/rbac/components/RoleBasedRouter.tsx +5 -1
  438. package/src/rbac/components/SecureDataProvider.test.tsx +84 -49
  439. package/src/rbac/components/SecureDataProvider.tsx +20 -5
  440. package/src/rbac/components/__tests__/PagePermissionGuard.race-condition.test.tsx +24 -14
  441. package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +7 -0
  442. package/src/rbac/components/__tests__/PagePermissionGuard.verification.test.tsx +14 -6
  443. package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +15 -4
  444. package/src/rbac/components/__tests__/SecureDataProvider.fixed.test.tsx +148 -24
  445. package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +81 -15
  446. package/src/rbac/engine.ts +38 -14
  447. package/src/rbac/hooks/permissions/index.ts +7 -0
  448. package/src/rbac/hooks/permissions/useAccessLevel.ts +105 -0
  449. package/src/rbac/hooks/permissions/useCachedPermissions.ts +79 -0
  450. package/src/rbac/hooks/permissions/useCan.ts +347 -0
  451. package/src/rbac/hooks/permissions/useHasAllPermissions.ts +90 -0
  452. package/src/rbac/hooks/permissions/useHasAnyPermission.ts +90 -0
  453. package/src/rbac/hooks/permissions/useMultiplePermissions.ts +93 -0
  454. package/src/rbac/hooks/permissions/usePermissions.ts +253 -0
  455. package/src/rbac/hooks/useCan.test.ts +71 -64
  456. package/src/rbac/hooks/usePermissions.ts +14 -995
  457. package/src/rbac/hooks/useResourcePermissions.test.ts +54 -18
  458. package/src/rbac/hooks/useResourcePermissions.ts +14 -4
  459. package/src/rbac/hooks/useSecureSupabase.ts +33 -13
  460. package/src/rbac/permissions.ts +0 -30
  461. package/src/rbac/secureClient.ts +200 -61
  462. package/src/rbac/types.ts +8 -0
  463. package/src/theming/__tests__/parseEventColours.test.ts +6 -9
  464. package/src/theming/parseEventColours.ts +5 -19
  465. package/src/types/vitest-globals.d.ts +51 -26
  466. package/src/utils/__mocks__/supabaseMock.ts +1 -3
  467. package/src/utils/__tests__/formatting.unit.test.ts +4 -4
  468. package/src/utils/__tests__/index.unit.test.ts +2 -2
  469. package/src/utils/audit/audit.ts +0 -3
  470. package/src/utils/core/cn.ts +1 -1
  471. package/src/utils/file-reference/index.ts +53 -1
  472. package/src/utils/formatting/formatting.ts +8 -18
  473. package/src/utils/index.ts +0 -1
  474. package/dist/chunk-3QRJFVBR.js.map +0 -1
  475. package/dist/chunk-3XTALGJF.js.map +0 -1
  476. package/dist/chunk-4N5C5XZU.js.map +0 -1
  477. package/dist/chunk-4ZC4GX36.js.map +0 -1
  478. package/dist/chunk-BYFSK72L.js.map +0 -1
  479. package/dist/chunk-EXUD6RNJ.js +0 -451
  480. package/dist/chunk-EXUD6RNJ.js.map +0 -1
  481. package/dist/chunk-GLK6VM3F.js.map +0 -1
  482. package/dist/chunk-I7PSE6JW.js.map +0 -1
  483. package/dist/chunk-JBKQ3SAO.js.map +0 -1
  484. package/dist/chunk-KNC55RTG.js.map +0 -1
  485. package/dist/chunk-LXQLPRQ2.js.map +0 -1
  486. package/dist/chunk-R77UEZ4E.js.map +0 -1
  487. package/dist/chunk-SQGMNID3.js.map +0 -1
  488. package/dist/chunk-T33XF5ZC.js.map +0 -1
  489. package/dist/chunk-XM25TVIE.js.map +0 -1
  490. package/docs/api/classes/ErrorBoundary.md +0 -144
  491. package/docs/migration/quick-migration-guide.md +0 -356
  492. package/docs/migration/service-architecture.md +0 -281
  493. package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +0 -680
  494. package/src/hooks/useSecureDataAccess.test.ts +0 -559
  495. package/src/hooks/useSecureDataAccess.ts +0 -681
  496. /package/dist/{DataTable-DQ7RSOHE.js.map → DataTable-TPTKCX4D.js.map} +0 -0
  497. /package/dist/{UnifiedAuthProvider-ATAP5UTR.js.map → UnifiedAuthProvider-CH6Z342H.js.map} +0 -0
  498. /package/dist/{api-N774RPUA.js.map → api-MVVQZLJI.js.map} +0 -0
  499. /package/docs/migration/{organisation-context-timing-fix.md → V0.3.44_organisation-context-timing-fix.md} +0 -0
  500. /package/docs/migration/{rbac-migration.md → V0.4.0_rbac-migration.md} +0 -0
  501. /package/docs/migration/{person-scoped-profiles-migration-guide.md → V0.5.190_person-scoped-profiles-migration-guide.md} +0 -0
  502. /package/docs/migration/{REACT_19_MIGRATION.md → V0.6.0_REACT_19_MIGRATION.md} +0 -0
@@ -1,681 +0,0 @@
1
- /**
2
- * @file useSecureDataAccess Hook
3
- * @package @jmruthers/pace-core
4
- * @module Hooks/useSecureDataAccess
5
- * @since 0.4.0
6
- *
7
- * Hook for secure database operations with mandatory organisation context.
8
- * Ensures all data access is properly scoped to the user's current organisation.
9
- *
10
- * @example
11
- * ```tsx
12
- * function DataComponent() {
13
- * const { secureQuery, secureInsert, secureUpdate, secureDelete } = useSecureDataAccess();
14
- *
15
- * const loadData = async () => {
16
- * try {
17
- * // Automatically includes organisation_id filter
18
- * const events = await secureQuery('core_events', '*', { is_visible: true });
19
- * console.log('Organisation events:', events);
20
- * } catch (error) {
21
- * console.error('Failed to load data:', error);
22
- * }
23
- * };
24
- *
25
- * const createEvent = async (eventData) => {
26
- * try {
27
- * // Automatically sets organisation_id
28
- * const newEvent = await secureInsert('core_events', eventData);
29
- * console.log('Created event:', newEvent);
30
- * } catch (error) {
31
- * console.error('Failed to create event:', error);
32
- * }
33
- * };
34
- *
35
- * return (
36
- * <div>
37
- * <button onClick={loadData}>Load Data</button>
38
- * <button onClick={() => createEvent({ event_name: 'New Event' })}>
39
- * Create Event
40
- * </button>
41
- * </div>
42
- * );
43
- * }
44
- * ```
45
- *
46
- * @security
47
- * - All queries automatically include organisation_id filter
48
- * - Validates organisation context before any operation
49
- * - Prevents data leaks between organisations
50
- * - Error handling for security violations
51
- * - Type-safe database operations
52
- */
53
-
54
- import { useCallback, useState, useContext } from 'react';
55
- import { useUnifiedAuth } from '../providers';
56
- import { useOrganisations } from './useOrganisations';
57
- import { EventServiceContext } from '../providers/services/EventServiceProvider';
58
- import { setOrganisationContext } from '../utils/context/organisationContext';
59
- import { logger } from '../utils/core/logger';
60
- import type { Permission } from '../rbac/types';
61
- import type { OrganisationSecurityError } from '../types/organisation';
62
- import { useOrganisationSecurity } from './useOrganisationSecurity';
63
- import { useResolvedScope } from '../rbac/hooks/useResolvedScope';
64
-
65
- export interface SecureDataAccessReturn {
66
- /** Execute a secure query with organisation filtering */
67
- secureQuery: <T = any>(
68
- table: string,
69
- columns: string,
70
- filters?: Record<string, any>,
71
- options?: {
72
- orderBy?: string;
73
- ascending?: boolean;
74
- limit?: number;
75
- offset?: number;
76
- }
77
- ) => Promise<T[]>;
78
-
79
- /** Execute a secure insert with organisation context */
80
- secureInsert: <T = any>(
81
- table: string,
82
- data: Record<string, any>
83
- ) => Promise<T>;
84
-
85
- /** Execute a secure update with organisation filtering */
86
- secureUpdate: <T = any>(
87
- table: string,
88
- data: Record<string, any>,
89
- filters: Record<string, any>
90
- ) => Promise<T[]>;
91
-
92
- /** Execute a secure delete with organisation filtering */
93
- secureDelete: (
94
- table: string,
95
- filters: Record<string, any>
96
- ) => Promise<void>;
97
-
98
- /** Execute a secure RPC call with organisation context */
99
- secureRpc: <T = any>(
100
- functionName: string,
101
- params?: Record<string, any>
102
- ) => Promise<T>;
103
-
104
- /** Get current organisation ID */
105
- getCurrentOrganisationId: () => string;
106
-
107
- /** Validate organisation context */
108
- validateContext: () => void;
109
-
110
- // NEW: Phase 1 - Enhanced Security Features
111
- /** Check if data access is allowed for a table and operation */
112
- isDataAccessAllowed: (table: string, operation: string) => boolean;
113
-
114
- /** Get all data access permissions for current user */
115
- getDataAccessPermissions: () => Record<string, string[]>;
116
-
117
- /** Check if strict mode is enabled */
118
- isStrictMode: boolean;
119
-
120
- /** Check if audit logging is enabled */
121
- isAuditLogEnabled: boolean;
122
-
123
- /** Get data access history */
124
- getDataAccessHistory: () => DataAccessRecord[];
125
-
126
- /** Clear data access history */
127
- clearDataAccessHistory: () => void;
128
-
129
- /** Validate data access attempt */
130
- validateDataAccess: (table: string, operation: string) => boolean;
131
- }
132
-
133
- export interface DataAccessRecord {
134
- table: string;
135
- operation: string;
136
- userId: string;
137
- organisationId: string;
138
- allowed: boolean;
139
- timestamp: string;
140
- query?: string;
141
- filters?: Record<string, any>;
142
- }
143
-
144
- /**
145
- * Hook for secure data access with automatic organisation filtering
146
- *
147
- * All database operations automatically include organisation context:
148
- * - Queries filter by organisation_id
149
- * - Inserts include organisation_id
150
- * - Updates/deletes are scoped to organisation
151
- * - RPC calls include organisation_id parameter
152
- */
153
- export function useSecureDataAccess(): SecureDataAccessReturn {
154
- const { supabase, user, session, selectedOrganisation, selectedEvent } = useUnifiedAuth();
155
-
156
- // Get selected event for event-scoped RPC calls
157
- // Use useContext directly to safely check if EventServiceProvider is available
158
- const eventServiceContext = useContext(EventServiceContext);
159
- const eventFromContext = eventServiceContext?.eventService?.getSelectedEvent() || null;
160
- const effectiveSelectedEvent = selectedEvent || eventFromContext;
161
- const { superAdminContext } = useOrganisationSecurity();
162
- const isSuperAdmin = superAdminContext.isSuperAdmin;
163
-
164
- // Use resolved scope to get organisationId (derived from event if needed)
165
- const { resolvedScope } = useResolvedScope({
166
- supabase,
167
- selectedOrganisationId: selectedOrganisation?.id || null,
168
- selectedEventId: effectiveSelectedEvent?.event_id || null
169
- });
170
-
171
- const validateContext = useCallback((): void => {
172
- if (!supabase) {
173
- throw new Error('No Supabase client available') as OrganisationSecurityError;
174
- }
175
- if (!user || !session) {
176
- throw new Error('User must be authenticated with valid session') as OrganisationSecurityError;
177
- }
178
-
179
- if (isSuperAdmin) {
180
- return;
181
- }
182
-
183
- if (!resolvedScope?.organisationId) {
184
- throw new Error('Organisation context is required for data access') as OrganisationSecurityError;
185
- }
186
- }, [supabase, user, session, resolvedScope, isSuperAdmin]);
187
-
188
- const getCurrentOrganisationId = useCallback((): string => {
189
- if (isSuperAdmin) {
190
- // For super admins, try to get org from resolved scope or selectedOrganisation
191
- return resolvedScope?.organisationId || selectedOrganisation?.id || '';
192
- }
193
-
194
- validateContext();
195
- return resolvedScope?.organisationId || '';
196
- }, [validateContext, resolvedScope, selectedOrganisation, isSuperAdmin]);
197
-
198
- // Set organisation context in database session
199
- const setOrganisationContextInSession = useCallback(async (organisationId?: string): Promise<void> => {
200
- if (!supabase || !organisationId) {
201
- return;
202
- }
203
-
204
- await setOrganisationContext(supabase, organisationId);
205
- }, [supabase]);
206
-
207
- const secureQuery = useCallback(async <T = any>(
208
- table: string,
209
- columns: string,
210
- filters: Record<string, any> = {},
211
- options: {
212
- orderBy?: string;
213
- ascending?: boolean;
214
- limit?: number;
215
- offset?: number;
216
- } = {}
217
- ): Promise<T[]> => {
218
- validateContext();
219
- const bypassOrganisationFilter = isSuperAdmin;
220
- const organisationId = bypassOrganisationFilter ? undefined : getCurrentOrganisationId();
221
-
222
- // Set organisation context in database session
223
- await setOrganisationContextInSession(organisationId);
224
-
225
- // Build query with organisation filter
226
- let query = supabase!
227
- .from(table)
228
- .select(columns);
229
-
230
- // Add organisation filter only if table has organisation_id column
231
- // Defense in depth strategy:
232
- // - RLS policies are the primary security layer (cannot be bypassed)
233
- // - Application-level filtering adds an additional layer of protection
234
- const tablesWithOrganisation = [
235
- 'core_events', 'organisation_settings',
236
- 'rbac_event_app_roles', 'rbac_organisation_roles',
237
- // SECURITY: Phase 2 additions - complete organisation table mapping
238
- 'organisation_audit_log', 'organisation_invitations', 'organisation_app_access',
239
- // SECURITY: Emergency additions for Phase 1 fixes
240
- 'cake_meal', 'cake_mealtype',
241
- // NOTE: core_person, core_member, core_contact, core_consent, core_identification,
242
- // core_qualification, and medi_profile are person-scoped tables that do NOT have
243
- // organisation_id columns. They were removed as part of the person-scoped profiles migration.
244
- // Do NOT add organisation_id filters to these tables - it will cause 400 errors.
245
- // SECURITY: Phase 3A additions - medical and personal data
246
- // NOTE: medi_condition, medi_diet, medi_action_plan, medi_profile_versions are now person-scoped
247
- // (via medi_profile) - removed from this list
248
- // core_identification_type remains organisation-scoped (lookup table)
249
- 'core_identification_type',
250
- 'form_responses', 'form_response_values', 'forms',
251
- // SECURITY: Phase 3B additions - remaining critical tables
252
- 'invoice', 'line_item', 'credit_balance', 'payment_method',
253
- 'form_contexts', 'form_field_config', 'form_fields',
254
- 'cake_delivery', 'cake_diettype', 'cake_diner', 'cake_dish', 'cake_item',
255
- 'cake_logistics', 'cake_mealplan', 'cake_package', 'cake_recipe', 'cake_supplier',
256
- 'cake_supply', 'cake_unit', 'event_app_access', 'base_application', 'base_questions',
257
- // rbac_user_profiles has organisation_id but uses conditional filtering
258
- 'rbac_user_profiles'
259
- ];
260
-
261
- // Apply organisation filtering based on table and super admin status
262
- if (!bypassOrganisationFilter && organisationId && tablesWithOrganisation.includes(table)) {
263
- // For rbac_user_profiles: Super admins see all (no filter), non-super-admins get filtered (defense in depth)
264
- // For other tables: Always apply filter
265
- if (table === 'rbac_user_profiles' && isSuperAdmin) {
266
- // Super admin: No org filter - RLS handles access control
267
- // This allows super admins to see all users across all organisations
268
- } else {
269
- // Non-super-admin OR other tables: Apply org filter as defense in depth
270
- query = query.eq('organisation_id', organisationId);
271
- }
272
- }
273
-
274
- // Apply additional filters
275
- Object.entries(filters).forEach(([key, value]) => {
276
- if (value !== undefined && value !== null) {
277
- // Handle qualified column names (e.g., 'users.role')
278
- const columnName = key.includes('.') ? key.split('.').pop()! : key;
279
- query = query.eq(columnName, value);
280
- }
281
- });
282
-
283
- // Apply options
284
- if (options.orderBy) {
285
- // Only use the column name, not a qualified name
286
- const orderByColumn = options.orderBy.split('.').pop();
287
- if (orderByColumn) {
288
- query = query.order(orderByColumn, { ascending: options.ascending ?? true });
289
- }
290
- }
291
-
292
- if (options.limit) {
293
- query = query.limit(options.limit);
294
- }
295
-
296
- if (options.offset) {
297
- query = query.range(options.offset, options.offset + (options.limit || 100) - 1);
298
- }
299
-
300
- const { data, error } = await query;
301
-
302
- if (error) {
303
- logger.error('useSecureDataAccess', 'Query failed', { table, columns, filters, error });
304
- // NEW: Phase 1 - Record failed data access attempt
305
- recordDataAccess(table, 'read', false, `SELECT ${columns} FROM ${table}`, filters);
306
- throw error;
307
- }
308
-
309
- // NEW: Phase 1 - Record successful data access attempt
310
- recordDataAccess(table, 'read', true, `SELECT ${columns} FROM ${table}`, filters);
311
-
312
- return (data as T[]) || [];
313
- }, [validateContext, getCurrentOrganisationId, setOrganisationContextInSession, supabase, isSuperAdmin]);
314
-
315
- const secureInsert = useCallback(async <T = any>(
316
- table: string,
317
- data: Record<string, any>
318
- ): Promise<T> => {
319
- validateContext();
320
- const bypassOrganisationFilter = isSuperAdmin;
321
- const organisationId = bypassOrganisationFilter ? undefined : getCurrentOrganisationId();
322
-
323
- // Set organisation context in database session
324
- await setOrganisationContextInSession(organisationId);
325
-
326
- // Ensure organisation_id is set
327
- const secureData = bypassOrganisationFilter
328
- ? { ...data }
329
- : {
330
- ...data,
331
- organisation_id: organisationId
332
- };
333
-
334
- const { data: insertData, error } = await supabase!
335
- .from(table)
336
- .insert(secureData)
337
- .select()
338
- .single();
339
-
340
- if (error) {
341
- logger.error('useSecureDataAccess', 'Insert failed', { table, data: secureData, error });
342
- throw error;
343
- }
344
-
345
- return insertData as T;
346
- }, [validateContext, getCurrentOrganisationId, setOrganisationContextInSession, supabase, isSuperAdmin]);
347
-
348
- const secureUpdate = useCallback(async <T = any>(
349
- table: string,
350
- data: Record<string, any>,
351
- filters: Record<string, any>
352
- ): Promise<T[]> => {
353
- validateContext();
354
- const bypassOrganisationFilter = isSuperAdmin;
355
- const organisationId = bypassOrganisationFilter ? undefined : getCurrentOrganisationId();
356
-
357
- // Set organisation context in database session
358
- await setOrganisationContextInSession(organisationId);
359
-
360
- // Filter out organisation_id from data to prevent manipulation
361
- const { organisation_id, ...secureData } = data;
362
-
363
- // Build update query with organisation filter
364
- let query = supabase!
365
- .from(table)
366
- .update(secureData);
367
-
368
- // Add organisation filter only if table has organisation_id column
369
- const tablesWithOrganisation = [
370
- 'core_events', 'organisation_settings',
371
- 'rbac_event_app_roles', 'rbac_organisation_roles',
372
- // SECURITY: Phase 2 additions - complete organisation table mapping
373
- 'organisation_audit_log', 'organisation_invitations', 'organisation_app_access',
374
- // SECURITY: Emergency additions for Phase 1 fixes
375
- 'cake_meal', 'cake_mealtype', 'core_person',
376
- // SECURITY: These tables still have organisation_id columns and must be filtered
377
- 'core_member', 'core_contact', 'core_consent', 'core_identification', 'core_qualification',
378
- 'medi_profile',
379
- // SECURITY: Phase 3A additions - medical and personal data
380
- 'medi_condition', 'medi_diet', 'medi_action_plan', 'medi_profile_versions',
381
- 'core_identification_type',
382
- 'form_responses', 'form_response_values', 'forms',
383
- // SECURITY: Phase 3B additions - remaining critical tables
384
- 'invoice', 'line_item', 'credit_balance', 'payment_method',
385
- 'form_contexts', 'form_field_config', 'form_fields',
386
- 'cake_delivery', 'cake_diettype', 'cake_diner', 'cake_dish', 'cake_item',
387
- 'cake_logistics', 'cake_mealplan', 'cake_package', 'cake_recipe', 'cake_supplier',
388
- 'cake_supply', 'cake_unit', 'event_app_access', 'base_application', 'base_questions'
389
- ];
390
-
391
- if (!bypassOrganisationFilter && organisationId && tablesWithOrganisation.includes(table)) {
392
- query = query.eq('organisation_id', organisationId);
393
- }
394
-
395
- // Apply filters
396
- Object.entries(filters).forEach(([key, value]) => {
397
- if (value !== undefined && value !== null) {
398
- query = query.eq(key, value);
399
- }
400
- });
401
-
402
- const { data: updateData, error } = await query.select();
403
-
404
- if (error) {
405
- logger.error('useSecureDataAccess', 'Update failed', { table, data: secureData, filters, error });
406
- throw error;
407
- }
408
-
409
- return (updateData as T[]) || [];
410
- }, [validateContext, getCurrentOrganisationId, setOrganisationContextInSession, supabase, isSuperAdmin]);
411
-
412
- const secureDelete = useCallback(async (
413
- table: string,
414
- filters: Record<string, any>
415
- ): Promise<void> => {
416
- validateContext();
417
- const bypassOrganisationFilter = isSuperAdmin;
418
- const organisationId = bypassOrganisationFilter ? undefined : getCurrentOrganisationId();
419
-
420
- // Set organisation context in database session
421
- await setOrganisationContextInSession(organisationId);
422
-
423
- // Build delete query with organisation filter
424
- let query = supabase!
425
- .from(table)
426
- .delete();
427
-
428
- // Add organisation filter only if table has organisation_id column
429
- const tablesWithOrganisation = [
430
- 'core_events', 'organisation_settings',
431
- 'rbac_event_app_roles', 'rbac_organisation_roles',
432
- // SECURITY: Phase 2 additions - complete organisation table mapping
433
- 'organisation_audit_log', 'organisation_invitations', 'organisation_app_access',
434
- // SECURITY: Emergency additions for Phase 1 fixes
435
- 'cake_meal', 'cake_mealtype', 'core_person', 'core_member',
436
- // SECURITY: Phase 3A additions - medical and personal data
437
- 'medi_profile', 'medi_condition', 'medi_diet', 'medi_action_plan', 'medi_profile_versions',
438
- 'core_consent', 'core_contact', 'core_identification', 'core_identification_type', 'core_qualification',
439
- 'form_responses', 'form_response_values', 'forms',
440
- // SECURITY: Phase 3B additions - remaining critical tables
441
- 'invoice', 'line_item', 'credit_balance', 'payment_method',
442
- 'form_contexts', 'form_field_config', 'form_fields',
443
- 'cake_delivery', 'cake_diettype', 'cake_diner', 'cake_dish', 'cake_item',
444
- 'cake_logistics', 'cake_mealplan', 'cake_package', 'cake_recipe', 'cake_supplier',
445
- 'cake_supply', 'cake_unit', 'event_app_access', 'base_application', 'base_questions'
446
- ];
447
-
448
- if (!bypassOrganisationFilter && organisationId && tablesWithOrganisation.includes(table)) {
449
- query = query.eq('organisation_id', organisationId);
450
- }
451
-
452
- // Apply filters
453
- Object.entries(filters).forEach(([key, value]) => {
454
- if (value !== undefined && value !== null) {
455
- query = query.eq(key, value);
456
- }
457
- });
458
-
459
- const { error } = await query;
460
-
461
- if (error) {
462
- logger.error('useSecureDataAccess', 'Delete failed', { table, filters, error });
463
- throw error;
464
- }
465
- }, [validateContext, getCurrentOrganisationId, setOrganisationContextInSession, supabase, isSuperAdmin]);
466
-
467
- const secureRpc = useCallback(async <T = any>(
468
- functionName: string,
469
- params: Record<string, any> = {}
470
- ): Promise<T> => {
471
- validateContext();
472
- const bypassOrganisationFilter = isSuperAdmin;
473
- const organisationId = bypassOrganisationFilter ? undefined : getCurrentOrganisationId();
474
-
475
- // Set organisation context in database session
476
- await setOrganisationContextInSession(organisationId);
477
-
478
- // Include organisation_id in RPC parameters
479
- // Some functions use p_organisation_id instead of organisation_id (to avoid conflicts with RETURNS TABLE columns)
480
- const functionsWithPOrganisationId = [
481
- 'data_cake_diners_list',
482
- 'data_cake_mealplans_list'
483
- ];
484
-
485
- const paramName = functionsWithPOrganisationId.includes(functionName)
486
- ? 'p_organisation_id'
487
- : 'organisation_id';
488
-
489
- // Functions that need p_event_id for event-app role permission checks
490
- // Note: Even org-scoped functions (like items, packages, suppliers) need event_id
491
- // for permission checks when users have event-app roles
492
- const functionsNeedingEventId = [
493
- 'data_cake_items_list',
494
- 'data_cake_packages_list',
495
- 'data_cake_suppliers_list',
496
- 'data_cake_diettypes_list',
497
- 'data_cake_mealtypes_list',
498
- 'data_cake_diners_list',
499
- 'data_cake_mealplans_list',
500
- 'data_cake_dishes_list',
501
- 'data_cake_recipes_list',
502
- 'data_cake_meals_list',
503
- 'data_cake_units_list',
504
- 'data_cake_orders_list',
505
- 'app_cake_item_create',
506
- 'app_cake_item_update',
507
- 'app_cake_package_create',
508
- 'app_cake_package_update',
509
- 'app_cake_supplier_create',
510
- 'app_cake_supplier_update',
511
- 'app_cake_supplier_delete',
512
- 'app_cake_meal_create',
513
- 'app_cake_meal_update',
514
- 'app_cake_meal_delete',
515
- 'app_cake_unit_create',
516
- 'app_cake_unit_update',
517
- 'app_cake_unit_delete',
518
- 'app_cake_delivery_upsert'
519
- ];
520
-
521
- // Build secureParams with correct parameter order
522
- // For functions that require p_event_id as first parameter, ensure it's first
523
- const secureParams: Record<string, any> = {};
524
-
525
- // Functions where p_event_id is the FIRST required parameter (no default)
526
- const functionsWithEventIdFirst = [
527
- 'data_cake_meals_list',
528
- 'data_cake_units_list'
529
- ];
530
-
531
- // Add p_user_id explicitly for functions that need it (even though it has a default)
532
- // This ensures parameter matching works correctly
533
- if (user?.id) {
534
- secureParams.p_user_id = user.id;
535
- }
536
-
537
- // Add organisation_id parameter when needed
538
- if (!bypassOrganisationFilter && organisationId) {
539
- secureParams[paramName] = organisationId;
540
- } else if (organisationId && !(paramName in params)) {
541
- // Default to the current organisation if caller didn't specify one
542
- secureParams[paramName] = organisationId;
543
- }
544
-
545
- // Add p_event_id if function needs it and event is selected
546
- // CRITICAL: This must be added AFTER organisation_id but BEFORE caller params
547
- // to ensure it's not overwritten. For data_cake_items_list, p_event_id is the 3rd param.
548
- if (functionsNeedingEventId.includes(functionName) && selectedEvent?.event_id) {
549
- secureParams.p_event_id = selectedEvent.event_id;
550
- }
551
-
552
- // Add any other params passed by caller (limit, offset, etc.)
553
- // NOTE: This will NOT overwrite p_event_id if caller passes it, but we want to ensure
554
- // our value takes precedence if event is selected
555
- Object.assign(secureParams, params);
556
-
557
- // Ensure p_event_id is set if needed (after Object.assign, so it overrides caller params)
558
- if (functionsNeedingEventId.includes(functionName) && selectedEvent?.event_id) {
559
- secureParams.p_event_id = selectedEvent.event_id;
560
- }
561
-
562
- const { data, error } = await supabase!.rpc(functionName, secureParams);
563
-
564
- if (error) {
565
- logger.error('useSecureDataAccess', 'RPC failed', { functionName, params: secureParams, error });
566
- throw error;
567
- }
568
-
569
- return data as T;
570
- }, [validateContext, getCurrentOrganisationId, setOrganisationContextInSession, supabase, selectedEvent?.event_id, user?.id, isSuperAdmin]);
571
-
572
- // NEW: Phase 1 - Enhanced Security Features
573
- const [dataAccessHistory, setDataAccessHistory] = useState<DataAccessRecord[]>([]);
574
- const [isStrictMode] = useState(true); // Always enabled in Phase 1
575
- const [isAuditLogEnabled] = useState(true); // Always enabled in Phase 1
576
-
577
- // Check if data access is allowed for a table and operation
578
- const isDataAccessAllowed = useCallback((table: string, operation: string): boolean => {
579
- if (!user?.id) return false;
580
-
581
- // Use the existing RBAC system to check data access permissions
582
- // This is a synchronous check for the context - actual permission checking
583
- // happens in the secure data operations using the RBAC engine
584
- const permission = `${operation}:data.${table}` as Permission;
585
-
586
- // For now, we'll return true and let the secure data operations
587
- // handle the actual permission checking asynchronously
588
- // This context is mainly for tracking and audit purposes
589
- return true;
590
- }, [user?.id]);
591
-
592
- // Get all data access permissions for current user
593
- const getDataAccessPermissions = useCallback((): Record<string, string[]> => {
594
- if (!user?.id) return {};
595
-
596
- // For now, return empty object - this will be enhanced with actual permission checking
597
- // when we integrate with the existing RBAC system
598
- return {};
599
- }, [user?.id]);
600
-
601
- // Get data access history
602
- const getDataAccessHistory = useCallback((): DataAccessRecord[] => {
603
- return [...dataAccessHistory];
604
- }, [dataAccessHistory]);
605
-
606
- // Clear data access history
607
- const clearDataAccessHistory = useCallback(() => {
608
- setDataAccessHistory([]);
609
- }, []);
610
-
611
- // Validate data access attempt
612
- const validateDataAccess = useCallback((table: string, operation: string): boolean => {
613
- if (!user?.id) return false;
614
-
615
- // Validate organisation context
616
- try {
617
- validateContext();
618
- } catch (error) {
619
- logger.error('useSecureDataAccess', 'Organisation context validation failed', { table, operation, error });
620
- return false;
621
- }
622
-
623
- return isDataAccessAllowed(table, operation);
624
- }, [user?.id, validateContext, isDataAccessAllowed]);
625
-
626
- // Record data access attempt
627
- const recordDataAccess = useCallback((
628
- table: string,
629
- operation: string,
630
- allowed: boolean,
631
- query?: string,
632
- filters?: Record<string, any>
633
- ) => {
634
- if (!isAuditLogEnabled || !user?.id) return;
635
- const auditOrganisationId = getCurrentOrganisationId() || 'super-admin-bypass';
636
-
637
- const record: DataAccessRecord = {
638
- table,
639
- operation,
640
- userId: user.id,
641
- organisationId: auditOrganisationId,
642
- allowed,
643
- timestamp: new Date().toISOString(),
644
- query,
645
- filters
646
- };
647
-
648
- setDataAccessHistory(prev => {
649
- const newHistory = [record, ...prev];
650
- return newHistory.slice(0, 1000); // Keep last 1000 records
651
- });
652
-
653
- if (isStrictMode && !allowed) {
654
- logger.error('useSecureDataAccess', 'STRICT MODE VIOLATION: User attempted data access without permission', {
655
- table,
656
- operation,
657
- userId: user.id,
658
- organisationId: auditOrganisationId,
659
- timestamp: new Date().toISOString()
660
- });
661
- }
662
- }, [isAuditLogEnabled, isStrictMode, user?.id, getCurrentOrganisationId]);
663
-
664
- return {
665
- secureQuery,
666
- secureInsert,
667
- secureUpdate,
668
- secureDelete,
669
- secureRpc,
670
- getCurrentOrganisationId,
671
- validateContext,
672
- // NEW: Phase 1 - Enhanced Security Features
673
- isDataAccessAllowed,
674
- getDataAccessPermissions,
675
- isStrictMode,
676
- isAuditLogEnabled,
677
- getDataAccessHistory,
678
- clearDataAccessHistory,
679
- validateDataAccess
680
- };
681
- }