@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,544 @@
1
+ /**
2
+ * Standard 6: Security & RBAC Audit
3
+ * @package @jmruthers/pace-core
4
+ * @module Audit/Standard6
5
+ *
6
+ * Audits consuming apps for compliance with Standard 6: Security & RBAC.
7
+ * Validates RLS policies in SQL migrations, PagePermissionGuard coverage, and Edge Functions RBAC.
8
+ *
9
+ * Reference: packages/core/docs/standards/6-security-rbac-standards.md
10
+ */
11
+
12
+ const path = require('path');
13
+ const fs = require('fs');
14
+ const { findSQLFiles, findSourceFiles, readFileSafe, getRelativePath, directoryExists } = require('../utils/file-utils.cjs');
15
+ const { getLineNumber, getCodeSnippet, isInCommentOrStringSQL, isInCommentOrString, importsFromPaceCore } = require('../utils/code-utils.cjs');
16
+
17
+ /**
18
+ * Check RLS policy compliance in SQL migrations
19
+ */
20
+ function checkRLSPolicies(consumingAppPath) {
21
+ const issues = [];
22
+
23
+ // Find SQL migration files
24
+ const migrationsPath = path.join(consumingAppPath, 'supabase', 'migrations');
25
+ const altMigrationsPath = path.join(consumingAppPath, 'migrations');
26
+
27
+ const migrationsDir = fs.existsSync(migrationsPath) ? migrationsPath :
28
+ (fs.existsSync(altMigrationsPath) ? altMigrationsPath : null);
29
+
30
+ if (!migrationsDir) {
31
+ return issues; // No migrations directory, skip check
32
+ }
33
+
34
+ const sqlFiles = findSQLFiles(migrationsDir);
35
+
36
+ sqlFiles.forEach(filePath => {
37
+ try {
38
+ const content = readFileSafe(filePath);
39
+ if (!content) {
40
+ return;
41
+ }
42
+
43
+ const relativePath = getRelativePath(filePath, consumingAppPath);
44
+
45
+ // Find CREATE POLICY statements
46
+ const policyPattern = /CREATE\s+POLICY\s+["']?([^"'\s]+)["']?\s+ON\s+(\w+)\s+FOR\s+(\w+)/gi;
47
+ let match;
48
+
49
+ while ((match = policyPattern.exec(content)) !== null) {
50
+ const policyName = match[1];
51
+ const tableName = match[2];
52
+ const operation = match[3].toLowerCase();
53
+ const policyStart = match.index;
54
+
55
+ // Check policy naming: rbac_{operation}_{table_name}_{scope}
56
+ const expectedPattern = new RegExp(`^rbac_${operation}_${tableName}(?:_\\w+)?$`, 'i');
57
+ if (!expectedPattern.test(policyName)) {
58
+ issues.push({
59
+ type: 'rlsPolicy',
60
+ file: relativePath,
61
+ line: getLineNumber(content, policyStart),
62
+ message: `RLS policy '${policyName}' does not follow naming convention. Should be 'rbac_${operation}_${tableName}' or 'rbac_${operation}_${tableName}_scope'.`,
63
+ code: getCodeSnippet(content, policyStart, 0, 100),
64
+ severity: 'error',
65
+ fix: `Rename policy to follow pattern: rbac_${operation}_${tableName}`,
66
+ });
67
+ }
68
+
69
+ // Find the USING/WITH CHECK clause
70
+ const policyEnd = content.indexOf(';', policyStart);
71
+ if (policyEnd === -1) continue;
72
+
73
+ const policyBody = content.substring(policyStart, policyEnd);
74
+
75
+ // Check for inline auth.uid() calls (performance issue)
76
+ const authUidPattern = /\bauth\.uid\s*\(/gi;
77
+ if (authUidPattern.test(policyBody) && !isInCommentOrStringSQL(content, content.indexOf('auth.uid', policyStart))) {
78
+ issues.push({
79
+ type: 'rlsPolicy',
80
+ file: relativePath,
81
+ line: getLineNumber(content, policyStart),
82
+ message: `RLS policy '${policyName}' contains inline auth.uid() call. Must use helper function instead for performance.`,
83
+ code: getCodeSnippet(content, policyStart, 0, 200),
84
+ severity: 'error',
85
+ fix: 'Replace auth.uid() with helper function like get_effective_user_id(). Helper functions must be STABLE SECURITY DEFINER.',
86
+ });
87
+ }
88
+
89
+ // Check for subqueries in USING/WITH CHECK (performance issue)
90
+ const subqueryPattern = /(?:USING|WITH\s+CHECK)\s*\([^)]*(?:SELECT\s+[^)]+FROM[^)]+WHERE[^)]*)\)/gi;
91
+ if (subqueryPattern.test(policyBody)) {
92
+ issues.push({
93
+ type: 'rlsPolicy',
94
+ file: relativePath,
95
+ line: getLineNumber(content, policyStart),
96
+ message: `RLS policy '${policyName}' contains subquery. Must use helper functions instead for performance.`,
97
+ code: getCodeSnippet(content, policyStart, 0, 200),
98
+ severity: 'error',
99
+ fix: 'Replace subquery with helper function. Helper functions must be STABLE SECURITY DEFINER SET search_path TO public.',
100
+ });
101
+ }
102
+
103
+ // Check for inline current_setting calls (performance issue)
104
+ const currentSettingPattern = /\bcurrent_setting\s*\(/gi;
105
+ if (currentSettingPattern.test(policyBody) && !isInCommentOrStringSQL(content, content.indexOf('current_setting', policyStart))) {
106
+ issues.push({
107
+ type: 'rlsPolicy',
108
+ file: relativePath,
109
+ line: getLineNumber(content, policyStart),
110
+ message: `RLS policy '${policyName}' contains inline current_setting() call. Must use helper function instead for performance.`,
111
+ code: getCodeSnippet(content, policyStart, 0, 200),
112
+ severity: 'error',
113
+ fix: 'Replace current_setting() with helper function. Helper functions must be STABLE SECURITY DEFINER.',
114
+ });
115
+ }
116
+
117
+ // Check for is_super_admin() without parameter (security risk)
118
+ const isSuperAdminWithoutParamPattern = /\bis_super_admin\s*\(\s*\)/gi;
119
+ if (isSuperAdminWithoutParamPattern.test(policyBody) && !isInCommentOrStringSQL(content, content.indexOf('is_super_admin', policyStart))) {
120
+ issues.push({
121
+ type: 'rlsPolicy',
122
+ file: relativePath,
123
+ line: getLineNumber(content, policyStart),
124
+ message: `RLS policy '${policyName}' uses is_super_admin() without parameter. Must use is_super_admin(safe_get_user_id_for_rls()) to avoid fallback strategy vulnerabilities.`,
125
+ code: getCodeSnippet(content, policyStart, 0, 200),
126
+ severity: 'error',
127
+ fix: 'Replace is_super_admin() with is_super_admin(safe_get_user_id_for_rls()) to require explicit parameter passing.',
128
+ });
129
+ }
130
+
131
+ // Check for missing super-admin checks in authenticated policies
132
+ const isAuthenticatedPolicy = /TO\s+authenticated/i.test(policyBody);
133
+ const hasSuperAdminCheck = /\bis_super_admin\s*\(/i.test(policyBody);
134
+ const isPublicPolicy = /TO\s+(?:public|anon)/i.test(policyBody) || policyName.toLowerCase().includes('public') || policyName.toLowerCase().includes('anon');
135
+
136
+ // Exception: User-scoped policies that only check user_id don't need super-admin
137
+ const isUserScopedOnly = /organisation_id\s+IS\s+NULL/i.test(policyBody) &&
138
+ /get_effective_user_id\s*\(\s*\)\s*=\s*user_id/i.test(policyBody) &&
139
+ !/\bOR\b/i.test(policyBody);
140
+
141
+ // Skip check for public/anonymous policies or service role policies
142
+ if (isAuthenticatedPolicy && !hasSuperAdminCheck && !isPublicPolicy && !policyName.toLowerCase().includes('service')) {
143
+ if (!isUserScopedOnly) {
144
+ issues.push({
145
+ type: 'rlsPolicy',
146
+ file: relativePath,
147
+ line: getLineNumber(content, policyStart),
148
+ message: `RLS policy '${policyName}' for authenticated users is missing super-admin check. All authenticated policies must include is_super_admin(safe_get_user_id_for_rls()) as a bypass.`,
149
+ code: getCodeSnippet(content, policyStart, 0, 200),
150
+ severity: 'error',
151
+ fix: 'Add is_super_admin(safe_get_user_id_for_rls()) check. Pattern: (is_super_admin(safe_get_user_id_for_rls()) OR ...other checks...)',
152
+ });
153
+ }
154
+ }
155
+
156
+ // Check for use of deprecated event access functions when RBAC permissions should be used
157
+ const usesEventAccess = /\bcheck_user_event_access\s*\(/i.test(policyBody);
158
+ const usesEventCreator = /\bcheck_user_is_event_creator\s*\(/i.test(policyBody);
159
+ const usesRBACPermission = /\bcheck_rbac_permission_with_context\s*\(/i.test(policyBody);
160
+
161
+ // Tables that have page permissions and should use RBAC permission checks
162
+ const tablesWithPagePermissions = [
163
+ 'trac_contacts', 'trac_risks', 'trac_journal_posts', 'trac_currency_rates',
164
+ 'trac_accommodation', 'trac_activity', 'trac_transport',
165
+ 'mint_budgets', 'mint_budget_variables',
166
+ 'cake_dish', 'cake_meal', 'cake_item', 'cake_recipe',
167
+ 'medi_profile', 'medi_action_plan'
168
+ ];
169
+
170
+ const hasPagePermissions = tablesWithPagePermissions.some(t =>
171
+ tableName.toLowerCase().includes(t.toLowerCase())
172
+ );
173
+
174
+ if (hasPagePermissions && (usesEventAccess || usesEventCreator) && !usesRBACPermission) {
175
+ issues.push({
176
+ type: 'rlsPolicy',
177
+ file: relativePath,
178
+ line: getLineNumber(content, policyStart),
179
+ message: `RLS policy '${policyName}' uses ${usesEventAccess ? 'check_user_event_access()' : 'check_user_is_event_creator()'} but table '${tableName}' has page permissions. Should use check_rbac_permission_with_context() with page-level permissions instead.`,
180
+ code: getCodeSnippet(content, policyStart, 0, 200),
181
+ severity: 'error',
182
+ fix: `Replace ${usesEventAccess ? 'check_user_event_access(event_id)' : 'check_user_is_event_creator(event_id)'} with check_rbac_permission_with_context('${operation}:page.{page_name}', '{page_name}', organisation_id, event_id::text, get_app_id('{app_name}')). See RBAC compliance standard for page name mapping.`,
183
+ });
184
+ }
185
+
186
+ // Check for missing required field checks in event-scoped tables
187
+ const hasEventIdColumn = /event_id/i.test(policyBody) || tableName.toLowerCase().includes('trac_') ||
188
+ tableName.toLowerCase().includes('mint_') ||
189
+ tableName.toLowerCase().includes('cake_') ||
190
+ tableName.toLowerCase().includes('medi_');
191
+ const hasOrganisationIdColumn = /organisation_id/i.test(policyBody) ||
192
+ !tableName.toLowerCase().includes('rbac_') &&
193
+ !tableName.toLowerCase().includes('core_organisations');
194
+
195
+ const checksEventIdNotNull = /event_id\s+IS\s+NOT\s+NULL/i.test(policyBody);
196
+ const checksOrganisationIdNotNull = /organisation_id\s+IS\s+NOT\s+NULL/i.test(policyBody);
197
+
198
+ // For INSERT policies on event-scoped tables, event_id should be required
199
+ if (operation === 'insert' && hasEventIdColumn && !checksEventIdNotNull) {
200
+ issues.push({
201
+ type: 'rlsPolicy',
202
+ file: relativePath,
203
+ line: getLineNumber(content, policyStart),
204
+ message: `RLS INSERT policy '${policyName}' for event-scoped table '${tableName}' should require event_id IS NOT NULL. Permission checks need event_id to verify event-level roles.`,
205
+ code: getCodeSnippet(content, policyStart, 0, 200),
206
+ severity: 'error',
207
+ fix: 'Add event_id IS NOT NULL check to WITH CHECK clause. Pattern: event_id IS NOT NULL AND ...',
208
+ });
209
+ }
210
+
211
+ // For authenticated policies, organisation_id should typically be checked
212
+ if (isAuthenticatedPolicy && hasOrganisationIdColumn && !checksOrganisationIdNotNull &&
213
+ !isUserScopedOnly && !tableName.toLowerCase().includes('rbac_')) {
214
+ // Exception: Some tables legitimately allow NULL organisation_id (e.g., user profiles)
215
+ const allowsNullOrg = /organisation_id\s+IS\s+NULL/i.test(policyBody) &&
216
+ /get_effective_user_id\s*\(\s*\)\s*=\s*user_id/i.test(policyBody);
217
+
218
+ if (!allowsNullOrg) {
219
+ issues.push({
220
+ type: 'rlsPolicy',
221
+ file: relativePath,
222
+ line: getLineNumber(content, policyStart),
223
+ message: `RLS policy '${policyName}' for table '${tableName}' should check organisation_id IS NOT NULL. Permission checks need organisation_id for proper RBAC context.`,
224
+ code: getCodeSnippet(content, policyStart, 0, 200),
225
+ severity: 'warning',
226
+ fix: 'Add organisation_id IS NOT NULL check. Pattern: organisation_id IS NOT NULL AND ...',
227
+ });
228
+ }
229
+ }
230
+ }
231
+
232
+ // Check helper function definitions for required attributes
233
+ const functionPattern = /CREATE\s+(?:OR\s+REPLACE\s+)?FUNCTION\s+["']?(\w+)["']?\s*\([^)]*\)\s*RETURNS[^;]+LANGUAGE\s+plpgsql[^;]*AS/gi;
234
+ let funcMatch;
235
+ while ((funcMatch = functionPattern.exec(content)) !== null) {
236
+ const funcName = funcMatch[1];
237
+ const funcStart = funcMatch.index;
238
+ const funcEnd = content.indexOf('AS $$', funcStart);
239
+ if (funcEnd === -1) continue;
240
+
241
+ const funcDef = content.substring(funcStart, funcEnd);
242
+
243
+ // Only flag if function looks like it might be used in RLS (has common helper function patterns)
244
+ const isLikelyRLSHelper = /check_|get_|is_/.test(funcName.toLowerCase());
245
+
246
+ // Check for security-critical functions with fallback strategies
247
+ if (funcName.toLowerCase() === 'is_super_admin' || funcName.toLowerCase().includes('super_admin')) {
248
+ const funcBodyStart = content.indexOf('AS $$', funcStart);
249
+ if (funcBodyStart !== -1) {
250
+ const funcBodyEnd = content.indexOf('$$;', funcBodyStart);
251
+ if (funcBodyEnd !== -1) {
252
+ const funcBody = content.substring(funcBodyStart, funcBodyEnd);
253
+
254
+ // Check for DEFAULT NULL parameter (allows fallback strategies)
255
+ const hasDefaultNull = /p_\w+\s+UUID\s+DEFAULT\s+NULL/i.test(funcDef);
256
+
257
+ // Check for multiple fallback patterns in function body
258
+ const hasFallbackPatterns = (
259
+ /IF\s+\w+\s+IS\s+NULL\s+THEN/i.test(funcBody) &&
260
+ /ELSE/i.test(funcBody) &&
261
+ (/\bauth\.uid\s*\(/i.test(funcBody) || /\bcurrent_setting\s*\(/i.test(funcBody))
262
+ );
263
+
264
+ if (hasDefaultNull || hasFallbackPatterns) {
265
+ issues.push({
266
+ type: 'rlsPolicy',
267
+ file: relativePath,
268
+ line: getLineNumber(content, funcStart),
269
+ message: `Security-critical function '${funcName}' uses fallback strategies (DEFAULT NULL parameter or multiple fallback patterns). This is a security risk - functions should require explicit parameters and fail secure.`,
270
+ code: getCodeSnippet(content, funcStart, 0, 300),
271
+ severity: 'error',
272
+ fix: 'Remove DEFAULT NULL parameter and all fallback logic. Function should require explicit parameter and return false if parameter is NULL (fail secure).',
273
+ });
274
+ }
275
+ }
276
+ }
277
+ }
278
+
279
+ if (isLikelyRLSHelper) {
280
+ const hasStable = /\bSTABLE\b/i.test(funcDef);
281
+ const hasSecurityDefiner = /\bSECURITY\s+DEFINER\b/i.test(funcDef);
282
+ const hasSearchPath = /SET\s+search_path\s+TO\s+['"]?public['"]?/i.test(funcDef);
283
+
284
+ if (!hasStable) {
285
+ issues.push({
286
+ type: 'rlsPolicy',
287
+ file: relativePath,
288
+ line: getLineNumber(content, funcStart),
289
+ message: `Helper function '${funcName}' missing STABLE attribute. RLS helper functions must be STABLE for performance.`,
290
+ code: getCodeSnippet(content, funcStart, 0, 150),
291
+ severity: 'error',
292
+ fix: 'Add STABLE attribute to function definition.',
293
+ });
294
+ }
295
+
296
+ if (!hasSecurityDefiner) {
297
+ issues.push({
298
+ type: 'rlsPolicy',
299
+ file: relativePath,
300
+ line: getLineNumber(content, funcStart),
301
+ message: `Helper function '${funcName}' missing SECURITY DEFINER attribute. RLS helper functions must be SECURITY DEFINER to bypass RLS recursion.`,
302
+ code: getCodeSnippet(content, funcStart, 0, 150),
303
+ severity: 'error',
304
+ fix: 'Add SECURITY DEFINER attribute to function definition.',
305
+ });
306
+ }
307
+
308
+ if (!hasSearchPath) {
309
+ issues.push({
310
+ type: 'rlsPolicy',
311
+ file: relativePath,
312
+ line: getLineNumber(content, funcStart),
313
+ message: `Helper function '${funcName}' missing SET search_path TO public. Required to prevent search path injection.`,
314
+ code: getCodeSnippet(content, funcStart, 0, 150),
315
+ severity: 'error',
316
+ fix: 'Add SET search_path TO public to function definition.',
317
+ });
318
+ }
319
+ }
320
+ }
321
+ } catch (error) {
322
+ // Skip files that can't be read
323
+ }
324
+ });
325
+
326
+ return issues;
327
+ }
328
+
329
+ /**
330
+ * Check PagePermissionGuard coverage (all protected pages have guard)
331
+ */
332
+ function checkPagePermissionGuardCoverage(consumingAppPath) {
333
+ const issues = [];
334
+
335
+ const srcDir = path.join(consumingAppPath, 'src');
336
+ if (!directoryExists(srcDir)) {
337
+ return issues;
338
+ }
339
+
340
+ const sourceFiles = findSourceFiles(srcDir);
341
+
342
+ // Helper to check if file is likely a page component
343
+ function isPageComponent(filePath, content) {
344
+ const fileName = path.basename(filePath);
345
+ const dirName = path.dirname(filePath);
346
+ const dirParts = dirName.split(path.sep);
347
+
348
+ // EXCLUDE: Provider components, routing components, shared components
349
+ if (dirParts.some(part => part.toLowerCase() === 'providers' || part.toLowerCase() === 'provider')) {
350
+ return false;
351
+ }
352
+ if (/Route(s)?\.(tsx?|jsx?)$/i.test(fileName)) {
353
+ return false;
354
+ }
355
+
356
+ // INCLUDE: Files in pages/ directory
357
+ if (dirParts.some(part => part.toLowerCase() === 'pages')) {
358
+ return true;
359
+ }
360
+
361
+ // INCLUDE: Files matching *Page.tsx pattern
362
+ if (/Page\.(tsx?|jsx?)$/i.test(fileName)) {
363
+ return true;
364
+ }
365
+
366
+ return false;
367
+ }
368
+
369
+ sourceFiles.forEach(filePath => {
370
+ const content = readFileSafe(filePath);
371
+ if (!content) {
372
+ return;
373
+ }
374
+
375
+ if (!isPageComponent(filePath, content)) {
376
+ return;
377
+ }
378
+
379
+ const relativePath = getRelativePath(filePath, consumingAppPath);
380
+
381
+ // Check if PagePermissionGuard is used
382
+ const hasPagePermissionGuard = /<PagePermissionGuard/.test(content) ||
383
+ /PagePermissionGuard\s*</.test(content);
384
+
385
+ // Check if RBAC hooks are imported (indicates this should be protected)
386
+ const hasRBACHooks = importsFromPaceCore(content, 'useCan') ||
387
+ importsFromPaceCore(content, 'useResourcePermissions') ||
388
+ importsFromPaceCore(content, 'usePermissions') ||
389
+ importsFromPaceCore(content, 'useRBAC');
390
+
391
+ // Check if component returns JSX (likely a page)
392
+ const returnsJSX = /return\s*\(/.test(content) || /return\s+</.test(content);
393
+
394
+ if (returnsJSX && !hasPagePermissionGuard) {
395
+ if (hasRBACHooks) {
396
+ // Definitely should be protected
397
+ issues.push({
398
+ type: 'rbacPageGuard',
399
+ file: relativePath,
400
+ line: 1,
401
+ message: 'Page component missing PagePermissionGuard wrapper. Pages using RBAC hooks must be protected.',
402
+ severity: 'error',
403
+ fix: 'Wrap page content with <PagePermissionGuard pageName="page-name" operation="read">',
404
+ });
405
+ } else {
406
+ // Might be a public page, but flag for review
407
+ issues.push({
408
+ type: 'rbacPageGuard',
409
+ file: relativePath,
410
+ line: 1,
411
+ message: 'Page component does not use PagePermissionGuard. Verify if this page should be protected.',
412
+ severity: 'warning',
413
+ fix: 'If page should be protected, wrap with <PagePermissionGuard pageName="page-name" operation="read">',
414
+ });
415
+ }
416
+ }
417
+
418
+ // Check if PagePermissionGuard is used incorrectly (missing required props)
419
+ if (hasPagePermissionGuard) {
420
+ const pageGuardPattern = /<PagePermissionGuard[^>]*>/g;
421
+ let match;
422
+ while ((match = pageGuardPattern.exec(content)) !== null) {
423
+ if (isInCommentOrString(content, match.index)) {
424
+ continue;
425
+ }
426
+
427
+ const guardProps = match[0];
428
+ const hasPageName = /pageName\s*=/.test(guardProps);
429
+ const hasOperation = /operation\s*=/.test(guardProps);
430
+
431
+ if (!hasPageName || !hasOperation) {
432
+ issues.push({
433
+ type: 'rbacPageGuard',
434
+ file: relativePath,
435
+ line: getLineNumber(content, match.index),
436
+ message: 'PagePermissionGuard missing required props (pageName or operation)',
437
+ code: guardProps,
438
+ severity: 'error',
439
+ fix: 'Add required props: <PagePermissionGuard pageName="page-name" operation="read">',
440
+ });
441
+ }
442
+ }
443
+ }
444
+ });
445
+
446
+ return issues;
447
+ }
448
+
449
+ /**
450
+ * Check Edge Functions RBAC setup
451
+ */
452
+ function checkEdgeFunctionsRBAC(consumingAppPath) {
453
+ const issues = [];
454
+
455
+ const edgeFunctionsDir = path.join(consumingAppPath, 'supabase', 'functions');
456
+ if (!directoryExists(edgeFunctionsDir)) {
457
+ return issues; // No edge functions, skip
458
+ }
459
+
460
+ // Find all edge function files
461
+ function findEdgeFunctionFiles(dir) {
462
+ const files = [];
463
+ if (!fs.existsSync(dir)) return files;
464
+
465
+ const entries = fs.readdirSync(dir);
466
+ entries.forEach(entry => {
467
+ const entryPath = path.join(dir, entry);
468
+ const stat = fs.statSync(entryPath);
469
+
470
+ if (stat.isDirectory()) {
471
+ // Look for index.ts or index.js in function directory
472
+ const indexFiles = ['index.ts', 'index.js', 'index.tsx', 'index.jsx'];
473
+ indexFiles.forEach(indexFile => {
474
+ const indexPath = path.join(entryPath, indexFile);
475
+ if (fs.existsSync(indexPath)) {
476
+ files.push(indexPath);
477
+ }
478
+ });
479
+ }
480
+ });
481
+
482
+ return files;
483
+ }
484
+
485
+ const edgeFunctionFiles = findEdgeFunctionFiles(edgeFunctionsDir);
486
+
487
+ edgeFunctionFiles.forEach(filePath => {
488
+ const content = readFileSafe(filePath);
489
+ if (!content) {
490
+ return;
491
+ }
492
+
493
+ const relativePath = getRelativePath(filePath, consumingAppPath);
494
+
495
+ // Check for setupRBAC import
496
+ const hasSetupRBAC = /setupRBAC|from\s+['"]@jmruthers\/pace-core\/rbac['"]/.test(content);
497
+
498
+ // Check for isPermitted usage
499
+ const hasIsPermitted = /\bisPermitted\s*\(/.test(content);
500
+
501
+ // If function uses RBAC but doesn't call setupRBAC
502
+ if (hasIsPermitted && !hasSetupRBAC) {
503
+ issues.push({
504
+ type: 'edgeFunctionRBAC',
505
+ file: relativePath,
506
+ line: 1,
507
+ message: 'Edge function uses isPermitted() but does not call setupRBAC(). Must call setupRBAC() before using RBAC functions.',
508
+ severity: 'error',
509
+ fix: 'Add: import { setupRBAC, isPermitted } from \'@jmruthers/pace-core/rbac\'; and call setupRBAC() at the start of the handler.',
510
+ });
511
+ }
512
+ });
513
+
514
+ return issues;
515
+ }
516
+
517
+ /**
518
+ * Run audit for Standard 6: Security & RBAC
519
+ * @param {string} consumingAppPath - Path to consuming app
520
+ * @returns {object} - Audit results with issues array
521
+ */
522
+ function runStandard6Audit(consumingAppPath) {
523
+ const issues = [];
524
+
525
+ try {
526
+ issues.push(...checkRLSPolicies(consumingAppPath));
527
+ issues.push(...checkPagePermissionGuardCoverage(consumingAppPath));
528
+ issues.push(...checkEdgeFunctionsRBAC(consumingAppPath));
529
+ } catch (error) {
530
+ return {
531
+ standard: '06-security-rbac',
532
+ issues: [],
533
+ error: error.message,
534
+ };
535
+ }
536
+
537
+ return {
538
+ standard: '06-security-rbac',
539
+ issues,
540
+ error: null,
541
+ };
542
+ }
543
+
544
+ module.exports = { runStandard6Audit };