@jmruthers/pace-core 0.6.4 → 0.6.6
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/core-usage-manifest.json +93 -0
- package/cursor-rules/00-pace-core-compliance.mdc +128 -26
- package/cursor-rules/01-standards-compliance.mdc +49 -8
- package/cursor-rules/02-project-structure.mdc +6 -0
- package/cursor-rules/03-solid-principles.mdc +2 -0
- package/cursor-rules/04-testing-standards.mdc +2 -0
- package/cursor-rules/05-bug-reports-and-features.mdc +2 -0
- package/cursor-rules/06-code-quality.mdc +2 -0
- package/cursor-rules/07-tech-stack-compliance.mdc +2 -0
- package/cursor-rules/08-markup-quality.mdc +52 -27
- package/cursor-rules/09-rbac-compliance.mdc +462 -0
- package/cursor-rules/10-error-handling-patterns.mdc +179 -0
- package/cursor-rules/11-performance-optimization.mdc +169 -0
- package/cursor-rules/12-ci-cd-integration.mdc +150 -0
- package/dist/{AuthService-Cb34EQs3.d.ts → AuthService-DmfO5rGS.d.ts} +10 -0
- package/dist/{DataTable-BMRU8a1j.d.ts → DataTable-2N_tqbfq.d.ts} +1 -1
- package/dist/DataTable-LRJL4IRV.js +15 -0
- package/dist/{PublicPageProvider-DEMpysFR.d.ts → PublicPageProvider-BBH6Vqg7.d.ts} +72 -139
- package/dist/UnifiedAuthProvider-ZT6TIGM7.js +7 -0
- package/dist/api-Y4MQWOFW.js +4 -0
- package/dist/audit-MYQXYZFU.js +3 -0
- package/dist/{chunk-J36DSWQK.js → chunk-2HGJFNAH.js} +8 -28
- package/dist/{chunk-OEWDTMG7.js → chunk-3O3WHILE.js} +38 -121
- package/dist/{chunk-M43Y4SSO.js → chunk-3QC3KRHK.js} +1 -14
- package/dist/{chunk-DGUM43GV.js → chunk-3RG5ZIWI.js} +1 -4
- package/dist/{chunk-QXHPKYJV.js → chunk-4SXLQIZO.js} +1 -26
- package/dist/chunk-4T7OBVTU.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-NN6WWZ5U.js → chunk-7TYHROIV.js} +579 -563
- package/dist/{chunk-M7MPQISP.js → chunk-A55DK444.js} +9 -16
- package/dist/{chunk-63FOKYGO.js → chunk-AHU7G2R5.js} +2 -11
- package/dist/{chunk-L4OXEN46.js → chunk-BVP2BCJF.js} +2 -16
- package/dist/chunk-C7NSAPTL.js +1 -0
- package/dist/{chunk-YKRAFF5K.js → chunk-FENMYN2U.js} +73 -149
- package/dist/{chunk-AVMLPIM7.js → chunk-FTCRZOG2.js} +284 -432
- package/dist/{chunk-G37KK66H.js → chunk-FYHN4DD5.js} +60 -19
- package/dist/{chunk-VBXEHIUJ.js → chunk-HF6O3O37.js} +6 -88
- package/dist/{chunk-I6DAQMWX.js → chunk-LAZMKTTF.js} +930 -891
- package/dist/{chunk-5EC5MEWX.js → chunk-MAGBIDNS.js} +77 -222
- package/dist/chunk-MBADTM7L.js +64 -0
- package/dist/chunk-OHIK3MIO.js +994 -0
- package/dist/{chunk-6SOIHG6Z.js → chunk-S7DKJPLT.js} +115 -44
- package/dist/{chunk-FMUCXFII.js → chunk-SD6WQY43.js} +1 -5
- package/dist/{chunk-PWLANIRT.js → chunk-TTRFSOKR.js} +1 -7
- package/dist/{chunk-5DRSZLL2.js → chunk-UH3NTO3F.js} +1 -6
- package/dist/{chunk-FFQEQTNW.js → chunk-UIYSCEV7.js} +134 -45
- package/dist/{chunk-3LPHPB62.js → chunk-ZFYPMX46.js} +271 -87
- package/dist/{chunk-7JPAB3T5.js → chunk-ZS5VO5JB.js} +1989 -1283
- package/dist/components.d.ts +6 -6
- package/dist/components.js +57 -267
- package/dist/{database.generated-CzIvgcPu.d.ts → database.generated-CcnC_DRc.d.ts} +4795 -3691
- package/dist/eslint-rules/index.cjs +22 -0
- package/dist/eslint-rules/rules/compliance.cjs +348 -0
- package/dist/eslint-rules/rules/components.cjs +113 -0
- package/dist/eslint-rules/rules/imports.cjs +102 -0
- package/dist/eslint-rules/rules/rbac.cjs +790 -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 +5 -5
- package/dist/hooks.js +62 -270
- package/dist/icons/index.d.ts +1 -0
- package/dist/icons/index.js +1 -0
- package/dist/index.d.ts +36 -26
- package/dist/index.js +87 -690
- 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 +124 -594
- package/dist/rbac/index.js +14 -207
- package/dist/styles/index.js +2 -12
- 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-B-K_5VnO.d.ts} +4 -0
- package/dist/types-t9H8qKRw.d.ts +55 -0
- package/dist/types.d.ts +1 -1
- package/dist/types.js +7 -94
- package/dist/{usePublicRouteParams-i3qtoBgg.d.ts → usePublicRouteParams-COZ28Mvq.d.ts} +9 -9
- package/dist/utils.d.ts +24 -117
- package/dist/utils.js +54 -392
- package/docs/README.md +16 -6
- package/docs/api/README.md +4 -402
- package/docs/api/modules.md +454 -930
- package/docs/api-reference/components.md +3 -1
- package/docs/api-reference/deprecated.md +31 -6
- package/docs/api-reference/rpc-functions.md +78 -3
- package/docs/best-practices/accessibility.md +6 -3
- package/docs/getting-started/cursor-rules.md +3 -23
- package/docs/getting-started/dependencies.md +650 -0
- package/docs/getting-started/installation-guide.md +20 -7
- package/docs/getting-started/quick-start.md +23 -12
- package/docs/implementation-guides/permission-enforcement.md +4 -0
- package/docs/rbac/MIGRATION_GUIDE.md +819 -0
- package/docs/rbac/RBAC_CONTRACT.md +724 -0
- package/docs/rbac/README.md +12 -3
- package/docs/rbac/edge-functions-guide.md +376 -0
- package/docs/rbac/secure-client-protection.md +0 -34
- package/docs/standards/00-pace-core-compliance.md +967 -0
- package/docs/standards/01-standards-compliance.md +188 -0
- package/docs/standards/02-project-structure.md +985 -0
- package/docs/standards/03-solid-principles.md +39 -0
- package/docs/standards/04-testing-standards.md +36 -0
- package/docs/standards/05-bug-reports-and-features.md +27 -0
- package/docs/standards/{04-code-style-standard.md → 06-code-quality.md} +2 -0
- package/docs/standards/07-tech-stack-compliance.md +30 -0
- package/docs/standards/08-markup-quality.md +345 -0
- package/docs/standards/{07-rbac-and-rls-standard.md → 09-rbac-compliance.md} +149 -54
- package/docs/standards/10-error-handling-patterns.md +401 -0
- package/docs/standards/11-performance-optimization.md +348 -0
- package/docs/standards/12-ci-cd-integration.md +370 -0
- package/docs/standards/ALIGNMENT_REVIEW_SUMMARY.md +192 -0
- package/docs/standards/README.md +62 -33
- package/docs/troubleshooting/organisation-context-setup.md +42 -19
- package/eslint-config-pace-core.cjs +20 -4
- package/package.json +31 -21
- package/scripts/audit/audit-compliance.cjs +1295 -0
- package/scripts/audit/audit-components.cjs +260 -0
- package/scripts/audit/audit-dependencies.cjs +395 -0
- package/scripts/audit/audit-rbac.cjs +954 -0
- package/scripts/audit/audit-standards.cjs +1268 -0
- package/scripts/audit/index.cjs +1898 -194
- package/scripts/install-cursor-rules.cjs +259 -8
- package/scripts/validate-master.js +1 -1
- package/src/__tests__/fixtures/supabase.ts +1 -1
- package/src/__tests__/helpers/__tests__/component-test-utils.test.tsx +1 -1
- 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-utils.test.tsx +3 -3
- package/src/__tests__/helpers/component-test-utils.tsx +1 -1
- package/src/__tests__/helpers/supabaseMock.ts +2 -2
- package/src/__tests__/public-recipe-view.test.ts +38 -9
- package/src/components/Button/Button.tsx +5 -1
- package/src/components/ContextSelector/ContextSelector.tsx +42 -39
- package/src/components/DataTable/__tests__/keyboard.test.tsx +15 -2
- package/src/components/DataTable/components/DataTableBody.tsx +55 -31
- package/src/components/DataTable/components/DataTableCore.tsx +186 -13
- package/src/components/DataTable/components/DataTableLayout.tsx +30 -5
- package/src/components/DataTable/components/EditFields.tsx +23 -3
- package/src/components/DataTable/components/EditableRow.tsx +7 -2
- package/src/components/DataTable/components/ImportModal.tsx +4 -6
- package/src/components/DataTable/components/RowComponent.tsx +12 -0
- package/src/components/DataTable/components/ViewRowModal.tsx +4 -4
- package/src/components/DataTable/components/__tests__/ImportModal.test.tsx +455 -96
- package/src/components/DataTable/components/__tests__/ViewRowModal.test.tsx +122 -58
- package/src/components/DataTable/components/hooks/usePermissionTracking.ts +0 -4
- package/src/components/DataTable/core/DataTableContext.tsx +1 -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 -0
- package/src/components/DateTimeField/DateTimeField.tsx +20 -20
- package/src/components/DateTimeField/README.md +5 -2
- package/src/components/Dialog/Dialog.test.tsx +361 -318
- package/src/components/Dialog/Dialog.tsx +1154 -323
- package/src/components/Dialog/index.ts +3 -3
- package/src/components/FileDisplay/FileDisplay.test.tsx +45 -2
- package/src/components/FileDisplay/FileDisplay.tsx +28 -22
- package/src/components/Form/Form.test.tsx +9 -10
- package/src/components/Form/Form.tsx +369 -9
- package/src/components/InactivityWarningModal/InactivityWarningModal.test.tsx +28 -28
- package/src/components/InactivityWarningModal/InactivityWarningModal.tsx +40 -54
- package/src/components/LoginForm/LoginForm.tsx +2 -2
- package/src/components/NavigationMenu/NavigationMenu.test.tsx +14 -13
- package/src/components/NavigationMenu/NavigationMenu.tsx +2 -2
- package/src/components/NavigationMenu/useNavigationFiltering.ts +11 -21
- package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +6 -4
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +30 -41
- package/src/components/PaceAppLayout/README.md +10 -9
- package/src/components/PaceAppLayout/test-setup.tsx +40 -31
- package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +108 -61
- package/src/components/PaceLoginPage/PaceLoginPage.tsx +27 -3
- package/src/components/PasswordChange/PasswordChangeForm.test.tsx +61 -0
- package/src/components/PasswordChange/PasswordChangeForm.tsx +20 -13
- package/src/components/PublicLayout/PublicLayout.test.tsx +7 -3
- package/src/components/PublicLayout/PublicPageLayout.tsx +5 -8
- package/src/components/Select/Select.tsx +23 -21
- package/src/components/Select/types.ts +1 -1
- package/src/components/UserMenu/UserMenu.test.tsx +38 -6
- package/src/components/UserMenu/UserMenu.tsx +39 -34
- package/src/components/index.ts +3 -4
- package/src/eslint-rules/index.cjs +22 -0
- package/src/eslint-rules/rules/compliance.cjs +348 -0
- package/src/eslint-rules/rules/components.cjs +113 -0
- package/src/eslint-rules/rules/imports.cjs +102 -0
- package/src/eslint-rules/rules/rbac.cjs +790 -0
- package/src/eslint-rules/utils/helpers.cjs +42 -0
- package/src/eslint-rules/utils/manifest-loader.cjs +75 -0
- package/src/hooks/__tests__/hooks.integration.test.tsx +6 -8
- package/src/hooks/__tests__/useAppConfig.unit.test.ts +129 -67
- 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 +62 -190
- package/src/hooks/public/usePublicEventLogo.test.ts +70 -17
- package/src/hooks/public/usePublicEventLogo.ts +19 -9
- package/src/hooks/useAppConfig.ts +26 -24
- package/src/hooks/useEventTheme.test.ts +211 -233
- package/src/hooks/useEventTheme.ts +19 -28
- package/src/hooks/useEvents.ts +11 -7
- package/src/hooks/useKeyboardShortcuts.ts +1 -1
- package/src/hooks/useOrganisationPermissions.ts +9 -11
- package/src/hooks/useOrganisations.ts +13 -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 +16 -1
- package/src/providers/OrganisationProvider.tsx +23 -14
- package/src/providers/services/EventServiceProvider.tsx +1 -24
- package/src/providers/services/UnifiedAuthProvider.tsx +5 -48
- package/src/providers/services/__tests__/UnifiedAuthProvider.integration.test.tsx +3 -0
- package/src/rbac/README.md +20 -20
- 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/useRBAC.test.ts +21 -3
- package/src/rbac/hooks/useRBAC.ts +4 -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 +241 -60
- package/src/rbac/hooks/useResourcePermissions.ts +182 -63
- 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/permissions.ts +17 -17
- package/src/rbac/utils/contextValidator.ts +45 -7
- package/src/services/AuthService.ts +132 -23
- package/src/services/EventService.ts +4 -97
- package/src/services/InactivityService.ts +155 -58
- 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 +4 -0
- package/src/types/database.generated.ts +4733 -3809
- 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/formatting/formatTime.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/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/CHANGELOG.md +0 -119
- package/cursor-rules/README.md +0 -192
- package/dist/DataTable-E7YQZD7D.js +0 -175
- package/dist/DataTable-E7YQZD7D.js.map +0 -1
- package/dist/UnifiedAuthProvider-QPXO24B4.js +0 -18
- package/dist/UnifiedAuthProvider-QPXO24B4.js.map +0 -1
- package/dist/api-6LVZTHDS.js +0 -52
- package/dist/api-6LVZTHDS.js.map +0 -1
- package/dist/audit-V53FV5AG.js +0 -17
- package/dist/audit-V53FV5AG.js.map +0 -1
- package/dist/chunk-36LVWXB2.js +0 -227
- package/dist/chunk-36LVWXB2.js.map +0 -1
- package/dist/chunk-3LPHPB62.js.map +0 -1
- package/dist/chunk-5DRSZLL2.js.map +0 -1
- package/dist/chunk-5EC5MEWX.js.map +0 -1
- package/dist/chunk-63FOKYGO.js.map +0 -1
- package/dist/chunk-6SOIHG6Z.js.map +0 -1
- package/dist/chunk-7JPAB3T5.js.map +0 -1
- package/dist/chunk-ATKZM7RX.js +0 -2053
- package/dist/chunk-ATKZM7RX.js.map +0 -1
- package/dist/chunk-AVMLPIM7.js.map +0 -1
- package/dist/chunk-DGUM43GV.js.map +0 -1
- package/dist/chunk-E66EQZE6.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-I6DAQMWX.js.map +0 -1
- package/dist/chunk-J36DSWQK.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-NN6WWZ5U.js.map +0 -1
- package/dist/chunk-OEWDTMG7.js.map +0 -1
- package/dist/chunk-PWLANIRT.js.map +0 -1
- package/dist/chunk-QXHPKYJV.js.map +0 -1
- package/dist/chunk-VBXEHIUJ.js.map +0 -1
- package/dist/chunk-YKRAFF5K.js.map +0 -1
- package/dist/chunk-ZSAAAMVR.js.map +0 -1
- package/dist/components.js.map +0 -1
- package/dist/contextValidator-OOPCLPZW.js +0 -9
- package/dist/contextValidator-OOPCLPZW.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/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/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/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/eslint-rules/pace-core-compliance.cjs +0 -510
- 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,462 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Enforce RBAC contract compliance - strict rules for permission checking, page protection, and RBAC usage patterns
|
|
3
|
+
globs: ["src/**/*.{ts,tsx,js,jsx}"]
|
|
4
|
+
alwaysApply: false
|
|
5
|
+
paceCoreVersion: "0.6.x"
|
|
6
|
+
rulesVersion: "2025-01-28"
|
|
7
|
+
---
|
|
8
|
+
# RBAC Compliance Guide
|
|
9
|
+
|
|
10
|
+
**📚 Human-Readable Standard**: See [09-rbac-compliance.md](../../packages/core/docs/standards/09-rbac-compliance.md) for complete documentation including RLS policy patterns, helper functions, and security requirements.
|
|
11
|
+
|
|
12
|
+
This guide enforces the **mandatory RBAC contract** between pace-core and consuming apps. These rules are **enforced by ESLint** and violations will result in build errors.
|
|
13
|
+
|
|
14
|
+
**⚠️ CRITICAL**: This contract is **non-negotiable**. See [RBAC Contract](../../packages/core/docs/rbac/RBAC_CONTRACT.md) for complete documentation.
|
|
15
|
+
|
|
16
|
+
## MUST: Use PagePermissionGuard for All Protected Pages
|
|
17
|
+
|
|
18
|
+
**MUST wrap all protected pages with `PagePermissionGuard`:**
|
|
19
|
+
|
|
20
|
+
```tsx
|
|
21
|
+
// ✅ CORRECT
|
|
22
|
+
import { PagePermissionGuard } from '@jmruthers/pace-core/rbac';
|
|
23
|
+
|
|
24
|
+
function DashboardPage() {
|
|
25
|
+
return (
|
|
26
|
+
<PagePermissionGuard pageName="dashboard" operation="read">
|
|
27
|
+
<DashboardContent />
|
|
28
|
+
</PagePermissionGuard>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**MUST NOT:**
|
|
34
|
+
- Render protected content without `PagePermissionGuard`
|
|
35
|
+
- Create wrapper components around `PagePermissionGuard`
|
|
36
|
+
- Bypass page-level permission checks
|
|
37
|
+
|
|
38
|
+
```tsx
|
|
39
|
+
// ❌ FORBIDDEN - No guard
|
|
40
|
+
function DashboardPage() {
|
|
41
|
+
return <DashboardContent />; // ERROR: Unprotected page
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ❌ FORBIDDEN - Wrapper component
|
|
45
|
+
function EventPageGuard({ pageName, children }) {
|
|
46
|
+
return <PagePermissionGuard pageName={pageName}>{children}</PagePermissionGuard>;
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## MUST: Use pace-core Permission Hooks Directly
|
|
51
|
+
|
|
52
|
+
**MUST use pace-core hooks directly without wrappers:**
|
|
53
|
+
|
|
54
|
+
```tsx
|
|
55
|
+
// ✅ CORRECT
|
|
56
|
+
import { useCan, useResourcePermissions } from '@jmruthers/pace-core/rbac';
|
|
57
|
+
import { RESOURCE_NAMES } from '@/config/resource-names';
|
|
58
|
+
|
|
59
|
+
function MyComponent() {
|
|
60
|
+
const { canUpdate, canDelete } = useResourcePermissions(RESOURCE_NAMES.JOURNAL);
|
|
61
|
+
const canEdit = useCan(userId, scope, 'update:users');
|
|
62
|
+
// Use canUpdate, canDelete, canEdit directly
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**MUST NOT:**
|
|
67
|
+
- Create wrapper functions around permission hooks
|
|
68
|
+
- Create custom permission utility functions
|
|
69
|
+
- Use string literals in `useResourcePermissions` calls
|
|
70
|
+
|
|
71
|
+
```tsx
|
|
72
|
+
// ❌ FORBIDDEN - Wrapper function
|
|
73
|
+
const canEdit = (postId: string) => {
|
|
74
|
+
const hasPermission = canUpdate('journal');
|
|
75
|
+
const post = posts.find(p => p.id === postId);
|
|
76
|
+
return hasPermission && !!post;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// ❌ FORBIDDEN - Custom utility
|
|
80
|
+
function checkPermission(permission: string) {
|
|
81
|
+
// Custom logic...
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ❌ FORBIDDEN - String literal
|
|
85
|
+
const { canUpdate } = useResourcePermissions('journal');
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## MUST: Use RESOURCE_NAMES Constants
|
|
89
|
+
|
|
90
|
+
**MUST use `RESOURCE_NAMES` constant object for all resource permission checks:**
|
|
91
|
+
|
|
92
|
+
```tsx
|
|
93
|
+
// ✅ CORRECT
|
|
94
|
+
import { RESOURCE_NAMES } from '@/config/resource-names';
|
|
95
|
+
const { canCreate, canUpdate, canDelete } = useResourcePermissions(RESOURCE_NAMES.JOURNAL);
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
**MUST NOT:**
|
|
99
|
+
- Use string literals in `useResourcePermissions` calls
|
|
100
|
+
- Hardcode resource names
|
|
101
|
+
|
|
102
|
+
```tsx
|
|
103
|
+
// ❌ FORBIDDEN - String literal
|
|
104
|
+
const { canUpdate } = useResourcePermissions('journal');
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## MUST: Use Standard AccessDenied Component
|
|
108
|
+
|
|
109
|
+
**MUST use `AccessDenied` from pace-core for all access denied scenarios:**
|
|
110
|
+
|
|
111
|
+
```tsx
|
|
112
|
+
// ✅ CORRECT
|
|
113
|
+
import { AccessDenied } from '@jmruthers/pace-core/rbac';
|
|
114
|
+
|
|
115
|
+
<PagePermissionGuard
|
|
116
|
+
pageName="dashboard"
|
|
117
|
+
operation="read"
|
|
118
|
+
fallback={<AccessDenied />}
|
|
119
|
+
>
|
|
120
|
+
<DashboardContent />
|
|
121
|
+
</PagePermissionGuard>
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
**MUST NOT:**
|
|
125
|
+
- Create custom access denied components
|
|
126
|
+
- Use custom permission denied components
|
|
127
|
+
|
|
128
|
+
```tsx
|
|
129
|
+
// ❌ FORBIDDEN - Custom component
|
|
130
|
+
function CustomAccessDenied() {
|
|
131
|
+
return <div>Access Denied</div>;
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## MUST: Use pace-core API for Permission Checks
|
|
136
|
+
|
|
137
|
+
**MUST use pace-core API functions for programmatic permission checks:**
|
|
138
|
+
|
|
139
|
+
```tsx
|
|
140
|
+
// ✅ CORRECT
|
|
141
|
+
import { isPermitted, isPermittedCached } from '@jmruthers/pace-core/rbac';
|
|
142
|
+
|
|
143
|
+
const hasAccess = await isPermitted({
|
|
144
|
+
userId: 'user-123',
|
|
145
|
+
scope: { organisationId: 'org-456' },
|
|
146
|
+
permission: 'read:dashboard',
|
|
147
|
+
pageId: 'dashboard'
|
|
148
|
+
});
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
**MUST NOT:**
|
|
152
|
+
- Call RBAC RPC functions directly
|
|
153
|
+
- Query RBAC tables directly (without `useSecureSupabase`)
|
|
154
|
+
- Create custom RBAC helper functions
|
|
155
|
+
|
|
156
|
+
```tsx
|
|
157
|
+
// ❌ FORBIDDEN - Direct RPC call
|
|
158
|
+
const { data } = await supabase.rpc('rbac_check_permission_simplified', {
|
|
159
|
+
p_user_id: userId,
|
|
160
|
+
p_permission: 'read:dashboard'
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// ❌ FORBIDDEN - Direct table query
|
|
164
|
+
const { data } = await supabase
|
|
165
|
+
.from('rbac_user_profiles')
|
|
166
|
+
.select('*');
|
|
167
|
+
|
|
168
|
+
// ❌ FORBIDDEN - Custom RBAC helper
|
|
169
|
+
function checkPermission(userId: string, permission: string) {
|
|
170
|
+
// Custom logic that bypasses pace-core
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## MUST: Use pace-core API in Edge Functions (No Exceptions)
|
|
175
|
+
|
|
176
|
+
**Edge Functions (Deno serverless functions) MUST use pace-core's `isPermitted()` API function.** Edge Functions cannot use React hooks, but pace-core provides programmatic APIs that work outside React.
|
|
177
|
+
|
|
178
|
+
**✅ CORRECT - Edge Function Pattern:**
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
// supabase/functions/my-function/index.ts
|
|
182
|
+
import { createClient } from 'jsr:@supabase/supabase-js@2';
|
|
183
|
+
import { setupRBAC, isPermitted } from 'npm:@jmruthers/pace-core@^0.6.0/rbac';
|
|
184
|
+
|
|
185
|
+
Deno.serve(async (req: Request) => {
|
|
186
|
+
// 1. Create Supabase client from request headers
|
|
187
|
+
const authHeader = req.headers.get('Authorization');
|
|
188
|
+
if (!authHeader) {
|
|
189
|
+
return new Response(JSON.stringify({ error: 'Unauthorized' }), { status: 401 });
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const supabase = createClient(
|
|
193
|
+
Deno.env.get('SUPABASE_URL') ?? '',
|
|
194
|
+
Deno.env.get('SUPABASE_ANON_KEY') ?? '',
|
|
195
|
+
{
|
|
196
|
+
global: {
|
|
197
|
+
headers: { Authorization: authHeader },
|
|
198
|
+
},
|
|
199
|
+
}
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
// 2. Get user from session
|
|
203
|
+
const { data: { user } } = await supabase.auth.getUser();
|
|
204
|
+
if (!user) {
|
|
205
|
+
return new Response(JSON.stringify({ error: 'Unauthorized' }), { status: 401 });
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// 3. Setup RBAC (required before using isPermitted)
|
|
209
|
+
setupRBAC(supabase);
|
|
210
|
+
|
|
211
|
+
// 4. Extract organisation context from request (headers, body, or query params)
|
|
212
|
+
const organisationId = req.headers.get('x-organisation-id') ||
|
|
213
|
+
(await req.json()).organisationId;
|
|
214
|
+
|
|
215
|
+
if (!organisationId) {
|
|
216
|
+
return new Response(JSON.stringify({ error: 'Organisation context required' }), { status: 400 });
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// 5. Check permission using pace-core API
|
|
220
|
+
const hasPermission = await isPermitted({
|
|
221
|
+
userId: user.id,
|
|
222
|
+
scope: { organisationId },
|
|
223
|
+
permission: 'read:dashboard',
|
|
224
|
+
pageId: 'dashboard'
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
if (!hasPermission) {
|
|
228
|
+
return new Response(JSON.stringify({ error: 'Permission denied' }), { status: 403 });
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// 6. Proceed with function logic
|
|
232
|
+
return new Response(JSON.stringify({ success: true }));
|
|
233
|
+
});
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
**❌ FORBIDDEN - Custom RBAC Helper in Edge Functions:**
|
|
237
|
+
|
|
238
|
+
```typescript
|
|
239
|
+
// ❌ FORBIDDEN - Creating custom RBAC helper
|
|
240
|
+
// supabase/functions/_shared/rbac.ts
|
|
241
|
+
export async function checkPermission(userId: string, permission: string) {
|
|
242
|
+
// Custom logic that bypasses pace-core
|
|
243
|
+
const { data } = await supabase.rpc('rbac_check_permission_simplified', {
|
|
244
|
+
p_user_id: userId,
|
|
245
|
+
p_permission: permission
|
|
246
|
+
});
|
|
247
|
+
return data;
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
**Why No Exceptions:**
|
|
252
|
+
- pace-core provides `isPermitted()` API that works outside React
|
|
253
|
+
- `setupRBAC()` initializes the engine with a Supabase client
|
|
254
|
+
- No custom helpers needed - use pace-core APIs directly
|
|
255
|
+
- Custom helpers bypass security validation, caching, and audit logging
|
|
256
|
+
|
|
257
|
+
**Edge Function Requirements:**
|
|
258
|
+
1. **MUST** call `setupRBAC(supabase)` before using `isPermitted()`
|
|
259
|
+
2. **MUST** extract `userId` from Supabase auth session
|
|
260
|
+
3. **MUST** extract `organisationId` from request (headers, body, or query params)
|
|
261
|
+
4. **MUST** use `isPermitted()` with complete `PermissionCheck` input
|
|
262
|
+
5. **MUST NOT** create custom RBAC helper functions
|
|
263
|
+
6. **MUST NOT** call `rbac_check_permission_simplified` RPC directly
|
|
264
|
+
|
|
265
|
+
## MUST: Use useSecureSupabase for RBAC Table Queries
|
|
266
|
+
|
|
267
|
+
**If you must query RBAC tables (rare), MUST use `useSecureSupabase`:**
|
|
268
|
+
|
|
269
|
+
```tsx
|
|
270
|
+
// ✅ CORRECT
|
|
271
|
+
import { useSecureSupabase } from '@jmruthers/pace-core/rbac';
|
|
272
|
+
|
|
273
|
+
function MyComponent() {
|
|
274
|
+
const secureSupabase = useSecureSupabase();
|
|
275
|
+
// If your hook signature requires a client argument, pass ONLY the pace-core-provided client (never a local createClient()).
|
|
276
|
+
const { data } = await secureSupabase
|
|
277
|
+
.from('rbac_user_profiles') // Allowed through secure client
|
|
278
|
+
.select('*');
|
|
279
|
+
}
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
**MUST NOT query these tables directly:**
|
|
283
|
+
- `rbac_organisation_roles`
|
|
284
|
+
- `rbac_event_app_roles`
|
|
285
|
+
- `rbac_global_roles`
|
|
286
|
+
- `rbac_apps`
|
|
287
|
+
- `rbac_app_pages`
|
|
288
|
+
- `rbac_page_permissions`
|
|
289
|
+
- `rbac_user_profiles`
|
|
290
|
+
|
|
291
|
+
## MUST NOT: Use Hardcoded Role Checks
|
|
292
|
+
|
|
293
|
+
**MUST NOT compare roles directly:**
|
|
294
|
+
|
|
295
|
+
```tsx
|
|
296
|
+
// ❌ FORBIDDEN - Hardcoded role check
|
|
297
|
+
if (user.role === 'admin') {
|
|
298
|
+
// ...
|
|
299
|
+
}
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
**MUST use pace-core APIs:**
|
|
303
|
+
|
|
304
|
+
```tsx
|
|
305
|
+
// ✅ CORRECT
|
|
306
|
+
import { useAccessLevel, getRoleContext } from '@jmruthers/pace-core/rbac';
|
|
307
|
+
|
|
308
|
+
const { accessLevel } = useAccessLevel(userId, scope);
|
|
309
|
+
const roleContext = await getRoleContext({ userId, scope });
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
## MUST: Configure enforcePermissions Correctly
|
|
313
|
+
|
|
314
|
+
**For event-based apps (where pages handle their own checks):**
|
|
315
|
+
|
|
316
|
+
```tsx
|
|
317
|
+
// ✅ CORRECT - Event-based app pattern
|
|
318
|
+
<PaceAppLayout
|
|
319
|
+
appName="MyApp"
|
|
320
|
+
enforcePermissions={false} // Pages handle checks via PagePermissionGuard
|
|
321
|
+
// ...
|
|
322
|
+
>
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
**For organisation-based apps (where layout handles checks):**
|
|
326
|
+
|
|
327
|
+
```tsx
|
|
328
|
+
// ✅ CORRECT - Organisation-based app pattern
|
|
329
|
+
<PaceAppLayout
|
|
330
|
+
appName="MyApp"
|
|
331
|
+
enforcePermissions={true} // Layout handles checks
|
|
332
|
+
defaultPermission="read"
|
|
333
|
+
// ...
|
|
334
|
+
>
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
## MUST: Setup RBAC Before Use
|
|
338
|
+
|
|
339
|
+
**MUST call `setupRBAC()` before any RBAC usage:**
|
|
340
|
+
|
|
341
|
+
```tsx
|
|
342
|
+
// main.tsx - MUST be first
|
|
343
|
+
import { setupRBAC } from '@jmruthers/pace-core/rbac';
|
|
344
|
+
setupRBAC(supabase);
|
|
345
|
+
// Then render app
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
## Enforcement
|
|
349
|
+
|
|
350
|
+
These rules are enforced by ESLint rules (all ERROR severity):
|
|
351
|
+
|
|
352
|
+
1. **`no-direct-rbac-rpc`** - Detects direct calls to `rbac_check_permission_simplified`
|
|
353
|
+
2. **`no-direct-rbac-tables`** - Detects direct queries to RBAC tables
|
|
354
|
+
3. **`no-bypass-page-guard`** - Detects routes without `PagePermissionGuard`
|
|
355
|
+
4. **`no-custom-access-denied`** - Detects custom access denied components
|
|
356
|
+
5. **`no-hardcoded-role-checks`** - Detects hardcoded role comparisons
|
|
357
|
+
6. **`no-custom-permission-utilities`** - Detects custom permission utility functions
|
|
358
|
+
7. **`no-resource-permission-string-literals`** - Detects string literals in `useResourcePermissions` calls
|
|
359
|
+
8. **`no-permission-wrapper-functions`** - Detects wrapper functions around pace-core permission hooks
|
|
360
|
+
|
|
361
|
+
## Compliance Checklist
|
|
362
|
+
|
|
363
|
+
Before committing RBAC-related code, verify:
|
|
364
|
+
|
|
365
|
+
- [ ] All protected pages use `PagePermissionGuard`
|
|
366
|
+
- [ ] No wrapper components around `PagePermissionGuard`
|
|
367
|
+
- [ ] No wrapper functions around permission hooks
|
|
368
|
+
- [ ] All `useResourcePermissions` calls use `RESOURCE_NAMES` constants
|
|
369
|
+
- [ ] No direct RPC calls to `rbac_check_permission_simplified`
|
|
370
|
+
- [ ] No direct queries to RBAC tables (use `useSecureSupabase` if needed)
|
|
371
|
+
- [ ] No hardcoded role checks (use `useAccessLevel` or `getRoleContext`)
|
|
372
|
+
- [ ] No custom permission utility functions
|
|
373
|
+
- [ ] All access denied scenarios use `AccessDenied` from pace-core
|
|
374
|
+
- [ ] `enforcePermissions` configured correctly for app type
|
|
375
|
+
- [ ] `setupRBAC()` called before any RBAC usage
|
|
376
|
+
- [ ] Edge Functions use `isPermitted()` API (no custom RBAC helpers)
|
|
377
|
+
- [ ] All RLS policies for authenticated users include super-admin checks
|
|
378
|
+
- [ ] All `is_super_admin()` calls use explicit parameter (`safe_get_user_id_for_rls()`)
|
|
379
|
+
- [ ] No security-critical functions use fallback strategies
|
|
380
|
+
- [ ] ESLint rules pass without errors
|
|
381
|
+
|
|
382
|
+
## MUST: Include Super-Admin Checks in RLS Policies
|
|
383
|
+
|
|
384
|
+
**MUST include super-admin bypass in all RLS policies for authenticated users:**
|
|
385
|
+
|
|
386
|
+
```sql
|
|
387
|
+
-- ✅ CORRECT: Super-admin check included
|
|
388
|
+
CREATE POLICY "rbac_select_table_name" ON table_name
|
|
389
|
+
FOR SELECT TO authenticated
|
|
390
|
+
USING (
|
|
391
|
+
organisation_id IS NOT NULL
|
|
392
|
+
AND (
|
|
393
|
+
is_super_admin(safe_get_user_id_for_rls())
|
|
394
|
+
OR check_user_organisation_access(organisation_id)
|
|
395
|
+
)
|
|
396
|
+
);
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
**MUST NOT:**
|
|
400
|
+
- Create RLS policies without super-admin checks (except for public/anonymous policies)
|
|
401
|
+
- Use `is_super_admin()` without a parameter (relies on fallback strategies)
|
|
402
|
+
- Create security-critical functions with fallback strategies
|
|
403
|
+
|
|
404
|
+
```sql
|
|
405
|
+
-- ❌ FORBIDDEN: Missing super-admin check
|
|
406
|
+
CREATE POLICY "rbac_select_table_name" ON table_name
|
|
407
|
+
FOR SELECT TO authenticated
|
|
408
|
+
USING (
|
|
409
|
+
check_user_organisation_access(organisation_id)
|
|
410
|
+
);
|
|
411
|
+
|
|
412
|
+
-- ❌ FORBIDDEN: is_super_admin() without parameter
|
|
413
|
+
CREATE POLICY "rbac_select_table_name" ON table_name
|
|
414
|
+
FOR SELECT TO authenticated
|
|
415
|
+
USING (
|
|
416
|
+
is_super_admin() -- Missing parameter, uses fallback strategies
|
|
417
|
+
OR check_user_organisation_access(organisation_id)
|
|
418
|
+
);
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
**MUST use explicit parameter passing:**
|
|
422
|
+
|
|
423
|
+
```sql
|
|
424
|
+
-- ✅ CORRECT: Explicit user ID parameter
|
|
425
|
+
is_super_admin(safe_get_user_id_for_rls())
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
**MUST NOT create security-critical functions with fallback strategies:**
|
|
429
|
+
|
|
430
|
+
```sql
|
|
431
|
+
-- ❌ FORBIDDEN: Multiple fallback strategies
|
|
432
|
+
CREATE FUNCTION is_super_admin(p_user_id UUID DEFAULT NULL)
|
|
433
|
+
RETURNS boolean AS $$
|
|
434
|
+
BEGIN
|
|
435
|
+
IF p_user_id IS NOT NULL THEN
|
|
436
|
+
v_user_id := p_user_id;
|
|
437
|
+
ELSE
|
|
438
|
+
v_user_id := auth.uid(); -- Fallback 1
|
|
439
|
+
END IF;
|
|
440
|
+
-- More fallbacks...
|
|
441
|
+
END;
|
|
442
|
+
$$;
|
|
443
|
+
|
|
444
|
+
-- ✅ CORRECT: Required parameter, no fallbacks
|
|
445
|
+
CREATE FUNCTION is_super_admin(p_user_id UUID)
|
|
446
|
+
RETURNS boolean AS $$
|
|
447
|
+
BEGIN
|
|
448
|
+
IF p_user_id IS NULL THEN
|
|
449
|
+
RETURN false; -- Fail secure
|
|
450
|
+
END IF;
|
|
451
|
+
-- Check super-admin status
|
|
452
|
+
END;
|
|
453
|
+
$$;
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
## Reference
|
|
457
|
+
|
|
458
|
+
- **RBAC Contract**: `packages/core/docs/rbac/RBAC_CONTRACT.md` - Complete contract documentation
|
|
459
|
+
- **Migration Guide**: `packages/core/docs/rbac/MIGRATION_GUIDE.md` - Migrating to RBAC Contract v2.0.0
|
|
460
|
+
- **Quick Start**: `packages/core/docs/rbac/quick-start.md` - Getting started guide
|
|
461
|
+
- **API Reference**: `packages/core/docs/rbac/api-reference.md` - Complete API documentation
|
|
462
|
+
- **RLS Standards**: `packages/core/docs/standards/09-rbac-compliance.md` - RLS policy patterns and super-admin requirements
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Enforce consistent error handling patterns including type-safe errors, user-friendly messages, and proper logging
|
|
3
|
+
globs: ["src/**/*.{ts,tsx,js,jsx}"]
|
|
4
|
+
alwaysApply: false
|
|
5
|
+
paceCoreVersion: "0.6.x"
|
|
6
|
+
rulesVersion: "2025-01-28"
|
|
7
|
+
---
|
|
8
|
+
# Error Handling Patterns Guide
|
|
9
|
+
|
|
10
|
+
**📚 Human-Readable Standard**: See [10-error-handling-patterns.md](../../packages/core/docs/standards/10-error-handling-patterns.md) for complete documentation.
|
|
11
|
+
|
|
12
|
+
This guide enforces consistent error handling patterns to ensure user-friendly errors, type-safe handling, and proper logging.
|
|
13
|
+
|
|
14
|
+
**AI Agent Instructions**: When writing error handling code, ALWAYS follow these patterns. Never expose internal details to users. Always use type-safe error handling.
|
|
15
|
+
|
|
16
|
+
## MUST: Use Type-Safe Error Handling
|
|
17
|
+
|
|
18
|
+
**MUST use type guards or Result types, NEVER use `any`:**
|
|
19
|
+
|
|
20
|
+
```tsx
|
|
21
|
+
// ❌ WRONG: Using any
|
|
22
|
+
catch (error: any) {
|
|
23
|
+
console.log(error.message);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// ✅ CORRECT: Type guard
|
|
27
|
+
function isApiError(error: unknown): error is ApiError {
|
|
28
|
+
return typeof error === 'object' && error !== null && 'ok' in error && (error as ApiError).ok === false;
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
if (isApiError(error)) {
|
|
32
|
+
handleApiError(error);
|
|
33
|
+
} else {
|
|
34
|
+
handleUnknownError(error);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ✅ CORRECT: Result type
|
|
39
|
+
type Result<T> = { ok: true; data: T } | { ok: false; error: ApiError };
|
|
40
|
+
const result = await fetchData();
|
|
41
|
+
if (result.ok) {
|
|
42
|
+
useData(result.data);
|
|
43
|
+
} else {
|
|
44
|
+
showError(result.error.message);
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## MUST: Never Expose Internal Details
|
|
49
|
+
|
|
50
|
+
**MUST NOT expose SQL, stack traces, file paths, or internal errors to users:**
|
|
51
|
+
|
|
52
|
+
```tsx
|
|
53
|
+
// ❌ WRONG: Exposing internal details
|
|
54
|
+
toast.error(error.message); // May contain SQL, stack traces
|
|
55
|
+
|
|
56
|
+
// ✅ CORRECT: User-friendly message
|
|
57
|
+
toast.error('Unable to save changes. Please try again.');
|
|
58
|
+
logger.error('Save failed', { error: error.message, context: 'saveUser' });
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## MUST: Use Consistent Error Shapes
|
|
62
|
+
|
|
63
|
+
**MUST use ApiResult shape for API errors:**
|
|
64
|
+
|
|
65
|
+
```tsx
|
|
66
|
+
// ✅ CORRECT: Consistent error shape
|
|
67
|
+
type ApiError = {
|
|
68
|
+
ok: false;
|
|
69
|
+
error: {
|
|
70
|
+
code: string;
|
|
71
|
+
message: string; // User-friendly
|
|
72
|
+
details?: object; // Non-sensitive context
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## MUST: Log Errors with Context
|
|
78
|
+
|
|
79
|
+
**MUST log errors with context, but NEVER log sensitive data:**
|
|
80
|
+
|
|
81
|
+
```tsx
|
|
82
|
+
// ✅ CORRECT: Log with context, no sensitive data
|
|
83
|
+
logger.error('Failed to save user', {
|
|
84
|
+
userId: user.id,
|
|
85
|
+
operation: 'updateUser',
|
|
86
|
+
errorCode: error.code,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// ❌ WRONG: Logging sensitive data
|
|
90
|
+
logger.error('Failed to save user', {
|
|
91
|
+
password: user.password, // NEVER
|
|
92
|
+
token: authToken, // NEVER
|
|
93
|
+
ssn: user.ssn, // NEVER
|
|
94
|
+
});
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## SHOULD: Provide Recovery Paths
|
|
98
|
+
|
|
99
|
+
**SHOULD provide retry logic or fallback values when appropriate:**
|
|
100
|
+
|
|
101
|
+
```tsx
|
|
102
|
+
// ✅ CORRECT: Retry with exponential backoff
|
|
103
|
+
async function fetchWithRetry<T>(fn: () => Promise<Result<T>>, maxRetries = 3): Promise<Result<T>> {
|
|
104
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
105
|
+
const result = await fn();
|
|
106
|
+
if (result.ok) return result;
|
|
107
|
+
if (result.error.code?.startsWith('4')) return result; // Don't retry 4xx
|
|
108
|
+
await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt) * 1000));
|
|
109
|
+
}
|
|
110
|
+
return { ok: false, error: { code: 'MAX_RETRIES', message: 'Operation failed after retries' } };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ✅ CORRECT: Fallback values
|
|
114
|
+
const preferences = await fetchPreferences().catch(() => ({ theme: 'light', language: 'en' }));
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## SHOULD: Use Error Boundaries for React
|
|
118
|
+
|
|
119
|
+
**SHOULD use ErrorBoundary for React component errors:**
|
|
120
|
+
|
|
121
|
+
```tsx
|
|
122
|
+
// ✅ CORRECT: Error boundary
|
|
123
|
+
import { ErrorBoundary } from '@jmruthers/pace-core';
|
|
124
|
+
<ErrorBoundary fallback={<ErrorFallback />} onError={(error, errorInfo) => logger.error('React error', { error, errorInfo })}>
|
|
125
|
+
<YourApp />
|
|
126
|
+
</ErrorBoundary>
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Decision Tree: Error Handling
|
|
130
|
+
|
|
131
|
+
**ALWAYS follow this decision tree:**
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
1. What type of error is this?
|
|
135
|
+
├─ API Error → Use ApiResult shape, user-friendly message
|
|
136
|
+
├─ Validation Error → Use Zod errors, field-specific messages
|
|
137
|
+
├─ Network Error → Retry logic, connection message
|
|
138
|
+
└─ Unknown Error → Generic user message, detailed log
|
|
139
|
+
|
|
140
|
+
2. Should user see this error?
|
|
141
|
+
├─ YES → User-friendly message (no internals)
|
|
142
|
+
└─ NO → Log only, show generic message
|
|
143
|
+
|
|
144
|
+
3. Can we recover?
|
|
145
|
+
├─ YES → Retry logic or fallback value
|
|
146
|
+
└─ NO → Show error, allow user to retry
|
|
147
|
+
|
|
148
|
+
4. Is this sensitive data?
|
|
149
|
+
├─ YES → Never log (passwords, tokens, PII)
|
|
150
|
+
└─ NO → Log with context
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Error Handling Checklist
|
|
154
|
+
|
|
155
|
+
Before committing error handling code, verify:
|
|
156
|
+
|
|
157
|
+
- [ ] Type-safe error handling (no `any`)
|
|
158
|
+
- [ ] User-friendly error messages (no internals)
|
|
159
|
+
- [ ] Errors logged with context (no sensitive data)
|
|
160
|
+
- [ ] Recovery paths provided when possible
|
|
161
|
+
- [ ] Consistent error shapes used
|
|
162
|
+
- [ ] Error boundaries for React components
|
|
163
|
+
- [ ] Async operations have proper error handling
|
|
164
|
+
- [ ] Validation errors use Zod
|
|
165
|
+
- [ ] Network errors handled gracefully
|
|
166
|
+
|
|
167
|
+
## Common Mistakes to Avoid
|
|
168
|
+
|
|
169
|
+
1. **Exposing internal details** - Never show SQL, stack traces, file paths
|
|
170
|
+
2. **Using `any` for errors** - Always use type guards or Result types
|
|
171
|
+
3. **Logging sensitive data** - Never log passwords, tokens, PII
|
|
172
|
+
4. **Ignoring errors** - Always handle errors, even if just logging
|
|
173
|
+
5. **Generic error messages** - Provide specific, actionable messages
|
|
174
|
+
|
|
175
|
+
## Reference
|
|
176
|
+
|
|
177
|
+
- **Standard**: `packages/core/docs/standards/10-error-handling-patterns.md`
|
|
178
|
+
- **Code Quality**: See `06-code-quality.mdc` for TypeScript standards
|
|
179
|
+
- **Security**: See `01-standards-compliance.mdc` for security requirements
|