@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.
- package/CHANGELOG.md +104 -0
- package/README.md +5 -403
- package/audit-tool/00-dependencies.cjs +394 -0
- package/audit-tool/audits/01-pace-core-compliance.cjs +556 -0
- package/audit-tool/audits/02-project-structure.cjs +255 -0
- package/audit-tool/audits/03-architecture.cjs +196 -0
- package/audit-tool/audits/04-code-quality.cjs +149 -0
- package/audit-tool/audits/05-styling.cjs +224 -0
- package/audit-tool/audits/06-security-rbac.cjs +544 -0
- package/audit-tool/audits/07-api-tech-stack.cjs +301 -0
- package/audit-tool/audits/08-testing-documentation.cjs +202 -0
- package/audit-tool/audits/09-operations.cjs +208 -0
- package/audit-tool/index.cjs +291 -0
- package/audit-tool/utils/code-utils.cjs +218 -0
- package/audit-tool/utils/file-utils.cjs +230 -0
- package/audit-tool/utils/report-utils.cjs +241 -0
- package/core-usage-manifest.json +93 -0
- package/cursor-rules/00-standards-overview.mdc +156 -0
- package/cursor-rules/01-pace-core-compliance.mdc +586 -0
- package/cursor-rules/02-project-structure.mdc +42 -4
- package/cursor-rules/{03-solid-principles.mdc → 03-architecture.mdc} +126 -10
- package/cursor-rules/04-code-quality.mdc +419 -0
- package/cursor-rules/{08-markup-quality.mdc → 05-styling.mdc} +104 -34
- package/cursor-rules/06-security-rbac.mdc +518 -0
- package/cursor-rules/07-api-tech-stack.mdc +377 -0
- package/cursor-rules/08-testing-documentation.mdc +324 -0
- package/cursor-rules/09-operations.mdc +365 -0
- package/dist/{AuthService-Cb34EQs3.d.ts → AuthService-DmfO5rGS.d.ts} +10 -0
- package/dist/DataTable-7PMH7XN7.js +15 -0
- package/dist/{DataTable-BMRU8a1j.d.ts → DataTable-DRUIgtUH.d.ts} +1 -1
- package/dist/{PublicPageProvider-QTFVrL-Z.d.ts → PublicPageProvider-DlsCaR5v.d.ts} +33 -72
- package/dist/UnifiedAuthProvider-ZT6TIGM7.js +7 -0
- package/dist/api-Y4MQWOFW.js +4 -0
- package/dist/audit-MYQXYZFU.js +3 -0
- package/dist/{chunk-DGUM43GV.js → chunk-3RG5ZIWI.js} +1 -4
- package/dist/{chunk-QXHPKYJV.js → chunk-4SXLQIZO.js} +1 -26
- package/dist/{chunk-UPPMRMYG.js → chunk-5X4QLXRG.js} +73 -151
- package/dist/chunk-6F3IILHI.js +62 -0
- package/dist/{chunk-E66EQZE6.js → chunk-6GLLNA6U.js} +3 -9
- package/dist/{chunk-ZSAAAMVR.js → chunk-6QYDGKQY.js} +1 -4
- package/dist/{chunk-FMUCXFII.js → chunk-7ILTDCL2.js} +9 -5
- package/dist/{chunk-M43Y4SSO.js → chunk-A3W6LW53.js} +15 -13
- package/dist/{chunk-63FOKYGO.js → chunk-AHU7G2R5.js} +2 -11
- package/dist/{chunk-HU2C6SSC.js → chunk-BM4CQ5P3.js} +606 -559
- package/dist/chunk-C7NSAPTL.js +1 -0
- package/dist/{chunk-J36DSWQK.js → chunk-FEJLJNWA.js} +7 -41
- package/dist/{chunk-IHB5DR3H.js → chunk-FTCRZOG2.js} +188 -387
- package/dist/{chunk-G37KK66H.js → chunk-FYHN4DD5.js} +60 -19
- package/dist/chunk-GHYHJTYV.js +994 -0
- package/dist/{chunk-VBXEHIUJ.js → chunk-HF6O3O37.js} +6 -88
- package/dist/{chunk-FFQEQTNW.js → chunk-IUBRCBSY.js} +134 -45
- package/dist/{chunk-6COVEUS7.js → chunk-JGWDVX64.js} +983 -1034
- package/dist/{chunk-RGAWHO7N.js → chunk-L4XMVJKY.js} +77 -222
- package/dist/chunk-MBADTM7L.js +64 -0
- package/dist/{chunk-M7MPQISP.js → chunk-OJ4SKRSV.js} +3 -16
- package/dist/{chunk-IVOFDYWT.js → chunk-Q7Q7V5NV.js} +2109 -1604
- package/dist/{chunk-JGRYX5UX.js → chunk-S7DKJPLT.js} +29 -58
- package/dist/{chunk-PWLANIRT.js → chunk-TTRFSOKR.js} +1 -7
- package/dist/{chunk-5DRSZLL2.js → chunk-UH3NTO3F.js} +1 -6
- package/dist/{chunk-NTM7ZSB6.js → chunk-VBCS3DUA.js} +261 -168
- package/dist/{chunk-EFN2EIMK.js → chunk-ZFYPMX46.js} +271 -87
- package/dist/{chunk-L4OXEN46.js → chunk-ZKAWKYT4.js} +10 -24
- package/dist/components.d.ts +7 -5
- package/dist/components.js +46 -257
- package/dist/{database.generated-CzIvgcPu.d.ts → database.generated-CcnC_DRc.d.ts} +4795 -3691
- package/dist/eslint-rules/index.cjs +35 -0
- package/{src/eslint-rules/pace-core-compliance.cjs → dist/eslint-rules/rules/01-pace-core-compliance.cjs} +234 -235
- package/dist/eslint-rules/rules/04-code-quality.cjs +290 -0
- package/dist/eslint-rules/rules/05-styling.cjs +61 -0
- package/dist/eslint-rules/rules/06-security-rbac.cjs +806 -0
- package/dist/eslint-rules/rules/07-api-tech-stack.cjs +263 -0
- package/dist/eslint-rules/rules/08-testing.cjs +94 -0
- package/dist/eslint-rules/utils/helpers.cjs +42 -0
- package/dist/eslint-rules/utils/manifest-loader.cjs +75 -0
- package/dist/hooks.d.ts +6 -6
- package/dist/hooks.js +62 -172
- package/dist/icons/index.d.ts +1 -0
- package/dist/icons/index.js +1 -0
- package/dist/index.d.ts +12 -11
- package/dist/index.js +67 -660
- package/dist/providers.d.ts +2 -2
- package/dist/providers.js +8 -35
- package/dist/rbac/eslint-rules.d.ts +46 -44
- package/dist/rbac/eslint-rules.js +7 -4
- package/dist/rbac/index.d.ts +109 -586
- package/dist/rbac/index.js +14 -207
- package/dist/styles/index.js +2 -12
- package/dist/theming/runtime.d.ts +14 -1
- package/dist/theming/runtime.js +3 -19
- package/dist/{timezone-CHhWg6b4.d.ts → timezone-BZe_eUxx.d.ts} +175 -1
- package/dist/{types-CkbwOr4Y.d.ts → types-DXstZpNI.d.ts} +4 -17
- package/dist/types-t9H8qKRw.d.ts +55 -0
- package/dist/types.d.ts +1 -1
- package/dist/types.js +7 -94
- package/dist/{usePublicRouteParams-ClnV4tnv.d.ts → usePublicRouteParams-MamNgwqe.d.ts} +20 -20
- package/dist/utils.d.ts +24 -117
- package/dist/utils.js +54 -392
- package/docs/README.md +17 -7
- package/docs/api/README.md +4 -402
- package/docs/api/modules.md +301 -871
- package/docs/api-reference/components.md +21 -21
- package/docs/api-reference/deprecated.md +31 -6
- package/docs/api-reference/hooks.md +80 -80
- package/docs/api-reference/rpc-functions.md +78 -3
- package/docs/api-reference/types.md +1 -1
- package/docs/api-reference/utilities.md +1 -1
- package/docs/architecture/README.md +1 -1
- package/docs/core-concepts/events.md +3 -3
- package/docs/core-concepts/organisations.md +6 -6
- package/docs/core-concepts/permissions.md +6 -6
- package/docs/documentation-index.md +12 -18
- package/docs/getting-started/cursor-rules.md +3 -23
- package/docs/getting-started/dependencies.md +650 -0
- package/docs/getting-started/documentation-index.md +1 -1
- package/docs/getting-started/examples/README.md +4 -4
- package/docs/getting-started/examples/full-featured-app.md +1 -1
- package/docs/getting-started/faq.md +2 -2
- package/docs/getting-started/installation-guide.md +20 -7
- package/docs/getting-started/quick-reference.md +4 -4
- package/docs/getting-started/quick-start.md +23 -12
- package/docs/implementation-guides/authentication.md +15 -15
- package/docs/implementation-guides/component-styling.md +1 -1
- package/docs/implementation-guides/data-tables.md +126 -33
- package/docs/implementation-guides/datatable-rbac-usage.md +1 -1
- package/docs/implementation-guides/dynamic-colors.md +3 -3
- package/docs/implementation-guides/file-upload-storage.md +2 -2
- package/docs/implementation-guides/hierarchical-datatable.md +40 -60
- package/docs/implementation-guides/inactivity-tracking.md +3 -3
- package/docs/implementation-guides/large-datasets.md +3 -2
- package/docs/implementation-guides/organisation-security.md +2 -2
- package/docs/implementation-guides/performance.md +2 -2
- package/docs/implementation-guides/permission-enforcement.md +5 -1
- package/docs/migration/V0.3.44_organisation-context-timing-fix.md +1 -1
- package/docs/migration/V0.4.0_rbac-migration.md +6 -6
- package/docs/rbac/MIGRATION_GUIDE.md +819 -0
- package/docs/rbac/RBAC_CONTRACT.md +724 -0
- package/docs/rbac/README.md +17 -8
- package/docs/rbac/advanced-patterns.md +6 -6
- package/docs/rbac/api-reference.md +20 -20
- package/docs/rbac/edge-functions-guide.md +376 -0
- package/docs/rbac/event-based-apps.md +3 -3
- package/docs/rbac/examples.md +41 -41
- package/docs/rbac/getting-started.md +37 -37
- package/docs/rbac/performance.md +1 -1
- package/docs/rbac/quick-start.md +52 -52
- package/docs/rbac/secure-client-protection.md +1 -35
- package/docs/rbac/troubleshooting.md +1 -1
- package/docs/security/README.md +5 -5
- package/docs/standards/0-standards-overview.md +220 -0
- package/docs/standards/1-pace-core-compliance-standards.md +986 -0
- package/docs/standards/2-project-structure-standards.md +949 -0
- package/docs/standards/3-architecture-standards.md +606 -0
- package/docs/standards/4-code-quality-standards.md +728 -0
- package/docs/standards/5-styling-standards.md +348 -0
- package/docs/standards/{07-rbac-and-rls-standard.md → 6-security-rbac-standards.md} +269 -66
- package/docs/standards/7-api-tech-stack-standards.md +662 -0
- package/docs/standards/8-testing-documentation-standards.md +401 -0
- package/docs/standards/9-operations-standards.md +1102 -0
- package/docs/standards/README.md +185 -57
- package/docs/troubleshooting/README.md +4 -4
- package/docs/troubleshooting/common-issues.md +2 -2
- package/docs/troubleshooting/debugging.md +9 -9
- package/docs/troubleshooting/migration.md +4 -4
- package/docs/troubleshooting/organisation-context-setup.md +42 -19
- package/eslint-config-pace-core.cjs +33 -6
- package/package.json +35 -23
- package/scripts/install-cursor-rules.cjs +25 -6
- package/scripts/install-eslint-config.cjs +284 -0
- package/src/__tests__/fixtures/supabase.ts +1 -1
- package/src/__tests__/helpers/__tests__/component-test-utils.test.tsx +3 -3
- package/src/__tests__/helpers/__tests__/optimized-test-setup.test.ts +1 -1
- package/src/__tests__/helpers/__tests__/supabaseMock.test.ts +1 -1
- package/src/__tests__/helpers/__tests__/test-providers.test.tsx +2 -2
- package/src/__tests__/helpers/__tests__/test-utils.test.tsx +13 -13
- package/src/__tests__/helpers/component-test-utils.tsx +1 -1
- package/src/__tests__/helpers/supabaseMock.ts +2 -2
- package/src/__tests__/integration/UserProfile.test.tsx +14 -14
- package/src/__tests__/public-recipe-view.test.ts +38 -9
- package/src/__tests__/rbac/PagePermissionGuard.test.tsx +6 -6
- package/src/__tests__/templates/accessibility.test.template.tsx +9 -9
- package/src/__tests__/templates/component.test.template.tsx +18 -15
- package/src/components/Button/Button.tsx +5 -1
- package/src/components/Calendar/Calendar.tsx +201 -47
- package/src/components/ContextSelector/ContextSelector.tsx +106 -119
- package/src/components/DataTable/AUDIT_REPORT.md +293 -0
- package/src/components/DataTable/__tests__/DataTableCore.test.tsx +10 -2
- package/src/components/DataTable/__tests__/a11y.basic.test.tsx +10 -4
- package/src/components/DataTable/__tests__/test-utils/sharedTestUtils.tsx +9 -9
- package/src/components/DataTable/components/ColumnFilter.tsx +63 -74
- package/src/components/DataTable/components/ColumnVisibilityDropdown.tsx +43 -41
- package/src/components/DataTable/components/DataTableCore.tsx +186 -13
- package/src/components/DataTable/components/DataTableErrorBoundary.tsx +9 -11
- package/src/components/DataTable/components/DataTableLayout.tsx +35 -21
- package/src/components/DataTable/components/EditFields.tsx +23 -3
- package/src/components/DataTable/components/EditableRow.tsx +12 -9
- package/src/components/DataTable/components/EmptyState.tsx +10 -9
- package/src/components/DataTable/components/FilterRow.tsx +2 -4
- package/src/components/DataTable/components/ImportModal.tsx +124 -126
- package/src/components/DataTable/components/LoadingState.tsx +5 -6
- package/src/components/DataTable/components/RowComponent.tsx +12 -0
- package/src/components/DataTable/components/SortIndicator.tsx +50 -0
- package/src/components/DataTable/components/__tests__/COVERAGE_NOTE.md +4 -4
- package/src/components/DataTable/components/__tests__/ColumnFilter.test.tsx +23 -82
- package/src/components/DataTable/components/__tests__/DataTableErrorBoundary.test.tsx +37 -9
- package/src/components/DataTable/components/__tests__/EmptyState.test.tsx +7 -4
- package/src/components/DataTable/components/__tests__/FilterRow.test.tsx +12 -4
- package/src/components/DataTable/components/__tests__/LoadingState.test.tsx +41 -27
- package/src/components/DataTable/components/hooks/usePermissionTracking.ts +0 -4
- package/src/components/DataTable/components/index.ts +2 -1
- package/src/components/DataTable/hooks/__tests__/useDataTableState.test.ts +51 -47
- package/src/components/DataTable/hooks/useDataTablePermissions.ts +24 -21
- package/src/components/DataTable/hooks/useDataTableState.ts +125 -9
- package/src/components/DataTable/hooks/useTableColumns.ts +40 -2
- package/src/components/DataTable/hooks/useTableHandlers.ts +11 -0
- package/src/components/DataTable/types.ts +5 -18
- package/src/components/DataTable/utils/a11yUtils.ts +17 -0
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +2 -1
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.tsx +11 -15
- package/src/components/DateTimeField/DateTimeField.tsx +10 -9
- package/src/components/Dialog/Dialog.test.tsx +128 -104
- package/src/components/Dialog/Dialog.tsx +742 -24
- package/src/components/ErrorBoundary/ErrorBoundary.tsx +77 -79
- package/src/components/FileDisplay/FileDisplay.test.tsx +4 -2
- package/src/components/FileDisplay/FileDisplay.tsx +23 -17
- package/src/components/FileUpload/FileUpload.test.tsx +52 -14
- package/src/components/FileUpload/FileUpload.tsx +112 -130
- package/src/components/Form/Form.test.tsx +6 -8
- package/src/components/Form/Form.tsx +365 -4
- package/src/components/NavigationMenu/NavigationMenu.test.tsx +14 -13
- package/src/components/NavigationMenu/useNavigationFiltering.ts +11 -21
- package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +6 -4
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +11 -15
- package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +108 -61
- package/src/components/PaceLoginPage/PaceLoginPage.tsx +27 -3
- package/src/components/Progress/Progress.tsx +2 -4
- package/src/components/ProtectedRoute/ProtectedRoute.tsx +8 -8
- package/src/components/Select/Select.tsx +109 -98
- package/src/components/Select/types.ts +4 -1
- package/src/components/UserMenu/UserMenu.tsx +9 -6
- package/src/hooks/__tests__/ServiceHooks.test.tsx +16 -16
- package/src/hooks/__tests__/hooks.integration.test.tsx +55 -57
- package/src/hooks/__tests__/useAppConfig.unit.test.ts +129 -67
- package/src/hooks/__tests__/useFocusTrap.unit.test.tsx +97 -97
- package/src/hooks/__tests__/usePublicEvent.simple.test.ts +149 -67
- package/src/hooks/__tests__/usePublicEvent.test.ts +149 -79
- package/src/hooks/__tests__/usePublicEvent.unit.test.ts +158 -109
- package/src/hooks/__tests__/useSessionDraft.test.ts +163 -0
- package/src/hooks/__tests__/useSessionRestoration.unit.test.tsx +10 -5
- package/src/hooks/public/usePublicEvent.ts +67 -195
- package/src/hooks/public/usePublicEventLogo.test.ts +70 -17
- package/src/hooks/public/usePublicEventLogo.ts +24 -14
- package/src/hooks/public/usePublicFileDisplay.ts +2 -2
- package/src/hooks/public/usePublicRouteParams.ts +5 -5
- package/src/hooks/useAppConfig.ts +28 -26
- package/src/hooks/useEventTheme.test.ts +217 -239
- package/src/hooks/useEventTheme.ts +16 -28
- package/src/hooks/useFileDisplay.ts +2 -2
- package/src/hooks/useOrganisationPermissions.ts +5 -7
- package/src/hooks/useQueryCache.ts +0 -1
- package/src/hooks/useSessionDraft.ts +380 -0
- package/src/hooks/useSessionRestoration.ts +3 -1
- package/src/icons/index.ts +27 -0
- package/src/index.ts +5 -0
- package/src/providers/OrganisationProvider.tsx +23 -14
- package/src/providers/UnifiedAuthProvider.smoke.test.tsx +21 -21
- package/src/providers/__tests__/AuthProvider.test.tsx +21 -21
- package/src/providers/__tests__/EventProvider.test.tsx +61 -61
- package/src/providers/__tests__/InactivityProvider.test.tsx +56 -56
- package/src/providers/__tests__/OrganisationProvider.test.tsx +75 -75
- package/src/providers/__tests__/ProviderLifecycle.test.tsx +37 -37
- package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +103 -103
- package/src/providers/services/EventServiceProvider.tsx +1 -24
- package/src/providers/services/UnifiedAuthProvider.tsx +5 -48
- package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +7 -7
- package/src/providers/services/__tests__/UnifiedAuthProvider.integration.test.tsx +13 -10
- package/src/rbac/__tests__/adapters.comprehensive.test.tsx +7 -457
- package/src/rbac/__tests__/auth-rbac.e2e.test.tsx +33 -7
- package/src/rbac/adapters.tsx +7 -295
- package/src/rbac/api.test.ts +44 -56
- package/src/rbac/api.ts +10 -17
- package/src/rbac/cache-invalidation.ts +0 -1
- package/src/rbac/compliance/index.ts +10 -0
- package/src/rbac/compliance/pattern-detector.ts +553 -0
- package/src/rbac/compliance/runtime-compliance.ts +22 -0
- package/src/rbac/components/AccessDenied.tsx +150 -0
- package/src/rbac/components/NavigationGuard.tsx +12 -20
- package/src/rbac/components/PagePermissionGuard.tsx +4 -24
- package/src/rbac/components/__tests__/NavigationGuard.test.tsx +21 -8
- package/src/rbac/components/index.ts +3 -41
- package/src/rbac/eslint-rules.js +1 -1
- package/src/rbac/hooks/index.ts +0 -3
- package/src/rbac/hooks/permissions/index.ts +0 -3
- package/src/rbac/hooks/permissions/useAccessLevel.ts +4 -8
- package/src/rbac/hooks/usePermissions.ts +0 -3
- package/src/rbac/hooks/useResolvedScope.test.ts +57 -47
- package/src/rbac/hooks/useResolvedScope.ts +58 -140
- package/src/rbac/hooks/useResourcePermissions.test.ts +124 -38
- package/src/rbac/hooks/useResourcePermissions.ts +139 -48
- package/src/rbac/hooks/useRoleManagement.test.ts +65 -22
- package/src/rbac/hooks/useRoleManagement.ts +147 -19
- package/src/rbac/hooks/useSecureSupabase.ts +4 -8
- package/src/rbac/index.ts +7 -9
- package/src/rbac/utils/contextValidator.ts +9 -7
- package/src/services/AuthService.ts +130 -18
- package/src/services/EventService.ts +4 -97
- package/src/services/InactivityService.ts +16 -0
- package/src/services/OrganisationService.ts +7 -44
- package/src/services/__tests__/OrganisationService.test.ts +26 -8
- package/src/services/base/BaseService.ts +0 -3
- package/src/styles/core.css +7 -0
- package/src/theming/__tests__/parseEventColours.test.ts +9 -3
- package/src/theming/parseEventColours.ts +22 -10
- package/src/types/database.generated.ts +4733 -3809
- package/src/utils/__tests__/lazyLoad.unit.test.tsx +42 -39
- package/src/utils/__tests__/organisationContext.unit.test.ts +9 -10
- package/src/utils/context/organisationContext.test.ts +13 -28
- package/src/utils/context/organisationContext.ts +21 -52
- package/src/utils/dynamic/dynamicUtils.ts +1 -1
- package/src/utils/file-reference/index.ts +39 -15
- package/src/utils/formatting/formatDateTime.test.ts +3 -2
- package/src/utils/google-places/loadGoogleMapsScript.ts +29 -4
- package/src/utils/index.ts +4 -1
- package/src/utils/persistence/__tests__/keyDerivation.test.ts +135 -0
- package/src/utils/persistence/__tests__/sensitiveFieldDetection.test.ts +123 -0
- package/src/utils/persistence/keyDerivation.ts +304 -0
- package/src/utils/persistence/sensitiveFieldDetection.ts +212 -0
- package/src/utils/security/secureStorage.ts +5 -5
- package/src/utils/storage/README.md +1 -1
- package/src/utils/storage/helpers.ts +3 -3
- package/src/utils/supabase/createBaseClient.ts +147 -0
- package/src/utils/timezone/timezone.test.ts +1 -2
- package/src/utils/timezone/timezone.ts +1 -1
- package/src/utils/validation/csrf.ts +4 -4
- package/cursor-rules/00-pace-core-compliance.mdc +0 -331
- package/cursor-rules/01-standards-compliance.mdc +0 -244
- package/cursor-rules/04-testing-standards.mdc +0 -268
- package/cursor-rules/05-bug-reports-and-features.mdc +0 -246
- package/cursor-rules/06-code-quality.mdc +0 -309
- package/cursor-rules/07-tech-stack-compliance.mdc +0 -214
- package/cursor-rules/CHANGELOG.md +0 -119
- package/cursor-rules/README.md +0 -192
- package/dist/DataTable-AOVNCPTX.js +0 -175
- package/dist/DataTable-AOVNCPTX.js.map +0 -1
- package/dist/UnifiedAuthProvider-4SBX4LU5.js +0 -18
- package/dist/UnifiedAuthProvider-4SBX4LU5.js.map +0 -1
- package/dist/api-O6HTBX5Y.js +0 -52
- package/dist/api-O6HTBX5Y.js.map +0 -1
- package/dist/audit-V53FV5AG.js +0 -17
- package/dist/audit-V53FV5AG.js.map +0 -1
- package/dist/chunk-5DRSZLL2.js.map +0 -1
- package/dist/chunk-63FOKYGO.js.map +0 -1
- package/dist/chunk-6COVEUS7.js.map +0 -1
- package/dist/chunk-AFVQODI2.js +0 -263
- package/dist/chunk-AFVQODI2.js.map +0 -1
- package/dist/chunk-DGUM43GV.js.map +0 -1
- package/dist/chunk-E66EQZE6.js.map +0 -1
- package/dist/chunk-EFN2EIMK.js.map +0 -1
- package/dist/chunk-FFQEQTNW.js.map +0 -1
- package/dist/chunk-FMUCXFII.js.map +0 -1
- package/dist/chunk-G37KK66H.js.map +0 -1
- package/dist/chunk-G7QEZTYQ.js +0 -2053
- package/dist/chunk-G7QEZTYQ.js.map +0 -1
- package/dist/chunk-HU2C6SSC.js.map +0 -1
- package/dist/chunk-IHB5DR3H.js.map +0 -1
- package/dist/chunk-IVOFDYWT.js.map +0 -1
- package/dist/chunk-J36DSWQK.js.map +0 -1
- package/dist/chunk-JGRYX5UX.js.map +0 -1
- package/dist/chunk-KQCRWDSA.js +0 -1
- package/dist/chunk-KQCRWDSA.js.map +0 -1
- package/dist/chunk-L4OXEN46.js.map +0 -1
- package/dist/chunk-LMC26NLJ.js +0 -84
- package/dist/chunk-LMC26NLJ.js.map +0 -1
- package/dist/chunk-M43Y4SSO.js.map +0 -1
- package/dist/chunk-M7MPQISP.js.map +0 -1
- package/dist/chunk-NTM7ZSB6.js.map +0 -1
- package/dist/chunk-PWLANIRT.js.map +0 -1
- package/dist/chunk-QXHPKYJV.js.map +0 -1
- package/dist/chunk-RGAWHO7N.js.map +0 -1
- package/dist/chunk-UPPMRMYG.js.map +0 -1
- package/dist/chunk-VBXEHIUJ.js.map +0 -1
- package/dist/chunk-ZSAAAMVR.js.map +0 -1
- package/dist/components.js.map +0 -1
- package/dist/contextValidator-5OGXSPKS.js +0 -9
- package/dist/contextValidator-5OGXSPKS.js.map +0 -1
- package/dist/eslint-rules/pace-core-compliance.cjs +0 -510
- package/dist/hooks.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/providers.js.map +0 -1
- package/dist/rbac/eslint-rules.js.map +0 -1
- package/dist/rbac/index.js.map +0 -1
- package/dist/styles/index.js.map +0 -1
- package/dist/theming/runtime.js.map +0 -1
- package/dist/types.js.map +0 -1
- package/dist/utils.js.map +0 -1
- package/docs/best-practices/README.md +0 -472
- package/docs/best-practices/accessibility.md +0 -601
- package/docs/best-practices/common-patterns.md +0 -516
- package/docs/best-practices/deployment.md +0 -1103
- package/docs/best-practices/performance.md +0 -1328
- package/docs/best-practices/security.md +0 -940
- package/docs/best-practices/testing.md +0 -1034
- package/docs/rbac/compliance/compliance-guide.md +0 -544
- package/docs/standards/01-architecture-standard.md +0 -44
- package/docs/standards/02-api-and-rpc-standard.md +0 -39
- package/docs/standards/03-component-standard.md +0 -32
- package/docs/standards/04-code-style-standard.md +0 -32
- package/docs/standards/05-security-standard.md +0 -44
- package/docs/standards/06-testing-and-docs-standard.md +0 -29
- package/docs/standards/pace-core-compliance.md +0 -432
- package/scripts/audit/core/checks/accessibility.cjs +0 -197
- package/scripts/audit/core/checks/api-usage.cjs +0 -191
- package/scripts/audit/core/checks/bundle.cjs +0 -142
- package/scripts/audit/core/checks/compliance.cjs +0 -2706
- package/scripts/audit/core/checks/config.cjs +0 -54
- package/scripts/audit/core/checks/coverage.cjs +0 -84
- package/scripts/audit/core/checks/dependencies.cjs +0 -994
- package/scripts/audit/core/checks/documentation.cjs +0 -268
- package/scripts/audit/core/checks/environment.cjs +0 -116
- package/scripts/audit/core/checks/error-handling.cjs +0 -340
- package/scripts/audit/core/checks/forms.cjs +0 -172
- package/scripts/audit/core/checks/heuristics.cjs +0 -68
- package/scripts/audit/core/checks/hooks.cjs +0 -334
- package/scripts/audit/core/checks/imports.cjs +0 -244
- package/scripts/audit/core/checks/performance.cjs +0 -325
- package/scripts/audit/core/checks/routes.cjs +0 -117
- package/scripts/audit/core/checks/state.cjs +0 -130
- package/scripts/audit/core/checks/structure.cjs +0 -65
- package/scripts/audit/core/checks/style.cjs +0 -584
- package/scripts/audit/core/checks/testing.cjs +0 -122
- package/scripts/audit/core/checks/typescript.cjs +0 -61
- package/scripts/audit/core/scanner.cjs +0 -199
- package/scripts/audit/core/utils.cjs +0 -137
- package/scripts/audit/index.cjs +0 -223
- package/scripts/audit/reporters/console.cjs +0 -151
- package/scripts/audit/reporters/json.cjs +0 -54
- package/scripts/audit/reporters/markdown.cjs +0 -124
- package/scripts/audit-consuming-app.cjs +0 -86
- package/src/components/DataTable/components/DataTableBody.tsx +0 -454
- package/src/components/DataTable/components/DraggableColumnHeader.tsx +0 -156
- package/src/components/DataTable/components/ExpandButton.tsx +0 -113
- package/src/components/DataTable/components/GroupHeader.tsx +0 -54
- package/src/components/DataTable/components/ViewRowModal.tsx +0 -68
- package/src/components/DataTable/components/VirtualizedDataTable.tsx +0 -525
- package/src/components/DataTable/components/__tests__/ExpandButton.test.tsx +0 -462
- package/src/components/DataTable/components/__tests__/GroupHeader.test.tsx +0 -393
- package/src/components/DataTable/components/__tests__/ViewRowModal.test.tsx +0 -476
- package/src/components/DataTable/components/__tests__/VirtualizedDataTable.test.tsx +0 -128
- package/src/components/DataTable/core/DataTableContext.tsx +0 -216
- package/src/components/DataTable/core/__tests__/DataTableContext.test.tsx +0 -136
- package/src/components/DataTable/hooks/__tests__/useColumnReordering.test.ts +0 -570
- package/src/components/DataTable/hooks/useColumnReordering.ts +0 -123
- package/src/components/DataTable/utils/debugTools.ts +0 -514
- package/src/eslint-rules/pace-core-compliance.js +0 -638
- package/src/rbac/components/EnhancedNavigationMenu.test.tsx +0 -555
- package/src/rbac/components/EnhancedNavigationMenu.tsx +0 -293
- package/src/rbac/components/NavigationProvider.test.tsx +0 -481
- package/src/rbac/components/NavigationProvider.tsx +0 -345
- package/src/rbac/components/PagePermissionProvider.test.tsx +0 -476
- package/src/rbac/components/PagePermissionProvider.tsx +0 -279
- package/src/rbac/components/PermissionEnforcer.tsx +0 -312
- package/src/rbac/components/RoleBasedRouter.tsx +0 -440
- package/src/rbac/components/SecureDataProvider.test.tsx +0 -543
- package/src/rbac/components/SecureDataProvider.tsx +0 -339
- package/src/rbac/components/__tests__/EnhancedNavigationMenu.test.tsx +0 -620
- package/src/rbac/components/__tests__/NavigationProvider.test.tsx +0 -726
- package/src/rbac/components/__tests__/PagePermissionProvider.test.tsx +0 -661
- package/src/rbac/components/__tests__/PermissionEnforcer.test.tsx +0 -881
- package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +0 -783
- package/src/rbac/components/__tests__/SecureDataProvider.fixed.test.tsx +0 -645
- package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +0 -659
- package/src/rbac/hooks/permissions/useCachedPermissions.ts +0 -79
- package/src/rbac/hooks/permissions/useHasAllPermissions.ts +0 -90
- 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 };
|