@jmruthers/pace-core 0.6.5 → 0.6.7

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 (473) hide show
  1. package/CHANGELOG.md +104 -0
  2. package/README.md +5 -403
  3. package/audit-tool/00-dependencies.cjs +394 -0
  4. package/audit-tool/audits/01-pace-core-compliance.cjs +556 -0
  5. package/audit-tool/audits/02-project-structure.cjs +255 -0
  6. package/audit-tool/audits/03-architecture.cjs +196 -0
  7. package/audit-tool/audits/04-code-quality.cjs +149 -0
  8. package/audit-tool/audits/05-styling.cjs +224 -0
  9. package/audit-tool/audits/06-security-rbac.cjs +544 -0
  10. package/audit-tool/audits/07-api-tech-stack.cjs +301 -0
  11. package/audit-tool/audits/08-testing-documentation.cjs +202 -0
  12. package/audit-tool/audits/09-operations.cjs +208 -0
  13. package/audit-tool/index.cjs +291 -0
  14. package/audit-tool/utils/code-utils.cjs +218 -0
  15. package/audit-tool/utils/file-utils.cjs +230 -0
  16. package/audit-tool/utils/report-utils.cjs +241 -0
  17. package/core-usage-manifest.json +93 -0
  18. package/cursor-rules/00-standards-overview.mdc +156 -0
  19. package/cursor-rules/01-pace-core-compliance.mdc +586 -0
  20. package/cursor-rules/02-project-structure.mdc +42 -4
  21. package/cursor-rules/{03-solid-principles.mdc → 03-architecture.mdc} +126 -10
  22. package/cursor-rules/04-code-quality.mdc +419 -0
  23. package/cursor-rules/{08-markup-quality.mdc → 05-styling.mdc} +104 -34
  24. package/cursor-rules/06-security-rbac.mdc +518 -0
  25. package/cursor-rules/07-api-tech-stack.mdc +377 -0
  26. package/cursor-rules/08-testing-documentation.mdc +324 -0
  27. package/cursor-rules/09-operations.mdc +365 -0
  28. package/dist/{AuthService-Cb34EQs3.d.ts → AuthService-DmfO5rGS.d.ts} +10 -0
  29. package/dist/DataTable-7PMH7XN7.js +15 -0
  30. package/dist/{DataTable-BMRU8a1j.d.ts → DataTable-DRUIgtUH.d.ts} +1 -1
  31. package/dist/{PublicPageProvider-QTFVrL-Z.d.ts → PublicPageProvider-DlsCaR5v.d.ts} +33 -72
  32. package/dist/UnifiedAuthProvider-ZT6TIGM7.js +7 -0
  33. package/dist/api-Y4MQWOFW.js +4 -0
  34. package/dist/audit-MYQXYZFU.js +3 -0
  35. package/dist/{chunk-DGUM43GV.js → chunk-3RG5ZIWI.js} +1 -4
  36. package/dist/{chunk-QXHPKYJV.js → chunk-4SXLQIZO.js} +1 -26
  37. package/dist/{chunk-UPPMRMYG.js → chunk-5X4QLXRG.js} +73 -151
  38. package/dist/chunk-6F3IILHI.js +62 -0
  39. package/dist/{chunk-E66EQZE6.js → chunk-6GLLNA6U.js} +3 -9
  40. package/dist/{chunk-ZSAAAMVR.js → chunk-6QYDGKQY.js} +1 -4
  41. package/dist/{chunk-FMUCXFII.js → chunk-7ILTDCL2.js} +9 -5
  42. package/dist/{chunk-M43Y4SSO.js → chunk-A3W6LW53.js} +15 -13
  43. package/dist/{chunk-63FOKYGO.js → chunk-AHU7G2R5.js} +2 -11
  44. package/dist/{chunk-HU2C6SSC.js → chunk-BM4CQ5P3.js} +606 -559
  45. package/dist/chunk-C7NSAPTL.js +1 -0
  46. package/dist/{chunk-J36DSWQK.js → chunk-FEJLJNWA.js} +7 -41
  47. package/dist/{chunk-IHB5DR3H.js → chunk-FTCRZOG2.js} +188 -387
  48. package/dist/{chunk-G37KK66H.js → chunk-FYHN4DD5.js} +60 -19
  49. package/dist/chunk-GHYHJTYV.js +994 -0
  50. package/dist/{chunk-VBXEHIUJ.js → chunk-HF6O3O37.js} +6 -88
  51. package/dist/{chunk-FFQEQTNW.js → chunk-IUBRCBSY.js} +134 -45
  52. package/dist/{chunk-6COVEUS7.js → chunk-JGWDVX64.js} +983 -1034
  53. package/dist/{chunk-RGAWHO7N.js → chunk-L4XMVJKY.js} +77 -222
  54. package/dist/chunk-MBADTM7L.js +64 -0
  55. package/dist/{chunk-M7MPQISP.js → chunk-OJ4SKRSV.js} +3 -16
  56. package/dist/{chunk-IVOFDYWT.js → chunk-Q7Q7V5NV.js} +2109 -1604
  57. package/dist/{chunk-JGRYX5UX.js → chunk-S7DKJPLT.js} +29 -58
  58. package/dist/{chunk-PWLANIRT.js → chunk-TTRFSOKR.js} +1 -7
  59. package/dist/{chunk-5DRSZLL2.js → chunk-UH3NTO3F.js} +1 -6
  60. package/dist/{chunk-NTM7ZSB6.js → chunk-VBCS3DUA.js} +261 -168
  61. package/dist/{chunk-EFN2EIMK.js → chunk-ZFYPMX46.js} +271 -87
  62. package/dist/{chunk-L4OXEN46.js → chunk-ZKAWKYT4.js} +10 -24
  63. package/dist/components.d.ts +7 -5
  64. package/dist/components.js +46 -257
  65. package/dist/{database.generated-CzIvgcPu.d.ts → database.generated-CcnC_DRc.d.ts} +4795 -3691
  66. package/dist/eslint-rules/index.cjs +35 -0
  67. package/{src/eslint-rules/pace-core-compliance.cjs → dist/eslint-rules/rules/01-pace-core-compliance.cjs} +234 -235
  68. package/dist/eslint-rules/rules/04-code-quality.cjs +290 -0
  69. package/dist/eslint-rules/rules/05-styling.cjs +61 -0
  70. package/dist/eslint-rules/rules/06-security-rbac.cjs +806 -0
  71. package/dist/eslint-rules/rules/07-api-tech-stack.cjs +263 -0
  72. package/dist/eslint-rules/rules/08-testing.cjs +94 -0
  73. package/dist/eslint-rules/utils/helpers.cjs +42 -0
  74. package/dist/eslint-rules/utils/manifest-loader.cjs +75 -0
  75. package/dist/hooks.d.ts +6 -6
  76. package/dist/hooks.js +62 -172
  77. package/dist/icons/index.d.ts +1 -0
  78. package/dist/icons/index.js +1 -0
  79. package/dist/index.d.ts +12 -11
  80. package/dist/index.js +67 -660
  81. package/dist/providers.d.ts +2 -2
  82. package/dist/providers.js +8 -35
  83. package/dist/rbac/eslint-rules.d.ts +46 -44
  84. package/dist/rbac/eslint-rules.js +7 -4
  85. package/dist/rbac/index.d.ts +109 -586
  86. package/dist/rbac/index.js +14 -207
  87. package/dist/styles/index.js +2 -12
  88. package/dist/theming/runtime.d.ts +14 -1
  89. package/dist/theming/runtime.js +3 -19
  90. package/dist/{timezone-CHhWg6b4.d.ts → timezone-BZe_eUxx.d.ts} +175 -1
  91. package/dist/{types-CkbwOr4Y.d.ts → types-DXstZpNI.d.ts} +4 -17
  92. package/dist/types-t9H8qKRw.d.ts +55 -0
  93. package/dist/types.d.ts +1 -1
  94. package/dist/types.js +7 -94
  95. package/dist/{usePublicRouteParams-ClnV4tnv.d.ts → usePublicRouteParams-MamNgwqe.d.ts} +20 -20
  96. package/dist/utils.d.ts +24 -117
  97. package/dist/utils.js +54 -392
  98. package/docs/README.md +17 -7
  99. package/docs/api/README.md +4 -402
  100. package/docs/api/modules.md +301 -871
  101. package/docs/api-reference/components.md +21 -21
  102. package/docs/api-reference/deprecated.md +31 -6
  103. package/docs/api-reference/hooks.md +80 -80
  104. package/docs/api-reference/rpc-functions.md +78 -3
  105. package/docs/api-reference/types.md +1 -1
  106. package/docs/api-reference/utilities.md +1 -1
  107. package/docs/architecture/README.md +1 -1
  108. package/docs/core-concepts/events.md +3 -3
  109. package/docs/core-concepts/organisations.md +6 -6
  110. package/docs/core-concepts/permissions.md +6 -6
  111. package/docs/documentation-index.md +12 -18
  112. package/docs/getting-started/cursor-rules.md +3 -23
  113. package/docs/getting-started/dependencies.md +650 -0
  114. package/docs/getting-started/documentation-index.md +1 -1
  115. package/docs/getting-started/examples/README.md +4 -4
  116. package/docs/getting-started/examples/full-featured-app.md +1 -1
  117. package/docs/getting-started/faq.md +2 -2
  118. package/docs/getting-started/installation-guide.md +20 -7
  119. package/docs/getting-started/quick-reference.md +4 -4
  120. package/docs/getting-started/quick-start.md +23 -12
  121. package/docs/implementation-guides/authentication.md +15 -15
  122. package/docs/implementation-guides/component-styling.md +1 -1
  123. package/docs/implementation-guides/data-tables.md +126 -33
  124. package/docs/implementation-guides/datatable-rbac-usage.md +1 -1
  125. package/docs/implementation-guides/dynamic-colors.md +3 -3
  126. package/docs/implementation-guides/file-upload-storage.md +2 -2
  127. package/docs/implementation-guides/hierarchical-datatable.md +40 -60
  128. package/docs/implementation-guides/inactivity-tracking.md +3 -3
  129. package/docs/implementation-guides/large-datasets.md +3 -2
  130. package/docs/implementation-guides/organisation-security.md +2 -2
  131. package/docs/implementation-guides/performance.md +2 -2
  132. package/docs/implementation-guides/permission-enforcement.md +5 -1
  133. package/docs/migration/V0.3.44_organisation-context-timing-fix.md +1 -1
  134. package/docs/migration/V0.4.0_rbac-migration.md +6 -6
  135. package/docs/rbac/MIGRATION_GUIDE.md +819 -0
  136. package/docs/rbac/RBAC_CONTRACT.md +724 -0
  137. package/docs/rbac/README.md +17 -8
  138. package/docs/rbac/advanced-patterns.md +6 -6
  139. package/docs/rbac/api-reference.md +20 -20
  140. package/docs/rbac/edge-functions-guide.md +376 -0
  141. package/docs/rbac/event-based-apps.md +3 -3
  142. package/docs/rbac/examples.md +41 -41
  143. package/docs/rbac/getting-started.md +37 -37
  144. package/docs/rbac/performance.md +1 -1
  145. package/docs/rbac/quick-start.md +52 -52
  146. package/docs/rbac/secure-client-protection.md +1 -35
  147. package/docs/rbac/troubleshooting.md +1 -1
  148. package/docs/security/README.md +5 -5
  149. package/docs/standards/0-standards-overview.md +220 -0
  150. package/docs/standards/1-pace-core-compliance-standards.md +986 -0
  151. package/docs/standards/2-project-structure-standards.md +949 -0
  152. package/docs/standards/3-architecture-standards.md +606 -0
  153. package/docs/standards/4-code-quality-standards.md +728 -0
  154. package/docs/standards/5-styling-standards.md +348 -0
  155. package/docs/standards/{07-rbac-and-rls-standard.md → 6-security-rbac-standards.md} +269 -66
  156. package/docs/standards/7-api-tech-stack-standards.md +662 -0
  157. package/docs/standards/8-testing-documentation-standards.md +401 -0
  158. package/docs/standards/9-operations-standards.md +1102 -0
  159. package/docs/standards/README.md +185 -57
  160. package/docs/troubleshooting/README.md +4 -4
  161. package/docs/troubleshooting/common-issues.md +2 -2
  162. package/docs/troubleshooting/debugging.md +9 -9
  163. package/docs/troubleshooting/migration.md +4 -4
  164. package/docs/troubleshooting/organisation-context-setup.md +42 -19
  165. package/eslint-config-pace-core.cjs +33 -6
  166. package/package.json +35 -23
  167. package/scripts/install-cursor-rules.cjs +25 -6
  168. package/scripts/install-eslint-config.cjs +284 -0
  169. package/src/__tests__/fixtures/supabase.ts +1 -1
  170. package/src/__tests__/helpers/__tests__/component-test-utils.test.tsx +3 -3
  171. package/src/__tests__/helpers/__tests__/optimized-test-setup.test.ts +1 -1
  172. package/src/__tests__/helpers/__tests__/supabaseMock.test.ts +1 -1
  173. package/src/__tests__/helpers/__tests__/test-providers.test.tsx +2 -2
  174. package/src/__tests__/helpers/__tests__/test-utils.test.tsx +13 -13
  175. package/src/__tests__/helpers/component-test-utils.tsx +1 -1
  176. package/src/__tests__/helpers/supabaseMock.ts +2 -2
  177. package/src/__tests__/integration/UserProfile.test.tsx +14 -14
  178. package/src/__tests__/public-recipe-view.test.ts +38 -9
  179. package/src/__tests__/rbac/PagePermissionGuard.test.tsx +6 -6
  180. package/src/__tests__/templates/accessibility.test.template.tsx +9 -9
  181. package/src/__tests__/templates/component.test.template.tsx +18 -15
  182. package/src/components/Button/Button.tsx +5 -1
  183. package/src/components/Calendar/Calendar.tsx +201 -47
  184. package/src/components/ContextSelector/ContextSelector.tsx +106 -119
  185. package/src/components/DataTable/AUDIT_REPORT.md +293 -0
  186. package/src/components/DataTable/__tests__/DataTableCore.test.tsx +10 -2
  187. package/src/components/DataTable/__tests__/a11y.basic.test.tsx +10 -4
  188. package/src/components/DataTable/__tests__/test-utils/sharedTestUtils.tsx +9 -9
  189. package/src/components/DataTable/components/ColumnFilter.tsx +63 -74
  190. package/src/components/DataTable/components/ColumnVisibilityDropdown.tsx +43 -41
  191. package/src/components/DataTable/components/DataTableCore.tsx +186 -13
  192. package/src/components/DataTable/components/DataTableErrorBoundary.tsx +9 -11
  193. package/src/components/DataTable/components/DataTableLayout.tsx +35 -21
  194. package/src/components/DataTable/components/EditFields.tsx +23 -3
  195. package/src/components/DataTable/components/EditableRow.tsx +12 -9
  196. package/src/components/DataTable/components/EmptyState.tsx +10 -9
  197. package/src/components/DataTable/components/FilterRow.tsx +2 -4
  198. package/src/components/DataTable/components/ImportModal.tsx +124 -126
  199. package/src/components/DataTable/components/LoadingState.tsx +5 -6
  200. package/src/components/DataTable/components/RowComponent.tsx +12 -0
  201. package/src/components/DataTable/components/SortIndicator.tsx +50 -0
  202. package/src/components/DataTable/components/__tests__/COVERAGE_NOTE.md +4 -4
  203. package/src/components/DataTable/components/__tests__/ColumnFilter.test.tsx +23 -82
  204. package/src/components/DataTable/components/__tests__/DataTableErrorBoundary.test.tsx +37 -9
  205. package/src/components/DataTable/components/__tests__/EmptyState.test.tsx +7 -4
  206. package/src/components/DataTable/components/__tests__/FilterRow.test.tsx +12 -4
  207. package/src/components/DataTable/components/__tests__/LoadingState.test.tsx +41 -27
  208. package/src/components/DataTable/components/hooks/usePermissionTracking.ts +0 -4
  209. package/src/components/DataTable/components/index.ts +2 -1
  210. package/src/components/DataTable/hooks/__tests__/useDataTableState.test.ts +51 -47
  211. package/src/components/DataTable/hooks/useDataTablePermissions.ts +24 -21
  212. package/src/components/DataTable/hooks/useDataTableState.ts +125 -9
  213. package/src/components/DataTable/hooks/useTableColumns.ts +40 -2
  214. package/src/components/DataTable/hooks/useTableHandlers.ts +11 -0
  215. package/src/components/DataTable/types.ts +5 -18
  216. package/src/components/DataTable/utils/a11yUtils.ts +17 -0
  217. package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +2 -1
  218. package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.tsx +11 -15
  219. package/src/components/DateTimeField/DateTimeField.tsx +10 -9
  220. package/src/components/Dialog/Dialog.test.tsx +128 -104
  221. package/src/components/Dialog/Dialog.tsx +742 -24
  222. package/src/components/ErrorBoundary/ErrorBoundary.tsx +77 -79
  223. package/src/components/FileDisplay/FileDisplay.test.tsx +4 -2
  224. package/src/components/FileDisplay/FileDisplay.tsx +23 -17
  225. package/src/components/FileUpload/FileUpload.test.tsx +52 -14
  226. package/src/components/FileUpload/FileUpload.tsx +112 -130
  227. package/src/components/Form/Form.test.tsx +6 -8
  228. package/src/components/Form/Form.tsx +365 -4
  229. package/src/components/NavigationMenu/NavigationMenu.test.tsx +14 -13
  230. package/src/components/NavigationMenu/useNavigationFiltering.ts +11 -21
  231. package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +6 -4
  232. package/src/components/PaceAppLayout/PaceAppLayout.tsx +11 -15
  233. package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +108 -61
  234. package/src/components/PaceLoginPage/PaceLoginPage.tsx +27 -3
  235. package/src/components/Progress/Progress.tsx +2 -4
  236. package/src/components/ProtectedRoute/ProtectedRoute.tsx +8 -8
  237. package/src/components/Select/Select.tsx +109 -98
  238. package/src/components/Select/types.ts +4 -1
  239. package/src/components/UserMenu/UserMenu.tsx +9 -6
  240. package/src/hooks/__tests__/ServiceHooks.test.tsx +16 -16
  241. package/src/hooks/__tests__/hooks.integration.test.tsx +55 -57
  242. package/src/hooks/__tests__/useAppConfig.unit.test.ts +129 -67
  243. package/src/hooks/__tests__/useFocusTrap.unit.test.tsx +97 -97
  244. package/src/hooks/__tests__/usePublicEvent.simple.test.ts +149 -67
  245. package/src/hooks/__tests__/usePublicEvent.test.ts +149 -79
  246. package/src/hooks/__tests__/usePublicEvent.unit.test.ts +158 -109
  247. package/src/hooks/__tests__/useSessionDraft.test.ts +163 -0
  248. package/src/hooks/__tests__/useSessionRestoration.unit.test.tsx +10 -5
  249. package/src/hooks/public/usePublicEvent.ts +67 -195
  250. package/src/hooks/public/usePublicEventLogo.test.ts +70 -17
  251. package/src/hooks/public/usePublicEventLogo.ts +24 -14
  252. package/src/hooks/public/usePublicFileDisplay.ts +2 -2
  253. package/src/hooks/public/usePublicRouteParams.ts +5 -5
  254. package/src/hooks/useAppConfig.ts +28 -26
  255. package/src/hooks/useEventTheme.test.ts +217 -239
  256. package/src/hooks/useEventTheme.ts +16 -28
  257. package/src/hooks/useFileDisplay.ts +2 -2
  258. package/src/hooks/useOrganisationPermissions.ts +5 -7
  259. package/src/hooks/useQueryCache.ts +0 -1
  260. package/src/hooks/useSessionDraft.ts +380 -0
  261. package/src/hooks/useSessionRestoration.ts +3 -1
  262. package/src/icons/index.ts +27 -0
  263. package/src/index.ts +5 -0
  264. package/src/providers/OrganisationProvider.tsx +23 -14
  265. package/src/providers/UnifiedAuthProvider.smoke.test.tsx +21 -21
  266. package/src/providers/__tests__/AuthProvider.test.tsx +21 -21
  267. package/src/providers/__tests__/EventProvider.test.tsx +61 -61
  268. package/src/providers/__tests__/InactivityProvider.test.tsx +56 -56
  269. package/src/providers/__tests__/OrganisationProvider.test.tsx +75 -75
  270. package/src/providers/__tests__/ProviderLifecycle.test.tsx +37 -37
  271. package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +103 -103
  272. package/src/providers/services/EventServiceProvider.tsx +1 -24
  273. package/src/providers/services/UnifiedAuthProvider.tsx +5 -48
  274. package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +7 -7
  275. package/src/providers/services/__tests__/UnifiedAuthProvider.integration.test.tsx +13 -10
  276. package/src/rbac/__tests__/adapters.comprehensive.test.tsx +7 -457
  277. package/src/rbac/__tests__/auth-rbac.e2e.test.tsx +33 -7
  278. package/src/rbac/adapters.tsx +7 -295
  279. package/src/rbac/api.test.ts +44 -56
  280. package/src/rbac/api.ts +10 -17
  281. package/src/rbac/cache-invalidation.ts +0 -1
  282. package/src/rbac/compliance/index.ts +10 -0
  283. package/src/rbac/compliance/pattern-detector.ts +553 -0
  284. package/src/rbac/compliance/runtime-compliance.ts +22 -0
  285. package/src/rbac/components/AccessDenied.tsx +150 -0
  286. package/src/rbac/components/NavigationGuard.tsx +12 -20
  287. package/src/rbac/components/PagePermissionGuard.tsx +4 -24
  288. package/src/rbac/components/__tests__/NavigationGuard.test.tsx +21 -8
  289. package/src/rbac/components/index.ts +3 -41
  290. package/src/rbac/eslint-rules.js +1 -1
  291. package/src/rbac/hooks/index.ts +0 -3
  292. package/src/rbac/hooks/permissions/index.ts +0 -3
  293. package/src/rbac/hooks/permissions/useAccessLevel.ts +4 -8
  294. package/src/rbac/hooks/usePermissions.ts +0 -3
  295. package/src/rbac/hooks/useResolvedScope.test.ts +57 -47
  296. package/src/rbac/hooks/useResolvedScope.ts +58 -140
  297. package/src/rbac/hooks/useResourcePermissions.test.ts +124 -38
  298. package/src/rbac/hooks/useResourcePermissions.ts +139 -48
  299. package/src/rbac/hooks/useRoleManagement.test.ts +65 -22
  300. package/src/rbac/hooks/useRoleManagement.ts +147 -19
  301. package/src/rbac/hooks/useSecureSupabase.ts +4 -8
  302. package/src/rbac/index.ts +7 -9
  303. package/src/rbac/utils/contextValidator.ts +9 -7
  304. package/src/services/AuthService.ts +130 -18
  305. package/src/services/EventService.ts +4 -97
  306. package/src/services/InactivityService.ts +16 -0
  307. package/src/services/OrganisationService.ts +7 -44
  308. package/src/services/__tests__/OrganisationService.test.ts +26 -8
  309. package/src/services/base/BaseService.ts +0 -3
  310. package/src/styles/core.css +7 -0
  311. package/src/theming/__tests__/parseEventColours.test.ts +9 -3
  312. package/src/theming/parseEventColours.ts +22 -10
  313. package/src/types/database.generated.ts +4733 -3809
  314. package/src/utils/__tests__/lazyLoad.unit.test.tsx +42 -39
  315. package/src/utils/__tests__/organisationContext.unit.test.ts +9 -10
  316. package/src/utils/context/organisationContext.test.ts +13 -28
  317. package/src/utils/context/organisationContext.ts +21 -52
  318. package/src/utils/dynamic/dynamicUtils.ts +1 -1
  319. package/src/utils/file-reference/index.ts +39 -15
  320. package/src/utils/formatting/formatDateTime.test.ts +3 -2
  321. package/src/utils/google-places/loadGoogleMapsScript.ts +29 -4
  322. package/src/utils/index.ts +4 -1
  323. package/src/utils/persistence/__tests__/keyDerivation.test.ts +135 -0
  324. package/src/utils/persistence/__tests__/sensitiveFieldDetection.test.ts +123 -0
  325. package/src/utils/persistence/keyDerivation.ts +304 -0
  326. package/src/utils/persistence/sensitiveFieldDetection.ts +212 -0
  327. package/src/utils/security/secureStorage.ts +5 -5
  328. package/src/utils/storage/README.md +1 -1
  329. package/src/utils/storage/helpers.ts +3 -3
  330. package/src/utils/supabase/createBaseClient.ts +147 -0
  331. package/src/utils/timezone/timezone.test.ts +1 -2
  332. package/src/utils/timezone/timezone.ts +1 -1
  333. package/src/utils/validation/csrf.ts +4 -4
  334. package/cursor-rules/00-pace-core-compliance.mdc +0 -331
  335. package/cursor-rules/01-standards-compliance.mdc +0 -244
  336. package/cursor-rules/04-testing-standards.mdc +0 -268
  337. package/cursor-rules/05-bug-reports-and-features.mdc +0 -246
  338. package/cursor-rules/06-code-quality.mdc +0 -309
  339. package/cursor-rules/07-tech-stack-compliance.mdc +0 -214
  340. package/cursor-rules/CHANGELOG.md +0 -119
  341. package/cursor-rules/README.md +0 -192
  342. package/dist/DataTable-AOVNCPTX.js +0 -175
  343. package/dist/DataTable-AOVNCPTX.js.map +0 -1
  344. package/dist/UnifiedAuthProvider-4SBX4LU5.js +0 -18
  345. package/dist/UnifiedAuthProvider-4SBX4LU5.js.map +0 -1
  346. package/dist/api-O6HTBX5Y.js +0 -52
  347. package/dist/api-O6HTBX5Y.js.map +0 -1
  348. package/dist/audit-V53FV5AG.js +0 -17
  349. package/dist/audit-V53FV5AG.js.map +0 -1
  350. package/dist/chunk-5DRSZLL2.js.map +0 -1
  351. package/dist/chunk-63FOKYGO.js.map +0 -1
  352. package/dist/chunk-6COVEUS7.js.map +0 -1
  353. package/dist/chunk-AFVQODI2.js +0 -263
  354. package/dist/chunk-AFVQODI2.js.map +0 -1
  355. package/dist/chunk-DGUM43GV.js.map +0 -1
  356. package/dist/chunk-E66EQZE6.js.map +0 -1
  357. package/dist/chunk-EFN2EIMK.js.map +0 -1
  358. package/dist/chunk-FFQEQTNW.js.map +0 -1
  359. package/dist/chunk-FMUCXFII.js.map +0 -1
  360. package/dist/chunk-G37KK66H.js.map +0 -1
  361. package/dist/chunk-G7QEZTYQ.js +0 -2053
  362. package/dist/chunk-G7QEZTYQ.js.map +0 -1
  363. package/dist/chunk-HU2C6SSC.js.map +0 -1
  364. package/dist/chunk-IHB5DR3H.js.map +0 -1
  365. package/dist/chunk-IVOFDYWT.js.map +0 -1
  366. package/dist/chunk-J36DSWQK.js.map +0 -1
  367. package/dist/chunk-JGRYX5UX.js.map +0 -1
  368. package/dist/chunk-KQCRWDSA.js +0 -1
  369. package/dist/chunk-KQCRWDSA.js.map +0 -1
  370. package/dist/chunk-L4OXEN46.js.map +0 -1
  371. package/dist/chunk-LMC26NLJ.js +0 -84
  372. package/dist/chunk-LMC26NLJ.js.map +0 -1
  373. package/dist/chunk-M43Y4SSO.js.map +0 -1
  374. package/dist/chunk-M7MPQISP.js.map +0 -1
  375. package/dist/chunk-NTM7ZSB6.js.map +0 -1
  376. package/dist/chunk-PWLANIRT.js.map +0 -1
  377. package/dist/chunk-QXHPKYJV.js.map +0 -1
  378. package/dist/chunk-RGAWHO7N.js.map +0 -1
  379. package/dist/chunk-UPPMRMYG.js.map +0 -1
  380. package/dist/chunk-VBXEHIUJ.js.map +0 -1
  381. package/dist/chunk-ZSAAAMVR.js.map +0 -1
  382. package/dist/components.js.map +0 -1
  383. package/dist/contextValidator-5OGXSPKS.js +0 -9
  384. package/dist/contextValidator-5OGXSPKS.js.map +0 -1
  385. package/dist/eslint-rules/pace-core-compliance.cjs +0 -510
  386. package/dist/hooks.js.map +0 -1
  387. package/dist/index.js.map +0 -1
  388. package/dist/providers.js.map +0 -1
  389. package/dist/rbac/eslint-rules.js.map +0 -1
  390. package/dist/rbac/index.js.map +0 -1
  391. package/dist/styles/index.js.map +0 -1
  392. package/dist/theming/runtime.js.map +0 -1
  393. package/dist/types.js.map +0 -1
  394. package/dist/utils.js.map +0 -1
  395. package/docs/best-practices/README.md +0 -472
  396. package/docs/best-practices/accessibility.md +0 -601
  397. package/docs/best-practices/common-patterns.md +0 -516
  398. package/docs/best-practices/deployment.md +0 -1103
  399. package/docs/best-practices/performance.md +0 -1328
  400. package/docs/best-practices/security.md +0 -940
  401. package/docs/best-practices/testing.md +0 -1034
  402. package/docs/rbac/compliance/compliance-guide.md +0 -544
  403. package/docs/standards/01-architecture-standard.md +0 -44
  404. package/docs/standards/02-api-and-rpc-standard.md +0 -39
  405. package/docs/standards/03-component-standard.md +0 -32
  406. package/docs/standards/04-code-style-standard.md +0 -32
  407. package/docs/standards/05-security-standard.md +0 -44
  408. package/docs/standards/06-testing-and-docs-standard.md +0 -29
  409. package/docs/standards/pace-core-compliance.md +0 -432
  410. package/scripts/audit/core/checks/accessibility.cjs +0 -197
  411. package/scripts/audit/core/checks/api-usage.cjs +0 -191
  412. package/scripts/audit/core/checks/bundle.cjs +0 -142
  413. package/scripts/audit/core/checks/compliance.cjs +0 -2706
  414. package/scripts/audit/core/checks/config.cjs +0 -54
  415. package/scripts/audit/core/checks/coverage.cjs +0 -84
  416. package/scripts/audit/core/checks/dependencies.cjs +0 -994
  417. package/scripts/audit/core/checks/documentation.cjs +0 -268
  418. package/scripts/audit/core/checks/environment.cjs +0 -116
  419. package/scripts/audit/core/checks/error-handling.cjs +0 -340
  420. package/scripts/audit/core/checks/forms.cjs +0 -172
  421. package/scripts/audit/core/checks/heuristics.cjs +0 -68
  422. package/scripts/audit/core/checks/hooks.cjs +0 -334
  423. package/scripts/audit/core/checks/imports.cjs +0 -244
  424. package/scripts/audit/core/checks/performance.cjs +0 -325
  425. package/scripts/audit/core/checks/routes.cjs +0 -117
  426. package/scripts/audit/core/checks/state.cjs +0 -130
  427. package/scripts/audit/core/checks/structure.cjs +0 -65
  428. package/scripts/audit/core/checks/style.cjs +0 -584
  429. package/scripts/audit/core/checks/testing.cjs +0 -122
  430. package/scripts/audit/core/checks/typescript.cjs +0 -61
  431. package/scripts/audit/core/scanner.cjs +0 -199
  432. package/scripts/audit/core/utils.cjs +0 -137
  433. package/scripts/audit/index.cjs +0 -223
  434. package/scripts/audit/reporters/console.cjs +0 -151
  435. package/scripts/audit/reporters/json.cjs +0 -54
  436. package/scripts/audit/reporters/markdown.cjs +0 -124
  437. package/scripts/audit-consuming-app.cjs +0 -86
  438. package/src/components/DataTable/components/DataTableBody.tsx +0 -454
  439. package/src/components/DataTable/components/DraggableColumnHeader.tsx +0 -156
  440. package/src/components/DataTable/components/ExpandButton.tsx +0 -113
  441. package/src/components/DataTable/components/GroupHeader.tsx +0 -54
  442. package/src/components/DataTable/components/ViewRowModal.tsx +0 -68
  443. package/src/components/DataTable/components/VirtualizedDataTable.tsx +0 -525
  444. package/src/components/DataTable/components/__tests__/ExpandButton.test.tsx +0 -462
  445. package/src/components/DataTable/components/__tests__/GroupHeader.test.tsx +0 -393
  446. package/src/components/DataTable/components/__tests__/ViewRowModal.test.tsx +0 -476
  447. package/src/components/DataTable/components/__tests__/VirtualizedDataTable.test.tsx +0 -128
  448. package/src/components/DataTable/core/DataTableContext.tsx +0 -216
  449. package/src/components/DataTable/core/__tests__/DataTableContext.test.tsx +0 -136
  450. package/src/components/DataTable/hooks/__tests__/useColumnReordering.test.ts +0 -570
  451. package/src/components/DataTable/hooks/useColumnReordering.ts +0 -123
  452. package/src/components/DataTable/utils/debugTools.ts +0 -514
  453. package/src/eslint-rules/pace-core-compliance.js +0 -638
  454. package/src/rbac/components/EnhancedNavigationMenu.test.tsx +0 -555
  455. package/src/rbac/components/EnhancedNavigationMenu.tsx +0 -293
  456. package/src/rbac/components/NavigationProvider.test.tsx +0 -481
  457. package/src/rbac/components/NavigationProvider.tsx +0 -345
  458. package/src/rbac/components/PagePermissionProvider.test.tsx +0 -476
  459. package/src/rbac/components/PagePermissionProvider.tsx +0 -279
  460. package/src/rbac/components/PermissionEnforcer.tsx +0 -312
  461. package/src/rbac/components/RoleBasedRouter.tsx +0 -440
  462. package/src/rbac/components/SecureDataProvider.test.tsx +0 -543
  463. package/src/rbac/components/SecureDataProvider.tsx +0 -339
  464. package/src/rbac/components/__tests__/EnhancedNavigationMenu.test.tsx +0 -620
  465. package/src/rbac/components/__tests__/NavigationProvider.test.tsx +0 -726
  466. package/src/rbac/components/__tests__/PagePermissionProvider.test.tsx +0 -661
  467. package/src/rbac/components/__tests__/PermissionEnforcer.test.tsx +0 -881
  468. package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +0 -783
  469. package/src/rbac/components/__tests__/SecureDataProvider.fixed.test.tsx +0 -645
  470. package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +0 -659
  471. package/src/rbac/hooks/permissions/useCachedPermissions.ts +0 -79
  472. package/src/rbac/hooks/permissions/useHasAllPermissions.ts +0 -90
  473. package/src/rbac/hooks/permissions/useHasAnyPermission.ts +0 -90
