@jmruthers/pace-core 0.6.1 → 0.6.3

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 (549) hide show
  1. package/CHANGELOG.md +88 -10
  2. package/cursor-rules/00-pace-core-compliance.mdc +46 -87
  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-Cb34EQs3.d.ts} +63 -1
  14. package/dist/{DataTable-CH1U5Tpy.d.ts → DataTable-BMRU8a1j.d.ts} +33 -1
  15. package/dist/{DataTable-DQ7RSOHE.js → DataTable-THFPBKTP.js} +12 -10
  16. package/dist/{PublicPageProvider-ce4xlHYA.d.ts → PublicPageProvider-DEMpysFR.d.ts} +394 -171
  17. package/dist/{UnifiedAuthProvider-185Ih4dj.d.ts → UnifiedAuthProvider-CKvHP1MK.d.ts} +30 -8
  18. package/dist/{UnifiedAuthProvider-ATAP5UTR.js → UnifiedAuthProvider-KAGUYQ4J.js} +5 -4
  19. package/dist/{api-N774RPUA.js → api-IAGWF3ZG.js} +10 -10
  20. package/dist/{audit-B5P6FFIR.js → audit-V53FV5AG.js} +2 -2
  21. package/dist/{chunk-JBKQ3SAO.js → chunk-2T2IG7T7.js} +107 -57
  22. package/dist/chunk-2T2IG7T7.js.map +1 -0
  23. package/dist/{chunk-3QRJFVBR.js → chunk-6SOIHG6Z.js} +1 -1
  24. package/dist/chunk-6SOIHG6Z.js.map +1 -0
  25. package/dist/{chunk-3XTALGJF.js → chunk-6Z7LTB3D.js} +69 -240
  26. package/dist/chunk-6Z7LTB3D.js.map +1 -0
  27. package/dist/{chunk-4ZC4GX36.js → chunk-CNCQDFLN.js} +199 -46
  28. package/dist/chunk-CNCQDFLN.js.map +1 -0
  29. package/dist/chunk-DGUM43GV.js +11 -0
  30. package/dist/{chunk-BYFSK72L.js → chunk-DWUBLJJM.js} +361 -187
  31. package/dist/chunk-DWUBLJJM.js.map +1 -0
  32. package/dist/{chunk-LXQLPRQ2.js → chunk-FFQEQTNW.js} +6 -8
  33. package/dist/chunk-FFQEQTNW.js.map +1 -0
  34. package/dist/chunk-FMUCXFII.js +76 -0
  35. package/dist/chunk-FMUCXFII.js.map +1 -0
  36. package/dist/{chunk-4N5C5XZU.js → chunk-HFZBI76P.js} +4 -4
  37. package/dist/chunk-HFZBI76P.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-I7PSE6JW.js → chunk-M7MPQISP.js} +3 -76
  43. package/dist/chunk-M7MPQISP.js.map +1 -0
  44. package/dist/chunk-PQBSKX33.js +7793 -0
  45. package/dist/chunk-PQBSKX33.js.map +1 -0
  46. package/dist/chunk-QRPVRXYT.js +226 -0
  47. package/dist/chunk-QRPVRXYT.js.map +1 -0
  48. package/dist/{chunk-KNC55RTG.js → chunk-RWEBCB47.js} +194 -416
  49. package/dist/chunk-RWEBCB47.js.map +1 -0
  50. package/dist/{chunk-XM25TVIE.js → chunk-YDQHOZNA.js} +843 -388
  51. package/dist/chunk-YDQHOZNA.js.map +1 -0
  52. package/dist/{chunk-GLK6VM3F.js → chunk-ZNIWI3UC.js} +739 -737
  53. package/dist/chunk-ZNIWI3UC.js.map +1 -0
  54. package/dist/components.d.ts +5 -5
  55. package/dist/components.js +18 -16
  56. package/dist/components.js.map +1 -1
  57. package/dist/contextValidator-3JNZKUTX.js +9 -0
  58. package/dist/contextValidator-3JNZKUTX.js.map +1 -0
  59. package/dist/eslint-rules/pace-core-compliance.cjs +106 -0
  60. package/dist/{functions-D_kgHktt.d.ts → functions-DHebl8-F.d.ts} +1 -1
  61. package/dist/hooks.d.ts +55 -122
  62. package/dist/hooks.js +10 -13
  63. package/dist/hooks.js.map +1 -1
  64. package/dist/index.d.ts +60 -13
  65. package/dist/index.js +30 -25
  66. package/dist/index.js.map +1 -1
  67. package/dist/providers.d.ts +21 -3
  68. package/dist/providers.js +4 -3
  69. package/dist/rbac/index.d.ts +210 -139
  70. package/dist/rbac/index.js +17 -13
  71. package/dist/styles/index.js +1 -1
  72. package/dist/theming/runtime.d.ts +1 -13
  73. package/dist/theming/runtime.js +2 -2
  74. package/dist/{timezone-_pgH8qrY.d.ts → timezone-CHhWg6b4.d.ts} +3 -10
  75. package/dist/{types-UU913iLA.d.ts → types-BeoeWV5I.d.ts} +8 -0
  76. package/dist/{types-CEpcvwwF.d.ts → types-CkbwOr4Y.d.ts} +6 -0
  77. package/dist/types.d.ts +2 -2
  78. package/dist/types.js +1 -1
  79. package/dist/{usePublicRouteParams-BJAlWfuJ.d.ts → usePublicRouteParams-i3qtoBgg.d.ts} +38 -17
  80. package/dist/utils.d.ts +4 -5
  81. package/dist/utils.js +17 -19
  82. package/dist/utils.js.map +1 -1
  83. package/docs/api/README.md +21 -17
  84. package/docs/api/modules.md +4191 -2967
  85. package/docs/architecture/database-schema-requirements.md +161 -0
  86. package/docs/components/context-selector.md +126 -0
  87. package/docs/core-concepts/rbac-system.md +3 -3
  88. package/docs/documentation-index.md +2 -4
  89. package/docs/getting-started/cursor-rules.md +2 -1
  90. package/docs/migration/DOCUMENTATION_STRUCTURE.md +441 -0
  91. package/docs/migration/MIGRATION_GUIDE.md +2 -24
  92. package/docs/migration/RBAC_SCOPE_MIGRATION.md +385 -0
  93. package/docs/migration/README.md +52 -6
  94. package/docs/migration/V0.5.190_TO_V0.6.1_MIGRATION.md +1153 -0
  95. package/docs/migration/database-changes-december-2025.md +3 -3
  96. package/docs/pace-mint-fix-auto-selection.md +218 -0
  97. package/docs/pace-mint-rbac-setup.md +391 -0
  98. package/docs/rbac/event-based-apps.md +1 -1
  99. package/docs/rbac/getting-started.md +1 -1
  100. package/docs/rbac/quick-start.md +1 -1
  101. package/docs/rbac/secure-client-protection.md +330 -0
  102. package/docs/standards/README.md +1 -0
  103. package/package.json +4 -3
  104. package/scripts/audit/core/checks/accessibility.cjs +197 -0
  105. package/scripts/audit/core/checks/api-usage.cjs +191 -0
  106. package/scripts/audit/core/checks/bundle.cjs +142 -0
  107. package/scripts/{check-pace-core-compliance.cjs → audit/core/checks/compliance.cjs} +784 -685
  108. package/scripts/audit/core/checks/config.cjs +54 -0
  109. package/scripts/audit/core/checks/coverage.cjs +84 -0
  110. package/scripts/audit/core/checks/dependencies.cjs +985 -0
  111. package/scripts/audit/core/checks/documentation.cjs +268 -0
  112. package/scripts/audit/core/checks/environment.cjs +116 -0
  113. package/scripts/audit/core/checks/error-handling.cjs +340 -0
  114. package/scripts/audit/core/checks/forms.cjs +172 -0
  115. package/scripts/audit/core/checks/heuristics.cjs +68 -0
  116. package/scripts/audit/core/checks/hooks.cjs +334 -0
  117. package/scripts/audit/core/checks/imports.cjs +244 -0
  118. package/scripts/audit/core/checks/performance.cjs +325 -0
  119. package/scripts/audit/core/checks/routes.cjs +117 -0
  120. package/scripts/audit/core/checks/state.cjs +130 -0
  121. package/scripts/audit/core/checks/structure.cjs +65 -0
  122. package/scripts/audit/core/checks/style.cjs +584 -0
  123. package/scripts/audit/core/checks/testing.cjs +122 -0
  124. package/scripts/audit/core/checks/typescript.cjs +61 -0
  125. package/scripts/audit/core/scanner.cjs +199 -0
  126. package/scripts/audit/core/utils.cjs +137 -0
  127. package/scripts/audit/index.cjs +223 -0
  128. package/scripts/audit/reporters/console.cjs +151 -0
  129. package/scripts/audit/reporters/json.cjs +54 -0
  130. package/scripts/audit/reporters/markdown.cjs +124 -0
  131. package/scripts/audit-consuming-app.cjs +61 -936
  132. package/scripts/build-docs/build-decision.js +240 -0
  133. package/scripts/build-docs/cache-utils.js +105 -0
  134. package/scripts/build-docs/content-normalization.js +150 -0
  135. package/scripts/build-docs/file-utils.js +105 -0
  136. package/scripts/build-docs/git-utils.js +86 -0
  137. package/scripts/build-docs/hash-utils.js +116 -0
  138. package/scripts/build-docs/typedoc-runner.js +220 -0
  139. package/scripts/build-docs-incremental.js +77 -913
  140. package/scripts/utils/command-runner.js +16 -11
  141. package/scripts/validate-formats.js +61 -56
  142. package/scripts/validate-master.js +74 -69
  143. package/scripts/validate-pre-publish.js +70 -65
  144. package/src/__tests__/hooks/usePermissions.test.ts +2 -2
  145. package/src/components/Alert/Alert.test.tsx +12 -18
  146. package/src/components/Alert/Alert.tsx +5 -7
  147. package/src/components/Avatar/Avatar.test.tsx +4 -4
  148. package/src/components/Badge/Badge.tsx +14 -0
  149. package/src/components/Button/Button.tsx +22 -0
  150. package/src/components/Calendar/Calendar.tsx +8 -2
  151. package/src/components/Card/Card.tsx +4 -0
  152. package/src/components/Checkbox/Checkbox.test.tsx +12 -12
  153. package/src/components/Checkbox/Checkbox.tsx +2 -2
  154. package/src/components/ContextSelector/ContextSelector.tsx +384 -0
  155. package/src/components/ContextSelector/index.ts +3 -0
  156. package/src/components/DataTable/DataTable.tsx +38 -4
  157. package/src/components/DataTable/__tests__/DataTableCore.test-setup.ts +5 -6
  158. package/src/components/DataTable/__tests__/pagination.modes.test.tsx +18 -4
  159. package/src/components/DataTable/__tests__/test-utils/sharedTestUtils.tsx +2 -3
  160. package/src/components/DataTable/components/AccessDeniedPage.tsx +16 -25
  161. package/src/components/DataTable/components/ActionButtons.tsx +10 -7
  162. package/src/components/DataTable/components/BulkOperationsDropdown.tsx +1 -1
  163. package/src/components/DataTable/components/ColumnFilter.tsx +10 -0
  164. package/src/components/DataTable/components/ColumnVisibilityDropdown.tsx +12 -0
  165. package/src/components/DataTable/components/DataTableBody.tsx +8 -0
  166. package/src/components/DataTable/components/DataTableCore.tsx +196 -554
  167. package/src/components/DataTable/components/DataTableErrorBoundary.tsx +11 -0
  168. package/src/components/DataTable/components/DataTableLayout.tsx +559 -0
  169. package/src/components/DataTable/components/DataTableModals.tsx +8 -0
  170. package/src/components/DataTable/components/DataTableToolbar.tsx +8 -0
  171. package/src/components/DataTable/components/DraggableColumnHeader.tsx +12 -0
  172. package/src/components/DataTable/components/EditFields.tsx +307 -0
  173. package/src/components/DataTable/components/EditableRow.tsx +8 -0
  174. package/src/components/DataTable/components/EmptyState.tsx +10 -0
  175. package/src/components/DataTable/components/FilterRow.tsx +12 -0
  176. package/src/components/DataTable/components/GroupHeader.tsx +12 -0
  177. package/src/components/DataTable/components/GroupingDropdown.tsx +12 -0
  178. package/src/components/DataTable/components/ImportModal.tsx +7 -0
  179. package/src/components/DataTable/components/LoadingState.tsx +6 -0
  180. package/src/components/DataTable/components/PaginationControls.tsx +16 -1
  181. package/src/components/DataTable/components/RowComponent.tsx +391 -0
  182. package/src/components/DataTable/components/UnifiedTableBody.tsx +63 -851
  183. package/src/components/DataTable/components/VirtualizedDataTable.tsx +16 -4
  184. package/src/components/DataTable/components/__tests__/AccessDeniedPage.test.tsx +4 -2
  185. package/src/components/DataTable/components/cellValueUtils.ts +40 -0
  186. package/src/components/DataTable/components/hooks/useImportModalFocus.ts +53 -0
  187. package/src/components/DataTable/components/hooks/usePermissionTracking.ts +126 -0
  188. package/src/components/DataTable/context/DataTableContext.tsx +50 -0
  189. package/src/components/DataTable/core/ColumnFactory.ts +31 -0
  190. package/src/components/DataTable/core/DataTableContext.tsx +32 -1
  191. package/src/components/DataTable/hooks/useColumnOrderPersistence.ts +10 -0
  192. package/src/components/DataTable/hooks/useColumnReordering.ts +12 -0
  193. package/src/components/DataTable/hooks/useColumnVisibilityPersistence.ts +10 -0
  194. package/src/components/DataTable/hooks/useDataTableDataPipeline.ts +16 -0
  195. package/src/components/DataTable/hooks/useDataTablePermissions.ts +127 -33
  196. package/src/components/DataTable/hooks/useDataTableState.ts +35 -1
  197. package/src/components/DataTable/hooks/useEffectiveColumnOrder.ts +12 -0
  198. package/src/components/DataTable/hooks/useServerSideDataEffect.ts +11 -0
  199. package/src/components/DataTable/hooks/useTableColumns.ts +8 -0
  200. package/src/components/DataTable/hooks/useTableHandlers.ts +14 -0
  201. package/src/components/DataTable/styles.ts +6 -6
  202. package/src/components/DataTable/types.ts +6 -10
  203. package/src/components/DataTable/utils/a11yUtils.ts +7 -0
  204. package/src/components/DataTable/utils/debugTools.ts +18 -113
  205. package/src/components/DataTable/utils/errorHandling.ts +12 -0
  206. package/src/components/DataTable/utils/exportUtils.ts +9 -0
  207. package/src/components/DataTable/utils/flexibleImport.ts +12 -48
  208. package/src/components/DataTable/utils/paginationUtils.ts +8 -0
  209. package/src/components/DataTable/utils/performanceUtils.ts +5 -1
  210. package/src/components/Dialog/Dialog.tsx +31 -3
  211. package/src/components/ErrorBoundary/ErrorBoundary.test.tsx +180 -1
  212. package/src/components/ErrorBoundary/ErrorBoundary.tsx +45 -5
  213. package/src/components/ErrorBoundary/ErrorBoundaryContext.tsx +129 -0
  214. package/src/components/ErrorBoundary/index.ts +27 -2
  215. package/src/components/FileDisplay/FileDisplay.tsx +74 -28
  216. package/src/components/FileUpload/FileUpload.tsx +22 -2
  217. package/src/components/Footer/Footer.test.tsx +16 -16
  218. package/src/components/Footer/Footer.tsx +14 -11
  219. package/src/components/Form/Form.tsx +1 -0
  220. package/src/components/Header/Header.test.tsx +43 -73
  221. package/src/components/Header/Header.tsx +59 -49
  222. package/src/components/Input/Input.test.tsx +2 -2
  223. package/src/components/Input/Input.tsx +8 -4
  224. package/src/components/LoadingSpinner/LoadingSpinner.test.tsx +4 -4
  225. package/src/components/LoginForm/LoginForm.tsx +4 -0
  226. package/src/components/NavigationMenu/NavigationMenu.tsx +14 -513
  227. package/src/components/NavigationMenu/types.ts +56 -0
  228. package/src/components/NavigationMenu/useNavigationFiltering.ts +390 -0
  229. package/src/components/PaceAppLayout/PaceAppLayout.integration.test.tsx +10 -19
  230. package/src/components/PaceAppLayout/PaceAppLayout.performance.test.tsx +2 -2
  231. package/src/components/PaceAppLayout/PaceAppLayout.security.test.tsx +5 -5
  232. package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +13 -11
  233. package/src/components/PaceAppLayout/PaceAppLayout.tsx +167 -44
  234. package/src/components/PaceAppLayout/README.md +14 -17
  235. package/src/components/PaceAppLayout/test-setup.tsx +3 -4
  236. package/src/components/PaceLoginPage/PaceLoginPage.tsx +3 -0
  237. package/src/components/PasswordChange/PasswordChangeForm.tsx +9 -0
  238. package/src/components/ProtectedRoute/ProtectedRoute.tsx +3 -9
  239. package/src/components/PublicLayout/PublicPageLayout.tsx +2 -5
  240. package/src/components/PublicLayout/PublicPageProvider.tsx +4 -0
  241. package/src/components/Select/Select.tsx +80 -434
  242. package/src/components/Select/context.ts +23 -0
  243. package/src/components/Select/hooks/useSelectEvents.ts +87 -0
  244. package/src/components/Select/hooks/useSelectSearch.ts +91 -0
  245. package/src/components/Select/hooks/useSelectState.ts +104 -0
  246. package/src/components/Select/index.ts +9 -1
  247. package/src/components/Select/types.ts +123 -0
  248. package/src/components/Select/utils/text.ts +26 -0
  249. package/src/components/SessionRestorationLoader/SessionRestorationLoader.tsx +4 -5
  250. package/src/components/Switch/Switch.tsx +4 -4
  251. package/src/components/Tabs/Tabs.tsx +1 -1
  252. package/src/components/Toast/Toast.tsx +4 -0
  253. package/src/components/Tooltip/Tooltip.tsx +2 -2
  254. package/src/components/UserMenu/UserMenu.test.tsx +24 -11
  255. package/src/components/UserMenu/UserMenu.tsx +21 -18
  256. package/src/components/index.ts +7 -7
  257. package/src/eslint-rules/pace-core-compliance.cjs +106 -0
  258. package/src/hooks/__tests__/index.unit.test.ts +2 -5
  259. package/src/hooks/__tests__/useAppConfig.unit.test.ts +4 -98
  260. package/src/hooks/index.ts +1 -2
  261. package/src/hooks/public/usePublicEvent.ts +4 -0
  262. package/src/hooks/public/usePublicEventLogo.ts +4 -0
  263. package/src/hooks/public/usePublicFileDisplay.ts +4 -0
  264. package/src/hooks/public/usePublicRouteParams.ts +4 -0
  265. package/src/hooks/services/useAuth.ts +32 -0
  266. package/src/hooks/services/useCurrentEvent.ts +6 -0
  267. package/src/hooks/services/useCurrentOrganisation.ts +6 -0
  268. package/src/hooks/useAppConfig.ts +15 -30
  269. package/src/hooks/useDebounce.ts +9 -0
  270. package/src/hooks/useEventTheme.ts +6 -0
  271. package/src/hooks/useFileDisplay.ts +81 -50
  272. package/src/hooks/useFileReference.ts +25 -7
  273. package/src/hooks/useFileUrl.ts +11 -1
  274. package/src/hooks/useFocusManagement.ts +14 -0
  275. package/src/hooks/useFocusTrap.ts +3 -0
  276. package/src/hooks/useInactivityTracker.ts +3 -0
  277. package/src/hooks/useKeyboardShortcuts.ts +4 -0
  278. package/src/hooks/useOrganisationPermissions.ts +4 -0
  279. package/src/hooks/useOrganisationSecurity.ts +4 -0
  280. package/src/hooks/usePerformanceMonitor.ts +4 -0
  281. package/src/hooks/usePermissionCache.ts +7 -0
  282. package/src/hooks/useQueryCache.ts +12 -1
  283. package/src/hooks/useSessionRestoration.ts +4 -0
  284. package/src/hooks/useStorage.ts +4 -0
  285. package/src/hooks/useToast.ts +1 -1
  286. package/src/index.ts +6 -6
  287. package/src/providers/__tests__/OrganisationProvider.test.tsx +92 -70
  288. package/src/providers/services/AuthServiceProvider.tsx +35 -7
  289. package/src/providers/services/EventServiceProvider.tsx +51 -5
  290. package/src/providers/services/InactivityServiceProvider.tsx +18 -0
  291. package/src/providers/services/OrganisationServiceProvider.tsx +18 -0
  292. package/src/providers/services/UnifiedAuthProvider.tsx +126 -134
  293. package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +29 -13
  294. package/src/rbac/README.md +1 -1
  295. package/src/rbac/__tests__/adapters.comprehensive.test.tsx +1 -1
  296. package/src/rbac/__tests__/scenarios.user-role.test.tsx +4 -5
  297. package/src/rbac/adapters.tsx +12 -3
  298. package/src/rbac/api.test.ts +59 -51
  299. package/src/rbac/api.ts +246 -167
  300. package/src/rbac/components/NavigationProvider.tsx +4 -1
  301. package/src/rbac/components/PagePermissionGuard.tsx +185 -17
  302. package/src/rbac/components/RoleBasedRouter.tsx +5 -1
  303. package/src/rbac/components/SecureDataProvider.test.tsx +84 -49
  304. package/src/rbac/components/SecureDataProvider.tsx +20 -5
  305. package/src/rbac/components/__tests__/PagePermissionGuard.race-condition.test.tsx +24 -14
  306. package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +7 -0
  307. package/src/rbac/components/__tests__/PagePermissionGuard.verification.test.tsx +14 -6
  308. package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +15 -4
  309. package/src/rbac/components/__tests__/SecureDataProvider.fixed.test.tsx +148 -24
  310. package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +81 -15
  311. package/src/rbac/engine.ts +38 -14
  312. package/src/rbac/hooks/__tests__/useSecureSupabase.test.ts +32 -21
  313. package/src/rbac/hooks/permissions/index.ts +7 -0
  314. package/src/rbac/hooks/permissions/useAccessLevel.ts +105 -0
  315. package/src/rbac/hooks/permissions/useCachedPermissions.ts +79 -0
  316. package/src/rbac/hooks/permissions/useCan.ts +377 -0
  317. package/src/rbac/hooks/permissions/useHasAllPermissions.ts +90 -0
  318. package/src/rbac/hooks/permissions/useHasAnyPermission.ts +90 -0
  319. package/src/rbac/hooks/permissions/useMultiplePermissions.ts +93 -0
  320. package/src/rbac/hooks/permissions/usePermissions.ts +253 -0
  321. package/src/rbac/hooks/useCan.test.ts +64 -66
  322. package/src/rbac/hooks/usePermissions.ts +14 -995
  323. package/src/rbac/hooks/useRBAC.test.ts +1 -5
  324. package/src/rbac/hooks/useRBAC.ts +36 -37
  325. package/src/rbac/hooks/useResolvedScope.test.ts +120 -35
  326. package/src/rbac/hooks/useResolvedScope.ts +35 -40
  327. package/src/rbac/hooks/useResourcePermissions.test.ts +54 -18
  328. package/src/rbac/hooks/useResourcePermissions.ts +14 -4
  329. package/src/rbac/hooks/useSecureSupabase.ts +27 -7
  330. package/src/rbac/index.ts +7 -0
  331. package/src/rbac/permissions.ts +0 -30
  332. package/src/rbac/secureClient.test.ts +22 -18
  333. package/src/rbac/secureClient.ts +294 -68
  334. package/src/rbac/security.ts +0 -17
  335. package/src/rbac/types.ts +9 -0
  336. package/src/rbac/utils/__tests__/contextValidator.test.ts +64 -86
  337. package/src/rbac/utils/clientSecurity.ts +93 -0
  338. package/src/rbac/utils/contextValidator.ts +77 -168
  339. package/src/services/AuthService.ts +39 -7
  340. package/src/services/EventService.ts +186 -54
  341. package/src/services/OrganisationService.ts +81 -14
  342. package/src/services/__tests__/EventService.test.ts +1 -2
  343. package/src/services/base/BaseService.ts +3 -0
  344. package/src/theming/__tests__/parseEventColours.test.ts +6 -9
  345. package/src/theming/parseEventColours.ts +5 -19
  346. package/src/types/vitest-globals.d.ts +51 -26
  347. package/src/utils/__mocks__/supabaseMock.ts +1 -3
  348. package/src/utils/__tests__/formatting.unit.test.ts +4 -4
  349. package/src/utils/__tests__/index.unit.test.ts +2 -2
  350. package/src/utils/audit/audit.ts +0 -3
  351. package/src/utils/core/cn.ts +1 -1
  352. package/src/utils/dynamic/dynamicUtils.ts +7 -4
  353. package/src/utils/file-reference/index.ts +53 -1
  354. package/src/utils/formatting/formatting.ts +8 -18
  355. package/src/utils/index.ts +0 -1
  356. package/dist/chunk-3QRJFVBR.js.map +0 -1
  357. package/dist/chunk-3XTALGJF.js.map +0 -1
  358. package/dist/chunk-4N5C5XZU.js.map +0 -1
  359. package/dist/chunk-4ZC4GX36.js.map +0 -1
  360. package/dist/chunk-7D4SUZUM.js +0 -38
  361. package/dist/chunk-BYFSK72L.js.map +0 -1
  362. package/dist/chunk-EXUD6RNJ.js +0 -451
  363. package/dist/chunk-EXUD6RNJ.js.map +0 -1
  364. package/dist/chunk-GLK6VM3F.js.map +0 -1
  365. package/dist/chunk-I7PSE6JW.js.map +0 -1
  366. package/dist/chunk-JBKQ3SAO.js.map +0 -1
  367. package/dist/chunk-KNC55RTG.js.map +0 -1
  368. package/dist/chunk-LXQLPRQ2.js.map +0 -1
  369. package/dist/chunk-R77UEZ4E.js.map +0 -1
  370. package/dist/chunk-SQGMNID3.js.map +0 -1
  371. package/dist/chunk-T33XF5ZC.js +0 -12922
  372. package/dist/chunk-T33XF5ZC.js.map +0 -1
  373. package/dist/chunk-XM25TVIE.js.map +0 -1
  374. package/docs/api/classes/ColumnFactory.md +0 -243
  375. package/docs/api/classes/ErrorBoundary.md +0 -144
  376. package/docs/api/classes/InvalidScopeError.md +0 -73
  377. package/docs/api/classes/Logger.md +0 -178
  378. package/docs/api/classes/MissingUserContextError.md +0 -66
  379. package/docs/api/classes/OrganisationContextRequiredError.md +0 -66
  380. package/docs/api/classes/PermissionDeniedError.md +0 -73
  381. package/docs/api/classes/RBACAuditManager.md +0 -297
  382. package/docs/api/classes/RBACCache.md +0 -322
  383. package/docs/api/classes/RBACEngine.md +0 -171
  384. package/docs/api/classes/RBACError.md +0 -76
  385. package/docs/api/classes/RBACNotInitializedError.md +0 -66
  386. package/docs/api/classes/SecureSupabaseClient.md +0 -160
  387. package/docs/api/classes/StorageUtils.md +0 -328
  388. package/docs/api/enums/FileCategory.md +0 -184
  389. package/docs/api/enums/LogLevel.md +0 -54
  390. package/docs/api/enums/RBACErrorCode.md +0 -228
  391. package/docs/api/enums/RPCFunction.md +0 -118
  392. package/docs/api/interfaces/AddressFieldProps.md +0 -241
  393. package/docs/api/interfaces/AddressFieldRef.md +0 -94
  394. package/docs/api/interfaces/AggregateConfig.md +0 -43
  395. package/docs/api/interfaces/AutocompleteOptions.md +0 -75
  396. package/docs/api/interfaces/AvatarProps.md +0 -128
  397. package/docs/api/interfaces/BadgeProps.md +0 -27
  398. package/docs/api/interfaces/ButtonProps.md +0 -53
  399. package/docs/api/interfaces/CalendarProps.md +0 -70
  400. package/docs/api/interfaces/CardProps.md +0 -66
  401. package/docs/api/interfaces/ColorPalette.md +0 -7
  402. package/docs/api/interfaces/ColorShade.md +0 -66
  403. package/docs/api/interfaces/ComplianceResult.md +0 -30
  404. package/docs/api/interfaces/DataAccessRecord.md +0 -96
  405. package/docs/api/interfaces/DataRecord.md +0 -11
  406. package/docs/api/interfaces/DataTableAction.md +0 -249
  407. package/docs/api/interfaces/DataTableColumn.md +0 -504
  408. package/docs/api/interfaces/DataTableProps.md +0 -625
  409. package/docs/api/interfaces/DataTableToolbarButton.md +0 -96
  410. package/docs/api/interfaces/DatabaseComplianceResult.md +0 -85
  411. package/docs/api/interfaces/DatabaseIssue.md +0 -41
  412. package/docs/api/interfaces/EmptyStateConfig.md +0 -61
  413. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +0 -235
  414. package/docs/api/interfaces/EventAppRoleData.md +0 -71
  415. package/docs/api/interfaces/ExportColumn.md +0 -90
  416. package/docs/api/interfaces/ExportOptions.md +0 -126
  417. package/docs/api/interfaces/FileDisplayProps.md +0 -249
  418. package/docs/api/interfaces/FileMetadata.md +0 -129
  419. package/docs/api/interfaces/FileReference.md +0 -118
  420. package/docs/api/interfaces/FileSizeLimits.md +0 -7
  421. package/docs/api/interfaces/FileUploadOptions.md +0 -139
  422. package/docs/api/interfaces/FileUploadProps.md +0 -293
  423. package/docs/api/interfaces/FooterProps.md +0 -105
  424. package/docs/api/interfaces/FormFieldProps.md +0 -166
  425. package/docs/api/interfaces/FormProps.md +0 -113
  426. package/docs/api/interfaces/GrantEventAppRoleParams.md +0 -122
  427. package/docs/api/interfaces/InactivityWarningModalProps.md +0 -115
  428. package/docs/api/interfaces/InputProps.md +0 -53
  429. package/docs/api/interfaces/LabelProps.md +0 -107
  430. package/docs/api/interfaces/LoggerConfig.md +0 -62
  431. package/docs/api/interfaces/LoginFormProps.md +0 -184
  432. package/docs/api/interfaces/NavigationAccessRecord.md +0 -107
  433. package/docs/api/interfaces/NavigationContextType.md +0 -164
  434. package/docs/api/interfaces/NavigationGuardProps.md +0 -139
  435. package/docs/api/interfaces/NavigationItem.md +0 -120
  436. package/docs/api/interfaces/NavigationMenuProps.md +0 -221
  437. package/docs/api/interfaces/NavigationProviderProps.md +0 -117
  438. package/docs/api/interfaces/Organisation.md +0 -140
  439. package/docs/api/interfaces/OrganisationContextType.md +0 -388
  440. package/docs/api/interfaces/OrganisationMembership.md +0 -140
  441. package/docs/api/interfaces/OrganisationProviderProps.md +0 -76
  442. package/docs/api/interfaces/OrganisationSecurityError.md +0 -62
  443. package/docs/api/interfaces/PaceAppLayoutProps.md +0 -406
  444. package/docs/api/interfaces/PaceLoginPageProps.md +0 -47
  445. package/docs/api/interfaces/PageAccessRecord.md +0 -85
  446. package/docs/api/interfaces/PagePermissionContextType.md +0 -140
  447. package/docs/api/interfaces/PagePermissionGuardProps.md +0 -153
  448. package/docs/api/interfaces/PagePermissionProviderProps.md +0 -119
  449. package/docs/api/interfaces/PaletteData.md +0 -41
  450. package/docs/api/interfaces/ParsedAddress.md +0 -120
  451. package/docs/api/interfaces/PermissionEnforcerProps.md +0 -153
  452. package/docs/api/interfaces/ProgressProps.md +0 -42
  453. package/docs/api/interfaces/ProtectedRouteProps.md +0 -97
  454. package/docs/api/interfaces/PublicPageFooterProps.md +0 -112
  455. package/docs/api/interfaces/PublicPageHeaderProps.md +0 -125
  456. package/docs/api/interfaces/PublicPageLayoutProps.md +0 -198
  457. package/docs/api/interfaces/QuickFix.md +0 -52
  458. package/docs/api/interfaces/RBACAccessValidateParams.md +0 -52
  459. package/docs/api/interfaces/RBACAccessValidateResult.md +0 -41
  460. package/docs/api/interfaces/RBACAuditLogParams.md +0 -85
  461. package/docs/api/interfaces/RBACAuditLogResult.md +0 -52
  462. package/docs/api/interfaces/RBACConfig.md +0 -133
  463. package/docs/api/interfaces/RBACContext.md +0 -52
  464. package/docs/api/interfaces/RBACLogger.md +0 -112
  465. package/docs/api/interfaces/RBACPageAccessCheckParams.md +0 -74
  466. package/docs/api/interfaces/RBACPerformanceMetrics.md +0 -138
  467. package/docs/api/interfaces/RBACPermissionCheckParams.md +0 -74
  468. package/docs/api/interfaces/RBACPermissionCheckResult.md +0 -52
  469. package/docs/api/interfaces/RBACPermissionsGetParams.md +0 -63
  470. package/docs/api/interfaces/RBACPermissionsGetResult.md +0 -63
  471. package/docs/api/interfaces/RBACResult.md +0 -58
  472. package/docs/api/interfaces/RBACRoleGrantParams.md +0 -63
  473. package/docs/api/interfaces/RBACRoleGrantResult.md +0 -52
  474. package/docs/api/interfaces/RBACRoleRevokeParams.md +0 -63
  475. package/docs/api/interfaces/RBACRoleRevokeResult.md +0 -52
  476. package/docs/api/interfaces/RBACRoleValidateParams.md +0 -52
  477. package/docs/api/interfaces/RBACRoleValidateResult.md +0 -63
  478. package/docs/api/interfaces/RBACRolesListParams.md +0 -52
  479. package/docs/api/interfaces/RBACRolesListResult.md +0 -74
  480. package/docs/api/interfaces/RBACSessionTrackParams.md +0 -74
  481. package/docs/api/interfaces/RBACSessionTrackResult.md +0 -52
  482. package/docs/api/interfaces/ResourcePermissions.md +0 -155
  483. package/docs/api/interfaces/RevokeEventAppRoleParams.md +0 -100
  484. package/docs/api/interfaces/RoleBasedRouterContextType.md +0 -151
  485. package/docs/api/interfaces/RoleBasedRouterProps.md +0 -156
  486. package/docs/api/interfaces/RoleManagementResult.md +0 -52
  487. package/docs/api/interfaces/RouteAccessRecord.md +0 -107
  488. package/docs/api/interfaces/RouteConfig.md +0 -134
  489. package/docs/api/interfaces/RuntimeComplianceResult.md +0 -55
  490. package/docs/api/interfaces/SecureDataContextType.md +0 -168
  491. package/docs/api/interfaces/SecureDataProviderProps.md +0 -132
  492. package/docs/api/interfaces/SessionRestorationLoaderProps.md +0 -34
  493. package/docs/api/interfaces/SetupIssue.md +0 -41
  494. package/docs/api/interfaces/StorageConfig.md +0 -41
  495. package/docs/api/interfaces/StorageFileInfo.md +0 -74
  496. package/docs/api/interfaces/StorageFileMetadata.md +0 -151
  497. package/docs/api/interfaces/StorageListOptions.md +0 -99
  498. package/docs/api/interfaces/StorageListResult.md +0 -41
  499. package/docs/api/interfaces/StorageUploadOptions.md +0 -101
  500. package/docs/api/interfaces/StorageUploadResult.md +0 -63
  501. package/docs/api/interfaces/StorageUrlOptions.md +0 -60
  502. package/docs/api/interfaces/StyleImport.md +0 -19
  503. package/docs/api/interfaces/SwitchProps.md +0 -34
  504. package/docs/api/interfaces/TabsContentProps.md +0 -9
  505. package/docs/api/interfaces/TabsListProps.md +0 -9
  506. package/docs/api/interfaces/TabsProps.md +0 -9
  507. package/docs/api/interfaces/TabsTriggerProps.md +0 -50
  508. package/docs/api/interfaces/TextareaProps.md +0 -53
  509. package/docs/api/interfaces/ToastActionElement.md +0 -9
  510. package/docs/api/interfaces/ToastProps.md +0 -9
  511. package/docs/api/interfaces/UnifiedAuthContextType.md +0 -820
  512. package/docs/api/interfaces/UnifiedAuthProviderProps.md +0 -171
  513. package/docs/api/interfaces/UseFormDialogOptions.md +0 -62
  514. package/docs/api/interfaces/UseFormDialogReturn.md +0 -117
  515. package/docs/api/interfaces/UseInactivityTrackerOptions.md +0 -136
  516. package/docs/api/interfaces/UseInactivityTrackerReturn.md +0 -123
  517. package/docs/api/interfaces/UsePublicEventLogoOptions.md +0 -87
  518. package/docs/api/interfaces/UsePublicEventLogoReturn.md +0 -81
  519. package/docs/api/interfaces/UsePublicEventOptions.md +0 -34
  520. package/docs/api/interfaces/UsePublicEventReturn.md +0 -68
  521. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +0 -47
  522. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +0 -120
  523. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +0 -94
  524. package/docs/api/interfaces/UseResolvedScopeOptions.md +0 -47
  525. package/docs/api/interfaces/UseResolvedScopeReturn.md +0 -47
  526. package/docs/api/interfaces/UseResourcePermissionsOptions.md +0 -34
  527. package/docs/api/interfaces/UserEventAccess.md +0 -118
  528. package/docs/api/interfaces/UserMenuProps.md +0 -86
  529. package/docs/api/interfaces/UserProfile.md +0 -63
  530. package/docs/migration/quick-migration-guide.md +0 -356
  531. package/docs/migration/service-architecture.md +0 -281
  532. package/src/components/EventSelector/EventSelector.test.tsx +0 -720
  533. package/src/components/EventSelector/EventSelector.tsx +0 -420
  534. package/src/components/EventSelector/index.ts +0 -3
  535. package/src/components/OrganisationSelector/OrganisationSelector.test.tsx +0 -784
  536. package/src/components/OrganisationSelector/OrganisationSelector.tsx +0 -324
  537. package/src/components/OrganisationSelector/index.ts +0 -9
  538. package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +0 -680
  539. package/src/hooks/useSecureDataAccess.test.ts +0 -559
  540. package/src/hooks/useSecureDataAccess.ts +0 -681
  541. /package/dist/{DataTable-DQ7RSOHE.js.map → DataTable-THFPBKTP.js.map} +0 -0
  542. /package/dist/{UnifiedAuthProvider-ATAP5UTR.js.map → UnifiedAuthProvider-KAGUYQ4J.js.map} +0 -0
  543. /package/dist/{api-N774RPUA.js.map → api-IAGWF3ZG.js.map} +0 -0
  544. /package/dist/{audit-B5P6FFIR.js.map → audit-V53FV5AG.js.map} +0 -0
  545. /package/dist/{chunk-7D4SUZUM.js.map → chunk-DGUM43GV.js.map} +0 -0
  546. /package/docs/migration/{organisation-context-timing-fix.md → V0.3.44_organisation-context-timing-fix.md} +0 -0
  547. /package/docs/migration/{rbac-migration.md → V0.4.0_rbac-migration.md} +0 -0
  548. /package/docs/migration/{person-scoped-profiles-migration-guide.md → V0.5.190_person-scoped-profiles-migration-guide.md} +0 -0
  549. /package/docs/migration/{REACT_19_MIGRATION.md → V0.6.0_REACT_19_MIGRATION.md} +0 -0
