@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,304 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Key Derivation Utilities
|
|
3
|
+
* @package @jmruthers/pace-core
|
|
4
|
+
* @module Utils/Persistence
|
|
5
|
+
* @since 1.0.0
|
|
6
|
+
*
|
|
7
|
+
* Utilities for automatically deriving stable persistence keys for components.
|
|
8
|
+
* Provides deterministic key generation based on component props, route, and context.
|
|
9
|
+
*
|
|
10
|
+
* Features:
|
|
11
|
+
* - Automatic key derivation from component props
|
|
12
|
+
* - Route-based fallbacks when props unavailable
|
|
13
|
+
* - Stable hashing for fingerprint-based keys
|
|
14
|
+
* - Support for nested component contexts
|
|
15
|
+
*
|
|
16
|
+
* @dependencies
|
|
17
|
+
* - React Router (optional) - For route-based keys
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Simple hash function for creating stable fingerprints
|
|
22
|
+
* Uses djb2 algorithm for fast, stable hashing
|
|
23
|
+
*/
|
|
24
|
+
function hashString(str: string): string {
|
|
25
|
+
let hash = 5381;
|
|
26
|
+
for (let i = 0; i < str.length; i++) {
|
|
27
|
+
hash = ((hash << 5) + hash) + str.charCodeAt(i);
|
|
28
|
+
hash = hash & hash; // Convert to 32-bit integer
|
|
29
|
+
}
|
|
30
|
+
// Convert to positive hex string
|
|
31
|
+
return Math.abs(hash).toString(16);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Create a stable hash from an array of strings
|
|
36
|
+
*/
|
|
37
|
+
function hashStableFingerprint(values: string[]): string {
|
|
38
|
+
const normalized = values
|
|
39
|
+
.filter(Boolean)
|
|
40
|
+
.map((v) => String(v).toLowerCase().trim())
|
|
41
|
+
.sort()
|
|
42
|
+
.join('|');
|
|
43
|
+
|
|
44
|
+
return hashString(normalized);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Normalize a string for use in storage keys
|
|
49
|
+
* Removes special characters and converts to lowercase
|
|
50
|
+
* Preserves path structure by keeping slashes
|
|
51
|
+
*/
|
|
52
|
+
function normalizeKey(str: string, preserveSlashes = false): string {
|
|
53
|
+
if (preserveSlashes) {
|
|
54
|
+
// For pathnames, preserve slashes but normalize other characters
|
|
55
|
+
const trimmed = str.toLowerCase().trim();
|
|
56
|
+
const hasLeadingSlash = trimmed.startsWith('/');
|
|
57
|
+
const parts = trimmed.split('/').map(segment => segment.replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '')).filter(Boolean);
|
|
58
|
+
const normalized = parts.join('/');
|
|
59
|
+
return hasLeadingSlash ? `/${normalized}` : normalized;
|
|
60
|
+
}
|
|
61
|
+
return str
|
|
62
|
+
.toLowerCase()
|
|
63
|
+
.trim()
|
|
64
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
65
|
+
.replace(/^-+|-+$/g, '');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Get route pathname if React Router is available
|
|
70
|
+
* Returns null if router is not available
|
|
71
|
+
*/
|
|
72
|
+
function getRoutePathname(): string | null {
|
|
73
|
+
if (typeof window === 'undefined') {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
// Try to use React Router's useLocation if available
|
|
79
|
+
// Since we can't use hooks here, we'll use window.location as fallback
|
|
80
|
+
// Components using this should pass location from useLocation() hook
|
|
81
|
+
return window.location.pathname;
|
|
82
|
+
} catch {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Props for DataTable key derivation
|
|
89
|
+
*/
|
|
90
|
+
export interface DataTableKeyProps {
|
|
91
|
+
/** RBAC page ID (mandatory, always available) */
|
|
92
|
+
rbacPageId?: string;
|
|
93
|
+
/** Table title */
|
|
94
|
+
title?: string;
|
|
95
|
+
/** Column IDs for fingerprint fallback */
|
|
96
|
+
columnIds?: string[];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Derive a persistence key for DataTable component
|
|
101
|
+
*
|
|
102
|
+
* Priority order:
|
|
103
|
+
* 1. rbac.pageId (mandatory prop, always available)
|
|
104
|
+
* 2. title prop (normalized)
|
|
105
|
+
* 3. Hashed fingerprint of column IDs
|
|
106
|
+
* 4. Route pathname + component type (only if not root path)
|
|
107
|
+
*
|
|
108
|
+
* @param props - DataTable props
|
|
109
|
+
* @param location - Optional location from useLocation() hook
|
|
110
|
+
* @param userId - Optional user ID to scope persistence by user
|
|
111
|
+
* @returns Derived key or null if no stable key can be determined
|
|
112
|
+
*/
|
|
113
|
+
export function deriveDataTableKey(
|
|
114
|
+
props: DataTableKeyProps,
|
|
115
|
+
location?: { pathname: string } | null,
|
|
116
|
+
userId?: string | null
|
|
117
|
+
): string | null {
|
|
118
|
+
let baseKey: string | null = null;
|
|
119
|
+
|
|
120
|
+
// Priority 1: rbac.pageId (mandatory, always available)
|
|
121
|
+
if (props.rbacPageId) {
|
|
122
|
+
baseKey = `datatable:${normalizeKey(props.rbacPageId)}`;
|
|
123
|
+
}
|
|
124
|
+
// Priority 2: title prop
|
|
125
|
+
else if (props.title) {
|
|
126
|
+
baseKey = `datatable:${normalizeKey(props.title)}`;
|
|
127
|
+
}
|
|
128
|
+
// Priority 3: Hashed fingerprint of column IDs (before pathname fallback)
|
|
129
|
+
else if (props.columnIds && props.columnIds.length > 0) {
|
|
130
|
+
const fingerprint = hashStableFingerprint(props.columnIds);
|
|
131
|
+
if (fingerprint) {
|
|
132
|
+
baseKey = `datatable:${fingerprint}`;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
// Priority 4: Route pathname + component type (only if not root path)
|
|
136
|
+
else {
|
|
137
|
+
const pathname = location?.pathname || getRoutePathname();
|
|
138
|
+
if (pathname && pathname !== '/') {
|
|
139
|
+
// Preserve path structure with slashes
|
|
140
|
+
const normalized = normalizeKey(pathname, true);
|
|
141
|
+
baseKey = `datatable:${normalized}`;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// No stable key can be determined
|
|
146
|
+
if (!baseKey) {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Scope by user ID if provided (prevents data leakage between users)
|
|
151
|
+
if (userId) {
|
|
152
|
+
return `${baseKey}:user:${userId}`;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return baseKey;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Props for Dialog key derivation
|
|
160
|
+
*/
|
|
161
|
+
export interface DialogKeyProps {
|
|
162
|
+
/** Dialog title */
|
|
163
|
+
title?: string;
|
|
164
|
+
/** Dialog description (for fingerprint fallback) */
|
|
165
|
+
description?: string;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Derive a persistence key for Dialog component
|
|
170
|
+
*
|
|
171
|
+
* Priority order:
|
|
172
|
+
* 1. title prop (if stable and provided)
|
|
173
|
+
* 2. Route pathname + component type
|
|
174
|
+
* 3. Hashed fingerprint of dialog content structure
|
|
175
|
+
*
|
|
176
|
+
* @param props - Dialog props
|
|
177
|
+
* @param location - Optional location from useLocation() hook
|
|
178
|
+
* @param userId - Optional user ID to scope persistence by user
|
|
179
|
+
* @returns Derived key or null if no stable key can be determined
|
|
180
|
+
*/
|
|
181
|
+
export function deriveDialogKey(
|
|
182
|
+
props: DialogKeyProps,
|
|
183
|
+
location?: { pathname: string } | null,
|
|
184
|
+
userId?: string | null
|
|
185
|
+
): string | null {
|
|
186
|
+
let baseKey: string | null = null;
|
|
187
|
+
|
|
188
|
+
// Priority 1: title prop
|
|
189
|
+
if (props.title) {
|
|
190
|
+
baseKey = `dialog:${normalizeKey(props.title)}`;
|
|
191
|
+
}
|
|
192
|
+
// Priority 2: Route pathname + component type
|
|
193
|
+
else {
|
|
194
|
+
const pathname = location?.pathname || getRoutePathname();
|
|
195
|
+
if (pathname && pathname !== '/') {
|
|
196
|
+
// Preserve path structure with slashes
|
|
197
|
+
const normalized = normalizeKey(pathname, true);
|
|
198
|
+
baseKey = `dialog:${normalized}`;
|
|
199
|
+
}
|
|
200
|
+
// Priority 3: Hashed fingerprint of dialog structure
|
|
201
|
+
else {
|
|
202
|
+
const fingerprintParts: string[] = [];
|
|
203
|
+
if (props.description) {
|
|
204
|
+
fingerprintParts.push(props.description);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (fingerprintParts.length > 0) {
|
|
208
|
+
const fingerprint = hashStableFingerprint(fingerprintParts);
|
|
209
|
+
baseKey = `dialog:${fingerprint}`;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// No stable key can be determined
|
|
215
|
+
if (!baseKey) {
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Scope by user ID if provided (prevents data leakage between users)
|
|
220
|
+
if (userId) {
|
|
221
|
+
return `${baseKey}:user:${userId}`;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return baseKey;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Props for Form key derivation
|
|
229
|
+
*/
|
|
230
|
+
export interface FormKeyProps {
|
|
231
|
+
/** Form field names (for fingerprint fallback) */
|
|
232
|
+
fieldNames?: string[];
|
|
233
|
+
/** Parent dialog title (if form is inside dialog) */
|
|
234
|
+
parentDialogTitle?: string;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Derive a persistence key for Form component
|
|
239
|
+
*
|
|
240
|
+
* Priority order:
|
|
241
|
+
* 1. Parent Dialog's title (if inside Dialog)
|
|
242
|
+
* 2. Route pathname + form field names hash
|
|
243
|
+
* 3. Route pathname + component type
|
|
244
|
+
*
|
|
245
|
+
* @param props - Form props
|
|
246
|
+
* @param parentContext - Optional parent context (e.g., Dialog title)
|
|
247
|
+
* @param location - Optional location from useLocation() hook
|
|
248
|
+
* @param userId - Optional user ID to scope persistence by user
|
|
249
|
+
* @returns Derived key or null if no stable key can be determined
|
|
250
|
+
*/
|
|
251
|
+
export function deriveFormKey(
|
|
252
|
+
props: FormKeyProps = {},
|
|
253
|
+
parentContext?: { dialogTitle?: string } | null,
|
|
254
|
+
location?: { pathname: string } | null,
|
|
255
|
+
userId?: string | null
|
|
256
|
+
): string | null {
|
|
257
|
+
let baseKey: string | null = null;
|
|
258
|
+
|
|
259
|
+
// Priority 1: Parent Dialog's title
|
|
260
|
+
const dialogTitle = parentContext?.dialogTitle || props.parentDialogTitle;
|
|
261
|
+
if (dialogTitle) {
|
|
262
|
+
baseKey = `form:${normalizeKey(dialogTitle)}`;
|
|
263
|
+
}
|
|
264
|
+
// Priority 2: Route pathname + form field names hash
|
|
265
|
+
else {
|
|
266
|
+
const pathname = location?.pathname || getRoutePathname();
|
|
267
|
+
if (pathname && pathname !== '/' && props.fieldNames && props.fieldNames.length > 0) {
|
|
268
|
+
const fieldHash = hashStableFingerprint(props.fieldNames);
|
|
269
|
+
if (fieldHash) {
|
|
270
|
+
// Preserve path structure with slashes
|
|
271
|
+
const normalized = normalizeKey(pathname, true);
|
|
272
|
+
baseKey = `form:${normalized}:${fieldHash}`;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
// Priority 3: Route pathname + component type
|
|
276
|
+
else if (pathname && pathname !== '/') {
|
|
277
|
+
// Preserve path structure with slashes
|
|
278
|
+
const normalized = normalizeKey(pathname, true);
|
|
279
|
+
baseKey = `form:${normalized}`;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// No stable key can be determined
|
|
284
|
+
if (!baseKey) {
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Scope by user ID if provided (prevents data leakage between users)
|
|
289
|
+
if (userId) {
|
|
290
|
+
return `${baseKey}:user:${userId}`;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return baseKey;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Create a stable hash from an array of values
|
|
298
|
+
* Exported for use in components that need custom fingerprinting
|
|
299
|
+
*
|
|
300
|
+
* @param values - Array of string values to hash
|
|
301
|
+
* @returns Stable hash string
|
|
302
|
+
*/
|
|
303
|
+
export { hashStableFingerprint };
|
|
304
|
+
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Sensitive Field Detection
|
|
3
|
+
* @package @jmruthers/pace-core
|
|
4
|
+
* @module Utils/Persistence
|
|
5
|
+
* @since 1.0.0
|
|
6
|
+
*
|
|
7
|
+
* Utilities for detecting and filtering sensitive fields that should not be persisted.
|
|
8
|
+
* Provides pattern matching and type-based detection for sensitive data.
|
|
9
|
+
*
|
|
10
|
+
* Features:
|
|
11
|
+
* - Pattern-based field name matching
|
|
12
|
+
* - Input type-based detection
|
|
13
|
+
* - Safe filtering of sensitive fields from data objects
|
|
14
|
+
* - Configurable denylist patterns
|
|
15
|
+
*
|
|
16
|
+
* Security:
|
|
17
|
+
* - Defaults to NOT persisting when uncertain
|
|
18
|
+
* - Comprehensive pattern matching for common sensitive fields
|
|
19
|
+
* - Type-based detection for password and hidden inputs
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Denylist patterns for sensitive field names (case-insensitive)
|
|
24
|
+
* Matches any field name containing these patterns
|
|
25
|
+
*/
|
|
26
|
+
const SENSITIVE_FIELD_PATTERNS = [
|
|
27
|
+
// Authentication and credentials
|
|
28
|
+
/password/i,
|
|
29
|
+
/secret/i,
|
|
30
|
+
/token/i,
|
|
31
|
+
/key/i,
|
|
32
|
+
/credential/i,
|
|
33
|
+
/auth/i,
|
|
34
|
+
/api[_-]?key/i,
|
|
35
|
+
/access[_-]?token/i,
|
|
36
|
+
/refresh[_-]?token/i,
|
|
37
|
+
/session[_-]?id/i,
|
|
38
|
+
|
|
39
|
+
// Payment information
|
|
40
|
+
/credit[_-]?card/i,
|
|
41
|
+
/card[_-]?number/i,
|
|
42
|
+
/cvv/i,
|
|
43
|
+
/cvc/i,
|
|
44
|
+
/pin/i,
|
|
45
|
+
/account[_-]?number/i,
|
|
46
|
+
/routing[_-]?number/i,
|
|
47
|
+
|
|
48
|
+
// Personal identification
|
|
49
|
+
/ssn/i,
|
|
50
|
+
/social[_-]?security/i,
|
|
51
|
+
/tax[_-]?id/i,
|
|
52
|
+
/driver[_-]?license/i,
|
|
53
|
+
/passport/i,
|
|
54
|
+
|
|
55
|
+
// Medical and health information
|
|
56
|
+
/medical/i,
|
|
57
|
+
/health/i,
|
|
58
|
+
/phi/i, // Protected Health Information
|
|
59
|
+
/hipaa/i,
|
|
60
|
+
/diagnosis/i,
|
|
61
|
+
/prescription/i,
|
|
62
|
+
|
|
63
|
+
// Other sensitive data
|
|
64
|
+
/private[_-]?key/i,
|
|
65
|
+
/signing[_-]?key/i,
|
|
66
|
+
/encryption[_-]?key/i,
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Sensitive input types that should never be persisted
|
|
71
|
+
*/
|
|
72
|
+
const SENSITIVE_INPUT_TYPES = new Set([
|
|
73
|
+
'password',
|
|
74
|
+
'hidden',
|
|
75
|
+
]);
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Check if a field name matches sensitive patterns
|
|
79
|
+
*
|
|
80
|
+
* @param name - Field name to check
|
|
81
|
+
* @returns true if field is sensitive
|
|
82
|
+
*/
|
|
83
|
+
function isSensitiveFieldName(name: string): boolean {
|
|
84
|
+
if (!name || typeof name !== 'string') {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const normalizedName = name.trim();
|
|
89
|
+
if (normalizedName.length === 0) {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Check against denylist patterns
|
|
94
|
+
return SENSITIVE_FIELD_PATTERNS.some((pattern) => pattern.test(normalizedName));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Check if an input type is sensitive
|
|
99
|
+
*
|
|
100
|
+
* @param type - Input type to check
|
|
101
|
+
* @returns true if input type is sensitive
|
|
102
|
+
*/
|
|
103
|
+
function isSensitiveInputType(type?: string): boolean {
|
|
104
|
+
if (!type || typeof type !== 'string') {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return SENSITIVE_INPUT_TYPES.has(type.toLowerCase().trim());
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Check if a field is sensitive based on name and/or type
|
|
113
|
+
*
|
|
114
|
+
* @param name - Field name
|
|
115
|
+
* @param type - Optional input type
|
|
116
|
+
* @returns true if field should not be persisted
|
|
117
|
+
*
|
|
118
|
+
* @example
|
|
119
|
+
* ```ts
|
|
120
|
+
* isSensitiveField('password', 'password'); // true
|
|
121
|
+
* isSensitiveField('email', 'text'); // false
|
|
122
|
+
* isSensitiveField('api_key', 'text'); // true
|
|
123
|
+
* ```
|
|
124
|
+
*/
|
|
125
|
+
export function isSensitiveField(name: string, type?: string): boolean {
|
|
126
|
+
// Check input type first (most reliable)
|
|
127
|
+
if (isSensitiveInputType(type)) {
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Check field name patterns
|
|
132
|
+
if (isSensitiveFieldName(name)) {
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Default to NOT sensitive when uncertain
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Filter sensitive fields from a data object
|
|
142
|
+
*
|
|
143
|
+
* Creates a new object with only non-sensitive fields.
|
|
144
|
+
* When uncertain, fields are excluded (safe default).
|
|
145
|
+
*
|
|
146
|
+
* @template T - Type of the data object
|
|
147
|
+
* @param data - Data object to filter
|
|
148
|
+
* @param fieldNames - Array of field names in the data
|
|
149
|
+
* @param fieldTypes - Optional map of field names to input types
|
|
150
|
+
* @returns Filtered data with sensitive fields removed
|
|
151
|
+
*
|
|
152
|
+
* @example
|
|
153
|
+
* ```ts
|
|
154
|
+
* const data = { name: 'John', password: 'secret123', email: 'john@example.com' };
|
|
155
|
+
* const filtered = filterSensitiveFields(data, ['name', 'password', 'email'], {
|
|
156
|
+
* password: 'password'
|
|
157
|
+
* });
|
|
158
|
+
* // Result: { name: 'John', email: 'john@example.com' }
|
|
159
|
+
* ```
|
|
160
|
+
*/
|
|
161
|
+
export function filterSensitiveFields<T extends Record<string, any>>(
|
|
162
|
+
data: T,
|
|
163
|
+
fieldNames: string[],
|
|
164
|
+
fieldTypes?: Record<string, string>
|
|
165
|
+
): Partial<T> {
|
|
166
|
+
if (!data || typeof data !== 'object') {
|
|
167
|
+
return {};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const filtered: Partial<T> = {};
|
|
171
|
+
|
|
172
|
+
for (const fieldName of fieldNames) {
|
|
173
|
+
// Skip if field doesn't exist in data
|
|
174
|
+
if (!(fieldName in data)) {
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Get field type if provided
|
|
179
|
+
const fieldType = fieldTypes?.[fieldName];
|
|
180
|
+
|
|
181
|
+
// Check if field is sensitive
|
|
182
|
+
if (isSensitiveField(fieldName, fieldType)) {
|
|
183
|
+
// Skip sensitive field
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Include non-sensitive field
|
|
188
|
+
filtered[fieldName as keyof T] = data[fieldName];
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return filtered;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Get list of sensitive field names from an array of field names
|
|
196
|
+
*
|
|
197
|
+
* Useful for logging or debugging which fields were filtered.
|
|
198
|
+
*
|
|
199
|
+
* @param fieldNames - Array of field names to check
|
|
200
|
+
* @param fieldTypes - Optional map of field names to input types
|
|
201
|
+
* @returns Array of sensitive field names
|
|
202
|
+
*/
|
|
203
|
+
export function getSensitiveFieldNames(
|
|
204
|
+
fieldNames: string[],
|
|
205
|
+
fieldTypes?: Record<string, string>
|
|
206
|
+
): string[] {
|
|
207
|
+
return fieldNames.filter((name) => {
|
|
208
|
+
const type = fieldTypes?.[name];
|
|
209
|
+
return isSensitiveField(name, type);
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
@@ -37,7 +37,7 @@ class SecureStorageImpl {
|
|
|
37
37
|
false,
|
|
38
38
|
['encrypt', 'decrypt']
|
|
39
39
|
);
|
|
40
|
-
} catch (
|
|
40
|
+
} catch (_error) {
|
|
41
41
|
await this.generateNewKey();
|
|
42
42
|
}
|
|
43
43
|
} else {
|
|
@@ -45,7 +45,7 @@ class SecureStorageImpl {
|
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
this.initialized = true;
|
|
48
|
-
} catch (
|
|
48
|
+
} catch (_error) {
|
|
49
49
|
this.initialized = true;
|
|
50
50
|
}
|
|
51
51
|
}
|
|
@@ -101,7 +101,7 @@ class SecureStorageImpl {
|
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
return parsed.value;
|
|
104
|
-
} catch (
|
|
104
|
+
} catch (_error) {
|
|
105
105
|
// Silent fail - try plain storage
|
|
106
106
|
}
|
|
107
107
|
}
|
|
@@ -120,7 +120,7 @@ class SecureStorageImpl {
|
|
|
120
120
|
}
|
|
121
121
|
|
|
122
122
|
return parsed.value || plainData;
|
|
123
|
-
} catch (
|
|
123
|
+
} catch (_error) {
|
|
124
124
|
// If parsing fails, return as-is (backward compatibility)
|
|
125
125
|
return plainData;
|
|
126
126
|
}
|
|
@@ -163,7 +163,7 @@ class SecureStorageImpl {
|
|
|
163
163
|
const exportedKey = await window.crypto.subtle.exportKey('raw', this.encryptionKey);
|
|
164
164
|
const keyData = this.arrayBufferToBase64(exportedKey);
|
|
165
165
|
localStorage.setItem('_sec_key', keyData);
|
|
166
|
-
} catch (
|
|
166
|
+
} catch (_error) {
|
|
167
167
|
// Silent fail - encryption not available
|
|
168
168
|
}
|
|
169
169
|
}
|
|
@@ -106,7 +106,7 @@ export async function extractFileMetadata(
|
|
|
106
106
|
const dimensions = await getImageDimensions(file);
|
|
107
107
|
metadata.width = dimensions.width;
|
|
108
108
|
metadata.height = dimensions.height;
|
|
109
|
-
} catch (
|
|
109
|
+
} catch (_error) {
|
|
110
110
|
// Non-critical error - image dimensions are optional metadata
|
|
111
111
|
// Using Logger would be better, but this is in a utility function
|
|
112
112
|
// For now, silently continue - dimensions are optional
|
|
@@ -116,7 +116,7 @@ export async function extractFileMetadata(
|
|
|
116
116
|
// Generate file hash if possible
|
|
117
117
|
try {
|
|
118
118
|
metadata.hash = await generateFileHash(file);
|
|
119
|
-
} catch (
|
|
119
|
+
} catch (_error) {
|
|
120
120
|
// Non-critical error - file hash is optional metadata
|
|
121
121
|
// Using Logger would be better, but this is in a utility function
|
|
122
122
|
// For now, silently continue - hash is optional
|
|
@@ -320,7 +320,7 @@ export function getPublicUrl(
|
|
|
320
320
|
}
|
|
321
321
|
|
|
322
322
|
// Normalise path by trimming whitespace and leading slashes
|
|
323
|
-
|
|
323
|
+
const normalisedPath = path.trim().replace(/^\/+/, '');
|
|
324
324
|
|
|
325
325
|
if (!normalisedPath) {
|
|
326
326
|
throw new Error('Storage path cannot be empty after normalisation');
|