@@ -0,0 +1,806 @@
1
+ /**
2
+ * Security & RBAC Rules (Standard 6)
3
+ * @package @jmruthers/pace-core
4
+ * @module ESLintRules/rules/security-rbac
5
+ *
6
+ * Enforces security and RBAC patterns from Standard 6:
7
+ * - Use secure Supabase client (useSecureSupabase)
8
+ * - Use RESOURCE_NAMES constants
9
+ * - No wrapper components/functions around RBAC hooks
10
+ * - Proper permission loading state handling
11
+ *
12
+ * Reference: packages/core/docs/standards/6-security-rbac-standards.md
13
+ */
14
+
15
+ const { isPaceCoreSourceFile } = require('../utils/helpers.cjs');
16
+
17
+ module.exports = {
18
+ rules: {
19
+ /**
20
+ * Disallow direct Supabase client creation - must use useSecureSupabase
21
+ */
22
+ 'no-direct-supabase-client': {
23
+ meta: {
24
+ type: 'problem',
25
+ docs: {
26
+ description: 'Disallow direct createClient calls from @supabase/supabase-js. Use useSecureSupabase() from pace-core instead to ensure organisation context and RLS policies are enforced.',
27
+ category: 'Security',
28
+ recommended: true,
29
+ url: 'https://github.com/jmruthers/pace-core/blob/main/packages/core/docs/standards/6-security-rbac-standards.md'
30
+ },
31
+ messages: {
32
+ directClientCreation: "Direct Supabase client creation detected. You MUST use useSecureSupabase() from '@jmruthers/pace-core/rbac' instead to ensure organisation context and RLS policies are enforced. This prevents cross-organisation data access.",
33
+ directClientImport: "Direct import of createClient from @supabase/supabase-js is not allowed. Use useSecureSupabase() from '@jmruthers/pace-core/rbac' instead."
34
+ },
35
+ hasSuggestions: true
36
+ },
37
+ create(context) {
38
+ const filename = context.getFilename();
39
+
40
+ // Exclude pace-core source files - these rules are for consuming apps
41
+ if (isPaceCoreSourceFile(filename)) {
42
+ return {};
43
+ }
44
+
45
+ // Allow createClient in specific config files (supabaseClient.ts/js, etc.)
46
+ const isConfigFile = /(supabase|client)\.(ts|js|tsx|jsx)$/i.test(filename) &&
47
+ (filename.includes('supabase') || filename.includes('client'));
48
+
49
+ return {
50
+ ImportDeclaration(node) {
51
+ const importSource = node.source.value;
52
+
53
+ // Check for @supabase/supabase-js import
54
+ if (importSource === '@supabase/supabase-js') {
55
+ // Check if createClient is imported
56
+ const hasCreateClient = node.specifiers.some(spec => {
57
+ if (spec.type === 'ImportSpecifier') {
58
+ return spec.imported.name === 'createClient';
59
+ }
60
+ if (spec.type === 'ImportNamespaceSpecifier') {
61
+ return true; // import * as supabase
62
+ }
63
+ return false;
64
+ });
65
+
66
+ if (hasCreateClient && !isConfigFile) {
67
+ context.report({
68
+ node: node.source,
69
+ messageId: 'directClientImport',
70
+ suggest: [{
71
+ desc: 'Replace with useSecureSupabase hook',
72
+ fix(fixer) {
73
+ return fixer.remove(node);
74
+ }
75
+ }]
76
+ });
77
+ }
78
+ }
79
+ },
80
+
81
+ CallExpression(node) {
82
+ // Check for createClient() calls
83
+ if (node.callee.type === 'Identifier' && node.callee.name === 'createClient') {
84
+ if (!isConfigFile) {
85
+ context.report({
86
+ node,
87
+ messageId: 'directClientCreation',
88
+ suggest: [{
89
+ desc: 'Use useSecureSupabase() hook instead',
90
+ fix(fixer) {
91
+ return null;
92
+ }
93
+ }]
94
+ });
95
+ }
96
+ }
97
+
98
+ // Check for supabase.createClient() or similar patterns
99
+ if (node.callee.type === 'MemberExpression' &&
100
+ node.callee.property?.name === 'createClient') {
101
+ if (!isConfigFile) {
102
+ context.report({
103
+ node,
104
+ messageId: 'directClientCreation',
105
+ suggest: [{
106
+ desc: 'Use useSecureSupabase() hook instead',
107
+ fix(fixer) {
108
+ return null;
109
+ }
110
+ }]
111
+ });
112
+ }
113
+ }
114
+ }
115
+ };
116
+ }
117
+ },
118
+
119
+ /**
120
+ * Check RBAC permission loading state usage
121
+ */
122
+ 'rbac-permission-loading': {
123
+ meta: {
124
+ type: 'problem',
125
+ docs: {
126
+ description: 'Require isLoading extraction from useResourcePermissions and check it before permission calls in mutations.',
127
+ category: 'Security',
128
+ recommended: true,
129
+ url: 'https://github.com/jmruthers/pace-core/blob/main/packages/core/docs/standards/6-security-rbac-standards.md'
130
+ },
131
+ messages: {
132
+ missingIsLoading: "useResourcePermissions is used but 'isLoading' is not extracted. Permission checks may fail if scope resolution is still in progress.",
133
+ missingLoadingCheck: "Permission check '{{permission}}()' is called without checking isLoading first. This can cause false negatives when scope resolution is still in progress."
134
+ },
135
+ hasSuggestions: true
136
+ },
137
+ create(context) {
138
+ let useResourcePermissionsFound = false;
139
+ let hasIsLoadingExtraction = false;
140
+ let isLoadingVarName = null;
141
+
142
+ return {
143
+ CallExpression(node) {
144
+ // Find useResourcePermissions calls
145
+ if (node.callee.type === 'Identifier' &&
146
+ node.callee.name === 'useResourcePermissions') {
147
+ useResourcePermissionsFound = true;
148
+
149
+ // Check parent to see if isLoading is destructured
150
+ const parent = node.parent;
151
+ if (parent && parent.type === 'VariableDeclarator' &&
152
+ parent.id && parent.id.type === 'ObjectPattern') {
153
+ const properties = parent.id.properties;
154
+ const isLoadingProp = properties.find(prop => {
155
+ if (prop.type === 'Property') {
156
+ const key = prop.key;
157
+ if (key.type === 'Identifier' && key.name === 'isLoading') {
158
+ return true;
159
+ }
160
+ // Also check for renamed: isLoading: permissionsLoading
161
+ if (key.type === 'Identifier' && key.name === 'isLoading') {
162
+ if (prop.value && prop.value.type === 'Identifier') {
163
+ isLoadingVarName = prop.value.name;
164
+ }
165
+ return true;
166
+ }
167
+ }
168
+ return false;
169
+ });
170
+
171
+ if (isLoadingProp) {
172
+ hasIsLoadingExtraction = true;
173
+ if (!isLoadingVarName) {
174
+ isLoadingVarName = 'isLoading';
175
+ }
176
+ }
177
+ }
178
+ }
179
+
180
+ // Check for permission function calls in mutations
181
+ if (useResourcePermissionsFound &&
182
+ node.callee.type === 'Identifier' &&
183
+ ['canCreate', 'canUpdate', 'canDelete', 'canRead'].includes(node.callee.name)) {
184
+
185
+ // Check if we're in a mutation context
186
+ const sourceCode = context.sourceCode || context.getSourceCode();
187
+ // ESLint 9: use sourceCode.getAncestors(node), ESLint 8: use context.getAncestors()
188
+ const ancestors = sourceCode.getAncestors ? sourceCode.getAncestors(node) : (context.getAncestors ? context.getAncestors() : []);
189
+ const isInMutation = ancestors.some(ancestor => {
190
+ if (ancestor.type === 'Property' && ancestor.key) {
191
+ const keyName = ancestor.key.name || (ancestor.key.type === 'Identifier' && ancestor.key.name);
192
+ return keyName === 'mutationFn' || keyName === 'mutateAsync';
193
+ }
194
+ return false;
195
+ });
196
+
197
+ if (isInMutation) {
198
+ // Check if isLoading is checked before this call
199
+ const nodeText = sourceCode.getText(node);
200
+ const beforeNode = sourceCode.getText().substring(0, sourceCode.getIndexFromLoc(node.loc.start));
201
+
202
+ // Look for isLoading check before this call
203
+ const hasLoadingCheck = new RegExp(
204
+ `(if\\s*\\(\\s*!?\\s*(${isLoadingVarName || 'isLoading'})|if\\s*\\(\\s*(${isLoadingVarName || 'isLoading'})\\s*===\\s*false|if\\s*\\(\\s*(${isLoadingVarName || 'isLoading'})\\s*!==\\s*true|await)`,
205
+ 'i'
206
+ ).test(beforeNode);
207
+
208
+ if (!hasIsLoadingExtraction) {
209
+ context.report({
210
+ node,
211
+ messageId: 'missingIsLoading',
212
+ suggest: [{
213
+ desc: `Extract 'isLoading' from useResourcePermissions`,
214
+ fix(fixer) {
215
+ return null; // Complex fix
216
+ }
217
+ }]
218
+ });
219
+ } else if (!hasLoadingCheck) {
220
+ context.report({
221
+ node,
222
+ messageId: 'missingLoadingCheck',
223
+ data: {
224
+ permission: node.callee.name
225
+ },
226
+ suggest: [{
227
+ desc: `Check ${isLoadingVarName || 'isLoading'} before calling permission function`,
228
+ fix(fixer) {
229
+ return null; // Complex fix
230
+ }
231
+ }]
232
+ });
233
+ }
234
+ }
235
+ }
236
+ }
237
+ };
238
+ }
239
+ },
240
+
241
+ /**
242
+ * Disallow direct RPC calls to RBAC functions
243
+ */
244
+ 'no-direct-rbac-rpc': {
245
+ meta: {
246
+ type: 'problem',
247
+ docs: {
248
+ description: 'Disallow direct calls to RBAC RPC functions. Use pace-core RBAC hooks instead.',
249
+ category: 'Security',
250
+ recommended: true,
251
+ url: 'https://github.com/jmruthers/pace-core/blob/main/packages/core/docs/standards/6-security-rbac-standards.md'
252
+ },
253
+ messages: {
254
+ directRbacRpc: "Direct RPC call to '{{rpcName}}' detected. Use pace-core RBAC hooks from '@jmruthers/pace-core/rbac' instead."
255
+ },
256
+ hasSuggestions: true
257
+ },
258
+ create(context) {
259
+ const filename = context.getFilename();
260
+
261
+ // Exclude pace-core source files - these rules are for consuming apps
262
+ if (isPaceCoreSourceFile(filename)) {
263
+ return {};
264
+ }
265
+
266
+ const rbacRpcPatterns = ['rbac_check_permission_simplified', 'rbac_'];
267
+
268
+ return {
269
+ CallExpression(node) {
270
+ // Check for supabase.rpc('rbac_*', ...)
271
+ if (node.callee.type === 'MemberExpression' &&
272
+ node.callee.property &&
273
+ node.callee.property.name === 'rpc' &&
274
+ node.arguments.length > 0) {
275
+
276
+ const firstArg = node.arguments[0];
277
+ if (firstArg.type === 'Literal' && typeof firstArg.value === 'string') {
278
+ const rpcName = firstArg.value;
279
+ if (rpcName.startsWith('rbac_')) {
280
+ context.report({
281
+ node: firstArg,
282
+ messageId: 'directRbacRpc',
283
+ data: {
284
+ rpcName
285
+ },
286
+ suggest: [{
287
+ desc: 'Use pace-core RBAC hooks instead',
288
+ fix(fixer) {
289
+ return null;
290
+ }
291
+ }]
292
+ });
293
+ }
294
+ }
295
+ }
296
+ }
297
+ };
298
+ }
299
+ },
300
+
301
+ /**
302
+ * Disallow direct queries to RBAC tables
303
+ */
304
+ 'no-direct-rbac-table': {
305
+ meta: {
306
+ type: 'problem',
307
+ docs: {
308
+ description: 'Disallow direct queries to RBAC tables. Use pace-core RBAC API functions or useSecureSupabase instead.',
309
+ category: 'Security',
310
+ recommended: true,
311
+ url: 'https://github.com/jmruthers/pace-core/blob/main/packages/core/docs/standards/6-security-rbac-standards.md'
312
+ },
313
+ messages: {
314
+ directRbacTable: "Direct query to RBAC table '{{tableName}}' detected. Use pace-core RBAC API functions from '@jmruthers/pace-core/rbac' or useSecureSupabase hook instead."
315
+ },
316
+ hasSuggestions: true
317
+ },
318
+ create(context) {
319
+ const filename = context.getFilename();
320
+
321
+ // Exclude pace-core source files - these rules are for consuming apps
322
+ if (isPaceCoreSourceFile(filename)) {
323
+ return {};
324
+ }
325
+
326
+ const rbacTables = [
327
+ 'rbac_organisation_roles',
328
+ 'rbac_event_app_roles',
329
+ 'rbac_global_roles',
330
+ 'rbac_apps',
331
+ 'rbac_app_pages',
332
+ 'rbac_page_permissions',
333
+ 'rbac_user_profiles'
334
+ ];
335
+
336
+ let hasSecureSupabase = false;
337
+ let secureSupabaseVarName = null;
338
+
339
+ return {
340
+ ImportDeclaration(node) {
341
+ const importSource = node.source.value;
342
+ if (importSource === '@jmruthers/pace-core/rbac' ||
343
+ importSource.startsWith('@jmruthers/pace-core/rbac/')) {
344
+ if (node.specifiers.some(spec =>
345
+ spec.type === 'ImportSpecifier' && spec.imported.name === 'useSecureSupabase'
346
+ )) {
347
+ hasSecureSupabase = true;
348
+ }
349
+ }
350
+ },
351
+ VariableDeclarator(node) {
352
+ if (node.init &&
353
+ node.init.type === 'CallExpression' &&
354
+ node.init.callee &&
355
+ node.init.callee.name === 'useSecureSupabase') {
356
+ if (node.id.type === 'Identifier') {
357
+ secureSupabaseVarName = node.id.name;
358
+ }
359
+ }
360
+ },
361
+ CallExpression(node) {
362
+ if (node.callee.type === 'MemberExpression' &&
363
+ node.callee.property &&
364
+ node.callee.property.name === 'from' &&
365
+ node.arguments.length > 0) {
366
+
367
+ // Check if this is called on secureSupabase
368
+ let isSecureClient = false;
369
+ if (node.callee.object.type === 'Identifier') {
370
+ const objName = node.callee.object.name;
371
+ if (objName === 'secureSupabase' ||
372
+ objName === secureSupabaseVarName ||
373
+ (hasSecureSupabase && objName.includes('secure'))) {
374
+ isSecureClient = true;
375
+ }
376
+ }
377
+
378
+ // Allow queries through secureSupabase
379
+ if (isSecureClient) {
380
+ return;
381
+ }
382
+
383
+ const firstArg = node.arguments[0];
384
+ if (firstArg.type === 'Literal' && typeof firstArg.value === 'string') {
385
+ const tableName = firstArg.value;
386
+ if (rbacTables.includes(tableName) || tableName.startsWith('rbac_')) {
387
+ context.report({
388
+ node: firstArg,
389
+ messageId: 'directRbacTable',
390
+ data: {
391
+ tableName
392
+ },
393
+ suggest: [{
394
+ desc: 'Use useSecureSupabase hook or pace-core RBAC API functions',
395
+ fix(fixer) {
396
+ return null;
397
+ }
398
+ }]
399
+ });
400
+ }
401
+ }
402
+ }
403
+ }
404
+ };
405
+ }
406
+ },
407
+
408
+ /**
409
+ * Disallow hardcoded role checks
410
+ */
411
+ 'no-hardcoded-role-checks': {
412
+ meta: {
413
+ type: 'problem',
414
+ docs: {
415
+ description: 'Disallow hardcoded role checks. Use useAccessLevel hook or getRoleContext API from pace-core instead.',
416
+ category: 'Security',
417
+ recommended: true,
418
+ url: 'https://github.com/jmruthers/pace-core/blob/main/packages/core/docs/standards/6-security-rbac-standards.md'
419
+ },
420
+ messages: {
421
+ hardcodedRoleCheck: "Hardcoded role check detected. Use useAccessLevel hook or getRoleContext API from '@jmruthers/pace-core/rbac' instead."
422
+ },
423
+ hasSuggestions: true
424
+ },
425
+ create(context) {
426
+ const filename = context.getFilename();
427
+
428
+ // Exclude pace-core source files - these rules are for consuming apps
429
+ if (isPaceCoreSourceFile(filename)) {
430
+ return {};
431
+ }
432
+
433
+ const roleNames = ['admin', 'org_admin', 'event_admin', 'user', 'member', 'viewer', 'editor'];
434
+ const roleCheckPattern = new RegExp(
435
+ `(?:role|user\\.role|userRole|currentRole|userRoleName)\\s*(?:===|!==|==|!=)\\s*['"](${roleNames.join('|')})['"]`,
436
+ 'i'
437
+ );
438
+
439
+ let hasPaceCoreImport = false;
440
+
441
+ return {
442
+ ImportDeclaration(node) {
443
+ const importSource = node.source.value;
444
+ if (importSource === '@jmruthers/pace-core/rbac' ||
445
+ importSource.startsWith('@jmruthers/pace-core/rbac/')) {
446
+ hasPaceCoreImport = true;
447
+ }
448
+ },
449
+ BinaryExpression(node) {
450
+ if (node.operator === '===' || node.operator === '!==' || node.operator === '==' || node.operator === '!=') {
451
+ const sourceCode = context.getSourceCode();
452
+ const leftText = sourceCode.getText(node.left);
453
+ const rightText = sourceCode.getText(node.right);
454
+
455
+ // Check if it's a role comparison
456
+ if (roleCheckPattern.test(leftText + ' ' + node.operator + ' ' + rightText)) {
457
+ // Check if using pace-core APIs
458
+ if (!hasPaceCoreImport ||
459
+ (!leftText.includes('useAccessLevel') && !leftText.includes('getRoleContext'))) {
460
+ context.report({
461
+ node,
462
+ messageId: 'hardcodedRoleCheck',
463
+ suggest: [{
464
+ desc: 'Use useAccessLevel hook or getRoleContext API',
465
+ fix(fixer) {
466
+ return null;
467
+ }
468
+ }]
469
+ });
470
+ }
471
+ }
472
+ }
473
+ }
474
+ };
475
+ }
476
+ },
477
+
478
+ /**
479
+ * Require RESOURCE_NAMES constants in useResourcePermissions
480
+ */
481
+ 'rbac-use-resource-names-constants': {
482
+ meta: {
483
+ type: 'problem',
484
+ docs: {
485
+ description: 'Require RESOURCE_NAMES constants instead of string literals in useResourcePermissions calls.',
486
+ category: 'Best Practices',
487
+ recommended: true,
488
+ url: 'https://github.com/jmruthers/pace-core/blob/main/packages/core/docs/standards/6-security-rbac-standards.md'
489
+ },
490
+ messages: {
491
+ resourcePermissionStringLiteral: "Resource permission string literal detected. Use RESOURCE_NAMES constant object instead (e.g., RESOURCE_NAMES.ORGANISATIONS, RESOURCE_NAMES.EVENTS)."
492
+ },
493
+ hasSuggestions: true
494
+ },
495
+ create(context) {
496
+ const filename = context.getFilename();
497
+
498
+ // Exclude pace-core source files - these rules are for consuming apps
499
+ if (isPaceCoreSourceFile(filename)) {
500
+ return {};
501
+ }
502
+
503
+ let hasResourceNamesImport = false;
504
+
505
+ return {
506
+ ImportDeclaration(node) {
507
+ const importSource = node.source.value;
508
+ if (node.specifiers.some(spec =>
509
+ spec.type === 'ImportSpecifier' && spec.imported.name === 'RESOURCE_NAMES'
510
+ )) {
511
+ hasResourceNamesImport = true;
512
+ }
513
+ },
514
+ CallExpression(node) {
515
+ if (node.callee.type === 'Identifier' &&
516
+ node.callee.name === 'useResourcePermissions') {
517
+ // Check if argument is a string literal
518
+ if (node.arguments.length > 0 &&
519
+ node.arguments[0].type === 'Literal' &&
520
+ typeof node.arguments[0].value === 'string') {
521
+ // Check if RESOURCE_NAMES is used in the file
522
+ const sourceCode = context.getSourceCode();
523
+ const fileText = sourceCode.getText();
524
+
525
+ if (!hasResourceNamesImport || !fileText.includes('RESOURCE_NAMES.')) {
526
+ context.report({
527
+ node: node.arguments[0],
528
+ messageId: 'resourcePermissionStringLiteral',
529
+ suggest: [{
530
+ desc: 'Use RESOURCE_NAMES constant instead',
531
+ fix(fixer) {
532
+ return null; // Complex fix
533
+ }
534
+ }]
535
+ });
536
+ }
537
+ }
538
+ }
539
+ }
540
+ };
541
+ }
542
+ },
543
+
544
+ /**
545
+ * Disallow wrapper components around PagePermissionGuard
546
+ */
547
+ 'no-rbac-wrapper-components': {
548
+ meta: {
549
+ type: 'problem',
550
+ docs: {
551
+ description: 'Disallow wrapper components around PagePermissionGuard. Use PagePermissionGuard directly.',
552
+ category: 'Security',
553
+ recommended: true,
554
+ url: 'https://github.com/jmruthers/pace-core/blob/main/packages/core/docs/standards/6-security-rbac-standards.md'
555
+ },
556
+ messages: {
557
+ wrapperComponent: "Wrapper component '{{componentName}}' detected around PagePermissionGuard. Must use PagePermissionGuard directly, not through wrappers."
558
+ },
559
+ hasSuggestions: true
560
+ },
561
+ create(context) {
562
+ const filename = context.getFilename();
563
+
564
+ // Allow in pace-core package itself
565
+ if (filename.includes('packages/core/src/rbac') || filename.includes('packages\\core\\src\\rbac')) {
566
+ return {};
567
+ }
568
+
569
+ let hasPagePermissionGuard = false;
570
+ let wrapperComponentName = null;
571
+
572
+ return {
573
+ JSXOpeningElement(node) {
574
+ if (node.name.type === 'JSXIdentifier' && node.name.name === 'PagePermissionGuard') {
575
+ hasPagePermissionGuard = true;
576
+
577
+ // Check if this is inside a wrapper component
578
+ const sourceCode = context.sourceCode || context.getSourceCode();
579
+ // ESLint 9: use sourceCode.getAncestors(node), ESLint 8: use context.getAncestors()
580
+ const ancestors = sourceCode.getAncestors ? sourceCode.getAncestors(node) : (context.getAncestors ? context.getAncestors() : []);
581
+ const componentAncestor = ancestors.find(ancestor =>
582
+ ancestor.type === 'FunctionDeclaration' ||
583
+ (ancestor.type === 'VariableDeclarator' && ancestor.init &&
584
+ (ancestor.init.type === 'ArrowFunctionExpression' || ancestor.init.type === 'FunctionExpression'))
585
+ );
586
+
587
+ if (componentAncestor) {
588
+ let componentName = null;
589
+ let componentParams = null;
590
+
591
+ if (componentAncestor.type === 'FunctionDeclaration' && componentAncestor.id) {
592
+ componentName = componentAncestor.id.name;
593
+ componentParams = componentAncestor.params;
594
+ } else if (componentAncestor.type === 'VariableDeclarator' && componentAncestor.id.type === 'Identifier') {
595
+ componentName = componentAncestor.id.name;
596
+ // For arrow functions and function expressions, get params from init
597
+ if (componentAncestor.init) {
598
+ if (componentAncestor.init.type === 'ArrowFunctionExpression' ||
599
+ componentAncestor.init.type === 'FunctionExpression') {
600
+ componentParams = componentAncestor.init.params;
601
+ }
602
+ }
603
+ }
604
+
605
+ // Check if component accepts pageName as a FUNCTION PARAMETER (not just uses it in JSX)
606
+ // This distinguishes wrapper components from legitimate page components
607
+ // Wrapper pattern: function Wrapper({ pageName }) { return <PagePermissionGuard pageName={pageName}> }
608
+ // Legitimate pattern: function Page() { return <PagePermissionGuard pageName={PAGE_NAMES.PAGE}> }
609
+ const acceptsPageNameAsParam = componentParams && componentParams.some(param => {
610
+ if (param.type === 'Identifier') {
611
+ return param.name === 'pageName';
612
+ }
613
+ // Handle destructured params: { pageName } or { pageName: pn }
614
+ if (param.type === 'ObjectPattern') {
615
+ return param.properties.some(prop => {
616
+ if (prop.type === 'Property') {
617
+ const key = prop.key;
618
+ if (key.type === 'Identifier') {
619
+ return key.name === 'pageName';
620
+ }
621
+ }
622
+ return false;
623
+ });
624
+ }
625
+ return false;
626
+ });
627
+
628
+ // Only flag if component accepts pageName as a parameter (wrapper pattern)
629
+ // Legitimate page components use constants like PAGE_NAMES.CONTACTS, not accept it as prop
630
+ if (acceptsPageNameAsParam) {
631
+ context.report({
632
+ node,
633
+ messageId: 'wrapperComponent',
634
+ data: {
635
+ componentName: componentName || 'Unknown'
636
+ },
637
+ suggest: [{
638
+ desc: 'Remove wrapper component and use PagePermissionGuard directly in pages',
639
+ fix(fixer) {
640
+ return null;
641
+ }
642
+ }]
643
+ });
644
+ }
645
+ }
646
+ }
647
+ }
648
+ };
649
+ }
650
+ },
651
+
652
+ /**
653
+ * Disallow wrapper functions around permission hooks
654
+ */
655
+ 'no-rbac-wrapper-functions': {
656
+ meta: {
657
+ type: 'problem',
658
+ docs: {
659
+ description: 'Disallow wrapper functions around pace-core permission hooks. Use hooks directly in components.',
660
+ category: 'Security',
661
+ recommended: true,
662
+ url: 'https://github.com/jmruthers/pace-core/blob/main/packages/core/docs/standards/6-security-rbac-standards.md'
663
+ },
664
+ messages: {
665
+ wrapperFunction: "Permission wrapper function '{{functionName}}' detected. Use pace-core hooks (useCan, useResourcePermissions) directly in components instead of wrapping them."
666
+ },
667
+ hasSuggestions: true
668
+ },
669
+ create(context) {
670
+ const filename = context.getFilename();
671
+
672
+ // Exclude pace-core source files - these rules are for consuming apps
673
+ if (isPaceCoreSourceFile(filename)) {
674
+ return {};
675
+ }
676
+
677
+ const permissionHookNames = ['useCan', 'useResourcePermissions', 'usePermissions', 'useMultiplePermissions'];
678
+ const permissionFunctionNames = ['canCreate', 'canUpdate', 'canDelete', 'canRead', 'can'];
679
+
680
+ let hasPaceCoreRBACImport = false;
681
+
682
+ return {
683
+ ImportDeclaration(node) {
684
+ const importSource = node.source.value;
685
+ if (importSource === '@jmruthers/pace-core/rbac' ||
686
+ importSource.startsWith('@jmruthers/pace-core/rbac/')) {
687
+ hasPaceCoreRBACImport = true;
688
+ }
689
+ },
690
+ FunctionDeclaration(node) {
691
+ if (!node.id || !hasPaceCoreRBACImport) return;
692
+
693
+ const functionName = node.id.name;
694
+ if (!functionName) return;
695
+
696
+ // Check if function body uses permission hooks/functions
697
+ const sourceCode = context.getSourceCode();
698
+ const functionText = sourceCode.getText(node.body);
699
+
700
+ // Check if function uses permission hooks or functions
701
+ const usesPermissionHooks = permissionHookNames.some(hook => functionText.includes(hook));
702
+ const usesPermissionFunctions = permissionFunctionNames.some(fn => functionText.includes(fn));
703
+
704
+ // Check if function has additional logic beyond permission checking
705
+ const hasAdditionalLogic = (
706
+ functionText.includes('&&') ||
707
+ functionText.includes('||') ||
708
+ functionText.includes('if') ||
709
+ (functionText.match(/return/g) || []).length > 1 ||
710
+ functionText.includes('.find') ||
711
+ functionText.includes('.filter') ||
712
+ functionText.includes('.map')
713
+ );
714
+
715
+ // Pattern: Functions that start with 'can' and use permission hooks/functions
716
+ // and have additional logic
717
+ const isPermissionWrapper = (
718
+ (functionName.toLowerCase().startsWith('can') ||
719
+ functionName.toLowerCase().includes('permission') ||
720
+ functionName.toLowerCase().includes('access')) &&
721
+ (usesPermissionHooks || usesPermissionFunctions) &&
722
+ hasAdditionalLogic &&
723
+ node.params.length > 0
724
+ );
725
+
726
+ if (isPermissionWrapper) {
727
+ context.report({
728
+ node: node.id,
729
+ messageId: 'wrapperFunction',
730
+ data: {
731
+ functionName
732
+ },
733
+ suggest: [{
734
+ desc: 'Use permission hooks directly in components',
735
+ fix(fixer) {
736
+ return null;
737
+ }
738
+ }]
739
+ });
740
+ }
741
+ },
742
+ VariableDeclarator(node) {
743
+ if (!hasPaceCoreRBACImport) return;
744
+ if (node.id.type !== 'Identifier') return;
745
+
746
+ const varName = node.id.name;
747
+ if (!varName) return;
748
+
749
+ // Only check arrow functions and function expressions
750
+ if (!node.init ||
751
+ (node.init.type !== 'ArrowFunctionExpression' &&
752
+ node.init.type !== 'FunctionExpression')) {
753
+ return;
754
+ }
755
+
756
+ const sourceCode = context.getSourceCode();
757
+ const functionText = sourceCode.getText(node.init);
758
+
759
+ // Check if function uses permission hooks or functions
760
+ const usesPermissionHooks = permissionHookNames.some(hook => functionText.includes(hook));
761
+ const usesPermissionFunctions = permissionFunctionNames.some(fn => functionText.includes(fn));
762
+
763
+ // Check if function has additional logic beyond permission checking
764
+ const hasAdditionalLogic = (
765
+ functionText.includes('&&') ||
766
+ functionText.includes('||') ||
767
+ functionText.includes('if') ||
768
+ (functionText.match(/return/g) || []).length > 1 ||
769
+ functionText.includes('.find') ||
770
+ functionText.includes('.filter') ||
771
+ functionText.includes('.map')
772
+ );
773
+
774
+ // Pattern: Variables that start with 'can' and use permission hooks/functions
775
+ // and have additional logic
776
+ const isPermissionWrapper = (
777
+ (varName.toLowerCase().startsWith('can') ||
778
+ varName.toLowerCase().includes('permission') ||
779
+ varName.toLowerCase().includes('access')) &&
780
+ (usesPermissionHooks || usesPermissionFunctions) &&
781
+ hasAdditionalLogic &&
782
+ node.init.params && node.init.params.length > 0
783
+ );
784
+
785
+ if (isPermissionWrapper) {
786
+ context.report({
787
+ node: node.id,
788
+ messageId: 'wrapperFunction',
789
+ data: {
790
+ functionName: varName
791
+ },
792
+ suggest: [{
793
+ desc: 'Use permission hooks directly in components',
794
+ fix(fixer) {
795
+ return null;
796
+ }
797
+ }]
798
+ });
799
+ }
800
+ }
801
+ };
802
+ }
803
+ }
804
+ }
805
+ };
806
+