@memberjunction/server 6.2.0-edge.5 → 6.2.0-edge.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -1
- package/dist/.tsbuildinfo +1 -1
- package/dist/agentSessions/SessionJanitor.d.ts +3 -3
- package/dist/agentSessions/SessionJanitor.js +3 -3
- package/dist/agentSessions/SessionManager.d.ts +15 -5
- package/dist/agentSessions/SessionManager.d.ts.map +1 -1
- package/dist/agentSessions/SessionManager.js +34 -12
- package/dist/agentSessions/SessionManager.js.map +1 -1
- package/dist/auth/AuthProviderCatalogRouter.d.ts.map +1 -1
- package/dist/auth/AuthProviderCatalogRouter.js +2 -0
- package/dist/auth/AuthProviderCatalogRouter.js.map +1 -1
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +51 -10
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/initializeProviders.d.ts +10 -0
- package/dist/auth/initializeProviders.d.ts.map +1 -1
- package/dist/auth/initializeProviders.js +37 -1
- package/dist/auth/initializeProviders.js.map +1 -1
- package/dist/auth/localDevAuth.d.ts +79 -0
- package/dist/auth/localDevAuth.d.ts.map +1 -0
- package/dist/auth/localDevAuth.js +439 -0
- package/dist/auth/localDevAuth.js.map +1 -0
- package/dist/auth/magicLink/MagicLinkKeys.d.ts +2 -2
- package/dist/auth/magicLink/MagicLinkKeys.d.ts.map +1 -1
- package/dist/auth/magicLink/MagicLinkKeys.js +1 -1
- package/dist/auth/magicLink/MagicLinkKeys.js.map +1 -1
- package/dist/auth/magicLink/types.d.ts +12 -0
- package/dist/auth/magicLink/types.d.ts.map +1 -1
- package/dist/auth/sessionUserClone.d.ts +1 -1
- package/dist/auth/sessionUserClone.d.ts.map +1 -1
- package/dist/auth/sessionUserClone.js +3 -2
- package/dist/auth/sessionUserClone.js.map +1 -1
- package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.d.ts +292 -0
- package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.d.ts.map +1 -0
- package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.js +442 -0
- package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.js.map +1 -0
- package/dist/broadcast/index.d.ts +6 -0
- package/dist/broadcast/index.d.ts.map +1 -0
- package/dist/broadcast/index.js +6 -0
- package/dist/broadcast/index.js.map +1 -0
- package/dist/broadcast/providers/AWSIoTCoreBroadcastProvider.d.ts +43 -0
- package/dist/broadcast/providers/AWSIoTCoreBroadcastProvider.d.ts.map +1 -0
- package/dist/broadcast/providers/AWSIoTCoreBroadcastProvider.js +144 -0
- package/dist/broadcast/providers/AWSIoTCoreBroadcastProvider.js.map +1 -0
- package/dist/broadcast/providers/AzureWebPubSubBroadcastProvider.d.ts +46 -0
- package/dist/broadcast/providers/AzureWebPubSubBroadcastProvider.d.ts.map +1 -0
- package/dist/broadcast/providers/AzureWebPubSubBroadcastProvider.js +149 -0
- package/dist/broadcast/providers/AzureWebPubSubBroadcastProvider.js.map +1 -0
- package/dist/broadcast/providers/GCPPubSubBroadcastProvider.d.ts +49 -0
- package/dist/broadcast/providers/GCPPubSubBroadcastProvider.d.ts.map +1 -0
- package/dist/broadcast/providers/GCPPubSubBroadcastProvider.js +131 -0
- package/dist/broadcast/providers/GCPPubSubBroadcastProvider.js.map +1 -0
- package/dist/broadcast/providers/SequencedRoleBucketBroadcastProvider.d.ts +24 -0
- package/dist/broadcast/providers/SequencedRoleBucketBroadcastProvider.d.ts.map +1 -0
- package/dist/broadcast/providers/SequencedRoleBucketBroadcastProvider.js +51 -0
- package/dist/broadcast/providers/SequencedRoleBucketBroadcastProvider.js.map +1 -0
- package/dist/cache.d.ts +3 -2
- package/dist/cache.d.ts.map +1 -1
- package/dist/cache.js.map +1 -1
- package/dist/config.d.ts +496 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +61 -0
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts +3 -1
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +29 -8
- package/dist/context.js.map +1 -1
- package/dist/coreRoutes.d.ts.map +1 -1
- package/dist/coreRoutes.js +14 -0
- package/dist/coreRoutes.js.map +1 -1
- package/dist/generated/graphql-schemas/__mj.d.ts +635 -14
- package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
- package/dist/generated/graphql-schemas/__mj.js +3366 -418
- package/dist/generated/graphql-schemas/__mj.js.map +1 -1
- package/dist/generic/CacheInvalidationResolver.d.ts +50 -30
- package/dist/generic/CacheInvalidationResolver.d.ts.map +1 -1
- package/dist/generic/CacheInvalidationResolver.js +154 -79
- package/dist/generic/CacheInvalidationResolver.js.map +1 -1
- package/dist/generic/OrphanedConversationDetailReconciler.d.ts.map +1 -1
- package/dist/generic/OrphanedConversationDetailReconciler.js +2 -0
- package/dist/generic/OrphanedConversationDetailReconciler.js.map +1 -1
- package/dist/generic/PubSubManager.d.ts +10 -0
- package/dist/generic/PubSubManager.d.ts.map +1 -1
- package/dist/generic/PubSubManager.js +44 -1
- package/dist/generic/PubSubManager.js.map +1 -1
- package/dist/generic/PushStatusResolver.d.ts +39 -6
- package/dist/generic/PushStatusResolver.d.ts.map +1 -1
- package/dist/generic/PushStatusResolver.js +93 -11
- package/dist/generic/PushStatusResolver.js.map +1 -1
- package/dist/generic/ResolverBase.d.ts +65 -25
- package/dist/generic/ResolverBase.d.ts.map +1 -1
- package/dist/generic/ResolverBase.js +201 -101
- package/dist/generic/ResolverBase.js.map +1 -1
- package/dist/generic/RunViewResolver.d.ts +11 -3
- package/dist/generic/RunViewResolver.d.ts.map +1 -1
- package/dist/generic/RunViewResolver.js +79 -13
- package/dist/generic/RunViewResolver.js.map +1 -1
- package/dist/generic/actionOutputFieldSecurity.d.ts +26 -0
- package/dist/generic/actionOutputFieldSecurity.d.ts.map +1 -0
- package/dist/generic/actionOutputFieldSecurity.js +47 -0
- package/dist/generic/actionOutputFieldSecurity.js.map +1 -0
- package/dist/generic/fieldSecurityTransport.d.ts +30 -0
- package/dist/generic/fieldSecurityTransport.d.ts.map +1 -0
- package/dist/generic/fieldSecurityTransport.js +30 -0
- package/dist/generic/fieldSecurityTransport.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +122 -66
- package/dist/index.js.map +1 -1
- package/dist/integration/CustomColumnPromoter.d.ts.map +1 -1
- package/dist/integration/CustomColumnPromoter.js +6 -1
- package/dist/integration/CustomColumnPromoter.js.map +1 -1
- package/dist/logging/variablesLoggingMiddleware.d.ts +12 -0
- package/dist/logging/variablesLoggingMiddleware.d.ts.map +1 -1
- package/dist/logging/variablesLoggingMiddleware.js +50 -1
- package/dist/logging/variablesLoggingMiddleware.js.map +1 -1
- package/dist/realtimeSessions/SessionVerificationPortsBase.d.ts.map +1 -1
- package/dist/realtimeSessions/SessionVerificationPortsBase.js +1 -0
- package/dist/realtimeSessions/SessionVerificationPortsBase.js.map +1 -1
- package/dist/realtimeSessions/ownedRealtimeSession.d.ts +8 -6
- package/dist/realtimeSessions/ownedRealtimeSession.d.ts.map +1 -1
- package/dist/realtimeSessions/ownedRealtimeSession.js +15 -6
- package/dist/realtimeSessions/ownedRealtimeSession.js.map +1 -1
- package/dist/realtimeSessions/sessionAccess.d.ts +88 -11
- package/dist/realtimeSessions/sessionAccess.d.ts.map +1 -1
- package/dist/realtimeSessions/sessionAccess.js +125 -22
- package/dist/realtimeSessions/sessionAccess.js.map +1 -1
- package/dist/realtimeSessions/sessionConfigStamps.js +1 -1
- package/dist/realtimeSessions/sessionConfigStamps.js.map +1 -1
- package/dist/realtimeSessions/subscriptionAuthorization.d.ts.map +1 -1
- package/dist/realtimeSessions/subscriptionAuthorization.js +5 -2
- package/dist/realtimeSessions/subscriptionAuthorization.js.map +1 -1
- package/dist/realtimeSessions/verificationWorkflow.d.ts +2 -0
- package/dist/realtimeSessions/verificationWorkflow.d.ts.map +1 -1
- package/dist/realtimeSessions/verificationWorkflow.js +1 -1
- package/dist/realtimeSessions/verificationWorkflow.js.map +1 -1
- package/dist/realtimeWidget/WidgetRouter.d.ts.map +1 -1
- package/dist/realtimeWidget/WidgetRouter.js +19 -3
- package/dist/realtimeWidget/WidgetRouter.js.map +1 -1
- package/dist/realtimeWidget/WidgetSessionService.d.ts +67 -7
- package/dist/realtimeWidget/WidgetSessionService.d.ts.map +1 -1
- package/dist/realtimeWidget/WidgetSessionService.js +201 -19
- package/dist/realtimeWidget/WidgetSessionService.js.map +1 -1
- package/dist/realtimeWidget/host-identity.d.ts +1 -1
- package/dist/realtimeWidget/host-identity.js +1 -1
- package/dist/realtimeWidget/widgetCore.d.ts +20 -1
- package/dist/realtimeWidget/widgetCore.d.ts.map +1 -1
- package/dist/realtimeWidget/widgetCore.js +17 -1
- package/dist/realtimeWidget/widgetCore.js.map +1 -1
- package/dist/realtimeWidget/widgetGuestElevation.d.ts +1 -1
- package/dist/realtimeWidget/widgetGuestElevation.d.ts.map +1 -1
- package/dist/realtimeWidget/widgetGuestElevation.js +2 -1
- package/dist/realtimeWidget/widgetGuestElevation.js.map +1 -1
- package/dist/resolvers/ActionResolver.d.ts +19 -1
- package/dist/resolvers/ActionResolver.d.ts.map +1 -1
- package/dist/resolvers/ActionResolver.js +63 -12
- package/dist/resolvers/ActionResolver.js.map +1 -1
- package/dist/resolvers/AdhocQueryResolver.d.ts.map +1 -1
- package/dist/resolvers/AdhocQueryResolver.js +1 -0
- package/dist/resolvers/AdhocQueryResolver.js.map +1 -1
- package/dist/resolvers/AgentRunStatusPublisher.d.ts +18 -4
- package/dist/resolvers/AgentRunStatusPublisher.d.ts.map +1 -1
- package/dist/resolvers/AgentRunStatusPublisher.js +32 -8
- package/dist/resolvers/AgentRunStatusPublisher.js.map +1 -1
- package/dist/resolvers/AgentSessionResolver.d.ts +10 -4
- package/dist/resolvers/AgentSessionResolver.d.ts.map +1 -1
- package/dist/resolvers/AgentSessionResolver.js +40 -17
- package/dist/resolvers/AgentSessionResolver.js.map +1 -1
- package/dist/resolvers/EntityCommunicationsResolver.d.ts +13 -1
- package/dist/resolvers/EntityCommunicationsResolver.d.ts.map +1 -1
- package/dist/resolvers/EntityCommunicationsResolver.js +50 -14
- package/dist/resolvers/EntityCommunicationsResolver.js.map +1 -1
- package/dist/resolvers/FetchEntityVectorsResolver.d.ts +28 -0
- package/dist/resolvers/FetchEntityVectorsResolver.d.ts.map +1 -1
- package/dist/resolvers/FetchEntityVectorsResolver.js +117 -21
- package/dist/resolvers/FetchEntityVectorsResolver.js.map +1 -1
- package/dist/resolvers/FileCategoryResolver.d.ts.map +1 -1
- package/dist/resolvers/FileCategoryResolver.js +1 -0
- package/dist/resolvers/FileCategoryResolver.js.map +1 -1
- package/dist/resolvers/FileResolver.d.ts +5 -0
- package/dist/resolvers/FileResolver.d.ts.map +1 -1
- package/dist/resolvers/FileResolver.js +13 -4
- package/dist/resolvers/FileResolver.js.map +1 -1
- package/dist/resolvers/IntegrationDiscoveryResolver.d.ts.map +1 -1
- package/dist/resolvers/IntegrationDiscoveryResolver.js +45 -7
- package/dist/resolvers/IntegrationDiscoveryResolver.js.map +1 -1
- package/dist/resolvers/MergeRecordsResolver.d.ts +8 -1
- package/dist/resolvers/MergeRecordsResolver.d.ts.map +1 -1
- package/dist/resolvers/MergeRecordsResolver.js +31 -3
- package/dist/resolvers/MergeRecordsResolver.js.map +1 -1
- package/dist/resolvers/PipelineProgressResolver.d.ts +6 -0
- package/dist/resolvers/PipelineProgressResolver.d.ts.map +1 -1
- package/dist/resolvers/PipelineProgressResolver.js +5 -3
- package/dist/resolvers/PipelineProgressResolver.js.map +1 -1
- package/dist/resolvers/QueryResolver.d.ts.map +1 -1
- package/dist/resolvers/QueryResolver.js +20 -6
- package/dist/resolvers/QueryResolver.js.map +1 -1
- package/dist/resolvers/QuerySystemUserResolver.d.ts +2 -0
- package/dist/resolvers/QuerySystemUserResolver.d.ts.map +1 -1
- package/dist/resolvers/QuerySystemUserResolver.js +15 -6
- package/dist/resolvers/QuerySystemUserResolver.js.map +1 -1
- package/dist/resolvers/RSUResolver.d.ts.map +1 -1
- package/dist/resolvers/RSUResolver.js +12 -0
- package/dist/resolvers/RSUResolver.js.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts +16 -2
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.js +82 -28
- package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
- package/dist/resolvers/RemoteBrowserActionResolver.d.ts.map +1 -1
- package/dist/resolvers/RemoteBrowserActionResolver.js +11 -9
- package/dist/resolvers/RemoteBrowserActionResolver.js.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.d.ts +56 -8
- package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.js +209 -64
- package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
- package/dist/resolvers/RunTemplateResolver.d.ts.map +1 -1
- package/dist/resolvers/RunTemplateResolver.js +4 -4
- package/dist/resolvers/RunTemplateResolver.js.map +1 -1
- package/dist/resolvers/RunTestResolver.d.ts.map +1 -1
- package/dist/resolvers/RunTestResolver.js +3 -1
- package/dist/resolvers/RunTestResolver.js.map +1 -1
- package/dist/resolvers/SyncDataResolver.d.ts.map +1 -1
- package/dist/resolvers/SyncDataResolver.js +4 -0
- package/dist/resolvers/SyncDataResolver.js.map +1 -1
- package/dist/resolvers/TestQuerySQLResolver.d.ts.map +1 -1
- package/dist/resolvers/TestQuerySQLResolver.js +1 -0
- package/dist/resolvers/TestQuerySQLResolver.js.map +1 -1
- package/dist/resolvers/TransactionGroupResolver.d.ts +17 -2
- package/dist/resolvers/TransactionGroupResolver.d.ts.map +1 -1
- package/dist/resolvers/TransactionGroupResolver.js +60 -15
- package/dist/resolvers/TransactionGroupResolver.js.map +1 -1
- package/dist/resolvers/UserViewResolver.d.ts +1 -1
- package/dist/resolvers/UserViewResolver.d.ts.map +1 -1
- package/dist/resolvers/UserViewResolver.js +2 -1
- package/dist/resolvers/UserViewResolver.js.map +1 -1
- package/dist/rest/EntityCRUDHandler.d.ts +6 -0
- package/dist/rest/EntityCRUDHandler.d.ts.map +1 -1
- package/dist/rest/EntityCRUDHandler.js +19 -5
- package/dist/rest/EntityCRUDHandler.js.map +1 -1
- package/dist/rest/RESTEndpointHandler.d.ts.map +1 -1
- package/dist/rest/RESTEndpointHandler.js +8 -5
- package/dist/rest/RESTEndpointHandler.js.map +1 -1
- package/dist/rest/RealtimeRecordingHandler.d.ts +3 -3
- package/dist/rest/RealtimeRecordingHandler.d.ts.map +1 -1
- package/dist/rest/RealtimeRecordingHandler.js +7 -7
- package/dist/rest/RealtimeRecordingHandler.js.map +1 -1
- package/dist/rest/ViewOperationsHandler.d.ts +6 -0
- package/dist/rest/ViewOperationsHandler.d.ts.map +1 -1
- package/dist/rest/ViewOperationsHandler.js +22 -5
- package/dist/rest/ViewOperationsHandler.js.map +1 -1
- package/dist/serverRoleConfigUnits.d.ts +29 -0
- package/dist/serverRoleConfigUnits.d.ts.map +1 -0
- package/dist/serverRoleConfigUnits.js +33 -0
- package/dist/serverRoleConfigUnits.js.map +1 -0
- package/dist/services/TaskGraphContinuationDeliverer.d.ts +5 -0
- package/dist/services/TaskGraphContinuationDeliverer.d.ts.map +1 -1
- package/dist/services/TaskGraphContinuationDeliverer.js +42 -15
- package/dist/services/TaskGraphContinuationDeliverer.js.map +1 -1
- package/dist/types.d.ts +23 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +98 -98
- package/src/__tests__/ActionResolver.fieldSecurity.test.ts +237 -0
- package/src/__tests__/AdhocQueryResolver.dialect.test.ts +137 -0
- package/src/__tests__/AgentRunStatusPublisher.test.ts +49 -0
- package/src/__tests__/AgentSessionResolver.ownership.test.ts +137 -0
- package/src/__tests__/CustomColumnPromoterOnePass.test.ts +15 -1
- package/src/__tests__/EntityCommunicationsResolver.actingUser.test.ts +151 -0
- package/src/__tests__/EvolutionActsOnWhatItDetects.test.ts +113 -0
- package/src/__tests__/FetchEntityVectorsResolver.test.ts +206 -3
- package/src/__tests__/FileResolver.fieldSecurity.test.ts +82 -0
- package/src/__tests__/PipelineProgressResolver.filter.test.ts +58 -0
- package/src/__tests__/QuerySystemUserResolver.fieldSecurity.test.ts +49 -0
- package/src/__tests__/RESTEndpointHandler.test.ts +25 -0
- package/src/__tests__/RealtimeClientSessionResolver.configTamper.test.ts +23 -3
- package/src/__tests__/RealtimeClientSessionResolver.test.ts +324 -46
- package/src/__tests__/RemoteBrowserActionResolver.ownership.test.ts +98 -0
- package/src/__tests__/ResolverBase.clientOldValues.test.ts +37 -4
- package/src/__tests__/ResolverBase.deleteReturnProjection.test.ts +52 -0
- package/src/__tests__/ResolverBase.entityMessageRelay.test.ts +176 -0
- package/src/__tests__/ResolverBase.includeBinaryFields.test.ts +23 -1
- package/src/__tests__/ResolverBase.publishStatusUpdate.test.ts +68 -0
- package/src/__tests__/ResolverBase.userViewCacheAndDedup.test.ts +130 -0
- package/src/__tests__/RunAIAgentResolver.branch.test.ts +68 -13
- package/src/__tests__/RunAIAgentResolver.conversationWriter.test.ts +210 -0
- package/src/__tests__/RunAIAgentResolver.fireAndForgetCompletion.test.ts +209 -0
- package/src/__tests__/RunAIAgentResolver.historyFrom.test.ts +17 -27
- package/src/__tests__/RunAIAgentResolver.runDataGuard.test.ts +8 -2
- package/src/__tests__/RunAIAgentResolver.serverReply.test.ts +358 -0
- package/src/__tests__/RunAIAgentResolver.stoppedPredecessor.test.ts +4 -1
- package/src/__tests__/RunTemplateResolver.test.ts +35 -2
- package/src/__tests__/RunViewResolver.cacheCheckForwarding.test.ts +11 -0
- package/src/__tests__/SessionManager.test.ts +95 -0
- package/src/__tests__/TaskGraphContinuationDeliverer.test.ts +114 -3
- package/src/__tests__/TransactionGroupResolver.fieldSecurity.test.ts +199 -0
- package/src/__tests__/UserViewResolver.updateWhereClause.test.ts +15 -0
- package/src/__tests__/ViewOperationsHandler.test.ts +7 -2
- package/src/__tests__/WidgetSessionService.test.ts +583 -4
- package/src/__tests__/apiRootBoot.test.ts +1 -1
- package/src/__tests__/buildMagicLinkSessionUser.test.ts +96 -0
- package/src/__tests__/cacheBroadcastProviders.test.ts +403 -0
- package/src/__tests__/cacheInvalidationRecordData.test.ts +45 -0
- package/src/__tests__/fieldSecurityFixtures.ts +91 -0
- package/src/__tests__/globalCors.test.ts +3 -3
- package/src/__tests__/localDevAuth.test.ts +196 -0
- package/src/__tests__/orphanedConversationDetailReconciler.test.ts +10 -0
- package/src/__tests__/pushStatusResolver.filter.test.ts +57 -1
- package/src/__tests__/realtimeRecordingHandler.test.ts +19 -2
- package/src/__tests__/realtimeSessions/RealtimeSessionEventsResolver.test.ts +29 -1
- package/src/__tests__/realtimeSessions/ownedRealtimeSession.anonymous.test.ts +31 -0
- package/src/__tests__/realtimeSessions/ownedRealtimeSession.namedScoped.test.ts +37 -0
- package/src/__tests__/realtimeSessions/sessionAccess.test.ts +146 -7
- package/src/__tests__/realtimeSessions/sessionConfigStamps.test.ts +1 -1
- package/src/__tests__/realtimeSessions/verificationWorkflow.test.ts +25 -4
- package/src/__tests__/resolverBase.cloudEventChangedFields.test.ts +157 -0
- package/src/__tests__/resolverBase.fieldSecurityTransport.test.ts +222 -0
- package/src/__tests__/resolverBase.fls.test.ts +146 -97
- package/src/__tests__/restTransport.fieldSecurity.test.ts +148 -0
- package/src/__tests__/runParamsTimeZone.test.ts +179 -0
- package/src/__tests__/runStreams.fieldSecurity.test.ts +100 -0
- package/src/__tests__/runViewProperties.test.ts +114 -0
- package/src/__tests__/scalabilityBenchmarks.test.ts +765 -0
- package/src/__tests__/scalabilityDatabaseIntegration.test.ts +207 -0
- package/src/__tests__/sessionUserClone.test.ts +5 -1
- package/src/__tests__/startupSweepOrder.test.ts +40 -0
- package/src/__tests__/unifiedAuth.test.ts +28 -0
- package/src/__tests__/widgetGuestElevation.test.ts +2 -0
- package/src/agentSessions/SessionJanitor.ts +3 -3
- package/src/agentSessions/SessionManager.ts +35 -12
- package/src/auth/AuthProviderCatalogRouter.ts +3 -0
- package/src/auth/index.ts +56 -12
- package/src/auth/initializeProviders.ts +43 -1
- package/src/auth/localDevAuth.ts +528 -0
- package/src/auth/magicLink/MagicLinkKeys.ts +2 -2
- package/src/auth/magicLink/types.ts +10 -0
- package/src/auth/sessionUserClone.ts +3 -2
- package/src/broadcast/BaseCacheInvalidationBroadcastProvider.ts +674 -0
- package/src/broadcast/index.ts +5 -0
- package/src/broadcast/providers/AWSIoTCoreBroadcastProvider.ts +180 -0
- package/src/broadcast/providers/AzureWebPubSubBroadcastProvider.ts +182 -0
- package/src/broadcast/providers/GCPPubSubBroadcastProvider.ts +169 -0
- package/src/broadcast/providers/SequencedRoleBucketBroadcastProvider.ts +57 -0
- package/src/cache.ts +2 -1
- package/src/config.ts +69 -0
- package/src/context.ts +32 -8
- package/src/coreRoutes.ts +15 -0
- package/src/generated/graphql-schemas/__mj.ts +2239 -102
- package/src/generic/CacheInvalidationResolver.ts +135 -78
- package/src/generic/OrphanedConversationDetailReconciler.ts +4 -2
- package/src/generic/PubSubManager.ts +54 -1
- package/src/generic/PushStatusResolver.ts +108 -11
- package/src/generic/ResolverBase.ts +237 -107
- package/src/generic/RunViewResolver.ts +80 -15
- package/src/generic/actionOutputFieldSecurity.ts +75 -0
- package/src/generic/fieldSecurityTransport.ts +62 -0
- package/src/index.ts +127 -68
- package/src/integration/CustomColumnPromoter.ts +6 -1
- package/src/logging/variablesLoggingMiddleware.ts +59 -3
- package/src/realtimeSessions/README.md +7 -3
- package/src/realtimeSessions/SessionVerificationPortsBase.ts +1 -0
- package/src/realtimeSessions/ownedRealtimeSession.ts +20 -6
- package/src/realtimeSessions/sessionAccess.ts +181 -25
- package/src/realtimeSessions/sessionConfigStamps.ts +1 -1
- package/src/realtimeSessions/subscriptionAuthorization.ts +4 -2
- package/src/realtimeSessions/verificationWorkflow.ts +3 -1
- package/src/realtimeWidget/WidgetRouter.ts +22 -4
- package/src/realtimeWidget/WidgetSessionService.ts +252 -17
- package/src/realtimeWidget/host-identity.ts +1 -1
- package/src/realtimeWidget/widgetCore.ts +30 -1
- package/src/realtimeWidget/widgetGuestElevation.ts +2 -1
- package/src/resolvers/ActionResolver.ts +78 -15
- package/src/resolvers/AdhocQueryResolver.ts +1 -0
- package/src/resolvers/AgentRunStatusPublisher.ts +36 -8
- package/src/resolvers/AgentSessionResolver.ts +46 -18
- package/src/resolvers/EntityCommunicationsResolver.ts +56 -15
- package/src/resolvers/FetchEntityVectorsResolver.ts +132 -21
- package/src/resolvers/FileCategoryResolver.ts +1 -0
- package/src/resolvers/FileResolver.ts +14 -4
- package/src/resolvers/IntegrationDiscoveryResolver.ts +49 -7
- package/src/resolvers/MergeRecordsResolver.ts +23 -2
- package/src/resolvers/PipelineProgressResolver.ts +12 -3
- package/src/resolvers/QueryResolver.ts +20 -6
- package/src/resolvers/QuerySystemUserResolver.ts +15 -6
- package/src/resolvers/RSUResolver.ts +12 -0
- package/src/resolvers/RealtimeClientSessionResolver.ts +104 -33
- package/src/resolvers/RemoteBrowserActionResolver.ts +15 -10
- package/src/resolvers/RunAIAgentResolver.ts +258 -82
- package/src/resolvers/RunTemplateResolver.ts +6 -4
- package/src/resolvers/RunTestResolver.ts +3 -1
- package/src/resolvers/SyncDataResolver.ts +4 -0
- package/src/resolvers/TestQuerySQLResolver.ts +1 -0
- package/src/resolvers/TransactionGroupResolver.ts +66 -15
- package/src/resolvers/UserViewResolver.ts +2 -1
- package/src/resolvers/__tests__/MergeRecordsResolver.test.ts +43 -0
- package/src/rest/EntityCRUDHandler.ts +22 -5
- package/src/rest/RESTEndpointHandler.ts +8 -5
- package/src/rest/RealtimeRecordingHandler.ts +13 -10
- package/src/rest/ViewOperationsHandler.ts +24 -6
- package/src/serverRoleConfigUnits.ts +48 -0
- package/src/services/TaskGraphContinuationDeliverer.ts +52 -20
- package/src/types.ts +23 -1
|
@@ -151,6 +151,7 @@ export class TestQuerySQLResolver extends ResolverBase {
|
|
|
151
151
|
|
|
152
152
|
return {
|
|
153
153
|
Success: result.Success,
|
|
154
|
+
// fls-guard: query rows; the SQL is refused before it runs if it reads a field the caller is denied
|
|
154
155
|
Results: result.Results ? JSON.stringify(result.Results) : undefined,
|
|
155
156
|
RowCount: result.RowCount,
|
|
156
157
|
ExecutionTime: result.ExecutionTime,
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { Arg, Ctx, Field, InputType, Int, Mutation, ObjectType, registerEnumType } from 'type-graphql';
|
|
2
2
|
import { AppContext } from '../types.js';
|
|
3
|
-
import { CompositeKey, EntityInfo, IMetadataProvider, KeyValuePair, LogError, Metadata, TransactionVariable, TransactionGroupBase, BaseEntity,
|
|
3
|
+
import { AssertClientInputWritableForUser, CompositeKey, EntityInfo, IMetadataProvider, KeyValuePair, LogError, Metadata, RecordChangeMetadataSource, TransactionVariable, TransactionGroupBase, BaseEntity, UserInfo } from '@memberjunction/core';
|
|
4
4
|
import { SafeJSONParse, ValuesEqual } from '@memberjunction/global';
|
|
5
5
|
import { GetReadWriteProvider } from '../util.js';
|
|
6
6
|
import { ResolverBase } from '../generic/ResolverBase.js';
|
|
7
|
+
import { ProjectValuesForCaller } from '../generic/fieldSecurityTransport.js';
|
|
7
8
|
|
|
8
9
|
export enum TransactionVariableType {
|
|
9
10
|
Define = "Define",
|
|
@@ -148,16 +149,18 @@ export class TransactionResolver extends ResolverBase {
|
|
|
148
149
|
// BEFORE any Save()/Delete() runs below: a 'Use' variable sets its field at Submit(), after
|
|
149
150
|
// every server-side write check has passed, so an entity whose check depends on a field's
|
|
150
151
|
// final value must be able to see that a variable will set it.
|
|
152
|
+
const user: UserInfo = context.userPayload.userRecord;
|
|
151
153
|
for (const item of group.Items) {
|
|
152
|
-
entityObjects.push(await md.GetEntityObject(item.EntityName,
|
|
154
|
+
entityObjects.push(await md.GetEntityObject(item.EntityName, user));
|
|
153
155
|
}
|
|
156
|
+
this.AssertNetworkInputWritable(group, entityObjects, user);
|
|
154
157
|
this.AddNetworkVariables(group, entityObjects, tg);
|
|
155
158
|
|
|
156
159
|
for (const [index, item] of group.Items.entries()) {
|
|
157
160
|
const entity = entityObjects[index];
|
|
158
161
|
|
|
159
162
|
// get the values from the payload
|
|
160
|
-
const itemValues = SafeJSONParse(item.EntityObjectJSON);
|
|
163
|
+
const itemValues = SafeJSONParse<Record<string, unknown>>(item.EntityObjectJSON) ?? {};
|
|
161
164
|
// build a primary key for the item
|
|
162
165
|
const pkey = new CompositeKey(entity.PrimaryKeys.map(pk => {
|
|
163
166
|
const kv = new KeyValuePair();
|
|
@@ -171,8 +174,10 @@ export class TransactionResolver extends ResolverBase {
|
|
|
171
174
|
if (item.OperationType === "Update") {
|
|
172
175
|
await entity.InnerLoad(pkey);
|
|
173
176
|
if (typeof entity.GetDataObject === 'function') {
|
|
177
|
+
// fls-guard: pre-update snapshot; projected before the delta is built
|
|
174
178
|
preUpdateValues.set(index, await entity.GetDataObject());
|
|
175
179
|
} else if (typeof entity.GetAll === 'function') {
|
|
180
|
+
// fls-guard: pre-update snapshot; projected before the delta is built
|
|
176
181
|
preUpdateValues.set(index, { ...(entity.GetAll() as Record<string, unknown>) });
|
|
177
182
|
}
|
|
178
183
|
}
|
|
@@ -197,6 +202,7 @@ export class TransactionResolver extends ResolverBase {
|
|
|
197
202
|
// Awaiting costs nothing here — DataObjectParams defaults
|
|
198
203
|
// includeRelatedEntityData to false, which guards the method's only awaited
|
|
199
204
|
// work, so with no params this is GetAll() plus a filter loop.
|
|
205
|
+
// fls-guard: projected in PrepareReturnValue
|
|
200
206
|
objectValues.push(await entity.GetDataObject());
|
|
201
207
|
entity.TransactionGroup = tg;
|
|
202
208
|
if (!await entity.Delete()) {
|
|
@@ -221,17 +227,17 @@ export class TransactionResolver extends ResolverBase {
|
|
|
221
227
|
`TransactionResolver::ExecuteTransactionGroup --- ${refusals.length} of ${group.Items.length} ` +
|
|
222
228
|
`item(s) were refused, so the group was not submitted:\n${refusals.join('\n')}`
|
|
223
229
|
);
|
|
224
|
-
return await this.PrepareReturnValue(false, entityObjects, objectValues, group, preUpdateValues);
|
|
230
|
+
return await this.PrepareReturnValue(false, entityObjects, objectValues, group, preUpdateValues, md, user);
|
|
225
231
|
}
|
|
226
232
|
|
|
227
233
|
// after all that, we are ready to roll, so let's run the TG
|
|
228
234
|
if (await tg.Submit()) {
|
|
229
235
|
// success!
|
|
230
|
-
return await this.PrepareReturnValue(true, entityObjects, objectValues, group, preUpdateValues);
|
|
236
|
+
return await this.PrepareReturnValue(true, entityObjects, objectValues, group, preUpdateValues, md, user);
|
|
231
237
|
}
|
|
232
238
|
else {
|
|
233
239
|
// failure, send back the results
|
|
234
|
-
return await this.PrepareReturnValue(false, entityObjects, objectValues, group, preUpdateValues);
|
|
240
|
+
return await this.PrepareReturnValue(false, entityObjects, objectValues, group, preUpdateValues, md, user);
|
|
235
241
|
}
|
|
236
242
|
}
|
|
237
243
|
catch (err) {
|
|
@@ -240,6 +246,32 @@ export class TransactionResolver extends ResolverBase {
|
|
|
240
246
|
}
|
|
241
247
|
}
|
|
242
248
|
|
|
249
|
+
/**
|
|
250
|
+
* Field-level security gate on everything the client sent, run before any item is applied.
|
|
251
|
+
*
|
|
252
|
+
* Each create or update payload must name only fields the caller may read, and so must every transaction
|
|
253
|
+
* variable: a `Define` on a denied field would copy its value into another item's readable
|
|
254
|
+
* field at `Submit()`, and a `Use` would write it. A key that names no field is refused with
|
|
255
|
+
* the same message as a denied one (see `AssertClientInputWritableForUser`). Callers denied
|
|
256
|
+
* nothing on an entity are not affected. A delete payload is not gated: only its primary key
|
|
257
|
+
* is read, and the record is loaded from the database.
|
|
258
|
+
*/
|
|
259
|
+
protected AssertNetworkInputWritable(group: TransactionInputType, entityObjects: BaseEntity[], user: UserInfo): void {
|
|
260
|
+
for (const [index, item] of group.Items.entries()) {
|
|
261
|
+
if (item.OperationType === TransactionOperationType.Delete) {
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
const values = SafeJSONParse<Record<string, unknown>>(item.EntityObjectJSON);
|
|
265
|
+
AssertClientInputWritableForUser(entityObjects[index]?.EntityInfo, values, user);
|
|
266
|
+
}
|
|
267
|
+
for (const networkVar of group.Variables ?? []) {
|
|
268
|
+
const target = entityObjects[networkVar.ItemIndex];
|
|
269
|
+
if (target && networkVar.FieldName) {
|
|
270
|
+
AssertClientInputWritableForUser(target.EntityInfo, { [networkVar.FieldName]: null }, user);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
243
275
|
/**
|
|
244
276
|
* Adds the network payload's variables to the group, mapping each one's item index to the entity
|
|
245
277
|
* object built for that item.
|
|
@@ -261,13 +293,18 @@ export class TransactionResolver extends ResolverBase {
|
|
|
261
293
|
entityObjects: BaseEntity[],
|
|
262
294
|
objectValues: unknown[],
|
|
263
295
|
group: TransactionInputType,
|
|
264
|
-
preUpdateValues?: Map<number, Record<string, unknown
|
|
296
|
+
preUpdateValues?: Map<number, Record<string, unknown>>,
|
|
297
|
+
provider?: RecordChangeMetadataSource,
|
|
298
|
+
user?: UserInfo
|
|
265
299
|
): Promise<TransactionOutputType> {
|
|
266
300
|
const jsonResults = [];
|
|
267
301
|
for (let i = 0; i < group.Items.length; i++) {
|
|
268
302
|
const item = group.Items[i];
|
|
303
|
+
const project = (values: Record<string, unknown>) =>
|
|
304
|
+
ProjectValuesForCaller(entityObjects[i]?.EntityInfo, values, user, provider);
|
|
269
305
|
if (item.OperationType === 'Delete') {
|
|
270
|
-
|
|
306
|
+
const deleted = objectValues[i];
|
|
307
|
+
jsonResults.push(JSON.stringify(deleted && typeof deleted === 'object' ? project(deleted as Record<string, unknown>) : deleted));
|
|
271
308
|
}
|
|
272
309
|
else if (item.OperationType === 'Update') {
|
|
273
310
|
jsonResults.push(
|
|
@@ -276,13 +313,15 @@ export class TransactionResolver extends ResolverBase {
|
|
|
276
313
|
objectValues[i],
|
|
277
314
|
preUpdateValues?.get(i),
|
|
278
315
|
group,
|
|
279
|
-
i
|
|
316
|
+
i,
|
|
317
|
+
project
|
|
280
318
|
)
|
|
281
319
|
);
|
|
282
320
|
}
|
|
283
321
|
else {
|
|
284
|
-
// create, return what is in the database
|
|
285
|
-
|
|
322
|
+
// create, return what is in the database, as the caller may read it
|
|
323
|
+
// fls-guard: projected by project()
|
|
324
|
+
jsonResults.push(JSON.stringify(project(await entityObjects[i].GetDataObject())));
|
|
286
325
|
}
|
|
287
326
|
}
|
|
288
327
|
|
|
@@ -303,20 +342,32 @@ export class TransactionResolver extends ResolverBase {
|
|
|
303
342
|
* - Any field whose post-save value differs from its pre-update DB snapshot (`preSaveValues`),
|
|
304
343
|
* capturing virtual fields, triggers, or server-side `BeforeSave()` mutations automatically
|
|
305
344
|
* while omitting unchanged columns.
|
|
345
|
+
*
|
|
346
|
+
* Both rows are projected for the caller BEFORE they are compared. Comparing a full-width
|
|
347
|
+
* post-save row against a snapshot taken from a narrowed load would count every denied value
|
|
348
|
+
* as changed, and the delta would then deliver exactly the values the caller may not read.
|
|
306
349
|
*/
|
|
307
350
|
private async serializeUpdateReturnValue(
|
|
308
351
|
entity: BaseEntity,
|
|
309
352
|
sentValues: unknown,
|
|
310
353
|
preSaveValues: Record<string, unknown> | undefined,
|
|
311
354
|
group: TransactionInputType,
|
|
312
|
-
itemIndex: number
|
|
355
|
+
itemIndex: number,
|
|
356
|
+
project: (values: Record<string, unknown>) => Record<string, unknown>
|
|
313
357
|
): Promise<string> {
|
|
314
|
-
const
|
|
358
|
+
const rawDataObj: Record<string, unknown> | null = typeof entity.GetDataObject === 'function'
|
|
359
|
+
// fls-guard: projected by project() below
|
|
315
360
|
? (await entity.GetDataObject() as Record<string, unknown>)
|
|
361
|
+
// fls-guard: projected by project() below
|
|
316
362
|
: SafeJSONParse<Record<string, unknown>>(await entity.GetDataObjectJSON());
|
|
317
|
-
if (!
|
|
318
|
-
return
|
|
363
|
+
if (!rawDataObj || typeof rawDataObj !== 'object') {
|
|
364
|
+
return JSON.stringify(rawDataObj);
|
|
365
|
+
}
|
|
366
|
+
const fullDataObj = project(rawDataObj);
|
|
367
|
+
if (!sentValues || typeof sentValues !== 'object') {
|
|
368
|
+
return JSON.stringify(fullDataObj);
|
|
319
369
|
}
|
|
370
|
+
preSaveValues = preSaveValues ? project(preSaveValues) : preSaveValues;
|
|
320
371
|
|
|
321
372
|
const sentRecord = sentValues as Record<string, unknown>;
|
|
322
373
|
const includeKeys = new Set<string>([
|
|
@@ -5,6 +5,7 @@ import { MJUserView_, MJUserViewResolverBase } from '../generated/generated.js';
|
|
|
5
5
|
import { UserResolver } from './UserResolver.js';
|
|
6
6
|
import { MJUserViewEntity, MJUserViewEntityExtended } from '@memberjunction/core-entities';
|
|
7
7
|
import { GetReadOnlyProvider, GetReadWriteProvider } from '../util.js';
|
|
8
|
+
import { ProjectEntityObjectForCaller } from '../generic/fieldSecurityTransport.js';
|
|
8
9
|
|
|
9
10
|
@Resolver(MJUserView_)
|
|
10
11
|
export class UserViewResolver extends MJUserViewResolverBase {
|
|
@@ -63,7 +64,7 @@ export class UserViewResolver extends MJUserViewResolverBase {
|
|
|
63
64
|
await viewEntity.UpdateWhereClause(true); // force regeneration regardless of dirty state — that is the whole point of this endpoint
|
|
64
65
|
|
|
65
66
|
if (await viewEntity.Save()) {
|
|
66
|
-
return viewEntity
|
|
67
|
+
return ProjectEntityObjectForCaller(viewEntity, u, p);
|
|
67
68
|
} else {
|
|
68
69
|
throw new Error('Failed to update where clause');
|
|
69
70
|
}
|
|
@@ -90,4 +90,47 @@ describe('RecordDependencyResolver', () => {
|
|
|
90
90
|
expect(result[0].IsSoftLink).toBeUndefined();
|
|
91
91
|
expect(result[0].EntityIDFieldName).toBeUndefined();
|
|
92
92
|
});
|
|
93
|
+
|
|
94
|
+
it('forwards contextUser and options to provider and maps FieldDisplayName', async () => {
|
|
95
|
+
const mockDep: RecordDependency = {
|
|
96
|
+
EntityName: 'Users',
|
|
97
|
+
RelatedEntityName: 'MJ: Task Links',
|
|
98
|
+
FieldName: 'RecordID',
|
|
99
|
+
FieldDisplayName: 'Linked Record',
|
|
100
|
+
PrimaryKey: new CompositeKey([new KeyValuePair('ID', 'TL-200')]),
|
|
101
|
+
IsSoftLink: true,
|
|
102
|
+
EntityIDFieldName: 'EntityID',
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const getRecordDepsSpy = vi.fn().mockResolvedValue([mockDep]);
|
|
106
|
+
const mockProvider = {
|
|
107
|
+
GetRecordDependencies: getRecordDepsSpy,
|
|
108
|
+
} as unknown as IMetadataProvider;
|
|
109
|
+
|
|
110
|
+
vi.mocked(GetReadOnlyProvider).mockReturnValue(mockProvider as never);
|
|
111
|
+
|
|
112
|
+
const resolver = new RecordDependencyResolver();
|
|
113
|
+
const mockUser = { ID: 'u-1', Email: 'test@example.com' };
|
|
114
|
+
const mockContext = {
|
|
115
|
+
dataSource: {},
|
|
116
|
+
userPayload: { userRecord: mockUser },
|
|
117
|
+
providers: [],
|
|
118
|
+
} as unknown as AppContext;
|
|
119
|
+
const mockPubSub = {} as PubSubEngine;
|
|
120
|
+
const options = { LinkType: 'soft' as const, IncludeSystemEntities: false };
|
|
121
|
+
|
|
122
|
+
const result = await resolver.GetRecordDependencies(
|
|
123
|
+
'Users',
|
|
124
|
+
{ KeyValuePairs: [{ FieldName: 'ID', Value: 'USER-1' }] },
|
|
125
|
+
mockContext,
|
|
126
|
+
mockPubSub,
|
|
127
|
+
options
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
expect(getRecordDepsSpy).toHaveBeenCalledTimes(1);
|
|
131
|
+
expect(getRecordDepsSpy.mock.calls[0][0]).toBe('Users');
|
|
132
|
+
expect(getRecordDepsSpy.mock.calls[0][2]).toBe(mockUser);
|
|
133
|
+
expect(getRecordDepsSpy.mock.calls[0][3]).toEqual(options);
|
|
134
|
+
expect(result[0].FieldDisplayName).toBe('Linked Record');
|
|
135
|
+
});
|
|
93
136
|
});
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
|
-
BaseEntity, CompositeKey, EntityDeleteOptions, EntityPermissionType,
|
|
2
|
+
AssertClientInputWritableForUser, BaseEntity, CompositeKey, EntityDeleteOptions, EntityPermissionType,
|
|
3
3
|
EntitySaveOptions, LogError, Metadata, UserInfo
|
|
4
4
|
} from '@memberjunction/core';
|
|
5
|
+
import { ProjectValuesForCaller } from '../generic/fieldSecurityTransport.js';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Entity CRUD Implementation Functions for REST endpoints
|
|
@@ -42,6 +43,9 @@ export class EntityCRUDHandler {
|
|
|
42
43
|
delete data.options;
|
|
43
44
|
}
|
|
44
45
|
|
|
46
|
+
// Field-level security: refuse a key the caller may not read, or that names no field
|
|
47
|
+
AssertClientInputWritableForUser(entity.EntityInfo, data, user);
|
|
48
|
+
|
|
45
49
|
// Set values on the entity
|
|
46
50
|
for (const key in data) {
|
|
47
51
|
entity.Set(key, data[key]);
|
|
@@ -70,7 +74,7 @@ export class EntityCRUDHandler {
|
|
|
70
74
|
}
|
|
71
75
|
|
|
72
76
|
// Get entity data for response
|
|
73
|
-
const entityData = await
|
|
77
|
+
const entityData = await this.responseData(entity, user, md);
|
|
74
78
|
return { success: true, entity: entityData };
|
|
75
79
|
} catch (error: any) {
|
|
76
80
|
LogError(error);
|
|
@@ -114,7 +118,7 @@ export class EntityCRUDHandler {
|
|
|
114
118
|
}
|
|
115
119
|
|
|
116
120
|
// Get entity data for response
|
|
117
|
-
const entityData = await
|
|
121
|
+
const entityData = await this.responseData(entity, user, md);
|
|
118
122
|
return { success: true, entity: entityData };
|
|
119
123
|
} catch (error: any) {
|
|
120
124
|
LogError(error);
|
|
@@ -171,6 +175,9 @@ export class EntityCRUDHandler {
|
|
|
171
175
|
delete data.options;
|
|
172
176
|
}
|
|
173
177
|
|
|
178
|
+
// Field-level security: refuse a key the caller may not read, or that names no field
|
|
179
|
+
AssertClientInputWritableForUser(entity.EntityInfo, data, user);
|
|
180
|
+
|
|
174
181
|
// Update entity with new values
|
|
175
182
|
for (const key in data) {
|
|
176
183
|
entity.Set(key, data[key]);
|
|
@@ -179,7 +186,7 @@ export class EntityCRUDHandler {
|
|
|
179
186
|
// Check if entity is dirty
|
|
180
187
|
if (!entity.Dirty && !options.IgnoreDirtyState) {
|
|
181
188
|
// Nothing changed, return success
|
|
182
|
-
const entityData = await
|
|
189
|
+
const entityData = await this.responseData(entity, user, md);
|
|
183
190
|
return { success: true, entity: entityData };
|
|
184
191
|
}
|
|
185
192
|
|
|
@@ -206,7 +213,7 @@ export class EntityCRUDHandler {
|
|
|
206
213
|
}
|
|
207
214
|
|
|
208
215
|
// Get entity data for response
|
|
209
|
-
const entityData = await
|
|
216
|
+
const entityData = await this.responseData(entity, user, md);
|
|
210
217
|
return { success: true, entity: entityData };
|
|
211
218
|
} catch (error: any) {
|
|
212
219
|
LogError(error);
|
|
@@ -273,6 +280,16 @@ export class EntityCRUDHandler {
|
|
|
273
280
|
return this.DeleteEntity(entityName, id, options, user);
|
|
274
281
|
}
|
|
275
282
|
|
|
283
|
+
/**
|
|
284
|
+
* The record's field values for the response, reduced to what `user` may READ. An entity
|
|
285
|
+
* object built for a restricted user holds no denied value already; this is the backstop for
|
|
286
|
+
* one that does.
|
|
287
|
+
*/
|
|
288
|
+
private static async responseData(entity: BaseEntity, user: UserInfo, md: Metadata): Promise<Record<string, unknown>> {
|
|
289
|
+
// fls-guard: projected by ProjectValuesForCaller
|
|
290
|
+
return ProjectValuesForCaller(entity.EntityInfo, await entity.GetDataObject() as Record<string, unknown>, user, md);
|
|
291
|
+
}
|
|
292
|
+
|
|
276
293
|
/**
|
|
277
294
|
* Build the record key from the `:id` path segment. A single-column key (any column name) is
|
|
278
295
|
* the bare value; a composite key is the URL-encoded `Field1|Value1||Field2|Value2` segment.
|
|
@@ -2,7 +2,7 @@ import express from 'express';
|
|
|
2
2
|
import {
|
|
3
3
|
BaseEntity, CompositeKey, EntityDeleteOptions, EntityInfo,
|
|
4
4
|
EntityPermissionType, EntitySaveOptions, LogError, Metadata,
|
|
5
|
-
RunView, RunViewParams, UserInfo
|
|
5
|
+
RunView, RunViewParams, StripServerOnlyRunViewParams, UserInfo
|
|
6
6
|
} from '@memberjunction/core';
|
|
7
7
|
import { EntityCRUDHandler } from './EntityCRUDHandler.js';
|
|
8
8
|
import { ViewOperationsHandler } from './ViewOperationsHandler.js';
|
|
@@ -499,10 +499,11 @@ export class RESTEndpointHandler {
|
|
|
499
499
|
|
|
500
500
|
const user = req['mjUser'];
|
|
501
501
|
|
|
502
|
-
// Create RunViewParams from the request body
|
|
502
|
+
// Create RunViewParams from the request body, without the fields only server code sets;
|
|
503
|
+
// the time zone comes from the x-mj-timezone header, as on the GraphQL path
|
|
503
504
|
const params: RunViewParams = {
|
|
504
|
-
EntityName: entityName,
|
|
505
|
-
|
|
505
|
+
...StripServerOnlyRunViewParams({ ...viewParams, EntityName: entityName }),
|
|
506
|
+
TimeZone: req.userPayload?.TimeZone
|
|
506
507
|
};
|
|
507
508
|
|
|
508
509
|
const result = await ViewOperationsHandler.runView(params, user);
|
|
@@ -533,7 +534,9 @@ export class RESTEndpointHandler {
|
|
|
533
534
|
|
|
534
535
|
// Filter out any views for entities that aren't allowed
|
|
535
536
|
// using our enhanced entity filtering with wildcards and schema support
|
|
536
|
-
const filteredParams = params
|
|
537
|
+
const filteredParams = params
|
|
538
|
+
.filter(p => this.isEntityAllowed(p.EntityName))
|
|
539
|
+
.map(p => ({ ...StripServerOnlyRunViewParams(p), TimeZone: req.userPayload?.TimeZone }));
|
|
537
540
|
|
|
538
541
|
// If all requested entities were filtered out, return an error
|
|
539
542
|
if (filteredParams.length === 0 && params.length > 0) {
|
|
@@ -34,7 +34,7 @@ import {
|
|
|
34
34
|
LoadOwnedRealtimeSession, RealtimeSessionNotFoundError, RealtimeSessionOwnershipError,
|
|
35
35
|
} from '../realtimeSessions/ownedRealtimeSession.js';
|
|
36
36
|
import type { IRecordingAssemblyScheduler } from '../realtimeSessions/RecordingAssemblyScheduler.js';
|
|
37
|
-
import { AuthorizeSessionAccess,
|
|
37
|
+
import { AuthorizeSessionAccess, LoadSessionConversationScope } from '../realtimeSessions/sessionAccess.js';
|
|
38
38
|
|
|
39
39
|
/** Where {@link CreateRealtimeRecordingRouter} is mounted (under the reserved `/realtime` root). */
|
|
40
40
|
export const REALTIME_RECORDING_MOUNT_PATH = '/realtime/recording';
|
|
@@ -76,8 +76,8 @@ export interface RealtimeRecordingRouterDeps {
|
|
|
76
76
|
/** The authenticated caller, or null (answered 401). */
|
|
77
77
|
ResolveCaller(req: Request): RealtimeRecordingCaller | null;
|
|
78
78
|
LoadOwnedSession: typeof LoadOwnedRealtimeSession;
|
|
79
|
-
/** The session conversation's
|
|
80
|
-
|
|
79
|
+
/** The session conversation's scope id and whether the caller could read it: the scope half of the access decision. */
|
|
80
|
+
LoadConversationScope: typeof LoadSessionConversationScope;
|
|
81
81
|
/** The ledger for one request; `contextUser` is the caller (used for logging by the procs). */
|
|
82
82
|
CreateLedger(provider: DatabaseProviderBase, contextUser: UserInfo): IRealtimeRecordingLedger;
|
|
83
83
|
Scheduler: IRecordingAssemblyScheduler;
|
|
@@ -258,16 +258,19 @@ async function handleTerminal(deps: RealtimeRecordingRouterDeps, req: Request, r
|
|
|
258
258
|
|
|
259
259
|
/**
|
|
260
260
|
* Loads the session through the shared ownership gate, then applies the per-session scope rule of
|
|
261
|
-
* {@link AuthorizeSessionAccess}. The gate
|
|
262
|
-
*
|
|
263
|
-
*
|
|
264
|
-
*
|
|
265
|
-
*
|
|
261
|
+
* {@link AuthorizeSessionAccess}. The production gate (`LoadOwnedRealtimeSession`) already applies the same
|
|
262
|
+
* rule, so this is a deliberate second check at the HTTP boundary: `LoadOwnedSession` is an injected dependency,
|
|
263
|
+
* and without the scope rule one anonymous guest could write shards to another guest's recording (every guest
|
|
264
|
+
* shares the one Anonymous user), and a different-bytes shard would mark it Failed. It costs one extra
|
|
265
|
+
* conversation read per request. Named users without a scope are unaffected.
|
|
266
266
|
*/
|
|
267
267
|
async function loadAuthorizedSession(deps: RealtimeRecordingRouterDeps, sessionID: string, caller: RealtimeRecordingCaller): Promise<MJAIAgentSessionEntity> {
|
|
268
268
|
const session = await deps.LoadOwnedSession(sessionID, caller.ContextUser, caller.Provider);
|
|
269
|
-
const
|
|
270
|
-
const decision = AuthorizeSessionAccess(
|
|
269
|
+
const conversation = await deps.LoadConversationScope(session, caller.ContextUser, caller.Provider);
|
|
270
|
+
const decision = AuthorizeSessionAccess(
|
|
271
|
+
{ SessionUserID: session.UserID, ConversationExternalID: conversation.ExternalID, ConversationReadable: conversation.Readable },
|
|
272
|
+
caller.ContextUser,
|
|
273
|
+
);
|
|
271
274
|
if (decision.Allowed === false) {
|
|
272
275
|
LogError(`RealtimeRecordingHandler: refused user ${caller.ContextUser.ID} on session ${sessionID}: ${decision.Reason}`);
|
|
273
276
|
throw new RecordingRequestError(403, 'Not authorized for this realtime session');
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
|
-
BaseEntity, LogError, Metadata, RunView, RunViewParams,
|
|
2
|
+
BaseEntity, EntityInfo, LogError, Metadata, RunView, RunViewParams,
|
|
3
3
|
RunViewResult, UserInfo, type IRunViewProvider
|
|
4
4
|
} from '@memberjunction/core';
|
|
5
5
|
import { ClientClauseScreen } from '@memberjunction/generic-database-provider';
|
|
6
|
+
import { ProjectRowsForCaller } from '../generic/fieldSecurityTransport.js';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* View Operations Implementation for REST endpoints
|
|
@@ -40,7 +41,7 @@ export class ViewOperationsHandler {
|
|
|
40
41
|
// Execute the view
|
|
41
42
|
const result = await runView.RunView(params, user);
|
|
42
43
|
|
|
43
|
-
return { success: true, result };
|
|
44
|
+
return { success: true, result: this.projectResult(result, entity, user, md) };
|
|
44
45
|
} catch (error) {
|
|
45
46
|
LogError(error);
|
|
46
47
|
return { success: false, error: (error as Error)?.message || 'Unknown error' };
|
|
@@ -60,6 +61,7 @@ export class ViewOperationsHandler {
|
|
|
60
61
|
// Validate and sanitize each set of parameters against the provider that runs the views
|
|
61
62
|
const md = new Metadata(); // global-provider-ok: REST endpoint — no per-request provider injection in REST middleware yet
|
|
62
63
|
const runView = new RunView();
|
|
64
|
+
const entities: EntityInfo[] = [];
|
|
63
65
|
for (const params of paramsArray) {
|
|
64
66
|
// Validate entity exists
|
|
65
67
|
const entity = md.Entities.find(e => e.Name === params.EntityName);
|
|
@@ -81,12 +83,14 @@ export class ViewOperationsHandler {
|
|
|
81
83
|
|
|
82
84
|
// Sanitize parameters
|
|
83
85
|
this.sanitizeRunViewParams(params, md, runView.ProviderToUse, user);
|
|
86
|
+
entities.push(entity);
|
|
84
87
|
}
|
|
85
88
|
|
|
86
89
|
// Execute the views
|
|
87
90
|
const results = await runView.RunViews(paramsArray, user);
|
|
88
91
|
|
|
89
|
-
|
|
92
|
+
const projected = results?.map((r, i) => this.projectResult(r, entities[i], user, md));
|
|
93
|
+
return { success: true, results: projected?.some((r, i) => r !== results[i]) ? projected : results };
|
|
90
94
|
} catch (error) {
|
|
91
95
|
LogError(error);
|
|
92
96
|
return { success: false, error: (error as Error)?.message || 'Unknown error' };
|
|
@@ -120,7 +124,7 @@ export class ViewOperationsHandler {
|
|
|
120
124
|
this.sanitizeRunViewParams(params, md, runView.ProviderToUse, user);
|
|
121
125
|
|
|
122
126
|
// Execute the view
|
|
123
|
-
return await runView.RunView(params, user);
|
|
127
|
+
return this.projectResult(await runView.RunView(params, user), entity, user, md);
|
|
124
128
|
} catch (error) {
|
|
125
129
|
LogError(error);
|
|
126
130
|
throw error;
|
|
@@ -193,6 +197,19 @@ export class ViewOperationsHandler {
|
|
|
193
197
|
return this.GetEntityViews(entityName, user);
|
|
194
198
|
}
|
|
195
199
|
|
|
200
|
+
/**
|
|
201
|
+
* The result with its rows reduced to what `user` may READ. The provider already narrows the
|
|
202
|
+
* rows it reads for a restricted caller; this is the backstop for any that arrive wider. The
|
|
203
|
+
* same result object comes back when no row needed changing.
|
|
204
|
+
*/
|
|
205
|
+
private static projectResult(result: RunViewResult, entity: EntityInfo, user: UserInfo, md: Metadata): RunViewResult {
|
|
206
|
+
if (!result?.Results?.length) {
|
|
207
|
+
return result;
|
|
208
|
+
}
|
|
209
|
+
const rows = ProjectRowsForCaller(entity, result.Results as Record<string, unknown>[], user, md);
|
|
210
|
+
return rows === result.Results ? result : { ...result, Results: rows };
|
|
211
|
+
}
|
|
212
|
+
|
|
196
213
|
/**
|
|
197
214
|
* Sanitize and validate RunViewParams. Client filter and sort text passes the same clause
|
|
198
215
|
* screen as the GraphQL RunView resolvers, parsed in the dialect of `provider`, the provider
|
|
@@ -231,8 +248,9 @@ export class ViewOperationsHandler {
|
|
|
231
248
|
params.StartRow = Number.isFinite(parsed) ? Math.max(0, Math.floor(parsed as number)) : undefined;
|
|
232
249
|
}
|
|
233
250
|
|
|
234
|
-
//
|
|
235
|
-
|
|
251
|
+
// Plain rows only on the wire. An entity object serializes its internal state rather than
|
|
252
|
+
// its projected field values, so a caller may not ask for one; a count carries no row.
|
|
253
|
+
if (params.ResultType !== 'count_only') {
|
|
236
254
|
params.ResultType = 'simple';
|
|
237
255
|
}
|
|
238
256
|
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Supported MJServer horizontal scaling roles (`MJ_SERVER_ROLE`).
|
|
3
|
+
* - `'all'` (default): Monolithic mode — serves HTTP/GraphQL/WebSocket traffic AND runs all background worker loops.
|
|
4
|
+
* - `'api'`: Stateless API/WebSocket edge — serves HTTP/GraphQL/WebSocket traffic ONLY; skips background schedulers,
|
|
5
|
+
* queue dispatchers, and janitors so event-loop responsiveness is isolated from heavy background jobs.
|
|
6
|
+
* - `'worker'`: Background worker replica — runs `ScheduledJobsService`, `IntegrationSyncWorkerService`,
|
|
7
|
+
* `StartTaskGraphDispatcher`, `StartWorkQueueHost`, `SessionJanitor`, and orphan reconcilers while keeping
|
|
8
|
+
* only lightweight healthcheck/metrics HTTP endpoints active.
|
|
9
|
+
*/
|
|
10
|
+
export type ServerRole = 'all' | 'api' | 'worker';
|
|
11
|
+
|
|
12
|
+
const VALID_SERVER_ROLES: ReadonlySet<string> = new Set<ServerRole>(['all', 'api', 'worker']);
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Resolves the server role from `MJ_SERVER_ROLE` or `mj.config.cjs`'s `serverRole`.
|
|
16
|
+
* Defaults to `'all'` when unset or blank. Throws on unrecognized values so deployment typos
|
|
17
|
+
* fail fast at startup instead of silently running the wrong workload profile.
|
|
18
|
+
*/
|
|
19
|
+
export function ResolveServerRole(rawValue?: string | null): ServerRole {
|
|
20
|
+
const trimmed = rawValue?.trim().toLowerCase();
|
|
21
|
+
if (!trimmed) {
|
|
22
|
+
return 'all';
|
|
23
|
+
}
|
|
24
|
+
if (VALID_SERVER_ROLES.has(trimmed)) {
|
|
25
|
+
return trimmed as ServerRole;
|
|
26
|
+
}
|
|
27
|
+
throw new Error(
|
|
28
|
+
`Invalid MJ_SERVER_ROLE "${rawValue}". Expected one of: 'all', 'api', 'worker'.`
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Returns `true` when this server instance should start background worker loops
|
|
34
|
+
* (`ScheduledJobsService`, `IntegrationSyncWorkerService`, `StartTaskGraphDispatcher`,
|
|
35
|
+
* `StartWorkQueueHost`, `SessionJanitor`, `StartBridgeOrphanReconciliation`, and
|
|
36
|
+
* `OrphanDetailReconciler`).
|
|
37
|
+
*/
|
|
38
|
+
export function ShouldRunBackgroundWorkers(role: ServerRole): boolean {
|
|
39
|
+
return role === 'all' || role === 'worker';
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Returns `true` when this server instance is designated to serve interactive
|
|
44
|
+
* API / GraphQL / WebSocket client traffic.
|
|
45
|
+
*/
|
|
46
|
+
export function ShouldServeInteractiveTraffic(role: ServerRole): boolean {
|
|
47
|
+
return role === 'all' || role === 'api';
|
|
48
|
+
}
|