@jmruthers/pace-core 0.5.76 → 0.5.78

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 (447) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/{RBACService-C4udt_Zp.d.ts → AuthService-Df3IozMG.d.ts} +10 -118
  3. package/dist/{DataTable-ntgmhO2W.d.ts → DataTable-BE0OXZKQ.d.ts} +9 -2
  4. package/dist/{DataTable-4GAVPIEG.js → DataTable-ETGVF4Y5.js} +50 -13
  5. package/dist/{PublicLoadingSpinner-BiNER8F5.d.ts → PublicLoadingSpinner-CnUaz0vG.d.ts} +5 -2
  6. package/dist/{UnifiedAuthProvider-Bj6YCf7c.d.ts → UnifiedAuthProvider-B391Aqum.d.ts} +42 -45
  7. package/dist/{UnifiedAuthProvider-3NKDOSOK.js → UnifiedAuthProvider-P5SOJAQ6.js} +4 -5
  8. package/dist/{api-DDMUKIUD.js → api-KG4A2X7P.js} +9 -3
  9. package/dist/{audit-6TOCAMKO.js → audit-65VNHEV2.js} +2 -2
  10. package/dist/{chunk-K34IM5CT.js → chunk-2OGV6IRV.js} +196 -626
  11. package/dist/chunk-2OGV6IRV.js.map +1 -0
  12. package/dist/{chunk-NTNILOBC.js → chunk-5BO3MI5Y.js} +4 -4
  13. package/dist/{chunk-XLZ7U46Z.js → chunk-CVMVPYAL.js} +9 -60
  14. package/dist/chunk-CVMVPYAL.js.map +1 -0
  15. package/dist/{chunk-URUTVZ7N.js → chunk-FL4ZCQLD.js} +2 -2
  16. package/dist/{chunk-LW7MMEAQ.js → chunk-FT2M4R4F.js} +2 -2
  17. package/dist/{chunk-5BSLGBYI.js → chunk-JCQZ6LA7.js} +2 -8
  18. package/dist/{chunk-5BSLGBYI.js.map → chunk-JCQZ6LA7.js.map} +1 -1
  19. package/dist/{chunk-KHJS6VIA.js → chunk-LRQ6RBJC.js} +157 -112
  20. package/dist/chunk-LRQ6RBJC.js.map +1 -0
  21. package/dist/{chunk-WN6XJWOS.js → chunk-MNJXXD6C.js} +274 -743
  22. package/dist/chunk-MNJXXD6C.js.map +1 -0
  23. package/dist/{chunk-KK73ZB4E.js → chunk-PTR5PMPE.js} +153 -132
  24. package/dist/chunk-PTR5PMPE.js.map +1 -0
  25. package/dist/{chunk-B2WTCLCV.js → chunk-Q7APDV6H.js} +18 -8
  26. package/dist/chunk-Q7APDV6H.js.map +1 -0
  27. package/dist/{chunk-A4FUBC7B.js → chunk-QGVSOUJ2.js} +2 -4
  28. package/dist/{chunk-A4FUBC7B.js.map → chunk-QGVSOUJ2.js.map} +1 -1
  29. package/dist/{chunk-FGMFQSHX.js → chunk-S63MFSY6.js} +500 -551
  30. package/dist/chunk-S63MFSY6.js.map +1 -0
  31. package/dist/{chunk-AFGTSUAD.js → chunk-VSOKOFRF.js} +4 -4
  32. package/dist/chunk-WUXCWRL6.js +20 -0
  33. package/dist/chunk-WUXCWRL6.js.map +1 -0
  34. package/dist/{chunk-Y6TXWPJO.js → chunk-YVVGHRGI.js} +105 -31
  35. package/dist/chunk-YVVGHRGI.js.map +1 -0
  36. package/dist/{chunk-M5IWZRBT.js → chunk-ZMNXIJP4.js} +2187 -981
  37. package/dist/chunk-ZMNXIJP4.js.map +1 -0
  38. package/dist/components.d.ts +6 -6
  39. package/dist/components.js +14 -18
  40. package/dist/components.js.map +1 -1
  41. package/dist/{database-C3Szpi5J.d.ts → database-BXAfr2Y_.d.ts} +18 -0
  42. package/dist/hooks.d.ts +5 -5
  43. package/dist/hooks.js +8 -9
  44. package/dist/hooks.js.map +1 -1
  45. package/dist/index.d.ts +19 -27
  46. package/dist/index.js +21 -29
  47. package/dist/index.js.map +1 -1
  48. package/dist/{organisation-BtshODVF.d.ts → organisation-D6qRDtbF.d.ts} +1 -1
  49. package/dist/providers.d.ts +7 -21
  50. package/dist/providers.js +3 -10
  51. package/dist/rbac/index.d.ts +71 -221
  52. package/dist/rbac/index.js +15 -16
  53. package/dist/{types-CGX9Vyf5.d.ts → types-BDg1mAGG.d.ts} +36 -6
  54. package/dist/types.d.ts +3 -3
  55. package/dist/types.js +61 -18
  56. package/dist/types.js.map +1 -1
  57. package/dist/{unified-CM7T0aTK.d.ts → unified-DQ4VcT7H.d.ts} +1 -1
  58. package/dist/{usePublicRouteParams-B-CumWRc.d.ts → usePublicRouteParams-BlgwXweB.d.ts} +3 -3
  59. package/dist/utils.d.ts +2 -2
  60. package/dist/utils.js +52 -9
  61. package/dist/utils.js.map +1 -1
  62. package/docs/CONTENT_AUDIT_REPORT.md +253 -0
  63. package/docs/DOCUMENTATION_AUDIT.md +172 -0
  64. package/docs/README.md +142 -147
  65. package/docs/STYLE_GUIDE.md +37 -0
  66. package/docs/api/classes/ColumnFactory.md +17 -17
  67. package/docs/api/classes/ErrorBoundary.md +1 -1
  68. package/docs/api/classes/InvalidScopeError.md +4 -4
  69. package/docs/api/classes/MissingUserContextError.md +4 -4
  70. package/docs/api/classes/OrganisationContextRequiredError.md +4 -4
  71. package/docs/api/classes/PermissionDeniedError.md +5 -5
  72. package/docs/api/classes/PublicErrorBoundary.md +1 -1
  73. package/docs/api/classes/RBACAuditManager.md +8 -8
  74. package/docs/api/classes/RBACCache.md +35 -5
  75. package/docs/api/classes/RBACEngine.md +49 -20
  76. package/docs/api/classes/RBACError.md +4 -4
  77. package/docs/api/classes/RBACNotInitializedError.md +4 -4
  78. package/docs/api/classes/SecureSupabaseClient.md +1 -1
  79. package/docs/api/classes/StorageUtils.md +1 -1
  80. package/docs/api/enums/FileCategory.md +1 -1
  81. package/docs/api/interfaces/AggregateConfig.md +4 -4
  82. package/docs/api/interfaces/ButtonProps.md +1 -1
  83. package/docs/api/interfaces/CardProps.md +1 -1
  84. package/docs/api/interfaces/ColorPalette.md +1 -1
  85. package/docs/api/interfaces/ColorShade.md +1 -1
  86. package/docs/api/interfaces/DataAccessRecord.md +1 -1
  87. package/docs/api/interfaces/DataRecord.md +11 -0
  88. package/docs/api/interfaces/DataTableAction.md +65 -29
  89. package/docs/api/interfaces/DataTableColumn.md +36 -23
  90. package/docs/api/interfaces/DataTableProps.md +80 -38
  91. package/docs/api/interfaces/DataTableToolbarButton.md +7 -7
  92. package/docs/api/interfaces/EmptyStateConfig.md +5 -5
  93. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  94. package/docs/api/interfaces/EventLogoProps.md +1 -1
  95. package/docs/api/interfaces/FileDisplayProps.md +1 -1
  96. package/docs/api/interfaces/FileMetadata.md +1 -1
  97. package/docs/api/interfaces/FileReference.md +1 -1
  98. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  99. package/docs/api/interfaces/FileUploadOptions.md +1 -1
  100. package/docs/api/interfaces/FileUploadProps.md +1 -1
  101. package/docs/api/interfaces/FooterProps.md +1 -1
  102. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  103. package/docs/api/interfaces/InputProps.md +1 -1
  104. package/docs/api/interfaces/LabelProps.md +1 -1
  105. package/docs/api/interfaces/LoginFormProps.md +1 -1
  106. package/docs/api/interfaces/NavigationAccessRecord.md +11 -11
  107. package/docs/api/interfaces/NavigationContextType.md +9 -9
  108. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  109. package/docs/api/interfaces/NavigationItem.md +1 -1
  110. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  111. package/docs/api/interfaces/NavigationProviderProps.md +7 -7
  112. package/docs/api/interfaces/Organisation.md +1 -1
  113. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  114. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  115. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  116. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  117. package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
  118. package/docs/api/interfaces/PaceLoginPageProps.md +16 -3
  119. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  120. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  121. package/docs/api/interfaces/PagePermissionGuardProps.md +2 -2
  122. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  123. package/docs/api/interfaces/PaletteData.md +1 -1
  124. package/docs/api/interfaces/PermissionEnforcerProps.md +4 -4
  125. package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
  126. package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
  127. package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
  128. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  129. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  130. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  131. package/docs/api/interfaces/RBACConfig.md +1 -1
  132. package/docs/api/interfaces/RBACLogger.md +1 -1
  133. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  134. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  135. package/docs/api/interfaces/RouteAccessRecord.md +2 -2
  136. package/docs/api/interfaces/RouteConfig.md +2 -2
  137. package/docs/api/interfaces/SecureDataContextType.md +1 -1
  138. package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
  139. package/docs/api/interfaces/StorageConfig.md +1 -1
  140. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  141. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  142. package/docs/api/interfaces/StorageListOptions.md +1 -1
  143. package/docs/api/interfaces/StorageListResult.md +1 -1
  144. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  145. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  146. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  147. package/docs/api/interfaces/StyleImport.md +1 -1
  148. package/docs/api/interfaces/SwitchProps.md +1 -1
  149. package/docs/api/interfaces/ToastActionElement.md +1 -1
  150. package/docs/api/interfaces/ToastProps.md +1 -1
  151. package/docs/api/interfaces/UnifiedAuthContextType.md +94 -521
  152. package/docs/api/interfaces/UnifiedAuthProviderProps.md +16 -16
  153. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  154. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  155. package/docs/api/interfaces/UsePublicEventLogoOptions.md +1 -1
  156. package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
  157. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  158. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  159. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  160. package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
  161. package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
  162. package/docs/api/interfaces/UserEventAccess.md +11 -11
  163. package/docs/api/interfaces/UserMenuProps.md +1 -1
  164. package/docs/api/interfaces/UserProfile.md +1 -1
  165. package/docs/api/modules.md +251 -269
  166. package/docs/api-reference/components.md +193 -0
  167. package/docs/api-reference/hooks.md +265 -0
  168. package/docs/api-reference/providers.md +6 -0
  169. package/docs/api-reference/types.md +6 -0
  170. package/docs/api-reference/utilities.md +207 -0
  171. package/docs/architecture/README.md +6 -0
  172. package/docs/{database-schema-requirements.md → architecture/database-schema-requirements.md} +6 -0
  173. package/docs/architecture/rbac-security-architecture.md +258 -0
  174. package/docs/architecture/services.md +9 -1
  175. package/docs/best-practices/README.md +6 -0
  176. package/docs/best-practices/accessibility.md +6 -0
  177. package/docs/{common-patterns.md → best-practices/common-patterns.md} +6 -0
  178. package/docs/best-practices/deployment.md +6 -0
  179. package/docs/best-practices/performance.md +475 -2
  180. package/docs/best-practices/security.md +6 -0
  181. package/docs/best-practices/testing.md +6 -0
  182. package/docs/core-concepts/authentication.md +6 -0
  183. package/docs/core-concepts/events.md +6 -0
  184. package/docs/core-concepts/organisations.md +6 -0
  185. package/docs/core-concepts/permissions.md +6 -0
  186. package/docs/core-concepts/rbac-system.md +8 -0
  187. package/docs/documentation-index.md +121 -182
  188. package/docs/{consuming-app-vite-config.md → getting-started/consuming-app-vite-config.md} +6 -0
  189. package/docs/getting-started/documentation-index.md +40 -0
  190. package/docs/getting-started/examples/README.md +878 -35
  191. package/docs/{faq.md → getting-started/faq.md} +7 -1
  192. package/docs/getting-started/installation-guide.md +6 -0
  193. package/docs/{quick-reference.md → getting-started/quick-reference.md} +6 -0
  194. package/docs/implementation-guides/app-layout.md +6 -0
  195. package/docs/implementation-guides/authentication.md +1021 -0
  196. package/docs/implementation-guides/component-styling.md +6 -0
  197. package/docs/implementation-guides/data-tables.md +1264 -2076
  198. package/docs/implementation-guides/dynamic-colors.md +6 -0
  199. package/docs/implementation-guides/event-theming-summary.md +6 -0
  200. package/docs/{file-reference-system.md → implementation-guides/file-reference-system.md} +6 -0
  201. package/docs/implementation-guides/file-upload-storage.md +6 -0
  202. package/docs/implementation-guides/forms.md +6 -0
  203. package/docs/implementation-guides/inactivity-tracking.md +6 -0
  204. package/docs/implementation-guides/navigation.md +6 -0
  205. package/docs/implementation-guides/organisation-security.md +6 -0
  206. package/docs/implementation-guides/permission-enforcement.md +6 -0
  207. package/docs/implementation-guides/public-pages-advanced.md +6 -0
  208. package/docs/implementation-guides/public-pages.md +6 -0
  209. package/docs/migration/MIGRATION_GUIDE.md +827 -351
  210. package/docs/migration/README.md +7 -1
  211. package/docs/migration/organisation-context-timing-fix.md +6 -0
  212. package/docs/migration/rbac-migration.md +44 -1
  213. package/docs/migration/service-architecture.md +6 -0
  214. package/docs/migration/v0.4.15-tailwind-scanning.md +6 -0
  215. package/docs/migration/v0.4.16-css-first-approach.md +6 -0
  216. package/docs/migration/v0.4.17-source-path-fix.md +6 -0
  217. package/docs/rbac/README-rbac-rls-integration.md +6 -0
  218. package/docs/rbac/README.md +6 -0
  219. package/docs/rbac/advanced-patterns.md +6 -0
  220. package/docs/rbac/api-reference.md +7 -1
  221. package/docs/rbac/breaking-changes-v3.md +222 -0
  222. package/docs/rbac/examples/rbac-rls-integration-example.md +6 -0
  223. package/docs/rbac/examples.md +6 -0
  224. package/docs/rbac/getting-started.md +6 -0
  225. package/docs/rbac/migration-guide.md +260 -0
  226. package/docs/rbac/quick-start.md +70 -13
  227. package/docs/rbac/rbac-rls-integration.md +6 -0
  228. package/docs/rbac/super-admin-guide.md +6 -0
  229. package/docs/rbac/troubleshooting.md +6 -0
  230. package/docs/security/README.md +6 -0
  231. package/docs/security/checklist.md +6 -0
  232. package/docs/styles/README.md +7 -1
  233. package/docs/{usage.md → styles/usage.md} +6 -0
  234. package/docs/testing/README.md +6 -0
  235. package/docs/{visual-testing.md → testing/visual-testing.md} +6 -0
  236. package/docs/troubleshooting/README.md +387 -5
  237. package/docs/troubleshooting/cake-page-permission-guard-issue-summary.md +6 -0
  238. package/docs/troubleshooting/common-issues.md +6 -0
  239. package/docs/troubleshooting/database-view-compatibility.md +6 -0
  240. package/docs/troubleshooting/organisation-context-setup.md +6 -0
  241. package/docs/troubleshooting/react-hooks-issue-analysis.md +6 -0
  242. package/docs/troubleshooting/styling-issues.md +6 -0
  243. package/docs/troubleshooting/tailwind-content-scanning.md +6 -0
  244. package/package.json +1 -1
  245. package/src/__tests__/helpers/__tests__/test-providers.test.tsx +2 -1
  246. package/src/__tests__/helpers/test-providers.tsx +3 -53
  247. package/src/components/DataTable/DataTable.test.tsx +319 -0
  248. package/src/components/DataTable/DataTable.tsx +32 -11
  249. package/src/components/DataTable/__tests__/{DataTable.comprehensive.test.tsx → DataTable.comprehensive.test.tsx.skip} +6 -4
  250. package/src/components/DataTable/__tests__/{DataTable.test.tsx → DataTable.test.tsx.skip} +6 -4
  251. package/src/components/DataTable/__tests__/DataTableCore.test.tsx +31 -9
  252. package/src/components/DataTable/__tests__/a11y.basic.test.tsx +601 -0
  253. package/src/components/DataTable/__tests__/keyboard.test.tsx +615 -0
  254. package/src/components/DataTable/__tests__/pagination.modes.test.tsx +639 -0
  255. package/src/components/DataTable/__tests__/ssr.strict-mode.test.tsx.skip +330 -0
  256. package/src/components/DataTable/components/AccessDeniedPage.tsx +2 -2
  257. package/src/components/DataTable/components/ActionButtons.tsx +88 -104
  258. package/src/components/DataTable/components/DataTableCore.tsx +309 -337
  259. package/src/components/DataTable/components/DataTableErrorBoundary.tsx +4 -2
  260. package/src/components/DataTable/components/DataTableModals.tsx +22 -1
  261. package/src/components/DataTable/components/EditableRow.tsx +69 -84
  262. package/src/components/DataTable/components/EmptyState.tsx +5 -1
  263. package/src/components/DataTable/components/ImportModal.tsx +65 -36
  264. package/src/components/DataTable/components/PaginationControls.tsx +40 -100
  265. package/src/components/DataTable/components/UnifiedTableBody.tsx +125 -148
  266. package/src/components/DataTable/context/DataTableContext.tsx +1 -1
  267. package/src/components/DataTable/core/ColumnFactory.ts +5 -0
  268. package/src/components/DataTable/examples/HierarchicalActionsExample.tsx +12 -10
  269. package/src/components/DataTable/examples/HierarchicalExample.tsx +1 -1
  270. package/src/components/DataTable/examples/InitialPageSizeExample.tsx +1 -0
  271. package/src/components/DataTable/examples/PerformanceExample.tsx +1 -0
  272. package/src/components/DataTable/hooks/__tests__/useColumnOrderPersistence.test.ts +1 -5
  273. package/src/components/DataTable/hooks/__tests__/useColumnVisibilityPersistence.test.ts +167 -0
  274. package/src/components/DataTable/hooks/index.ts +7 -0
  275. package/src/components/DataTable/hooks/useColumnOrderPersistence.ts +32 -15
  276. package/src/components/DataTable/hooks/useColumnVisibilityPersistence.ts +102 -0
  277. package/src/components/DataTable/hooks/useDataTableConfiguration.ts +89 -0
  278. package/src/components/DataTable/hooks/useDataTableDataPipeline.ts +117 -0
  279. package/src/components/DataTable/hooks/useDataTablePermissions.ts +71 -27
  280. package/src/components/DataTable/hooks/useDataTableState.ts +39 -11
  281. package/src/components/DataTable/hooks/useEffectiveColumnOrder.ts +33 -0
  282. package/src/components/DataTable/hooks/useHierarchicalState.ts +15 -1
  283. package/src/components/DataTable/hooks/useKeyboardNavigation.ts +447 -0
  284. package/src/components/DataTable/hooks/useServerSideDataEffect.ts +94 -0
  285. package/src/components/DataTable/hooks/useTableColumns.ts +10 -7
  286. package/src/components/DataTable/hooks/useTableHandlers.ts +174 -0
  287. package/src/components/DataTable/index.ts +12 -3
  288. package/src/components/DataTable/types.ts +129 -9
  289. package/src/components/DataTable/utils/__tests__/exportUtils.test.ts +159 -22
  290. package/src/components/DataTable/utils/__tests__/flexibleImport.test.ts +111 -0
  291. package/src/components/DataTable/utils/__tests__/rowUtils.test.ts +15 -29
  292. package/src/components/DataTable/utils/a11yUtils.ts +244 -0
  293. package/src/components/DataTable/utils/debugTools.ts +609 -0
  294. package/src/components/DataTable/utils/exportUtils.ts +114 -16
  295. package/src/components/DataTable/utils/flexibleImport.ts +202 -32
  296. package/src/components/DataTable/utils/hierarchicalUtils.ts +1 -1
  297. package/src/components/DataTable/utils/index.ts +2 -0
  298. package/src/components/DataTable/utils/paginationUtils.ts +350 -0
  299. package/src/components/DataTable/utils/rowUtils.ts +6 -5
  300. package/src/components/NavigationMenu/NavigationMenu.test.tsx +19 -24
  301. package/src/components/NavigationMenu/NavigationMenu.tsx +19 -8
  302. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.security.test.tsx +1 -23
  303. package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +56 -6
  304. package/src/components/PaceLoginPage/PaceLoginPage.tsx +137 -13
  305. package/src/components/PublicLayout/__tests__/PublicPageHeader.test.tsx +1 -1
  306. package/src/components/Select/Select.tsx +1 -0
  307. package/src/components/examples/PermissionExample.tsx +173 -0
  308. package/src/examples/CorrectPublicPageImplementation.tsx +301 -0
  309. package/src/examples/PublicEventPage.tsx +274 -0
  310. package/src/examples/PublicPageApp.tsx +308 -0
  311. package/src/examples/PublicPageUsageExample.tsx +216 -0
  312. package/src/hooks/__tests__/useOrganisationPermissions.unit.test.tsx +12 -1
  313. package/src/hooks/__tests__/useOrganisationSecurity.unit.test.tsx +129 -17
  314. package/src/hooks/__tests__/useRBAC.unit.test.ts +151 -846
  315. package/src/hooks/useOrganisationPermissions.test.ts +42 -18
  316. package/src/hooks/useOrganisationPermissions.ts +12 -6
  317. package/src/hooks/useOrganisationSecurity.test.ts +138 -85
  318. package/src/hooks/useOrganisationSecurity.ts +41 -10
  319. package/src/index.ts +0 -1
  320. package/src/providers/AuthProvider.simplified.tsx +880 -0
  321. package/src/providers/UnifiedAuthProvider.test.simple.tsx +8 -8
  322. package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +29 -19
  323. package/src/providers/index.ts +0 -1
  324. package/src/providers/services/EventServiceProvider.tsx +19 -15
  325. package/src/providers/services/InactivityServiceProvider.tsx +19 -15
  326. package/src/providers/services/OrganisationServiceProvider.tsx +19 -15
  327. package/src/providers/services/UnifiedAuthProvider.tsx +156 -127
  328. package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +1 -1
  329. package/src/providers/services/__tests__/UnifiedAuthProvider.integration.test.tsx +3 -3
  330. package/src/rbac/README.md +1 -1
  331. package/src/rbac/__tests__/adapters.comprehensive.test.tsx +25 -27
  332. package/src/rbac/__tests__/auth-rbac-security.integration.test.tsx +313 -0
  333. package/src/rbac/__tests__/engine.comprehensive.test.ts +114 -348
  334. package/src/rbac/__tests__/rbac-engine-core-logic.test.ts +28 -110
  335. package/src/rbac/__tests__/rbac-engine-simplified.test.ts +33 -85
  336. package/src/rbac/__tests__/scenarios.user-role.test.tsx +2 -2
  337. package/src/rbac/adapters.tsx +26 -69
  338. package/src/rbac/api.test.ts +90 -27
  339. package/src/rbac/api.ts +61 -10
  340. package/src/rbac/audit.test.ts +33 -38
  341. package/src/rbac/audit.ts +21 -6
  342. package/src/rbac/cache.ts +33 -1
  343. package/src/rbac/components/NavigationGuard.tsx +11 -11
  344. package/src/rbac/components/NavigationProvider.test.tsx +11 -5
  345. package/src/rbac/components/NavigationProvider.tsx +37 -13
  346. package/src/rbac/components/PagePermissionGuard.tsx +111 -50
  347. package/src/rbac/components/PagePermissionProvider.tsx +5 -5
  348. package/src/rbac/components/PermissionEnforcer.tsx +11 -11
  349. package/src/rbac/components/RoleBasedRouter.tsx +5 -5
  350. package/src/rbac/components/SecureDataProvider.tsx +5 -5
  351. package/src/rbac/components/__tests__/NavigationGuard.test.tsx +8 -8
  352. package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +14 -14
  353. package/src/rbac/components/__tests__/PermissionEnforcer.test.tsx +12 -12
  354. package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +6 -6
  355. package/src/rbac/engine.test.simple.ts +19 -13
  356. package/src/rbac/engine.test.ts +1 -0
  357. package/src/rbac/engine.ts +330 -766
  358. package/src/rbac/errors.ts +156 -0
  359. package/src/rbac/hooks/usePermissions.ts +32 -10
  360. package/src/rbac/hooks/useRBAC.test.ts +126 -512
  361. package/src/rbac/hooks/useRBAC.ts +147 -193
  362. package/src/rbac/hooks/useResolvedScope.ts +12 -0
  363. package/src/rbac/index.ts +7 -4
  364. package/src/rbac/security.ts +109 -18
  365. package/src/rbac/types.ts +12 -1
  366. package/src/services/AuthService.ts +2 -15
  367. package/src/services/EventService.ts +43 -46
  368. package/src/services/OrganisationService.ts +51 -31
  369. package/src/services/__tests__/AuthService.test.ts +1 -1
  370. package/src/services/__tests__/EventService.test.ts +1 -1
  371. package/src/services/__tests__/OrganisationService.test.ts +1 -1
  372. package/src/services/base/BaseService.ts +8 -0
  373. package/src/styles/base.css +208 -0
  374. package/src/styles/semantic.css +24 -0
  375. package/src/types/database.generated.ts +7347 -0
  376. package/src/types/database.ts +20 -0
  377. package/src/utils/logger.ts +179 -0
  378. package/src/utils/organisationContext.ts +11 -4
  379. package/src/utils/storage/__tests__/helpers.unit.test.ts +6 -2
  380. package/dist/appNameResolver-UURKN7NF.js +0 -22
  381. package/dist/audit-6TOCAMKO.js.map +0 -1
  382. package/dist/chunk-B2WTCLCV.js.map +0 -1
  383. package/dist/chunk-FGMFQSHX.js.map +0 -1
  384. package/dist/chunk-K34IM5CT.js.map +0 -1
  385. package/dist/chunk-KHJS6VIA.js.map +0 -1
  386. package/dist/chunk-KK73ZB4E.js.map +0 -1
  387. package/dist/chunk-M5IWZRBT.js.map +0 -1
  388. package/dist/chunk-ULBI5JGB.js +0 -109
  389. package/dist/chunk-ULBI5JGB.js.map +0 -1
  390. package/dist/chunk-WN6XJWOS.js.map +0 -1
  391. package/dist/chunk-XLZ7U46Z.js.map +0 -1
  392. package/dist/chunk-Y6TXWPJO.js.map +0 -1
  393. package/docs/DOCUMENTATION_CHECKLIST.md +0 -281
  394. package/docs/TERMINOLOGY.md +0 -231
  395. package/docs/api/interfaces/RBACContextType.md +0 -468
  396. package/docs/api/interfaces/RBACProviderProps.md +0 -107
  397. package/docs/best-practices/performance-expansion.md +0 -473
  398. package/docs/breaking-changes.md +0 -179
  399. package/docs/consuming-app-example.md +0 -290
  400. package/docs/documentation-templates.md +0 -539
  401. package/docs/examples/navigation-menu-auth-fix.md +0 -344
  402. package/docs/getting-started/examples/basic-auth-app.md +0 -520
  403. package/docs/getting-started/examples/full-featured-app.md +0 -616
  404. package/docs/getting-started/quick-start.md +0 -376
  405. package/docs/implementation-guides/datatable-filtering.md +0 -313
  406. package/docs/implementation-guides/datatable-rbac-usage.md +0 -317
  407. package/docs/implementation-guides/hierarchical-datatable.md +0 -850
  408. package/docs/implementation-guides/large-datasets.md +0 -281
  409. package/docs/implementation-guides/performance.md +0 -403
  410. package/docs/migration/quick-migration-guide.md +0 -320
  411. package/docs/migration-guide.md +0 -193
  412. package/docs/migration-guides/unified-auth-provider-mandatory-timeouts.md +0 -226
  413. package/docs/performance/README.md +0 -551
  414. package/docs/style-guide.md +0 -964
  415. package/docs/troubleshooting/authentication-issues.md +0 -334
  416. package/docs/troubleshooting/debugging.md +0 -1117
  417. package/docs/troubleshooting/migration.md +0 -918
  418. package/src/__tests__/hooks/usePermissions.test.ts +0 -261
  419. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.rbac.test.tsx +0 -574
  420. package/src/hooks/__tests__/ServiceHooks.test.tsx +0 -613
  421. package/src/hooks/services/__tests__/useServiceHooks.test.tsx +0 -137
  422. package/src/hooks/services/usePermissions.ts +0 -70
  423. package/src/hooks/services/useRBACService.ts +0 -30
  424. package/src/hooks/usePermissionCheck.ts +0 -150
  425. package/src/providers/__tests__/ServiceProviders.test.tsx +0 -477
  426. package/src/providers/services/RBACServiceProvider.tsx +0 -79
  427. package/src/rbac/__tests__/integration.authflow.test.tsx +0 -119
  428. package/src/rbac/__tests__/integration.navigation.test.tsx +0 -69
  429. package/src/rbac/__tests__/integration.securedata.test.tsx +0 -92
  430. package/src/rbac/__tests__/integration.smoke.test.tsx +0 -73
  431. package/src/rbac/providers/RBACProvider.tsx +0 -645
  432. package/src/rbac/providers/__tests__/RBACProvider.integration.test.tsx +0 -688
  433. package/src/rbac/providers/__tests__/RBACProvider.test.tsx +0 -1186
  434. package/src/rbac/providers/index.ts +0 -11
  435. package/src/services/RBACService.ts +0 -522
  436. package/src/services/__tests__/RBACService.test.ts +0 -492
  437. package/src/services/interfaces/IRBACService.ts +0 -62
  438. package/src/utils/appNameResolver.test 2.ts +0 -494
  439. /package/dist/{DataTable-4GAVPIEG.js.map → DataTable-ETGVF4Y5.js.map} +0 -0
  440. /package/dist/{UnifiedAuthProvider-3NKDOSOK.js.map → UnifiedAuthProvider-P5SOJAQ6.js.map} +0 -0
  441. /package/dist/{api-DDMUKIUD.js.map → api-KG4A2X7P.js.map} +0 -0
  442. /package/dist/{appNameResolver-UURKN7NF.js.map → audit-65VNHEV2.js.map} +0 -0
  443. /package/dist/{chunk-NTNILOBC.js.map → chunk-5BO3MI5Y.js.map} +0 -0
  444. /package/dist/{chunk-URUTVZ7N.js.map → chunk-FL4ZCQLD.js.map} +0 -0
  445. /package/dist/{chunk-LW7MMEAQ.js.map → chunk-FT2M4R4F.js.map} +0 -0
  446. /package/dist/{chunk-AFGTSUAD.js.map → chunk-VSOKOFRF.js.map} +0 -0
  447. /package/docs/{app.css.example → styles/app.css.example} +0 -0