@@ -12,6 +12,7 @@ import { createClient, SupabaseClient } from '@supabase/supabase-js';
12
12
  import { Database } from '../types/database';
13
13
  import { UUID } from './types';
14
14
  import { OrganisationContextRequiredError } from './types';
15
+ import { markClientAsSecure } from './utils/clientSecurity';
15
16
 
16
17
  /**
17
18
  * Secure Supabase Client that enforces organisation context
@@ -19,26 +20,48 @@ import { OrganisationContextRequiredError } from './types';
19
20
  * This client automatically injects organisation context into all requests
20
21
  * and prevents queries that don't have the required context.
21
22
  *
22
- * Note: Callers should derive organisationId from eventId before creating this client
23
- * if working with event-required apps. The client requires organisationId.
23
+ * Note: For non-super-admins, organisationId is required. Super-admins can operate
24
+ * without organisationId to access system-wide tables (like core_organisations).
25
+ * Callers should derive organisationId from eventId before creating this client
26
+ * if working with event-required apps.
24
27
  */
25
28
  export class SecureSupabaseClient {
26
29
  private supabase: SupabaseClient<Database>;
27
30
  private edgeFunctionClient: SupabaseClient<Database> | null = null;
28
31
  private supabaseUrl: string;
29
32
  private supabaseKey: string;
30
- private organisationId: UUID;
33
+ private organisationId: UUID | null;
31
34
  private eventId?: string;
32
35
  private appId?: UUID;
33
36
  private isSuperAdmin: boolean;
37
+ private usesExistingClient: boolean = false;
38
+
39
+ // Cache for RPC function signatures to avoid repeated database queries
40
+ // Maps function name -> Set of parameter names it accepts
41
+ private static rpcSignatureCache = new Map<string, Set<string>>();
42
+
43
+ /**
44
+ * RPC functions that are safe to call without organisation context.
45
+ *
46
+ * These functions must:
47
+ * - rely on JWT context (auth.uid()) for authentication
48
+ * - not read or write organisation-scoped data
49
+ *
50
+ * This allowlist enables compliant consuming apps to use `secureSupabase.rpc(...)`
51
+ * even before an organisation is selected (common during initial page load/refresh).
52
+ */
53
+ private static readonly GLOBAL_RPC_ALLOWLIST = new Set<string>([
54
+ 'data_rbac_apps_list',
55
+ ]);
34
56
 
35
57
  constructor(
36
58
  supabaseUrl: string,
37
59
  supabaseKey: string,
38
- organisationId: UUID,
60
+ organisationId: UUID | null,
39
61
  eventId?: string,
40
62
  appId?: UUID,
41
- isSuperAdmin: boolean = false
63
+ isSuperAdmin: boolean = false,
64
+ existingClient?: SupabaseClient<Database>
42
65
  ) {
43
66
  this.supabaseUrl = supabaseUrl;
44
67
  this.supabaseKey = supabaseKey;
@@ -47,18 +70,25 @@ export class SecureSupabaseClient {
47
70
  this.appId = appId;
48
71
  this.isSuperAdmin = isSuperAdmin;
49
72
 
50
- // Create the base Supabase client with context headers
51
- // Note: We'll override functions.invoke to exclude headers for Edge Functions
52
- // as they may not have CORS configured to accept custom headers
53
- this.supabase = createClient<Database>(supabaseUrl, supabaseKey, {
54
- global: {
55
- headers: {
56
- 'x-organisation-id': organisationId,
57
- 'x-event-id': eventId || '',
58
- 'x-app-id': appId || '',
73
+ // Prefer reusing an existing authenticated client (avoids multiple GoTrue instances
74
+ // and ensures auth context is present for RLS policies).
75
+ if (existingClient) {
76
+ this.supabase = existingClient;
77
+ this.usesExistingClient = true;
78
+ } else {
79
+ // Create the base Supabase client with context headers
80
+ // Note: We'll override functions.invoke to exclude headers for Edge Functions
81
+ // as they may not have CORS configured to accept custom headers
82
+ this.supabase = createClient<Database>(supabaseUrl, supabaseKey, {
83
+ global: {
84
+ headers: {
85
+ 'x-organisation-id': organisationId || '',
86
+ 'x-event-id': eventId || '',
87
+ 'x-app-id': appId || '',
88
+ },
59
89
  },
60
- },
61
- });
90
+ });
91
+ }
62
92
 
63
93
  // Override the auth methods to inject context
64
94
  this.setupContextInjection();
@@ -66,6 +96,9 @@ export class SecureSupabaseClient {
66
96
  // Override functions.invoke to exclude custom headers for Edge Functions
67
97
  // Edge Functions may not have CORS configured to accept custom headers
68
98
  this.setupEdgeFunctionHandling();
99
+
100
+ // Mark the client as secure
101
+ markClientAsSecure(this.supabase);
69
102
  }
70
103
 
71
104
  /**
@@ -75,8 +108,10 @@ export class SecureSupabaseClient {
75
108
  const originalFrom = this.supabase.from.bind(this.supabase);
76
109
 
77
110
  (this.supabase as any).from = (table: string): any => {
78
- // Validate context before allowing any database operations
79
- this.validateContext();
111
+ // Validate context before allowing database operations.
112
+ // Some tables are not organisation-scoped (e.g. rbac_apps) and must be queryable
113
+ // without organisation context for initial bootstrapping.
114
+ this.validateContextForTable(table);
80
115
 
81
116
  // Type assertion needed because table is a string but Supabase expects specific table names
82
117
  const query = originalFrom(table as any);
@@ -94,16 +129,46 @@ export class SecureSupabaseClient {
94
129
  // Type assertion needed because we're wrapping the generic rpc method
95
130
  // The fn parameter is typed as string to match Supabase's rpc signature
96
131
  (this.supabase as any).rpc = (fn: string, args?: any, options?: any): any => {
97
- // Validate context before allowing any RPC calls
98
- this.validateContext();
132
+ // Validate context before allowing RPC calls.
133
+ // Some RPCs are global (not organisation-scoped) but still require auth.uid() from JWT.
134
+ // Allow these even without organisation context.
135
+ this.validateContextForRpc(fn);
136
+
137
+ // SYSTEMIC FIX: Use opt-in whitelist approach instead of brittle blacklist.
138
+ // PostgREST matches RPCs by *exact* parameter signature; sending unexpected params results in:
139
+ // - HTTP 404 + PGRST202 "Could not find the function ... with parameters ..."
140
+ //
141
+ // By default, we don't inject context unless the function is explicitly whitelisted.
142
+ // This prevents PGRST202 errors and makes the system more maintainable.
143
+ const acceptedParams = this.getRpcAcceptedParams(fn);
144
+
145
+ // Only inject context parameters that:
146
+ // 1. The function accepts (according to our whitelist)
147
+ // 2. Are not already explicitly provided
148
+ // 3. We have values for
149
+ // IMPORTANT: Do NOT overwrite explicitly provided RPC parameters.
150
+ // Some RPCs legitimately take `p_app_id`/`p_event_id` as the *target* entity,
151
+ // which may differ from the current RBAC scope app/event.
152
+ const safeArgs = (args ?? {}) as Record<string, unknown>;
153
+ const contextArgs: Record<string, unknown> = { ...safeArgs };
154
+
155
+ if (acceptedParams.has('p_organisation_id') &&
156
+ this.organisationId &&
157
+ safeArgs.p_organisation_id === undefined) {
158
+ contextArgs.p_organisation_id = this.organisationId;
159
+ }
160
+
161
+ if (acceptedParams.has('p_event_id') &&
162
+ this.eventId &&
163
+ safeArgs.p_event_id === undefined) {
164
+ contextArgs.p_event_id = this.eventId;
165
+ }
99
166
 
100
- // Inject context into RPC calls
101
- const contextArgs = {
102
- ...args,
103
- p_organisation_id: this.organisationId,
104
- p_event_id: this.eventId,
105
- p_app_id: this.appId,
106
- };
167
+ if (acceptedParams.has('p_app_id') &&
168
+ this.appId &&
169
+ safeArgs.p_app_id === undefined) {
170
+ contextArgs.p_app_id = this.appId;
171
+ }
107
172
 
108
173
  return originalRpc(fn as any, contextArgs, options);
109
174
  };
@@ -119,10 +184,19 @@ export class SecureSupabaseClient {
119
184
  * This avoids interfering with the main client's operations.
120
185
  */
121
186
  private setupEdgeFunctionHandling() {
122
- // Create a separate client without custom headers for Edge Functions
123
- // This prevents CORS errors when Edge Functions don't accept custom headers
124
- // Store it as an instance variable to avoid creating multiple clients
125
- // We'll use this client directly for Edge Function calls instead of overriding
187
+ // IMPORTANT:
188
+ // Do not create a second Supabase client when we are already reusing an authenticated
189
+ // base client (this triggers "Multiple GoTrueClient instances" warnings and can cause
190
+ // session/auth desync that breaks RLS-protected reads).
191
+ //
192
+ // If we're using an existing client, just use it for Edge Functions too.
193
+ if (this.usesExistingClient) {
194
+ this.edgeFunctionClient = null;
195
+ return;
196
+ }
197
+
198
+ // Otherwise, create a separate client without the custom RBAC headers for Edge Functions.
199
+ // This prevents CORS errors when Edge Functions don't accept custom headers.
126
200
  this.edgeFunctionClient = createClient<Database>(this.supabaseUrl, this.supabaseKey);
127
201
  }
128
202
 
@@ -147,6 +221,8 @@ export class SecureSupabaseClient {
147
221
  // Override select to add organisation filter
148
222
  query.select = (columns?: string) => {
149
223
  const result = originalSelect(columns);
224
+ // Store table name on query object so we can access it in filter methods
225
+ (result as any)._tableName = tableName;
150
226
  return this.addOrganisationFilter(result, tableName);
151
227
  };
152
228
 
@@ -173,13 +249,28 @@ export class SecureSupabaseClient {
173
249
  return originalInsert(values);
174
250
  }
175
251
  // Non-super-admin: Add organisation_id as defense in depth
252
+ // organisationId should always be available for non-super-admins (validateContext ensures this)
253
+ if (!this.organisationId) {
254
+ throw new OrganisationContextRequiredError();
255
+ }
176
256
  const contextValues = Array.isArray(values)
177
257
  ? values.map(v => ({ ...v, organisation_id: this.organisationId }))
178
258
  : { ...values, organisation_id: this.organisationId };
179
259
  return originalInsert(contextValues);
180
260
  }
181
261
 
182
- // For other tables, always add organisation_id
262
+ // For other tables, add organisation_id if available
263
+ // Super-admins might not have organisationId set, so allow them to set it explicitly
264
+ if (this.isSuperAdmin && !this.organisationId) {
265
+ // Super admin without organisationId: Don't force it (can be set explicitly if needed)
266
+ return originalInsert(values);
267
+ }
268
+
269
+ // Non-super-admin or super-admin with organisationId: Add organisation_id
270
+ // organisationId should always be available for non-super-admins (validateContext ensures this)
271
+ if (!this.organisationId) {
272
+ throw new OrganisationContextRequiredError();
273
+ }
183
274
  const contextValues = Array.isArray(values)
184
275
  ? values.map(v => ({ ...v, organisation_id: this.organisationId }))
185
276
  : { ...values, organisation_id: this.organisationId };
@@ -213,6 +304,10 @@ export class SecureSupabaseClient {
213
304
  * - Super admins: No org filter (see all users) - RLS will allow access
214
305
  * - Non-super-admins: Apply org filter as defense in depth - RLS will also filter
215
306
  *
307
+ * For system-wide tables (like core_organisations):
308
+ * - Super admins: No org filter (see all records) - RLS will allow access
309
+ * - Non-super-admins: Apply org filter as defense in depth - RLS will also filter
310
+ *
216
311
  * For other tables:
217
312
  * - Always apply org filter unless super admin bypasses it
218
313
  */
@@ -247,15 +342,29 @@ export class SecureSupabaseClient {
247
342
  return query;
248
343
  }
249
344
 
345
+ // System-wide tables that super-admins should be able to query without organisation filters
346
+ // These tables have organisation_id but super-admins need to see all records
347
+ const systemWideTablesForSuperAdmins = [
348
+ 'core_organisations', // Super-admins need to see all organisations
349
+ ];
350
+
351
+ // For system-wide tables, super-admins bypass organisation filter
352
+ if (systemWideTablesForSuperAdmins.includes(tableName) && this.isSuperAdmin) {
353
+ return query; // No filter - RLS handles access control
354
+ }
355
+
250
356
  // For rbac_user_profiles, use conditional filtering based on super admin status
251
357
  if (tableName === 'rbac_user_profiles') {
252
358
  // Super admins: No org filter (see all users via RLS)
253
- // Non-super-admins: Apply org filter as defense in depth (RLS also filters)
254
359
  if (this.isSuperAdmin) {
255
360
  return query; // No filter - RLS handles access control
256
361
  }
257
- // Apply org filter for non-super-admins as additional security layer
258
- return query.eq('organisation_id', this.organisationId);
362
+
363
+ // For non-super-admins: Apply org filter, but allow NULL organisation_id
364
+ // User profiles can have organisation_id = NULL (users not yet assigned to an org)
365
+ // We use .or() to allow either matching organisation_id OR NULL
366
+ // RLS policies will still enforce access control
367
+ return query.or(`organisation_id.eq.${this.organisationId},organisation_id.is.null`);
259
368
  }
260
369
 
261
370
  // For all other tables, apply organisation filter
@@ -273,17 +382,69 @@ export class SecureSupabaseClient {
273
382
 
274
383
  /**
275
384
  * Validate that required context is present
385
+ * Super-admins can operate without organisation context
276
386
  */
277
387
  private validateContext() {
388
+ // Super-admins can operate without organisation context
389
+ if (this.isSuperAdmin) {
390
+ return;
391
+ }
392
+
278
393
  if (!this.organisationId) {
279
394
  throw new OrganisationContextRequiredError();
280
395
  }
281
396
  }
282
397
 
398
+ /**
399
+ * Determine whether a table requires organisation context.
400
+ * Tables without an organisation_id column (or global configuration tables) are safe without org context.
401
+ */
402
+ private tableRequiresOrganisationContext(tableName: string): boolean {
403
+ // Keep this list aligned with the tables handled in `addOrganisationFilter` / `injectContext`.
404
+ const tablesWithoutOrganisationId = new Set<string>([
405
+ 'core_organisations',
406
+ 'rbac_apps',
407
+ 'rbac_app_pages',
408
+ 'rbac_global_roles',
409
+ 'core_person',
410
+ 'core_member',
411
+ 'core_contact',
412
+ 'core_consent',
413
+ 'core_identification',
414
+ 'core_qualification',
415
+ 'medi_profile',
416
+ 'medi_condition',
417
+ 'medi_diet',
418
+ 'medi_action_plan',
419
+ 'medi_profile_versions',
420
+ ]);
421
+
422
+ return !tablesWithoutOrganisationId.has(tableName);
423
+ }
424
+
425
+ /**
426
+ * Validate context for a specific table operation.
427
+ */
428
+ private validateContextForTable(tableName: string) {
429
+ if (this.isSuperAdmin) return;
430
+ if (!this.organisationId && this.tableRequiresOrganisationContext(tableName)) {
431
+ throw new OrganisationContextRequiredError();
432
+ }
433
+ }
434
+
435
+ /**
436
+ * Validate context for a specific RPC call.
437
+ */
438
+ private validateContextForRpc(fn: string) {
439
+ if (this.isSuperAdmin) return;
440
+ if (SecureSupabaseClient.GLOBAL_RPC_ALLOWLIST.has(fn)) return;
441
+ this.validateContext();
442
+ }
443
+
283
444
  /**
284
445
  * Get the current organisation ID
285
446
  */
286
- getOrganisationId(): UUID {
447
+ getOrganisationId(): UUID | null {
287
448
  return this.organisationId;
288
449
  }
289
450
 
@@ -305,7 +466,7 @@ export class SecureSupabaseClient {
305
466
  * Create a new client with updated context
306
467
  */
307
468
  withContext(updates: {
308
- organisationId?: UUID;
469
+ organisationId?: UUID | null;
309
470
  eventId?: string;
310
471
  appId?: UUID;
311
472
  isSuperAdmin?: boolean;
@@ -313,7 +474,7 @@ export class SecureSupabaseClient {
313
474
  return new SecureSupabaseClient(
314
475
  this.supabaseUrl,
315
476
  this.supabaseKey,
316
- updates.organisationId || this.organisationId,
477
+ updates.organisationId !== undefined ? updates.organisationId : this.organisationId,
317
478
  updates.eventId !== undefined ? updates.eventId : this.eventId,
318
479
  updates.appId !== undefined ? updates.appId : this.appId,
319
480
  updates.isSuperAdmin !== undefined ? updates.isSuperAdmin : this.isSuperAdmin
@@ -327,7 +488,7 @@ export class SecureSupabaseClient {
327
488
  getClient(): SupabaseClient<Database> {
328
489
  // Return a proxy that intercepts functions.invoke calls to use edge function client
329
490
  // This avoids CORS issues with Edge Functions while keeping the main client intact
330
- return new Proxy(this.supabase, {
491
+ const proxiedClient = new Proxy(this.supabase, {
331
492
  get: (target, prop) => {
332
493
  if (prop === 'functions' && this.edgeFunctionClient) {
333
494
  // Return the edge function client's functions for invoke calls
@@ -338,36 +499,100 @@ export class SecureSupabaseClient {
338
499
  return (target as any)[prop];
339
500
  }
340
501
  }) as SupabaseClient<Database>;
502
+
503
+ // Mark the proxied client as secure
504
+ markClientAsSecure(proxiedClient);
505
+
506
+ return proxiedClient;
507
+ }
508
+
509
+ /**
510
+ * Get the set of parameter names that an RPC function accepts.
511
+ * Uses a static whitelist of RPCs that we know accept context parameters.
512
+ *
513
+ * This is an opt-in approach: by default, we don't inject context unless
514
+ * the function is explicitly whitelisted. This prevents PGRST202 errors from
515
+ * injecting unexpected parameters.
516
+ *
517
+ * @param fn - The RPC function name
518
+ * @returns Set of parameter names the function accepts
519
+ */
520
+ private getRpcAcceptedParams(fn: string): Set<string> {
521
+ // Check cache first
522
+ if (SecureSupabaseClient.rpcSignatureCache.has(fn)) {
523
+ return SecureSupabaseClient.rpcSignatureCache.get(fn)!;
524
+ }
525
+
526
+ // Whitelist of RPCs that accept context parameters
527
+ // Format: function name -> Set of parameters it accepts
528
+ //
529
+ // SYSTEMIC FIX: This is an opt-in approach. By default, we don't inject context
530
+ // unless the function is explicitly whitelisted. This prevents PGRST202 errors
531
+ // from injecting unexpected parameters.
532
+ //
533
+ // To add a new RPC:
534
+ // 1. Check the function signature in the database:
535
+ // SELECT pg_get_function_identity_arguments(oid) FROM pg_proc WHERE proname = 'function_name';
536
+ // 2. Add it here with the parameters it accepts
537
+ const rpcContextWhitelist: Record<string, Set<string>> = {
538
+ // RPCs that accept all three context parameters
539
+ 'rbac_roles_list': new Set(['p_organisation_id', 'p_event_id', 'p_app_id']),
540
+
541
+ // RPCs that accept only p_organisation_id (not p_app_id or p_event_id)
542
+ 'data_file_reference_by_category_list': new Set(['p_organisation_id']),
543
+
544
+ // Add more RPCs here as we discover them
545
+ // Format: 'function_name': new Set(['p_organisation_id', 'p_event_id', 'p_app_id']),
546
+ };
547
+
548
+ // Default: empty set (no context injection) unless whitelisted
549
+ // This is the safe default - prevents PGRST202 errors
550
+ const acceptedParams = rpcContextWhitelist[fn] || new Set<string>();
551
+
552
+ // Cache the result to avoid repeated lookups
553
+ SecureSupabaseClient.rpcSignatureCache.set(fn, acceptedParams);
554
+
555
+ return acceptedParams;
341
556
  }
342
557
  }
343
558
 
344
- /**
345
- * Create a secure Supabase client with organisation context
346
- *
347
- * @param supabaseUrl - Supabase project URL
348
- * @param supabaseKey - Supabase publishable key or anon key (accepts both legacy anon keys and modern publishable keys)
349
- * @param organisationId - Required organisation ID
350
- * @param eventId - Optional event ID
351
- * @param appId - Optional app ID
352
- * @param isSuperAdmin - Optional super admin flag (defaults to false)
353
- * @returns SecureSupabaseClient instance
354
- *
355
- * @example
356
- * ```typescript
357
- * const client = createSecureClient(
358
- * 'https://your-project.supabase.co',
359
- * 'your-publishable-key-or-anon-key',
360
- * 'org-123',
361
- * 'event-456',
362
- * 'app-789',
363
- * false // isSuperAdmin
364
- * );
365
- * ```
366
- */
559
+ /**
560
+ * Create a secure Supabase client with organisation context
561
+ *
562
+ * @param supabaseUrl - Supabase project URL
563
+ * @param supabaseKey - Supabase publishable key or anon key (accepts both legacy anon keys and modern publishable keys)
564
+ * @param organisationId - Organisation ID (optional for super-admins)
565
+ * @param eventId - Optional event ID
566
+ * @param appId - Optional app ID
567
+ * @param isSuperAdmin - Optional super admin flag (defaults to false). When true, organisationId can be null.
568
+ * @returns SecureSupabaseClient instance
569
+ *
570
+ * @example
571
+ * ```typescript
572
+ * const client = createSecureClient(
573
+ * 'https://your-project.supabase.co',
574
+ * 'your-publishable-key-or-anon-key',
575
+ * 'org-123',
576
+ * 'event-456',
577
+ * 'app-789',
578
+ * false // isSuperAdmin
579
+ * );
580
+ *
581
+ * // For super-admins, organisationId can be null
582
+ * const superAdminClient = createSecureClient(
583
+ * 'https://your-project.supabase.co',
584
+ * 'your-publishable-key-or-anon-key',
585
+ * null, // organisationId not required for super-admins
586
+ * undefined,
587
+ * undefined,
588
+ * true // isSuperAdmin
589
+ * );
590
+ * ```
591
+ */
367
592
  export function createSecureClient(
368
593
  supabaseUrl: string,
369
594
  supabaseKey: string,
370
- organisationId: UUID,
595
+ organisationId: UUID | null,
371
596
  eventId?: string,
372
597
  appId?: UUID,
373
598
  isSuperAdmin: boolean = false
@@ -386,11 +611,12 @@ export function createSecureClient(
386
611
  */
387
612
  export function fromSupabaseClient(
388
613
  client: SupabaseClient<Database>,
389
- organisationId: UUID,
614
+ organisationId: UUID | null,
390
615
  eventId?: string,
391
- appId?: UUID
616
+ appId?: UUID,
617
+ isSuperAdmin: boolean = false
392
618
  ): SecureSupabaseClient {
393
- // We need the URL and key to create a new client, but they're not accessible
394
- // This function should be used with createSecureClient instead
395
- throw new Error('fromSupabaseClient is not supported. Use createSecureClient instead.');
619
+ // Wrap the existing client to reuse auth/session while enforcing organisation/event/app context.
620
+ // URL/key are unused in this mode.
621
+ return new SecureSupabaseClient('', '', organisationId, eventId, appId, isSuperAdmin, client);
396
622
  }
@@ -9,8 +9,6 @@
9
9
 
10
10
  import { UUID, Permission, Scope } from './types';
11
11
  import { createLogger } from '../utils/core/logger';
12
- import { ContextValidator } from './utils/contextValidator';
13
- import type { AppConfig } from './utils/contextValidator';
14
12
 
15
13
  const log = createLogger('RBACSecurity');
16
14
 
@@ -161,21 +159,6 @@ export class RBACSecurityValidator {
161
159
  return true;
162
160
  }
163
161
 
164
- /**
165
- * Validate context requirements for security
166
- * @param scope - Scope object
167
- * @param appConfig - App configuration
168
- * @param appName - App name (for PORTAL special case)
169
- * @returns True if context is valid, false otherwise
170
- */
171
- static async validateContextRequirements(
172
- scope: Scope,
173
- appConfig?: AppConfig | null,
174
- appName?: string
175
- ): Promise<boolean> {
176
- const validation = await ContextValidator.validateScope(scope, appConfig || null, appName);
177
- return validation.isValid;
178
- }
179
162
 
180
163
  /**
181
164
  * Log security event for monitoring
package/src/rbac/types.ts CHANGED
@@ -28,12 +28,20 @@ export type AccessLevel =
28
28
  | 'admin'
29
29
  | 'super';
30
30
 
31
+ /**
32
+ * Scope defines the context for permission checks.
33
+ * Can include organisation, event, and/or app identifiers.
34
+ */
31
35
  export type Scope = {
32
36
  organisationId?: UUID;
33
37
  eventId?: string; // event_id is text/varchar
34
38
  appId?: AppId | UUID;
35
39
  };
36
40
 
41
+ /**
42
+ * Permission check request parameters.
43
+ * Defines who (userId) is checking what permission in what context (scope).
44
+ */
37
45
  export type PermissionCheck = {
38
46
  userId: UUID;
39
47
  scope: Scope;
@@ -118,6 +126,7 @@ export interface RBACAppPage {
118
126
  created_by: UUID | null;
119
127
  updated_by: UUID | null;
120
128
  app_id: UUID;
129
+ scope_type: 'event' | 'organisation' | 'both'; // Required - single source of truth for page scoping
121
130
  }
122
131
 
123
132
  export interface RBACApp {