@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,50 +1,96 @@
1
1
  import {
2
2
  useAppConfig,
3
3
  useEvents,
4
- useOrganisationSecurity,
5
- useResolvedScope
6
- } from "./chunk-3XTALGJF.js";
4
+ useOrganisationSecurity
5
+ } from "./chunk-6Z7LTB3D.js";
7
6
  import {
8
7
  useOrganisations,
9
8
  useUnifiedAuth
10
- } from "./chunk-BYFSK72L.js";
9
+ } from "./chunk-DWUBLJJM.js";
11
10
  import {
12
- ContextValidator,
13
- OrganisationContextRequiredError,
14
11
  getAccessLevel,
12
+ getPageScopeType,
15
13
  getPermissionMap,
16
14
  getRBACLogger,
17
15
  getRoleContext,
18
16
  isPermitted,
19
17
  isPermittedCached,
20
18
  resolveAppContext
21
- } from "./chunk-KNC55RTG.js";
19
+ } from "./chunk-RWEBCB47.js";
20
+ import {
21
+ ContextValidator,
22
+ OrganisationContextRequiredError
23
+ } from "./chunk-QRPVRXYT.js";
22
24
  import {
25
+ getCurrentAppName
26
+ } from "./chunk-M7MPQISP.js";
27
+ import {
28
+ createLogger,
23
29
  logger
24
30
  } from "./chunk-PWLANIRT.js";
25
31
 
32
+ // src/rbac/utils/clientSecurity.ts
33
+ var SECURE_CLIENT_SYMBOL = Symbol("pace-core-secure-client");
34
+ function isSecureClient(client) {
35
+ if (!client) return false;
36
+ return client[SECURE_CLIENT_SYMBOL] === true;
37
+ }
38
+ function warnIfInsecureClient(client, context) {
39
+ if (typeof process !== "undefined" && true) {
40
+ return;
41
+ }
42
+ if (!client) return;
43
+ if (!isSecureClient(client)) {
44
+ const contextMsg = context ? ` in ${context}` : "";
45
+ console.warn(
46
+ `[pace-core Security Warning] Non-secure Supabase client detected${contextMsg}.
47
+ You are using a Supabase client created with createClient() instead of useSecureSupabase().
48
+ This bypasses organisation context enforcement and RLS policies, which can lead to:
49
+ - Cross-organisation data access
50
+ - Security vulnerabilities
51
+ - Data leakage between organisations
52
+
53
+ Fix: Replace with:
54
+ import { useSecureSupabase } from '@jmruthers/pace-core/rbac';
55
+ const supabase = useSecureSupabase();
56
+
57
+ See: https://github.com/jmruthers/pace-core/blob/main/packages/core/docs/rbac/getting-started.md`
58
+ );
59
+ }
60
+ }
61
+ function markClientAsSecure(client) {
62
+ client[SECURE_CLIENT_SYMBOL] = true;
63
+ }
64
+
26
65
  // src/rbac/secureClient.ts
27
66
  import { createClient } from "@supabase/supabase-js";
