@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,806 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Security & RBAC Rules (Standard 6)
|
|
3
|
+
* @package @jmruthers/pace-core
|
|
4
|
+
* @module ESLintRules/rules/security-rbac
|
|
5
|
+
*
|
|
6
|
+
* Enforces security and RBAC patterns from Standard 6:
|
|
7
|
+
* - Use secure Supabase client (useSecureSupabase)
|
|
8
|
+
* - Use RESOURCE_NAMES constants
|
|
9
|
+
* - No wrapper components/functions around RBAC hooks
|
|
10
|
+
* - Proper permission loading state handling
|
|
11
|
+
*
|
|
12
|
+
* Reference: packages/core/docs/standards/6-security-rbac-standards.md
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const { isPaceCoreSourceFile } = require('../utils/helpers.cjs');
|
|
16
|
+
|
|
17
|
+
module.exports = {
|
|
18
|
+
rules: {
|
|
19
|
+
/**
|
|
20
|
+
* Disallow direct Supabase client creation - must use useSecureSupabase
|
|
21
|
+
*/
|
|
22
|
+
'no-direct-supabase-client': {
|
|
23
|
+
meta: {
|
|
24
|
+
type: 'problem',
|
|
25
|
+
docs: {
|
|
26
|
+
description: 'Disallow direct createClient calls from @supabase/supabase-js. Use useSecureSupabase() from pace-core instead to ensure organisation context and RLS policies are enforced.',
|
|
27
|
+
category: 'Security',
|
|
28
|
+
recommended: true,
|
|
29
|
+
url: 'https://github.com/jmruthers/pace-core/blob/main/packages/core/docs/standards/6-security-rbac-standards.md'
|
|
30
|
+
},
|
|
31
|
+
messages: {
|
|
32
|
+
directClientCreation: "Direct Supabase client creation detected. You MUST use useSecureSupabase() from '@jmruthers/pace-core/rbac' instead to ensure organisation context and RLS policies are enforced. This prevents cross-organisation data access.",
|
|
33
|
+
directClientImport: "Direct import of createClient from @supabase/supabase-js is not allowed. Use useSecureSupabase() from '@jmruthers/pace-core/rbac' instead."
|
|
34
|
+
},
|
|
35
|
+
hasSuggestions: true
|
|
36
|
+
},
|
|
37
|
+
create(context) {
|
|
38
|
+
const filename = context.getFilename();
|
|
39
|
+
|
|
40
|
+
// Exclude pace-core source files - these rules are for consuming apps
|
|
41
|
+
if (isPaceCoreSourceFile(filename)) {
|
|
42
|
+
return {};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Allow createClient in specific config files (supabaseClient.ts/js, etc.)
|
|
46
|
+
const isConfigFile = /(supabase|client)\.(ts|js|tsx|jsx)$/i.test(filename) &&
|
|
47
|
+
(filename.includes('supabase') || filename.includes('client'));
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
ImportDeclaration(node) {
|
|
51
|
+
const importSource = node.source.value;
|
|
52
|
+
|
|
53
|
+
// Check for @supabase/supabase-js import
|
|
54
|
+
if (importSource === '@supabase/supabase-js') {
|
|
55
|
+
// Check if createClient is imported
|
|
56
|
+
const hasCreateClient = node.specifiers.some(spec => {
|
|
57
|
+
if (spec.type === 'ImportSpecifier') {
|
|
58
|
+
return spec.imported.name === 'createClient';
|
|
59
|
+
}
|
|
60
|
+
if (spec.type === 'ImportNamespaceSpecifier') {
|
|
61
|
+
return true; // import * as supabase
|
|
62
|
+
}
|
|
63
|
+
return false;
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
if (hasCreateClient && !isConfigFile) {
|
|
67
|
+
context.report({
|
|
68
|
+
node: node.source,
|
|
69
|
+
messageId: 'directClientImport',
|
|
70
|
+
suggest: [{
|
|
71
|
+
desc: 'Replace with useSecureSupabase hook',
|
|
72
|
+
fix(fixer) {
|
|
73
|
+
return fixer.remove(node);
|
|
74
|
+
}
|
|
75
|
+
}]
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
CallExpression(node) {
|
|
82
|
+
// Check for createClient() calls
|
|
83
|
+
if (node.callee.type === 'Identifier' && node.callee.name === 'createClient') {
|
|
84
|
+
if (!isConfigFile) {
|
|
85
|
+
context.report({
|
|
86
|
+
node,
|
|
87
|
+
messageId: 'directClientCreation',
|
|
88
|
+
suggest: [{
|
|
89
|
+
desc: 'Use useSecureSupabase() hook instead',
|
|
90
|
+
fix(fixer) {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
}]
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Check for supabase.createClient() or similar patterns
|
|
99
|
+
if (node.callee.type === 'MemberExpression' &&
|
|
100
|
+
node.callee.property?.name === 'createClient') {
|
|
101
|
+
if (!isConfigFile) {
|
|
102
|
+
context.report({
|
|
103
|
+
node,
|
|
104
|
+
messageId: 'directClientCreation',
|
|
105
|
+
suggest: [{
|
|
106
|
+
desc: 'Use useSecureSupabase() hook instead',
|
|
107
|
+
fix(fixer) {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
}]
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Check RBAC permission loading state usage
|
|
121
|
+
*/
|
|
122
|
+
'rbac-permission-loading': {
|
|
123
|
+
meta: {
|
|
124
|
+
type: 'problem',
|
|
125
|
+
docs: {
|
|
126
|
+
description: 'Require isLoading extraction from useResourcePermissions and check it before permission calls in mutations.',
|
|
127
|
+
category: 'Security',
|
|
128
|
+
recommended: true,
|
|
129
|
+
url: 'https://github.com/jmruthers/pace-core/blob/main/packages/core/docs/standards/6-security-rbac-standards.md'
|
|
130
|
+
},
|
|
131
|
+
messages: {
|
|
132
|
+
missingIsLoading: "useResourcePermissions is used but 'isLoading' is not extracted. Permission checks may fail if scope resolution is still in progress.",
|
|
133
|
+
missingLoadingCheck: "Permission check '{{permission}}()' is called without checking isLoading first. This can cause false negatives when scope resolution is still in progress."
|
|
134
|
+
},
|
|
135
|
+
hasSuggestions: true
|
|
136
|
+
},
|
|
137
|
+
create(context) {
|
|
138
|
+
let useResourcePermissionsFound = false;
|
|
139
|
+
let hasIsLoadingExtraction = false;
|
|
140
|
+
let isLoadingVarName = null;
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
CallExpression(node) {
|
|
144
|
+
// Find useResourcePermissions calls
|
|
145
|
+
if (node.callee.type === 'Identifier' &&
|
|
146
|
+
node.callee.name === 'useResourcePermissions') {
|
|
147
|
+
useResourcePermissionsFound = true;
|
|
148
|
+
|
|
149
|
+
// Check parent to see if isLoading is destructured
|
|
150
|
+
const parent = node.parent;
|
|
151
|
+
if (parent && parent.type === 'VariableDeclarator' &&
|
|
152
|
+
parent.id && parent.id.type === 'ObjectPattern') {
|
|
153
|
+
const properties = parent.id.properties;
|
|
154
|
+
const isLoadingProp = properties.find(prop => {
|
|
155
|
+
if (prop.type === 'Property') {
|
|
156
|
+
const key = prop.key;
|
|
157
|
+
if (key.type === 'Identifier' && key.name === 'isLoading') {
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
// Also check for renamed: isLoading: permissionsLoading
|
|
161
|
+
if (key.type === 'Identifier' && key.name === 'isLoading') {
|
|
162
|
+
if (prop.value && prop.value.type === 'Identifier') {
|
|
163
|
+
isLoadingVarName = prop.value.name;
|
|
164
|
+
}
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return false;
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
if (isLoadingProp) {
|
|
172
|
+
hasIsLoadingExtraction = true;
|
|
173
|
+
if (!isLoadingVarName) {
|
|
174
|
+
isLoadingVarName = 'isLoading';
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Check for permission function calls in mutations
|
|
181
|
+
if (useResourcePermissionsFound &&
|
|
182
|
+
node.callee.type === 'Identifier' &&
|
|
183
|
+
['canCreate', 'canUpdate', 'canDelete', 'canRead'].includes(node.callee.name)) {
|
|
184
|
+
|
|
185
|
+
// Check if we're in a mutation context
|
|
186
|
+
const sourceCode = context.sourceCode || context.getSourceCode();
|
|
187
|
+
// ESLint 9: use sourceCode.getAncestors(node), ESLint 8: use context.getAncestors()
|
|
188
|
+
const ancestors = sourceCode.getAncestors ? sourceCode.getAncestors(node) : (context.getAncestors ? context.getAncestors() : []);
|
|
189
|
+
const isInMutation = ancestors.some(ancestor => {
|
|
190
|
+
if (ancestor.type === 'Property' && ancestor.key) {
|
|
191
|
+
const keyName = ancestor.key.name || (ancestor.key.type === 'Identifier' && ancestor.key.name);
|
|
192
|
+
return keyName === 'mutationFn' || keyName === 'mutateAsync';
|
|
193
|
+
}
|
|
194
|
+
return false;
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
if (isInMutation) {
|
|
198
|
+
// Check if isLoading is checked before this call
|
|
199
|
+
const nodeText = sourceCode.getText(node);
|
|
200
|
+
const beforeNode = sourceCode.getText().substring(0, sourceCode.getIndexFromLoc(node.loc.start));
|
|
201
|
+
|
|
202
|
+
// Look for isLoading check before this call
|
|
203
|
+
const hasLoadingCheck = new RegExp(
|
|
204
|
+
`(if\\s*\\(\\s*!?\\s*(${isLoadingVarName || 'isLoading'})|if\\s*\\(\\s*(${isLoadingVarName || 'isLoading'})\\s*===\\s*false|if\\s*\\(\\s*(${isLoadingVarName || 'isLoading'})\\s*!==\\s*true|await)`,
|
|
205
|
+
'i'
|
|
206
|
+
).test(beforeNode);
|
|
207
|
+
|
|
208
|
+
if (!hasIsLoadingExtraction) {
|
|
209
|
+
context.report({
|
|
210
|
+
node,
|
|
211
|
+
messageId: 'missingIsLoading',
|
|
212
|
+
suggest: [{
|
|
213
|
+
desc: `Extract 'isLoading' from useResourcePermissions`,
|
|
214
|
+
fix(fixer) {
|
|
215
|
+
return null; // Complex fix
|
|
216
|
+
}
|
|
217
|
+
}]
|
|
218
|
+
});
|
|
219
|
+
} else if (!hasLoadingCheck) {
|
|
220
|
+
context.report({
|
|
221
|
+
node,
|
|
222
|
+
messageId: 'missingLoadingCheck',
|
|
223
|
+
data: {
|
|
224
|
+
permission: node.callee.name
|
|
225
|
+
},
|
|
226
|
+
suggest: [{
|
|
227
|
+
desc: `Check ${isLoadingVarName || 'isLoading'} before calling permission function`,
|
|
228
|
+
fix(fixer) {
|
|
229
|
+
return null; // Complex fix
|
|
230
|
+
}
|
|
231
|
+
}]
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Disallow direct RPC calls to RBAC functions
|
|
243
|
+
*/
|
|
244
|
+
'no-direct-rbac-rpc': {
|
|
245
|
+
meta: {
|
|
246
|
+
type: 'problem',
|
|
247
|
+
docs: {
|
|
248
|
+
description: 'Disallow direct calls to RBAC RPC functions. Use pace-core RBAC hooks instead.',
|
|
249
|
+
category: 'Security',
|
|
250
|
+
recommended: true,
|
|
251
|
+
url: 'https://github.com/jmruthers/pace-core/blob/main/packages/core/docs/standards/6-security-rbac-standards.md'
|
|
252
|
+
},
|
|
253
|
+
messages: {
|
|
254
|
+
directRbacRpc: "Direct RPC call to '{{rpcName}}' detected. Use pace-core RBAC hooks from '@jmruthers/pace-core/rbac' instead."
|
|
255
|
+
},
|
|
256
|
+
hasSuggestions: true
|
|
257
|
+
},
|
|
258
|
+
create(context) {
|
|
259
|
+
const filename = context.getFilename();
|
|
260
|
+
|
|
261
|
+
// Exclude pace-core source files - these rules are for consuming apps
|
|
262
|
+
if (isPaceCoreSourceFile(filename)) {
|
|
263
|
+
return {};
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const rbacRpcPatterns = ['rbac_check_permission_simplified', 'rbac_'];
|
|
267
|
+
|
|
268
|
+
return {
|
|
269
|
+
CallExpression(node) {
|
|
270
|
+
// Check for supabase.rpc('rbac_*', ...)
|
|
271
|
+
if (node.callee.type === 'MemberExpression' &&
|
|
272
|
+
node.callee.property &&
|
|
273
|
+
node.callee.property.name === 'rpc' &&
|
|
274
|
+
node.arguments.length > 0) {
|
|
275
|
+
|
|
276
|
+
const firstArg = node.arguments[0];
|
|
277
|
+
if (firstArg.type === 'Literal' && typeof firstArg.value === 'string') {
|
|
278
|
+
const rpcName = firstArg.value;
|
|
279
|
+
if (rpcName.startsWith('rbac_')) {
|
|
280
|
+
context.report({
|
|
281
|
+
node: firstArg,
|
|
282
|
+
messageId: 'directRbacRpc',
|
|
283
|
+
data: {
|
|
284
|
+
rpcName
|
|
285
|
+
},
|
|
286
|
+
suggest: [{
|
|
287
|
+
desc: 'Use pace-core RBAC hooks instead',
|
|
288
|
+
fix(fixer) {
|
|
289
|
+
return null;
|
|
290
|
+
}
|
|
291
|
+
}]
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
},
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Disallow direct queries to RBAC tables
|
|
303
|
+
*/
|
|
304
|
+
'no-direct-rbac-table': {
|
|
305
|
+
meta: {
|
|
306
|
+
type: 'problem',
|
|
307
|
+
docs: {
|
|
308
|
+
description: 'Disallow direct queries to RBAC tables. Use pace-core RBAC API functions or useSecureSupabase instead.',
|
|
309
|
+
category: 'Security',
|
|
310
|
+
recommended: true,
|
|
311
|
+
url: 'https://github.com/jmruthers/pace-core/blob/main/packages/core/docs/standards/6-security-rbac-standards.md'
|
|
312
|
+
},
|
|
313
|
+
messages: {
|
|
314
|
+
directRbacTable: "Direct query to RBAC table '{{tableName}}' detected. Use pace-core RBAC API functions from '@jmruthers/pace-core/rbac' or useSecureSupabase hook instead."
|
|
315
|
+
},
|
|
316
|
+
hasSuggestions: true
|
|
317
|
+
},
|
|
318
|
+
create(context) {
|
|
319
|
+
const filename = context.getFilename();
|
|
320
|
+
|
|
321
|
+
// Exclude pace-core source files - these rules are for consuming apps
|
|
322
|
+
if (isPaceCoreSourceFile(filename)) {
|
|
323
|
+
return {};
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const rbacTables = [
|
|
327
|
+
'rbac_organisation_roles',
|
|
328
|
+
'rbac_event_app_roles',
|
|
329
|
+
'rbac_global_roles',
|
|
330
|
+
'rbac_apps',
|
|
331
|
+
'rbac_app_pages',
|
|
332
|
+
'rbac_page_permissions',
|
|
333
|
+
'rbac_user_profiles'
|
|
334
|
+
];
|
|
335
|
+
|
|
336
|
+
let hasSecureSupabase = false;
|
|
337
|
+
let secureSupabaseVarName = null;
|
|
338
|
+
|
|
339
|
+
return {
|
|
340
|
+
ImportDeclaration(node) {
|
|
341
|
+
const importSource = node.source.value;
|
|
342
|
+
if (importSource === '@jmruthers/pace-core/rbac' ||
|
|
343
|
+
importSource.startsWith('@jmruthers/pace-core/rbac/')) {
|
|
344
|
+
if (node.specifiers.some(spec =>
|
|
345
|
+
spec.type === 'ImportSpecifier' && spec.imported.name === 'useSecureSupabase'
|
|
346
|
+
)) {
|
|
347
|
+
hasSecureSupabase = true;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
},
|
|
351
|
+
VariableDeclarator(node) {
|
|
352
|
+
if (node.init &&
|
|
353
|
+
node.init.type === 'CallExpression' &&
|
|
354
|
+
node.init.callee &&
|
|
355
|
+
node.init.callee.name === 'useSecureSupabase') {
|
|
356
|
+
if (node.id.type === 'Identifier') {
|
|
357
|
+
secureSupabaseVarName = node.id.name;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
},
|
|
361
|
+
CallExpression(node) {
|
|
362
|
+
if (node.callee.type === 'MemberExpression' &&
|
|
363
|
+
node.callee.property &&
|
|
364
|
+
node.callee.property.name === 'from' &&
|
|
365
|
+
node.arguments.length > 0) {
|
|
366
|
+
|
|
367
|
+
// Check if this is called on secureSupabase
|
|
368
|
+
let isSecureClient = false;
|
|
369
|
+
if (node.callee.object.type === 'Identifier') {
|
|
370
|
+
const objName = node.callee.object.name;
|
|
371
|
+
if (objName === 'secureSupabase' ||
|
|
372
|
+
objName === secureSupabaseVarName ||
|
|
373
|
+
(hasSecureSupabase && objName.includes('secure'))) {
|
|
374
|
+
isSecureClient = true;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Allow queries through secureSupabase
|
|
379
|
+
if (isSecureClient) {
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const firstArg = node.arguments[0];
|
|
384
|
+
if (firstArg.type === 'Literal' && typeof firstArg.value === 'string') {
|
|
385
|
+
const tableName = firstArg.value;
|
|
386
|
+
if (rbacTables.includes(tableName) || tableName.startsWith('rbac_')) {
|
|
387
|
+
context.report({
|
|
388
|
+
node: firstArg,
|
|
389
|
+
messageId: 'directRbacTable',
|
|
390
|
+
data: {
|
|
391
|
+
tableName
|
|
392
|
+
},
|
|
393
|
+
suggest: [{
|
|
394
|
+
desc: 'Use useSecureSupabase hook or pace-core RBAC API functions',
|
|
395
|
+
fix(fixer) {
|
|
396
|
+
return null;
|
|
397
|
+
}
|
|
398
|
+
}]
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
},
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Disallow hardcoded role checks
|
|
410
|
+
*/
|
|
411
|
+
'no-hardcoded-role-checks': {
|
|
412
|
+
meta: {
|
|
413
|
+
type: 'problem',
|
|
414
|
+
docs: {
|
|
415
|
+
description: 'Disallow hardcoded role checks. Use useAccessLevel hook or getRoleContext API from pace-core instead.',
|
|
416
|
+
category: 'Security',
|
|
417
|
+
recommended: true,
|
|
418
|
+
url: 'https://github.com/jmruthers/pace-core/blob/main/packages/core/docs/standards/6-security-rbac-standards.md'
|
|
419
|
+
},
|
|
420
|
+
messages: {
|
|
421
|
+
hardcodedRoleCheck: "Hardcoded role check detected. Use useAccessLevel hook or getRoleContext API from '@jmruthers/pace-core/rbac' instead."
|
|
422
|
+
},
|
|
423
|
+
hasSuggestions: true
|
|
424
|
+
},
|
|
425
|
+
create(context) {
|
|
426
|
+
const filename = context.getFilename();
|
|
427
|
+
|
|
428
|
+
// Exclude pace-core source files - these rules are for consuming apps
|
|
429
|
+
if (isPaceCoreSourceFile(filename)) {
|
|
430
|
+
return {};
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
const roleNames = ['admin', 'org_admin', 'event_admin', 'user', 'member', 'viewer', 'editor'];
|
|
434
|
+
const roleCheckPattern = new RegExp(
|
|
435
|
+
`(?:role|user\\.role|userRole|currentRole|userRoleName)\\s*(?:===|!==|==|!=)\\s*['"](${roleNames.join('|')})['"]`,
|
|
436
|
+
'i'
|
|
437
|
+
);
|
|
438
|
+
|
|
439
|
+
let hasPaceCoreImport = false;
|
|
440
|
+
|
|
441
|
+
return {
|
|
442
|
+
ImportDeclaration(node) {
|
|
443
|
+
const importSource = node.source.value;
|
|
444
|
+
if (importSource === '@jmruthers/pace-core/rbac' ||
|
|
445
|
+
importSource.startsWith('@jmruthers/pace-core/rbac/')) {
|
|
446
|
+
hasPaceCoreImport = true;
|
|
447
|
+
}
|
|
448
|
+
},
|
|
449
|
+
BinaryExpression(node) {
|
|
450
|
+
if (node.operator === '===' || node.operator === '!==' || node.operator === '==' || node.operator === '!=') {
|
|
451
|
+
const sourceCode = context.getSourceCode();
|
|
452
|
+
const leftText = sourceCode.getText(node.left);
|
|
453
|
+
const rightText = sourceCode.getText(node.right);
|
|
454
|
+
|
|
455
|
+
// Check if it's a role comparison
|
|
456
|
+
if (roleCheckPattern.test(leftText + ' ' + node.operator + ' ' + rightText)) {
|
|
457
|
+
// Check if using pace-core APIs
|
|
458
|
+
if (!hasPaceCoreImport ||
|
|
459
|
+
(!leftText.includes('useAccessLevel') && !leftText.includes('getRoleContext'))) {
|
|
460
|
+
context.report({
|
|
461
|
+
node,
|
|
462
|
+
messageId: 'hardcodedRoleCheck',
|
|
463
|
+
suggest: [{
|
|
464
|
+
desc: 'Use useAccessLevel hook or getRoleContext API',
|
|
465
|
+
fix(fixer) {
|
|
466
|
+
return null;
|
|
467
|
+
}
|
|
468
|
+
}]
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
},
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Require RESOURCE_NAMES constants in useResourcePermissions
|
|
480
|
+
*/
|
|
481
|
+
'rbac-use-resource-names-constants': {
|
|
482
|
+
meta: {
|
|
483
|
+
type: 'problem',
|
|
484
|
+
docs: {
|
|
485
|
+
description: 'Require RESOURCE_NAMES constants instead of string literals in useResourcePermissions calls.',
|
|
486
|
+
category: 'Best Practices',
|
|
487
|
+
recommended: true,
|
|
488
|
+
url: 'https://github.com/jmruthers/pace-core/blob/main/packages/core/docs/standards/6-security-rbac-standards.md'
|
|
489
|
+
},
|
|
490
|
+
messages: {
|
|
491
|
+
resourcePermissionStringLiteral: "Resource permission string literal detected. Use RESOURCE_NAMES constant object instead (e.g., RESOURCE_NAMES.ORGANISATIONS, RESOURCE_NAMES.EVENTS)."
|
|
492
|
+
},
|
|
493
|
+
hasSuggestions: true
|
|
494
|
+
},
|
|
495
|
+
create(context) {
|
|
496
|
+
const filename = context.getFilename();
|
|
497
|
+
|
|
498
|
+
// Exclude pace-core source files - these rules are for consuming apps
|
|
499
|
+
if (isPaceCoreSourceFile(filename)) {
|
|
500
|
+
return {};
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
let hasResourceNamesImport = false;
|
|
504
|
+
|
|
505
|
+
return {
|
|
506
|
+
ImportDeclaration(node) {
|
|
507
|
+
const importSource = node.source.value;
|
|
508
|
+
if (node.specifiers.some(spec =>
|
|
509
|
+
spec.type === 'ImportSpecifier' && spec.imported.name === 'RESOURCE_NAMES'
|
|
510
|
+
)) {
|
|
511
|
+
hasResourceNamesImport = true;
|
|
512
|
+
}
|
|
513
|
+
},
|
|
514
|
+
CallExpression(node) {
|
|
515
|
+
if (node.callee.type === 'Identifier' &&
|
|
516
|
+
node.callee.name === 'useResourcePermissions') {
|
|
517
|
+
// Check if argument is a string literal
|
|
518
|
+
if (node.arguments.length > 0 &&
|
|
519
|
+
node.arguments[0].type === 'Literal' &&
|
|
520
|
+
typeof node.arguments[0].value === 'string') {
|
|
521
|
+
// Check if RESOURCE_NAMES is used in the file
|
|
522
|
+
const sourceCode = context.getSourceCode();
|
|
523
|
+
const fileText = sourceCode.getText();
|
|
524
|
+
|
|
525
|
+
if (!hasResourceNamesImport || !fileText.includes('RESOURCE_NAMES.')) {
|
|
526
|
+
context.report({
|
|
527
|
+
node: node.arguments[0],
|
|
528
|
+
messageId: 'resourcePermissionStringLiteral',
|
|
529
|
+
suggest: [{
|
|
530
|
+
desc: 'Use RESOURCE_NAMES constant instead',
|
|
531
|
+
fix(fixer) {
|
|
532
|
+
return null; // Complex fix
|
|
533
|
+
}
|
|
534
|
+
}]
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
},
|
|
543
|
+
|
|
544
|
+
/**
|
|
545
|
+
* Disallow wrapper components around PagePermissionGuard
|
|
546
|
+
*/
|
|
547
|
+
'no-rbac-wrapper-components': {
|
|
548
|
+
meta: {
|
|
549
|
+
type: 'problem',
|
|
550
|
+
docs: {
|
|
551
|
+
description: 'Disallow wrapper components around PagePermissionGuard. Use PagePermissionGuard directly.',
|
|
552
|
+
category: 'Security',
|
|
553
|
+
recommended: true,
|
|
554
|
+
url: 'https://github.com/jmruthers/pace-core/blob/main/packages/core/docs/standards/6-security-rbac-standards.md'
|
|
555
|
+
},
|
|
556
|
+
messages: {
|
|
557
|
+
wrapperComponent: "Wrapper component '{{componentName}}' detected around PagePermissionGuard. Must use PagePermissionGuard directly, not through wrappers."
|
|
558
|
+
},
|
|
559
|
+
hasSuggestions: true
|
|
560
|
+
},
|
|
561
|
+
create(context) {
|
|
562
|
+
const filename = context.getFilename();
|
|
563
|
+
|
|
564
|
+
// Allow in pace-core package itself
|
|
565
|
+
if (filename.includes('packages/core/src/rbac') || filename.includes('packages\\core\\src\\rbac')) {
|
|
566
|
+
return {};
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
let hasPagePermissionGuard = false;
|
|
570
|
+
let wrapperComponentName = null;
|
|
571
|
+
|
|
572
|
+
return {
|
|
573
|
+
JSXOpeningElement(node) {
|
|
574
|
+
if (node.name.type === 'JSXIdentifier' && node.name.name === 'PagePermissionGuard') {
|
|
575
|
+
hasPagePermissionGuard = true;
|
|
576
|
+
|
|
577
|
+
// Check if this is inside a wrapper component
|
|
578
|
+
const sourceCode = context.sourceCode || context.getSourceCode();
|
|
579
|
+
// ESLint 9: use sourceCode.getAncestors(node), ESLint 8: use context.getAncestors()
|
|
580
|
+
const ancestors = sourceCode.getAncestors ? sourceCode.getAncestors(node) : (context.getAncestors ? context.getAncestors() : []);
|
|
581
|
+
const componentAncestor = ancestors.find(ancestor =>
|
|
582
|
+
ancestor.type === 'FunctionDeclaration' ||
|
|
583
|
+
(ancestor.type === 'VariableDeclarator' && ancestor.init &&
|
|
584
|
+
(ancestor.init.type === 'ArrowFunctionExpression' || ancestor.init.type === 'FunctionExpression'))
|
|
585
|
+
);
|
|
586
|
+
|
|
587
|
+
if (componentAncestor) {
|
|
588
|
+
let componentName = null;
|
|
589
|
+
let componentParams = null;
|
|
590
|
+
|
|
591
|
+
if (componentAncestor.type === 'FunctionDeclaration' && componentAncestor.id) {
|
|
592
|
+
componentName = componentAncestor.id.name;
|
|
593
|
+
componentParams = componentAncestor.params;
|
|
594
|
+
} else if (componentAncestor.type === 'VariableDeclarator' && componentAncestor.id.type === 'Identifier') {
|
|
595
|
+
componentName = componentAncestor.id.name;
|
|
596
|
+
// For arrow functions and function expressions, get params from init
|
|
597
|
+
if (componentAncestor.init) {
|
|
598
|
+
if (componentAncestor.init.type === 'ArrowFunctionExpression' ||
|
|
599
|
+
componentAncestor.init.type === 'FunctionExpression') {
|
|
600
|
+
componentParams = componentAncestor.init.params;
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
// Check if component accepts pageName as a FUNCTION PARAMETER (not just uses it in JSX)
|
|
606
|
+
// This distinguishes wrapper components from legitimate page components
|
|
607
|
+
// Wrapper pattern: function Wrapper({ pageName }) { return <PagePermissionGuard pageName={pageName}> }
|
|
608
|
+
// Legitimate pattern: function Page() { return <PagePermissionGuard pageName={PAGE_NAMES.PAGE}> }
|
|
609
|
+
const acceptsPageNameAsParam = componentParams && componentParams.some(param => {
|
|
610
|
+
if (param.type === 'Identifier') {
|
|
611
|
+
return param.name === 'pageName';
|
|
612
|
+
}
|
|
613
|
+
// Handle destructured params: { pageName } or { pageName: pn }
|
|
614
|
+
if (param.type === 'ObjectPattern') {
|
|
615
|
+
return param.properties.some(prop => {
|
|
616
|
+
if (prop.type === 'Property') {
|
|
617
|
+
const key = prop.key;
|
|
618
|
+
if (key.type === 'Identifier') {
|
|
619
|
+
return key.name === 'pageName';
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
return false;
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
return false;
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
// Only flag if component accepts pageName as a parameter (wrapper pattern)
|
|
629
|
+
// Legitimate page components use constants like PAGE_NAMES.CONTACTS, not accept it as prop
|
|
630
|
+
if (acceptsPageNameAsParam) {
|
|
631
|
+
context.report({
|
|
632
|
+
node,
|
|
633
|
+
messageId: 'wrapperComponent',
|
|
634
|
+
data: {
|
|
635
|
+
componentName: componentName || 'Unknown'
|
|
636
|
+
},
|
|
637
|
+
suggest: [{
|
|
638
|
+
desc: 'Remove wrapper component and use PagePermissionGuard directly in pages',
|
|
639
|
+
fix(fixer) {
|
|
640
|
+
return null;
|
|
641
|
+
}
|
|
642
|
+
}]
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
},
|
|
651
|
+
|
|
652
|
+
/**
|
|
653
|
+
* Disallow wrapper functions around permission hooks
|
|
654
|
+
*/
|
|
655
|
+
'no-rbac-wrapper-functions': {
|
|
656
|
+
meta: {
|
|
657
|
+
type: 'problem',
|
|
658
|
+
docs: {
|
|
659
|
+
description: 'Disallow wrapper functions around pace-core permission hooks. Use hooks directly in components.',
|
|
660
|
+
category: 'Security',
|
|
661
|
+
recommended: true,
|
|
662
|
+
url: 'https://github.com/jmruthers/pace-core/blob/main/packages/core/docs/standards/6-security-rbac-standards.md'
|
|
663
|
+
},
|
|
664
|
+
messages: {
|
|
665
|
+
wrapperFunction: "Permission wrapper function '{{functionName}}' detected. Use pace-core hooks (useCan, useResourcePermissions) directly in components instead of wrapping them."
|
|
666
|
+
},
|
|
667
|
+
hasSuggestions: true
|
|
668
|
+
},
|
|
669
|
+
create(context) {
|
|
670
|
+
const filename = context.getFilename();
|
|
671
|
+
|
|
672
|
+
// Exclude pace-core source files - these rules are for consuming apps
|
|
673
|
+
if (isPaceCoreSourceFile(filename)) {
|
|
674
|
+
return {};
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
const permissionHookNames = ['useCan', 'useResourcePermissions', 'usePermissions', 'useMultiplePermissions'];
|
|
678
|
+
const permissionFunctionNames = ['canCreate', 'canUpdate', 'canDelete', 'canRead', 'can'];
|
|
679
|
+
|
|
680
|
+
let hasPaceCoreRBACImport = false;
|
|
681
|
+
|
|
682
|
+
return {
|
|
683
|
+
ImportDeclaration(node) {
|
|
684
|
+
const importSource = node.source.value;
|
|
685
|
+
if (importSource === '@jmruthers/pace-core/rbac' ||
|
|
686
|
+
importSource.startsWith('@jmruthers/pace-core/rbac/')) {
|
|
687
|
+
hasPaceCoreRBACImport = true;
|
|
688
|
+
}
|
|
689
|
+
},
|
|
690
|
+
FunctionDeclaration(node) {
|
|
691
|
+
if (!node.id || !hasPaceCoreRBACImport) return;
|
|
692
|
+
|
|
693
|
+
const functionName = node.id.name;
|
|
694
|
+
if (!functionName) return;
|
|
695
|
+
|
|
696
|
+
// Check if function body uses permission hooks/functions
|
|
697
|
+
const sourceCode = context.getSourceCode();
|
|
698
|
+
const functionText = sourceCode.getText(node.body);
|
|
699
|
+
|
|
700
|
+
// Check if function uses permission hooks or functions
|
|
701
|
+
const usesPermissionHooks = permissionHookNames.some(hook => functionText.includes(hook));
|
|
702
|
+
const usesPermissionFunctions = permissionFunctionNames.some(fn => functionText.includes(fn));
|
|
703
|
+
|
|
704
|
+
// Check if function has additional logic beyond permission checking
|
|
705
|
+
const hasAdditionalLogic = (
|
|
706
|
+
functionText.includes('&&') ||
|
|
707
|
+
functionText.includes('||') ||
|
|
708
|
+
functionText.includes('if') ||
|
|
709
|
+
(functionText.match(/return/g) || []).length > 1 ||
|
|
710
|
+
functionText.includes('.find') ||
|
|
711
|
+
functionText.includes('.filter') ||
|
|
712
|
+
functionText.includes('.map')
|
|
713
|
+
);
|
|
714
|
+
|
|
715
|
+
// Pattern: Functions that start with 'can' and use permission hooks/functions
|
|
716
|
+
// and have additional logic
|
|
717
|
+
const isPermissionWrapper = (
|
|
718
|
+
(functionName.toLowerCase().startsWith('can') ||
|
|
719
|
+
functionName.toLowerCase().includes('permission') ||
|
|
720
|
+
functionName.toLowerCase().includes('access')) &&
|
|
721
|
+
(usesPermissionHooks || usesPermissionFunctions) &&
|
|
722
|
+
hasAdditionalLogic &&
|
|
723
|
+
node.params.length > 0
|
|
724
|
+
);
|
|
725
|
+
|
|
726
|
+
if (isPermissionWrapper) {
|
|
727
|
+
context.report({
|
|
728
|
+
node: node.id,
|
|
729
|
+
messageId: 'wrapperFunction',
|
|
730
|
+
data: {
|
|
731
|
+
functionName
|
|
732
|
+
},
|
|
733
|
+
suggest: [{
|
|
734
|
+
desc: 'Use permission hooks directly in components',
|
|
735
|
+
fix(fixer) {
|
|
736
|
+
return null;
|
|
737
|
+
}
|
|
738
|
+
}]
|
|
739
|
+
});
|
|
740
|
+
}
|
|
741
|
+
},
|
|
742
|
+
VariableDeclarator(node) {
|
|
743
|
+
if (!hasPaceCoreRBACImport) return;
|
|
744
|
+
if (node.id.type !== 'Identifier') return;
|
|
745
|
+
|
|
746
|
+
const varName = node.id.name;
|
|
747
|
+
if (!varName) return;
|
|
748
|
+
|
|
749
|
+
// Only check arrow functions and function expressions
|
|
750
|
+
if (!node.init ||
|
|
751
|
+
(node.init.type !== 'ArrowFunctionExpression' &&
|
|
752
|
+
node.init.type !== 'FunctionExpression')) {
|
|
753
|
+
return;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
const sourceCode = context.getSourceCode();
|
|
757
|
+
const functionText = sourceCode.getText(node.init);
|
|
758
|
+
|
|
759
|
+
// Check if function uses permission hooks or functions
|
|
760
|
+
const usesPermissionHooks = permissionHookNames.some(hook => functionText.includes(hook));
|
|
761
|
+
const usesPermissionFunctions = permissionFunctionNames.some(fn => functionText.includes(fn));
|
|
762
|
+
|
|
763
|
+
// Check if function has additional logic beyond permission checking
|
|
764
|
+
const hasAdditionalLogic = (
|
|
765
|
+
functionText.includes('&&') ||
|
|
766
|
+
functionText.includes('||') ||
|
|
767
|
+
functionText.includes('if') ||
|
|
768
|
+
(functionText.match(/return/g) || []).length > 1 ||
|
|
769
|
+
functionText.includes('.find') ||
|
|
770
|
+
functionText.includes('.filter') ||
|
|
771
|
+
functionText.includes('.map')
|
|
772
|
+
);
|
|
773
|
+
|
|
774
|
+
// Pattern: Variables that start with 'can' and use permission hooks/functions
|
|
775
|
+
// and have additional logic
|
|
776
|
+
const isPermissionWrapper = (
|
|
777
|
+
(varName.toLowerCase().startsWith('can') ||
|
|
778
|
+
varName.toLowerCase().includes('permission') ||
|
|
779
|
+
varName.toLowerCase().includes('access')) &&
|
|
780
|
+
(usesPermissionHooks || usesPermissionFunctions) &&
|
|
781
|
+
hasAdditionalLogic &&
|
|
782
|
+
node.init.params && node.init.params.length > 0
|
|
783
|
+
);
|
|
784
|
+
|
|
785
|
+
if (isPermissionWrapper) {
|
|
786
|
+
context.report({
|
|
787
|
+
node: node.id,
|
|
788
|
+
messageId: 'wrapperFunction',
|
|
789
|
+
data: {
|
|
790
|
+
functionName: varName
|
|
791
|
+
},
|
|
792
|
+
suggest: [{
|
|
793
|
+
desc: 'Use permission hooks directly in components',
|
|
794
|
+
fix(fixer) {
|
|
795
|
+
return null;
|
|
796
|
+
}
|
|
797
|
+
}]
|
|
798
|
+
});
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
};
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
};
|
|
806
|
+
|