@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
@@ -1,12 +1,10 @@
1
1
  import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
2
  import type { SupabaseClient } from '@supabase/supabase-js';
3
- import { ContextValidator, type AppConfig } from '../contextValidator';
3
+ import { ContextValidator, type PageScopeType } from '../contextValidator';
4
4
  import { EventContextRequiredError, OrganisationContextRequiredError, type Scope } from '../../types';
5
5
  import type { Database } from '../../../types/database';
6
6
 
7
7
  describe('ContextValidator', () => {
8
- const eventRequiredConfig: AppConfig = { requires_event: true };
9
- const orgRequiredConfig: AppConfig = { requires_event: false };
10
8
  const scopeWithOrg: Scope = { organisationId: 'org-1', eventId: 'event-1', appId: 'app-1' };
11
9
 
12
10
  const createSupabaseMock = () => ({}) as SupabaseClient<Database>;
@@ -19,132 +17,112 @@ describe('ContextValidator', () => {
19
17
  vi.clearAllMocks();
20
18
  });
21
19
 
22
- describe('validateScope', () => {
23
- it('treats PORTAL and ADMIN apps as always valid', async () => {
24
- const baseScope: Scope = { appId: 'portal-app' };
25
- const result = await ContextValidator.validateScope(baseScope, eventRequiredConfig, 'PORTAL');
26
-
27
- expect(result).toEqual({
28
- isValid: true,
29
- resolvedScope: { organisationId: undefined, eventId: undefined, appId: 'portal-app' },
30
- error: null
31
- });
32
- });
20
+ describe('deriveOrgFromEvent', () => {
21
+ it('derives organisation ID from event ID', async () => {
22
+ const supabase = createSupabaseMock();
23
+ // Mock the underlying function
24
+ const { getOrganisationFromEvent } = await import('../eventContext');
25
+ vi.spyOn(await import('../eventContext'), 'getOrganisationFromEvent').mockResolvedValue('derived-org' as any);
33
26
 
34
- it('requires organisation context when no app config exists', async () => {
35
- const baseScope: Scope = { eventId: 'event-1' };
36
- const result = await ContextValidator.validateScope(baseScope, null);
27
+ const result = await ContextValidator.deriveOrgFromEvent(supabase, 'event-1');
37
28
 
38
- expect(result.isValid).toBe(false);
39
- expect(result.error).toBeInstanceOf(OrganisationContextRequiredError);
29
+ expect(result).toBe('derived-org');
40
30
  });
31
+ });
41
32
 
42
- it('requires event context for event-based apps', async () => {
43
- const baseScope: Scope = { organisationId: 'org-1' };
44
- const result = await ContextValidator.validateScope(baseScope, eventRequiredConfig);
33
+ describe('resolveScopeForPage', () => {
34
+ it('handles event scope - requires event context', async () => {
35
+ const scope: Scope = { organisationId: 'org-1' };
36
+ const result = await ContextValidator.resolveScopeForPage(scope, 'event');
45
37
 
46
38
  expect(result.isValid).toBe(false);
47
39
  expect(result.error).toBeInstanceOf(EventContextRequiredError);
48
40
  });
49
41
 
50
- it('accepts scopes that satisfy event-based requirements', async () => {
51
- const result = await ContextValidator.validateScope({ eventId: 'event-1' }, eventRequiredConfig);
42
+ it('handles event scope - derives org from event when event provided', async () => {
43
+ const supabase = createSupabaseMock();
44
+ vi.spyOn(ContextValidator, 'deriveOrgFromEvent').mockResolvedValue('derived-org');
45
+
46
+ const scope: Scope = { eventId: 'event-1', appId: 'app-1' };
47
+ const result = await ContextValidator.resolveScopeForPage(scope, 'event', undefined, supabase);
52
48
 
53
49
  expect(result.isValid).toBe(true);
50
+ expect(result.resolvedScope?.organisationId).toBe('derived-org');
54
51
  expect(result.resolvedScope?.eventId).toBe('event-1');
55
52
  });
56
53
 
57
- it('enforces organisation requirements for org-based apps', async () => {
58
- const result = await ContextValidator.validateScope({ eventId: 'event-1' }, orgRequiredConfig);
54
+ it('handles organisation scope - requires organisation context', async () => {
55
+ const scope: Scope = { eventId: 'event-1' };
56
+ const result = await ContextValidator.resolveScopeForPage(scope, 'organisation');
59
57
 
60
58
  expect(result.isValid).toBe(false);
61
59
  expect(result.error).toBeInstanceOf(OrganisationContextRequiredError);
62
60
  });
63
- });
64
61
 
65
- describe('resolveRequiredContext', () => {
66
- it('passes through scope for optional contexts', async () => {
67
- const baseScope: Scope = { appId: 'admin-app' };
68
- const result = await ContextValidator.resolveRequiredContext(baseScope, eventRequiredConfig, 'ADMIN');
62
+ it('handles organisation scope - accepts organisation context', async () => {
63
+ const scope: Scope = { organisationId: 'org-1', eventId: 'event-1', appId: 'app-1' };
64
+ const result = await ContextValidator.resolveScopeForPage(scope, 'organisation');
69
65
 
70
- expect(result).toEqual({
71
- isValid: true,
72
- resolvedScope: { organisationId: undefined, eventId: undefined, appId: 'admin-app' },
73
- error: null
74
- });
66
+ expect(result.isValid).toBe(true);
67
+ expect(result.resolvedScope).toEqual(scope);
75
68
  });
76
69
 
77
- it('returns organisation error when no app config and org is missing', async () => {
78
- const result = await ContextValidator.resolveRequiredContext({ eventId: 'event-1' }, null);
70
+ it('handles both scope - requires at least one context', async () => {
71
+ const scope: Scope = { appId: 'app-1' };
72
+ const result = await ContextValidator.resolveScopeForPage(scope, 'both');
79
73
 
80
74
  expect(result.isValid).toBe(false);
81
- expect(result.error).toBeInstanceOf(OrganisationContextRequiredError);
75
+ expect(result.error?.message).toContain('either organisation or event context');
82
76
  });
83
77
 
84
- it('derives organisation id for event-required apps when missing', async () => {
85
- const supabase = createSupabaseMock();
86
- const deriveSpy = vi.spyOn(ContextValidator, 'deriveOrgFromEvent').mockResolvedValue('derived-org');
87
-
88
- const result = await ContextValidator.resolveRequiredContext({ eventId: 'event-1' }, eventRequiredConfig, undefined, supabase);
78
+ it('handles both scope - accepts organisation context', async () => {
79
+ const scope: Scope = { organisationId: 'org-1', appId: 'app-1' };
80
+ const result = await ContextValidator.resolveScopeForPage(scope, 'both');
89
81
 
90
- expect(deriveSpy).toHaveBeenCalledWith(supabase, 'event-1');
91
- expect(result).toEqual({
92
- isValid: true,
93
- resolvedScope: { organisationId: 'derived-org', eventId: 'event-1', appId: undefined },
94
- error: null
95
- });
82
+ expect(result.isValid).toBe(true);
83
+ expect(result.resolvedScope?.organisationId).toBe('org-1');
96
84
  });
97
85
 
98
- it('surfaces derivation failures when supabase is unavailable', async () => {
99
- const result = await ContextValidator.resolveRequiredContext({ eventId: 'event-1' }, eventRequiredConfig, undefined, null);
86
+ it('handles both scope - accepts event context', async () => {
87
+ const scope: Scope = { eventId: 'event-1', appId: 'app-1' };
88
+ const result = await ContextValidator.resolveScopeForPage(scope, 'both');
100
89
 
101
- expect(result.isValid).toBe(false);
102
- expect(result.error?.message).toContain('supabase client not available');
90
+ expect(result.isValid).toBe(true);
91
+ expect(result.resolvedScope?.eventId).toBe('event-1');
103
92
  });
104
93
 
105
- it('returns derivation error messages when fetch fails', async () => {
94
+ it('handles both scope - derives org from event when event provided', async () => {
106
95
  const supabase = createSupabaseMock();
107
- vi.spyOn(ContextValidator, 'deriveOrgFromEvent').mockRejectedValue(new Error('network down'));
96
+ vi.spyOn(ContextValidator, 'deriveOrgFromEvent').mockResolvedValue('derived-org');
108
97
 
109
- const result = await ContextValidator.resolveRequiredContext({ eventId: 'event-1' }, eventRequiredConfig, undefined, supabase);
98
+ const scope: Scope = { eventId: 'event-1', appId: 'app-1' };
99
+ const result = await ContextValidator.resolveScopeForPage(scope, 'both', undefined, supabase);
110
100
 
111
- expect(result.isValid).toBe(false);
112
- expect(result.error).toBeInstanceOf(Error);
113
- expect(result.error?.message).toContain('network down');
114
- });
115
-
116
- it('enforces organisation requirement for org-based apps', async () => {
117
- const result = await ContextValidator.resolveRequiredContext({ eventId: 'event-1' }, orgRequiredConfig);
118
-
119
- expect(result.isValid).toBe(false);
120
- expect(result.error).toBeInstanceOf(OrganisationContextRequiredError);
101
+ expect(result.isValid).toBe(true);
102
+ expect(result.resolvedScope?.organisationId).toBe('derived-org');
103
+ expect(result.resolvedScope?.eventId).toBe('event-1');
121
104
  });
122
105
 
123
- it('returns valid result when org context is present', async () => {
124
- const result = await ContextValidator.resolveRequiredContext(scopeWithOrg, orgRequiredConfig);
106
+ it('handles event scope - returns error when org derivation fails', async () => {
107
+ const supabase = createSupabaseMock();
108
+ vi.spyOn(ContextValidator, 'deriveOrgFromEvent').mockResolvedValue(null);
125
109
 
126
- expect(result).toEqual({ isValid: true, resolvedScope: scopeWithOrg, error: null });
127
- });
128
- });
110
+ const scope: Scope = { eventId: 'event-1', appId: 'app-1' };
111
+ const result = await ContextValidator.resolveScopeForPage(scope, 'event', undefined, supabase);
129
112
 
130
- describe('isContextReady', () => {
131
- it('is always ready for PORTAL/ADMIN apps', () => {
132
- expect(ContextValidator.isContextReady({}, eventRequiredConfig, 'PORTAL')).toBe(true);
113
+ expect(result.isValid).toBe(false);
114
+ expect(result.error?.message).toContain('Could not resolve organisation from event context');
133
115
  });
134
116
 
135
- it('checks event selection for event-required apps', () => {
136
- expect(ContextValidator.isContextReady({}, eventRequiredConfig, undefined, true)).toBe(true);
137
- expect(ContextValidator.isContextReady({}, eventRequiredConfig, undefined, false)).toBe(false);
138
- });
117
+ it('handles event scope - returns error when org derivation throws', async () => {
118
+ const supabase = createSupabaseMock();
119
+ vi.spyOn(ContextValidator, 'deriveOrgFromEvent').mockRejectedValue(new Error('network error'));
139
120
 
140
- it('checks organisation selection for org-required apps', () => {
141
- expect(ContextValidator.isContextReady({}, orgRequiredConfig, undefined, false, true)).toBe(true);
142
- expect(ContextValidator.isContextReady({}, orgRequiredConfig, undefined, false, false)).toBe(false);
143
- });
121
+ const scope: Scope = { eventId: 'event-1', appId: 'app-1' };
122
+ const result = await ContextValidator.resolveScopeForPage(scope, 'event', undefined, supabase);
144
123
 
145
- it('defaults to organisation requirement when no config', () => {
146
- expect(ContextValidator.isContextReady({}, null, undefined, false, true)).toBe(true);
147
- expect(ContextValidator.isContextReady({}, null, undefined, false, false)).toBe(false);
124
+ expect(result.isValid).toBe(false);
125
+ expect(result.error?.message).toContain('network error');
148
126
  });
149
127
  });
150
128
  });
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Client Security Detection Utilities
3
+ * @package @jmruthers/pace-core
4
+ * @module RBAC/Utils/ClientSecurity
5
+ * @since 1.0.0
6
+ *
7
+ * Utilities to detect and warn about insecure Supabase client usage.
8
+ */
9
+
10
+ import type { SupabaseClient } from '@supabase/supabase-js';
11
+ import { Database } from '../../types/database';
12
+
13
+ /**
14
+ * Symbol to mark secure clients
15
+ * This is attached to clients created by SecureSupabaseClient
16
+ */
17
+ export const SECURE_CLIENT_SYMBOL = Symbol('pace-core-secure-client');
18
+
19
+ /**
20
+ * Check if a Supabase client is a secure client (created via useSecureSupabase or createSecureClient)
21
+ *
22
+ * @param client - The Supabase client to check
23
+ * @returns true if the client is secure, false otherwise
24
+ *
25
+ * @example
26
+ * ```tsx
27
+ * import { isSecureClient } from '@jmruthers/pace-core/rbac/utils/clientSecurity';
28
+ *
29
+ * const supabase = useSecureSupabase();
30
+ * if (isSecureClient(supabase)) {
31
+ * // Client is secure, safe to use
32
+ * }
33
+ * ```
34
+ */
35
+ export function isSecureClient(client: SupabaseClient<Database> | null | undefined): boolean {
36
+ if (!client) return false;
37
+
38
+ // Check for the secure client symbol
39
+ return (client as any)[SECURE_CLIENT_SYMBOL] === true;
40
+ }
41
+
42
+ /**
43
+ * Warn about insecure client usage in development
44
+ *
45
+ * @param client - The client being used
46
+ * @param context - Context about where the client is being used (for better error messages)
47
+ *
48
+ * @example
49
+ * ```tsx
50
+ * import { warnIfInsecureClient } from '@jmruthers/pace-core/rbac/utils/clientSecurity';
51
+ *
52
+ * const supabase = createClient(...); // Wrong!
53
+ * warnIfInsecureClient(supabase, 'MyComponent');
54
+ * ```
55
+ */
56
+ export function warnIfInsecureClient(
57
+ client: SupabaseClient<Database> | null | undefined,
58
+ context?: string
59
+ ): void {
60
+ // Only warn in development
61
+ if (typeof process !== 'undefined' && process.env.NODE_ENV === 'production') {
62
+ return;
63
+ }
64
+
65
+ if (!client) return;
66
+
67
+ if (!isSecureClient(client)) {
68
+ const contextMsg = context ? ` in ${context}` : '';
69
+ console.warn(
70
+ `[pace-core Security Warning] Non-secure Supabase client detected${contextMsg}.\n` +
71
+ `You are using a Supabase client created with createClient() instead of useSecureSupabase().\n` +
72
+ `This bypasses organisation context enforcement and RLS policies, which can lead to:\n` +
73
+ `- Cross-organisation data access\n` +
74
+ `- Security vulnerabilities\n` +
75
+ `- Data leakage between organisations\n\n` +
76
+ `Fix: Replace with:\n` +
77
+ ` import { useSecureSupabase } from '@jmruthers/pace-core/rbac';\n` +
78
+ ` const supabase = useSecureSupabase();\n\n` +
79
+ `See: https://github.com/jmruthers/pace-core/blob/main/packages/core/docs/rbac/getting-started.md`
80
+ );
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Mark a client as secure (internal use only)
86
+ * This is called by SecureSupabaseClient to mark clients as secure
87
+ *
88
+ * @internal
89
+ */
90
+ export function markClientAsSecure(client: SupabaseClient<Database>): void {
91
+ (client as any)[SECURE_CLIENT_SYMBOL] = true;
92
+ }
93
+
@@ -22,9 +22,11 @@ import { createLogger } from '../../utils/core/logger';
22
22
 
23
23
  const log = createLogger('ContextValidator');
24
24
 
25
- export interface AppConfig {
26
- requires_event: boolean;
27
- }
25
+ /**
26
+ * Page scope type - determines what context is required for a page
27
+ * This is the single source of truth for page scoping.
28
+ */
29
+ export type PageScopeType = 'event' | 'organisation' | 'both';
28
30
 
29
31
  /**
30
32
  * Check if an app allows optional contexts (both organisation and event optional)
@@ -47,130 +49,79 @@ export interface ContextValidationResult {
47
49
  * Validates and resolves RBAC scope based on app configuration requirements.
48
50
  */
49
51
  export class ContextValidator {
52
+
50
53
  /**
51
- * Validate scope against app requirements
54
+ * Derive organisation ID from event ID
52
55
  *
53
- * @param scope - Current scope
54
- * @param appConfig - App configuration (requires_event flag)
55
- * @param appName - App name (for PORTAL/ADMIN special case)
56
- * @returns Validation result with resolved scope
56
+ * @param supabase - Supabase client
57
+ * @param eventId - Event ID
58
+ * @returns Organisation ID or null
57
59
  */
58
- static async validateScope(
59
- scope: Scope,
60
- appConfig: AppConfig | null,
61
- appName?: string
62
- ): Promise<ContextValidationResult> {
63
- // PORTAL/ADMIN special case: both contexts optional
64
- if (allowsOptionalContexts(appName)) {
65
- return {
66
- isValid: true,
67
- resolvedScope: {
68
- organisationId: scope.organisationId,
69
- eventId: scope.eventId,
70
- appId: scope.appId
71
- },
72
- error: null
73
- };
74
- }
75
-
76
- // If no app config, default to requiring org context
77
- if (!appConfig) {
78
- if (!scope.organisationId) {
79
- return {
80
- isValid: false,
81
- resolvedScope: null,
82
- error: new OrganisationContextRequiredError()
83
- };
84
- }
85
- return {
86
- isValid: true,
87
- resolvedScope: scope,
88
- error: null
89
- };
90
- }
91
-
92
- // Event-required apps: must have eventId, derive org from event
93
- if (appConfig.requires_event) {
94
- if (!scope.eventId) {
95
- return {
96
- isValid: false,
97
- resolvedScope: null,
98
- error: new EventContextRequiredError()
99
- };
100
- }
101
-
102
- // If org is not provided, we'll need to derive it from event
103
- // But for validation, we just check that eventId exists
104
- // The actual derivation happens in resolveRequiredContext
105
- return {
106
- isValid: true,
107
- resolvedScope: scope,
108
- error: null
109
- };
110
- }
111
-
112
- // Org-required apps: must have organisationId, eventId optional
113
- if (!scope.organisationId) {
114
- return {
115
- isValid: false,
116
- resolvedScope: null,
117
- error: new OrganisationContextRequiredError()
118
- };
119
- }
120
-
121
- return {
122
- isValid: true,
123
- resolvedScope: scope,
124
- error: null
125
- };
60
+ static async deriveOrgFromEvent(
61
+ supabase: SupabaseClient<Database>,
62
+ eventId: string
63
+ ): Promise<UUID | null> {
64
+ return getOrganisationFromEvent(supabase, eventId);
126
65
  }
127
66
 
128
67
  /**
129
- * Resolve required context and derive missing values
68
+ * Resolve scope based on page-level scope_type
69
+ *
70
+ * This method handles page-level scoping. All pages have explicit scope_type set.
71
+ * Used for hybrid apps like pace-mint that have both event and organisation pages.
130
72
  *
131
73
  * @param scope - Current scope
132
- * @param appConfig - App configuration
74
+ * @param pageScopeType - Page scope type ('event', 'organisation', or 'both')
133
75
  * @param appName - App name (for PORTAL/ADMIN special case)
134
76
  * @param supabase - Supabase client (for deriving org from event)
135
77
  * @returns Resolved scope with all required context
136
78
  */
137
- static async resolveRequiredContext(
79
+ static async resolveScopeForPage(
138
80
  scope: Scope,
139
- appConfig: AppConfig | null,
81
+ pageScopeType: PageScopeType,
140
82
  appName?: string,
141
83
  supabase?: SupabaseClient<Database> | null
142
84
  ): Promise<ContextValidationResult> {
143
- // PORTAL/ADMIN special case: both contexts optional
144
- if (allowsOptionalContexts(appName)) {
145
- return {
146
- isValid: true,
147
- resolvedScope: {
148
- organisationId: scope.organisationId,
149
- eventId: scope.eventId,
150
- appId: scope.appId
151
- },
152
- error: null
153
- };
154
- }
155
-
156
- // If no app config, default to requiring org context
157
- if (!appConfig) {
158
- if (!scope.organisationId) {
85
+ // Use page-level scope (single source of truth)
86
+ const effectiveScopeType = pageScopeType;
87
+
88
+ // Handle 'both' scope - requires both contexts available, but can use either
89
+ if (effectiveScopeType === 'both') {
90
+ // For 'both' pages, we need at least one context (org or event)
91
+ // Both will be checked during permission evaluation
92
+ if (!scope.organisationId && !scope.eventId) {
159
93
  return {
160
94
  isValid: false,
161
95
  resolvedScope: null,
162
- error: new OrganisationContextRequiredError()
96
+ error: new Error('Page requires either organisation or event context')
163
97
  };
164
98
  }
99
+
100
+ // Derive org from event if event is provided but org is not
101
+ let organisationId = scope.organisationId;
102
+ if (!organisationId && scope.eventId && supabase) {
103
+ try {
104
+ const derivedOrgId = await this.deriveOrgFromEvent(supabase, scope.eventId);
105
+ organisationId = derivedOrgId || undefined;
106
+ } catch (error) {
107
+ log.warn('Failed to derive org from event for both-scope page:', error);
108
+ // Continue without org - permission check will handle it
109
+ }
110
+ }
111
+
165
112
  return {
166
113
  isValid: true,
167
- resolvedScope: scope,
114
+ resolvedScope: {
115
+ organisationId,
116
+ eventId: scope.eventId,
117
+ appId: scope.appId
118
+ },
168
119
  error: null
169
120
  };
170
121
  }
171
-
172
- // Event-required apps: must have eventId, derive org from event
173
- if (appConfig.requires_event) {
122
+
123
+ // Handle 'event' scope - requires event context
124
+ if (effectiveScopeType === 'event') {
174
125
  if (!scope.eventId) {
175
126
  return {
176
127
  isValid: false,
@@ -178,7 +129,7 @@ export class ContextValidator {
178
129
  error: new EventContextRequiredError()
179
130
  };
180
131
  }
181
-
132
+
182
133
  // Derive organisationId from event if not provided
183
134
  let organisationId: UUID | undefined = scope.organisationId;
184
135
  if (!organisationId && supabase && scope.eventId) {
@@ -200,14 +151,8 @@ export class ContextValidator {
200
151
  error: error instanceof Error ? error : new Error('Failed to derive organisation from event')
201
152
  };
202
153
  }
203
- } else if (!organisationId) {
204
- return {
205
- isValid: false,
206
- resolvedScope: null,
207
- error: new Error('Event context requires organisationId but it could not be derived (supabase client not available)')
208
- };
209
154
  }
210
-
155
+
211
156
  return {
212
157
  isValid: true,
213
158
  resolvedScope: {
@@ -218,71 +163,35 @@ export class ContextValidator {
218
163
  error: null
219
164
  };
220
165
  }
221
-
222
- // Org-required apps: must have organisationId, eventId optional
223
- if (!scope.organisationId) {
166
+
167
+ // Handle 'organisation' scope - requires organisation context
168
+ if (effectiveScopeType === 'organisation') {
169
+ if (!scope.organisationId) {
170
+ return {
171
+ isValid: false,
172
+ resolvedScope: null,
173
+ error: new OrganisationContextRequiredError()
174
+ };
175
+ }
176
+
224
177
  return {
225
- isValid: false,
226
- resolvedScope: null,
227
- error: new OrganisationContextRequiredError()
178
+ isValid: true,
179
+ resolvedScope: {
180
+ organisationId: scope.organisationId,
181
+ eventId: scope.eventId, // Event is optional for org-scoped pages
182
+ appId: scope.appId
183
+ },
184
+ error: null
228
185
  };
229
186
  }
230
-
187
+
188
+ // Fallback (should not happen)
231
189
  return {
232
- isValid: true,
233
- resolvedScope: scope,
234
- error: null
190
+ isValid: false,
191
+ resolvedScope: null,
192
+ error: new Error('Invalid scope type')
235
193
  };
236
194
  }
237
195
 
238
- /**
239
- * Derive organisation ID from event ID
240
- *
241
- * @param supabase - Supabase client
242
- * @param eventId - Event ID
243
- * @returns Organisation ID or null
244
- */
245
- static async deriveOrgFromEvent(
246
- supabase: SupabaseClient<Database>,
247
- eventId: string
248
- ): Promise<UUID | null> {
249
- return getOrganisationFromEvent(supabase, eventId);
250
- }
251
-
252
- /**
253
- * Check if context is ready for permission checks
254
- *
255
- * @param scope - Current scope
256
- * @param appConfig - App configuration
257
- * @param appName - App name
258
- * @param hasSelectedEvent - Whether event is selected
259
- * @param hasSelectedOrganisation - Whether organisation is selected
260
- * @returns True if context is ready
261
- */
262
- static isContextReady(
263
- scope: Scope,
264
- appConfig: AppConfig | null,
265
- appName?: string,
266
- hasSelectedEvent?: boolean,
267
- hasSelectedOrganisation?: boolean
268
- ): boolean {
269
- // PORTAL/ADMIN special case: context is always ready
270
- if (allowsOptionalContexts(appName)) {
271
- return true;
272
- }
273
-
274
- // If no app config, default to requiring org context
275
- if (!appConfig) {
276
- return !!hasSelectedOrganisation || !!scope.organisationId;
277
- }
278
-
279
- // Event-required apps: need event context
280
- if (appConfig.requires_event) {
281
- return !!hasSelectedEvent || !!scope.eventId;
282
- }
283
-
284
- // Org-required apps: need org context
285
- return !!hasSelectedOrganisation || !!scope.organisationId;
286
- }
287
196
  }
288
197