28
- var SecureSupabaseClient = class _SecureSupabaseClient {
29
- constructor(supabaseUrl, supabaseKey, organisationId, eventId, appId, isSuperAdmin = false) {
67
+ var _SecureSupabaseClient = class _SecureSupabaseClient {
68
+ constructor(supabaseUrl, supabaseKey, organisationId, eventId, appId, isSuperAdmin = false, existingClient) {
30
69
  this.edgeFunctionClient = null;
70
+ this.usesExistingClient = false;
31
71
  this.supabaseUrl = supabaseUrl;
32
72
  this.supabaseKey = supabaseKey;
33
73
  this.organisationId = organisationId;
34
74
  this.eventId = eventId;
35
75
  this.appId = appId;
36
76
  this.isSuperAdmin = isSuperAdmin;
37
- this.supabase = createClient(supabaseUrl, supabaseKey, {
38
- global: {
39
- headers: {
40
- "x-organisation-id": organisationId,
41
- "x-event-id": eventId || "",
42
- "x-app-id": appId || ""
77
+ if (existingClient) {
78
+ this.supabase = existingClient;
79
+ this.usesExistingClient = true;
80
+ } else {
81
+ this.supabase = createClient(supabaseUrl, supabaseKey, {
82
+ global: {
83
+ headers: {
84
+ "x-organisation-id": organisationId || "",
85
+ "x-event-id": eventId || "",
86
+ "x-app-id": appId || ""
87
+ }
43
88
  }
44
- }
45
- });
89
+ });
90
+ }
46
91
  this.setupContextInjection();
47
92
  this.setupEdgeFunctionHandling();
93
+ markClientAsSecure(this.supabase);
48
94
  }
49
95
  /**
50
96
  * Setup context injection for all database operations
@@ -52,20 +98,26 @@ var SecureSupabaseClient = class _SecureSupabaseClient {
52
98
  setupContextInjection() {
53
99
  const originalFrom = this.supabase.from.bind(this.supabase);
54
100
  this.supabase.from = (table) => {
55
- this.validateContext();
101
+ this.validateContextForTable(table);
56
102
  const query = originalFrom(table);
57
103
  query._tableName = table;
58
104
  return this.injectContext(query, table);
59
105
  };
60
106
  const originalRpc = this.supabase.rpc.bind(this.supabase);
61
107
  this.supabase.rpc = (fn, args, options) => {
62
- this.validateContext();
63
- const contextArgs = {
64
- ...args,
65
- p_organisation_id: this.organisationId,
66
- p_event_id: this.eventId,
67
- p_app_id: this.appId
68
- };
108
+ this.validateContextForRpc(fn);
109
+ const acceptedParams = this.getRpcAcceptedParams(fn);
110
+ const safeArgs = args ?? {};
111
+ const contextArgs = { ...safeArgs };
112
+ if (acceptedParams.has("p_organisation_id") && this.organisationId && safeArgs.p_organisation_id === void 0) {
113
+ contextArgs.p_organisation_id = this.organisationId;
114
+ }
115
+ if (acceptedParams.has("p_event_id") && this.eventId && safeArgs.p_event_id === void 0) {
116
+ contextArgs.p_event_id = this.eventId;
117
+ }
118
+ if (acceptedParams.has("p_app_id") && this.appId && safeArgs.p_app_id === void 0) {
119
+ contextArgs.p_app_id = this.appId;
120
+ }
69
121
  return originalRpc(fn, contextArgs, options);
70
122
  };
71
123
  }
@@ -79,6 +131,10 @@ var SecureSupabaseClient = class _SecureSupabaseClient {
79
131
  * This avoids interfering with the main client's operations.
80
132
  */
81
133
  setupEdgeFunctionHandling() {
134
+ if (this.usesExistingClient) {
135
+ this.edgeFunctionClient = null;
136
+ return;
137
+ }
82
138
  this.edgeFunctionClient = createClient(this.supabaseUrl, this.supabaseKey);
83
139
  }
84
140
  /**
@@ -99,6 +155,7 @@ var SecureSupabaseClient = class _SecureSupabaseClient {
99
155
  const originalDelete = query.delete.bind(query);
100
156
  query.select = (columns) => {
101
157
  const result = originalSelect(columns);
158
+ result._tableName = tableName;
102
159
  return this.addOrganisationFilter(result, tableName);
103
160
  };
104
161
  query.insert = (values) => {
@@ -119,9 +176,18 @@ var SecureSupabaseClient = class _SecureSupabaseClient {
119
176
  if (this.isSuperAdmin) {
120
177
  return originalInsert(values);
121
178
  }
179
+ if (!this.organisationId) {
180
+ throw new OrganisationContextRequiredError();
181
+ }
122
182
  const contextValues2 = Array.isArray(values) ? values.map((v) => ({ ...v, organisation_id: this.organisationId })) : { ...values, organisation_id: this.organisationId };
123
183
  return originalInsert(contextValues2);
124
184
  }
185
+ if (this.isSuperAdmin && !this.organisationId) {
186
+ return originalInsert(values);
187
+ }
188
+ if (!this.organisationId) {
189
+ throw new OrganisationContextRequiredError();
190
+ }
125
191
  const contextValues = Array.isArray(values) ? values.map((v) => ({ ...v, organisation_id: this.organisationId })) : { ...values, organisation_id: this.organisationId };
126
192
  return originalInsert(contextValues);
127
193
  };
@@ -146,6 +212,10 @@ var SecureSupabaseClient = class _SecureSupabaseClient {
146
212
  * - Super admins: No org filter (see all users) - RLS will allow access
147
213
  * - Non-super-admins: Apply org filter as defense in depth - RLS will also filter
148
214
  *
215
+ * For system-wide tables (like core_organisations):
216
+ * - Super admins: No org filter (see all records) - RLS will allow access
217
+ * - Non-super-admins: Apply org filter as defense in depth - RLS will also filter
218
+ *
149
219
  * For other tables:
150
220
  * - Always apply org filter unless super admin bypasses it
151
221
  */
@@ -189,11 +259,18 @@ var SecureSupabaseClient = class _SecureSupabaseClient {
189
259
  if (!this.organisationId) {
190
260
  return query;
191
261
  }
262
+ const systemWideTablesForSuperAdmins = [
263
+ "core_organisations"
264
+ // Super-admins need to see all organisations
265
+ ];
266
+ if (systemWideTablesForSuperAdmins.includes(tableName) && this.isSuperAdmin) {
267
+ return query;
268
+ }
192
269
  if (tableName === "rbac_user_profiles") {
193
270
  if (this.isSuperAdmin) {
194
271
  return query;
195
272
  }
196
- return query.eq("organisation_id", this.organisationId);
273
+ return query.or(`organisation_id.eq.${this.organisationId},organisation_id.is.null`);
197
274
  }
198
275
  if (this.isSuperAdmin) {
199
276
  return query.eq("organisation_id", this.organisationId);
@@ -202,12 +279,57 @@ var SecureSupabaseClient = class _SecureSupabaseClient {
202
279
  }
203
280
  /**
204
281
  * Validate that required context is present
282
+ * Super-admins can operate without organisation context
205
283
  */
206
284
  validateContext() {
285
+ if (this.isSuperAdmin) {
286
+ return;
287
+ }
207
288
  if (!this.organisationId) {
208
289
  throw new OrganisationContextRequiredError();
209
290
  }
210
291
  }
292
+ /**
293
+ * Determine whether a table requires organisation context.
294
+ * Tables without an organisation_id column (or global configuration tables) are safe without org context.
295
+ */
296
+ tableRequiresOrganisationContext(tableName) {
297
+ const tablesWithoutOrganisationId = /* @__PURE__ */ new Set([
298
+ "core_organisations",
299
+ "rbac_apps",
300
+ "rbac_app_pages",
301
+ "rbac_global_roles",
302
+ "core_person",
303
+ "core_member",
304
+ "core_contact",
305
+ "core_consent",
306
+ "core_identification",
307
+ "core_qualification",
308
+ "medi_profile",
309
+ "medi_condition",
310
+ "medi_diet",
311
+ "medi_action_plan",
312
+ "medi_profile_versions"
313
+ ]);
314
+ return !tablesWithoutOrganisationId.has(tableName);
315
+ }
316
+ /**
317
+ * Validate context for a specific table operation.
318
+ */
319
+ validateContextForTable(tableName) {
320
+ if (this.isSuperAdmin) return;
321
+ if (!this.organisationId && this.tableRequiresOrganisationContext(tableName)) {
322
+ throw new OrganisationContextRequiredError();
323
+ }
324
+ }
325
+ /**
326
+ * Validate context for a specific RPC call.
327
+ */
328
+ validateContextForRpc(fn) {
329
+ if (this.isSuperAdmin) return;
330
+ if (_SecureSupabaseClient.GLOBAL_RPC_ALLOWLIST.has(fn)) return;
331
+ this.validateContext();
332
+ }
211
333
  /**
212
334
  * Get the current organisation ID
213
335
  */
@@ -233,7 +355,7 @@ var SecureSupabaseClient = class _SecureSupabaseClient {
233
355
  return new _SecureSupabaseClient(
234
356
  this.supabaseUrl,
235
357
  this.supabaseKey,
236
- updates.organisationId || this.organisationId,
358
+ updates.organisationId !== void 0 ? updates.organisationId : this.organisationId,
237
359
  updates.eventId !== void 0 ? updates.eventId : this.eventId,
238
360
  updates.appId !== void 0 ? updates.appId : this.appId,
239
361
  updates.isSuperAdmin !== void 0 ? updates.isSuperAdmin : this.isSuperAdmin
@@ -244,7 +366,7 @@ var SecureSupabaseClient = class _SecureSupabaseClient {
244
366
  * @internal
245
367
  */
246
368
  getClient() {
247
- return new Proxy(this.supabase, {
369
+ const proxiedClient = new Proxy(this.supabase, {
248
370
  get: (target, prop) => {
249
371
  if (prop === "functions" && this.edgeFunctionClient) {
250
372
  return this.edgeFunctionClient.functions;
@@ -252,17 +374,248 @@ var SecureSupabaseClient = class _SecureSupabaseClient {
252
374
  return target[prop];
253
375
  }
254
376
  });
377
+ markClientAsSecure(proxiedClient);
378
+ return proxiedClient;
379
+ }
380
+ /**
381
+ * Get the set of parameter names that an RPC function accepts.
382
+ * Uses a static whitelist of RPCs that we know accept context parameters.
383
+ *
384
+ * This is an opt-in approach: by default, we don't inject context unless
385
+ * the function is explicitly whitelisted. This prevents PGRST202 errors from
386
+ * injecting unexpected parameters.
387
+ *
388
+ * @param fn - The RPC function name
389
+ * @returns Set of parameter names the function accepts
390
+ */
391
+ getRpcAcceptedParams(fn) {
392
+ if (_SecureSupabaseClient.rpcSignatureCache.has(fn)) {
393
+ return _SecureSupabaseClient.rpcSignatureCache.get(fn);
394
+ }
395
+ const rpcContextWhitelist = {
396
+ // RPCs that accept all three context parameters
397
+ "rbac_roles_list": /* @__PURE__ */ new Set(["p_organisation_id", "p_event_id", "p_app_id"]),
398
+ // RPCs that accept only p_organisation_id (not p_app_id or p_event_id)
399
+ "data_file_reference_by_category_list": /* @__PURE__ */ new Set(["p_organisation_id"])
400
+ // Add more RPCs here as we discover them
401
+ // Format: 'function_name': new Set(['p_organisation_id', 'p_event_id', 'p_app_id']),
402
+ };
403
+ const acceptedParams = rpcContextWhitelist[fn] || /* @__PURE__ */ new Set();
404
+ _SecureSupabaseClient.rpcSignatureCache.set(fn, acceptedParams);
405
+ return acceptedParams;
255
406
  }
256
407
  };
408
+ // Cache for RPC function signatures to avoid repeated database queries
409
+ // Maps function name -> Set of parameter names it accepts
410
+ _SecureSupabaseClient.rpcSignatureCache = /* @__PURE__ */ new Map();
411
+ /**
412
+ * RPC functions that are safe to call without organisation context.
413
+ *
414
+ * These functions must:
415
+ * - rely on JWT context (auth.uid()) for authentication
416
+ * - not read or write organisation-scoped data
417
+ *
418
+ * This allowlist enables compliant consuming apps to use `secureSupabase.rpc(...)`
419
+ * even before an organisation is selected (common during initial page load/refresh).
420
+ */
421
+ _SecureSupabaseClient.GLOBAL_RPC_ALLOWLIST = /* @__PURE__ */ new Set([
422
+ "data_rbac_apps_list"
423
+ ]);
424
+ var SecureSupabaseClient = _SecureSupabaseClient;
257
425
  function createSecureClient(supabaseUrl, supabaseKey, organisationId, eventId, appId, isSuperAdmin = false) {
258
426
  return new SecureSupabaseClient(supabaseUrl, supabaseKey, organisationId, eventId, appId, isSuperAdmin);
259
427
  }
260
- function fromSupabaseClient(client, organisationId, eventId, appId) {
261
- throw new Error("fromSupabaseClient is not supported. Use createSecureClient instead.");
428
+ function fromSupabaseClient(client, organisationId, eventId, appId, isSuperAdmin = false) {
429
+ return new SecureSupabaseClient("", "", organisationId, eventId, appId, isSuperAdmin, client);
430
+ }
431
+
432
+ // src/rbac/hooks/useResolvedScope.ts
433
+ import { useEffect, useState, useRef, useMemo } from "react";
434
+ var log = createLogger("useResolvedScope");
435
+ var appIdCache = /* @__PURE__ */ new Map();
436
+ var CACHE_TTL = 5 * 60 * 1e3;
437
+ function useResolvedScope({
438
+ supabase,
439
+ selectedOrganisationId,
440
+ selectedEventId
441
+ }) {
442
+ const [resolvedScope, setResolvedScope] = useState(null);
443
+ const [isLoading, setIsLoading] = useState(true);
444
+ const [error, setError] = useState(null);
445
+ const stableScopeRef = useRef({
446
+ organisationId: "",
447
+ appId: "",
448
+ eventId: void 0
449
+ });
450
+ useEffect(() => {
451
+ if (resolvedScope) {
452
+ const newScope = {
453
+ organisationId: resolvedScope.organisationId || "",
454
+ appId: resolvedScope.appId || "",
455
+ eventId: resolvedScope.eventId
456
+ };
457
+ if (stableScopeRef.current.organisationId !== newScope.organisationId || stableScopeRef.current.eventId !== newScope.eventId || stableScopeRef.current.appId !== newScope.appId) {
458
+ stableScopeRef.current = {
459
+ organisationId: newScope.organisationId,
460
+ appId: newScope.appId,
461
+ eventId: newScope.eventId
462
+ };
463
+ }
464
+ } else {
465
+ stableScopeRef.current = { organisationId: "", appId: "", eventId: void 0 };
466
+ }
467
+ }, [resolvedScope]);
468
+ const appName = getCurrentAppName();
469
+ const stableScope = stableScopeRef.current;
470
+ useEffect(() => {
471
+ let cancelled = false;
472
+ const resolveScope = async () => {
473
+ if (!supabase && !selectedOrganisationId && !selectedEventId) {
474
+ if (!cancelled) {
475
+ setResolvedScope(null);
476
+ setIsLoading(false);
477
+ setError(null);
478
+ }
479
+ return;
480
+ }
481
+ setIsLoading(true);
482
+ setError(null);
483
+ try {
484
+ const appName2 = getCurrentAppName();
485
+ let appId = void 0;
486
+ if (supabase && appName2) {
487
+ try {
488
+ const { data: session } = await supabase.auth.getSession();
489
+ if (!session?.session) {
490
+ log.debug(`Skipping app resolution for "${appName2}" - user not authenticated`);
491
+ } else {
492
+ const cached = appIdCache.get(appName2);
493
+ const now = Date.now();
494
+ if (cached && now - cached.timestamp < CACHE_TTL) {
495
+ appId = cached.appId;
496
+ } else {
497
+ const { data: app, error: error2 } = await supabase.from("rbac_apps").select("id, name, is_active").eq("name", appName2).eq("is_active", true).single();
498
+ if (error2) {
499
+ if (error2.code === "406" || error2.code === "PGRST116" || error2.message?.includes("406")) {
500
+ log.debug(`App resolution blocked by RLS for "${appName2}" - user may not be authenticated`);
501
+ appId = void 0;
502
+ } else {
503
+ const { data: inactiveApp } = await supabase.from("rbac_apps").select("id, name, is_active").eq("name", appName2).single();
504
+ if (inactiveApp) {
505
+ log.error(`App "${appName2}" exists but is inactive (is_active: ${inactiveApp.is_active})`);
506
+ appId = void 0;
507
+ } else {
508
+ log.error(`App "${appName2}" not found in rbac_apps table`, { error: error2 });
509
+ appId = void 0;
510
+ }
511
+ }
512
+ } else if (app) {
513
+ appId = app.id;
514
+ appIdCache.set(appName2, { appId, timestamp: now });
515
+ }
516
+ }
517
+ }
518
+ } catch (error2) {
519
+ const errorMessage = error2 instanceof Error ? error2.message : String(error2);
520
+ if (!errorMessage.includes("406") && !errorMessage.includes("PGRST116")) {
521
+ log.error("Unexpected error resolving app ID:", error2);
522
+ } else {
523
+ log.debug("App resolution skipped - authentication required");
524
+ }
525
+ }
526
+ }
527
+ const initialScope = {
528
+ organisationId: selectedOrganisationId || void 0,
529
+ eventId: selectedEventId || void 0,
530
+ appId
531
+ };
532
+ if (appName2 === "PORTAL" || appName2 === "ADMIN") {
533
+ if (!cancelled) {
534
+ const optionalContextScope = {
535
+ organisationId: void 0,
536
+ eventId: void 0,
537
+ appId: appId || void 0
538
+ };
539
+ setResolvedScope(optionalContextScope);
540
+ setError(null);
541
+ setIsLoading(false);
542
+ }
543
+ return;
544
+ }
545
+ const { ContextValidator: ContextValidator2 } = await import("./contextValidator-3JNZKUTX.js");
546
+ const validation = await ContextValidator2.resolveScopeForPage(
547
+ initialScope,
548
+ "organisation",
549
+ // Default to organisation scope when no page context
550
+ appName2 || void 0,
551
+ supabase
552
+ );
553
+ if (!validation.isValid) {
554
+ if (selectedEventId) {
555
+ if (!cancelled) {
556
+ const eventScope = {
557
+ organisationId: void 0,
558
+ // Will be derived from event during permission check
559
+ eventId: selectedEventId,
560
+ appId: appId || void 0
561
+ };
562
+ setResolvedScope(eventScope);
563
+ setError(null);
564
+ setIsLoading(false);
565
+ }
566
+ return;
567
+ }
568
+ if (!cancelled) {
569
+ setResolvedScope(null);
570
+ setError(validation.error || new Error("Context validation failed"));
571
+ setIsLoading(false);
572
+ }
573
+ return;
574
+ }
575
+ if (!cancelled) {
576
+ setResolvedScope(validation.resolvedScope);
577
+ setError(null);
578
+ setIsLoading(false);
579
+ }
580
+ } catch (err) {
581
+ if (!cancelled) {
582
+ setError(err);
583
+ setIsLoading(false);
584
+ }
585
+ }
586
+ };
587
+ resolveScope();
588
+ return () => {
589
+ cancelled = true;
590
+ };
591
+ }, [selectedOrganisationId, selectedEventId, supabase]);
592
+ const allowsOptionalContexts = appName === "PORTAL" || appName === "ADMIN";
593
+ const hasValidScope = allowsOptionalContexts ? true : stableScope.appId || stableScope.organisationId;
594
+ const finalScope = useMemo(() => {
595
+ if (!hasValidScope) {
596
+ return allowsOptionalContexts ? {} : null;
597
+ }
598
+ const scope = {};
599
+ if (stableScope.organisationId) {
600
+ scope.organisationId = stableScope.organisationId;
601
+ }
602
+ if (stableScope.eventId) {
603
+ scope.eventId = stableScope.eventId;
604
+ }
605
+ if (stableScope.appId) {
606
+ scope.appId = stableScope.appId;
607
+ }
608
+ return scope;
609
+ }, [hasValidScope, allowsOptionalContexts, stableScope.organisationId, stableScope.eventId, stableScope.appId]);
610
+ return {
611
+ resolvedScope: finalScope,
612
+ isLoading,
613
+ error
614
+ };
262
615
  }
263
616
 
264
617
  // src/rbac/hooks/useRBAC.ts
265
- import { useState, useEffect, useCallback, useMemo } from "react";
618
+ import { useState as useState2, useEffect as useEffect2, useCallback, useMemo as useMemo2 } from "react";
266
619
  function mapAccessLevelToEventRole(level) {
267
620
  switch (level) {
268
621
  case "viewer":
@@ -285,7 +638,6 @@ function useRBAC(pageId) {
285
638
  session,
286
639
  supabase,
287
640
  appName,
288
- appConfig,
289
641
  appId: contextAppId,
290
642
  selectedOrganisation,
291
643
  isContextReady: orgContextReady,
@@ -293,13 +645,13 @@ function useRBAC(pageId) {
293
645
  selectedEvent,
294
646
  eventLoading
295
647
  } = useUnifiedAuth();
296
- const [globalRole, setGlobalRole] = useState(null);
297
- const [organisationRole, setOrganisationRole] = useState(null);
298
- const [eventAppRole, setEventAppRole] = useState(null);
299
- const [permissionMap, setPermissionMap] = useState({});
300
- const [currentScope, setCurrentScope] = useState(null);
301
- const [isLoading, setIsLoading] = useState(false);
302
- const [error, setError] = useState(null);
648
+ const [globalRole, setGlobalRole] = useState2(null);
649
+ const [organisationRole, setOrganisationRole] = useState2(null);
650
+ const [eventAppRole, setEventAppRole] = useState2(null);
651
+ const [permissionMap, setPermissionMap] = useState2({});
652
+ const [currentScope, setCurrentScope] = useState2(null);
653
+ const [isLoading, setIsLoading] = useState2(false);
654
+ const [error, setError] = useState2(null);
303
655
  const resetState = useCallback(() => {
304
656
  setGlobalRole(null);
305
657
  setOrganisationRole(null);
@@ -314,20 +666,15 @@ function useRBAC(pageId) {
314
666
  return;
315
667
  }
316
668
  const initialScope = {
317
- organisationId: appConfig?.requires_event ? selectedEvent?.organisation_id || selectedOrganisation?.id : selectedOrganisation?.id,
669
+ organisationId: selectedEvent?.organisation_id || selectedOrganisation?.id || void 0,
318
670
  eventId: selectedEvent?.event_id || void 0,
319
671
  appId: void 0
320
672
  };
321
- const contextReady = ContextValidator.isContextReady(
322
- initialScope,
323
- appConfig,
324
- appName,
325
- !!selectedEvent,
326
- !!selectedOrganisation
327
- );
328
- if (appName !== "PORTAL" && appName !== "ADMIN" && !contextReady) {
329
- setIsLoading(true);
330
- return;
673
+ if (appName !== "PORTAL" && appName !== "ADMIN") {
674
+ if (!selectedOrganisation && !selectedEvent) {
675
+ setIsLoading(true);
676
+ return;
677
+ }
331
678
  }
332
679
  setIsLoading(true);
333
680
  setError(null);
@@ -338,11 +685,6 @@ function useRBAC(pageId) {
338
685
  const resolved = await resolveAppContext({ userId: user.id, appName });
339
686
  if (!resolved) {
340
687
  if (appName === "PORTAL" || appName === "ADMIN") {
341
- try {
342
- const { getAppConfigByName } = await import("./api-N774RPUA.js");
343
- await getAppConfigByName(appName);
344
- } catch (err) {
345
- }
346
688
  } else {
347
689
  throw new Error(`User does not have access to app "${appName}"`);
348
690
  }
@@ -372,9 +714,20 @@ function useRBAC(pageId) {
372
714
  ...initialScope,
373
715
  appId: appId || contextAppId
374
716
  };
375
- const validation = await ContextValidator.resolveRequiredContext(
717
+ let pageScopeType = "organisation";
718
+ if (pageId && scope.appId) {
719
+ try {
720
+ pageScopeType = await getPageScopeType(pageId, scope.appId, appName);
721
+ } catch (error2) {
722
+ logger2.warn("[useRBAC] Failed to get page scope type, defaulting to organisation", {
723
+ pageId,
724
+ error: error2 instanceof Error ? error2.message : String(error2)
725
+ });
726
+ }
727
+ }
728
+ const validation = await ContextValidator.resolveScopeForPage(
376
729
  scope,
377
- appConfig,
730
+ pageScopeType,
378
731
  appName,
379
732
  supabase || null
380
733
  );
@@ -384,9 +737,9 @@ function useRBAC(pageId) {
384
737
  const resolvedScope = validation.resolvedScope;
385
738
  setCurrentScope(resolvedScope);
386
739
  const [map, roleContext, accessLevel] = await Promise.all([
387
- getPermissionMap({ userId: user.id, scope: resolvedScope }, appConfig, appName),
388
- getRoleContext({ userId: user.id, scope: resolvedScope }, appConfig, appName),
389
- getAccessLevel({ userId: user.id, scope: resolvedScope }, appConfig, appName)
740
+ getPermissionMap({ userId: user.id, scope: resolvedScope }),
741
+ getRoleContext({ userId: user.id, scope: resolvedScope }),
742
+ getAccessLevel({ userId: user.id, scope: resolvedScope })
390
743
  ]);
391
744
  setPermissionMap(map);
392
745
  setGlobalRole(roleContext.globalRole);
@@ -408,7 +761,7 @@ function useRBAC(pageId) {
408
761
  } finally {
409
762
  setIsLoading(false);
410
763
  }
411
- }, [appName, logger2, resetState, selectedEvent?.event_id, selectedOrganisation?.id, session, user, eventLoading, appConfig, orgContextReady, orgLoading]);
764
+ }, [appName, logger2, resetState, selectedEvent?.event_id, selectedOrganisation?.id, session, user, eventLoading, orgContextReady, orgLoading]);
412
765
  const hasGlobalPermission = useCallback(
413
766
  (permission) => {
414
767
  if (globalRole === "super_admin" || permissionMap["*"]) {
@@ -424,14 +777,14 @@ function useRBAC(pageId) {
424
777
  },
425
778
  [globalRole, organisationRole, permissionMap]
426
779
  );
427
- const isSuperAdmin = useMemo(() => globalRole === "super_admin" || permissionMap["*"] === true, [globalRole, permissionMap]);
428
- const isOrgAdmin = useMemo(() => organisationRole === "org_admin" || isSuperAdmin, [organisationRole, isSuperAdmin]);
429
- const isEventAdmin = useMemo(() => eventAppRole === "event_admin" || isSuperAdmin, [eventAppRole, isSuperAdmin]);
430
- const canManageOrganisation = useMemo(() => isSuperAdmin || organisationRole === "org_admin", [isSuperAdmin, organisationRole]);
431
- const canManageEvent = useMemo(() => isSuperAdmin || eventAppRole === "event_admin", [isSuperAdmin, eventAppRole]);
432
- useEffect(() => {
780
+ const isSuperAdmin = useMemo2(() => globalRole === "super_admin" || permissionMap["*"] === true, [globalRole, permissionMap]);
781
+ const isOrgAdmin = useMemo2(() => organisationRole === "org_admin" || isSuperAdmin, [organisationRole, isSuperAdmin]);
782
+ const isEventAdmin = useMemo2(() => eventAppRole === "event_admin" || isSuperAdmin, [eventAppRole, isSuperAdmin]);
783
+ const canManageOrganisation = useMemo2(() => isSuperAdmin || organisationRole === "org_admin", [isSuperAdmin, organisationRole]);
784
+ const canManageEvent = useMemo2(() => isSuperAdmin || eventAppRole === "event_admin", [isSuperAdmin, eventAppRole]);
785
+ useEffect2(() => {
433
786
  loadRBACContext();
434
- }, [loadRBACContext, appName, appConfig, eventLoading, selectedEvent?.event_id, user, session, selectedOrganisation?.id, orgContextReady, orgLoading]);
787
+ }, [loadRBACContext, appName, eventLoading, selectedEvent?.event_id, user, session, selectedOrganisation?.id, orgContextReady, orgLoading]);
435
788
  return {
436
789
  user,
437
790
  globalRole,
@@ -448,197 +801,191 @@ function useRBAC(pageId) {
448
801
  };
449
802
  }
450
803
 
451
- // src/rbac/hooks/usePermissions.ts
452
- import { useState as useState2, useEffect as useEffect2, useCallback as useCallback2, useMemo as useMemo2, useRef } from "react";
453
-
454
- // src/rbac/utils/deep-equal.ts
455
- function scopeEqual(a, b) {
456
- if (a === b) {
457
- return true;
458
- }
459
- if (a == null || b == null) {
460
- return a === b;
804
+ // src/rbac/hooks/permissions/useAccessLevel.ts
805
+ import { useCallback as useCallback2, useEffect as useEffect3, useMemo as useMemo3, useState as useState3 } from "react";
806
+ function useAccessLevel(userId, scope) {
807
+ const [accessLevel, setAccessLevel] = useState3("viewer");
808
+ const [isLoading, setIsLoading] = useState3(true);
809
+ const [error, setError] = useState3(null);
810
+ let appName;
811
+ try {
812
+ const { appName: contextAppName } = useAppConfig();
813
+ appName = contextAppName;
814
+ } catch {
461
815
  }
462
- return a.organisationId === b.organisationId && a.eventId === b.eventId && a.appId === b.appId;
463
- }
464
-
465
- // src/rbac/hooks/usePermissions.ts
466
- function usePermissions(userId, organisationId, eventId, appId) {
467
- const [permissions, setPermissions] = useState2({});
468
- const [isLoading, setIsLoading] = useState2(true);
469
- const [error, setError] = useState2(null);
470
- const [fetchTrigger, setFetchTrigger] = useState2(0);
471
- const isFetchingRef = useRef(false);
472
- const logger2 = getRBACLogger();
473
- const prevValuesRef = useRef({ userId, organisationId, eventId, appId });
474
- const orgId = organisationId || "";
475
- useEffect2(() => {
816
+ const fetchAccessLevel = useCallback2(async () => {
476
817
  if (!userId) {
818
+ setAccessLevel("viewer");
819
+ setIsLoading(false);
477
820
  return;
478
821
  }
479
- if (!orgId || orgId === null || typeof orgId === "string" && orgId.trim() === "") {
480
- const timeoutId = setTimeout(() => {
481
- setError(new Error("Organisation context is required for permission checks"));
482
- setIsLoading(false);
483
- }, 3e3);
484
- return () => clearTimeout(timeoutId);
485
- }
486
- if (error?.message === "Organisation context is required for permission checks") {
822
+ try {
823
+ setIsLoading(true);
487
824
  setError(null);
488
- }
489
- }, [userId, organisationId, error, orgId]);
490
- useEffect2(() => {
491
- const paramsChanged = prevValuesRef.current.userId !== userId || prevValuesRef.current.organisationId !== organisationId || prevValuesRef.current.eventId !== eventId || prevValuesRef.current.appId !== appId;
492
- if (paramsChanged) {
493
- if (prevValuesRef.current.appId !== appId) {
494
- }
495
- prevValuesRef.current = { userId, organisationId, eventId, appId };
496
- setFetchTrigger((prev) => prev + 1);
497
- }
498
- }, [userId, organisationId, eventId, appId, logger2]);
499
- useEffect2(() => {
500
- const fetchPermissions = async () => {
501
- if (isFetchingRef.current) {
502
- return;
503
- }
504
- if (!userId) {
505
- setPermissions({});
825
+ const { isSuperAdmin: checkSuperAdmin } = await import("./api-IAGWF3ZG.js");
826
+ const isSuperAdminUser = await checkSuperAdmin(userId);
827
+ if (isSuperAdminUser) {
828
+ setAccessLevel("super");
506
829
  setIsLoading(false);
507
830
  return;
508
831
  }
509
- if (!userId) {
510
- setPermissions({});
832
+ if (appName !== "PORTAL" && appName !== "ADMIN" && !scope.organisationId && !scope.eventId) {
833
+ const orgError = new OrganisationContextRequiredError();
834
+ setError(orgError);
835
+ setAccessLevel("viewer");
511
836
  setIsLoading(false);
512
837
  return;
513
838
  }
514
- if (!orgId || orgId === null || typeof orgId === "string" && orgId.trim() === "") {
515
- setIsLoading(true);
516
- setError(null);
517
- return;
518
- }
519
- try {
520
- isFetchingRef.current = true;
521
- setIsLoading(true);
522
- setError(null);
523
- const scope = {
524
- organisationId: orgId,
525
- eventId,
526
- appId
527
- };
528
- const permissionMap = await getPermissionMap({ userId, scope });
529
- const permissionCount = Object.keys(permissionMap).length;
530
- if (permissionCount === 0 && Object.keys(permissions).length > 0) {
531
- logger2.warn("[usePermissions] Permissions fetched but returned empty map", {
532
- scope: { organisationId: orgId, eventId, appId }
533
- });
534
- }
535
- setPermissions(permissionMap);
536
- } catch (err) {
537
- logger2.error("[usePermissions] Failed to fetch permissions:", err);
538
- setError(err instanceof Error ? err : new Error("Failed to fetch permissions"));
539
- } finally {
540
- setIsLoading(false);
541
- isFetchingRef.current = false;
542
- }
543
- };
544
- fetchPermissions();
545
- }, [fetchTrigger, userId, organisationId, eventId, appId]);
546
- const hasPermission = useCallback2((permission) => {
547
- if (permissions["*"]) {
548
- return true;
549
- }
550
- return permissions[permission] === true;
551
- }, [permissions]);
552
- const hasAnyPermission = useCallback2((permissionList) => {
553
- if (permissions["*"]) {
554
- return true;
555
- }
556
- return permissionList.some((p) => permissions[p] === true);
557
- }, [permissions]);
558
- const hasAllPermissions = useCallback2((permissionList) => {
559
- if (permissions["*"]) {
560
- return true;
561
- }
562
- return permissionList.every((p) => permissions[p] === true);
563
- }, [permissions]);
564
- const refetch = useCallback2(async () => {
565
- if (isFetchingRef.current) {
566
- return;
839
+ const level = await getAccessLevel({ userId, scope }, appName);
840
+ setAccessLevel(level);
841
+ } catch (err) {
842
+ const error2 = err instanceof Error ? err : new Error("Failed to fetch access level");
843
+ setError(error2);
844
+ setAccessLevel("viewer");
845
+ } finally {
846
+ setIsLoading(false);
567
847
  }
848
+ }, [userId, scope.organisationId, scope.eventId, scope.appId, appName]);
849
+ useEffect3(() => {
850
+ fetchAccessLevel();
851
+ }, [fetchAccessLevel]);
852
+ return useMemo3(() => ({
853
+ accessLevel,
854
+ isLoading,
855
+ error,
856
+ refetch: fetchAccessLevel
857
+ }), [accessLevel, isLoading, error, fetchAccessLevel]);
858
+ }
859
+
860
+ // src/rbac/hooks/permissions/useCachedPermissions.ts
861
+ import { useCallback as useCallback3, useEffect as useEffect4, useMemo as useMemo4, useState as useState4 } from "react";
862
+ function useCachedPermissions(userId, scope) {
863
+ const [permissions, setPermissions] = useState4({});
864
+ const [isLoading, setIsLoading] = useState4(true);
865
+ const [error, setError] = useState4(null);
866
+ const fetchCachedPermissions = useCallback3(async () => {
568
867
  if (!userId) {
569
868
  setPermissions({});
570
869
  setIsLoading(false);
571
870
  return;
572
871
  }
573
- if (!orgId || orgId === null || typeof orgId === "string" && orgId.trim() === "") {
574
- setIsLoading(true);
575
- setError(null);
576
- return;
577
- }
578
872
  try {
579
- isFetchingRef.current = true;
580
873
  setIsLoading(true);
581
874
  setError(null);
582
- const scope = {
583
- organisationId: orgId,
584
- eventId,
585
- appId
586
- };
587
875
  const permissionMap = await getPermissionMap({ userId, scope });
588
876
  setPermissions(permissionMap);
589
877
  } catch (err) {
590
- const logger3 = getRBACLogger();
591
- logger3.error("Failed to refetch permissions:", err);
592
- setError(err instanceof Error ? err : new Error("Failed to fetch permissions"));
878
+ setError(err instanceof Error ? err : new Error("Failed to fetch cached permissions"));
593
879
  } finally {
594
880
  setIsLoading(false);
595
- isFetchingRef.current = false;
596
881
  }
597
- }, [userId, organisationId, eventId, appId]);
598
- return useMemo2(() => ({
882
+ }, [userId, scope.organisationId, scope.eventId, scope.appId]);
883
+ const invalidateCache = useCallback3(() => {
884
+ fetchCachedPermissions();
885
+ }, [fetchCachedPermissions]);
886
+ useEffect4(() => {
887
+ fetchCachedPermissions();
888
+ }, [fetchCachedPermissions]);
889
+ return useMemo4(() => ({
599
890
  permissions,
600
891
  isLoading,
601
892
  error,
602
- hasPermission,
603
- hasAnyPermission,
604
- hasAllPermissions,
605
- refetch
606
- }), [permissions, isLoading, error, hasPermission, hasAnyPermission, hasAllPermissions, refetch]);
893
+ invalidateCache,
894
+ refetch: fetchCachedPermissions
895
+ }), [permissions, isLoading, error, invalidateCache, fetchCachedPermissions]);
607
896
  }
608
- function useCan(userId, scope, permission, pageId, useCache = true, appName) {
609
- const [can, setCan] = useState2(false);
610
- const [isLoading, setIsLoading] = useState2(true);
611
- const [error, setError] = useState2(null);
612
- const [isSuperAdmin, setIsSuperAdmin] = useState2(null);
897
+
898
+ // src/rbac/hooks/permissions/useCan.ts
899
+ import { useCallback as useCallback4, useEffect as useEffect5, useMemo as useMemo5, useRef as useRef2, useState as useState5 } from "react";
900
+
901
+ // src/rbac/utils/deep-equal.ts
902
+ function scopeEqual(a, b) {
903
+ if (a === b) {
904
+ return true;
905
+ }
906
+ if (a == null || b == null) {
907
+ return a === b;
908
+ }
909
+ return a.organisationId === b.organisationId && a.eventId === b.eventId && a.appId === b.appId;
910
+ }
911
+
912
+ // src/rbac/hooks/permissions/useCan.ts
913
+ function useCan(userId, scope, permission, pageId, useCache = true, precomputedSuperAdmin = null, appName) {
914
+ const [isSuperAdmin, setIsSuperAdmin] = useState5(precomputedSuperAdmin ?? null);
915
+ const initialCan = precomputedSuperAdmin === true ? true : false;
916
+ const initialIsLoading = precomputedSuperAdmin === true ? false : true;
917
+ const [can, setCan] = useState5(initialCan);
918
+ const [isLoading, setIsLoading] = useState5(initialIsLoading);
919
+ const [error, setError] = useState5(null);
613
920
  const isValidScope = scope && typeof scope === "object";
614
921
  const organisationId = isValidScope ? scope.organisationId : void 0;
615
922
  const eventId = isValidScope ? scope.eventId : void 0;
616
923
  const appId = isValidScope ? scope.appId : void 0;
617
- useEffect2(() => {
618
- if (!userId) {
924
+ useEffect5(() => {
925
+ if (precomputedSuperAdmin === true && isSuperAdmin !== true) {
926
+ setIsSuperAdmin(true);
927
+ setCan(true);
928
+ setIsLoading(false);
929
+ setError(null);
930
+ } else if (precomputedSuperAdmin === false && isSuperAdmin !== false) {
619
931
  setIsSuperAdmin(false);
620
- return;
621
932
  }
622
- let cancelled = false;
623
- const checkSuperAdmin = async () => {
624
- try {
625
- const { isSuperAdmin: checkSuperAdmin2 } = await import("./api-N774RPUA.js");
626
- const isSuper = await checkSuperAdmin2(userId);
627
- if (!cancelled) {
628
- setIsSuperAdmin(isSuper);
629
- }
630
- } catch (err) {
631
- if (!cancelled) {
632
- setIsSuperAdmin(false);
633
- }
933
+ }, [precomputedSuperAdmin, isSuperAdmin]);
934
+ useEffect5(() => {
935
+ if (precomputedSuperAdmin === null) {
936
+ if (!userId) {
937
+ setIsSuperAdmin(false);
938
+ return;
634
939
  }
635
- };
636
- checkSuperAdmin();
637
- return () => {
638
- cancelled = true;
639
- };
640
- }, [userId]);
641
- useEffect2(() => {
940
+ let cancelled = false;
941
+ const checkSuperAdmin = async () => {
942
+ const startTime = Date.now();
943
+ try {
944
+ const { isSuperAdmin: checkSuperAdmin2 } = await import("./api-IAGWF3ZG.js");
945
+ const timeoutWarning = setTimeout(() => {
946
+ if (!cancelled) {
947
+ console.warn("[useCan] Super admin check taking longer than 5 seconds", {
948
+ userId,
949
+ elapsedMs: Date.now() - startTime
950
+ });
951
+ }
952
+ }, 5e3);
953
+ const isSuper = await checkSuperAdmin2(userId);
954
+ clearTimeout(timeoutWarning);
955
+ if (!cancelled) {
956
+ const elapsed = Date.now() - startTime;
957
+ if (elapsed > 1e3) {
958
+ console.warn("[useCan] Super admin check took longer than expected", {
959
+ userId,
960
+ elapsedMs: elapsed
961
+ });
962
+ }
963
+ setIsSuperAdmin(isSuper);
964
+ if (isSuper) {
965
+ setCan(true);
966
+ setIsLoading(false);
967
+ setError(null);
968
+ }
969
+ }
970
+ } catch (err) {
971
+ if (!cancelled) {
972
+ const elapsed = Date.now() - startTime;
973
+ console.error("[useCan] Error checking super admin", {
974
+ userId,
975
+ error: err,
976
+ elapsedMs: elapsed
977
+ });
978
+ setIsSuperAdmin(false);
979
+ }
980
+ }
981
+ };
982
+ checkSuperAdmin();
983
+ return () => {
984
+ cancelled = true;
985
+ };
986
+ }
987
+ }, [userId, precomputedSuperAdmin]);
988
+ useEffect5(() => {
642
989
  const isPagePermission = permission.includes(":page.") || !!pageId;
643
990
  const requiresOrgId = !isPagePermission;
644
991
  if (isSuperAdmin === true) {
@@ -656,12 +1003,13 @@ function useCan(userId, scope, permission, pageId, useCache = true, appName) {
656
1003
  setError(null);
657
1004
  }
658
1005
  }, [isValidScope, organisationId, error, permission, pageId, isSuperAdmin]);
659
- const lastUserIdRef = useRef(null);
660
- const lastScopeRef = useRef(null);
661
- const lastPermissionRef = useRef(null);
662
- const lastPageIdRef = useRef(null);
663
- const lastUseCacheRef = useRef(null);
664
- const stableScope = useMemo2(() => {
1006
+ const lastUserIdRef = useRef2(null);
1007
+ const lastScopeRef = useRef2(null);
1008
+ const lastPermissionRef = useRef2(null);
1009
+ const lastPageIdRef = useRef2(null);
1010
+ const lastUseCacheRef = useRef2(null);
1011
+ const lastIsSuperAdminRef = useRef2(null);
1012
+ const stableScope = useMemo5(() => {
665
1013
  if (!isValidScope) {
666
1014
  return null;
667
1015
  }
@@ -671,10 +1019,12 @@ function useCan(userId, scope, permission, pageId, useCache = true, appName) {
671
1019
  appId
672
1020
  };
673
1021
  }, [isValidScope, organisationId, eventId, appId]);
674
- const prevScopeRef = useRef(null);
675
- useEffect2(() => {
1022
+ const prevScopeRef = useRef2(null);
1023
+ useEffect5(() => {
676
1024
  const scopeChanged = !scopeEqual(prevScopeRef.current, stableScope);
677
- if (lastUserIdRef.current !== userId || scopeChanged || lastPermissionRef.current !== permission || lastPageIdRef.current !== pageId || lastUseCacheRef.current !== useCache) {
1025
+ const isSuperAdminChanged = lastIsSuperAdminRef.current !== isSuperAdmin;
1026
+ if (lastUserIdRef.current !== userId || scopeChanged || lastPermissionRef.current !== permission || lastPageIdRef.current !== pageId || lastUseCacheRef.current !== useCache || isSuperAdminChanged) {
1027
+ lastIsSuperAdminRef.current = isSuperAdmin;
678
1028
  lastUserIdRef.current = userId;
679
1029
  prevScopeRef.current = stableScope;
680
1030
  lastPermissionRef.current = permission;
@@ -683,7 +1033,19 @@ function useCan(userId, scope, permission, pageId, useCache = true, appName) {
683
1033
  const checkPermission = async () => {
684
1034
  if (!userId) {
685
1035
  setCan(false);
686
- setIsLoading(false);
1036
+ setIsLoading(false);
1037
+ return;
1038
+ }
1039
+ if (isSuperAdmin === true) {
1040
+ setCan(true);
1041
+ setIsLoading(false);
1042
+ setError(null);
1043
+ return;
1044
+ }
1045
+ if (isSuperAdmin === null) {
1046
+ setIsLoading(true);
1047
+ setCan(false);
1048
+ setError(null);
687
1049
  return;
688
1050
  }
689
1051
  if (!isValidScope) {
@@ -697,13 +1059,10 @@ function useCan(userId, scope, permission, pageId, useCache = true, appName) {
697
1059
  const isPageName = pageId && typeof pageId === "string" && !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(pageId);
698
1060
  const needsAppIdForPageName = isPagePermission && isPageName;
699
1061
  if (requiresOrgId && (!organisationId || organisationId === null || typeof organisationId === "string" && organisationId.trim() === "")) {
700
- if (isSuperAdmin === true) {
701
- } else {
702
- setIsLoading(true);
703
- setCan(false);
704
- setError(null);
705
- return;
706
- }
1062
+ setIsLoading(true);
1063
+ setCan(false);
1064
+ setError(null);
1065
+ return;
707
1066
  }
708
1067
  if (needsAppIdForPageName && (!appId || appId === null || typeof appId === "string" && appId.trim() === "")) {
709
1068
  setIsLoading(true);
@@ -719,11 +1078,12 @@ function useCan(userId, scope, permission, pageId, useCache = true, appName) {
719
1078
  ...eventId ? { eventId } : {},
720
1079
  ...appId ? { appId } : {}
721
1080
  };
722
- const result = useCache ? await isPermittedCached({ userId, scope: validScope, permission, pageId }, void 0, appName) : await isPermitted({ userId, scope: validScope, permission, pageId }, void 0, appName);
1081
+ const result = useCache ? await isPermittedCached({ userId, scope: validScope, permission, pageId }, appName) : await isPermitted({ userId, scope: validScope, permission, pageId }, appName, isSuperAdmin === false ? false : null);
723
1082
  setCan(result);
724
1083
  } catch (err) {
725
1084
  const logger2 = getRBACLogger();
726
1085
  logger2.error("Permission check error:", { permission, error: err });
1086
+ console.error("[useCan] Permission check error", { userId, permission, error: err });
727
1087
  setError(err instanceof Error ? err : new Error("Failed to check permission"));
728
1088
  setCan(false);
729
1089
  } finally {
@@ -733,7 +1093,7 @@ function useCan(userId, scope, permission, pageId, useCache = true, appName) {
733
1093
  checkPermission();
734
1094
  }
735
1095
  }, [userId, stableScope, permission, pageId, useCache, appName, isSuperAdmin]);
736
- const refetch = useCallback2(async () => {
1096
+ const refetch = useCallback4(async () => {
737
1097
  if (!userId) {
738
1098
  setCan(false);
739
1099
  setIsLoading(false);
@@ -761,7 +1121,7 @@ function useCan(userId, scope, permission, pageId, useCache = true, appName) {
761
1121
  ...eventId ? { eventId } : {},
762
1122
  ...appId ? { appId } : {}
763
1123
  };
764
- const result = useCache ? await isPermittedCached({ userId, scope: validScope, permission, pageId }, void 0, appName) : await isPermitted({ userId, scope: validScope, permission, pageId }, void 0, appName);
1124
+ const result = useCache ? await isPermittedCached({ userId, scope: validScope, permission, pageId }, appName) : await isPermitted({ userId, scope: validScope, permission, pageId }, appName, null);
765
1125
  setCan(result);
766
1126
  } catch (err) {
767
1127
  setError(err instanceof Error ? err : new Error("Failed to check permission"));
@@ -770,107 +1130,63 @@ function useCan(userId, scope, permission, pageId, useCache = true, appName) {
770
1130
  setIsLoading(false);
771
1131
  }
772
1132
  }, [userId, isValidScope, organisationId, eventId, appId, permission, pageId, useCache, appName]);
773
- return useMemo2(() => ({
1133
+ return useMemo5(() => ({
774
1134
  can,
775
1135
  isLoading,
776
1136
  error,
777
1137
  refetch
778
1138
  }), [can, isLoading, error, refetch]);
779
1139
  }
780
- function useAccessLevel(userId, scope) {
781
- const [accessLevel, setAccessLevel] = useState2("viewer");
782
- const [isLoading, setIsLoading] = useState2(true);
783
- const [error, setError] = useState2(null);
784
- let appName;
785
- try {
786
- const { appName: contextAppName } = useAppConfig();
787
- appName = contextAppName;
788
- } catch {
789
- }
790
- const fetchAccessLevel = useCallback2(async () => {
791
- if (!userId) {
792
- setAccessLevel("viewer");
793
- setIsLoading(false);
794
- return;
795
- }
796
- try {
797
- setIsLoading(true);
798
- setError(null);
799
- const { isSuperAdmin: checkSuperAdmin } = await import("./api-N774RPUA.js");
800
- const isSuperAdminUser = await checkSuperAdmin(userId);
801
- if (isSuperAdminUser) {
802
- setAccessLevel("super");
803
- setIsLoading(false);
804
- return;
805
- }
806
- if (appName !== "PORTAL" && appName !== "ADMIN" && !scope.organisationId && !scope.eventId) {
807
- const orgError = new OrganisationContextRequiredError();
808
- setError(orgError);
809
- setAccessLevel("viewer");
810
- setIsLoading(false);
811
- return;
812
- }
813
- const level = await getAccessLevel({ userId, scope }, null, appName);
814
- setAccessLevel(level);
815
- } catch (err) {
816
- const error2 = err instanceof Error ? err : new Error("Failed to fetch access level");
817
- setError(error2);
818
- setAccessLevel("viewer");
819
- } finally {
820
- setIsLoading(false);
821
- }
822
- }, [userId, scope.organisationId, scope.eventId, scope.appId, appName]);
823
- useEffect2(() => {
824
- fetchAccessLevel();
825
- }, [fetchAccessLevel]);
826
- return useMemo2(() => ({
827
- accessLevel,
828
- isLoading,
829
- error,
830
- refetch: fetchAccessLevel
831
- }), [accessLevel, isLoading, error, fetchAccessLevel]);
832
- }
833
- function useMultiplePermissions(userId, scope, permissions, useCache = true) {
834
- const [results, setResults] = useState2({});
835
- const [isLoading, setIsLoading] = useState2(true);
836
- const [error, setError] = useState2(null);
837
- const checkPermissions = useCallback2(async () => {
1140
+
1141
+ // src/rbac/hooks/permissions/useHasAllPermissions.ts
1142
+ import { useCallback as useCallback5, useEffect as useEffect6, useMemo as useMemo6, useState as useState6 } from "react";
1143
+ function useHasAllPermissions(userId, scope, permissions, useCache = true) {
1144
+ const [hasAll, setHasAll] = useState6(false);
1145
+ const [isLoading, setIsLoading] = useState6(true);
1146
+ const [error, setError] = useState6(null);
1147
+ const checkAllPermissions = useCallback5(async () => {
838
1148
  if (!userId || permissions.length === 0) {
839
- setResults({});
1149
+ setHasAll(false);
840
1150
  setIsLoading(false);
841
1151
  return;
842
1152
  }
843
1153
  try {
844
1154
  setIsLoading(true);
845
1155
  setError(null);
846
- const permissionResults = {};
1156
+ let hasAllPermissions = true;
847
1157
  for (const permission of permissions) {
848
1158
  const result = useCache ? await isPermittedCached({ userId, scope, permission }) : await isPermitted({ userId, scope, permission });
849
- permissionResults[permission] = result;
1159
+ if (!result) {
1160
+ hasAllPermissions = false;
1161
+ break;
1162
+ }
850
1163
  }
851
- setResults(permissionResults);
1164
+ setHasAll(hasAllPermissions);
852
1165
  } catch (err) {
853
1166
  setError(err instanceof Error ? err : new Error("Failed to check permissions"));
854
- setResults({});
1167
+ setHasAll(false);
855
1168
  } finally {
856
1169
  setIsLoading(false);
857
1170
  }
858
1171
  }, [userId, scope.organisationId, scope.eventId, scope.appId, permissions, useCache]);
859
- useEffect2(() => {
860
- checkPermissions();
861
- }, [checkPermissions]);
862
- return useMemo2(() => ({
863
- results,
1172
+ useEffect6(() => {
1173
+ checkAllPermissions();
1174
+ }, [checkAllPermissions]);
1175
+ return useMemo6(() => ({
1176
+ hasAll,
864
1177
  isLoading,
865
1178
  error,
866
- refetch: checkPermissions
867
- }), [results, isLoading, error, checkPermissions]);
1179
+ refetch: checkAllPermissions
1180
+ }), [hasAll, isLoading, error, checkAllPermissions]);
868
1181
  }
1182
+
1183
+ // src/rbac/hooks/permissions/useHasAnyPermission.ts
1184
+ import { useCallback as useCallback6, useEffect as useEffect7, useMemo as useMemo7, useState as useState7 } from "react";
869
1185
  function useHasAnyPermission(userId, scope, permissions, useCache = true) {
870
- const [hasAny, setHasAny] = useState2(false);
871
- const [isLoading, setIsLoading] = useState2(true);
872
- const [error, setError] = useState2(null);
873
- const checkAnyPermission = useCallback2(async () => {
1186
+ const [hasAny, setHasAny] = useState7(false);
1187
+ const [isLoading, setIsLoading] = useState7(true);
1188
+ const [error, setError] = useState7(null);
1189
+ const checkAnyPermission = useCallback6(async () => {
874
1190
  if (!userId || permissions.length === 0) {
875
1191
  setHasAny(false);
876
1192
  setIsLoading(false);
@@ -895,93 +1211,203 @@ function useHasAnyPermission(userId, scope, permissions, useCache = true) {
895
1211
  setIsLoading(false);
896
1212
  }
897
1213
  }, [userId, scope.organisationId, scope.eventId, scope.appId, permissions, useCache]);
898
- useEffect2(() => {
1214
+ useEffect7(() => {
899
1215
  checkAnyPermission();
900
1216
  }, [checkAnyPermission]);
901
- return useMemo2(() => ({
1217
+ return useMemo7(() => ({
902
1218
  hasAny,
903
1219
  isLoading,
904
1220
  error,
905
1221
  refetch: checkAnyPermission
906
1222
  }), [hasAny, isLoading, error, checkAnyPermission]);
907
1223
  }
908
- function useHasAllPermissions(userId, scope, permissions, useCache = true) {
909
- const [hasAll, setHasAll] = useState2(false);
910
- const [isLoading, setIsLoading] = useState2(true);
911
- const [error, setError] = useState2(null);
912
- const checkAllPermissions = useCallback2(async () => {
1224
+
1225
+ // src/rbac/hooks/permissions/useMultiplePermissions.ts
1226
+ import { useCallback as useCallback7, useEffect as useEffect8, useMemo as useMemo8, useState as useState8 } from "react";
1227
+ function useMultiplePermissions(userId, scope, permissions, useCache = true) {
1228
+ const [results, setResults] = useState8({});
1229
+ const [isLoading, setIsLoading] = useState8(true);
1230
+ const [error, setError] = useState8(null);
1231
+ const checkPermissions = useCallback7(async () => {
913
1232
  if (!userId || permissions.length === 0) {
914
- setHasAll(false);
1233
+ setResults({});
915
1234
  setIsLoading(false);
916
1235
  return;
917
1236
  }
918
1237
  try {
919
1238
  setIsLoading(true);
920
1239
  setError(null);
921
- let hasAllPermissions = true;
1240
+ const permissionResults = {};
922
1241
  for (const permission of permissions) {
923
1242
  const result = useCache ? await isPermittedCached({ userId, scope, permission }) : await isPermitted({ userId, scope, permission });
924
- if (!result) {
925
- hasAllPermissions = false;
926
- break;
927
- }
1243
+ permissionResults[permission] = result;
928
1244
  }
929
- setHasAll(hasAllPermissions);
1245
+ setResults(permissionResults);
930
1246
  } catch (err) {
931
1247
  setError(err instanceof Error ? err : new Error("Failed to check permissions"));
932
- setHasAll(false);
1248
+ setResults({});
933
1249
  } finally {
934
1250
  setIsLoading(false);
935
1251
  }
936
1252
  }, [userId, scope.organisationId, scope.eventId, scope.appId, permissions, useCache]);
937
- useEffect2(() => {
938
- checkAllPermissions();
939
- }, [checkAllPermissions]);
940
- return useMemo2(() => ({
941
- hasAll,
1253
+ useEffect8(() => {
1254
+ checkPermissions();
1255
+ }, [checkPermissions]);
1256
+ return useMemo8(() => ({
1257
+ results,
942
1258
  isLoading,
943
1259
  error,
944
- refetch: checkAllPermissions
945
- }), [hasAll, isLoading, error, checkAllPermissions]);
1260
+ refetch: checkPermissions
1261
+ }), [results, isLoading, error, checkPermissions]);
946
1262
  }
947
- function useCachedPermissions(userId, scope) {
948
- const [permissions, setPermissions] = useState2({});
949
- const [isLoading, setIsLoading] = useState2(true);
950
- const [error, setError] = useState2(null);
951
- const fetchCachedPermissions = useCallback2(async () => {
1263
+
1264
+ // src/rbac/hooks/permissions/usePermissions.ts
1265
+ import { useCallback as useCallback8, useEffect as useEffect9, useMemo as useMemo9, useRef as useRef3, useState as useState9 } from "react";
1266
+ function usePermissions(userId, organisationId, eventId, appId) {
1267
+ const [permissions, setPermissions] = useState9({});
1268
+ const [isLoading, setIsLoading] = useState9(true);
1269
+ const [error, setError] = useState9(null);
1270
+ const [fetchTrigger, setFetchTrigger] = useState9(0);
1271
+ const isFetchingRef = useRef3(false);
1272
+ const logger2 = getRBACLogger();
1273
+ const prevValuesRef = useRef3({ userId, organisationId, eventId, appId });
1274
+ const orgId = organisationId || "";
1275
+ useEffect9(() => {
1276
+ if (!userId) {
1277
+ return;
1278
+ }
1279
+ if (!orgId || orgId === null || typeof orgId === "string" && orgId.trim() === "") {
1280
+ const timeoutId = setTimeout(() => {
1281
+ setError(new Error("Organisation context is required for permission checks"));
1282
+ setIsLoading(false);
1283
+ }, 3e3);
1284
+ return () => clearTimeout(timeoutId);
1285
+ }
1286
+ if (error?.message === "Organisation context is required for permission checks") {
1287
+ setError(null);
1288
+ }
1289
+ }, [userId, organisationId, error, orgId]);
1290
+ useEffect9(() => {
1291
+ const paramsChanged = prevValuesRef.current.userId !== userId || prevValuesRef.current.organisationId !== organisationId || prevValuesRef.current.eventId !== eventId || prevValuesRef.current.appId !== appId;
1292
+ if (paramsChanged) {
1293
+ if (prevValuesRef.current.appId !== appId) {
1294
+ }
1295
+ prevValuesRef.current = { userId, organisationId, eventId, appId };
1296
+ setFetchTrigger((prev) => prev + 1);
1297
+ }
1298
+ }, [userId, organisationId, eventId, appId, logger2]);
1299
+ useEffect9(() => {
1300
+ const fetchPermissions = async () => {
1301
+ if (isFetchingRef.current) {
1302
+ return;
1303
+ }
1304
+ if (!userId) {
1305
+ setPermissions({});
1306
+ setIsLoading(false);
1307
+ return;
1308
+ }
1309
+ if (!userId) {
1310
+ setPermissions({});
1311
+ setIsLoading(false);
1312
+ return;
1313
+ }
1314
+ if (!orgId || orgId === null || typeof orgId === "string" && orgId.trim() === "") {
1315
+ setIsLoading(true);
1316
+ setError(null);
1317
+ return;
1318
+ }
1319
+ try {
1320
+ isFetchingRef.current = true;
1321
+ setIsLoading(true);
1322
+ setError(null);
1323
+ const scope = {
1324
+ organisationId: orgId,
1325
+ eventId,
1326
+ appId
1327
+ };
1328
+ const permissionMap = await getPermissionMap({ userId, scope });
1329
+ const permissionCount = Object.keys(permissionMap).length;
1330
+ if (permissionCount === 0 && Object.keys(permissions).length > 0) {
1331
+ logger2.warn("[usePermissions] Permissions fetched but returned empty map", {
1332
+ scope: { organisationId: orgId, eventId, appId }
1333
+ });
1334
+ }
1335
+ setPermissions(permissionMap);
1336
+ } catch (err) {
1337
+ logger2.error("[usePermissions] Failed to fetch permissions:", err);
1338
+ setError(err instanceof Error ? err : new Error("Failed to fetch permissions"));
1339
+ } finally {
1340
+ setIsLoading(false);
1341
+ isFetchingRef.current = false;
1342
+ }
1343
+ };
1344
+ fetchPermissions();
1345
+ }, [fetchTrigger, userId, organisationId, eventId, appId]);
1346
+ const hasPermission = useCallback8((permission) => {
1347
+ if (permissions["*"]) {
1348
+ return true;
1349
+ }
1350
+ return permissions[permission] === true;
1351
+ }, [permissions]);
1352
+ const hasAnyPermission = useCallback8((permissionList) => {
1353
+ if (permissions["*"]) {
1354
+ return true;
1355
+ }
1356
+ return permissionList.some((p) => permissions[p] === true);
1357
+ }, [permissions]);
1358
+ const hasAllPermissions = useCallback8((permissionList) => {
1359
+ if (permissions["*"]) {
1360
+ return true;
1361
+ }
1362
+ return permissionList.every((p) => permissions[p] === true);
1363
+ }, [permissions]);
1364
+ const refetch = useCallback8(async () => {
1365
+ if (isFetchingRef.current) {
1366
+ return;
1367
+ }
952
1368
  if (!userId) {
953
1369
  setPermissions({});
954
1370
  setIsLoading(false);
955
1371
  return;
956
1372
  }
1373
+ if (!orgId || orgId === null || typeof orgId === "string" && orgId.trim() === "") {
1374
+ setIsLoading(true);
1375
+ setError(null);
1376
+ return;
1377
+ }
957
1378
  try {
1379
+ isFetchingRef.current = true;
958
1380
  setIsLoading(true);
959
1381
  setError(null);
1382
+ const scope = {
1383
+ organisationId: orgId,
1384
+ eventId,
1385
+ appId
1386
+ };
960
1387
  const permissionMap = await getPermissionMap({ userId, scope });
961
1388
  setPermissions(permissionMap);
962
1389
  } catch (err) {
963
- setError(err instanceof Error ? err : new Error("Failed to fetch cached permissions"));
1390
+ const logger3 = getRBACLogger();
1391
+ logger3.error("Failed to refetch permissions:", err);
1392
+ setError(err instanceof Error ? err : new Error("Failed to fetch permissions"));
964
1393
  } finally {
965
1394
  setIsLoading(false);
1395
+ isFetchingRef.current = false;
966
1396
  }
967
- }, [userId, scope.organisationId, scope.eventId, scope.appId]);
968
- const invalidateCache = useCallback2(() => {
969
- fetchCachedPermissions();
970
- }, [fetchCachedPermissions]);
971
- useEffect2(() => {
972
- fetchCachedPermissions();
973
- }, [fetchCachedPermissions]);
974
- return useMemo2(() => ({
1397
+ }, [userId, organisationId, eventId, appId]);
1398
+ return useMemo9(() => ({
975
1399
  permissions,
976
1400
  isLoading,
977
1401
  error,
978
- invalidateCache,
979
- refetch: fetchCachedPermissions
980
- }), [permissions, isLoading, error, invalidateCache, fetchCachedPermissions]);
1402
+ hasPermission,
1403
+ hasAnyPermission,
1404
+ hasAllPermissions,
1405
+ refetch
1406
+ }), [permissions, isLoading, error, hasPermission, hasAnyPermission, hasAllPermissions, refetch]);
981
1407
  }
982
1408
 
983
1409
  // src/rbac/hooks/useResourcePermissions.ts
984
- import { useMemo as useMemo3 } from "react";
1410
+ import { useMemo as useMemo10 } from "react";
985
1411
  function useResourcePermissions(resource, options = {}) {
986
1412
  const { enableRead = false, requireScope = true } = options;
987
1413
  const { user, supabase } = useUnifiedAuth();
@@ -1009,8 +1435,12 @@ function useResourcePermissions(resource, options = {}) {
1009
1435
  `create:${resource}`,
1010
1436
  pageId,
1011
1437
  // Pass resource name as pageId when appId is available to enable page permission checks
1012
- true
1438
+ true,
1013
1439
  // useCache
1440
+ null,
1441
+ // precomputedSuperAdmin - not checked yet
1442
+ void 0
1443
+ // appName
1014
1444
  );
1015
1445
  const { can: canUpdateResult, isLoading: updateLoading, error: updateError } = useCan(
1016
1446
  user?.id || "",
@@ -1018,8 +1448,12 @@ function useResourcePermissions(resource, options = {}) {
1018
1448
  `update:${resource}`,
1019
1449
  pageId,
1020
1450
  // Pass resource name as pageId when appId is available to enable page permission checks
1021
- true
1451
+ true,
1022
1452
  // useCache
1453
+ null,
1454
+ // precomputedSuperAdmin - not checked yet
1455
+ void 0
1456
+ // appName
1023
1457
  );
1024
1458
  const { can: canDeleteResult, isLoading: deleteLoading, error: deleteError } = useCan(
1025
1459
  user?.id || "",
@@ -1027,8 +1461,12 @@ function useResourcePermissions(resource, options = {}) {
1027
1461
  `delete:${resource}`,
1028
1462
  pageId,
1029
1463
  // Pass resource name as pageId when appId is available to enable page permission checks
1030
- true
1464
+ true,
1031
1465
  // useCache
1466
+ null,
1467
+ // precomputedSuperAdmin - not checked yet
1468
+ void 0
1469
+ // appName
1032
1470
  );
1033
1471
  const { can: canReadResult, isLoading: readLoading, error: readError } = useCan(
1034
1472
  user?.id || "",
@@ -1036,13 +1474,17 @@ function useResourcePermissions(resource, options = {}) {
1036
1474
  `read:${resource}`,
1037
1475
  pageId,
1038
1476
  // Pass resource name as pageId when appId is available to enable page permission checks
1039
- true
1477
+ true,
1040
1478
  // useCache
1479
+ null,
1480
+ // precomputedSuperAdmin - not checked yet
1481
+ void 0
1482
+ // appName
1041
1483
  );
1042
- const isLoading = useMemo3(() => {
1484
+ const isLoading = useMemo10(() => {
1043
1485
  return scopeLoading || createLoading || updateLoading || deleteLoading || enableRead && readLoading;
1044
1486
  }, [scopeLoading, createLoading, updateLoading, deleteLoading, readLoading, enableRead]);
1045
- const error = useMemo3(() => {
1487
+ const error = useMemo10(() => {
1046
1488
  if (scopeError) return scopeError;
1047
1489
  if (createError) return createError;
1048
1490
  if (updateError) return updateError;
@@ -1050,7 +1492,7 @@ function useResourcePermissions(resource, options = {}) {
1050
1492
  if (enableRead && readError) return readError;
1051
1493
  return null;
1052
1494
  }, [scopeError, createError, updateError, deleteError, readError, enableRead]);
1053
- return useMemo3(() => ({
1495
+ return useMemo10(() => ({
1054
1496
  canCreate: (res) => {
1055
1497
  if (res !== resource) {
1056
1498
  return false;
@@ -1095,15 +1537,15 @@ function useResourcePermissions(resource, options = {}) {
1095
1537
  }
1096
1538
 
1097
1539
  // src/rbac/hooks/useRoleManagement.ts
1098
- import { useState as useState3, useCallback as useCallback3 } from "react";
1540
+ import { useState as useState10, useCallback as useCallback9 } from "react";
1099
1541
  function useRoleManagement() {
1100
1542
  const { user, supabase } = useUnifiedAuth();
1101
- const [isLoading, setIsLoading] = useState3(false);
1102
- const [error, setError] = useState3(null);
1543
+ const [isLoading, setIsLoading] = useState10(false);
1544
+ const [error, setError] = useState10(null);
1103
1545
  if (!supabase) {
1104
1546
  throw new Error("useRoleManagement requires a Supabase client. Ensure UnifiedAuthProvider is configured.");
1105
1547
  }
1106
- const revokeEventAppRole = useCallback3(async (params) => {
1548
+ const revokeEventAppRole = useCallback9(async (params) => {
1107
1549
  setIsLoading(true);
1108
1550
  setError(null);
1109
1551
  try {
@@ -1134,7 +1576,7 @@ function useRoleManagement() {
1134
1576
  setIsLoading(false);
1135
1577
  }
1136
1578
  }, [user?.id]);
1137
- const grantEventAppRole = useCallback3(async (params) => {
1579
+ const grantEventAppRole = useCallback9(async (params) => {
1138
1580
  setIsLoading(true);
1139
1581
  setError(null);
1140
1582
  try {
@@ -1173,7 +1615,7 @@ function useRoleManagement() {
1173
1615
  setIsLoading(false);
1174
1616
  }
1175
1617
  }, [user?.id]);
1176
- const revokeRoleById = useCallback3(async (roleId) => {
1618
+ const revokeRoleById = useCallback9(async (roleId) => {
1177
1619
  setIsLoading(true);
1178
1620
  setError(null);
1179
1621
  try {
@@ -1209,7 +1651,7 @@ function useRoleManagement() {
1209
1651
  setIsLoading(false);
1210
1652
  }
1211
1653
  }, [user?.id, supabase]);
1212
- const grantGlobalRole = useCallback3(async (params) => {
1654
+ const grantGlobalRole = useCallback9(async (params) => {
1213
1655
  setIsLoading(true);
1214
1656
  setError(null);
1215
1657
  try {
@@ -1248,7 +1690,7 @@ function useRoleManagement() {
1248
1690
  setIsLoading(false);
1249
1691
  }
1250
1692
  }, [user?.id, supabase]);
1251
- const revokeGlobalRole = useCallback3(async (params) => {
1693
+ const revokeGlobalRole = useCallback9(async (params) => {
1252
1694
  setIsLoading(true);
1253
1695
  setError(null);
1254
1696
  try {
@@ -1280,7 +1722,7 @@ function useRoleManagement() {
1280
1722
  setIsLoading(false);
1281
1723
  }
1282
1724
  }, [user?.id, supabase]);
1283
- const grantOrganisationRole = useCallback3(async (params) => {
1725
+ const grantOrganisationRole = useCallback9(async (params) => {
1284
1726
  setIsLoading(true);
1285
1727
  setError(null);
1286
1728
  try {
@@ -1319,7 +1761,7 @@ function useRoleManagement() {
1319
1761
  setIsLoading(false);
1320
1762
  }
1321
1763
  }, [user?.id, supabase]);
1322
- const revokeOrganisationRole = useCallback3(async (params) => {
1764
+ const revokeOrganisationRole = useCallback9(async (params) => {
1323
1765
  setIsLoading(true);
1324
1766
  setError(null);
1325
1767
  try {
@@ -1369,11 +1811,11 @@ function useRoleManagement() {
1369
1811
  }
1370
1812
 
1371
1813
  // src/rbac/hooks/useSecureSupabase.ts
1372
- import { useMemo as useMemo4, useRef as useRef2 } from "react";
1814
+ import { useMemo as useMemo11, useRef as useRef4 } from "react";
1373
1815
  var secureClientCache = /* @__PURE__ */ new Map();
1374
1816
  var MAX_CACHE_SIZE = 5;
1375
1817
  function getCacheKey(organisationId, eventId, appId, isSuperAdmin) {
1376
- return `${organisationId}-${eventId || "no-event"}-${appId || "no-app"}-${isSuperAdmin ? "super" : "regular"}`;
1818
+ return `${organisationId || "no-org"}-${eventId || "no-event"}-${appId || "no-app"}-${isSuperAdmin ? "super" : "regular"}`;
1377
1819
  }
1378
1820
  function getSupabaseConfig() {
1379
1821
  const getEnvVar = (key) => {
@@ -1405,19 +1847,20 @@ function useSecureSupabase(baseClient) {
1405
1847
  selectedOrganisationId: selectedOrganisation?.id || null,
1406
1848
  selectedEventId: selectedEvent?.event_id || null
1407
1849
  });
1408
- const prevContextRef = useRef2({
1850
+ const prevContextRef = useRef4({
1409
1851
  organisationId: void 0,
1410
1852
  eventId: void 0,
1411
1853
  appId: void 0
1412
1854
  });
1413
- return useMemo4(() => {
1855
+ return useMemo11(() => {
1414
1856
  if (eventLoading) {
1415
1857
  return baseClient || authSupabase || null;
1416
1858
  }
1417
1859
  const organisationId = resolvedScope?.organisationId;
1418
1860
  const eventId = resolvedScope?.eventId || selectedEvent?.event_id;
1419
1861
  const appId = resolvedScope?.appId;
1420
- if (organisationId && user?.id) {
1862
+ const canCreateSecureClient = user?.id && (isSuperAdmin || organisationId);
1863
+ if (canCreateSecureClient) {
1421
1864
  prevContextRef.current = { organisationId, eventId, appId };
1422
1865
  const cacheKey = getCacheKey(organisationId, eventId, appId, isSuperAdmin);
1423
1866
  const cachedClient = secureClientCache.get(cacheKey);
@@ -1432,11 +1875,19 @@ function useSecureSupabase(baseClient) {
1432
1875
  return baseClient || authSupabase || null;
1433
1876
  }
1434
1877
  try {
1435
- const secureClient = createSecureClient(
1878
+ const effectiveOrganisationId = isSuperAdmin ? organisationId || null : organisationId;
1879
+ const baseForSecureClient = baseClient || authSupabase || null;
1880
+ const secureClient = baseForSecureClient ? fromSupabaseClient(
1881
+ baseForSecureClient,
1882
+ effectiveOrganisationId ?? null,
1883
+ eventId,
1884
+ appId,
1885
+ isSuperAdmin
1886
+ ) : createSecureClient(
1436
1887
  config.url,
1437
1888
  config.key,
1438
- organisationId,
1439
- // organisationId is string, UUID is string alias
1889
+ effectiveOrganisationId,
1890
+ // organisationId is string | null, UUID is string alias
1440
1891
  eventId,
1441
1892
  appId,
1442
1893
  // appId is string | undefined, UUID is string alias
@@ -1471,20 +1922,24 @@ function useSecureSupabase(baseClient) {
1471
1922
  }
1472
1923
 
1473
1924
  export {
1925
+ SECURE_CLIENT_SYMBOL,
1926
+ isSecureClient,
1927
+ warnIfInsecureClient,
1474
1928
  SecureSupabaseClient,
1475
1929
  createSecureClient,
1476
1930
  fromSupabaseClient,
1931
+ useResolvedScope,
1477
1932
  useRBAC,
1933
+ useAccessLevel,
1934
+ useCachedPermissions,
1478
1935
  scopeEqual,
1479
- usePermissions,
1480
1936
  useCan,
1481
- useAccessLevel,
1482
- useMultiplePermissions,
1483
- useHasAnyPermission,
1484
1937
  useHasAllPermissions,
1485
- useCachedPermissions,
1938
+ useHasAnyPermission,
1939
+ useMultiplePermissions,
1940
+ usePermissions,
1486
1941
  useResourcePermissions,
1487
1942
  useRoleManagement,
1488
1943
  useSecureSupabase
1489
1944
  };
1490
- //# sourceMappingURL=chunk-XM25TVIE.js.map
1945
+ //# sourceMappingURL=chunk-YDQHOZNA.js.map