@jmruthers/pace-core 0.6.1 → 0.6.3
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 +88 -10
- package/cursor-rules/00-pace-core-compliance.mdc +46 -87
- package/cursor-rules/01-standards-compliance.mdc +16 -47
- package/cursor-rules/02-project-structure.mdc +4 -4
- package/cursor-rules/03-solid-principles.mdc +45 -164
- package/cursor-rules/04-testing-standards.mdc +22 -69
- package/cursor-rules/05-bug-reports-and-features.mdc +2 -2
- package/cursor-rules/06-code-quality.mdc +42 -125
- package/cursor-rules/07-tech-stack-compliance.mdc +33 -128
- package/cursor-rules/08-markup-quality.mdc +452 -0
- package/cursor-rules/CHANGELOG.md +18 -0
- package/cursor-rules/README.md +2 -1
- package/dist/{AuthService-DjnJHDtC.d.ts → AuthService-Cb34EQs3.d.ts} +63 -1
- package/dist/{DataTable-CH1U5Tpy.d.ts → DataTable-BMRU8a1j.d.ts} +33 -1
- package/dist/{DataTable-DQ7RSOHE.js → DataTable-THFPBKTP.js} +12 -10
- package/dist/{PublicPageProvider-ce4xlHYA.d.ts → PublicPageProvider-DEMpysFR.d.ts} +394 -171
- package/dist/{UnifiedAuthProvider-185Ih4dj.d.ts → UnifiedAuthProvider-CKvHP1MK.d.ts} +30 -8
- package/dist/{UnifiedAuthProvider-ATAP5UTR.js → UnifiedAuthProvider-KAGUYQ4J.js} +5 -4
- package/dist/{api-N774RPUA.js → api-IAGWF3ZG.js} +10 -10
- package/dist/{audit-B5P6FFIR.js → audit-V53FV5AG.js} +2 -2
- package/dist/{chunk-JBKQ3SAO.js → chunk-2T2IG7T7.js} +107 -57
- package/dist/chunk-2T2IG7T7.js.map +1 -0
- package/dist/{chunk-3QRJFVBR.js → chunk-6SOIHG6Z.js} +1 -1
- package/dist/chunk-6SOIHG6Z.js.map +1 -0
- package/dist/{chunk-3XTALGJF.js → chunk-6Z7LTB3D.js} +69 -240
- package/dist/chunk-6Z7LTB3D.js.map +1 -0
- package/dist/{chunk-4ZC4GX36.js → chunk-CNCQDFLN.js} +199 -46
- package/dist/chunk-CNCQDFLN.js.map +1 -0
- package/dist/chunk-DGUM43GV.js +11 -0
- package/dist/{chunk-BYFSK72L.js → chunk-DWUBLJJM.js} +361 -187
- package/dist/chunk-DWUBLJJM.js.map +1 -0
- package/dist/{chunk-LXQLPRQ2.js → chunk-FFQEQTNW.js} +6 -8
- package/dist/chunk-FFQEQTNW.js.map +1 -0
- package/dist/chunk-FMUCXFII.js +76 -0
- package/dist/chunk-FMUCXFII.js.map +1 -0
- package/dist/{chunk-4N5C5XZU.js → chunk-HFZBI76P.js} +4 -4
- package/dist/chunk-HFZBI76P.js.map +1 -0
- package/dist/{chunk-SQGMNID3.js → chunk-L4OXEN46.js} +4 -5
- package/dist/chunk-L4OXEN46.js.map +1 -0
- package/dist/{chunk-R77UEZ4E.js → chunk-M43Y4SSO.js} +1 -1
- package/dist/chunk-M43Y4SSO.js.map +1 -0
- package/dist/{chunk-I7PSE6JW.js → chunk-M7MPQISP.js} +3 -76
- package/dist/chunk-M7MPQISP.js.map +1 -0
- package/dist/chunk-PQBSKX33.js +7793 -0
- package/dist/chunk-PQBSKX33.js.map +1 -0
- package/dist/chunk-QRPVRXYT.js +226 -0
- package/dist/chunk-QRPVRXYT.js.map +1 -0
- package/dist/{chunk-KNC55RTG.js → chunk-RWEBCB47.js} +194 -416
- package/dist/chunk-RWEBCB47.js.map +1 -0
- package/dist/{chunk-XM25TVIE.js → chunk-YDQHOZNA.js} +843 -388
- package/dist/chunk-YDQHOZNA.js.map +1 -0
- package/dist/{chunk-GLK6VM3F.js → chunk-ZNIWI3UC.js} +739 -737
- package/dist/chunk-ZNIWI3UC.js.map +1 -0
- package/dist/components.d.ts +5 -5
- package/dist/components.js +18 -16
- package/dist/components.js.map +1 -1
- package/dist/contextValidator-3JNZKUTX.js +9 -0
- package/dist/contextValidator-3JNZKUTX.js.map +1 -0
- package/dist/eslint-rules/pace-core-compliance.cjs +106 -0
- package/dist/{functions-D_kgHktt.d.ts → functions-DHebl8-F.d.ts} +1 -1
- package/dist/hooks.d.ts +55 -122
- package/dist/hooks.js +10 -13
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +60 -13
- package/dist/index.js +30 -25
- package/dist/index.js.map +1 -1
- package/dist/providers.d.ts +21 -3
- package/dist/providers.js +4 -3
- package/dist/rbac/index.d.ts +210 -139
- package/dist/rbac/index.js +17 -13
- package/dist/styles/index.js +1 -1
- package/dist/theming/runtime.d.ts +1 -13
- package/dist/theming/runtime.js +2 -2
- package/dist/{timezone-_pgH8qrY.d.ts → timezone-CHhWg6b4.d.ts} +3 -10
- package/dist/{types-UU913iLA.d.ts → types-BeoeWV5I.d.ts} +8 -0
- package/dist/{types-CEpcvwwF.d.ts → types-CkbwOr4Y.d.ts} +6 -0
- package/dist/types.d.ts +2 -2
- package/dist/types.js +1 -1
- package/dist/{usePublicRouteParams-BJAlWfuJ.d.ts → usePublicRouteParams-i3qtoBgg.d.ts} +38 -17
- package/dist/utils.d.ts +4 -5
- package/dist/utils.js +17 -19
- package/dist/utils.js.map +1 -1
- package/docs/api/README.md +21 -17
- package/docs/api/modules.md +4191 -2967
- package/docs/architecture/database-schema-requirements.md +161 -0
- package/docs/components/context-selector.md +126 -0
- package/docs/core-concepts/rbac-system.md +3 -3
- package/docs/documentation-index.md +2 -4
- package/docs/getting-started/cursor-rules.md +2 -1
- package/docs/migration/DOCUMENTATION_STRUCTURE.md +441 -0
- package/docs/migration/MIGRATION_GUIDE.md +2 -24
- package/docs/migration/RBAC_SCOPE_MIGRATION.md +385 -0
- package/docs/migration/README.md +52 -6
- package/docs/migration/V0.5.190_TO_V0.6.1_MIGRATION.md +1153 -0
- package/docs/migration/database-changes-december-2025.md +3 -3
- package/docs/pace-mint-fix-auto-selection.md +218 -0
- package/docs/pace-mint-rbac-setup.md +391 -0
- package/docs/rbac/event-based-apps.md +1 -1
- package/docs/rbac/getting-started.md +1 -1
- package/docs/rbac/quick-start.md +1 -1
- package/docs/rbac/secure-client-protection.md +330 -0
- package/docs/standards/README.md +1 -0
- package/package.json +4 -3
- package/scripts/audit/core/checks/accessibility.cjs +197 -0
- package/scripts/audit/core/checks/api-usage.cjs +191 -0
- package/scripts/audit/core/checks/bundle.cjs +142 -0
- package/scripts/{check-pace-core-compliance.cjs → audit/core/checks/compliance.cjs} +784 -685
- package/scripts/audit/core/checks/config.cjs +54 -0
- package/scripts/audit/core/checks/coverage.cjs +84 -0
- package/scripts/audit/core/checks/dependencies.cjs +985 -0
- package/scripts/audit/core/checks/documentation.cjs +268 -0
- package/scripts/audit/core/checks/environment.cjs +116 -0
- package/scripts/audit/core/checks/error-handling.cjs +340 -0
- package/scripts/audit/core/checks/forms.cjs +172 -0
- package/scripts/audit/core/checks/heuristics.cjs +68 -0
- package/scripts/audit/core/checks/hooks.cjs +334 -0
- package/scripts/audit/core/checks/imports.cjs +244 -0
- package/scripts/audit/core/checks/performance.cjs +325 -0
- package/scripts/audit/core/checks/routes.cjs +117 -0
- package/scripts/audit/core/checks/state.cjs +130 -0
- package/scripts/audit/core/checks/structure.cjs +65 -0
- package/scripts/audit/core/checks/style.cjs +584 -0
- package/scripts/audit/core/checks/testing.cjs +122 -0
- package/scripts/audit/core/checks/typescript.cjs +61 -0
- package/scripts/audit/core/scanner.cjs +199 -0
- package/scripts/audit/core/utils.cjs +137 -0
- package/scripts/audit/index.cjs +223 -0
- package/scripts/audit/reporters/console.cjs +151 -0
- package/scripts/audit/reporters/json.cjs +54 -0
- package/scripts/audit/reporters/markdown.cjs +124 -0
- package/scripts/audit-consuming-app.cjs +61 -936
- package/scripts/build-docs/build-decision.js +240 -0
- package/scripts/build-docs/cache-utils.js +105 -0
- package/scripts/build-docs/content-normalization.js +150 -0
- package/scripts/build-docs/file-utils.js +105 -0
- package/scripts/build-docs/git-utils.js +86 -0
- package/scripts/build-docs/hash-utils.js +116 -0
- package/scripts/build-docs/typedoc-runner.js +220 -0
- package/scripts/build-docs-incremental.js +77 -913
- package/scripts/utils/command-runner.js +16 -11
- package/scripts/validate-formats.js +61 -56
- package/scripts/validate-master.js +74 -69
- package/scripts/validate-pre-publish.js +70 -65
- package/src/__tests__/hooks/usePermissions.test.ts +2 -2
- package/src/components/Alert/Alert.test.tsx +12 -18
- package/src/components/Alert/Alert.tsx +5 -7
- package/src/components/Avatar/Avatar.test.tsx +4 -4
- package/src/components/Badge/Badge.tsx +14 -0
- package/src/components/Button/Button.tsx +22 -0
- package/src/components/Calendar/Calendar.tsx +8 -2
- package/src/components/Card/Card.tsx +4 -0
- package/src/components/Checkbox/Checkbox.test.tsx +12 -12
- package/src/components/Checkbox/Checkbox.tsx +2 -2
- package/src/components/ContextSelector/ContextSelector.tsx +384 -0
- package/src/components/ContextSelector/index.ts +3 -0
- package/src/components/DataTable/DataTable.tsx +38 -4
- package/src/components/DataTable/__tests__/DataTableCore.test-setup.ts +5 -6
- package/src/components/DataTable/__tests__/pagination.modes.test.tsx +18 -4
- package/src/components/DataTable/__tests__/test-utils/sharedTestUtils.tsx +2 -3
- package/src/components/DataTable/components/AccessDeniedPage.tsx +16 -25
- package/src/components/DataTable/components/ActionButtons.tsx +10 -7
- package/src/components/DataTable/components/BulkOperationsDropdown.tsx +1 -1
- package/src/components/DataTable/components/ColumnFilter.tsx +10 -0
- package/src/components/DataTable/components/ColumnVisibilityDropdown.tsx +12 -0
- package/src/components/DataTable/components/DataTableBody.tsx +8 -0
- package/src/components/DataTable/components/DataTableCore.tsx +196 -554
- package/src/components/DataTable/components/DataTableErrorBoundary.tsx +11 -0
- package/src/components/DataTable/components/DataTableLayout.tsx +559 -0
- package/src/components/DataTable/components/DataTableModals.tsx +8 -0
- package/src/components/DataTable/components/DataTableToolbar.tsx +8 -0
- package/src/components/DataTable/components/DraggableColumnHeader.tsx +12 -0
- package/src/components/DataTable/components/EditFields.tsx +307 -0
- package/src/components/DataTable/components/EditableRow.tsx +8 -0
- package/src/components/DataTable/components/EmptyState.tsx +10 -0
- package/src/components/DataTable/components/FilterRow.tsx +12 -0
- package/src/components/DataTable/components/GroupHeader.tsx +12 -0
- package/src/components/DataTable/components/GroupingDropdown.tsx +12 -0
- package/src/components/DataTable/components/ImportModal.tsx +7 -0
- package/src/components/DataTable/components/LoadingState.tsx +6 -0
- package/src/components/DataTable/components/PaginationControls.tsx +16 -1
- package/src/components/DataTable/components/RowComponent.tsx +391 -0
- package/src/components/DataTable/components/UnifiedTableBody.tsx +63 -851
- package/src/components/DataTable/components/VirtualizedDataTable.tsx +16 -4
- package/src/components/DataTable/components/__tests__/AccessDeniedPage.test.tsx +4 -2
- package/src/components/DataTable/components/cellValueUtils.ts +40 -0
- package/src/components/DataTable/components/hooks/useImportModalFocus.ts +53 -0
- package/src/components/DataTable/components/hooks/usePermissionTracking.ts +126 -0
- package/src/components/DataTable/context/DataTableContext.tsx +50 -0
- package/src/components/DataTable/core/ColumnFactory.ts +31 -0
- package/src/components/DataTable/core/DataTableContext.tsx +32 -1
- package/src/components/DataTable/hooks/useColumnOrderPersistence.ts +10 -0
- package/src/components/DataTable/hooks/useColumnReordering.ts +12 -0
- package/src/components/DataTable/hooks/useColumnVisibilityPersistence.ts +10 -0
- package/src/components/DataTable/hooks/useDataTableDataPipeline.ts +16 -0
- package/src/components/DataTable/hooks/useDataTablePermissions.ts +127 -33
- package/src/components/DataTable/hooks/useDataTableState.ts +35 -1
- package/src/components/DataTable/hooks/useEffectiveColumnOrder.ts +12 -0
- package/src/components/DataTable/hooks/useServerSideDataEffect.ts +11 -0
- package/src/components/DataTable/hooks/useTableColumns.ts +8 -0
- package/src/components/DataTable/hooks/useTableHandlers.ts +14 -0
- package/src/components/DataTable/styles.ts +6 -6
- package/src/components/DataTable/types.ts +6 -10
- package/src/components/DataTable/utils/a11yUtils.ts +7 -0
- package/src/components/DataTable/utils/debugTools.ts +18 -113
- package/src/components/DataTable/utils/errorHandling.ts +12 -0
- package/src/components/DataTable/utils/exportUtils.ts +9 -0
- package/src/components/DataTable/utils/flexibleImport.ts +12 -48
- package/src/components/DataTable/utils/paginationUtils.ts +8 -0
- package/src/components/DataTable/utils/performanceUtils.ts +5 -1
- package/src/components/Dialog/Dialog.tsx +31 -3
- package/src/components/ErrorBoundary/ErrorBoundary.test.tsx +180 -1
- package/src/components/ErrorBoundary/ErrorBoundary.tsx +45 -5
- package/src/components/ErrorBoundary/ErrorBoundaryContext.tsx +129 -0
- package/src/components/ErrorBoundary/index.ts +27 -2
- package/src/components/FileDisplay/FileDisplay.tsx +74 -28
- package/src/components/FileUpload/FileUpload.tsx +22 -2
- package/src/components/Footer/Footer.test.tsx +16 -16
- package/src/components/Footer/Footer.tsx +14 -11
- package/src/components/Form/Form.tsx +1 -0
- package/src/components/Header/Header.test.tsx +43 -73
- package/src/components/Header/Header.tsx +59 -49
- package/src/components/Input/Input.test.tsx +2 -2
- package/src/components/Input/Input.tsx +8 -4
- package/src/components/LoadingSpinner/LoadingSpinner.test.tsx +4 -4
- package/src/components/LoginForm/LoginForm.tsx +4 -0
- package/src/components/NavigationMenu/NavigationMenu.tsx +14 -513
- package/src/components/NavigationMenu/types.ts +56 -0
- package/src/components/NavigationMenu/useNavigationFiltering.ts +390 -0
- package/src/components/PaceAppLayout/PaceAppLayout.integration.test.tsx +10 -19
- package/src/components/PaceAppLayout/PaceAppLayout.performance.test.tsx +2 -2
- package/src/components/PaceAppLayout/PaceAppLayout.security.test.tsx +5 -5
- package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +13 -11
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +167 -44
- package/src/components/PaceAppLayout/README.md +14 -17
- package/src/components/PaceAppLayout/test-setup.tsx +3 -4
- package/src/components/PaceLoginPage/PaceLoginPage.tsx +3 -0
- package/src/components/PasswordChange/PasswordChangeForm.tsx +9 -0
- package/src/components/ProtectedRoute/ProtectedRoute.tsx +3 -9
- package/src/components/PublicLayout/PublicPageLayout.tsx +2 -5
- package/src/components/PublicLayout/PublicPageProvider.tsx +4 -0
- package/src/components/Select/Select.tsx +80 -434
- package/src/components/Select/context.ts +23 -0
- package/src/components/Select/hooks/useSelectEvents.ts +87 -0
- package/src/components/Select/hooks/useSelectSearch.ts +91 -0
- package/src/components/Select/hooks/useSelectState.ts +104 -0
- package/src/components/Select/index.ts +9 -1
- package/src/components/Select/types.ts +123 -0
- package/src/components/Select/utils/text.ts +26 -0
- package/src/components/SessionRestorationLoader/SessionRestorationLoader.tsx +4 -5
- package/src/components/Switch/Switch.tsx +4 -4
- package/src/components/Tabs/Tabs.tsx +1 -1
- package/src/components/Toast/Toast.tsx +4 -0
- package/src/components/Tooltip/Tooltip.tsx +2 -2
- package/src/components/UserMenu/UserMenu.test.tsx +24 -11
- package/src/components/UserMenu/UserMenu.tsx +21 -18
- package/src/components/index.ts +7 -7
- package/src/eslint-rules/pace-core-compliance.cjs +106 -0
- package/src/hooks/__tests__/index.unit.test.ts +2 -5
- package/src/hooks/__tests__/useAppConfig.unit.test.ts +4 -98
- package/src/hooks/index.ts +1 -2
- package/src/hooks/public/usePublicEvent.ts +4 -0
- package/src/hooks/public/usePublicEventLogo.ts +4 -0
- package/src/hooks/public/usePublicFileDisplay.ts +4 -0
- package/src/hooks/public/usePublicRouteParams.ts +4 -0
- package/src/hooks/services/useAuth.ts +32 -0
- package/src/hooks/services/useCurrentEvent.ts +6 -0
- package/src/hooks/services/useCurrentOrganisation.ts +6 -0
- package/src/hooks/useAppConfig.ts +15 -30
- package/src/hooks/useDebounce.ts +9 -0
- package/src/hooks/useEventTheme.ts +6 -0
- package/src/hooks/useFileDisplay.ts +81 -50
- package/src/hooks/useFileReference.ts +25 -7
- package/src/hooks/useFileUrl.ts +11 -1
- package/src/hooks/useFocusManagement.ts +14 -0
- package/src/hooks/useFocusTrap.ts +3 -0
- package/src/hooks/useInactivityTracker.ts +3 -0
- package/src/hooks/useKeyboardShortcuts.ts +4 -0
- package/src/hooks/useOrganisationPermissions.ts +4 -0
- package/src/hooks/useOrganisationSecurity.ts +4 -0
- package/src/hooks/usePerformanceMonitor.ts +4 -0
- package/src/hooks/usePermissionCache.ts +7 -0
- package/src/hooks/useQueryCache.ts +12 -1
- package/src/hooks/useSessionRestoration.ts +4 -0
- package/src/hooks/useStorage.ts +4 -0
- package/src/hooks/useToast.ts +1 -1
- package/src/index.ts +6 -6
- package/src/providers/__tests__/OrganisationProvider.test.tsx +92 -70
- package/src/providers/services/AuthServiceProvider.tsx +35 -7
- package/src/providers/services/EventServiceProvider.tsx +51 -5
- package/src/providers/services/InactivityServiceProvider.tsx +18 -0
- package/src/providers/services/OrganisationServiceProvider.tsx +18 -0
- package/src/providers/services/UnifiedAuthProvider.tsx +126 -134
- package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +29 -13
- package/src/rbac/README.md +1 -1
- package/src/rbac/__tests__/adapters.comprehensive.test.tsx +1 -1
- package/src/rbac/__tests__/scenarios.user-role.test.tsx +4 -5
- package/src/rbac/adapters.tsx +12 -3
- package/src/rbac/api.test.ts +59 -51
- package/src/rbac/api.ts +246 -167
- package/src/rbac/components/NavigationProvider.tsx +4 -1
- package/src/rbac/components/PagePermissionGuard.tsx +185 -17
- package/src/rbac/components/RoleBasedRouter.tsx +5 -1
- package/src/rbac/components/SecureDataProvider.test.tsx +84 -49
- package/src/rbac/components/SecureDataProvider.tsx +20 -5
- package/src/rbac/components/__tests__/PagePermissionGuard.race-condition.test.tsx +24 -14
- package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +7 -0
- package/src/rbac/components/__tests__/PagePermissionGuard.verification.test.tsx +14 -6
- package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +15 -4
- package/src/rbac/components/__tests__/SecureDataProvider.fixed.test.tsx +148 -24
- package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +81 -15
- package/src/rbac/engine.ts +38 -14
- package/src/rbac/hooks/__tests__/useSecureSupabase.test.ts +32 -21
- package/src/rbac/hooks/permissions/index.ts +7 -0
- package/src/rbac/hooks/permissions/useAccessLevel.ts +105 -0
- package/src/rbac/hooks/permissions/useCachedPermissions.ts +79 -0
- package/src/rbac/hooks/permissions/useCan.ts +377 -0
- package/src/rbac/hooks/permissions/useHasAllPermissions.ts +90 -0
- package/src/rbac/hooks/permissions/useHasAnyPermission.ts +90 -0
- package/src/rbac/hooks/permissions/useMultiplePermissions.ts +93 -0
- package/src/rbac/hooks/permissions/usePermissions.ts +253 -0
- package/src/rbac/hooks/useCan.test.ts +64 -66
- package/src/rbac/hooks/usePermissions.ts +14 -995
- package/src/rbac/hooks/useRBAC.test.ts +1 -5
- package/src/rbac/hooks/useRBAC.ts +36 -37
- package/src/rbac/hooks/useResolvedScope.test.ts +120 -35
- package/src/rbac/hooks/useResolvedScope.ts +35 -40
- package/src/rbac/hooks/useResourcePermissions.test.ts +54 -18
- package/src/rbac/hooks/useResourcePermissions.ts +14 -4
- package/src/rbac/hooks/useSecureSupabase.ts +27 -7
- package/src/rbac/index.ts +7 -0
- package/src/rbac/permissions.ts +0 -30
- package/src/rbac/secureClient.test.ts +22 -18
- package/src/rbac/secureClient.ts +294 -68
- package/src/rbac/security.ts +0 -17
- package/src/rbac/types.ts +9 -0
- package/src/rbac/utils/__tests__/contextValidator.test.ts +64 -86
- package/src/rbac/utils/clientSecurity.ts +93 -0
- package/src/rbac/utils/contextValidator.ts +77 -168
- package/src/services/AuthService.ts +39 -7
- package/src/services/EventService.ts +186 -54
- package/src/services/OrganisationService.ts +81 -14
- package/src/services/__tests__/EventService.test.ts +1 -2
- package/src/services/base/BaseService.ts +3 -0
- package/src/theming/__tests__/parseEventColours.test.ts +6 -9
- package/src/theming/parseEventColours.ts +5 -19
- package/src/types/vitest-globals.d.ts +51 -26
- package/src/utils/__mocks__/supabaseMock.ts +1 -3
- package/src/utils/__tests__/formatting.unit.test.ts +4 -4
- package/src/utils/__tests__/index.unit.test.ts +2 -2
- package/src/utils/audit/audit.ts +0 -3
- package/src/utils/core/cn.ts +1 -1
- package/src/utils/dynamic/dynamicUtils.ts +7 -4
- package/src/utils/file-reference/index.ts +53 -1
- package/src/utils/formatting/formatting.ts +8 -18
- package/src/utils/index.ts +0 -1
- package/dist/chunk-3QRJFVBR.js.map +0 -1
- package/dist/chunk-3XTALGJF.js.map +0 -1
- package/dist/chunk-4N5C5XZU.js.map +0 -1
- package/dist/chunk-4ZC4GX36.js.map +0 -1
- package/dist/chunk-7D4SUZUM.js +0 -38
- package/dist/chunk-BYFSK72L.js.map +0 -1
- package/dist/chunk-EXUD6RNJ.js +0 -451
- package/dist/chunk-EXUD6RNJ.js.map +0 -1
- package/dist/chunk-GLK6VM3F.js.map +0 -1
- package/dist/chunk-I7PSE6JW.js.map +0 -1
- package/dist/chunk-JBKQ3SAO.js.map +0 -1
- package/dist/chunk-KNC55RTG.js.map +0 -1
- package/dist/chunk-LXQLPRQ2.js.map +0 -1
- package/dist/chunk-R77UEZ4E.js.map +0 -1
- package/dist/chunk-SQGMNID3.js.map +0 -1
- package/dist/chunk-T33XF5ZC.js +0 -12922
- package/dist/chunk-T33XF5ZC.js.map +0 -1
- package/dist/chunk-XM25TVIE.js.map +0 -1
- package/docs/api/classes/ColumnFactory.md +0 -243
- package/docs/api/classes/ErrorBoundary.md +0 -144
- package/docs/api/classes/InvalidScopeError.md +0 -73
- package/docs/api/classes/Logger.md +0 -178
- package/docs/api/classes/MissingUserContextError.md +0 -66
- package/docs/api/classes/OrganisationContextRequiredError.md +0 -66
- package/docs/api/classes/PermissionDeniedError.md +0 -73
- package/docs/api/classes/RBACAuditManager.md +0 -297
- package/docs/api/classes/RBACCache.md +0 -322
- package/docs/api/classes/RBACEngine.md +0 -171
- package/docs/api/classes/RBACError.md +0 -76
- package/docs/api/classes/RBACNotInitializedError.md +0 -66
- package/docs/api/classes/SecureSupabaseClient.md +0 -160
- package/docs/api/classes/StorageUtils.md +0 -328
- package/docs/api/enums/FileCategory.md +0 -184
- package/docs/api/enums/LogLevel.md +0 -54
- package/docs/api/enums/RBACErrorCode.md +0 -228
- package/docs/api/enums/RPCFunction.md +0 -118
- package/docs/api/interfaces/AddressFieldProps.md +0 -241
- package/docs/api/interfaces/AddressFieldRef.md +0 -94
- package/docs/api/interfaces/AggregateConfig.md +0 -43
- package/docs/api/interfaces/AutocompleteOptions.md +0 -75
- package/docs/api/interfaces/AvatarProps.md +0 -128
- package/docs/api/interfaces/BadgeProps.md +0 -27
- package/docs/api/interfaces/ButtonProps.md +0 -53
- package/docs/api/interfaces/CalendarProps.md +0 -70
- package/docs/api/interfaces/CardProps.md +0 -66
- package/docs/api/interfaces/ColorPalette.md +0 -7
- package/docs/api/interfaces/ColorShade.md +0 -66
- package/docs/api/interfaces/ComplianceResult.md +0 -30
- package/docs/api/interfaces/DataAccessRecord.md +0 -96
- package/docs/api/interfaces/DataRecord.md +0 -11
- package/docs/api/interfaces/DataTableAction.md +0 -249
- package/docs/api/interfaces/DataTableColumn.md +0 -504
- package/docs/api/interfaces/DataTableProps.md +0 -625
- package/docs/api/interfaces/DataTableToolbarButton.md +0 -96
- package/docs/api/interfaces/DatabaseComplianceResult.md +0 -85
- package/docs/api/interfaces/DatabaseIssue.md +0 -41
- package/docs/api/interfaces/EmptyStateConfig.md +0 -61
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +0 -235
- package/docs/api/interfaces/EventAppRoleData.md +0 -71
- package/docs/api/interfaces/ExportColumn.md +0 -90
- package/docs/api/interfaces/ExportOptions.md +0 -126
- package/docs/api/interfaces/FileDisplayProps.md +0 -249
- package/docs/api/interfaces/FileMetadata.md +0 -129
- package/docs/api/interfaces/FileReference.md +0 -118
- package/docs/api/interfaces/FileSizeLimits.md +0 -7
- package/docs/api/interfaces/FileUploadOptions.md +0 -139
- package/docs/api/interfaces/FileUploadProps.md +0 -293
- package/docs/api/interfaces/FooterProps.md +0 -105
- package/docs/api/interfaces/FormFieldProps.md +0 -166
- package/docs/api/interfaces/FormProps.md +0 -113
- package/docs/api/interfaces/GrantEventAppRoleParams.md +0 -122
- package/docs/api/interfaces/InactivityWarningModalProps.md +0 -115
- package/docs/api/interfaces/InputProps.md +0 -53
- package/docs/api/interfaces/LabelProps.md +0 -107
- package/docs/api/interfaces/LoggerConfig.md +0 -62
- package/docs/api/interfaces/LoginFormProps.md +0 -184
- package/docs/api/interfaces/NavigationAccessRecord.md +0 -107
- package/docs/api/interfaces/NavigationContextType.md +0 -164
- package/docs/api/interfaces/NavigationGuardProps.md +0 -139
- package/docs/api/interfaces/NavigationItem.md +0 -120
- package/docs/api/interfaces/NavigationMenuProps.md +0 -221
- package/docs/api/interfaces/NavigationProviderProps.md +0 -117
- package/docs/api/interfaces/Organisation.md +0 -140
- package/docs/api/interfaces/OrganisationContextType.md +0 -388
- package/docs/api/interfaces/OrganisationMembership.md +0 -140
- package/docs/api/interfaces/OrganisationProviderProps.md +0 -76
- package/docs/api/interfaces/OrganisationSecurityError.md +0 -62
- package/docs/api/interfaces/PaceAppLayoutProps.md +0 -406
- package/docs/api/interfaces/PaceLoginPageProps.md +0 -47
- package/docs/api/interfaces/PageAccessRecord.md +0 -85
- package/docs/api/interfaces/PagePermissionContextType.md +0 -140
- package/docs/api/interfaces/PagePermissionGuardProps.md +0 -153
- package/docs/api/interfaces/PagePermissionProviderProps.md +0 -119
- package/docs/api/interfaces/PaletteData.md +0 -41
- package/docs/api/interfaces/ParsedAddress.md +0 -120
- package/docs/api/interfaces/PermissionEnforcerProps.md +0 -153
- package/docs/api/interfaces/ProgressProps.md +0 -42
- package/docs/api/interfaces/ProtectedRouteProps.md +0 -97
- package/docs/api/interfaces/PublicPageFooterProps.md +0 -112
- package/docs/api/interfaces/PublicPageHeaderProps.md +0 -125
- package/docs/api/interfaces/PublicPageLayoutProps.md +0 -198
- package/docs/api/interfaces/QuickFix.md +0 -52
- package/docs/api/interfaces/RBACAccessValidateParams.md +0 -52
- package/docs/api/interfaces/RBACAccessValidateResult.md +0 -41
- package/docs/api/interfaces/RBACAuditLogParams.md +0 -85
- package/docs/api/interfaces/RBACAuditLogResult.md +0 -52
- package/docs/api/interfaces/RBACConfig.md +0 -133
- package/docs/api/interfaces/RBACContext.md +0 -52
- package/docs/api/interfaces/RBACLogger.md +0 -112
- package/docs/api/interfaces/RBACPageAccessCheckParams.md +0 -74
- package/docs/api/interfaces/RBACPerformanceMetrics.md +0 -138
- package/docs/api/interfaces/RBACPermissionCheckParams.md +0 -74
- package/docs/api/interfaces/RBACPermissionCheckResult.md +0 -52
- package/docs/api/interfaces/RBACPermissionsGetParams.md +0 -63
- package/docs/api/interfaces/RBACPermissionsGetResult.md +0 -63
- package/docs/api/interfaces/RBACResult.md +0 -58
- package/docs/api/interfaces/RBACRoleGrantParams.md +0 -63
- package/docs/api/interfaces/RBACRoleGrantResult.md +0 -52
- package/docs/api/interfaces/RBACRoleRevokeParams.md +0 -63
- package/docs/api/interfaces/RBACRoleRevokeResult.md +0 -52
- package/docs/api/interfaces/RBACRoleValidateParams.md +0 -52
- package/docs/api/interfaces/RBACRoleValidateResult.md +0 -63
- package/docs/api/interfaces/RBACRolesListParams.md +0 -52
- package/docs/api/interfaces/RBACRolesListResult.md +0 -74
- package/docs/api/interfaces/RBACSessionTrackParams.md +0 -74
- package/docs/api/interfaces/RBACSessionTrackResult.md +0 -52
- package/docs/api/interfaces/ResourcePermissions.md +0 -155
- package/docs/api/interfaces/RevokeEventAppRoleParams.md +0 -100
- package/docs/api/interfaces/RoleBasedRouterContextType.md +0 -151
- package/docs/api/interfaces/RoleBasedRouterProps.md +0 -156
- package/docs/api/interfaces/RoleManagementResult.md +0 -52
- package/docs/api/interfaces/RouteAccessRecord.md +0 -107
- package/docs/api/interfaces/RouteConfig.md +0 -134
- package/docs/api/interfaces/RuntimeComplianceResult.md +0 -55
- package/docs/api/interfaces/SecureDataContextType.md +0 -168
- package/docs/api/interfaces/SecureDataProviderProps.md +0 -132
- package/docs/api/interfaces/SessionRestorationLoaderProps.md +0 -34
- package/docs/api/interfaces/SetupIssue.md +0 -41
- package/docs/api/interfaces/StorageConfig.md +0 -41
- package/docs/api/interfaces/StorageFileInfo.md +0 -74
- package/docs/api/interfaces/StorageFileMetadata.md +0 -151
- package/docs/api/interfaces/StorageListOptions.md +0 -99
- package/docs/api/interfaces/StorageListResult.md +0 -41
- package/docs/api/interfaces/StorageUploadOptions.md +0 -101
- package/docs/api/interfaces/StorageUploadResult.md +0 -63
- package/docs/api/interfaces/StorageUrlOptions.md +0 -60
- package/docs/api/interfaces/StyleImport.md +0 -19
- package/docs/api/interfaces/SwitchProps.md +0 -34
- package/docs/api/interfaces/TabsContentProps.md +0 -9
- package/docs/api/interfaces/TabsListProps.md +0 -9
- package/docs/api/interfaces/TabsProps.md +0 -9
- package/docs/api/interfaces/TabsTriggerProps.md +0 -50
- package/docs/api/interfaces/TextareaProps.md +0 -53
- package/docs/api/interfaces/ToastActionElement.md +0 -9
- package/docs/api/interfaces/ToastProps.md +0 -9
- package/docs/api/interfaces/UnifiedAuthContextType.md +0 -820
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +0 -171
- package/docs/api/interfaces/UseFormDialogOptions.md +0 -62
- package/docs/api/interfaces/UseFormDialogReturn.md +0 -117
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +0 -136
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +0 -123
- package/docs/api/interfaces/UsePublicEventLogoOptions.md +0 -87
- package/docs/api/interfaces/UsePublicEventLogoReturn.md +0 -81
- package/docs/api/interfaces/UsePublicEventOptions.md +0 -34
- package/docs/api/interfaces/UsePublicEventReturn.md +0 -68
- package/docs/api/interfaces/UsePublicFileDisplayOptions.md +0 -47
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +0 -120
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +0 -94
- package/docs/api/interfaces/UseResolvedScopeOptions.md +0 -47
- package/docs/api/interfaces/UseResolvedScopeReturn.md +0 -47
- package/docs/api/interfaces/UseResourcePermissionsOptions.md +0 -34
- package/docs/api/interfaces/UserEventAccess.md +0 -118
- package/docs/api/interfaces/UserMenuProps.md +0 -86
- package/docs/api/interfaces/UserProfile.md +0 -63
- package/docs/migration/quick-migration-guide.md +0 -356
- package/docs/migration/service-architecture.md +0 -281
- package/src/components/EventSelector/EventSelector.test.tsx +0 -720
- package/src/components/EventSelector/EventSelector.tsx +0 -420
- package/src/components/EventSelector/index.ts +0 -3
- package/src/components/OrganisationSelector/OrganisationSelector.test.tsx +0 -784
- package/src/components/OrganisationSelector/OrganisationSelector.tsx +0 -324
- package/src/components/OrganisationSelector/index.ts +0 -9
- package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +0 -680
- package/src/hooks/useSecureDataAccess.test.ts +0 -559
- package/src/hooks/useSecureDataAccess.ts +0 -681
- /package/dist/{DataTable-DQ7RSOHE.js.map → DataTable-THFPBKTP.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-ATAP5UTR.js.map → UnifiedAuthProvider-KAGUYQ4J.js.map} +0 -0
- /package/dist/{api-N774RPUA.js.map → api-IAGWF3ZG.js.map} +0 -0
- /package/dist/{audit-B5P6FFIR.js.map → audit-V53FV5AG.js.map} +0 -0
- /package/dist/{chunk-7D4SUZUM.js.map → chunk-DGUM43GV.js.map} +0 -0
- /package/docs/migration/{organisation-context-timing-fix.md → V0.3.44_organisation-context-timing-fix.md} +0 -0
- /package/docs/migration/{rbac-migration.md → V0.4.0_rbac-migration.md} +0 -0
- /package/docs/migration/{person-scoped-profiles-migration-guide.md → V0.5.190_person-scoped-profiles-migration-guide.md} +0 -0
- /package/docs/migration/{REACT_19_MIGRATION.md → V0.6.0_REACT_19_MIGRATION.md} +0 -0
package/src/rbac/secureClient.ts
CHANGED
|
@@ -12,6 +12,7 @@ import { createClient, SupabaseClient } from '@supabase/supabase-js';
|
|
|
12
12
|
import { Database } from '../types/database';
|
|
13
13
|
import { UUID } from './types';
|
|
14
14
|
import { OrganisationContextRequiredError } from './types';
|
|
15
|
+
import { markClientAsSecure } from './utils/clientSecurity';
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* Secure Supabase Client that enforces organisation context
|
|
@@ -19,26 +20,48 @@ import { OrganisationContextRequiredError } from './types';
|
|
|
19
20
|
* This client automatically injects organisation context into all requests
|
|
20
21
|
* and prevents queries that don't have the required context.
|
|
21
22
|
*
|
|
22
|
-
* Note:
|
|
23
|
-
*
|
|
23
|
+
* Note: For non-super-admins, organisationId is required. Super-admins can operate
|
|
24
|
+
* without organisationId to access system-wide tables (like core_organisations).
|
|
25
|
+
* Callers should derive organisationId from eventId before creating this client
|
|
26
|
+
* if working with event-required apps.
|
|
24
27
|
*/
|
|
25
28
|
export class SecureSupabaseClient {
|
|
26
29
|
private supabase: SupabaseClient<Database>;
|
|
27
30
|
private edgeFunctionClient: SupabaseClient<Database> | null = null;
|
|
28
31
|
private supabaseUrl: string;
|
|
29
32
|
private supabaseKey: string;
|
|
30
|
-
private organisationId: UUID;
|
|
33
|
+
private organisationId: UUID | null;
|
|
31
34
|
private eventId?: string;
|
|
32
35
|
private appId?: UUID;
|
|
33
36
|
private isSuperAdmin: boolean;
|
|
37
|
+
private usesExistingClient: boolean = false;
|
|
38
|
+
|
|
39
|
+
// Cache for RPC function signatures to avoid repeated database queries
|
|
40
|
+
// Maps function name -> Set of parameter names it accepts
|
|
41
|
+
private static rpcSignatureCache = new Map<string, Set<string>>();
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* RPC functions that are safe to call without organisation context.
|
|
45
|
+
*
|
|
46
|
+
* These functions must:
|
|
47
|
+
* - rely on JWT context (auth.uid()) for authentication
|
|
48
|
+
* - not read or write organisation-scoped data
|
|
49
|
+
*
|
|
50
|
+
* This allowlist enables compliant consuming apps to use `secureSupabase.rpc(...)`
|
|
51
|
+
* even before an organisation is selected (common during initial page load/refresh).
|
|
52
|
+
*/
|
|
53
|
+
private static readonly GLOBAL_RPC_ALLOWLIST = new Set<string>([
|
|
54
|
+
'data_rbac_apps_list',
|
|
55
|
+
]);
|
|
34
56
|
|
|
35
57
|
constructor(
|
|
36
58
|
supabaseUrl: string,
|
|
37
59
|
supabaseKey: string,
|
|
38
|
-
organisationId: UUID,
|
|
60
|
+
organisationId: UUID | null,
|
|
39
61
|
eventId?: string,
|
|
40
62
|
appId?: UUID,
|
|
41
|
-
isSuperAdmin: boolean = false
|
|
63
|
+
isSuperAdmin: boolean = false,
|
|
64
|
+
existingClient?: SupabaseClient<Database>
|
|
42
65
|
) {
|
|
43
66
|
this.supabaseUrl = supabaseUrl;
|
|
44
67
|
this.supabaseKey = supabaseKey;
|
|
@@ -47,18 +70,25 @@ export class SecureSupabaseClient {
|
|
|
47
70
|
this.appId = appId;
|
|
48
71
|
this.isSuperAdmin = isSuperAdmin;
|
|
49
72
|
|
|
50
|
-
//
|
|
51
|
-
//
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
73
|
+
// Prefer reusing an existing authenticated client (avoids multiple GoTrue instances
|
|
74
|
+
// and ensures auth context is present for RLS policies).
|
|
75
|
+
if (existingClient) {
|
|
76
|
+
this.supabase = existingClient;
|
|
77
|
+
this.usesExistingClient = true;
|
|
78
|
+
} else {
|
|
79
|
+
// Create the base Supabase client with context headers
|
|
80
|
+
// Note: We'll override functions.invoke to exclude headers for Edge Functions
|
|
81
|
+
// as they may not have CORS configured to accept custom headers
|
|
82
|
+
this.supabase = createClient<Database>(supabaseUrl, supabaseKey, {
|
|
83
|
+
global: {
|
|
84
|
+
headers: {
|
|
85
|
+
'x-organisation-id': organisationId || '',
|
|
86
|
+
'x-event-id': eventId || '',
|
|
87
|
+
'x-app-id': appId || '',
|
|
88
|
+
},
|
|
59
89
|
},
|
|
60
|
-
}
|
|
61
|
-
}
|
|
90
|
+
});
|
|
91
|
+
}
|
|
62
92
|
|
|
63
93
|
// Override the auth methods to inject context
|
|
64
94
|
this.setupContextInjection();
|
|
@@ -66,6 +96,9 @@ export class SecureSupabaseClient {
|
|
|
66
96
|
// Override functions.invoke to exclude custom headers for Edge Functions
|
|
67
97
|
// Edge Functions may not have CORS configured to accept custom headers
|
|
68
98
|
this.setupEdgeFunctionHandling();
|
|
99
|
+
|
|
100
|
+
// Mark the client as secure
|
|
101
|
+
markClientAsSecure(this.supabase);
|
|
69
102
|
}
|
|
70
103
|
|
|
71
104
|
/**
|
|
@@ -75,8 +108,10 @@ export class SecureSupabaseClient {
|
|
|
75
108
|
const originalFrom = this.supabase.from.bind(this.supabase);
|
|
76
109
|
|
|
77
110
|
(this.supabase as any).from = (table: string): any => {
|
|
78
|
-
// Validate context before allowing
|
|
79
|
-
|
|
111
|
+
// Validate context before allowing database operations.
|
|
112
|
+
// Some tables are not organisation-scoped (e.g. rbac_apps) and must be queryable
|
|
113
|
+
// without organisation context for initial bootstrapping.
|
|
114
|
+
this.validateContextForTable(table);
|
|
80
115
|
|
|
81
116
|
// Type assertion needed because table is a string but Supabase expects specific table names
|
|
82
117
|
const query = originalFrom(table as any);
|
|
@@ -94,16 +129,46 @@ export class SecureSupabaseClient {
|
|
|
94
129
|
// Type assertion needed because we're wrapping the generic rpc method
|
|
95
130
|
// The fn parameter is typed as string to match Supabase's rpc signature
|
|
96
131
|
(this.supabase as any).rpc = (fn: string, args?: any, options?: any): any => {
|
|
97
|
-
// Validate context before allowing
|
|
98
|
-
|
|
132
|
+
// Validate context before allowing RPC calls.
|
|
133
|
+
// Some RPCs are global (not organisation-scoped) but still require auth.uid() from JWT.
|
|
134
|
+
// Allow these even without organisation context.
|
|
135
|
+
this.validateContextForRpc(fn);
|
|
136
|
+
|
|
137
|
+
// SYSTEMIC FIX: Use opt-in whitelist approach instead of brittle blacklist.
|
|
138
|
+
// PostgREST matches RPCs by *exact* parameter signature; sending unexpected params results in:
|
|
139
|
+
// - HTTP 404 + PGRST202 "Could not find the function ... with parameters ..."
|
|
140
|
+
//
|
|
141
|
+
// By default, we don't inject context unless the function is explicitly whitelisted.
|
|
142
|
+
// This prevents PGRST202 errors and makes the system more maintainable.
|
|
143
|
+
const acceptedParams = this.getRpcAcceptedParams(fn);
|
|
144
|
+
|
|
145
|
+
// Only inject context parameters that:
|
|
146
|
+
// 1. The function accepts (according to our whitelist)
|
|
147
|
+
// 2. Are not already explicitly provided
|
|
148
|
+
// 3. We have values for
|
|
149
|
+
// IMPORTANT: Do NOT overwrite explicitly provided RPC parameters.
|
|
150
|
+
// Some RPCs legitimately take `p_app_id`/`p_event_id` as the *target* entity,
|
|
151
|
+
// which may differ from the current RBAC scope app/event.
|
|
152
|
+
const safeArgs = (args ?? {}) as Record<string, unknown>;
|
|
153
|
+
const contextArgs: Record<string, unknown> = { ...safeArgs };
|
|
154
|
+
|
|
155
|
+
if (acceptedParams.has('p_organisation_id') &&
|
|
156
|
+
this.organisationId &&
|
|
157
|
+
safeArgs.p_organisation_id === undefined) {
|
|
158
|
+
contextArgs.p_organisation_id = this.organisationId;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (acceptedParams.has('p_event_id') &&
|
|
162
|
+
this.eventId &&
|
|
163
|
+
safeArgs.p_event_id === undefined) {
|
|
164
|
+
contextArgs.p_event_id = this.eventId;
|
|
165
|
+
}
|
|
99
166
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
p_app_id: this.appId,
|
|
106
|
-
};
|
|
167
|
+
if (acceptedParams.has('p_app_id') &&
|
|
168
|
+
this.appId &&
|
|
169
|
+
safeArgs.p_app_id === undefined) {
|
|
170
|
+
contextArgs.p_app_id = this.appId;
|
|
171
|
+
}
|
|
107
172
|
|
|
108
173
|
return originalRpc(fn as any, contextArgs, options);
|
|
109
174
|
};
|
|
@@ -119,10 +184,19 @@ export class SecureSupabaseClient {
|
|
|
119
184
|
* This avoids interfering with the main client's operations.
|
|
120
185
|
*/
|
|
121
186
|
private setupEdgeFunctionHandling() {
|
|
122
|
-
//
|
|
123
|
-
//
|
|
124
|
-
//
|
|
125
|
-
//
|
|
187
|
+
// IMPORTANT:
|
|
188
|
+
// Do not create a second Supabase client when we are already reusing an authenticated
|
|
189
|
+
// base client (this triggers "Multiple GoTrueClient instances" warnings and can cause
|
|
190
|
+
// session/auth desync that breaks RLS-protected reads).
|
|
191
|
+
//
|
|
192
|
+
// If we're using an existing client, just use it for Edge Functions too.
|
|
193
|
+
if (this.usesExistingClient) {
|
|
194
|
+
this.edgeFunctionClient = null;
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Otherwise, create a separate client without the custom RBAC headers for Edge Functions.
|
|
199
|
+
// This prevents CORS errors when Edge Functions don't accept custom headers.
|
|
126
200
|
this.edgeFunctionClient = createClient<Database>(this.supabaseUrl, this.supabaseKey);
|
|
127
201
|
}
|
|
128
202
|
|
|
@@ -147,6 +221,8 @@ export class SecureSupabaseClient {
|
|
|
147
221
|
// Override select to add organisation filter
|
|
148
222
|
query.select = (columns?: string) => {
|
|
149
223
|
const result = originalSelect(columns);
|
|
224
|
+
// Store table name on query object so we can access it in filter methods
|
|
225
|
+
(result as any)._tableName = tableName;
|
|
150
226
|
return this.addOrganisationFilter(result, tableName);
|
|
151
227
|
};
|
|
152
228
|
|
|
@@ -173,13 +249,28 @@ export class SecureSupabaseClient {
|
|
|
173
249
|
return originalInsert(values);
|
|
174
250
|
}
|
|
175
251
|
// Non-super-admin: Add organisation_id as defense in depth
|
|
252
|
+
// organisationId should always be available for non-super-admins (validateContext ensures this)
|
|
253
|
+
if (!this.organisationId) {
|
|
254
|
+
throw new OrganisationContextRequiredError();
|
|
255
|
+
}
|
|
176
256
|
const contextValues = Array.isArray(values)
|
|
177
257
|
? values.map(v => ({ ...v, organisation_id: this.organisationId }))
|
|
178
258
|
: { ...values, organisation_id: this.organisationId };
|
|
179
259
|
return originalInsert(contextValues);
|
|
180
260
|
}
|
|
181
261
|
|
|
182
|
-
// For other tables,
|
|
262
|
+
// For other tables, add organisation_id if available
|
|
263
|
+
// Super-admins might not have organisationId set, so allow them to set it explicitly
|
|
264
|
+
if (this.isSuperAdmin && !this.organisationId) {
|
|
265
|
+
// Super admin without organisationId: Don't force it (can be set explicitly if needed)
|
|
266
|
+
return originalInsert(values);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Non-super-admin or super-admin with organisationId: Add organisation_id
|
|
270
|
+
// organisationId should always be available for non-super-admins (validateContext ensures this)
|
|
271
|
+
if (!this.organisationId) {
|
|
272
|
+
throw new OrganisationContextRequiredError();
|
|
273
|
+
}
|
|
183
274
|
const contextValues = Array.isArray(values)
|
|
184
275
|
? values.map(v => ({ ...v, organisation_id: this.organisationId }))
|
|
185
276
|
: { ...values, organisation_id: this.organisationId };
|
|
@@ -213,6 +304,10 @@ export class SecureSupabaseClient {
|
|
|
213
304
|
* - Super admins: No org filter (see all users) - RLS will allow access
|
|
214
305
|
* - Non-super-admins: Apply org filter as defense in depth - RLS will also filter
|
|
215
306
|
*
|
|
307
|
+
* For system-wide tables (like core_organisations):
|
|
308
|
+
* - Super admins: No org filter (see all records) - RLS will allow access
|
|
309
|
+
* - Non-super-admins: Apply org filter as defense in depth - RLS will also filter
|
|
310
|
+
*
|
|
216
311
|
* For other tables:
|
|
217
312
|
* - Always apply org filter unless super admin bypasses it
|
|
218
313
|
*/
|
|
@@ -247,15 +342,29 @@ export class SecureSupabaseClient {
|
|
|
247
342
|
return query;
|
|
248
343
|
}
|
|
249
344
|
|
|
345
|
+
// System-wide tables that super-admins should be able to query without organisation filters
|
|
346
|
+
// These tables have organisation_id but super-admins need to see all records
|
|
347
|
+
const systemWideTablesForSuperAdmins = [
|
|
348
|
+
'core_organisations', // Super-admins need to see all organisations
|
|
349
|
+
];
|
|
350
|
+
|
|
351
|
+
// For system-wide tables, super-admins bypass organisation filter
|
|
352
|
+
if (systemWideTablesForSuperAdmins.includes(tableName) && this.isSuperAdmin) {
|
|
353
|
+
return query; // No filter - RLS handles access control
|
|
354
|
+
}
|
|
355
|
+
|
|
250
356
|
// For rbac_user_profiles, use conditional filtering based on super admin status
|
|
251
357
|
if (tableName === 'rbac_user_profiles') {
|
|
252
358
|
// Super admins: No org filter (see all users via RLS)
|
|
253
|
-
// Non-super-admins: Apply org filter as defense in depth (RLS also filters)
|
|
254
359
|
if (this.isSuperAdmin) {
|
|
255
360
|
return query; // No filter - RLS handles access control
|
|
256
361
|
}
|
|
257
|
-
|
|
258
|
-
|
|
362
|
+
|
|
363
|
+
// For non-super-admins: Apply org filter, but allow NULL organisation_id
|
|
364
|
+
// User profiles can have organisation_id = NULL (users not yet assigned to an org)
|
|
365
|
+
// We use .or() to allow either matching organisation_id OR NULL
|
|
366
|
+
// RLS policies will still enforce access control
|
|
367
|
+
return query.or(`organisation_id.eq.${this.organisationId},organisation_id.is.null`);
|
|
259
368
|
}
|
|
260
369
|
|
|
261
370
|
// For all other tables, apply organisation filter
|
|
@@ -273,17 +382,69 @@ export class SecureSupabaseClient {
|
|
|
273
382
|
|
|
274
383
|
/**
|
|
275
384
|
* Validate that required context is present
|
|
385
|
+
* Super-admins can operate without organisation context
|
|
276
386
|
*/
|
|
277
387
|
private validateContext() {
|
|
388
|
+
// Super-admins can operate without organisation context
|
|
389
|
+
if (this.isSuperAdmin) {
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
|
|
278
393
|
if (!this.organisationId) {
|
|
279
394
|
throw new OrganisationContextRequiredError();
|
|
280
395
|
}
|
|
281
396
|
}
|
|
282
397
|
|
|
398
|
+
/**
|
|
399
|
+
* Determine whether a table requires organisation context.
|
|
400
|
+
* Tables without an organisation_id column (or global configuration tables) are safe without org context.
|
|
401
|
+
*/
|
|
402
|
+
private tableRequiresOrganisationContext(tableName: string): boolean {
|
|
403
|
+
// Keep this list aligned with the tables handled in `addOrganisationFilter` / `injectContext`.
|
|
404
|
+
const tablesWithoutOrganisationId = new Set<string>([
|
|
405
|
+
'core_organisations',
|
|
406
|
+
'rbac_apps',
|
|
407
|
+
'rbac_app_pages',
|
|
408
|
+
'rbac_global_roles',
|
|
409
|
+
'core_person',
|
|
410
|
+
'core_member',
|
|
411
|
+
'core_contact',
|
|
412
|
+
'core_consent',
|
|
413
|
+
'core_identification',
|
|
414
|
+
'core_qualification',
|
|
415
|
+
'medi_profile',
|
|
416
|
+
'medi_condition',
|
|
417
|
+
'medi_diet',
|
|
418
|
+
'medi_action_plan',
|
|
419
|
+
'medi_profile_versions',
|
|
420
|
+
]);
|
|
421
|
+
|
|
422
|
+
return !tablesWithoutOrganisationId.has(tableName);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Validate context for a specific table operation.
|
|
427
|
+
*/
|
|
428
|
+
private validateContextForTable(tableName: string) {
|
|
429
|
+
if (this.isSuperAdmin) return;
|
|
430
|
+
if (!this.organisationId && this.tableRequiresOrganisationContext(tableName)) {
|
|
431
|
+
throw new OrganisationContextRequiredError();
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Validate context for a specific RPC call.
|
|
437
|
+
*/
|
|
438
|
+
private validateContextForRpc(fn: string) {
|
|
439
|
+
if (this.isSuperAdmin) return;
|
|
440
|
+
if (SecureSupabaseClient.GLOBAL_RPC_ALLOWLIST.has(fn)) return;
|
|
441
|
+
this.validateContext();
|
|
442
|
+
}
|
|
443
|
+
|
|
283
444
|
/**
|
|
284
445
|
* Get the current organisation ID
|
|
285
446
|
*/
|
|
286
|
-
getOrganisationId(): UUID {
|
|
447
|
+
getOrganisationId(): UUID | null {
|
|
287
448
|
return this.organisationId;
|
|
288
449
|
}
|
|
289
450
|
|
|
@@ -305,7 +466,7 @@ export class SecureSupabaseClient {
|
|
|
305
466
|
* Create a new client with updated context
|
|
306
467
|
*/
|
|
307
468
|
withContext(updates: {
|
|
308
|
-
organisationId?: UUID;
|
|
469
|
+
organisationId?: UUID | null;
|
|
309
470
|
eventId?: string;
|
|
310
471
|
appId?: UUID;
|
|
311
472
|
isSuperAdmin?: boolean;
|
|
@@ -313,7 +474,7 @@ export class SecureSupabaseClient {
|
|
|
313
474
|
return new SecureSupabaseClient(
|
|
314
475
|
this.supabaseUrl,
|
|
315
476
|
this.supabaseKey,
|
|
316
|
-
updates.organisationId
|
|
477
|
+
updates.organisationId !== undefined ? updates.organisationId : this.organisationId,
|
|
317
478
|
updates.eventId !== undefined ? updates.eventId : this.eventId,
|
|
318
479
|
updates.appId !== undefined ? updates.appId : this.appId,
|
|
319
480
|
updates.isSuperAdmin !== undefined ? updates.isSuperAdmin : this.isSuperAdmin
|
|
@@ -327,7 +488,7 @@ export class SecureSupabaseClient {
|
|
|
327
488
|
getClient(): SupabaseClient<Database> {
|
|
328
489
|
// Return a proxy that intercepts functions.invoke calls to use edge function client
|
|
329
490
|
// This avoids CORS issues with Edge Functions while keeping the main client intact
|
|
330
|
-
|
|
491
|
+
const proxiedClient = new Proxy(this.supabase, {
|
|
331
492
|
get: (target, prop) => {
|
|
332
493
|
if (prop === 'functions' && this.edgeFunctionClient) {
|
|
333
494
|
// Return the edge function client's functions for invoke calls
|
|
@@ -338,36 +499,100 @@ export class SecureSupabaseClient {
|
|
|
338
499
|
return (target as any)[prop];
|
|
339
500
|
}
|
|
340
501
|
}) as SupabaseClient<Database>;
|
|
502
|
+
|
|
503
|
+
// Mark the proxied client as secure
|
|
504
|
+
markClientAsSecure(proxiedClient);
|
|
505
|
+
|
|
506
|
+
return proxiedClient;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* Get the set of parameter names that an RPC function accepts.
|
|
511
|
+
* Uses a static whitelist of RPCs that we know accept context parameters.
|
|
512
|
+
*
|
|
513
|
+
* This is an opt-in approach: by default, we don't inject context unless
|
|
514
|
+
* the function is explicitly whitelisted. This prevents PGRST202 errors from
|
|
515
|
+
* injecting unexpected parameters.
|
|
516
|
+
*
|
|
517
|
+
* @param fn - The RPC function name
|
|
518
|
+
* @returns Set of parameter names the function accepts
|
|
519
|
+
*/
|
|
520
|
+
private getRpcAcceptedParams(fn: string): Set<string> {
|
|
521
|
+
// Check cache first
|
|
522
|
+
if (SecureSupabaseClient.rpcSignatureCache.has(fn)) {
|
|
523
|
+
return SecureSupabaseClient.rpcSignatureCache.get(fn)!;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// Whitelist of RPCs that accept context parameters
|
|
527
|
+
// Format: function name -> Set of parameters it accepts
|
|
528
|
+
//
|
|
529
|
+
// SYSTEMIC FIX: This is an opt-in approach. By default, we don't inject context
|
|
530
|
+
// unless the function is explicitly whitelisted. This prevents PGRST202 errors
|
|
531
|
+
// from injecting unexpected parameters.
|
|
532
|
+
//
|
|
533
|
+
// To add a new RPC:
|
|
534
|
+
// 1. Check the function signature in the database:
|
|
535
|
+
// SELECT pg_get_function_identity_arguments(oid) FROM pg_proc WHERE proname = 'function_name';
|
|
536
|
+
// 2. Add it here with the parameters it accepts
|
|
537
|
+
const rpcContextWhitelist: Record<string, Set<string>> = {
|
|
538
|
+
// RPCs that accept all three context parameters
|
|
539
|
+
'rbac_roles_list': new Set(['p_organisation_id', 'p_event_id', 'p_app_id']),
|
|
540
|
+
|
|
541
|
+
// RPCs that accept only p_organisation_id (not p_app_id or p_event_id)
|
|
542
|
+
'data_file_reference_by_category_list': new Set(['p_organisation_id']),
|
|
543
|
+
|
|
544
|
+
// Add more RPCs here as we discover them
|
|
545
|
+
// Format: 'function_name': new Set(['p_organisation_id', 'p_event_id', 'p_app_id']),
|
|
546
|
+
};
|
|
547
|
+
|
|
548
|
+
// Default: empty set (no context injection) unless whitelisted
|
|
549
|
+
// This is the safe default - prevents PGRST202 errors
|
|
550
|
+
const acceptedParams = rpcContextWhitelist[fn] || new Set<string>();
|
|
551
|
+
|
|
552
|
+
// Cache the result to avoid repeated lookups
|
|
553
|
+
SecureSupabaseClient.rpcSignatureCache.set(fn, acceptedParams);
|
|
554
|
+
|
|
555
|
+
return acceptedParams;
|
|
341
556
|
}
|
|
342
557
|
}
|
|
343
558
|
|
|
344
|
-
/**
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
559
|
+
/**
|
|
560
|
+
* Create a secure Supabase client with organisation context
|
|
561
|
+
*
|
|
562
|
+
* @param supabaseUrl - Supabase project URL
|
|
563
|
+
* @param supabaseKey - Supabase publishable key or anon key (accepts both legacy anon keys and modern publishable keys)
|
|
564
|
+
* @param organisationId - Organisation ID (optional for super-admins)
|
|
565
|
+
* @param eventId - Optional event ID
|
|
566
|
+
* @param appId - Optional app ID
|
|
567
|
+
* @param isSuperAdmin - Optional super admin flag (defaults to false). When true, organisationId can be null.
|
|
568
|
+
* @returns SecureSupabaseClient instance
|
|
569
|
+
*
|
|
570
|
+
* @example
|
|
571
|
+
* ```typescript
|
|
572
|
+
* const client = createSecureClient(
|
|
573
|
+
* 'https://your-project.supabase.co',
|
|
574
|
+
* 'your-publishable-key-or-anon-key',
|
|
575
|
+
* 'org-123',
|
|
576
|
+
* 'event-456',
|
|
577
|
+
* 'app-789',
|
|
578
|
+
* false // isSuperAdmin
|
|
579
|
+
* );
|
|
580
|
+
*
|
|
581
|
+
* // For super-admins, organisationId can be null
|
|
582
|
+
* const superAdminClient = createSecureClient(
|
|
583
|
+
* 'https://your-project.supabase.co',
|
|
584
|
+
* 'your-publishable-key-or-anon-key',
|
|
585
|
+
* null, // organisationId not required for super-admins
|
|
586
|
+
* undefined,
|
|
587
|
+
* undefined,
|
|
588
|
+
* true // isSuperAdmin
|
|
589
|
+
* );
|
|
590
|
+
* ```
|
|
591
|
+
*/
|
|
367
592
|
export function createSecureClient(
|
|
368
593
|
supabaseUrl: string,
|
|
369
594
|
supabaseKey: string,
|
|
370
|
-
organisationId: UUID,
|
|
595
|
+
organisationId: UUID | null,
|
|
371
596
|
eventId?: string,
|
|
372
597
|
appId?: UUID,
|
|
373
598
|
isSuperAdmin: boolean = false
|
|
@@ -386,11 +611,12 @@ export function createSecureClient(
|
|
|
386
611
|
*/
|
|
387
612
|
export function fromSupabaseClient(
|
|
388
613
|
client: SupabaseClient<Database>,
|
|
389
|
-
organisationId: UUID,
|
|
614
|
+
organisationId: UUID | null,
|
|
390
615
|
eventId?: string,
|
|
391
|
-
appId?: UUID
|
|
616
|
+
appId?: UUID,
|
|
617
|
+
isSuperAdmin: boolean = false
|
|
392
618
|
): SecureSupabaseClient {
|
|
393
|
-
//
|
|
394
|
-
//
|
|
395
|
-
|
|
619
|
+
// Wrap the existing client to reuse auth/session while enforcing organisation/event/app context.
|
|
620
|
+
// URL/key are unused in this mode.
|
|
621
|
+
return new SecureSupabaseClient('', '', organisationId, eventId, appId, isSuperAdmin, client);
|
|
396
622
|
}
|
package/src/rbac/security.ts
CHANGED
|
@@ -9,8 +9,6 @@
|
|
|
9
9
|
|
|
10
10
|
import { UUID, Permission, Scope } from './types';
|
|
11
11
|
import { createLogger } from '../utils/core/logger';
|
|
12
|
-
import { ContextValidator } from './utils/contextValidator';
|
|
13
|
-
import type { AppConfig } from './utils/contextValidator';
|
|
14
12
|
|
|
15
13
|
const log = createLogger('RBACSecurity');
|
|
16
14
|
|
|
@@ -161,21 +159,6 @@ export class RBACSecurityValidator {
|
|
|
161
159
|
return true;
|
|
162
160
|
}
|
|
163
161
|
|
|
164
|
-
/**
|
|
165
|
-
* Validate context requirements for security
|
|
166
|
-
* @param scope - Scope object
|
|
167
|
-
* @param appConfig - App configuration
|
|
168
|
-
* @param appName - App name (for PORTAL special case)
|
|
169
|
-
* @returns True if context is valid, false otherwise
|
|
170
|
-
*/
|
|
171
|
-
static async validateContextRequirements(
|
|
172
|
-
scope: Scope,
|
|
173
|
-
appConfig?: AppConfig | null,
|
|
174
|
-
appName?: string
|
|
175
|
-
): Promise<boolean> {
|
|
176
|
-
const validation = await ContextValidator.validateScope(scope, appConfig || null, appName);
|
|
177
|
-
return validation.isValid;
|
|
178
|
-
}
|
|
179
162
|
|
|
180
163
|
/**
|
|
181
164
|
* Log security event for monitoring
|
package/src/rbac/types.ts
CHANGED
|
@@ -28,12 +28,20 @@ export type AccessLevel =
|
|
|
28
28
|
| 'admin'
|
|
29
29
|
| 'super';
|
|
30
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Scope defines the context for permission checks.
|
|
33
|
+
* Can include organisation, event, and/or app identifiers.
|
|
34
|
+
*/
|
|
31
35
|
export type Scope = {
|
|
32
36
|
organisationId?: UUID;
|
|
33
37
|
eventId?: string; // event_id is text/varchar
|
|
34
38
|
appId?: AppId | UUID;
|
|
35
39
|
};
|
|
36
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Permission check request parameters.
|
|
43
|
+
* Defines who (userId) is checking what permission in what context (scope).
|
|
44
|
+
*/
|
|
37
45
|
export type PermissionCheck = {
|
|
38
46
|
userId: UUID;
|
|
39
47
|
scope: Scope;
|
|
@@ -118,6 +126,7 @@ export interface RBACAppPage {
|
|
|
118
126
|
created_by: UUID | null;
|
|
119
127
|
updated_by: UUID | null;
|
|
120
128
|
app_id: UUID;
|
|
129
|
+
scope_type: 'event' | 'organisation' | 'both'; // Required - single source of truth for page scoping
|
|
121
130
|
}
|
|
122
131
|
|
|
123
132
|
export interface RBACApp {
|