@jmruthers/pace-core 0.5.189 → 0.5.190
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/core-usage-manifest.json +0 -4
- package/dist/{AuthService-B-cd2MA4.d.ts → AuthService-CbP_utw2.d.ts} +7 -3
- package/dist/{DataTable-GUFUNZ3N.js → DataTable-ON3IXISJ.js} +8 -8
- package/dist/{PublicPageProvider-B8HaLe69.d.ts → PublicPageProvider-C4uxosp6.d.ts} +83 -24
- package/dist/{UnifiedAuthProvider-BG0AL5eE.d.ts → UnifiedAuthProvider-BYA9qB-o.d.ts} +4 -3
- package/dist/{UnifiedAuthProvider-643PUAIM.js → UnifiedAuthProvider-X5NXANVI.js} +4 -2
- package/dist/{api-YP7XD5L6.js → api-I6UCQ5S6.js} +4 -2
- package/dist/{chunk-DDM4CCYT.js → chunk-4QYC5L4K.js} +60 -35
- package/dist/chunk-4QYC5L4K.js.map +1 -0
- package/dist/{chunk-IM4QE42D.js → chunk-73HSNNOQ.js} +141 -326
- package/dist/chunk-73HSNNOQ.js.map +1 -0
- package/dist/{chunk-YHCN776L.js → chunk-DZWK57KZ.js} +2 -75
- package/dist/chunk-DZWK57KZ.js.map +1 -0
- package/dist/{chunk-3GOZZZYH.js → chunk-HQVPB5MZ.js} +238 -301
- package/dist/chunk-HQVPB5MZ.js.map +1 -0
- package/dist/{chunk-THRPYOFK.js → chunk-HW3OVDUF.js} +5 -5
- package/dist/chunk-HW3OVDUF.js.map +1 -0
- package/dist/{chunk-F2IMUDXZ.js → chunk-I7PSE6JW.js} +75 -2
- package/dist/chunk-I7PSE6JW.js.map +1 -0
- package/dist/{chunk-VGZZXKBR.js → chunk-J2XXC7R5.js} +280 -52
- package/dist/chunk-J2XXC7R5.js.map +1 -0
- package/dist/{chunk-UCQSRW7Z.js → chunk-NIU6J6OX.js} +425 -378
- package/dist/chunk-NIU6J6OX.js.map +1 -0
- package/dist/{chunk-HESYZWZW.js → chunk-QWWZ5CAQ.js} +2 -2
- package/dist/{chunk-HEHYGYOX.js → chunk-RUYZKXOD.js} +401 -46
- package/dist/chunk-RUYZKXOD.js.map +1 -0
- package/dist/{chunk-2UUZZJFT.js → chunk-SDMHPX3X.js} +176 -160
- package/dist/{chunk-2UUZZJFT.js.map → chunk-SDMHPX3X.js.map} +1 -1
- package/dist/{chunk-MX64ZF6I.js → chunk-STYK4OH2.js} +11 -11
- package/dist/chunk-STYK4OH2.js.map +1 -0
- package/dist/{chunk-YGPFYGA6.js → chunk-VVBAW5A5.js} +822 -498
- package/dist/chunk-VVBAW5A5.js.map +1 -0
- package/dist/chunk-Y4BUBBHD.js +614 -0
- package/dist/chunk-Y4BUBBHD.js.map +1 -0
- package/dist/{chunk-SAUPYVLF.js → chunk-ZSAAAMVR.js} +1 -1
- package/dist/chunk-ZSAAAMVR.js.map +1 -0
- package/dist/components.d.ts +3 -4
- package/dist/components.js +19 -19
- package/dist/components.js.map +1 -1
- package/dist/eslint-rules/pace-core-compliance.cjs +0 -2
- package/dist/{file-reference-D037xOFK.d.ts → file-reference-BavO2eQj.d.ts} +13 -10
- package/dist/hooks.d.ts +10 -5
- package/dist/hooks.js +14 -8
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +13 -11
- package/dist/index.js +79 -69
- package/dist/index.js.map +1 -1
- package/dist/providers.d.ts +3 -3
- package/dist/providers.js +3 -1
- package/dist/rbac/index.d.ts +76 -12
- package/dist/rbac/index.js +12 -9
- package/dist/types.d.ts +1 -1
- package/dist/types.js +1 -1
- package/dist/{usePublicRouteParams-CTDELQ7H.d.ts → usePublicRouteParams-DxIDS4bC.d.ts} +16 -9
- package/dist/utils.js +16 -16
- package/docs/README.md +2 -2
- package/docs/api/classes/ColumnFactory.md +1 -1
- package/docs/api/classes/ErrorBoundary.md +1 -1
- package/docs/api/classes/InvalidScopeError.md +2 -2
- package/docs/api/classes/Logger.md +1 -1
- package/docs/api/classes/MissingUserContextError.md +2 -2
- package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
- package/docs/api/classes/PermissionDeniedError.md +1 -1
- package/docs/api/classes/RBACAuditManager.md +1 -1
- package/docs/api/classes/RBACCache.md +1 -1
- package/docs/api/classes/RBACEngine.md +4 -4
- package/docs/api/classes/RBACError.md +1 -1
- package/docs/api/classes/RBACNotInitializedError.md +2 -2
- package/docs/api/classes/SecureSupabaseClient.md +21 -16
- package/docs/api/classes/StorageUtils.md +7 -4
- package/docs/api/enums/FileCategory.md +1 -1
- package/docs/api/enums/LogLevel.md +1 -1
- package/docs/api/enums/RBACErrorCode.md +1 -1
- package/docs/api/enums/RPCFunction.md +1 -1
- package/docs/api/interfaces/AddressFieldProps.md +1 -1
- package/docs/api/interfaces/AddressFieldRef.md +1 -1
- package/docs/api/interfaces/AggregateConfig.md +1 -1
- package/docs/api/interfaces/AutocompleteOptions.md +1 -1
- package/docs/api/interfaces/AvatarProps.md +1 -1
- package/docs/api/interfaces/BadgeProps.md +1 -1
- package/docs/api/interfaces/ButtonProps.md +1 -1
- package/docs/api/interfaces/CalendarProps.md +20 -6
- package/docs/api/interfaces/CardProps.md +1 -1
- package/docs/api/interfaces/ColorPalette.md +1 -1
- package/docs/api/interfaces/ColorShade.md +1 -1
- package/docs/api/interfaces/ComplianceResult.md +1 -1
- package/docs/api/interfaces/DataAccessRecord.md +9 -9
- package/docs/api/interfaces/DataRecord.md +1 -1
- package/docs/api/interfaces/DataTableAction.md +1 -1
- package/docs/api/interfaces/DataTableColumn.md +1 -1
- package/docs/api/interfaces/DataTableProps.md +1 -1
- package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
- package/docs/api/interfaces/DatabaseComplianceResult.md +1 -1
- package/docs/api/interfaces/DatabaseIssue.md +1 -1
- package/docs/api/interfaces/EmptyStateConfig.md +1 -1
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
- package/docs/api/interfaces/EventAppRoleData.md +1 -1
- package/docs/api/interfaces/ExportColumn.md +1 -1
- package/docs/api/interfaces/ExportOptions.md +1 -1
- package/docs/api/interfaces/FileDisplayProps.md +62 -16
- package/docs/api/interfaces/FileMetadata.md +1 -1
- package/docs/api/interfaces/FileReference.md +2 -2
- package/docs/api/interfaces/FileSizeLimits.md +1 -1
- package/docs/api/interfaces/FileUploadOptions.md +26 -12
- package/docs/api/interfaces/FileUploadProps.md +30 -19
- package/docs/api/interfaces/FooterProps.md +1 -1
- package/docs/api/interfaces/FormFieldProps.md +1 -1
- package/docs/api/interfaces/FormProps.md +1 -1
- package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
- package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
- package/docs/api/interfaces/InputProps.md +1 -1
- package/docs/api/interfaces/LabelProps.md +1 -1
- package/docs/api/interfaces/LoggerConfig.md +1 -1
- package/docs/api/interfaces/LoginFormProps.md +1 -1
- package/docs/api/interfaces/NavigationAccessRecord.md +10 -10
- package/docs/api/interfaces/NavigationContextType.md +9 -9
- package/docs/api/interfaces/NavigationGuardProps.md +1 -1
- package/docs/api/interfaces/NavigationItem.md +1 -1
- package/docs/api/interfaces/NavigationMenuProps.md +1 -1
- package/docs/api/interfaces/NavigationProviderProps.md +7 -7
- package/docs/api/interfaces/Organisation.md +1 -1
- package/docs/api/interfaces/OrganisationContextType.md +1 -1
- package/docs/api/interfaces/OrganisationMembership.md +1 -1
- package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
- package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
- package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
- package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
- package/docs/api/interfaces/PageAccessRecord.md +8 -8
- package/docs/api/interfaces/PagePermissionContextType.md +8 -8
- package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
- package/docs/api/interfaces/PagePermissionProviderProps.md +7 -7
- package/docs/api/interfaces/PaletteData.md +1 -1
- package/docs/api/interfaces/ParsedAddress.md +1 -1
- package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
- package/docs/api/interfaces/ProgressProps.md +3 -11
- package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
- package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
- package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
- package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
- package/docs/api/interfaces/QuickFix.md +1 -1
- package/docs/api/interfaces/RBACAccessValidateParams.md +1 -1
- package/docs/api/interfaces/RBACAccessValidateResult.md +1 -1
- package/docs/api/interfaces/RBACAuditLogParams.md +1 -1
- package/docs/api/interfaces/RBACAuditLogResult.md +1 -1
- package/docs/api/interfaces/RBACConfig.md +1 -1
- package/docs/api/interfaces/RBACContext.md +1 -1
- package/docs/api/interfaces/RBACLogger.md +1 -1
- package/docs/api/interfaces/RBACPageAccessCheckParams.md +1 -1
- package/docs/api/interfaces/RBACPerformanceMetrics.md +1 -1
- package/docs/api/interfaces/RBACPermissionCheckParams.md +1 -1
- package/docs/api/interfaces/RBACPermissionCheckResult.md +1 -1
- package/docs/api/interfaces/RBACPermissionsGetParams.md +1 -1
- package/docs/api/interfaces/RBACPermissionsGetResult.md +1 -1
- package/docs/api/interfaces/RBACResult.md +1 -1
- package/docs/api/interfaces/RBACRoleGrantParams.md +1 -1
- package/docs/api/interfaces/RBACRoleGrantResult.md +1 -1
- package/docs/api/interfaces/RBACRoleRevokeParams.md +1 -1
- package/docs/api/interfaces/RBACRoleRevokeResult.md +1 -1
- package/docs/api/interfaces/RBACRoleValidateParams.md +1 -1
- package/docs/api/interfaces/RBACRoleValidateResult.md +1 -1
- package/docs/api/interfaces/RBACRolesListParams.md +1 -1
- package/docs/api/interfaces/RBACRolesListResult.md +1 -1
- package/docs/api/interfaces/RBACSessionTrackParams.md +1 -1
- package/docs/api/interfaces/RBACSessionTrackResult.md +1 -1
- package/docs/api/interfaces/ResourcePermissions.md +1 -1
- package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterContextType.md +8 -8
- package/docs/api/interfaces/RoleBasedRouterProps.md +10 -10
- package/docs/api/interfaces/RoleManagementResult.md +1 -1
- package/docs/api/interfaces/RouteAccessRecord.md +10 -10
- package/docs/api/interfaces/RouteConfig.md +10 -10
- package/docs/api/interfaces/RuntimeComplianceResult.md +1 -1
- package/docs/api/interfaces/SecureDataContextType.md +9 -9
- package/docs/api/interfaces/SecureDataProviderProps.md +8 -8
- package/docs/api/interfaces/SessionRestorationLoaderProps.md +1 -1
- package/docs/api/interfaces/SetupIssue.md +1 -1
- package/docs/api/interfaces/StorageConfig.md +4 -4
- package/docs/api/interfaces/StorageFileInfo.md +7 -7
- package/docs/api/interfaces/StorageFileMetadata.md +25 -14
- package/docs/api/interfaces/StorageListOptions.md +22 -9
- package/docs/api/interfaces/StorageListResult.md +4 -4
- package/docs/api/interfaces/StorageUploadOptions.md +21 -8
- package/docs/api/interfaces/StorageUploadResult.md +6 -6
- package/docs/api/interfaces/StorageUrlOptions.md +19 -6
- package/docs/api/interfaces/StyleImport.md +1 -1
- package/docs/api/interfaces/SwitchProps.md +1 -1
- package/docs/api/interfaces/TabsContentProps.md +1 -1
- package/docs/api/interfaces/TabsListProps.md +1 -1
- package/docs/api/interfaces/TabsProps.md +1 -1
- package/docs/api/interfaces/TabsTriggerProps.md +1 -1
- package/docs/api/interfaces/TextareaProps.md +1 -1
- package/docs/api/interfaces/ToastActionElement.md +1 -1
- package/docs/api/interfaces/ToastProps.md +1 -1
- package/docs/api/interfaces/UnifiedAuthContextType.md +53 -53
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +13 -13
- package/docs/api/interfaces/UseFormDialogOptions.md +1 -1
- package/docs/api/interfaces/UseFormDialogReturn.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventLogoOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
- package/docs/api/interfaces/UsePublicFileDisplayOptions.md +1 -1
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeOptions.md +4 -4
- package/docs/api/interfaces/UseResolvedScopeReturn.md +4 -4
- package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
- package/docs/api/interfaces/UserEventAccess.md +11 -11
- package/docs/api/interfaces/UserMenuProps.md +1 -1
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +151 -92
- package/docs/api-reference/components.md +15 -7
- package/docs/api-reference/providers.md +2 -2
- package/docs/api-reference/rpc-functions.md +1 -0
- package/docs/best-practices/README.md +1 -1
- package/docs/best-practices/deployment.md +8 -8
- package/docs/getting-started/examples/README.md +2 -2
- package/docs/getting-started/installation-guide.md +4 -4
- package/docs/getting-started/quick-start.md +3 -3
- package/docs/migration/MIGRATION_GUIDE.md +3 -3
- package/docs/rbac/compliance/compliance-guide.md +2 -2
- package/docs/rbac/event-based-apps.md +2 -2
- package/docs/rbac/getting-started.md +2 -2
- package/docs/rbac/quick-start.md +2 -2
- package/docs/security/README.md +4 -4
- package/docs/standards/07-rbac-and-rls-standard.md +430 -7
- package/docs/troubleshooting/README.md +2 -2
- package/docs/troubleshooting/migration.md +3 -3
- package/package.json +1 -3
- package/scripts/check-pace-core-compliance.cjs +1 -1
- package/scripts/check-pace-core-compliance.js +1 -1
- package/src/__tests__/fixtures/supabase.ts +301 -0
- package/src/__tests__/public-recipe-view.test.ts +9 -9
- package/src/__tests__/rls-policies.test.ts +197 -61
- package/src/components/AddressField/AddressField.test.tsx +42 -0
- package/src/components/AddressField/AddressField.tsx +71 -60
- package/src/components/AddressField/README.md +1 -0
- package/src/components/Alert/Alert.test.tsx +50 -10
- package/src/components/Alert/Alert.tsx +5 -3
- package/src/components/Avatar/Avatar.test.tsx +95 -43
- package/src/components/Avatar/Avatar.tsx +16 -16
- package/src/components/Button/Button.test.tsx +2 -1
- package/src/components/Button/Button.tsx +3 -3
- package/src/components/Calendar/Calendar.test.tsx +53 -37
- package/src/components/Calendar/Calendar.tsx +409 -82
- package/src/components/Card/Card.test.tsx +7 -4
- package/src/components/Card/Card.tsx +3 -6
- package/src/components/Checkbox/Checkbox.tsx +2 -2
- package/src/components/DataTable/components/ActionButtons.tsx +5 -5
- package/src/components/DataTable/components/BulkOperationsDropdown.tsx +2 -2
- package/src/components/DataTable/components/ColumnFilter.tsx +1 -1
- package/src/components/DataTable/components/ColumnVisibilityDropdown.tsx +3 -3
- package/src/components/DataTable/components/DataTableBody.tsx +12 -12
- package/src/components/DataTable/components/DataTableCore.tsx +3 -3
- package/src/components/DataTable/components/DataTableToolbar.tsx +5 -5
- package/src/components/DataTable/components/DraggableColumnHeader.tsx +3 -3
- package/src/components/DataTable/components/EditableRow.tsx +2 -2
- package/src/components/DataTable/components/EmptyState.tsx +3 -3
- package/src/components/DataTable/components/GroupHeader.tsx +2 -2
- package/src/components/DataTable/components/GroupingDropdown.tsx +1 -1
- package/src/components/DataTable/components/ImportModal.tsx +4 -4
- package/src/components/DataTable/components/LoadingState.tsx +1 -1
- package/src/components/DataTable/components/PaginationControls.tsx +11 -11
- package/src/components/DataTable/components/UnifiedTableBody.tsx +9 -9
- package/src/components/DataTable/components/ViewRowModal.tsx +2 -2
- package/src/components/DataTable/components/__tests__/AccessDeniedPage.test.tsx +11 -37
- package/src/components/DataTable/components/__tests__/DataTableToolbar.test.tsx +157 -0
- package/src/components/DataTable/components/__tests__/LoadingState.test.tsx +2 -1
- package/src/components/DataTable/components/__tests__/VirtualizedDataTable.test.tsx +128 -0
- package/src/components/DataTable/core/__tests__/ActionManager.test.ts +19 -0
- package/src/components/DataTable/core/__tests__/ColumnFactory.test.ts +51 -0
- package/src/components/DataTable/core/__tests__/ColumnManager.test.ts +84 -0
- package/src/components/DataTable/core/__tests__/DataManager.test.ts +14 -0
- package/src/components/DataTable/core/__tests__/DataTableContext.test.tsx +136 -0
- package/src/components/DataTable/core/__tests__/LocalDataAdapter.test.ts +16 -0
- package/src/components/DataTable/core/__tests__/PluginRegistry.test.ts +18 -0
- package/src/components/DataTable/hooks/useDataTablePermissions.ts +28 -7
- package/src/components/DataTable/utils/__tests__/hierarchicalUtils.test.ts +30 -1
- package/src/components/DataTable/utils/hierarchicalUtils.ts +38 -10
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +8 -3
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.tsx +4 -4
- package/src/components/Dialog/Dialog.tsx +2 -2
- package/src/components/EventSelector/EventSelector.tsx +7 -7
- package/src/components/FileDisplay/FileDisplay.tsx +291 -179
- package/src/components/FileUpload/FileUpload.tsx +7 -4
- package/src/components/Header/Header.test.tsx +28 -0
- package/src/components/Header/Header.tsx +22 -9
- package/src/components/InactivityWarningModal/InactivityWarningModal.tsx +2 -2
- package/src/components/LoadingSpinner/LoadingSpinner.test.tsx +19 -14
- package/src/components/LoadingSpinner/LoadingSpinner.tsx +5 -5
- package/src/components/NavigationMenu/NavigationMenu.test.tsx +127 -1
- package/src/components/OrganisationSelector/OrganisationSelector.tsx +8 -8
- package/src/components/PaceAppLayout/PaceAppLayout.integration.test.tsx +4 -0
- package/src/components/PaceAppLayout/PaceAppLayout.performance.test.tsx +3 -0
- package/src/components/PaceAppLayout/PaceAppLayout.security.test.tsx +3 -0
- package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +16 -6
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +37 -3
- package/src/components/PaceAppLayout/test-setup.tsx +1 -0
- package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +66 -45
- package/src/components/PaceLoginPage/PaceLoginPage.tsx +6 -4
- package/src/components/Progress/Progress.test.tsx +18 -19
- package/src/components/Progress/Progress.tsx +31 -32
- package/src/components/PublicLayout/PublicLayout.test.tsx +6 -6
- package/src/components/PublicLayout/PublicPageProvider.tsx +5 -3
- package/src/components/Select/Select.tsx +5 -5
- package/src/components/Switch/Switch.test.tsx +2 -1
- package/src/components/Switch/Switch.tsx +1 -1
- package/src/components/Toast/Toast.tsx +1 -1
- package/src/components/Tooltip/Tooltip.test.tsx +8 -2
- package/src/components/UserMenu/UserMenu.tsx +3 -3
- package/src/eslint-rules/pace-core-compliance.cjs +0 -2
- package/src/eslint-rules/pace-core-compliance.js +0 -2
- package/src/hooks/__tests__/hooks.integration.test.tsx +4 -1
- package/src/hooks/__tests__/useAppConfig.unit.test.ts +76 -5
- package/src/hooks/__tests__/useDataTableState.test.ts +76 -0
- package/src/hooks/__tests__/useFileUrl.unit.test.ts +25 -69
- package/src/hooks/__tests__/useFileUrlCache.test.ts +129 -0
- package/src/hooks/__tests__/usePreventTabReload.test.ts +88 -0
- package/src/hooks/__tests__/{usePublicEvent.unit.test.ts → usePublicEvent.test.ts} +28 -1
- package/src/hooks/__tests__/useQueryCache.test.ts +144 -0
- package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +58 -16
- package/src/hooks/index.ts +1 -1
- package/src/hooks/public/usePublicEvent.ts +2 -2
- package/src/hooks/public/usePublicFileDisplay.ts +173 -87
- package/src/hooks/useAppConfig.ts +24 -5
- package/src/hooks/useFileDisplay.ts +297 -34
- package/src/hooks/useFileReference.ts +56 -11
- package/src/hooks/useFileUrl.ts +1 -1
- package/src/hooks/useInactivityTracker.ts +16 -7
- package/src/hooks/usePermissionCache.test.ts +85 -8
- package/src/hooks/useQueryCache.ts +21 -0
- package/src/hooks/useSecureDataAccess.test.ts +80 -35
- package/src/hooks/useSecureDataAccess.ts +80 -37
- package/src/providers/services/EventServiceProvider.tsx +37 -17
- package/src/providers/services/InactivityServiceProvider.tsx +4 -4
- package/src/providers/services/OrganisationServiceProvider.tsx +8 -1
- package/src/providers/services/UnifiedAuthProvider.tsx +115 -29
- package/src/rbac/__tests__/auth-rbac.e2e.test.tsx +451 -0
- package/src/rbac/__tests__/engine.comprehensive.test.ts +12 -0
- package/src/rbac/__tests__/rbac-engine-core-logic.test.ts +8 -0
- package/src/rbac/__tests__/rbac-engine-simplified.test.ts +4 -0
- package/src/rbac/api.ts +240 -36
- package/src/rbac/cache-invalidation.ts +21 -7
- package/src/rbac/compliance/quick-fix-suggestions.ts +1 -1
- package/src/rbac/components/NavigationGuard.tsx +23 -63
- package/src/rbac/components/NavigationProvider.test.tsx +52 -23
- package/src/rbac/components/NavigationProvider.tsx +13 -11
- package/src/rbac/components/PagePermissionGuard.tsx +77 -203
- package/src/rbac/components/PagePermissionProvider.tsx +13 -11
- package/src/rbac/components/PermissionEnforcer.tsx +24 -62
- package/src/rbac/components/RoleBasedRouter.tsx +14 -12
- package/src/rbac/components/SecureDataProvider.tsx +13 -11
- package/src/rbac/components/__tests__/NavigationGuard.test.tsx +104 -41
- package/src/rbac/components/__tests__/NavigationProvider.test.tsx +49 -12
- package/src/rbac/components/__tests__/PagePermissionGuard.race-condition.test.tsx +22 -1
- package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +161 -82
- package/src/rbac/components/__tests__/PagePermissionGuard.verification.test.tsx +22 -1
- package/src/rbac/components/__tests__/PermissionEnforcer.test.tsx +77 -30
- package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +39 -5
- package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +47 -4
- package/src/rbac/engine.ts +4 -2
- package/src/rbac/hooks/__tests__/useSecureSupabase.test.ts +144 -52
- package/src/rbac/hooks/index.ts +3 -0
- package/src/rbac/hooks/useCan.test.ts +101 -53
- package/src/rbac/hooks/usePermissions.ts +108 -41
- package/src/rbac/hooks/useRBAC.test.ts +11 -3
- package/src/rbac/hooks/useRBAC.ts +83 -40
- package/src/rbac/hooks/useResolvedScope.test.ts +189 -63
- package/src/rbac/hooks/useResolvedScope.ts +128 -70
- package/src/rbac/hooks/useSecureSupabase.ts +36 -19
- package/src/rbac/hooks/useSuperAdminBypass.ts +126 -0
- package/src/rbac/request-deduplication.ts +1 -1
- package/src/rbac/secureClient.ts +72 -12
- package/src/rbac/security.ts +29 -23
- package/src/rbac/types.ts +10 -0
- package/src/rbac/utils/__tests__/contextValidator.test.ts +150 -0
- package/src/rbac/utils/__tests__/deep-equal.test.ts +53 -0
- package/src/rbac/utils/__tests__/eventContext.test.ts +6 -1
- package/src/rbac/utils/contextValidator.ts +288 -0
- package/src/rbac/utils/eventContext.ts +48 -2
- package/src/services/EventService.ts +165 -21
- package/src/services/OrganisationService.ts +37 -2
- package/src/services/__tests__/EventService.test.ts +26 -21
- package/src/types/file-reference.ts +13 -10
- package/src/utils/app/appNameResolver.test.ts +346 -73
- package/src/utils/context/superAdminOverride.ts +58 -0
- package/src/utils/file-reference/index.ts +61 -33
- package/src/utils/google-places/googlePlacesUtils.test.ts +98 -0
- package/src/utils/google-places/loadGoogleMapsScript.test.ts +83 -0
- package/src/utils/storage/helpers.test.ts +1 -1
- package/src/utils/storage/helpers.ts +38 -19
- package/src/utils/storage/types.ts +15 -8
- package/src/utils/validation/__tests__/csrf.test.ts +105 -0
- package/src/utils/validation/__tests__/sqlInjectionProtection.test.ts +92 -0
- package/src/vite-env.d.ts +2 -2
- package/dist/chunk-3GOZZZYH.js.map +0 -1
- package/dist/chunk-DDM4CCYT.js.map +0 -1
- package/dist/chunk-E7UAOUMY.js +0 -75
- package/dist/chunk-E7UAOUMY.js.map +0 -1
- package/dist/chunk-F2IMUDXZ.js.map +0 -1
- package/dist/chunk-HEHYGYOX.js.map +0 -1
- package/dist/chunk-IM4QE42D.js.map +0 -1
- package/dist/chunk-MX64ZF6I.js.map +0 -1
- package/dist/chunk-SAUPYVLF.js.map +0 -1
- package/dist/chunk-THRPYOFK.js.map +0 -1
- package/dist/chunk-UCQSRW7Z.js.map +0 -1
- package/dist/chunk-VGZZXKBR.js.map +0 -1
- package/dist/chunk-YGPFYGA6.js.map +0 -1
- package/dist/chunk-YHCN776L.js.map +0 -1
- package/src/hooks/__tests__/usePermissionCache.simple.test.ts +0 -192
- package/src/hooks/__tests__/usePermissionCache.unit.test.ts +0 -741
- package/src/hooks/__tests__/usePublicEvent.simple.test.ts +0 -703
- package/src/rbac/hooks/useRBAC.simple.test.ts +0 -95
- package/src/rbac/utils/__tests__/eventContext.unit.test.ts +0 -428
- /package/dist/{DataTable-GUFUNZ3N.js.map → DataTable-ON3IXISJ.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-643PUAIM.js.map → UnifiedAuthProvider-X5NXANVI.js.map} +0 -0
- /package/dist/{api-YP7XD5L6.js.map → api-I6UCQ5S6.js.map} +0 -0
- /package/dist/{chunk-HESYZWZW.js.map → chunk-QWWZ5CAQ.js.map} +0 -0
package/src/rbac/api.ts
CHANGED
|
@@ -29,6 +29,8 @@ import { SecurityContext } from './security';
|
|
|
29
29
|
import { createLogger } from '../utils/core/logger';
|
|
30
30
|
import { enablePerformanceMonitoring } from './performance';
|
|
31
31
|
import { getOrCreateRequest } from './request-deduplication';
|
|
32
|
+
import { ContextValidator } from './utils/contextValidator';
|
|
33
|
+
import type { AppConfig } from './utils/contextValidator';
|
|
32
34
|
|
|
33
35
|
const log = createLogger('RBACAPI');
|
|
34
36
|
|
|
@@ -108,6 +110,8 @@ function getEngine(): RBACEngine {
|
|
|
108
110
|
* Get user's access level in a scope
|
|
109
111
|
*
|
|
110
112
|
* @param input - Access level input
|
|
113
|
+
* @param appConfig - Optional app configuration
|
|
114
|
+
* @param appName - Optional app name
|
|
111
115
|
* @returns Promise resolving to access level
|
|
112
116
|
*
|
|
113
117
|
* @example
|
|
@@ -118,18 +122,55 @@ function getEngine(): RBACEngine {
|
|
|
118
122
|
* });
|
|
119
123
|
* ```
|
|
120
124
|
*/
|
|
121
|
-
export async function getAccessLevel(
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
+
export async function getAccessLevel(
|
|
126
|
+
input: {
|
|
127
|
+
userId: UUID;
|
|
128
|
+
scope: Scope;
|
|
129
|
+
},
|
|
130
|
+
appConfig?: AppConfig | null,
|
|
131
|
+
appName?: string
|
|
132
|
+
): Promise<AccessLevel> {
|
|
125
133
|
const engine = getEngine();
|
|
126
|
-
|
|
134
|
+
|
|
135
|
+
// Check super admin status first - super admins bypass context requirements
|
|
136
|
+
const isSuperAdminUser = await engine['checkSuperAdmin'](input.userId);
|
|
137
|
+
if (isSuperAdminUser) {
|
|
138
|
+
return 'super';
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Fetch app config if not provided
|
|
142
|
+
let resolvedAppConfig: AppConfig | null = appConfig ?? null;
|
|
143
|
+
let resolvedAppName = appName;
|
|
144
|
+
|
|
145
|
+
if (!resolvedAppConfig && input.scope.appId) {
|
|
146
|
+
resolvedAppConfig = await getAppConfig(input.scope.appId);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Validate context using ContextValidator
|
|
150
|
+
const validation = await ContextValidator.resolveRequiredContext(
|
|
151
|
+
input.scope,
|
|
152
|
+
resolvedAppConfig,
|
|
153
|
+
resolvedAppName,
|
|
154
|
+
engine['supabase']
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
if (!validation.isValid || !validation.resolvedScope) {
|
|
158
|
+
throw validation.error || new OrganisationContextRequiredError();
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Use resolved scope
|
|
162
|
+
return engine.getAccessLevel({
|
|
163
|
+
...input,
|
|
164
|
+
scope: validation.resolvedScope
|
|
165
|
+
});
|
|
127
166
|
}
|
|
128
167
|
|
|
129
168
|
/**
|
|
130
169
|
* Get user's permission map for a scope
|
|
131
170
|
*
|
|
132
171
|
* @param input - Permission map input
|
|
172
|
+
* @param appConfig - Optional app configuration
|
|
173
|
+
* @param appName - Optional app name
|
|
133
174
|
* @returns Promise resolving to permission map
|
|
134
175
|
*
|
|
135
176
|
* @example
|
|
@@ -144,12 +185,41 @@ export async function getAccessLevel(input: {
|
|
|
144
185
|
* });
|
|
145
186
|
* ```
|
|
146
187
|
*/
|
|
147
|
-
export async function getPermissionMap(
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
188
|
+
export async function getPermissionMap(
|
|
189
|
+
input: {
|
|
190
|
+
userId: UUID;
|
|
191
|
+
scope: Scope;
|
|
192
|
+
},
|
|
193
|
+
appConfig?: AppConfig | null,
|
|
194
|
+
appName?: string
|
|
195
|
+
): Promise<PermissionMap> {
|
|
151
196
|
const engine = getEngine();
|
|
152
|
-
|
|
197
|
+
|
|
198
|
+
// Fetch app config if not provided
|
|
199
|
+
let resolvedAppConfig: AppConfig | null = appConfig ?? null;
|
|
200
|
+
let resolvedAppName = appName;
|
|
201
|
+
|
|
202
|
+
if (!resolvedAppConfig && input.scope.appId) {
|
|
203
|
+
resolvedAppConfig = await getAppConfig(input.scope.appId);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Validate context using ContextValidator
|
|
207
|
+
const validation = await ContextValidator.resolveRequiredContext(
|
|
208
|
+
input.scope,
|
|
209
|
+
resolvedAppConfig,
|
|
210
|
+
resolvedAppName,
|
|
211
|
+
engine['supabase']
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
if (!validation.isValid || !validation.resolvedScope) {
|
|
215
|
+
throw validation.error || new OrganisationContextRequiredError();
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Use resolved scope
|
|
219
|
+
return engine.getPermissionMap({
|
|
220
|
+
...input,
|
|
221
|
+
scope: validation.resolvedScope
|
|
222
|
+
});
|
|
153
223
|
}
|
|
154
224
|
|
|
155
225
|
export async function resolveAppContext(input: {
|
|
@@ -160,18 +230,49 @@ export async function resolveAppContext(input: {
|
|
|
160
230
|
return engine.resolveAppContext(input);
|
|
161
231
|
}
|
|
162
232
|
|
|
163
|
-
export async function getRoleContext(
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
233
|
+
export async function getRoleContext(
|
|
234
|
+
input: {
|
|
235
|
+
userId: UUID;
|
|
236
|
+
scope: Scope;
|
|
237
|
+
},
|
|
238
|
+
appConfig?: AppConfig | null,
|
|
239
|
+
appName?: string
|
|
240
|
+
): Promise<RBACRoleContext> {
|
|
167
241
|
const engine = getEngine();
|
|
168
|
-
|
|
242
|
+
|
|
243
|
+
// Fetch app config if not provided
|
|
244
|
+
let resolvedAppConfig: AppConfig | null = appConfig ?? null;
|
|
245
|
+
let resolvedAppName = appName;
|
|
246
|
+
|
|
247
|
+
if (!resolvedAppConfig && input.scope.appId) {
|
|
248
|
+
resolvedAppConfig = await getAppConfig(input.scope.appId);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Validate context using ContextValidator
|
|
252
|
+
const validation = await ContextValidator.resolveRequiredContext(
|
|
253
|
+
input.scope,
|
|
254
|
+
resolvedAppConfig,
|
|
255
|
+
resolvedAppName,
|
|
256
|
+
engine['supabase']
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
if (!validation.isValid || !validation.resolvedScope) {
|
|
260
|
+
throw validation.error || new OrganisationContextRequiredError();
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Use resolved scope
|
|
264
|
+
return engine.getRoleContext({
|
|
265
|
+
...input,
|
|
266
|
+
scope: validation.resolvedScope
|
|
267
|
+
});
|
|
169
268
|
}
|
|
170
269
|
|
|
171
270
|
/**
|
|
172
271
|
* Check if user has a specific permission
|
|
173
272
|
*
|
|
174
273
|
* @param input - Permission check input
|
|
274
|
+
* @param appConfig - Optional app configuration (if not provided, will be fetched)
|
|
275
|
+
* @param appName - Optional app name (for PORTAL/ADMIN special case and config lookup)
|
|
175
276
|
* @returns Promise resolving to permission result
|
|
176
277
|
*
|
|
177
278
|
* @example
|
|
@@ -184,24 +285,68 @@ export async function getRoleContext(input: {
|
|
|
184
285
|
* });
|
|
185
286
|
* ```
|
|
186
287
|
*/
|
|
187
|
-
export async function isPermitted(
|
|
288
|
+
export async function isPermitted(
|
|
289
|
+
input: PermissionCheck,
|
|
290
|
+
appConfig?: AppConfig | null,
|
|
291
|
+
appName?: string
|
|
292
|
+
): Promise<boolean> {
|
|
188
293
|
const engine = getEngine();
|
|
189
294
|
|
|
190
|
-
//
|
|
191
|
-
|
|
192
|
-
|
|
295
|
+
// Fetch app config if not provided and we have appId
|
|
296
|
+
let resolvedAppConfig: AppConfig | null = appConfig ?? null;
|
|
297
|
+
let resolvedAppName = appName;
|
|
298
|
+
|
|
299
|
+
if (!resolvedAppConfig && input.scope.appId) {
|
|
300
|
+
resolvedAppConfig = await getAppConfig(input.scope.appId);
|
|
193
301
|
}
|
|
194
302
|
|
|
195
|
-
//
|
|
196
|
-
|
|
303
|
+
// If we have appId but no appName, try to get it from the database
|
|
304
|
+
if (!resolvedAppName && input.scope.appId) {
|
|
305
|
+
try {
|
|
306
|
+
const { data } = await engine['supabase']
|
|
307
|
+
.from('rbac_apps')
|
|
308
|
+
.select('name')
|
|
309
|
+
.eq('id', input.scope.appId)
|
|
310
|
+
.eq('is_active', true)
|
|
311
|
+
.single() as { data: { name: string } | null };
|
|
312
|
+
if (data) {
|
|
313
|
+
resolvedAppName = data.name;
|
|
314
|
+
}
|
|
315
|
+
} catch (err) {
|
|
316
|
+
// Ignore errors - appName is optional
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Validate context using ContextValidator
|
|
321
|
+
const validation = await ContextValidator.resolveRequiredContext(
|
|
322
|
+
input.scope,
|
|
323
|
+
resolvedAppConfig,
|
|
324
|
+
resolvedAppName,
|
|
325
|
+
engine['supabase']
|
|
326
|
+
);
|
|
327
|
+
|
|
328
|
+
if (!validation.isValid || !validation.resolvedScope) {
|
|
329
|
+
throw validation.error || new OrganisationContextRequiredError();
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Use resolved scope for permission check
|
|
333
|
+
const validatedScope = validation.resolvedScope;
|
|
334
|
+
|
|
335
|
+
// Create security context from validated scope
|
|
197
336
|
const securityContext: SecurityContext = {
|
|
198
337
|
userId: input.userId,
|
|
199
|
-
organisationId:
|
|
338
|
+
organisationId: validatedScope.organisationId || null,
|
|
200
339
|
timestamp: new Date(),
|
|
201
340
|
// Optional fields can be omitted
|
|
202
341
|
};
|
|
203
342
|
|
|
204
|
-
|
|
343
|
+
// Create new input with validated scope
|
|
344
|
+
const validatedInput: PermissionCheck = {
|
|
345
|
+
...input,
|
|
346
|
+
scope: validatedScope
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
return engine.isPermitted(validatedInput, securityContext);
|
|
205
350
|
}
|
|
206
351
|
|
|
207
352
|
/**
|
|
@@ -211,15 +356,21 @@ export async function isPermitted(input: PermissionCheck): Promise<boolean> {
|
|
|
211
356
|
* and checks cache before making new requests. Uses session cache for page-level checks.
|
|
212
357
|
*
|
|
213
358
|
* @param input - Permission check input
|
|
359
|
+
* @param appConfig - Optional app configuration
|
|
360
|
+
* @param appName - Optional app name
|
|
214
361
|
* @returns Promise resolving to permission result
|
|
215
362
|
*/
|
|
216
|
-
export async function isPermittedCached(
|
|
363
|
+
export async function isPermittedCached(
|
|
364
|
+
input: PermissionCheck,
|
|
365
|
+
appConfig?: AppConfig | null,
|
|
366
|
+
appName?: string
|
|
367
|
+
): Promise<boolean> {
|
|
217
368
|
const { userId, scope, permission, pageId } = input;
|
|
218
369
|
|
|
219
370
|
// Check cache first (checks both short-term and session cache)
|
|
220
371
|
const cacheKey = RBACCache.generatePermissionKey({
|
|
221
372
|
userId,
|
|
222
|
-
organisationId: scope.organisationId
|
|
373
|
+
organisationId: scope.organisationId,
|
|
223
374
|
eventId: scope.eventId,
|
|
224
375
|
appId: scope.appId,
|
|
225
376
|
permission,
|
|
@@ -233,8 +384,8 @@ export async function isPermittedCached(input: PermissionCheck): Promise<boolean
|
|
|
233
384
|
|
|
234
385
|
// Use request deduplication - if same request is in-flight, share the promise
|
|
235
386
|
return getOrCreateRequest(input, async (checkInput) => {
|
|
236
|
-
// Check permission
|
|
237
|
-
const result = await isPermitted(checkInput);
|
|
387
|
+
// Check permission with context validation
|
|
388
|
+
const result = await isPermitted(checkInput, appConfig, appName);
|
|
238
389
|
|
|
239
390
|
// Determine if this is a page-level check (has pageId or permission contains 'page.')
|
|
240
391
|
const isPageLevelCheck = !!pageId || permission.includes('page.');
|
|
@@ -329,33 +480,86 @@ export async function isSuperAdmin(userId: UUID): Promise<boolean> {
|
|
|
329
480
|
* @param appId - App ID
|
|
330
481
|
* @returns Promise resolving to app configuration
|
|
331
482
|
*/
|
|
332
|
-
export async function getAppConfig(appId: UUID): Promise<
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
483
|
+
export async function getAppConfig(appId: UUID): Promise<AppConfig | null> {
|
|
484
|
+
try {
|
|
485
|
+
const engine = getEngine();
|
|
486
|
+
return getAppConfigWithClient(engine['supabase'], appId);
|
|
487
|
+
} catch (err) {
|
|
488
|
+
// RBAC not initialized - return null gracefully
|
|
489
|
+
if (err instanceof RBACNotInitializedError) {
|
|
490
|
+
return null;
|
|
491
|
+
}
|
|
492
|
+
throw err;
|
|
493
|
+
}
|
|
337
494
|
}
|
|
338
495
|
|
|
339
|
-
export async function getAppConfigWithClient(client: SupabaseClient, appId: UUID): Promise<
|
|
496
|
+
export async function getAppConfigWithClient(client: SupabaseClient | null | undefined, appId: UUID): Promise<AppConfig | null> {
|
|
497
|
+
// Return null if client is not available
|
|
498
|
+
if (!client) {
|
|
499
|
+
return null;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// Cache key for app config - cache for 5 minutes (app config rarely changes)
|
|
503
|
+
const cacheKey = `app_config:${appId}`;
|
|
504
|
+
|
|
505
|
+
// Check cache first
|
|
506
|
+
const cached = rbacCache.get<AppConfig>(cacheKey, true);
|
|
507
|
+
if (cached !== null) {
|
|
508
|
+
return cached;
|
|
509
|
+
}
|
|
510
|
+
|
|
340
511
|
try {
|
|
341
512
|
const { data, error } = await client
|
|
342
513
|
.from('rbac_apps')
|
|
343
|
-
.select('requires_event')
|
|
514
|
+
.select('requires_event, name')
|
|
344
515
|
.eq('id', appId)
|
|
345
516
|
.eq('is_active', true)
|
|
346
|
-
.single() as { data: { requires_event: boolean } | null; error: any };
|
|
517
|
+
.single() as { data: { requires_event: boolean; name: string } | null; error: any };
|
|
347
518
|
|
|
348
519
|
if (error || !data) {
|
|
349
520
|
return null;
|
|
350
521
|
}
|
|
351
522
|
|
|
352
|
-
|
|
523
|
+
const appConfig: AppConfig = { requires_event: data.requires_event ?? false };
|
|
524
|
+
|
|
525
|
+
// Cache the result for 5 minutes (300000ms) with session cache enabled
|
|
526
|
+
// App config rarely changes, so we can cache it longer
|
|
527
|
+
rbacCache.set(cacheKey, appConfig, 5 * 60 * 1000, true);
|
|
528
|
+
|
|
529
|
+
return appConfig;
|
|
353
530
|
} catch (err) {
|
|
354
531
|
log.error('Error fetching app config:', err);
|
|
355
532
|
return null;
|
|
356
533
|
}
|
|
357
534
|
}
|
|
358
535
|
|
|
536
|
+
/**
|
|
537
|
+
* Get app configuration by app name
|
|
538
|
+
*
|
|
539
|
+
* @param appName - App name
|
|
540
|
+
* @returns Promise resolving to app configuration
|
|
541
|
+
*/
|
|
542
|
+
export async function getAppConfigByName(appName: string): Promise<AppConfig | null> {
|
|
543
|
+
const engine = getEngine();
|
|
544
|
+
try {
|
|
545
|
+
const { data, error } = await engine['supabase']
|
|
546
|
+
.from('rbac_apps')
|
|
547
|
+
.select('requires_event, name')
|
|
548
|
+
.eq('name', appName)
|
|
549
|
+
.eq('is_active', true)
|
|
550
|
+
.single() as { data: { requires_event: boolean; name: string } | null; error: any };
|
|
551
|
+
|
|
552
|
+
if (error || !data) {
|
|
553
|
+
return null;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
return { requires_event: data.requires_event ?? false };
|
|
557
|
+
} catch (err) {
|
|
558
|
+
log.error('Error fetching app config by name:', err);
|
|
559
|
+
return null;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
359
563
|
/**
|
|
360
564
|
* Check if user is organisation admin
|
|
361
565
|
*
|
|
@@ -178,22 +178,36 @@ export class RBACCacheInvalidationManager {
|
|
|
178
178
|
});
|
|
179
179
|
}
|
|
180
180
|
|
|
181
|
+
/**
|
|
182
|
+
* Cleanup subscriptions only (not all callbacks)
|
|
183
|
+
* Used internally to cleanup before setting up new subscriptions
|
|
184
|
+
*/
|
|
185
|
+
private cleanupSubscriptions(): void {
|
|
186
|
+
this.channels.forEach(channel => {
|
|
187
|
+
try {
|
|
188
|
+
if (channel && typeof channel.unsubscribe === 'function') {
|
|
189
|
+
channel.unsubscribe();
|
|
190
|
+
}
|
|
191
|
+
} catch (error) {
|
|
192
|
+
log.warn('Failed to unsubscribe from channel:', error);
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
this.channels = [];
|
|
196
|
+
}
|
|
197
|
+
|
|
181
198
|
/**
|
|
182
199
|
* Setup realtime subscriptions for automatic cache invalidation
|
|
183
|
-
*
|
|
200
|
+
* Always cleans up existing subscriptions before setting up new ones to prevent duplicates
|
|
184
201
|
*/
|
|
185
202
|
private setupRealtimeSubscriptions(): void {
|
|
203
|
+
// Always cleanup existing subscriptions first to prevent duplicates
|
|
204
|
+
this.cleanupSubscriptions();
|
|
205
|
+
|
|
186
206
|
// Check if realtime is available (skip in test environments)
|
|
187
207
|
if (!this.supabase.channel || typeof this.supabase.channel !== 'function') {
|
|
188
208
|
log.debug('Realtime not available, skipping subscriptions');
|
|
189
209
|
return;
|
|
190
210
|
}
|
|
191
|
-
|
|
192
|
-
// Prevent duplicate subscriptions - if channels already exist, skip setup
|
|
193
|
-
if (this.channels.length > 0) {
|
|
194
|
-
log.debug('Realtime subscriptions already set up, skipping duplicate setup');
|
|
195
|
-
return;
|
|
196
|
-
}
|
|
197
211
|
|
|
198
212
|
// Subscribe to organisation role changes
|
|
199
213
|
const orgRolesChannel = this.supabase
|
|
@@ -111,7 +111,7 @@ export const supabase = createClient(url, key);
|
|
|
111
111
|
// src/lib/supabase.ts
|
|
112
112
|
export const supabase = createClient(
|
|
113
113
|
import.meta.env.VITE_SUPABASE_URL,
|
|
114
|
-
import.meta.env.
|
|
114
|
+
import.meta.env.VITE_SUPABASE_PUBLISHABLE_KEY
|
|
115
115
|
);
|
|
116
116
|
|
|
117
117
|
// src/utils/api.ts
|
|
@@ -67,8 +67,8 @@
|
|
|
67
67
|
import React, { useMemo, useCallback, useEffect, useState } from 'react';
|
|
68
68
|
import { useMultiplePermissions } from '../hooks/usePermissions';
|
|
69
69
|
import { useUnifiedAuth } from '../../providers/services/UnifiedAuthProvider';
|
|
70
|
+
import { useResolvedScope } from '../hooks/useResolvedScope';
|
|
70
71
|
import { UUID, Permission, Scope } from '../types';
|
|
71
|
-
import { createScopeFromEvent } from '../utils/eventContext';
|
|
72
72
|
import { NavigationItem } from './NavigationProvider';
|
|
73
73
|
import { getRBACLogger } from '../config';
|
|
74
74
|
|
|
@@ -124,66 +124,28 @@ export function NavigationGuard({
|
|
|
124
124
|
}: NavigationGuardProps) {
|
|
125
125
|
const { user, selectedOrganisation, selectedEvent, supabase } = useUnifiedAuth();
|
|
126
126
|
const [hasChecked, setHasChecked] = useState(false);
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
// If we have both organisation and event, use them directly
|
|
139
|
-
if (selectedOrganisation && selectedEvent) {
|
|
140
|
-
setResolvedScope({
|
|
141
|
-
organisationId: selectedOrganisation.id,
|
|
142
|
-
eventId: selectedEvent.event_id,
|
|
143
|
-
appId: undefined
|
|
144
|
-
});
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// If we only have organisation, use it
|
|
149
|
-
if (selectedOrganisation) {
|
|
150
|
-
setResolvedScope({
|
|
151
|
-
organisationId: selectedOrganisation.id,
|
|
152
|
-
eventId: selectedEvent?.event_id || undefined,
|
|
153
|
-
appId: undefined
|
|
154
|
-
});
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// If we only have event, resolve organisation from event
|
|
159
|
-
if (selectedEvent && supabase) {
|
|
160
|
-
try {
|
|
161
|
-
const eventScope = await createScopeFromEvent(supabase, selectedEvent.event_id);
|
|
162
|
-
if (!eventScope) {
|
|
163
|
-
setCheckError(new Error('Could not resolve organization from event context'));
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
|
-
setResolvedScope(eventScope);
|
|
167
|
-
} catch (error) {
|
|
168
|
-
setCheckError(error as Error);
|
|
169
|
-
}
|
|
170
|
-
return;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// No context available
|
|
174
|
-
setCheckError(new Error('Either organisation context or event context is required for navigation permission checking'));
|
|
175
|
-
};
|
|
176
|
-
|
|
177
|
-
resolveScope();
|
|
178
|
-
}, [scope, selectedOrganisation, selectedEvent, supabase]);
|
|
127
|
+
|
|
128
|
+
// Use useResolvedScope hook for consistent scope resolution
|
|
129
|
+
const { resolvedScope: hookResolvedScope, isLoading: scopeLoading, error: scopeError } = useResolvedScope({
|
|
130
|
+
supabase,
|
|
131
|
+
selectedOrganisationId: selectedOrganisation?.id || null,
|
|
132
|
+
selectedEventId: selectedEvent?.event_id || null
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// Use provided scope if available, otherwise use resolved scope
|
|
136
|
+
const effectiveScope = scope || hookResolvedScope;
|
|
137
|
+
const checkError = scopeError;
|
|
179
138
|
|
|
180
139
|
// Check all permissions using useMultiplePermissions hook
|
|
181
|
-
const { results: permissionResults, isLoading, error } = useMultiplePermissions(
|
|
140
|
+
const { results: permissionResults, isLoading: permissionsLoading, error: permissionsError } = useMultiplePermissions(
|
|
182
141
|
user?.id || '',
|
|
183
|
-
|
|
142
|
+
effectiveScope || { eventId: selectedEvent?.event_id || undefined },
|
|
184
143
|
navigationItem.permissions || [],
|
|
185
144
|
true // Use cache
|
|
186
145
|
);
|
|
146
|
+
|
|
147
|
+
const isLoading = scopeLoading || permissionsLoading;
|
|
148
|
+
const error = checkError || permissionsError;
|
|
187
149
|
|
|
188
150
|
// Determine if user has required permissions based on requireAll prop
|
|
189
151
|
const hasRequiredPermissions = useMemo((): boolean => {
|
|
@@ -202,13 +164,11 @@ export function NavigationGuard({
|
|
|
202
164
|
useEffect(() => {
|
|
203
165
|
if (!isLoading && !error) {
|
|
204
166
|
setHasChecked(true);
|
|
205
|
-
setCheckError(null);
|
|
206
167
|
|
|
207
168
|
if (!hasRequiredPermissions && onDenied) {
|
|
208
169
|
onDenied(navigationItem);
|
|
209
170
|
}
|
|
210
171
|
} else if (error) {
|
|
211
|
-
setCheckError(error);
|
|
212
172
|
setHasChecked(true);
|
|
213
173
|
}
|
|
214
174
|
}, [hasRequiredPermissions, isLoading, error, navigationItem, onDenied]);
|
|
@@ -221,13 +181,13 @@ export function NavigationGuard({
|
|
|
221
181
|
navigationItem: navigationItem.id,
|
|
222
182
|
permissions: navigationItem.permissions,
|
|
223
183
|
userId: user?.id,
|
|
224
|
-
scope:
|
|
184
|
+
scope: effectiveScope,
|
|
225
185
|
allowed: hasRequiredPermissions,
|
|
226
186
|
requireAll,
|
|
227
187
|
timestamp: new Date().toISOString()
|
|
228
188
|
});
|
|
229
189
|
}
|
|
230
|
-
}, [auditLog, hasChecked, isLoading, navigationItem, user?.id,
|
|
190
|
+
}, [auditLog, hasChecked, isLoading, navigationItem, user?.id, effectiveScope, hasRequiredPermissions, requireAll]);
|
|
231
191
|
|
|
232
192
|
// Handle strict mode violations
|
|
233
193
|
useEffect(() => {
|
|
@@ -237,15 +197,15 @@ export function NavigationGuard({
|
|
|
237
197
|
navigationItem: navigationItem.id,
|
|
238
198
|
permissions: navigationItem.permissions,
|
|
239
199
|
userId: user?.id,
|
|
240
|
-
scope:
|
|
200
|
+
scope: effectiveScope,
|
|
241
201
|
requireAll,
|
|
242
202
|
timestamp: new Date().toISOString()
|
|
243
203
|
});
|
|
244
204
|
}
|
|
245
|
-
}, [strictMode, hasChecked, isLoading, hasRequiredPermissions, navigationItem, user?.id,
|
|
205
|
+
}, [strictMode, hasChecked, isLoading, hasRequiredPermissions, navigationItem, user?.id, effectiveScope, requireAll]);
|
|
246
206
|
|
|
247
207
|
// Show loading state
|
|
248
|
-
if (isLoading || !
|
|
208
|
+
if (isLoading || !effectiveScope || !hasChecked) {
|
|
249
209
|
return <>{loading}</>;
|
|
250
210
|
}
|
|
251
211
|
|
|
@@ -288,7 +248,7 @@ function DefaultLoading() {
|
|
|
288
248
|
return (
|
|
289
249
|
<div className="flex items-center justify-center p-2">
|
|
290
250
|
<div className="flex items-center space-x-2">
|
|
291
|
-
<div className="animate-spin rounded-full
|
|
251
|
+
<div className="animate-spin rounded-full size-4 border-b-2 border-main-600"></div>
|
|
292
252
|
<span className="text-sm text-sec-600">Checking...</span>
|
|
293
253
|
</div>
|
|
294
254
|
</div>
|