@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
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
2
|
+
import type { Request } from 'express';
|
|
3
|
+
import jwt from 'jsonwebtoken';
|
|
4
|
+
import { AuthProviderFactory } from '@memberjunction/auth-providers';
|
|
5
|
+
import { UserInfo } from '@memberjunction/core';
|
|
6
|
+
|
|
7
|
+
vi.mock('../config.js', () => ({
|
|
8
|
+
configInfo: {
|
|
9
|
+
devAuth: { enabled: false },
|
|
10
|
+
magicLink: {},
|
|
11
|
+
authProviders: [],
|
|
12
|
+
},
|
|
13
|
+
}));
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
AssertLocalDevAuthAllowed,
|
|
17
|
+
IsLocalDevAuthEnabled,
|
|
18
|
+
IsLoopbackAddress,
|
|
19
|
+
MintLocalDevJwt,
|
|
20
|
+
RegisterLocalDevAuthProvider,
|
|
21
|
+
VerifyLocalhostRequest,
|
|
22
|
+
} from '../auth/localDevAuth.js';
|
|
23
|
+
import { MagicLinkKeyManager } from '../auth/magicLink/MagicLinkKeys.js';
|
|
24
|
+
|
|
25
|
+
function createMockRequest(overrides: {
|
|
26
|
+
remoteAddress?: string;
|
|
27
|
+
host?: string;
|
|
28
|
+
origin?: string;
|
|
29
|
+
referer?: string;
|
|
30
|
+
xff?: string;
|
|
31
|
+
}): Request {
|
|
32
|
+
const headers: Record<string, string | undefined> = {
|
|
33
|
+
host: overrides.host ?? 'localhost:4000',
|
|
34
|
+
origin: overrides.origin,
|
|
35
|
+
referer: overrides.referer,
|
|
36
|
+
'x-forwarded-for': overrides.xff,
|
|
37
|
+
};
|
|
38
|
+
return {
|
|
39
|
+
headers,
|
|
40
|
+
hostname: (overrides.host ?? 'localhost:4000').split(':')[0],
|
|
41
|
+
ip: overrides.remoteAddress ?? '127.0.0.1',
|
|
42
|
+
socket: {
|
|
43
|
+
remoteAddress: overrides.remoteAddress ?? '127.0.0.1',
|
|
44
|
+
},
|
|
45
|
+
} as unknown as Request;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
describe('localDevAuth security invariants', () => {
|
|
49
|
+
beforeEach(() => {
|
|
50
|
+
AuthProviderFactory.Instance.clear();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('IsLocalDevAuthEnabled detects MJ_DEV_AUTH_ENABLED=true or 1', () => {
|
|
54
|
+
expect(IsLocalDevAuthEnabled({})).toBe(false);
|
|
55
|
+
expect(IsLocalDevAuthEnabled({ MJ_DEV_AUTH_ENABLED: 'false' })).toBe(false);
|
|
56
|
+
expect(IsLocalDevAuthEnabled({ MJ_DEV_AUTH_ENABLED: 'true' })).toBe(true);
|
|
57
|
+
expect(IsLocalDevAuthEnabled({ MJ_DEV_AUTH_ENABLED: '1' })).toBe(true);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('AssertLocalDevAuthAllowed fails loudly when NODE_ENV=production', () => {
|
|
61
|
+
expect(() =>
|
|
62
|
+
AssertLocalDevAuthAllowed({
|
|
63
|
+
MJ_DEV_AUTH_ENABLED: 'true',
|
|
64
|
+
NODE_ENV: 'development',
|
|
65
|
+
})
|
|
66
|
+
).not.toThrow();
|
|
67
|
+
|
|
68
|
+
expect(() =>
|
|
69
|
+
AssertLocalDevAuthAllowed({
|
|
70
|
+
MJ_DEV_AUTH_ENABLED: 'true',
|
|
71
|
+
NODE_ENV: 'production',
|
|
72
|
+
})
|
|
73
|
+
).toThrow(/strictly forbidden when NODE_ENV=production/);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('IsLoopbackAddress accepts loopback and conditional Docker local bridge IPs', () => {
|
|
77
|
+
expect(IsLoopbackAddress('127.0.0.1', false)).toBe(true);
|
|
78
|
+
expect(IsLoopbackAddress('::1', false)).toBe(true);
|
|
79
|
+
expect(IsLoopbackAddress('::ffff:127.0.0.1', false)).toBe(true);
|
|
80
|
+
expect(IsLoopbackAddress('192.168.65.1', false)).toBe(false);
|
|
81
|
+
expect(IsLoopbackAddress('192.168.65.1', true)).toBe(true);
|
|
82
|
+
expect(IsLoopbackAddress('172.18.0.1', true)).toBe(true);
|
|
83
|
+
expect(IsLoopbackAddress('192.168.1.42', true)).toBe(false);
|
|
84
|
+
expect(IsLoopbackAddress('203.0.113.10', true)).toBe(false);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('VerifyLocalhostRequest allows genuine localhost requests and blocks non-localhost vectors', () => {
|
|
88
|
+
// 1. Standard localhost IPv4 and IPv6
|
|
89
|
+
expect(
|
|
90
|
+
VerifyLocalhostRequest(
|
|
91
|
+
createMockRequest({ remoteAddress: '127.0.0.1', host: 'localhost:4000', origin: 'http://localhost:4202' }),
|
|
92
|
+
{ NODE_ENV: 'development' }
|
|
93
|
+
).Allowed
|
|
94
|
+
).toBe(true);
|
|
95
|
+
|
|
96
|
+
expect(
|
|
97
|
+
VerifyLocalhostRequest(
|
|
98
|
+
createMockRequest({ remoteAddress: '::1', host: '127.0.0.1:4000', origin: 'http://127.0.0.1:4202' }),
|
|
99
|
+
{ NODE_ENV: 'development' }
|
|
100
|
+
).Allowed
|
|
101
|
+
).toBe(true);
|
|
102
|
+
|
|
103
|
+
// 2. Docker Desktop host-gateway when Host is localhost, inside container, and no XFF is present
|
|
104
|
+
expect(
|
|
105
|
+
VerifyLocalhostRequest(
|
|
106
|
+
createMockRequest({ remoteAddress: '::ffff:192.168.65.1', host: 'localhost:4000' }),
|
|
107
|
+
{ NODE_ENV: 'development', MJ_DEV_AUTH_ALLOW_DOCKER_BRIDGE: 'true' }
|
|
108
|
+
).Allowed
|
|
109
|
+
).toBe(true);
|
|
110
|
+
|
|
111
|
+
// 2b. Rejects Docker bridge IPs when NOT running inside a container
|
|
112
|
+
expect(
|
|
113
|
+
VerifyLocalhostRequest(
|
|
114
|
+
createMockRequest({ remoteAddress: '172.18.0.1', host: 'localhost:4000' }),
|
|
115
|
+
{ NODE_ENV: 'development', MJ_DEV_AUTH_ALLOW_DOCKER_BRIDGE: 'false' }
|
|
116
|
+
).Allowed
|
|
117
|
+
).toBe(false);
|
|
118
|
+
|
|
119
|
+
// 2c. Rejects non-gateway RFC1918 172.16.0.0/12 addresses even inside a container
|
|
120
|
+
expect(IsLoopbackAddress('172.20.10.5', true)).toBe(false);
|
|
121
|
+
|
|
122
|
+
// 3. Rejects in production even from 127.0.0.1
|
|
123
|
+
expect(
|
|
124
|
+
VerifyLocalhostRequest(
|
|
125
|
+
createMockRequest({ remoteAddress: '127.0.0.1', host: 'localhost:4000' }),
|
|
126
|
+
{ NODE_ENV: 'production' }
|
|
127
|
+
).Allowed
|
|
128
|
+
).toBe(false);
|
|
129
|
+
|
|
130
|
+
// 4. Rejects LAN IP on socket
|
|
131
|
+
expect(
|
|
132
|
+
VerifyLocalhostRequest(
|
|
133
|
+
createMockRequest({ remoteAddress: '192.168.1.50', host: 'localhost:4000' }),
|
|
134
|
+
{ NODE_ENV: 'development' }
|
|
135
|
+
).Allowed
|
|
136
|
+
).toBe(false);
|
|
137
|
+
|
|
138
|
+
// 5. Rejects non-localhost Host header
|
|
139
|
+
expect(
|
|
140
|
+
VerifyLocalhostRequest(
|
|
141
|
+
createMockRequest({ remoteAddress: '127.0.0.1', host: '192.168.1.50:4000' }),
|
|
142
|
+
{ NODE_ENV: 'development' }
|
|
143
|
+
).Allowed
|
|
144
|
+
).toBe(false);
|
|
145
|
+
|
|
146
|
+
// 6. Rejects external X-Forwarded-For
|
|
147
|
+
expect(
|
|
148
|
+
VerifyLocalhostRequest(
|
|
149
|
+
createMockRequest({ remoteAddress: '127.0.0.1', host: 'localhost:4000', xff: '203.0.113.5' }),
|
|
150
|
+
{ NODE_ENV: 'development' }
|
|
151
|
+
).Allowed
|
|
152
|
+
).toBe(false);
|
|
153
|
+
|
|
154
|
+
// 7. Rejects external Origin
|
|
155
|
+
expect(
|
|
156
|
+
VerifyLocalhostRequest(
|
|
157
|
+
createMockRequest({
|
|
158
|
+
remoteAddress: '127.0.0.1',
|
|
159
|
+
host: 'localhost:4000',
|
|
160
|
+
origin: 'https://attacker.example.com',
|
|
161
|
+
}),
|
|
162
|
+
{ NODE_ENV: 'development' }
|
|
163
|
+
).Allowed
|
|
164
|
+
).toBe(false);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('MintLocalDevJwt mints an RS256 token verifiable by the registered LocalDevProvider', () => {
|
|
168
|
+
MagicLinkKeyManager.Instance.Initialize();
|
|
169
|
+
RegisterLocalDevAuthProvider('http://localhost:4000');
|
|
170
|
+
|
|
171
|
+
const mockUser = new UserInfo(undefined, {
|
|
172
|
+
ID: '11111111-2222-3333-4444-555555555555',
|
|
173
|
+
Name: 'Admin User',
|
|
174
|
+
Email: 'admin@memberjunction.org',
|
|
175
|
+
FirstName: 'Admin',
|
|
176
|
+
LastName: 'User',
|
|
177
|
+
IsActive: true,
|
|
178
|
+
Type: 'Owner',
|
|
179
|
+
UserRoles: [],
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
const { token, claims } = MintLocalDevJwt(mockUser, 'http://localhost:4000');
|
|
183
|
+
expect(claims.iss).toBe('http://localhost:4000/local-dev');
|
|
184
|
+
expect(claims.aud).toBe('mj-local-dev');
|
|
185
|
+
expect(claims.email).toBe('admin@memberjunction.org');
|
|
186
|
+
expect(claims['mj_magic_link']).toBeUndefined();
|
|
187
|
+
|
|
188
|
+
const provider = AuthProviderFactory.Instance.getByIssuer('http://localhost:4000/local-dev');
|
|
189
|
+
expect(provider).toBeDefined();
|
|
190
|
+
const decoded = jwt.decode(token) as jwt.JwtPayload;
|
|
191
|
+
const userInfo = provider!.extractUserInfo(decoded);
|
|
192
|
+
expect(userInfo.email).toBe('admin@memberjunction.org');
|
|
193
|
+
expect(userInfo.firstName).toBe('Admin');
|
|
194
|
+
expect(userInfo.lastName).toBe('User');
|
|
195
|
+
});
|
|
196
|
+
});
|
|
@@ -74,6 +74,7 @@ function writable(saveResult = true, loadedStatus = 'In-Progress') {
|
|
|
74
74
|
ID: DETAIL_ID,
|
|
75
75
|
Status: loadedStatus,
|
|
76
76
|
Message: '',
|
|
77
|
+
MarkServerAuthoredReply: vi.fn(),
|
|
77
78
|
Load: vi.fn(async () => true),
|
|
78
79
|
Save: vi.fn(async () => saveResult),
|
|
79
80
|
LatestResult: { CompleteMessage: 'denied' },
|
|
@@ -245,6 +246,15 @@ describe('ReconcileOrphanedConversationDetails', () => {
|
|
|
245
246
|
);
|
|
246
247
|
});
|
|
247
248
|
|
|
249
|
+
it("marks the close as the server's write: the reply gate refuses an unmarked change to a reply's status", async () => {
|
|
250
|
+
script([detail()], [{ ConversationDetailID: DETAIL_ID, Status: 'Failed', CompletedAt: longAgo() }]);
|
|
251
|
+
|
|
252
|
+
await ReconcileOrphanedConversationDetails(PROVIDER, USER);
|
|
253
|
+
|
|
254
|
+
expect(writableStore.last?.MarkServerAuthoredReply).toHaveBeenCalledOnce();
|
|
255
|
+
expect(writableStore.last?.Save).toHaveBeenCalled();
|
|
256
|
+
});
|
|
257
|
+
|
|
248
258
|
it('leaves the detail alone when no owner can be resolved', async () => {
|
|
249
259
|
script([detail()], [{ ConversationDetailID: DETAIL_ID, Status: 'Failed', CompletedAt: longAgo() }], null);
|
|
250
260
|
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import 'reflect-metadata';
|
|
12
12
|
import { describe, it, expect } from 'vitest';
|
|
13
|
-
import { StatusUpdatesFilter, type PushStatusNotificationPayload, type StatusUpdatesFilterContext } from '../generic/PushStatusResolver';
|
|
13
|
+
import { StatusUpdatesFilter, StatusUpdateOwnerID, type PushStatusNotificationPayload, type StatusUpdatesFilterContext } from '../generic/PushStatusResolver';
|
|
14
14
|
import type { UserPayload } from '../types';
|
|
15
15
|
|
|
16
16
|
const OWNER = 'AA11BB22-0000-4000-8000-000000000001';
|
|
@@ -61,3 +61,59 @@ describe('statusUpdatesFilter (B49 session-hijack gate)', () => {
|
|
|
61
61
|
});
|
|
62
62
|
});
|
|
63
63
|
|
|
64
|
+
|
|
65
|
+
describe('StatusUpdateOwnerID (#4931)', () => {
|
|
66
|
+
it('is the request user, or the user an elevated payload stands in for', () => {
|
|
67
|
+
expect(StatusUpdateOwnerID({ sessionId: 's', email: 'e', userRecord: { ID: 'user-1' } } as UserPayload)).toBe('user-1');
|
|
68
|
+
expect(StatusUpdateOwnerID({ sessionId: 's', email: 'e', userRecord: { ID: 'system' }, ElevatedFrom: { ID: 'guest' } } as unknown as UserPayload)).toBe('guest');
|
|
69
|
+
expect(StatusUpdateOwnerID(undefined)).toBe('');
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe('statusUpdatesFilter — anonymous visitors share one user id (#4931)', () => {
|
|
74
|
+
const ANON = 'AAAAAAAA-0000-4000-8000-0000000000A0';
|
|
75
|
+
const visitor = (sid: string) => ({ ID: ANON, IsMagicLinkAnonymous: true, AnonymousVisitorID: sid, MagicLinkScope: { ResourceID: sid } });
|
|
76
|
+
const connection = (user: object): StatusUpdatesFilterContext => ({ userPayload: { sessionId: 'same-session', email: 'anonymous@magic-link.local', userRecord: user } as unknown as UserPayload });
|
|
77
|
+
// Visitor A's run, elevated to the system user: the event's owner comes from ElevatedFrom.
|
|
78
|
+
const aRun = { sessionId: 'same-session', email: 'system@example.com', userRecord: { ID: 'system' }, ElevatedFrom: visitor('sid-a') } as unknown as UserPayload;
|
|
79
|
+
const event = (): PushStatusNotificationPayload => ({ sessionId: 'same-session', ownerUserId: StatusUpdateOwnerID(aRun), message: 'complete' });
|
|
80
|
+
|
|
81
|
+
it("delivers visitor A's run events to visitor A's connection", () => {
|
|
82
|
+
expect(StatusUpdatesFilter({ payload: event(), args: { sessionId: 'same-session' }, context: connection(visitor('sid-a')) })).toBe(true);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('does not deliver them to visitor B, even with the same (client-chosen) session id', () => {
|
|
86
|
+
expect(StatusUpdatesFilter({ payload: event(), args: { sessionId: 'same-session' }, context: connection(visitor('sid-b')) })).toBe(false);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('does not deliver them to an anonymous connection with no scope identity (fail closed)', () => {
|
|
90
|
+
expect(StatusUpdatesFilter({ payload: event(), args: { sessionId: 'same-session' }, context: connection({ ID: ANON, IsMagicLinkAnonymous: true }) })).toBe(false);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
describe('statusUpdatesFilter — an anonymous owner key matches exactly', () => {
|
|
95
|
+
// The scope part is a signed, case-significant identity (an mj_sid or a resource scope), so the anonymous key is
|
|
96
|
+
// compared exactly. A UUID comparison (case- and whitespace-insensitive) would let a visitor whose scope differs
|
|
97
|
+
// only in case or padding receive another visitor's run events.
|
|
98
|
+
const ANON = 'AAAAAAAA-0000-4000-8000-0000000000A0';
|
|
99
|
+
const visitor = (sid: string) => ({ ID: ANON, IsMagicLinkAnonymous: true, AnonymousVisitorID: sid, MagicLinkScope: { ResourceID: sid } });
|
|
100
|
+
const connection = (user: object): StatusUpdatesFilterContext => ({ userPayload: { sessionId: 's', email: 'anonymous@magic-link.local', userRecord: user } as UserPayload });
|
|
101
|
+
const eventOwnedBy = (sid: string): PushStatusNotificationPayload => ({
|
|
102
|
+
sessionId: 's',
|
|
103
|
+
ownerUserId: `${ANON.toLowerCase()}|${sid}`,
|
|
104
|
+
message: 'complete',
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('delivers to the visitor whose scope is identical', () => {
|
|
108
|
+
expect(StatusUpdatesFilter({ payload: eventOwnedBy('sid-a'), args: { sessionId: 's' }, context: connection(visitor('sid-a')) })).toBe(true);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('does not deliver when the scopes differ only by case', () => {
|
|
112
|
+
expect(StatusUpdatesFilter({ payload: eventOwnedBy('SID-A'), args: { sessionId: 's' }, context: connection(visitor('sid-a')) })).toBe(false);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('does not deliver when the scopes differ only by surrounding whitespace', () => {
|
|
116
|
+
expect(StatusUpdatesFilter({ payload: eventOwnedBy('sid-a '), args: { sessionId: 's' }, context: connection(visitor('sid-a')) })).toBe(false);
|
|
117
|
+
expect(StatusUpdatesFilter({ payload: eventOwnedBy('sid-a'), args: { sessionId: 's' }, context: connection(visitor(' sid-a')) })).toBe(false);
|
|
118
|
+
});
|
|
119
|
+
});
|
|
@@ -135,6 +135,10 @@ class FakeLedger implements IRealtimeRecordingLedger {
|
|
|
135
135
|
throw new Error('not used by the routes');
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
+
public async StampFile(): Promise<{ RecordingStatus: string | null }> {
|
|
139
|
+
throw new Error('not used by the routes');
|
|
140
|
+
}
|
|
141
|
+
|
|
138
142
|
public async LoadShards(sessionID: string): Promise<LedgerShard[]> {
|
|
139
143
|
return [...this.shardsOf(sessionID).values()].sort((a, b) => a.Index - b.Index)
|
|
140
144
|
.map(s => ({ Index: s.Index, StartSample: s.StartSample, SampleCount: s.SampleCount, Data: s.Data }));
|
|
@@ -198,6 +202,7 @@ function callerLedger(inner: FakeLedger): IRealtimeRecordingLedger {
|
|
|
198
202
|
ClaimAssembly: id => inner.ClaimAssembly(id),
|
|
199
203
|
FinishAssembly: input => inner.FinishAssembly(input),
|
|
200
204
|
StampAssemblyGaps: () => inner.StampAssemblyGaps(),
|
|
205
|
+
StampFile: () => inner.StampFile(),
|
|
201
206
|
LoadShards: refuse,
|
|
202
207
|
LoadShardMetadata: refuse,
|
|
203
208
|
CountShardsBelow: refuse,
|
|
@@ -221,7 +226,7 @@ describe('realtime recording REST routes', () => {
|
|
|
221
226
|
const app = express();
|
|
222
227
|
app.use(REALTIME_RECORDING_MOUNT_PATH, CreateRealtimeRecordingRouter(deps));
|
|
223
228
|
server = await new Promise<Server>(resolve => {
|
|
224
|
-
const s = app.listen(0, () => resolve(s));
|
|
229
|
+
const s = app.listen(0, '127.0.0.1', () => resolve(s));
|
|
225
230
|
});
|
|
226
231
|
baseUrl = `http://127.0.0.1:${(server.address() as AddressInfo).port}${REALTIME_RECORDING_MOUNT_PATH}`;
|
|
227
232
|
}
|
|
@@ -264,11 +269,15 @@ describe('realtime recording REST routes', () => {
|
|
|
264
269
|
const user = {
|
|
265
270
|
ID: id,
|
|
266
271
|
IsMagicLinkAnonymous: req.header('x-test-anonymous') === 'true',
|
|
272
|
+
AnonymousVisitorID: scope ?? undefined,
|
|
267
273
|
MagicLinkScope: scope ? { ResourceID: scope } : undefined,
|
|
268
274
|
};
|
|
269
275
|
return id ? { ContextUser: user as unknown as UserInfo, Provider: {} as unknown as DatabaseProviderBase } : null;
|
|
270
276
|
},
|
|
271
|
-
|
|
277
|
+
LoadConversationScope: async session => {
|
|
278
|
+
const row = ledger.Sessions.get(session.ID);
|
|
279
|
+
return { ExternalID: row?.ConversationExternalID ?? null, Readable: row?.ConversationExternalID !== undefined };
|
|
280
|
+
},
|
|
272
281
|
LoadOwnedSession: async (agentSessionId, contextUser) => {
|
|
273
282
|
const row = ledger.Sessions.get(agentSessionId);
|
|
274
283
|
if (!row) {
|
|
@@ -391,6 +400,14 @@ describe('realtime recording REST routes', () => {
|
|
|
391
400
|
expect((await post(`/${SESSION}/end?lastShardIndex=0&capturedSamples=${SHARD_SAMPLES}`, null, OWNER_ID, headers)).status).toBe(200);
|
|
392
401
|
});
|
|
393
402
|
|
|
403
|
+
it('accepts a named owner holding a resource-scoped link on its own unstamped conversation, and refuses another stamp (#4931)', async () => {
|
|
404
|
+
const headers = { 'x-test-scope': 'res-1' };
|
|
405
|
+
addSession(SESSION, { ConversationExternalID: null });
|
|
406
|
+
expect((await post(`/${SESSION}/shard/0`, shardBytes(0), OWNER_ID, headers)).status).toBe(200);
|
|
407
|
+
addSession(SESSION, { ConversationExternalID: 'res-2' });
|
|
408
|
+
expect((await post(`/${SESSION}/shard/1`, shardBytes(1), OWNER_ID, headers)).status).toBe(403);
|
|
409
|
+
});
|
|
410
|
+
|
|
394
411
|
it('leaves a named owner (no scope, not anonymous) unaffected', async () => {
|
|
395
412
|
expect((await postShard(0)).status).toBe(200);
|
|
396
413
|
});
|
|
@@ -27,7 +27,12 @@ function contextFor(user: Partial<UserInfo> & { ID: string }): RealtimeSessionEv
|
|
|
27
27
|
|
|
28
28
|
const named = (id: string) => contextFor({ ID: id });
|
|
29
29
|
const guest = (id: string, scope?: string) =>
|
|
30
|
-
contextFor({
|
|
30
|
+
contextFor({
|
|
31
|
+
ID: id,
|
|
32
|
+
IsMagicLinkAnonymous: true,
|
|
33
|
+
AnonymousVisitorID: scope,
|
|
34
|
+
MagicLinkScope: scope ? { ResourceID: scope } : undefined,
|
|
35
|
+
} as Partial<UserInfo> & { ID: string });
|
|
31
36
|
|
|
32
37
|
function event(over: Partial<RealtimeSessionEventTopicPayload> = {}): RealtimeSessionEventTopicPayload {
|
|
33
38
|
return {
|
|
@@ -75,6 +80,18 @@ describe('RealtimeSessionEventsFilter (per-event gate)', () => {
|
|
|
75
80
|
});
|
|
76
81
|
});
|
|
77
82
|
|
|
83
|
+
describe('a named user holding a resource-scoped magic link (#4931)', () => {
|
|
84
|
+
const namedScoped = (scope: string) => contextFor({ ID: OWNER, MagicLinkScope: { ResourceID: scope } } as Partial<UserInfo> & { ID: string });
|
|
85
|
+
|
|
86
|
+
it('delivers its own session\'s events when the conversation carries no scope stamp', () => {
|
|
87
|
+
expect(filter(event({ ScopeKey: null }), SESSION_ID, namedScoped('res-1'))).toBe(true);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('does not deliver events of a session stamped with its other link\'s scope', () => {
|
|
91
|
+
expect(filter(event({ ScopeKey: 'res-2' }), SESSION_ID, namedScoped('res-1'))).toBe(false);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
78
95
|
it('fails closed on a missing connection identity, owner or argument', () => {
|
|
79
96
|
expect(filter(event(), SESSION_ID, undefined)).toBe(false);
|
|
80
97
|
expect(filter(event(), SESSION_ID, { userPayload: undefined })).toBe(false);
|
|
@@ -98,6 +115,17 @@ describe('AuthorizeRealtimeSessionSubscription (subscribe-time gate)', () => {
|
|
|
98
115
|
expect(d.LoadSession).toHaveBeenCalledWith(SESSION_ID, expect.objectContaining({ ID: OWNER }), expect.anything());
|
|
99
116
|
});
|
|
100
117
|
|
|
118
|
+
it('lets a named scoped owner subscribe to its session on a readable, unstamped conversation, and refuses it when unreadable (#4931)', async () => {
|
|
119
|
+
const namedScoped = contextFor({ ID: OWNER, MagicLinkScope: { ResourceID: 'res-1' } } as Partial<UserInfo> & { ID: string });
|
|
120
|
+
const load = (readable: boolean): SubscriptionAuthorizationDeps => ({
|
|
121
|
+
LoadSession: vi.fn(async () =>
|
|
122
|
+
({ Session: { ID: SESSION_ID, UserID: OWNER }, ConversationExternalID: null, ConversationReadable: readable } as unknown as LoadedSessionForAccess)),
|
|
123
|
+
GetProvider: () => ({}) as IMetadataProvider,
|
|
124
|
+
});
|
|
125
|
+
await expect(AuthorizeRealtimeSessionSubscription({ agentSessionId: SESSION_ID }, namedScoped, load(true))).resolves.toBe(SESSION_ID);
|
|
126
|
+
await expect(AuthorizeRealtimeSessionSubscription({ agentSessionId: SESSION_ID }, namedScoped, load(false))).rejects.toThrow('Realtime session not found.');
|
|
127
|
+
});
|
|
128
|
+
|
|
101
129
|
it('refuses a non-owner with the uniform not-found message', async () => {
|
|
102
130
|
await expect(AuthorizeRealtimeSessionSubscription({ agentSessionId: SESSION_ID }, named(STRANGER), deps({ UserID: OWNER, ExternalID: null }))).rejects.toThrow('Realtime session not found.');
|
|
103
131
|
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import type { IMetadataProvider, UserInfo } from '@memberjunction/core';
|
|
3
|
+
import { LoadOwnedRealtimeSession, RealtimeSessionOwnershipError } from '../../realtimeSessions/ownedRealtimeSession.js';
|
|
4
|
+
|
|
5
|
+
// Fixture UUIDs are assembled at runtime so the brief itself carries no record id.
|
|
6
|
+
const uuid = (a: string, b: string) => [a.repeat(8), b.repeat(4), '4' + b.repeat(3), '8' + b.repeat(3), b.repeat(12)].join('-');
|
|
7
|
+
const ANON = uuid('A', '0');
|
|
8
|
+
const SESSION = uuid('1', '1');
|
|
9
|
+
const CONVERSATION = uuid('2', '2');
|
|
10
|
+
const guest = (scope: string) =>
|
|
11
|
+
({ ID: ANON, IsMagicLinkAnonymous: true, AnonymousVisitorID: scope, MagicLinkScope: { ResourceID: scope } } as unknown as UserInfo);
|
|
12
|
+
|
|
13
|
+
/** A session owned by the shared Anonymous user; its conversation is stamped with visitor 1's scope. */
|
|
14
|
+
function provider(): IMetadataProvider {
|
|
15
|
+
return {
|
|
16
|
+
GetEntityObject: vi.fn(async (entity: string) =>
|
|
17
|
+
entity === 'MJ: AI Agent Sessions'
|
|
18
|
+
? { UserID: ANON, ConversationID: CONVERSATION, Load: vi.fn(async () => true) }
|
|
19
|
+
: { ExternalID: 'scope-visitor-1', Load: vi.fn(async () => true) }),
|
|
20
|
+
} as unknown as IMetadataProvider;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
describe('LoadOwnedRealtimeSession with two anonymous visitors', () => {
|
|
24
|
+
it('allows the visitor whose scope matches', async () => {
|
|
25
|
+
await expect(LoadOwnedRealtimeSession(SESSION, guest('scope-visitor-1'), provider())).resolves.toBeDefined();
|
|
26
|
+
});
|
|
27
|
+
it('refuses a second anonymous visitor (same Anonymous user id, different scope)', async () => {
|
|
28
|
+
await expect(LoadOwnedRealtimeSession(SESSION, guest('scope-visitor-2'), provider()))
|
|
29
|
+
.rejects.toBeInstanceOf(RealtimeSessionOwnershipError);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import type { IMetadataProvider, UserInfo } from '@memberjunction/core';
|
|
3
|
+
import { LoadOwnedRealtimeSession, RealtimeSessionOwnershipError } from '../../realtimeSessions/ownedRealtimeSession.js';
|
|
4
|
+
|
|
5
|
+
// Fixture UUIDs are assembled at runtime so no literal record id appears in the source.
|
|
6
|
+
const uuid = (a: string, b: string) => [a.repeat(8), b.repeat(4), '4' + b.repeat(3), '8' + b.repeat(3), b.repeat(12)].join('-');
|
|
7
|
+
const NAMED = uuid('B', '1');
|
|
8
|
+
const SESSION = uuid('1', '1');
|
|
9
|
+
const CONVERSATION = uuid('2', '2');
|
|
10
|
+
|
|
11
|
+
/** A named magic-link user (found by email) holding a link scoped to `scope`. */
|
|
12
|
+
const namedScoped = (scope: string) => ({ ID: NAMED, IsMagicLinkAnonymous: false, MagicLinkScope: { ResourceID: scope } } as unknown as UserInfo);
|
|
13
|
+
|
|
14
|
+
/** The named user's own session; its conversation loads with `externalID`, or not at all when `readable` is false. */
|
|
15
|
+
function provider(externalID: string | null, readable = true): IMetadataProvider {
|
|
16
|
+
return {
|
|
17
|
+
GetEntityObject: vi.fn(async (entity: string) =>
|
|
18
|
+
entity === 'MJ: AI Agent Sessions'
|
|
19
|
+
? { UserID: NAMED, ConversationID: CONVERSATION, Load: vi.fn(async () => true) }
|
|
20
|
+
: { ExternalID: externalID, Load: vi.fn(async () => readable) }),
|
|
21
|
+
} as unknown as IMetadataProvider;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
describe('LoadOwnedRealtimeSession for a named user with a resource-scoped link', () => {
|
|
25
|
+
it('allows its own session on a conversation it can read that carries no scope stamp (one started from the Conversations app)', async () => {
|
|
26
|
+
await expect(LoadOwnedRealtimeSession(SESSION, namedScoped('res-1'), provider(null))).resolves.toBeDefined();
|
|
27
|
+
});
|
|
28
|
+
it('allows its own session on a conversation stamped with the same scope', async () => {
|
|
29
|
+
await expect(LoadOwnedRealtimeSession(SESSION, namedScoped('res-1'), provider('res-1'))).resolves.toBeDefined();
|
|
30
|
+
});
|
|
31
|
+
it('refuses its own session on a conversation stamped with another scope (a session from its other link)', async () => {
|
|
32
|
+
await expect(LoadOwnedRealtimeSession(SESSION, namedScoped('res-2'), provider('res-1'))).rejects.toBeInstanceOf(RealtimeSessionOwnershipError);
|
|
33
|
+
});
|
|
34
|
+
it('refuses when it cannot read the conversation (fail closed)', async () => {
|
|
35
|
+
await expect(LoadOwnedRealtimeSession(SESSION, namedScoped('res-1'), provider(null, false))).rejects.toBeInstanceOf(RealtimeSessionOwnershipError);
|
|
36
|
+
});
|
|
37
|
+
});
|