@@ -2,7 +2,7 @@ import {
2
2
  createAuditManager,
3
3
  emitAuditEvent,
4
4
  setGlobalAuditManager
5
- } from "./chunk-B2WTCLCV.js";
5
+ } from "./chunk-Q7APDV6H.js";
6
6
 
7
7
  // src/rbac/types.ts
8
8
  var RBACError = class extends Error {
@@ -13,6 +13,16 @@ var RBACError = class extends Error {
13
13
  this.name = "RBACError";
14
14
  }
15
15
  };
16
+ var PermissionDeniedError = class extends RBACError {
17
+ constructor(permission, context) {
18
+ super(
19
+ `Permission denied: ${permission}`,
20
+ "PERMISSION_DENIED",
21
+ { permission, ...context }
22
+ );
23
+ this.name = "PermissionDeniedError";
24
+ }
25
+ };
16
26
  var OrganisationContextRequiredError = class extends RBACError {
17
27
  constructor() {
18
28
  super(
@@ -31,6 +41,25 @@ var RBACNotInitializedError = class extends RBACError {
31
41
  this.name = "RBACNotInitializedError";
32
42
  }
33
43
  };
44
+ var InvalidScopeError = class extends RBACError {
45
+ constructor(scope, reason) {
46
+ super(
47
+ `Invalid scope provided: ${JSON.stringify(scope)}. ${reason}`,
48
+ "INVALID_SCOPE",
49
+ { scope, reason }
50
+ );
51
+ this.name = "InvalidScopeError";
52
+ }
53
+ };
54
+ var MissingUserContextError = class extends RBACError {
55
+ constructor() {
56
+ super(
57
+ "User context is required but not available. Make sure to wrap your app with an auth provider.",
58
+ "MISSING_USER_CONTEXT"
59
+ );
60
+ this.name = "MissingUserContextError";
61
+ }
62
+ };
34
63
 
35
64
  // src/rbac/cache.ts
36
65
  var RBACCache = class {
@@ -122,7 +151,30 @@ var RBACCache = class {
122
151
  };
123
152
  }
124
153
  /**
125
- * Generate cache key for permission check
154
+ * Generate cache key for permission check (simplified signature)
155
+ *
156
+ * @param userId - User ID
157
+ * @param permission - Permission string
158
+ * @param organisationId - Organisation ID (optional)
159
+ * @param eventId - Event ID (optional)
160
+ * @param appId - App ID (optional)
161
+ * @param pageId - Page ID (optional)
162
+ * @returns String cache key
163
+ */
164
+ static generateKey(userId, permission, organisationId, eventId, appId, pageId) {
165
+ const parts = [
166
+ "perm",
167
+ userId,
168
+ organisationId || "null",
169
+ eventId || "null",
170
+ appId || "null",
171
+ permission || "null",
172
+ pageId || "null"
173
+ ];
174
+ return parts.join(":");
175
+ }
176
+ /**
177
+ * Generate cache key for permission check (object signature)
126
178
  *
127
179
  * @param key - Permission cache key object
128
180
  * @returns String cache key
@@ -413,6 +465,124 @@ function initializeCacheInvalidation(supabase) {
413
465
  return globalCacheInvalidationManager;
414
466
  }
415
467
 
468
+ // src/rbac/errors.ts
469
+ var RATE_LIMIT_STATUS_CODES = /* @__PURE__ */ new Set([429]);
470
+ var AUTH_STATUS_CODES = /* @__PURE__ */ new Set([401]);
471
+ var AUTHZ_STATUS_CODES = /* @__PURE__ */ new Set([403]);
472
+ function normalize(value) {
473
+ if (!value) {
474
+ return "";
475
+ }
476
+ if (typeof value === "string") {
477
+ return value.toLowerCase();
478
+ }
479
+ if (value instanceof Error && typeof value.message === "string") {
480
+ return value.message.toLowerCase();
481
+ }
482
+ return String(value).toLowerCase();
483
+ }
484
+ function categorizeError(error) {
485
+ if (error instanceof PermissionDeniedError) {
486
+ return "authorization_error" /* AUTHORIZATION */;
487
+ }
488
+ if (error instanceof OrganisationContextRequiredError || error instanceof InvalidScopeError) {
489
+ return "validation_error" /* VALIDATION */;
490
+ }
491
+ if (error instanceof MissingUserContextError) {
492
+ return "authentication_error" /* AUTHENTICATION */;
493
+ }
494
+ if (error instanceof RBACError) {
495
+ switch (error.code) {
496
+ case "PERMISSION_DENIED":
497
+ return "authorization_error" /* AUTHORIZATION */;
498
+ case "ORGANISATION_CONTEXT_REQUIRED":
499
+ case "INVALID_SCOPE":
500
+ return "validation_error" /* VALIDATION */;
501
+ case "MISSING_USER_CONTEXT":
502
+ return "authentication_error" /* AUTHENTICATION */;
503
+ default:
504
+ break;
505
+ }
506
+ }
507
+ if (error && typeof error === "object") {
508
+ const status = error.status;
509
+ if (typeof status === "number") {
510
+ if (RATE_LIMIT_STATUS_CODES.has(status)) {
511
+ return "rate_limit_error" /* RATE_LIMIT */;
512
+ }
513
+ if (AUTH_STATUS_CODES.has(status)) {
514
+ return "authentication_error" /* AUTHENTICATION */;
515
+ }
516
+ if (AUTHZ_STATUS_CODES.has(status)) {
517
+ return "authorization_error" /* AUTHORIZATION */;
518
+ }
519
+ if (status >= 500) {
520
+ return "database_error" /* DATABASE */;
521
+ }
522
+ }
523
+ const codeValue = normalize(error.code);
524
+ if (codeValue) {
525
+ if (codeValue.includes("network")) {
526
+ return "network_error" /* NETWORK */;
527
+ }
528
+ if (codeValue.includes("postgres") || codeValue.includes("database") || codeValue.includes("db")) {
529
+ return "database_error" /* DATABASE */;
530
+ }
531
+ if (codeValue.includes("rate")) {
532
+ return "rate_limit_error" /* RATE_LIMIT */;
533
+ }
534
+ if (codeValue.includes("auth")) {
535
+ return "authentication_error" /* AUTHENTICATION */;
536
+ }
537
+ if (codeValue.includes("permission")) {
538
+ return "authorization_error" /* AUTHORIZATION */;
539
+ }
540
+ if (codeValue.includes("scope") || codeValue.includes("invalid")) {
541
+ return "validation_error" /* VALIDATION */;
542
+ }
543
+ }
544
+ }
545
+ const message = normalize(error);
546
+ if (message.includes("timeout") || message.includes("network") || message.includes("fetch")) {
547
+ return "network_error" /* NETWORK */;
548
+ }
549
+ if (message.includes("postgres") || message.includes("database") || message.includes("connection")) {
550
+ return "database_error" /* DATABASE */;
551
+ }
552
+ if (message.includes("rate limit") || message.includes("too many requests")) {
553
+ return "rate_limit_error" /* RATE_LIMIT */;
554
+ }
555
+ if (message.includes("permission") || message.includes("forbidden")) {
556
+ return "authorization_error" /* AUTHORIZATION */;
557
+ }
558
+ if (message.includes("auth") || message.includes("token") || message.includes("session")) {
559
+ return "authentication_error" /* AUTHENTICATION */;
560
+ }
561
+ if (message.includes("invalid") || message.includes("scope") || message.includes("validation")) {
562
+ return "validation_error" /* VALIDATION */;
563
+ }
564
+ return "unknown_error" /* UNKNOWN */;
565
+ }
566
+ function mapErrorCategoryToSecurityEventType(category) {
567
+ switch (category) {
568
+ case "authorization_error" /* AUTHORIZATION */:
569
+ return "permission_denied";
570
+ case "network_error" /* NETWORK */:
571
+ return "network_error";
572
+ case "database_error" /* DATABASE */:
573
+ return "database_error";
574
+ case "validation_error" /* VALIDATION */:
575
+ return "validation_error";
576
+ case "rate_limit_error" /* RATE_LIMIT */:
577
+ return "rate_limit_error";
578
+ case "authentication_error" /* AUTHENTICATION */:
579
+ return "authentication_error";
580
+ case "unknown_error" /* UNKNOWN */:
581
+ default:
582
+ return "unknown_error";
583
+ }
584
+ }
585
+
416
586
  // src/rbac/security.ts
417
587
  var RBACSecurityValidator = class {
418
588
  /**
@@ -555,10 +725,17 @@ var RBACSecurityValidator = class {
555
725
  case "permission_denied":
556
726
  return "low";
557
727
  case "invalid_input":
558
- return "medium";
559
728
  case "rate_limit_exceeded":
729
+ case "rate_limit_error":
730
+ case "network_error":
560
731
  return "medium";
732
+ case "validation_error":
733
+ return "high";
734
+ case "authentication_error":
735
+ case "database_error":
736
+ return "critical";
561
737
  case "suspicious_activity":
738
+ case "unknown_error":
562
739
  return "high";
563
740
  default:
564
741
  return "low";
@@ -574,7 +751,21 @@ var DEFAULT_SECURITY_CONFIG = {
574
751
  };
575
752
  var RBACSecurityMiddleware = class {
576
753
  constructor(config = DEFAULT_SECURITY_CONFIG) {
754
+ /**
755
+ * In-memory rate limiting cache (sliding window)
756
+ * Note: For production, this should use Redis or Supabase Edge Functions
757
+ */
758
+ this.rateLimitCache = /* @__PURE__ */ new Map();
577
759
  this.config = config;
760
+ this._startCleanupInterval();
761
+ }
762
+ /**
763
+ * Start periodic cleanup of expired entries
764
+ */
765
+ _startCleanupInterval() {
766
+ setInterval(() => {
767
+ this.clearExpiredEntries();
768
+ }, 5 * 60 * 1e3);
578
769
  }
579
770
  /**
580
771
  * Validate input before processing
@@ -584,13 +775,10 @@ var RBACSecurityMiddleware = class {
584
775
  */
585
776
  async validateInput(input, context) {
586
777
  const errors = [];
587
- if (!this.config.enableInputValidation) {
588
- return { isValid: true, errors: [] };
589
- }
590
778
  if (!RBACSecurityValidator.validateUserId(context.userId)) {
591
779
  errors.push("Invalid user ID format");
592
780
  }
593
- if (!RBACSecurityValidator.validateUUID(context.organisationId)) {
781
+ if (context.organisationId && !RBACSecurityValidator.validateUUID(context.organisationId)) {
594
782
  errors.push("Invalid organisation ID format");
595
783
  }
596
784
  if (input.permission && !RBACSecurityValidator.validatePermission(input.permission)) {
@@ -599,6 +787,14 @@ var RBACSecurityMiddleware = class {
599
787
  if (input.scope && !RBACSecurityValidator.validateScope(input.scope)) {
600
788
  errors.push("Invalid scope format");
601
789
  }
790
+ if (this.config.enableInputValidation) {
791
+ if (context.ipAddress && typeof context.ipAddress !== "string") {
792
+ errors.push("Invalid IP address format");
793
+ }
794
+ if (context.userAgent && typeof context.userAgent !== "string") {
795
+ errors.push("Invalid user agent format");
796
+ }
797
+ }
602
798
  if (errors.length > 0) {
603
799
  RBACSecurityValidator.logSecurityEvent({
604
800
  type: "invalid_input",
@@ -620,15 +816,49 @@ var RBACSecurityMiddleware = class {
620
816
  if (!this.config.enableRateLimiting) {
621
817
  return { isAllowed: true, remaining: this.config.maxPermissionChecksPerMinute };
622
818
  }
623
- const isAllowed = await RBACSecurityValidator.checkRateLimit(
624
- context.userId,
625
- "permission_check"
626
- );
819
+ const isAllowed = await this._checkRateLimitInternal(context.userId);
820
+ const remaining = isAllowed ? this.config.maxPermissionChecksPerMinute - this._getRequestCount(context.userId) : 0;
627
821
  return {
628
822
  isAllowed,
629
- remaining: this.config.maxPermissionChecksPerMinute
823
+ remaining: Math.max(0, remaining)
630
824
  };
631
825
  }
826
+ async _checkRateLimitInternal(userId) {
827
+ const now = Date.now();
828
+ const windowMs = 60 * 1e3;
829
+ const entries = this.rateLimitCache.get(userId) || [];
830
+ const validEntries = entries.filter((entry) => now - entry.timestamp < windowMs);
831
+ const requestCount = validEntries.length;
832
+ const isAllowed = requestCount < this.config.maxPermissionChecksPerMinute;
833
+ if (isAllowed) {
834
+ validEntries.push({ timestamp: now });
835
+ }
836
+ this.rateLimitCache.set(userId, validEntries);
837
+ return isAllowed;
838
+ }
839
+ _getRequestCount(userId) {
840
+ const now = Date.now();
841
+ const windowMs = 60 * 1e3;
842
+ const entries = this.rateLimitCache.get(userId) || [];
843
+ const validEntries = entries.filter((entry) => now - entry.timestamp < windowMs);
844
+ return validEntries.length;
845
+ }
846
+ /**
847
+ * Clear old rate limit entries to prevent memory leaks
848
+ * Should be called periodically (e.g., every 5 minutes)
849
+ */
850
+ clearExpiredEntries() {
851
+ const now = Date.now();
852
+ const windowMs = 60 * 1e3;
853
+ for (const [userId, entries] of this.rateLimitCache.entries()) {
854
+ const validEntries = entries.filter((entry) => now - entry.timestamp < windowMs);
855
+ if (validEntries.length === 0) {
856
+ this.rateLimitCache.delete(userId);
857
+ } else {
858
+ this.rateLimitCache.set(userId, validEntries);
859
+ }
860
+ }
861
+ }
632
862
  /**
633
863
  * Sanitize input data
634
864
  * @param input - Input to sanitize
@@ -649,65 +879,73 @@ var RBACEngine = class {
649
879
  /**
650
880
  * Check if a user has a specific permission
651
881
  *
882
+ * This method now delegates to the database RPC function for all the heavy lifting.
883
+ *
652
884
  * @param input - Permission check input
653
- * @param securityContext - Optional security context for enhanced validation
885
+ * @param securityContext - Security context for validation (required)
654
886
  * @returns Promise resolving to permission result
655
887
  */
656
888
  async isPermitted(input, securityContext) {
657
889
  const startTime = Date.now();
658
890
  const { userId, permission, scope, pageId } = input;
659
891
  let cacheHit = false;
660
- let cacheSource = "database";
892
+ let cacheSource = "rpc";
661
893
  try {
662
- if (securityContext) {
663
- const validation = await this.securityMiddleware.validateInput(input, securityContext);
664
- if (!validation.isValid) {
665
- RBACSecurityValidator.logSecurityEvent({
666
- type: "invalid_input",
667
- userId,
668
- details: { errors: validation.errors, input: JSON.stringify(input) }
669
- });
670
- return false;
671
- }
672
- const rateLimit = await this.securityMiddleware.checkRateLimit(securityContext);
673
- if (!rateLimit.isAllowed) {
674
- RBACSecurityValidator.logSecurityEvent({
675
- type: "rate_limit_exceeded",
676
- userId,
677
- details: { remaining: rateLimit.remaining }
678
- });
679
- return false;
680
- }
894
+ const validation = await this.securityMiddleware.validateInput(input, securityContext);
895
+ if (!validation.isValid) {
896
+ RBACSecurityValidator.logSecurityEvent({
897
+ type: "invalid_input",
898
+ userId,
899
+ details: { errors: validation.errors, input: JSON.stringify(input) }
900
+ });
901
+ return false;
681
902
  }
682
- if (securityContext) {
683
- if (!RBACSecurityValidator.validateUserId(userId)) {
684
- RBACSecurityValidator.logSecurityEvent({
685
- type: "invalid_input",
686
- userId,
687
- details: { error: "Invalid user ID format" }
688
- });
689
- return false;
690
- }
691
- if (!RBACSecurityValidator.validatePermission(permission)) {
692
- RBACSecurityValidator.logSecurityEvent({
693
- type: "invalid_input",
694
- userId,
695
- details: { error: "Invalid permission format", permission }
696
- });
697
- return false;
698
- }
699
- if (!RBACSecurityValidator.validateScope(scope)) {
700
- RBACSecurityValidator.logSecurityEvent({
701
- type: "invalid_input",
702
- userId,
703
- details: { error: "Invalid scope format", scope }
704
- });
705
- return false;
706
- }
903
+ const rateLimit = await this.securityMiddleware.checkRateLimit(securityContext);
904
+ if (!rateLimit.isAllowed) {
905
+ RBACSecurityValidator.logSecurityEvent({
906
+ type: "rate_limit_exceeded",
907
+ userId,
908
+ details: { remaining: rateLimit.remaining }
909
+ });
910
+ return false;
911
+ }
912
+ if (!RBACSecurityValidator.validateUserId(userId)) {
913
+ RBACSecurityValidator.logSecurityEvent({
914
+ type: "invalid_input",
915
+ userId,
916
+ details: { error: "Invalid user ID format" }
917
+ });
918
+ return false;
919
+ }
920
+ if (!RBACSecurityValidator.validatePermission(permission)) {
921
+ RBACSecurityValidator.logSecurityEvent({
922
+ type: "invalid_input",
923
+ userId,
924
+ details: { error: "Invalid permission format", permission }
925
+ });
926
+ return false;
927
+ }
928
+ if (!RBACSecurityValidator.validateScope(scope)) {
929
+ RBACSecurityValidator.logSecurityEvent({
930
+ type: "invalid_input",
931
+ userId,
932
+ details: { error: "Invalid scope format", scope }
933
+ });
934
+ return false;
707
935
  }
708
- const isSuperAdmin2 = await this.checkSuperAdmin(userId);
709
- if (isSuperAdmin2) {
710
- const duration = Date.now() - startTime;
936
+ const cacheKey = RBACCache.generateKey(
937
+ userId,
938
+ permission,
939
+ scope.organisationId,
940
+ scope.eventId,
941
+ scope.appId,
942
+ pageId
943
+ );
944
+ const cached = rbacCache.get(cacheKey);
945
+ if (cached !== null) {
946
+ cacheHit = true;
947
+ cacheSource = "memory";
948
+ const duration2 = Date.now() - startTime;
711
949
  if (scope.organisationId) {
712
950
  const resolvedPageId = await this.resolvePageId(pageId, scope.appId);
713
951
  await emitAuditEvent({
@@ -718,600 +956,281 @@ var RBACEngine = class {
718
956
  appId: scope.appId,
719
957
  pageId: resolvedPageId,
720
958
  permission,
721
- decision: true,
959
+ decision: cached,
722
960
  source: "api",
723
- bypass: true,
724
- duration_ms: duration
961
+ duration_ms: duration2,
962
+ cache_hit: true,
963
+ cache_source: "memory"
725
964
  });
726
965
  }
727
- return true;
966
+ return cached;
728
967
  }
729
- const validatedScope = await this.validateContextRequirements(scope, scope.appId);
730
- if (!validatedScope) {
731
- const duration = Date.now() - startTime;
732
- if (scope.organisationId) {
733
- const resolvedPageId = await this.resolvePageId(pageId, scope.appId);
734
- await emitAuditEvent({
735
- type: "permission_denied",
736
- userId,
737
- organisationId: scope.organisationId,
738
- eventId: scope.eventId,
739
- appId: scope.appId,
740
- pageId: resolvedPageId,
968
+ const { data, error } = await this.supabase.rpc("rbac_check_permission_simplified", {
969
+ p_user_id: userId,
970
+ p_permission: permission,
971
+ p_organisation_id: scope.organisationId || void 0,
972
+ p_event_id: scope.eventId || void 0,
973
+ p_app_id: scope.appId || void 0,
974
+ p_page_id: pageId || void 0
975
+ });
976
+ if (error) {
977
+ console.error("[RBACEngine] RPC error:", error);
978
+ const category = categorizeError(error);
979
+ const eventType = mapErrorCategoryToSecurityEventType(category);
980
+ const errorDetails = error;
981
+ RBACSecurityValidator.logSecurityEvent({
982
+ type: eventType,
983
+ userId,
984
+ details: {
985
+ error: errorDetails?.message || "RPC call failed",
986
+ code: errorDetails?.code,
987
+ hint: errorDetails?.hint,
988
+ details: errorDetails?.details,
741
989
  permission,
742
- source: "api",
743
- metadata: {
744
- reason: "invalid_context_requirements",
745
- app_requires_event: scope.appId ? await this.getAppConfig(scope.appId).then((config) => config?.requires_event) : null
746
- }
747
- });
748
- }
749
- return false;
750
- }
751
- const grants = await this.collectActiveGrants(userId, validatedScope, pageId);
752
- console.log("[RBACEngine] Collected grants:", grants);
753
- const denies = grants.filter((g) => g.type === "deny");
754
- console.log("[RBACEngine] Deny grants:", denies);
755
- for (const deny of denies) {
756
- const matches = this.permissionMatches(deny.permission, permission);
757
- console.log("[RBACEngine] Checking deny:", {
758
- denyPermission: deny.permission,
759
- requestedPermission: permission,
760
- matches
761
- });
762
- if (matches) {
763
- const duration = Date.now() - startTime;
764
- console.log("[RBACEngine] Permission DENIED by explicit deny rule");
765
- if (scope.organisationId) {
766
- const resolvedPageId = await this.resolvePageId(pageId, scope.appId);
767
- await emitAuditEvent({
768
- type: "permission_denied",
769
- userId,
770
- organisationId: scope.organisationId,
771
- eventId: scope.eventId,
772
- appId: scope.appId,
773
- pageId: resolvedPageId,
774
- permission,
775
- source: "api"
776
- });
777
- }
778
- return false;
779
- }
780
- }
781
- const allows = grants.filter((g) => g.type === "allow");
782
- console.log("[RBACEngine] Allow grants:", allows);
783
- let hasPermission2 = false;
784
- const scopeOrder = ["page", "eventApp", "organisation", "global"];
785
- for (const scopeType of scopeOrder) {
786
- const scopeAllows = allows.filter((g) => g.scope === scopeType);
787
- console.log(`[RBACEngine] Checking ${scopeType} allows:`, scopeAllows);
788
- for (const allow of scopeAllows) {
789
- console.log(`[RBACEngine] About to check permission match for ${scopeType}:`, {
790
- allowPermission: allow.permission,
791
- requestedPermission: permission,
792
- scopeType
793
- });
794
- const matches = this.permissionMatches(allow.permission, permission);
795
- console.log(`[RBACEngine] Permission match result:`, {
796
- scopeType,
797
- allowPermission: allow.permission,
798
- requestedPermission: permission,
799
- matches
800
- });
801
- if (matches) {
802
- console.log(`[RBACEngine] Permission GRANTED by ${scopeType} allow rule`);
803
- hasPermission2 = true;
804
- break;
990
+ scope: JSON.stringify(scope),
991
+ category
805
992
  }
806
- }
807
- if (hasPermission2) break;
993
+ });
994
+ return false;
808
995
  }
809
- const finalDecision = hasPermission2;
810
- const _duration = Date.now() - startTime;
811
- console.log("[RBACEngine] Final decision:", {
812
- userId,
813
- permission,
814
- pageId,
815
- hasPermission: hasPermission2,
816
- grantsCount: grants.length,
817
- allowsCount: allows.length,
818
- deniesCount: denies.length,
819
- duration: _duration
820
- });
996
+ const hasPermission2 = data === true;
997
+ rbacCache.set(cacheKey, hasPermission2, 6e4);
998
+ const duration = Date.now() - startTime;
821
999
  if (scope.organisationId) {
822
1000
  const resolvedPageId = await this.resolvePageId(pageId, scope.appId);
823
1001
  await emitAuditEvent({
824
- type: "permission_check",
1002
+ type: hasPermission2 ? "permission_check" : "permission_denied",
825
1003
  userId,
826
1004
  organisationId: scope.organisationId,
827
1005
  eventId: scope.eventId,
828
1006
  appId: scope.appId,
829
1007
  pageId: resolvedPageId,
830
1008
  permission,
831
- decision: finalDecision,
1009
+ decision: hasPermission2,
832
1010
  source: "api",
833
- duration_ms: _duration,
1011
+ duration_ms: duration,
834
1012
  cache_hit: cacheHit,
835
1013
  cache_source: cacheSource
836
1014
  });
837
1015
  }
838
- return finalDecision;
1016
+ return hasPermission2;
839
1017
  } catch (error) {
1018
+ const category = categorizeError(error);
1019
+ const eventType = mapErrorCategoryToSecurityEventType(category);
1020
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
840
1021
  RBACSecurityValidator.logSecurityEvent({
841
- type: "suspicious_activity",
1022
+ type: eventType,
842
1023
  userId,
843
1024
  details: {
844
- error: error instanceof Error ? error.message : "Unknown error",
1025
+ error: errorMessage,
845
1026
  permission,
846
- scope: JSON.stringify(scope)
1027
+ scope: JSON.stringify(scope),
1028
+ category
847
1029
  }
848
1030
  });
1031
+ console.error("[RBACEngine] Permission check failed:", error);
849
1032
  return false;
850
1033
  }
851
1034
  }
852
1035
  /**
853
1036
  * Get user's access level in a scope
854
1037
  *
1038
+ * This is derived from roles, not permissions.
1039
+ *
855
1040
  * @param input - Access level input
856
1041
  * @returns Promise resolving to access level
857
1042
  */
858
1043
  async getAccessLevel(input) {
859
1044
  const { userId, scope } = input;
860
- const isSuperAdmin2 = await this.checkSuperAdmin(userId);
861
- if (isSuperAdmin2) {
862
- return "super";
863
- }
864
- const validatedScope = await this.validateContextRequirements(scope, scope.appId);
865
- if (!validatedScope) {
866
- return "viewer";
867
- }
868
1045
  const cacheKey = RBACCache.generateAccessLevelKey(
869
1046
  userId,
870
- validatedScope.organisationId,
871
- validatedScope.eventId,
872
- validatedScope.appId
1047
+ scope.organisationId || "",
1048
+ scope.eventId,
1049
+ scope.appId
873
1050
  );
874
1051
  const cached = rbacCache.get(cacheKey);
875
1052
  if (cached) {
876
1053
  return cached;
877
1054
  }
878
- const orgRole = await this.getOrganisationRole(userId, validatedScope.organisationId);
879
- if (orgRole === "org_admin") {
880
- rbacCache.set(cacheKey, "admin");
881
- return "admin";
1055
+ const isSuperAdmin2 = await this.checkSuperAdmin(userId);
1056
+ if (isSuperAdmin2) {
1057
+ rbacCache.set(cacheKey, "super", 6e4);
1058
+ return "super";
882
1059
  }
883
- if (validatedScope.eventId && validatedScope.appId) {
884
- const eventRole = await this.getEventAppRole(userId, validatedScope.eventId, validatedScope.appId);
885
- if (eventRole === "event_admin") {
886
- rbacCache.set(cacheKey, "admin");
1060
+ if (scope.organisationId) {
1061
+ const { data: orgRole } = await this.supabase.from("rbac_organisation_roles").select("role").eq("user_id", userId).eq("organisation_id", scope.organisationId).eq("status", "active").is("revoked_at", null).single();
1062
+ if (orgRole?.role === "org_admin") {
1063
+ rbacCache.set(cacheKey, "admin", 6e4);
887
1064
  return "admin";
888
1065
  }
889
- if (eventRole === "planner") {
890
- rbacCache.set(cacheKey, "planner");
1066
+ }
1067
+ if (scope.eventId && scope.appId) {
1068
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1069
+ const { data: eventRole } = await this.supabase.from("rbac_event_app_roles").select("role").eq("user_id", userId).eq("event_id", scope.eventId).eq("app_id", scope.appId).eq("status", "active").lte("valid_from", now).or(`valid_to.is.null,valid_to.gte.${now}`).single();
1070
+ if (eventRole?.role === "event_admin") {
1071
+ rbacCache.set(cacheKey, "admin", 6e4);
1072
+ return "admin";
1073
+ }
1074
+ if (eventRole?.role === "planner") {
1075
+ rbacCache.set(cacheKey, "planner", 6e4);
891
1076
  return "planner";
892
1077
  }
893
- if (eventRole === "participant") {
894
- rbacCache.set(cacheKey, "participant");
1078
+ if (eventRole?.role === "participant") {
1079
+ rbacCache.set(cacheKey, "participant", 6e4);
895
1080
  return "participant";
896
1081
  }
897
1082
  }
898
- rbacCache.set(cacheKey, "viewer");
1083
+ rbacCache.set(cacheKey, "viewer", 6e4);
899
1084
  return "viewer";
900
1085
  }
901
1086
  /**
902
1087
  * Get user's permission map for a scope
903
1088
  *
1089
+ * This builds a map of page IDs to allowed operations.
1090
+ * Uses the simplified RPC for each permission check.
1091
+ *
904
1092
  * @param input - Permission map input
905
1093
  * @returns Promise resolving to permission map
906
1094
  */
907
1095
  async getPermissionMap(input) {
908
1096
  const { userId, scope } = input;
1097
+ const cacheKey = RBACCache.generatePermissionMapKey(
1098
+ userId,
1099
+ scope.organisationId || "",
1100
+ scope.eventId,
1101
+ scope.appId
1102
+ );
909
1103
  const isSuperAdmin2 = await this.checkSuperAdmin(userId);
910
1104
  if (isSuperAdmin2) {
911
- return {};
1105
+ const wildcardMap = { "*": true };
1106
+ rbacCache.set(cacheKey, wildcardMap, 6e4);
1107
+ return wildcardMap;
912
1108
  }
913
- const validatedScope = await this.validateContextRequirements(scope, scope.appId);
914
- if (!validatedScope) {
1109
+ if (!scope.organisationId) {
915
1110
  return {};
916
1111
  }
917
- const cacheKey = RBACCache.generatePermissionMapKey(
918
- userId,
919
- validatedScope.organisationId,
920
- validatedScope.eventId,
921
- validatedScope.appId
922
- );
923
1112
  const cached = rbacCache.get(cacheKey);
924
1113
  if (cached) {
925
1114
  return cached;
926
1115
  }
927
1116
  const permissionMap = {};
928
- if (validatedScope.appId) {
929
- const { data: pages } = await this.supabase.from("rbac_app_pages").select("id, page_name").eq("app_id", validatedScope.appId);
1117
+ if (scope.appId) {
1118
+ const { data: pages } = await this.supabase.from("rbac_app_pages").select("id, page_name").eq("app_id", scope.appId);
930
1119
  if (pages) {
1120
+ const securityContext = {
1121
+ userId,
1122
+ organisationId: scope.organisationId,
1123
+ timestamp: /* @__PURE__ */ new Date()
1124
+ };
931
1125
  for (const page of pages) {
932
- const operations = [];
933
1126
  for (const operation of ["read", "create", "update", "delete"]) {
934
- const hasPermission2 = await this.isPermitted({
935
- userId,
936
- scope: validatedScope,
937
- permission: `${operation}:${page.page_name}`,
938
- pageId: page.id
939
- });
940
- if (hasPermission2) {
941
- operations.push(operation);
942
- }
1127
+ const hasPermission2 = await this.isPermitted(
1128
+ {
1129
+ userId,
1130
+ scope,
1131
+ permission: `${operation}:${page.page_name}`,
1132
+ pageId: page.id
1133
+ },
1134
+ securityContext
1135
+ );
1136
+ const permissionKey = `${operation}:${page.page_name}`;
1137
+ permissionMap[permissionKey] = hasPermission2;
943
1138
  }
944
- permissionMap[page.id] = operations;
945
1139
  }
946
1140
  }
947
1141
  }
948
- rbacCache.set(cacheKey, permissionMap);
1142
+ rbacCache.set(cacheKey, permissionMap, 6e4);
949
1143
  return permissionMap;
950
1144
  }
951
- /**
952
- * Check if user is super admin
953
- *
954
- * Directly queries the rbac_global_roles table to check for super_admin role.
955
- * This is consistent with how other RPC functions (rbac_permissions_get, etc.) check
956
- * for super admin status - they all use direct queries to the rbac_global_roles table.
957
- *
958
- * @param userId - User ID
959
- * @returns Promise resolving to super admin status
960
- */
961
- async checkSuperAdmin(userId) {
962
- const cacheKey = `super_admin:${userId}`;
963
- const cached = rbacCache.get(cacheKey);
964
- if (cached !== null) {
965
- return cached;
966
- }
967
- const now = (/* @__PURE__ */ new Date()).toISOString();
968
- const { data, error } = await this.supabase.from("rbac_global_roles").select("role").eq("user_id", userId).eq("role", "super_admin").lte("valid_from", now).or(`valid_to.is.null,valid_to.gte.${now}`).limit(1);
969
- const isSuperAdmin2 = !error && data && data.length > 0;
970
- rbacCache.set(cacheKey, isSuperAdmin2, 6e4);
971
- return Boolean(isSuperAdmin2);
972
- }
973
- /**
974
- * Get app configuration including requires_event setting
975
- *
976
- * @param appId - App ID
977
- * @returns Promise resolving to app configuration
978
- */
979
- async getAppConfig(appId) {
980
- const { data, error } = await this.supabase.from("rbac_apps").select("requires_event").eq("id", appId).eq("is_active", true).single();
981
- if (error || !data) {
982
- return null;
983
- }
984
- return { requires_event: data.requires_event };
985
- }
986
- /**
987
- * Resolve organisation ID from event ID
988
- *
989
- * @param eventId - Event ID
990
- * @returns Promise resolving to organisation ID
991
- */
992
- async resolveOrganisationFromEvent(eventId) {
993
- const { data, error } = await this.supabase.from("event").select("organisation_id").eq("id", eventId).single();
994
- if (error || !data) {
995
- return null;
996
- }
997
- return data.organisation_id;
998
- }
999
- /**
1000
- * Validate context requirements based on app configuration
1001
- *
1002
- * @param scope - Permission scope
1003
- * @param appId - Optional app ID
1004
- * @returns Promise resolving to validated scope with resolved organisation ID
1005
- */
1006
- async validateContextRequirements(scope, appId) {
1007
- if (appId) {
1008
- const appConfig = await this.getAppConfig(appId);
1009
- if (!appConfig) {
1145
+ async resolveAppContext(input) {
1146
+ try {
1147
+ const { userId, appName } = input;
1148
+ const { data, error } = await this.supabase.rpc("util_app_resolve", {
1149
+ p_user_id: userId,
1150
+ p_app_name: appName
1151
+ });
1152
+ if (error) {
1153
+ console.error("[RBACEngine] Failed to resolve app context:", error);
1010
1154
  return null;
1011
1155
  }
1012
- if (appConfig.requires_event) {
1013
- if (!scope.eventId) {
1014
- return null;
1015
- }
1016
- if (!scope.organisationId) {
1017
- const resolvedOrgId = await this.resolveOrganisationFromEvent(scope.eventId);
1018
- if (!resolvedOrgId) {
1019
- return null;
1020
- }
1021
- return {
1022
- ...scope,
1023
- organisationId: resolvedOrgId
1024
- };
1025
- }
1026
- return scope;
1027
- } else {
1028
- if (!scope.organisationId) {
1029
- return null;
1030
- }
1031
- return scope;
1156
+ if (!data || data.length === 0) {
1157
+ return null;
1032
1158
  }
1033
- }
1034
- if (!scope.organisationId) {
1159
+ const appData = data[0];
1160
+ if (!appData?.app_id) {
1161
+ return null;
1162
+ }
1163
+ return {
1164
+ appId: appData.app_id,
1165
+ hasAccess: appData.has_access !== false
1166
+ };
1167
+ } catch (error) {
1168
+ console.error("[RBACEngine] Unexpected error resolving app context:", error);
1035
1169
  return null;
1036
1170
  }
1037
- return scope;
1038
1171
  }
1039
- /**
1040
- * Collect active grants for a user in a scope
1041
- *
1042
- * @param userId - User ID
1043
- * @param scope - Permission scope
1044
- * @param pageId - Optional page ID
1045
- * @returns Promise resolving to grants array
1046
- *
1047
- * PRECEDENCE ORDER (closest scope first): page eventApp → organisation → global
1048
- */
1049
- async collectActiveGrants(userId, scope, pageId) {
1050
- const grants = [];
1051
- const now = (/* @__PURE__ */ new Date()).toISOString();
1052
- const userRoles = [];
1053
- if (scope.eventId && scope.appId) {
1054
- const { data: eventRoles } = await this.supabase.from("rbac_event_app_roles").select("role, status, valid_from, valid_to").eq("user_id", userId).eq("event_id", scope.eventId).eq("app_id", scope.appId).eq("status", "active").lte("valid_from", now).or(`valid_to.is.null,valid_to.gte.${now}`);
1055
- if (eventRoles) {
1056
- userRoles.push(...eventRoles.map((r) => r.role));
1057
- }
1058
- }
1059
- if (scope.organisationId) {
1060
- const { data: orgRoles } = await this.supabase.from("rbac_organisation_roles").select("role, status, valid_from, valid_to").eq("user_id", userId).eq("organisation_id", scope.organisationId).eq("status", "active").lte("valid_from", now).or(`valid_to.is.null,valid_to.gte.${now}`);
1061
- if (orgRoles) {
1062
- userRoles.push(...orgRoles.map((r) => r.role));
1172
+ async getRoleContext(input) {
1173
+ const result = {
1174
+ globalRole: null,
1175
+ organisationRole: null,
1176
+ eventAppRole: null
1177
+ };
1178
+ try {
1179
+ const { userId, scope } = input;
1180
+ const { data, error } = await this.supabase.rpc("rbac_permissions_get", {
1181
+ p_user_id: userId,
1182
+ p_organisation_id: scope.organisationId || null,
1183
+ p_event_id: scope.eventId || null,
1184
+ p_app_id: scope.appId || null
1185
+ });
1186
+ if (error) {
1187
+ console.error("[RBACEngine] Failed to load role context:", error);
1188
+ return result;
1063
1189
  }
1064
- }
1065
- console.log("[collectActiveGrants] User roles:", userRoles);
1066
- if (pageId) {
1067
- let resolvedPageId = null;
1068
- if (typeof pageId === "string") {
1069
- const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
1070
- if (uuidRegex.test(pageId)) {
1071
- resolvedPageId = pageId;
1072
- } else {
1073
- const appId = scope.appId;
1074
- if (appId) {
1075
- const { data: page } = await this.supabase.from("rbac_app_pages").select("id").eq("app_id", appId).eq("page_name", pageId).single();
1076
- resolvedPageId = page?.id || null;
1077
- }
1078
- }
1079
- } else {
1080
- resolvedPageId = pageId;
1190
+ if (!Array.isArray(data)) {
1191
+ return result;
1081
1192
  }
1082
- if (resolvedPageId && scope.appId) {
1083
- console.log("[collectActiveGrants] Fetching page permissions via RPC for page:", resolvedPageId);
1084
- let pageName = null;
1085
- if (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)) {
1086
- pageName = pageId;
1087
- } else {
1088
- const { data: page } = await this.supabase.from("rbac_app_pages").select("page_name").eq("id", resolvedPageId).single();
1089
- pageName = page?.page_name || null;
1193
+ for (const permission of data) {
1194
+ if (permission.permission_type === "all_permissions") {
1195
+ result.globalRole = "super_admin";
1090
1196
  }
1091
- const rpcResult = await this.supabase.rpc("rbac_permissions_get", {
1092
- p_user_id: userId,
1093
- p_app_id: scope.appId,
1094
- p_event_id: scope.eventId || null,
1095
- p_organisation_id: scope.organisationId || null,
1096
- p_page_id: resolvedPageId
1097
- });
1098
- const { data: rpcPermissions } = rpcResult;
1099
- console.log("[collectActiveGrants] RPC page permissions:", rpcPermissions);
1100
- if (rpcPermissions && pageName) {
1101
- const pagePerms = rpcPermissions.filter(
1102
- (p) => p.permission_type !== "all_permissions" && p.permission_type !== "organisation_access" && p.permission_type !== "event_app_access"
1103
- );
1104
- for (const perm of pagePerms) {
1105
- if (userRoles.includes(perm.role_name)) {
1106
- console.log("[collectActiveGrants] Adding page grant:", { operation: perm.permission_type, role: perm.role_name, allowed: perm.has_permission, pageName });
1107
- grants.push({
1108
- type: perm.has_permission ? "allow" : "deny",
1109
- // Use permission_type directly as it already includes the page name
1110
- permission: perm.permission_type,
1111
- scope: "page",
1112
- source: "rbac_page_permissions"
1113
- });
1114
- }
1115
- }
1197
+ if (permission.permission_type === "organisation_access") {
1198
+ result.organisationRole = permission.role_name;
1116
1199
  }
1117
- }
1118
- }
1119
- const { data: globalRoles } = await this.supabase.from("rbac_global_roles").select("role, valid_from, valid_to").eq("user_id", userId).lte("valid_from", now).or(`valid_to.is.null,valid_to.gte.${now}`);
1120
- if (globalRoles) {
1121
- for (const role of globalRoles) {
1122
- if (role.role === "super_admin") {
1123
- grants.push(
1124
- { type: "allow", permission: "read:*", scope: "global", source: "rbac_global_roles" },
1125
- { type: "allow", permission: "create:*", scope: "global", source: "rbac_global_roles" },
1126
- { type: "allow", permission: "update:*", scope: "global", source: "rbac_global_roles" },
1127
- { type: "allow", permission: "delete:*", scope: "global", source: "rbac_global_roles" }
1128
- );
1200
+ if (permission.permission_type === "event_app_access") {
1201
+ result.eventAppRole = permission.role_name;
1129
1202
  }
1130
1203
  }
1204
+ return result;
1205
+ } catch (error) {
1206
+ console.error("[RBACEngine] Unexpected error loading role context:", error);
1207
+ return result;
1131
1208
  }
1132
- console.log("[collectActiveGrants] Final grants:", grants);
1133
- return grants;
1134
- }
1135
- /**
1136
- * Check page-specific permissions
1137
- *
1138
- * @param userId - User ID
1139
- * @param pageId - Page ID
1140
- * @param permission - Permission to check
1141
- * @param scope - Permission scope
1142
- * @returns Promise resolving to page permission result
1143
- */
1144
- async checkPagePermissions(userId, pageId, permission, scope) {
1145
- if (!pageId) {
1146
- return true;
1147
- }
1148
- const [operation] = permission.split(":");
1149
- const userRoles = [];
1150
- if (scope.organisationId) {
1151
- const orgRole = await this.getOrganisationRole(userId, scope.organisationId);
1152
- if (orgRole) {
1153
- userRoles.push(orgRole);
1154
- }
1155
- }
1156
- if (scope.eventId && scope.appId) {
1157
- const eventRole = await this.getEventAppRole(userId, scope.eventId, scope.appId);
1158
- if (eventRole) {
1159
- userRoles.push(eventRole);
1160
- }
1161
- }
1162
- let resolvedPageId = null;
1163
- if (typeof pageId === "string") {
1164
- const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
1165
- if (uuidRegex.test(pageId)) {
1166
- resolvedPageId = pageId;
1167
- } else {
1168
- let appId = scope.appId;
1169
- if (!appId) {
1170
- const appName = import.meta.env.VITE_APP_NAME || import.meta.env.REACT_APP_NAME;
1171
- if (appName) {
1172
- const { data: app } = await this.supabase.from("rbac_apps").select("id").eq("name", appName).eq("is_active", true).single();
1173
- if (app) {
1174
- appId = app.id;
1175
- }
1176
- }
1177
- }
1178
- if (appId) {
1179
- const { data: page } = await this.supabase.from("rbac_app_pages").select("id").eq("app_id", appId).eq("page_name", pageId).single();
1180
- resolvedPageId = page?.id || null;
1181
- }
1182
- }
1183
- } else {
1184
- resolvedPageId = pageId;
1185
- }
1186
- if (!resolvedPageId) {
1187
- return false;
1188
- }
1189
- const { data: pagePermissions } = await this.supabase.from("rbac_page_permissions").select("allowed").eq("app_page_id", resolvedPageId).eq("operation", operation).in("role_name", userRoles).single();
1190
- return pagePermissions?.allowed ?? false;
1191
- }
1192
- /**
1193
- * Get organisation role for a user
1194
- *
1195
- * @param userId - User ID
1196
- * @param organisationId - Organisation ID
1197
- * @returns Promise resolving to organisation role
1198
- */
1199
- async getOrganisationRole(userId, organisationId) {
1200
- const { data, error } = await this.supabase.from("rbac_organisation_roles").select("role").eq("user_id", userId).eq("organisation_id", organisationId).eq("status", "active").single();
1201
- return error ? null : data?.role || null;
1202
1209
  }
1203
1210
  /**
1204
- * Get event-app role for a user
1211
+ * Check if user is super admin
1205
1212
  *
1206
1213
  * @param userId - User ID
1207
- * @param eventId - Event ID
1208
- * @param appId - App ID
1209
- * @returns Promise resolving to event-app role
1210
- */
1211
- async getEventAppRole(userId, eventId, appId) {
1212
- const { data, error } = await this.supabase.from("rbac_event_app_roles").select("role, status, valid_from, valid_to").eq("user_id", userId).eq("event_id", eventId).eq("app_id", appId).eq("status", "active").lte("valid_from", (/* @__PURE__ */ new Date()).toISOString()).or(`valid_to.is.null,valid_to.gte.${(/* @__PURE__ */ new Date()).toISOString()}`).single();
1213
- return error ? null : data?.role || null;
1214
- }
1215
- /**
1216
- * Get permission for organisation role
1217
- *
1218
- * @param role - Organisation role
1219
- * @returns Permission string
1220
- */
1221
- getPermissionForOrgRole(role) {
1222
- switch (role) {
1223
- case "org_admin":
1224
- return "read:*";
1225
- // Will be expanded to all CRUD in collectActiveGrants
1226
- case "leader":
1227
- return "read:organisation.*";
1228
- // Will be expanded to all CRUD in collectActiveGrants
1229
- case "member":
1230
- return "read:organisation.*";
1231
- case "supporter":
1232
- return "read:organisation.public";
1233
- default:
1234
- return "read:organisation.public";
1235
- }
1236
- }
1237
- /**
1238
- * Get permission for event-app role
1239
- *
1240
- * @param role - Event-app role
1241
- * @returns Permission string
1242
- */
1243
- getPermissionForEventRole(role) {
1244
- switch (role) {
1245
- case "event_admin":
1246
- return "read:event.*";
1247
- // Will be expanded to all CRUD in collectActiveGrants
1248
- case "planner":
1249
- return "read:event.planning";
1250
- // Will be expanded to all CRUD in collectActiveGrants
1251
- case "participant":
1252
- return "read:event.*";
1253
- case "viewer":
1254
- return "read:event.public";
1255
- default:
1256
- return "read:event.public";
1257
- }
1258
- }
1259
- /**
1260
- * Check if a permission matches another permission
1261
- *
1262
- * @param grantPermission - Permission from grant
1263
- * @param requestedPermission - Requested permission
1264
- * @returns True if permissions match
1214
+ * @returns Promise resolving to super admin status
1265
1215
  */
1266
- permissionMatches(grantPermission, requestedPermission) {
1267
- console.log("[permissionMatches] Checking:", { grantPermission, requestedPermission });
1268
- if (grantPermission === requestedPermission) {
1269
- console.log("[permissionMatches] Exact match found");
1270
- return true;
1271
- }
1272
- if (grantPermission.endsWith(":*") || grantPermission.endsWith(".*")) {
1273
- const [grantOp, grantResource] = grantPermission.split(":");
1274
- const [requestedOp, requestedResource] = requestedPermission.split(":");
1275
- console.log("[permissionMatches] Wildcard check:", {
1276
- grantOp,
1277
- grantResource,
1278
- requestedOp,
1279
- requestedResource,
1280
- operationsMatch: grantOp === requestedOp
1281
- });
1282
- if (grantOp === requestedOp) {
1283
- const prefix = grantResource.slice(0, -1);
1284
- const matches = prefix === "" || requestedResource.startsWith(prefix);
1285
- console.log("[permissionMatches] Wildcard match result:", { prefix, matches });
1286
- return matches;
1287
- }
1288
- }
1289
- if (grantPermission.includes("*")) {
1290
- const [grantOp, grantResource] = grantPermission.split(":");
1291
- const [requestedOp, requestedResource] = requestedPermission.split(":");
1292
- console.log("[permissionMatches] Other wildcard check:", {
1293
- grantOp,
1294
- grantResource,
1295
- requestedOp,
1296
- requestedResource,
1297
- operationsMatch: grantOp === requestedOp
1298
- });
1299
- if (grantOp === requestedOp) {
1300
- const prefix = grantResource.replace("*", "");
1301
- const matches = requestedResource.startsWith(prefix);
1302
- console.log("[permissionMatches] Other wildcard match result:", { prefix, matches });
1303
- return matches;
1304
- }
1216
+ async checkSuperAdmin(userId) {
1217
+ const cacheKey = `super_admin:${userId}`;
1218
+ const cached = rbacCache.get(cacheKey);
1219
+ if (cached !== null) {
1220
+ return cached;
1305
1221
  }
1306
- console.log("[permissionMatches] No match found");
1307
- return false;
1222
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1223
+ const { data, error } = await this.supabase.from("rbac_global_roles").select("role").eq("user_id", userId).eq("role", "super_admin").lte("valid_from", now).or(`valid_to.is.null,valid_to.gte.${now}`).limit(1);
1224
+ const isSuperAdmin2 = !error && data && data.length > 0;
1225
+ rbacCache.set(cacheKey, isSuperAdmin2, 6e4);
1226
+ return Boolean(isSuperAdmin2);
1308
1227
  }
1309
1228
  /**
1310
1229
  * Resolve a page ID to UUID if it's a page name
1311
1230
  *
1312
1231
  * @param pageId - Page ID (UUID) or page name (string)
1313
1232
  * @param appId - App ID to look up the page
1314
- * @returns Resolved page ID (UUID) or original pageId if it's already a UUID or can't be resolved
1233
+ * @returns Resolved page ID (UUID) or original pageId
1315
1234
  */
1316
1235
  async resolvePageId(pageId, appId) {
1317
1236
  if (!pageId) {
@@ -1447,9 +1366,24 @@ async function getPermissionMap(input) {
1447
1366
  const engine = getEngine();
1448
1367
  return engine.getPermissionMap(input);
1449
1368
  }
1369
+ async function resolveAppContext(input) {
1370
+ const engine = getEngine();
1371
+ return engine.resolveAppContext(input);
1372
+ }
1373
+ async function getRoleContext(input) {
1374
+ const engine = getEngine();
1375
+ return engine.getRoleContext(input);
1376
+ }
1450
1377
  async function isPermitted(input) {
1451
1378
  const engine = getEngine();
1452
- return engine.isPermitted(input);
1379
+ const securityContext = {
1380
+ userId: input.userId,
1381
+ organisationId: input.scope.organisationId || input.userId,
1382
+ // Fallback to userId as UUID
1383
+ timestamp: /* @__PURE__ */ new Date()
1384
+ // Optional fields can be omitted
1385
+ };
1386
+ return engine.isPermitted(input, securityContext);
1453
1387
  }
1454
1388
  async function isPermittedCached(input) {
1455
1389
  const { userId, scope, permission, pageId } = input;
@@ -1503,8 +1437,20 @@ async function isSuperAdmin(userId) {
1503
1437
  return engine["checkSuperAdmin"](userId);
1504
1438
  }
1505
1439
  async function getAppConfig(appId) {
1506
- const engine = getEngine();
1507
- return engine["getAppConfig"](appId);
1440
+ console.warn("[RBAC] getAppConfig called without Supabase client - returning null");
1441
+ return null;
1442
+ }
1443
+ async function getAppConfigWithClient(client, appId) {
1444
+ try {
1445
+ const { data, error } = await client.from("rbac_apps").select("requires_event").eq("id", appId).eq("is_active", true).single();
1446
+ if (error || !data) {
1447
+ return null;
1448
+ }
1449
+ return { requires_event: data.requires_event };
1450
+ } catch (err) {
1451
+ console.error("[RBAC] Error fetching app config:", err);
1452
+ return null;
1453
+ }
1508
1454
  }
1509
1455
  async function isOrganisationAdmin(userId, organisationId) {
1510
1456
  const accessLevel = await getAccessLevel({
@@ -1555,6 +1501,8 @@ export {
1555
1501
  setupRBAC,
1556
1502
  getAccessLevel,
1557
1503
  getPermissionMap,
1504
+ resolveAppContext,
1505
+ getRoleContext,
1558
1506
  isPermitted,
1559
1507
  isPermittedCached,
1560
1508
  hasPermission,
@@ -1562,6 +1510,7 @@ export {
1562
1510
  hasAllPermissions,
1563
1511
  isSuperAdmin,
1564
1512
  getAppConfig,
1513
+ getAppConfigWithClient,
1565
1514
  isOrganisationAdmin,
1566
1515
  isEventAdmin,
1567
1516
  invalidateUserCache,
@@ -1570,4 +1519,4 @@ export {
1570
1519
  invalidateAppCache,
1571
1520
  clearCache
1572
1521
  };
1573
- //# sourceMappingURL=chunk-FGMFQSHX.js.map
1522
+ //# sourceMappingURL=chunk-S63MFSY6.js.map