@memberjunction/server 6.2.0-edge.6 → 6.2.0-edge.8
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 +69 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/agentSessions/BridgeHostInstance.d.ts +8 -3
- package/dist/agentSessions/BridgeHostInstance.d.ts.map +1 -1
- package/dist/agentSessions/BridgeHostInstance.js +14 -4
- package/dist/agentSessions/BridgeHostInstance.js.map +1 -1
- package/dist/agentSessions/HandoffOfferExpiry.d.ts +10 -0
- package/dist/agentSessions/HandoffOfferExpiry.d.ts.map +1 -0
- package/dist/agentSessions/HandoffOfferExpiry.js +18 -0
- package/dist/agentSessions/HandoffOfferExpiry.js.map +1 -0
- package/dist/agentSessions/ReturningVisitorRecap.js +1 -0
- package/dist/agentSessions/ReturningVisitorRecap.js.map +1 -1
- package/dist/agentSessions/SessionJanitor.d.ts +31 -8
- package/dist/agentSessions/SessionJanitor.d.ts.map +1 -1
- package/dist/agentSessions/SessionJanitor.js +85 -41
- package/dist/agentSessions/SessionJanitor.js.map +1 -1
- package/dist/agentSessions/SessionManager.d.ts +14 -4
- package/dist/agentSessions/SessionManager.d.ts.map +1 -1
- package/dist/agentSessions/SessionManager.js +28 -4
- package/dist/agentSessions/SessionManager.js.map +1 -1
- package/dist/agentSessions/conversationBrowsers.d.ts +61 -0
- package/dist/agentSessions/conversationBrowsers.d.ts.map +1 -0
- package/dist/agentSessions/conversationBrowsers.js +118 -0
- package/dist/agentSessions/conversationBrowsers.js.map +1 -0
- package/dist/agentSessions/sessionDeadline.d.ts +43 -0
- package/dist/agentSessions/sessionDeadline.d.ts.map +1 -0
- package/dist/agentSessions/sessionDeadline.js +68 -0
- package/dist/agentSessions/sessionDeadline.js.map +1 -0
- package/dist/auth/APIKeyScopeAuth.d.ts.map +1 -1
- package/dist/auth/APIKeyScopeAuth.js +5 -4
- package/dist/auth/APIKeyScopeAuth.js.map +1 -1
- package/dist/auth/loginEmailResolution.d.ts +41 -0
- package/dist/auth/loginEmailResolution.d.ts.map +1 -0
- package/dist/auth/loginEmailResolution.js +47 -0
- package/dist/auth/loginEmailResolution.js.map +1 -0
- package/dist/auth/magicLink/MagicLinkKeys.d.ts +50 -2
- package/dist/auth/magicLink/MagicLinkKeys.d.ts.map +1 -1
- package/dist/auth/magicLink/MagicLinkKeys.js +129 -12
- package/dist/auth/magicLink/MagicLinkKeys.js.map +1 -1
- package/dist/auth/magicLink/MagicLinkRouter.d.ts +3 -0
- package/dist/auth/magicLink/MagicLinkRouter.d.ts.map +1 -1
- package/dist/auth/magicLink/MagicLinkRouter.js +23 -5
- package/dist/auth/magicLink/MagicLinkRouter.js.map +1 -1
- package/dist/auth/magicLink/MagicLinkService.d.ts +39 -5
- package/dist/auth/magicLink/MagicLinkService.d.ts.map +1 -1
- package/dist/auth/magicLink/MagicLinkService.js +221 -9
- package/dist/auth/magicLink/MagicLinkService.js.map +1 -1
- package/dist/auth/magicLink/index.d.ts +1 -1
- package/dist/auth/magicLink/index.d.ts.map +1 -1
- package/dist/auth/magicLink/index.js +1 -1
- package/dist/auth/magicLink/index.js.map +1 -1
- package/dist/auth/magicLink/redeemLanding.d.ts +2 -2
- package/dist/auth/magicLink/redeemLanding.d.ts.map +1 -1
- package/dist/auth/magicLink/redeemLanding.js +7 -4
- package/dist/auth/magicLink/redeemLanding.js.map +1 -1
- package/dist/auth/magicLink/types.d.ts +27 -3
- package/dist/auth/magicLink/types.d.ts.map +1 -1
- package/dist/auth/magicLink/types.js +0 -4
- package/dist/auth/magicLink/types.js.map +1 -1
- package/dist/auth/newUsers.d.ts.map +1 -1
- package/dist/auth/newUsers.js +1 -0
- package/dist/auth/newUsers.js.map +1 -1
- package/dist/backgroundSweepConfigUnits.d.ts +231 -0
- package/dist/backgroundSweepConfigUnits.d.ts.map +1 -0
- package/dist/backgroundSweepConfigUnits.js +180 -0
- package/dist/backgroundSweepConfigUnits.js.map +1 -0
- package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.d.ts.map +1 -1
- package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.js +2 -1
- package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.js.map +1 -1
- package/dist/config.d.ts +906 -8
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +59 -0
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts +2 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +41 -12
- package/dist/context.js.map +1 -1
- package/dist/coreRoutes.d.ts +3 -1
- package/dist/coreRoutes.d.ts.map +1 -1
- package/dist/coreRoutes.js +6 -4
- package/dist/coreRoutes.js.map +1 -1
- package/dist/generated/graphql-schemas/__mj.d.ts +1072 -5
- package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
- package/dist/generated/graphql-schemas/__mj.js +10174 -4618
- package/dist/generated/graphql-schemas/__mj.js.map +1 -1
- package/dist/generic/OrphanedConversationDetailReconciler.d.ts +11 -0
- package/dist/generic/OrphanedConversationDetailReconciler.d.ts.map +1 -1
- package/dist/generic/OrphanedConversationDetailReconciler.js +27 -1
- package/dist/generic/OrphanedConversationDetailReconciler.js.map +1 -1
- package/dist/generic/ResolverBase.d.ts +50 -5
- package/dist/generic/ResolverBase.d.ts.map +1 -1
- package/dist/generic/ResolverBase.js +329 -132
- package/dist/generic/ResolverBase.js.map +1 -1
- package/dist/generic/RunViewResolver.d.ts +4 -0
- package/dist/generic/RunViewResolver.d.ts.map +1 -1
- package/dist/generic/RunViewResolver.js +47 -7
- package/dist/generic/RunViewResolver.js.map +1 -1
- package/dist/globalCors.d.ts.map +1 -1
- package/dist/globalCors.js +1 -0
- package/dist/globalCors.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +201 -43
- package/dist/index.js.map +1 -1
- package/dist/integration/ConnectionPause.d.ts +121 -0
- package/dist/integration/ConnectionPause.d.ts.map +1 -0
- package/dist/integration/ConnectionPause.js +183 -0
- package/dist/integration/ConnectionPause.js.map +1 -0
- package/dist/integration/CustomColumnPromoter.d.ts +72 -6
- package/dist/integration/CustomColumnPromoter.d.ts.map +1 -1
- package/dist/integration/CustomColumnPromoter.js +87 -46
- package/dist/integration/CustomColumnPromoter.js.map +1 -1
- package/dist/integration/EntityMapLifecycle.d.ts +78 -4
- package/dist/integration/EntityMapLifecycle.d.ts.map +1 -1
- package/dist/integration/EntityMapLifecycle.js +199 -5
- package/dist/integration/EntityMapLifecycle.js.map +1 -1
- package/dist/integration/InactiveRowWarnings.d.ts +31 -0
- package/dist/integration/InactiveRowWarnings.d.ts.map +1 -1
- package/dist/integration/InactiveRowWarnings.js +29 -0
- package/dist/integration/InactiveRowWarnings.js.map +1 -1
- package/dist/integration/ObjectNamesByID.d.ts +28 -0
- package/dist/integration/ObjectNamesByID.d.ts.map +1 -0
- package/dist/integration/ObjectNamesByID.js +87 -0
- package/dist/integration/ObjectNamesByID.js.map +1 -0
- package/dist/integration/PostRestartIntrospect.d.ts +26 -0
- package/dist/integration/PostRestartIntrospect.d.ts.map +1 -0
- package/dist/integration/PostRestartIntrospect.js +27 -0
- package/dist/integration/PostRestartIntrospect.js.map +1 -0
- package/dist/integration/RSUPostRestartProgress.d.ts +176 -0
- package/dist/integration/RSUPostRestartProgress.d.ts.map +1 -0
- package/dist/integration/RSUPostRestartProgress.js +323 -0
- package/dist/integration/RSUPostRestartProgress.js.map +1 -0
- package/dist/integration/RSUProgressBridge.d.ts +41 -2
- package/dist/integration/RSUProgressBridge.d.ts.map +1 -1
- package/dist/integration/RSUProgressBridge.js +84 -1
- package/dist/integration/RSUProgressBridge.js.map +1 -1
- package/dist/integration/RunArtifactAuthorization.d.ts +39 -0
- package/dist/integration/RunArtifactAuthorization.d.ts.map +1 -0
- package/dist/integration/RunArtifactAuthorization.js +45 -0
- package/dist/integration/RunArtifactAuthorization.js.map +1 -0
- package/dist/integration/SchemaRefreshLaunch.d.ts +9 -0
- package/dist/integration/SchemaRefreshLaunch.d.ts.map +1 -1
- package/dist/integration/SchemaRefreshLaunch.js +3 -2
- package/dist/integration/SchemaRefreshLaunch.js.map +1 -1
- package/dist/integration/ScopedFieldsForBuild.d.ts +46 -0
- package/dist/integration/ScopedFieldsForBuild.d.ts.map +1 -0
- package/dist/integration/ScopedFieldsForBuild.js +91 -0
- package/dist/integration/ScopedFieldsForBuild.js.map +1 -0
- package/dist/integration/TargetConfigGuard.d.ts +40 -0
- package/dist/integration/TargetConfigGuard.d.ts.map +1 -0
- package/dist/integration/TargetConfigGuard.js +40 -0
- package/dist/integration/TargetConfigGuard.js.map +1 -0
- package/dist/logging/StartupLogger.d.ts +50 -0
- package/dist/logging/StartupLogger.d.ts.map +1 -1
- package/dist/logging/StartupLogger.js +15 -22
- package/dist/logging/StartupLogger.js.map +1 -1
- package/dist/middleware/BaseServerMiddleware.d.ts +21 -3
- package/dist/middleware/BaseServerMiddleware.d.ts.map +1 -1
- package/dist/middleware/BaseServerMiddleware.js +4 -2
- package/dist/middleware/BaseServerMiddleware.js.map +1 -1
- package/dist/realtimeSessions/ownedRealtimeSession.d.ts.map +1 -1
- package/dist/realtimeSessions/ownedRealtimeSession.js +2 -3
- package/dist/realtimeSessions/ownedRealtimeSession.js.map +1 -1
- package/dist/realtimeSessions/sessionAccess.d.ts +15 -0
- package/dist/realtimeSessions/sessionAccess.d.ts.map +1 -1
- package/dist/realtimeSessions/sessionAccess.js +22 -0
- package/dist/realtimeSessions/sessionAccess.js.map +1 -1
- package/dist/realtimeWidget/WidgetSessionService.d.ts.map +1 -1
- package/dist/realtimeWidget/WidgetSessionService.js +2 -0
- package/dist/realtimeWidget/WidgetSessionService.js.map +1 -1
- package/dist/realtimeWidget/visitorIdentity.d.ts.map +1 -1
- package/dist/realtimeWidget/visitorIdentity.js +1 -0
- package/dist/realtimeWidget/visitorIdentity.js.map +1 -1
- package/dist/realtimeWidget/widgetCore.d.ts.map +1 -1
- package/dist/realtimeWidget/widgetCore.js +4 -7
- package/dist/realtimeWidget/widgetCore.js.map +1 -1
- package/dist/realtimeWidget/widgetGuestElevation.js +1 -0
- package/dist/realtimeWidget/widgetGuestElevation.js.map +1 -1
- package/dist/requestOrigin.d.ts +22 -0
- package/dist/requestOrigin.d.ts.map +1 -0
- package/dist/requestOrigin.js +60 -0
- package/dist/requestOrigin.js.map +1 -0
- package/dist/resolvers/AgentRunStatusPublisher.d.ts +10 -0
- package/dist/resolvers/AgentRunStatusPublisher.d.ts.map +1 -1
- package/dist/resolvers/AgentRunStatusPublisher.js +18 -2
- package/dist/resolvers/AgentRunStatusPublisher.js.map +1 -1
- package/dist/resolvers/AgentSessionResolver.d.ts.map +1 -1
- package/dist/resolvers/AgentSessionResolver.js +6 -2
- package/dist/resolvers/AgentSessionResolver.js.map +1 -1
- package/dist/resolvers/AutotagPipelineResolver.d.ts +1 -1
- package/dist/resolvers/AutotagPipelineResolver.d.ts.map +1 -1
- package/dist/resolvers/AutotagPipelineResolver.js +61 -16
- package/dist/resolvers/AutotagPipelineResolver.js.map +1 -1
- package/dist/resolvers/ConversationRunEventsResolver.d.ts.map +1 -1
- package/dist/resolvers/ConversationRunEventsResolver.js +2 -0
- package/dist/resolvers/ConversationRunEventsResolver.js.map +1 -1
- package/dist/resolvers/ExecuteRemoteOperationResolver.d.ts +6 -0
- package/dist/resolvers/ExecuteRemoteOperationResolver.d.ts.map +1 -1
- package/dist/resolvers/ExecuteRemoteOperationResolver.js +30 -7
- package/dist/resolvers/ExecuteRemoteOperationResolver.js.map +1 -1
- package/dist/resolvers/GetDataResolver.d.ts.map +1 -1
- package/dist/resolvers/GetDataResolver.js +34 -13
- package/dist/resolvers/GetDataResolver.js.map +1 -1
- package/dist/resolvers/InfoResolver.js +1 -1
- package/dist/resolvers/InfoResolver.js.map +1 -1
- package/dist/resolvers/IntegrationDiscoveryResolver.d.ts +401 -11
- package/dist/resolvers/IntegrationDiscoveryResolver.d.ts.map +1 -1
- package/dist/resolvers/IntegrationDiscoveryResolver.js +2690 -1305
- package/dist/resolvers/IntegrationDiscoveryResolver.js.map +1 -1
- package/dist/resolvers/MCPResolver.d.ts.map +1 -1
- package/dist/resolvers/MCPResolver.js +13 -2
- package/dist/resolvers/MCPResolver.js.map +1 -1
- package/dist/resolvers/QuerySystemUserResolver.d.ts +38 -1
- package/dist/resolvers/QuerySystemUserResolver.d.ts.map +1 -1
- package/dist/resolvers/QuerySystemUserResolver.js +181 -88
- package/dist/resolvers/QuerySystemUserResolver.js.map +1 -1
- package/dist/resolvers/RealtimeBridgeResolver.d.ts.map +1 -1
- package/dist/resolvers/RealtimeBridgeResolver.js +1 -0
- package/dist/resolvers/RealtimeBridgeResolver.js.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts +6 -0
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.js +36 -5
- package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
- package/dist/resolvers/RemoteBrowserActionResolver.d.ts +62 -10
- package/dist/resolvers/RemoteBrowserActionResolver.d.ts.map +1 -1
- package/dist/resolvers/RemoteBrowserActionResolver.js +217 -34
- package/dist/resolvers/RemoteBrowserActionResolver.js.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.d.ts +24 -8
- package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.js +185 -67
- package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
- package/dist/resolvers/RunAIPromptResolver.d.ts +5 -3
- package/dist/resolvers/RunAIPromptResolver.d.ts.map +1 -1
- package/dist/resolvers/RunAIPromptResolver.js +19 -18
- package/dist/resolvers/RunAIPromptResolver.js.map +1 -1
- package/dist/resolvers/RunTemplateResolver.d.ts.map +1 -1
- package/dist/resolvers/RunTemplateResolver.js +3 -1
- package/dist/resolvers/RunTemplateResolver.js.map +1 -1
- package/dist/resolvers/SqlLoggingConfigResolver.d.ts.map +1 -1
- package/dist/resolvers/SqlLoggingConfigResolver.js +7 -8
- package/dist/resolvers/SqlLoggingConfigResolver.js.map +1 -1
- package/dist/resolvers/TagGovernanceResolver.d.ts +12 -0
- package/dist/resolvers/TagGovernanceResolver.d.ts.map +1 -1
- package/dist/resolvers/TagGovernanceResolver.js +84 -0
- package/dist/resolvers/TagGovernanceResolver.js.map +1 -1
- package/dist/resolvers/TelemetryResolver.d.ts +13 -8
- package/dist/resolvers/TelemetryResolver.d.ts.map +1 -1
- package/dist/resolvers/TelemetryResolver.js +40 -9
- package/dist/resolvers/TelemetryResolver.js.map +1 -1
- package/dist/resolvers/meetingRecordingRegistration.d.ts.map +1 -1
- package/dist/resolvers/meetingRecordingRegistration.js +2 -0
- package/dist/resolvers/meetingRecordingRegistration.js.map +1 -1
- package/dist/resolvers/meetings/meetingMutations.d.ts.map +1 -1
- package/dist/resolvers/meetings/meetingMutations.js +34 -4
- package/dist/resolvers/meetings/meetingMutations.js.map +1 -1
- package/dist/rest/MediaAccessKeys.d.ts.map +1 -1
- package/dist/rest/MediaAccessKeys.js +6 -2
- package/dist/rest/MediaAccessKeys.js.map +1 -1
- package/dist/rest/OAuthCallbackHandler.d.ts.map +1 -1
- package/dist/rest/OAuthCallbackHandler.js +11 -5
- package/dist/rest/OAuthCallbackHandler.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 +2 -4
- package/dist/rest/RealtimeRecordingHandler.js.map +1 -1
- package/dist/rest/ViewOperationsHandler.d.ts.map +1 -1
- package/dist/rest/ViewOperationsHandler.js +2 -1
- package/dist/rest/ViewOperationsHandler.js.map +1 -1
- package/dist/rest/setupRESTEndpoints.d.ts +3 -1
- package/dist/rest/setupRESTEndpoints.d.ts.map +1 -1
- package/dist/rest/setupRESTEndpoints.js +11 -2
- package/dist/rest/setupRESTEndpoints.js.map +1 -1
- package/dist/services/DurableEntityActionTaskSubmitter.d.ts.map +1 -1
- package/dist/services/DurableEntityActionTaskSubmitter.js +6 -1
- package/dist/services/DurableEntityActionTaskSubmitter.js.map +1 -1
- package/dist/services/TaskGraphActionRunner.d.ts.map +1 -1
- package/dist/services/TaskGraphActionRunner.js +2 -26
- package/dist/services/TaskGraphActionRunner.js.map +1 -1
- package/dist/services/TaskGraphAgentRunner.d.ts +6 -0
- package/dist/services/TaskGraphAgentRunner.d.ts.map +1 -1
- package/dist/services/TaskGraphAgentRunner.js +38 -0
- package/dist/services/TaskGraphAgentRunner.js.map +1 -1
- package/dist/services/TaskGraphContinuationDeliverer.d.ts.map +1 -1
- package/dist/services/TaskGraphContinuationDeliverer.js +1 -0
- package/dist/services/TaskGraphContinuationDeliverer.js.map +1 -1
- package/dist/services/WorkQueueHostService.d.ts +2 -1
- package/dist/services/WorkQueueHostService.d.ts.map +1 -1
- package/dist/services/WorkQueueHostService.js +19 -4
- package/dist/services/WorkQueueHostService.js.map +1 -1
- package/dist/services/workQueueConfig.d.ts +2 -2
- package/dist/services/workQueueConfig.js +5 -5
- package/dist/services/workQueueConfig.js.map +1 -1
- package/dist/sharedCache.js +1 -1
- package/dist/sharedCache.js.map +1 -1
- package/dist/types.d.ts +17 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js +55 -2
- package/dist/util.js.map +1 -1
- package/package.json +107 -100
- package/src/__tests__/APIKeyScopeAuth.test.ts +4 -8
- package/src/__tests__/ActiveOperations.test.ts +68 -0
- package/src/__tests__/AgentSessionResolver.ownership.test.ts +13 -0
- package/src/__tests__/AssertTargetConfigs.test.ts +75 -0
- package/src/__tests__/BatchApplyParity.test.ts +109 -0
- package/src/__tests__/BridgeHostInstance.test.ts +42 -0
- package/src/__tests__/CollectInactiveRowWarnings.test.ts +115 -0
- package/src/__tests__/ConnectionPause.test.ts +192 -0
- package/src/__tests__/ConnectionPauseResolver.test.ts +166 -0
- package/src/__tests__/ConnectionPauseSave.test.ts +143 -0
- package/src/__tests__/CustomColumnPromoterCompletion.test.ts +142 -0
- package/src/__tests__/DeleteConnectionRemovesPerConnectionCatalog.test.ts +51 -0
- package/src/__tests__/DetachedDiscoveryStart.test.ts +65 -0
- package/src/__tests__/DurableEntityActionTaskSubmitter.test.ts +73 -0
- package/src/__tests__/EntityMapLifecycle.rekey.test.ts +252 -0
- package/src/__tests__/ExecuteRemoteOperationResolver.test.ts +213 -0
- package/src/__tests__/HandoffOfferExpiry.test.ts +40 -0
- package/src/__tests__/IntegrationRunArtifactAuthorization.test.ts +126 -0
- package/src/__tests__/MagicLinkKeys.test.ts +102 -0
- package/src/__tests__/MediaAccessKeys.test.ts +57 -10
- package/src/__tests__/NewArrivalsDefaultDisabled.test.ts +50 -0
- package/src/__tests__/ObjectNamesByID.test.ts +176 -0
- package/src/__tests__/PostRestartMapsInCatalogScope.test.ts +146 -0
- package/src/__tests__/QuerySystemUserResolver.childRows.test.ts +80 -0
- package/src/__tests__/QuerySystemUserResolver.collision.test.ts +200 -0
- package/src/__tests__/QuerySystemUserResolver.permissions.test.ts +132 -0
- package/src/__tests__/RSUPostRestartProgress.test.ts +343 -0
- package/src/__tests__/RSUProgressBridge.test.ts +188 -5
- package/src/__tests__/RSUStartupSweepWiring.test.ts +30 -0
- package/src/__tests__/ReactivateRefreshMessage.test.ts +10 -3
- package/src/__tests__/RealtimeClientSessionResolver.test.ts +22 -0
- package/src/__tests__/{RefreshAdoptsNewColumns.test.ts → RefreshReportsNewColumns.test.ts} +25 -16
- package/src/__tests__/RemoteBrowserActionRecovery.test.ts +287 -0
- package/src/__tests__/RemoteBrowserActionResolver.goalRunLink.test.ts +3 -0
- package/src/__tests__/RemoteBrowserActionResolver.ownership.test.ts +6 -4
- package/src/__tests__/RemoteBrowserAudioStream.test.ts +3 -0
- package/src/__tests__/RemoteBrowserSnapshot.test.ts +3 -0
- package/src/__tests__/ResolverBase.clientOldValues.test.ts +327 -26
- package/src/__tests__/ResolverBase.diagnosticSenderRegistrations.test.ts +26 -0
- package/src/__tests__/ResolverBase.encryptedApiZeroReencrypt.test.ts +307 -0
- package/src/__tests__/ResolverBase.entityMessageRelay.test.ts +53 -2
- package/src/__tests__/ResolverBase.includeBinaryFields.test.ts +60 -0
- package/src/__tests__/RunAIAgentResolver.branch.test.ts +4 -0
- package/src/__tests__/RunAIAgentResolver.consultPredecessor.test.ts +289 -0
- package/src/__tests__/RunAIAgentResolver.handoff.test.ts +649 -0
- package/src/__tests__/RunAIAgentResolver.historyFrom.test.ts +2 -0
- package/src/__tests__/RunAIAgentResolver.runDataGuard.test.ts +2 -0
- package/src/__tests__/RunAIAgentResolver.stoppedPredecessor.test.ts +87 -12
- package/src/__tests__/RunAIAgentResolver.streaming.test.ts +28 -4
- package/src/__tests__/SchemaEvolutionCustomColumns.test.ts +119 -0
- package/src/__tests__/SchemaRefreshLaunch.test.ts +24 -2
- package/src/__tests__/ScopedFieldsForBuild.test.ts +192 -0
- package/src/__tests__/SessionJanitor.test.ts +195 -4
- package/src/__tests__/SessionManager.test.ts +69 -0
- package/src/__tests__/TagGovernanceAndAutotagResolvers.test.ts +261 -0
- package/src/__tests__/TagGovernanceResolver.test.ts +60 -2
- package/src/__tests__/TaskGraphActionRunner.durablePayload.test.ts +2 -2
- package/src/__tests__/TaskGraphAgentRunner.test.ts +272 -16
- package/src/__tests__/ViewOperationsHandler.test.ts +2 -5
- package/src/__tests__/WorkQueueHostService.test.ts +25 -7
- package/src/__tests__/backgroundSweepConfigUnits.test.ts +153 -0
- package/src/__tests__/contextRequestOrigin.test.ts +105 -0
- package/src/__tests__/conversationBrowsers.test.ts +62 -0
- package/src/__tests__/globalCors.test.ts +12 -0
- package/src/__tests__/magicLinkExtensionCapabilities.test.ts +413 -0
- package/src/__tests__/orphanedConversationDetailReconciler.start.test.ts +82 -0
- package/src/__tests__/ownedRealtimeSession.test.ts +14 -0
- package/src/__tests__/realtimeRecordingHandler.test.ts +7 -2
- package/src/__tests__/realtimeSessions/sessionAccess.test.ts +79 -1
- package/src/__tests__/requestOrigin.test.ts +104 -0
- package/src/__tests__/review-capture-valid-token-log.test.ts +98 -0
- package/src/__tests__/runStreams.fieldSecurity.test.ts +5 -5
- package/src/__tests__/securityHardeningPr6034.test.ts +120 -0
- package/src/__tests__/sessionDeadline.test.ts +51 -0
- package/src/__tests__/sharedCache.test.ts +11 -0
- package/src/__tests__/unifiedAuth.test.ts +73 -1
- package/src/agentSessions/BridgeHostInstance.ts +20 -5
- package/src/agentSessions/HandoffOfferExpiry.ts +18 -0
- package/src/agentSessions/ReturningVisitorRecap.ts +1 -0
- package/src/agentSessions/SessionJanitor.ts +92 -42
- package/src/agentSessions/SessionManager.ts +38 -8
- package/src/agentSessions/conversationBrowsers.ts +143 -0
- package/src/agentSessions/sessionDeadline.ts +81 -0
- package/src/auth/APIKeyScopeAuth.ts +5 -4
- package/src/auth/__tests__/loginEmailResolution.test.ts +67 -0
- package/src/auth/loginEmailResolution.ts +67 -0
- package/src/auth/magicLink/MagicLinkKeys.ts +146 -14
- package/src/auth/magicLink/MagicLinkRouter.ts +33 -6
- package/src/auth/magicLink/MagicLinkService.ts +250 -10
- package/src/auth/magicLink/index.ts +1 -1
- package/src/auth/magicLink/redeemLanding.ts +7 -4
- package/src/auth/magicLink/types.ts +28 -3
- package/src/auth/newUsers.ts +1 -0
- package/src/backgroundSweepConfigUnits.ts +213 -0
- package/src/broadcast/BaseCacheInvalidationBroadcastProvider.ts +2 -1
- package/src/config.ts +69 -0
- package/src/context.ts +46 -15
- package/src/coreRoutes.ts +14 -7
- package/src/generated/graphql-schemas/__mj.ts +10360 -6479
- package/src/generic/OrphanedConversationDetailReconciler.ts +32 -1
- package/src/generic/ResolverBase.ts +422 -143
- package/src/generic/RunViewResolver.ts +96 -49
- package/src/globalCors.ts +1 -0
- package/src/index.ts +246 -44
- package/src/integration/ConnectionPause.ts +222 -0
- package/src/integration/CustomColumnPromoter.ts +123 -53
- package/src/integration/EntityMapLifecycle.ts +229 -6
- package/src/integration/InactiveRowWarnings.ts +50 -0
- package/src/integration/ObjectNamesByID.ts +97 -0
- package/src/integration/PostRestartIntrospect.ts +49 -0
- package/src/integration/RSUPostRestartProgress.ts +359 -0
- package/src/integration/RSUProgressBridge.ts +85 -2
- package/src/integration/RunArtifactAuthorization.ts +65 -0
- package/src/integration/SchemaRefreshLaunch.ts +12 -2
- package/src/integration/ScopedFieldsForBuild.ts +113 -0
- package/src/integration/TargetConfigGuard.ts +71 -0
- package/src/logging/StartupLogger.ts +24 -0
- package/src/middleware/BaseServerMiddleware.ts +20 -3
- package/src/realtimeSessions/ownedRealtimeSession.ts +2 -6
- package/src/realtimeSessions/sessionAccess.ts +30 -0
- package/src/realtimeWidget/WidgetSessionService.ts +2 -0
- package/src/realtimeWidget/visitorIdentity.ts +1 -0
- package/src/realtimeWidget/widgetCore.ts +4 -8
- package/src/realtimeWidget/widgetGuestElevation.ts +1 -0
- package/src/requestOrigin.ts +64 -0
- package/src/resolvers/AgentRunStatusPublisher.ts +27 -2
- package/src/resolvers/AgentSessionResolver.ts +6 -2
- package/src/resolvers/AutotagPipelineResolver.ts +59 -14
- package/src/resolvers/ConversationRunEventsResolver.ts +2 -0
- package/src/resolvers/ExecuteRemoteOperationResolver.ts +41 -9
- package/src/resolvers/GetDataResolver.ts +35 -13
- package/src/resolvers/InfoResolver.ts +1 -1
- package/src/resolvers/IntegrationDiscoveryResolver.ts +1522 -203
- package/src/resolvers/MCPResolver.ts +15 -2
- package/src/resolvers/QuerySystemUserResolver.ts +211 -100
- package/src/resolvers/RealtimeBridgeResolver.ts +1 -0
- package/src/resolvers/RealtimeClientSessionResolver.ts +40 -7
- package/src/resolvers/RemoteBrowserActionResolver.ts +242 -32
- package/src/resolvers/RunAIAgentResolver.ts +265 -86
- package/src/resolvers/RunAIPromptResolver.ts +18 -18
- package/src/resolvers/RunTemplateResolver.ts +3 -1
- package/src/resolvers/SqlLoggingConfigResolver.ts +7 -9
- package/src/resolvers/TagGovernanceResolver.ts +65 -0
- package/src/resolvers/TelemetryResolver.ts +44 -9
- package/src/resolvers/__tests__/GetDataResolver.tokens.test.ts +52 -0
- package/src/resolvers/meetingRecordingRegistration.ts +2 -0
- package/src/resolvers/meetings/meetingMutations.ts +37 -7
- package/src/rest/MediaAccessKeys.ts +6 -2
- package/src/rest/OAuthCallbackHandler.ts +13 -5
- package/src/rest/RealtimeRecordingHandler.ts +5 -9
- package/src/rest/ViewOperationsHandler.ts +2 -1
- package/src/rest/setupRESTEndpoints.ts +11 -2
- package/src/services/DurableEntityActionTaskSubmitter.ts +10 -2
- package/src/services/TaskGraphActionRunner.ts +2 -23
- package/src/services/TaskGraphAgentRunner.ts +43 -0
- package/src/services/TaskGraphContinuationDeliverer.ts +1 -0
- package/src/services/WorkQueueHostService.ts +26 -4
- package/src/services/workQueueConfig.ts +5 -5
- package/src/sharedCache.ts +1 -1
- package/src/types.ts +18 -0
- package/src/util.ts +55 -3
- package/src/services/TaskOrchestration-Integration.md +0 -188
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
|
|
3
|
+
const { mockCorsConfig } = vi.hoisted(() => ({
|
|
4
|
+
mockCorsConfig: {
|
|
5
|
+
allowedOrigins: ['https://portal.example.com', 'https://forms.example.com'],
|
|
6
|
+
},
|
|
7
|
+
}));
|
|
8
|
+
|
|
9
|
+
vi.mock('@memberjunction/generic-database-provider', () => ({
|
|
10
|
+
UserCache: { Instance: { Users: [] } },
|
|
11
|
+
}));
|
|
12
|
+
vi.mock('@memberjunction/communication-engine', () => ({
|
|
13
|
+
CommunicationEngine: {
|
|
14
|
+
Instance: {
|
|
15
|
+
Config: vi.fn(async () => undefined),
|
|
16
|
+
SendSingleMessage: vi.fn(async () => ({ Success: true })),
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
}));
|
|
20
|
+
vi.mock('@memberjunction/communication-types', () => ({
|
|
21
|
+
Message: class {
|
|
22
|
+
From = '';
|
|
23
|
+
To = '';
|
|
24
|
+
Subject = '';
|
|
25
|
+
Body = '';
|
|
26
|
+
HTMLBody = '';
|
|
27
|
+
},
|
|
28
|
+
}));
|
|
29
|
+
vi.mock('../config.js', () => ({
|
|
30
|
+
configInfo: {
|
|
31
|
+
cors: mockCorsConfig,
|
|
32
|
+
},
|
|
33
|
+
}));
|
|
34
|
+
vi.mock('../isolatedProvider.js', () => ({
|
|
35
|
+
CreateIsolatedProvider: async () => {
|
|
36
|
+
throw new Error('provisioning is not exercised in this suite');
|
|
37
|
+
},
|
|
38
|
+
}));
|
|
39
|
+
|
|
40
|
+
import type { IMetadataProvider, UserInfo } from '@memberjunction/core';
|
|
41
|
+
import type { MJMagicLinkInviteEntity } from '@memberjunction/core-entities';
|
|
42
|
+
import { CommunicationEngine } from '@memberjunction/communication-engine';
|
|
43
|
+
import type { MagicLinkConfig } from '../config.js';
|
|
44
|
+
import { MagicLinkKeyManager } from '../auth/magicLink/MagicLinkKeys.js';
|
|
45
|
+
import { MagicLinkService, IsMagicLinkRedirectUrlAllowed } from '../auth/magicLink/MagicLinkService.js';
|
|
46
|
+
import { BuildSessionClaims, HashToken } from '../auth/magicLink/magicLinkCore.js';
|
|
47
|
+
|
|
48
|
+
interface StoredInviteState {
|
|
49
|
+
ID: string;
|
|
50
|
+
TokenHash: string;
|
|
51
|
+
Email: string | null;
|
|
52
|
+
ApplicationID: string;
|
|
53
|
+
RoleID: string;
|
|
54
|
+
ExpiresAt: Date;
|
|
55
|
+
MaxUses: number;
|
|
56
|
+
UseCount: number;
|
|
57
|
+
CreatedByUserID: string;
|
|
58
|
+
Status: MJMagicLinkInviteEntity['Status'];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function makeConfig(overrides: Partial<MagicLinkConfig> = {}): MagicLinkConfig {
|
|
62
|
+
return {
|
|
63
|
+
enabled: true,
|
|
64
|
+
audience: 'mj-magic-link',
|
|
65
|
+
restrictedRoleName: 'External Guest',
|
|
66
|
+
grantableRoleNames: ['External Guest'],
|
|
67
|
+
inviteIssuerRoleNames: ['Invite Issuer'],
|
|
68
|
+
defaultExpiresInHours: 24,
|
|
69
|
+
sessionTokenTtlHours: 8,
|
|
70
|
+
explorerUrl: 'https://explorer.example.com',
|
|
71
|
+
communicationProvider: 'SendGrid',
|
|
72
|
+
fromAddress: 'noreply@example.com',
|
|
73
|
+
provisioningGuard: 'block',
|
|
74
|
+
rateLimitWindowMs: 60_000,
|
|
75
|
+
redeemRateLimitMax: 20,
|
|
76
|
+
createRateLimitMax: 20,
|
|
77
|
+
...overrides,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function makeFakeProvider(storedInvites: Map<string, StoredInviteState>): IMetadataProvider {
|
|
82
|
+
let seq = 1;
|
|
83
|
+
const provider = {
|
|
84
|
+
Roles: [{ ID: 'role-guest', Name: 'External Guest' }],
|
|
85
|
+
Applications: [{ ID: 'app-forms', Name: 'Public Forms', Path: 'public-forms' }],
|
|
86
|
+
GetEntityObject: async (entityName: string) => {
|
|
87
|
+
if (entityName === 'MJ: Magic Link Invites') {
|
|
88
|
+
const record: StoredInviteState & {
|
|
89
|
+
NewRecord: () => void;
|
|
90
|
+
Load: (id: string) => Promise<boolean>;
|
|
91
|
+
Save: () => Promise<boolean>;
|
|
92
|
+
LatestResult?: { CompleteMessage: string };
|
|
93
|
+
} = {
|
|
94
|
+
ID: '',
|
|
95
|
+
TokenHash: '',
|
|
96
|
+
Email: null,
|
|
97
|
+
ApplicationID: '',
|
|
98
|
+
RoleID: '',
|
|
99
|
+
ExpiresAt: new Date(),
|
|
100
|
+
MaxUses: 1,
|
|
101
|
+
UseCount: 0,
|
|
102
|
+
CreatedByUserID: '',
|
|
103
|
+
Status: 'Active',
|
|
104
|
+
NewRecord: () => {
|
|
105
|
+
record.ID = `invite-${seq++}`;
|
|
106
|
+
},
|
|
107
|
+
Load: async (id: string) => {
|
|
108
|
+
const existing = storedInvites.get(id);
|
|
109
|
+
if (!existing) {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
Object.assign(record, existing);
|
|
113
|
+
return true;
|
|
114
|
+
},
|
|
115
|
+
Save: async () => {
|
|
116
|
+
storedInvites.set(record.ID, {
|
|
117
|
+
ID: record.ID,
|
|
118
|
+
TokenHash: record.TokenHash,
|
|
119
|
+
Email: record.Email,
|
|
120
|
+
ApplicationID: record.ApplicationID,
|
|
121
|
+
RoleID: record.RoleID,
|
|
122
|
+
ExpiresAt: record.ExpiresAt,
|
|
123
|
+
MaxUses: record.MaxUses,
|
|
124
|
+
UseCount: record.UseCount,
|
|
125
|
+
CreatedByUserID: record.CreatedByUserID,
|
|
126
|
+
Status: record.Status,
|
|
127
|
+
});
|
|
128
|
+
return true;
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
return record;
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
NewRecord: () => undefined,
|
|
135
|
+
Save: async () => true,
|
|
136
|
+
};
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
return provider as object as IMetadataProvider;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
describe('MagicLinkService programmatic extension capabilities (Item 3.6)', () => {
|
|
143
|
+
const ownerUser = {
|
|
144
|
+
ID: 'user-owner',
|
|
145
|
+
Email: 'owner@example.com',
|
|
146
|
+
Type: 'Owner',
|
|
147
|
+
UserRoles: [],
|
|
148
|
+
} as object as UserInfo;
|
|
149
|
+
|
|
150
|
+
const regularUser = {
|
|
151
|
+
ID: 'user-guest',
|
|
152
|
+
Email: 'guest@example.com',
|
|
153
|
+
Type: 'User',
|
|
154
|
+
UserRoles: [{ Role: 'External Guest' }],
|
|
155
|
+
} as object as UserInfo;
|
|
156
|
+
|
|
157
|
+
beforeEach(() => {
|
|
158
|
+
mockCorsConfig.allowedOrigins = ['https://portal.example.com', 'https://forms.example.com'];
|
|
159
|
+
MagicLinkKeyManager.Instance.Initialize();
|
|
160
|
+
vi.clearAllMocks();
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
describe('IsMagicLinkRedirectUrlAllowed', () => {
|
|
164
|
+
it('allows matching http(s) origins and rejects non-http schemes, unlisted origins, and wildcard *', () => {
|
|
165
|
+
const allowed = ['https://explorer.example.com', 'https://forms.example.com:8443'];
|
|
166
|
+
expect(IsMagicLinkRedirectUrlAllowed('https://forms.example.com:8443/respond/abc?step=2', allowed)).toBe(true);
|
|
167
|
+
expect(IsMagicLinkRedirectUrlAllowed('https://evil.example.com/phish', allowed)).toBe(false);
|
|
168
|
+
expect(IsMagicLinkRedirectUrlAllowed('javascript:alert(1)', allowed)).toBe(false);
|
|
169
|
+
expect(IsMagicLinkRedirectUrlAllowed('https://anywhere.example/cb', ['*'])).toBe(false);
|
|
170
|
+
expect(IsMagicLinkRedirectUrlAllowed('https://anywhere.example.org/ok', ['*', ''])).toBe(false);
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
describe('CreateInvite RedirectUrl & SendEmail controls', () => {
|
|
175
|
+
it('binds a permitted RedirectUrl via RS256 and skips default email when SendEmail=false', async () => {
|
|
176
|
+
const invites = new Map<string, StoredInviteState>();
|
|
177
|
+
const provider = makeFakeProvider(invites);
|
|
178
|
+
const service = new MagicLinkService('https://api.example.com', makeConfig());
|
|
179
|
+
|
|
180
|
+
const res = await service.CreateInvite(
|
|
181
|
+
{
|
|
182
|
+
email: 'respondent@example.com',
|
|
183
|
+
ApplicationId: 'app-forms',
|
|
184
|
+
RedirectUrl: 'https://forms.example.com/survey/42',
|
|
185
|
+
SendEmail: false,
|
|
186
|
+
},
|
|
187
|
+
ownerUser,
|
|
188
|
+
provider,
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
expect(res.success).toBe(true);
|
|
192
|
+
expect(res.emailSent).toBe(false);
|
|
193
|
+
expect(res.rawToken).toBeDefined();
|
|
194
|
+
expect(CommunicationEngine.Instance.SendSingleMessage).not.toHaveBeenCalled();
|
|
195
|
+
|
|
196
|
+
const url = new URL(res.redemptionUrl!);
|
|
197
|
+
const rt = url.searchParams.get('rt') ?? undefined;
|
|
198
|
+
expect(rt).toBeTruthy();
|
|
199
|
+
|
|
200
|
+
// Validated against matching rawToken
|
|
201
|
+
expect(service.ResolveVerifiedRedirectUrl(res.rawToken!, rt)).toBe('https://forms.example.com/survey/42');
|
|
202
|
+
// Fails closed if an attacker swaps `rt` onto a different invite's token
|
|
203
|
+
expect(service.ResolveVerifiedRedirectUrl('mj_ml_0000000000000000000000000000000000000000000000000000000000000000', rt)).toBeUndefined();
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it('rejects disallowed RedirectUrl origins with invalid_redirect_url', async () => {
|
|
207
|
+
const invites = new Map<string, StoredInviteState>();
|
|
208
|
+
const provider = makeFakeProvider(invites);
|
|
209
|
+
const service = new MagicLinkService('https://api.example.com', makeConfig());
|
|
210
|
+
|
|
211
|
+
const res = await service.CreateInvite(
|
|
212
|
+
{
|
|
213
|
+
email: 'respondent@example.com',
|
|
214
|
+
ApplicationId: 'app-forms',
|
|
215
|
+
RedirectUrl: 'https://attacker.invalid/steal',
|
|
216
|
+
},
|
|
217
|
+
ownerUser,
|
|
218
|
+
provider,
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
expect(res.success).toBe(false);
|
|
222
|
+
expect(res.errorCode).toBe('invalid_redirect_url');
|
|
223
|
+
expect(invites.size).toBe(0);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it('ignores wildcard * in cors.allowedOrigins while honoring explicit magicLink.allowedRedirectOrigins', async () => {
|
|
227
|
+
mockCorsConfig.allowedOrigins = ['*'];
|
|
228
|
+
const invites = new Map<string, StoredInviteState>();
|
|
229
|
+
const provider = makeFakeProvider(invites);
|
|
230
|
+
const service = new MagicLinkService(
|
|
231
|
+
'https://api.example.com',
|
|
232
|
+
makeConfig({
|
|
233
|
+
explorerUrl: 'https://explorer.example.com',
|
|
234
|
+
allowedRedirectOrigins: ['https://surveys.example.com'],
|
|
235
|
+
}),
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
// Wildcard CORS does NOT open up arbitrary redirect origins
|
|
239
|
+
const rejected = await service.CreateInvite(
|
|
240
|
+
{
|
|
241
|
+
email: 'respondent@example.com',
|
|
242
|
+
ApplicationId: 'app-forms',
|
|
243
|
+
RedirectUrl: 'https://anywhere.example/cb',
|
|
244
|
+
SendEmail: false,
|
|
245
|
+
},
|
|
246
|
+
ownerUser,
|
|
247
|
+
provider,
|
|
248
|
+
);
|
|
249
|
+
expect(rejected.success).toBe(false);
|
|
250
|
+
expect(rejected.errorCode).toBe('invalid_redirect_url');
|
|
251
|
+
|
|
252
|
+
// Explicit magicLink.allowedRedirectOrigins works cleanly under wildcard CORS
|
|
253
|
+
const permitted = await service.CreateInvite(
|
|
254
|
+
{
|
|
255
|
+
email: 'respondent@example.com',
|
|
256
|
+
ApplicationId: 'app-forms',
|
|
257
|
+
RedirectUrl: 'https://surveys.example.com/respond/99',
|
|
258
|
+
SendEmail: false,
|
|
259
|
+
},
|
|
260
|
+
ownerUser,
|
|
261
|
+
provider,
|
|
262
|
+
);
|
|
263
|
+
expect(permitted.success).toBe(true);
|
|
264
|
+
const url = new URL(permitted.redemptionUrl!);
|
|
265
|
+
const rt = url.searchParams.get('rt') ?? undefined;
|
|
266
|
+
expect(service.ResolveVerifiedRedirectUrl(permitted.rawToken!, rt)).toBe('https://surveys.example.com/respond/99');
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
describe('RevokeInvite & UpdateInviteExpiry', () => {
|
|
271
|
+
it('revokes an active invite and enforces issuer authorization', async () => {
|
|
272
|
+
const invites = new Map<string, StoredInviteState>();
|
|
273
|
+
const provider = makeFakeProvider(invites);
|
|
274
|
+
const service = new MagicLinkService('https://api.example.com', makeConfig());
|
|
275
|
+
|
|
276
|
+
const created = await service.CreateInvite(
|
|
277
|
+
{ email: 'person@example.com', ApplicationId: 'app-forms', SendEmail: false },
|
|
278
|
+
ownerUser,
|
|
279
|
+
provider,
|
|
280
|
+
);
|
|
281
|
+
expect(created.success).toBe(true);
|
|
282
|
+
|
|
283
|
+
const denied = await service.RevokeInvite(created.inviteId!, regularUser, provider);
|
|
284
|
+
expect(denied.success).toBe(false);
|
|
285
|
+
expect(denied.errorCode).toBe('forbidden');
|
|
286
|
+
|
|
287
|
+
const revoked = await service.RevokeInvite(created.inviteId!, ownerUser, provider);
|
|
288
|
+
expect(revoked.success).toBe(true);
|
|
289
|
+
expect(revoked.status).toBe('Revoked');
|
|
290
|
+
expect(invites.get(created.inviteId!)?.Status).toBe('Revoked');
|
|
291
|
+
|
|
292
|
+
// Cannot extend a revoked invite
|
|
293
|
+
const extendRevoked = await service.UpdateInviteExpiry(
|
|
294
|
+
created.inviteId!,
|
|
295
|
+
new Date(Date.now() + 72 * 3600 * 1000),
|
|
296
|
+
ownerUser,
|
|
297
|
+
provider,
|
|
298
|
+
);
|
|
299
|
+
expect(extendRevoked.success).toBe(false);
|
|
300
|
+
expect(extendRevoked.errorCode).toBe('invalid_state');
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
it('updates expiry on an active or expired invite and re-activates expired status when extended', async () => {
|
|
304
|
+
const invites = new Map<string, StoredInviteState>();
|
|
305
|
+
invites.set('inv-expired', {
|
|
306
|
+
ID: 'inv-expired',
|
|
307
|
+
TokenHash: HashToken('mj_ml_sample'),
|
|
308
|
+
Email: 'late@example.com',
|
|
309
|
+
ApplicationID: 'app-forms',
|
|
310
|
+
RoleID: 'role-guest',
|
|
311
|
+
ExpiresAt: new Date(Date.now() - 3600_000),
|
|
312
|
+
MaxUses: 1,
|
|
313
|
+
UseCount: 0,
|
|
314
|
+
CreatedByUserID: ownerUser.ID,
|
|
315
|
+
Status: 'Expired',
|
|
316
|
+
});
|
|
317
|
+
const provider = makeFakeProvider(invites);
|
|
318
|
+
const service = new MagicLinkService('https://api.example.com', makeConfig());
|
|
319
|
+
|
|
320
|
+
const newExpiry = new Date(Date.now() + 48 * 3600 * 1000);
|
|
321
|
+
const updated = await service.UpdateInviteExpiry('inv-expired', newExpiry, ownerUser, provider);
|
|
322
|
+
expect(updated.success).toBe(true);
|
|
323
|
+
expect(updated.status).toBe('Active');
|
|
324
|
+
expect(updated.expiresAt).toBe(newExpiry.toISOString());
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
it('keeps &rt= redirect binding valid after UpdateInviteExpiry extends an invite past its initial expiry, and guards UseCount/past-expiry transitions', async () => {
|
|
328
|
+
const invites = new Map<string, StoredInviteState>();
|
|
329
|
+
const provider = makeFakeProvider(invites);
|
|
330
|
+
const service = new MagicLinkService('https://api.example.com', makeConfig());
|
|
331
|
+
|
|
332
|
+
const rawToken = 'mj_ml_1111222233334444555566667777888899990000aaaabbbbccccddddeeeeffff';
|
|
333
|
+
const tokenHash = HashToken(rawToken);
|
|
334
|
+
const pastExpiry = new Date(Date.now() - 3600_000);
|
|
335
|
+
// Mint redirect binding with a past ExpiresAt epoch
|
|
336
|
+
const rt = MagicLinkKeyManager.Instance.SignRedirectBinding(
|
|
337
|
+
tokenHash,
|
|
338
|
+
'https://forms.example.com/survey/extended',
|
|
339
|
+
Math.floor(pastExpiry.getTime() / 1000),
|
|
340
|
+
);
|
|
341
|
+
|
|
342
|
+
invites.set('inv-extend-rt', {
|
|
343
|
+
ID: 'inv-extend-rt',
|
|
344
|
+
TokenHash: tokenHash,
|
|
345
|
+
Email: 'respondent@example.com',
|
|
346
|
+
ApplicationID: 'app-forms',
|
|
347
|
+
RoleID: 'role-guest',
|
|
348
|
+
ExpiresAt: pastExpiry,
|
|
349
|
+
MaxUses: 1,
|
|
350
|
+
UseCount: 0,
|
|
351
|
+
CreatedByUserID: ownerUser.ID,
|
|
352
|
+
Status: 'Expired',
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
// Extend invite into the future without re-issuing the URL
|
|
356
|
+
const extendedExpiry = new Date(Date.now() + 24 * 3600_000);
|
|
357
|
+
const extended = await service.UpdateInviteExpiry('inv-extend-rt', extendedExpiry, ownerUser, provider);
|
|
358
|
+
expect(extended.success).toBe(true);
|
|
359
|
+
expect(extended.status).toBe('Active');
|
|
360
|
+
|
|
361
|
+
// Original &rt= binding still resolves the allowlisted RedirectUrl
|
|
362
|
+
expect(service.ResolveVerifiedRedirectUrl(rawToken, rt)).toBe('https://forms.example.com/survey/extended');
|
|
363
|
+
|
|
364
|
+
// Shortening an Active invite to <= now transitions Status to Expired
|
|
365
|
+
const shortened = await service.UpdateInviteExpiry('inv-extend-rt', new Date(Date.now() - 1000), ownerUser, provider);
|
|
366
|
+
expect(shortened.success).toBe(true);
|
|
367
|
+
expect(shortened.status).toBe('Expired');
|
|
368
|
+
|
|
369
|
+
// An Expired invite whose UseCount >= MaxUses is NOT reactivated to Active
|
|
370
|
+
invites.set('inv-exhausted-expired', {
|
|
371
|
+
ID: 'inv-exhausted-expired',
|
|
372
|
+
TokenHash: HashToken('mj_ml_exhausted'),
|
|
373
|
+
Email: 'used@example.com',
|
|
374
|
+
ApplicationID: 'app-forms',
|
|
375
|
+
RoleID: 'role-guest',
|
|
376
|
+
ExpiresAt: pastExpiry,
|
|
377
|
+
MaxUses: 1,
|
|
378
|
+
UseCount: 1,
|
|
379
|
+
CreatedByUserID: ownerUser.ID,
|
|
380
|
+
Status: 'Expired',
|
|
381
|
+
});
|
|
382
|
+
const notReactivated = await service.UpdateInviteExpiry('inv-exhausted-expired', extendedExpiry, ownerUser, provider);
|
|
383
|
+
expect(notReactivated.success).toBe(true);
|
|
384
|
+
expect(notReactivated.status).toBe('Expired');
|
|
385
|
+
});
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
describe('VerifySessionToken', () => {
|
|
389
|
+
it('verifies a signed magic-link session JWT and rejects wrong audience or issuer', () => {
|
|
390
|
+
const service = new MagicLinkService('https://api.example.com', makeConfig());
|
|
391
|
+
const nowSeconds = Math.floor(Date.now() / 1000);
|
|
392
|
+
const claims = BuildSessionClaims({
|
|
393
|
+
issuer: 'https://api.example.com',
|
|
394
|
+
audience: 'mj-magic-link',
|
|
395
|
+
inviteId: 'inv-123',
|
|
396
|
+
email: 'person@example.com',
|
|
397
|
+
applicationId: 'app-forms',
|
|
398
|
+
roleName: 'External Guest',
|
|
399
|
+
nowSeconds,
|
|
400
|
+
ttlSeconds: 3600,
|
|
401
|
+
});
|
|
402
|
+
const jwtToken = MagicLinkKeyManager.Instance.Sign(claims);
|
|
403
|
+
|
|
404
|
+
const verified = service.VerifySessionToken(jwtToken);
|
|
405
|
+
expect(verified).not.toBeNull();
|
|
406
|
+
expect(verified?.email).toBe('person@example.com');
|
|
407
|
+
expect(verified?.mj_app_id).toBe('app-forms');
|
|
408
|
+
|
|
409
|
+
const wrongIssuerService = new MagicLinkService('https://other-api.example.com', makeConfig());
|
|
410
|
+
expect(wrongIssuerService.VerifySessionToken(jwtToken)).toBeNull();
|
|
411
|
+
});
|
|
412
|
+
});
|
|
413
|
+
});
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `conversationDetailReconciler` settings: the reconciler always runs its startup pass, and repeats
|
|
3
|
+
* on the configured interval only while enabled. Disabled, an idle server sends it no periodic query.
|
|
4
|
+
*/
|
|
5
|
+
import 'reflect-metadata';
|
|
6
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
7
|
+
import type { UserInfo } from '@memberjunction/core';
|
|
8
|
+
|
|
9
|
+
const { mockRunView } = vi.hoisted(() => ({ mockRunView: vi.fn() }));
|
|
10
|
+
|
|
11
|
+
vi.mock('@memberjunction/sqlserver-dataprovider', () => ({
|
|
12
|
+
UserCache: { Instance: { Users: [] } },
|
|
13
|
+
}));
|
|
14
|
+
|
|
15
|
+
vi.mock('@memberjunction/core', () => {
|
|
16
|
+
class MockRunView {
|
|
17
|
+
public static FromMetadataProvider(): MockRunView {
|
|
18
|
+
return new MockRunView();
|
|
19
|
+
}
|
|
20
|
+
public async RunView(...args: unknown[]): Promise<unknown> {
|
|
21
|
+
return mockRunView(...args);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return { LogError: vi.fn(), LogStatus: vi.fn(), RunView: MockRunView };
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
import { StartOrphanedConversationDetailReconciler } from '../generic/OrphanedConversationDetailReconciler.js';
|
|
28
|
+
|
|
29
|
+
const USER = { ID: 'U1' } as unknown as UserInfo;
|
|
30
|
+
const PROVIDER = {
|
|
31
|
+
EntityByName: (name: string) =>
|
|
32
|
+
name === 'MJ: AI Agent Runs' ? { SchemaName: '__mj', BaseView: 'vwAIAgentRuns' } : undefined,
|
|
33
|
+
QuoteIdentifier: (name: string) => `[${name}]`,
|
|
34
|
+
QuoteSchemaAndView: (schema: string, view: string) => `[${schema}].[${view}]`,
|
|
35
|
+
} as never;
|
|
36
|
+
const HOUR = 60 * 60_000;
|
|
37
|
+
|
|
38
|
+
/** Passes run so far: each pass starts with one read of in-progress conversation details. */
|
|
39
|
+
const passes = (): number => mockRunView.mock.calls.length;
|
|
40
|
+
|
|
41
|
+
describe('StartOrphanedConversationDetailReconciler', () => {
|
|
42
|
+
beforeEach(() => {
|
|
43
|
+
vi.useFakeTimers();
|
|
44
|
+
mockRunView.mockReset();
|
|
45
|
+
mockRunView.mockResolvedValue({ Success: true, Results: [] });
|
|
46
|
+
});
|
|
47
|
+
afterEach(() => {
|
|
48
|
+
vi.clearAllTimers();
|
|
49
|
+
vi.useRealTimers();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('enabled: runs a pass now, then one every interval', async () => {
|
|
53
|
+
const timer = StartOrphanedConversationDetailReconciler(PROVIDER, USER, { enabled: true, sweepIntervalMs: 60_000 });
|
|
54
|
+
await vi.advanceTimersByTimeAsync(5 * 60_000);
|
|
55
|
+
|
|
56
|
+
expect(timer).not.toBeNull();
|
|
57
|
+
expect(passes()).toBe(6);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('disabled: runs the startup pass only, and schedules nothing', async () => {
|
|
61
|
+
const timer = StartOrphanedConversationDetailReconciler(PROVIDER, USER, { enabled: false, sweepIntervalMs: 60_000 });
|
|
62
|
+
await vi.advanceTimersByTimeAsync(HOUR);
|
|
63
|
+
|
|
64
|
+
expect(passes()).toBe(1);
|
|
65
|
+
expect(timer).toBeNull();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('with no settings keeps the shipped 5-minute repeat', async () => {
|
|
69
|
+
StartOrphanedConversationDetailReconciler(PROVIDER, USER);
|
|
70
|
+
await vi.advanceTimersByTimeAsync(HOUR);
|
|
71
|
+
|
|
72
|
+
expect(passes()).toBe(13);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('a failing pass is logged, not thrown, and the next pass still runs', async () => {
|
|
76
|
+
mockRunView.mockRejectedValueOnce(new Error('db down'));
|
|
77
|
+
StartOrphanedConversationDetailReconciler(PROVIDER, USER, { enabled: true, sweepIntervalMs: 60_000 });
|
|
78
|
+
await vi.advanceTimersByTimeAsync(60_000);
|
|
79
|
+
|
|
80
|
+
expect(passes()).toBe(2);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
@@ -36,4 +36,18 @@ describe('LoadOwnedRealtimeSession', () => {
|
|
|
36
36
|
await expect(LoadOwnedRealtimeSession(SESSION, STRANGER, providerWith({ UserID: OWNER.ID })))
|
|
37
37
|
.rejects.toThrow('Not authorized: you do not own this realtime session');
|
|
38
38
|
});
|
|
39
|
+
|
|
40
|
+
it('skips loading MJ: Conversations when the caller is a standard named user without a scope identity', async () => {
|
|
41
|
+
const getEntityObject = vi.fn(async (name: string) => ({
|
|
42
|
+
UserID: OWNER.ID,
|
|
43
|
+
ConversationID: '22222222-2222-4222-8222-222222222222',
|
|
44
|
+
Load: vi.fn(async () => name === 'MJ: AI Agent Sessions'),
|
|
45
|
+
}));
|
|
46
|
+
const prov = { GetEntityObject: getEntityObject } as unknown as IMetadataProvider;
|
|
47
|
+
|
|
48
|
+
await LoadOwnedRealtimeSession(SESSION, OWNER, prov);
|
|
49
|
+
|
|
50
|
+
expect(getEntityObject).toHaveBeenCalledTimes(1);
|
|
51
|
+
expect(getEntityObject).toHaveBeenCalledWith('MJ: AI Agent Sessions', OWNER);
|
|
52
|
+
});
|
|
39
53
|
});
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
CreateRealtimeRecordingRouter, FINAL_TERMINAL_RECHECK_MS, REALTIME_RECORDING_MOUNT_PATH, type RealtimeRecordingRouterDeps,
|
|
26
26
|
} from '../rest/RealtimeRecordingHandler.js';
|
|
27
27
|
import { RealtimeSessionNotFoundError, RealtimeSessionOwnershipError } from '../realtimeSessions/ownedRealtimeSession.js';
|
|
28
|
+
import { AuthorizeSessionAccess } from '../realtimeSessions/sessionAccess.js';
|
|
28
29
|
import { RecordingAssemblyScheduler, type IRecordingAssemblyScheduler } from '../realtimeSessions/RecordingAssemblyScheduler.js';
|
|
29
30
|
|
|
30
31
|
const SESSION = '11111111-1111-4111-8111-111111111111';
|
|
@@ -274,9 +275,13 @@ describe('realtime recording REST routes', () => {
|
|
|
274
275
|
};
|
|
275
276
|
return id ? { ContextUser: user as unknown as UserInfo, Provider: {} as unknown as DatabaseProviderBase } : null;
|
|
276
277
|
},
|
|
277
|
-
|
|
278
|
+
// The real scope rule over this test's conversation rows (no database behind them).
|
|
279
|
+
AuthorizeSession: async (session, contextUser) => {
|
|
278
280
|
const row = ledger.Sessions.get(session.ID);
|
|
279
|
-
return
|
|
281
|
+
return AuthorizeSessionAccess(
|
|
282
|
+
{ SessionUserID: session.UserID, ConversationExternalID: row?.ConversationExternalID ?? null, ConversationReadable: row?.ConversationExternalID !== undefined },
|
|
283
|
+
contextUser,
|
|
284
|
+
);
|
|
280
285
|
},
|
|
281
286
|
LoadOwnedSession: async (agentSessionId, contextUser) => {
|
|
282
287
|
const row = ledger.Sessions.get(agentSessionId);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { describe, it, expect, vi } from 'vitest';
|
|
2
2
|
import type { IMetadataProvider, UserInfo } from '@memberjunction/core';
|
|
3
|
-
import {
|
|
3
|
+
import type { MJAIAgentSessionEntity } from '@memberjunction/core-entities';
|
|
4
|
+
import { AuthorizeSessionAccess, AuthorizeConversationTurn, AuthorizeAnonymousRoomSessions, LoadSessionForAccess, LoadSessionConversationScope, LoadConversationScope, AuthorizeLoadedSession } from '../../realtimeSessions/sessionAccess.js';
|
|
4
5
|
|
|
5
6
|
const OWNER = 'AAAAAAAA-0000-4000-8000-000000000001';
|
|
6
7
|
const OTHER = 'AAAAAAAA-0000-4000-8000-000000000002';
|
|
@@ -190,6 +191,25 @@ describe('LoadSessionForAccess', () => {
|
|
|
190
191
|
});
|
|
191
192
|
});
|
|
192
193
|
|
|
194
|
+
describe('LoadSessionConversationScope', () => {
|
|
195
|
+
const sessionWithConversation = { ID: SESSION_ID, UserID: OWNER, ConversationID: CONVERSATION_ID } as unknown as MJAIAgentSessionEntity;
|
|
196
|
+
|
|
197
|
+
it('reads the conversation for an unscoped named user too, so its result is the conversation as that user sees it', async () => {
|
|
198
|
+
const provider = fakeProvider({ conversation: { ExternalID: 'scope-1' } });
|
|
199
|
+
const scope = await LoadSessionConversationScope(sessionWithConversation, USER, provider);
|
|
200
|
+
expect(scope).toEqual({ ExternalID: 'scope-1', Readable: true });
|
|
201
|
+
expect(provider.GetEntityObject).toHaveBeenCalledTimes(1);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('reads the conversation when the caller carries a scope identity', async () => {
|
|
205
|
+
const scopedUser = { ID: OWNER, MagicLinkScope: { ResourceID: 'scope-1' } } as unknown as UserInfo;
|
|
206
|
+
const provider = fakeProvider({ conversation: { ExternalID: 'scope-1' } });
|
|
207
|
+
const scope = await LoadSessionConversationScope(sessionWithConversation, scopedUser, provider);
|
|
208
|
+
expect(scope).toEqual({ ExternalID: 'scope-1', Readable: true });
|
|
209
|
+
expect(provider.GetEntityObject).toHaveBeenCalledTimes(1);
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
|
|
193
213
|
describe('LoadConversationScope (a conversation addressed directly, e.g. by a text agent turn)', () => {
|
|
194
214
|
it('reports the owner and scope id of a readable conversation', async () => {
|
|
195
215
|
const scope = await LoadConversationScope(CONVERSATION_ID, USER, fakeProvider({ conversation: { UserID: OWNER, ExternalID: 'scope-1' } }));
|
|
@@ -266,3 +286,61 @@ describe('AuthorizeAnonymousRoomSessions (who of the anonymous visitors owns a L
|
|
|
266
286
|
});
|
|
267
287
|
});
|
|
268
288
|
|
|
289
|
+
|
|
290
|
+
describe('AuthorizeLoadedSession', () => {
|
|
291
|
+
const principals: Array<[string, UserInfo]> = [
|
|
292
|
+
['named owner, no scope', { ID: OWNER } as UserInfo],
|
|
293
|
+
['named owner, scope-1', { ID: OWNER, MagicLinkScope: { ResourceID: 'scope-1' } } as unknown as UserInfo],
|
|
294
|
+
['named owner, scope-2', { ID: OWNER, MagicLinkScope: { ResourceID: 'scope-2' } } as unknown as UserInfo],
|
|
295
|
+
['anonymous, scope-1', { ID: OWNER, IsMagicLinkAnonymous: true, AnonymousVisitorID: 'scope-1', MagicLinkScope: { ResourceID: 'scope-1' } } as unknown as UserInfo],
|
|
296
|
+
['anonymous, scope-2', { ID: OWNER, IsMagicLinkAnonymous: true, AnonymousVisitorID: 'scope-2', MagicLinkScope: { ResourceID: 'scope-2' } } as unknown as UserInfo],
|
|
297
|
+
['anonymous, visitor id only', { ID: OWNER, IsMagicLinkAnonymous: true, AnonymousVisitorID: 'scope-1' } as unknown as UserInfo],
|
|
298
|
+
['anonymous, no scope', { ID: OWNER, IsMagicLinkAnonymous: true } as unknown as UserInfo],
|
|
299
|
+
['named non-owner', { ID: OTHER } as UserInfo],
|
|
300
|
+
];
|
|
301
|
+
const conversations: Array<[string, { conversationId: string | null; rows: Parameters<typeof fakeProvider>[0] }]> = [
|
|
302
|
+
['no conversation', { conversationId: null, rows: {} }],
|
|
303
|
+
['missing or refused', { conversationId: CONVERSATION_ID, rows: { conversation: null } }],
|
|
304
|
+
['read fails', { conversationId: CONVERSATION_ID, rows: { conversation: {}, conversationThrows: true } }],
|
|
305
|
+
['unstamped', { conversationId: CONVERSATION_ID, rows: { conversation: { ExternalID: null } } }],
|
|
306
|
+
['stamped scope-1', { conversationId: CONVERSATION_ID, rows: { conversation: { ExternalID: 'scope-1' } } }],
|
|
307
|
+
['stamped scope-2', { conversationId: CONVERSATION_ID, rows: { conversation: { ExternalID: 'scope-2' } } }],
|
|
308
|
+
];
|
|
309
|
+
|
|
310
|
+
for (const [principalName, principal] of principals) {
|
|
311
|
+
for (const [conversationName, { conversationId, rows }] of conversations) {
|
|
312
|
+
it(`decides as if it always read the conversation: ${principalName} × ${conversationName}`, async () => {
|
|
313
|
+
const session = { ID: SESSION_ID, UserID: OWNER, ConversationID: conversationId } as unknown as MJAIAgentSessionEntity;
|
|
314
|
+
const scope = await LoadConversationScope(conversationId, principal, fakeProvider(rows));
|
|
315
|
+
const alwaysRead = AuthorizeSessionAccess(
|
|
316
|
+
{ SessionUserID: session.UserID, ConversationExternalID: scope.ExternalID, ConversationReadable: scope.Readable },
|
|
317
|
+
principal,
|
|
318
|
+
);
|
|
319
|
+
expect(await AuthorizeLoadedSession(session, principal, fakeProvider(rows))).toEqual(alwaysRead);
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const sessionWithConversation = { ID: SESSION_ID, UserID: OWNER, ConversationID: CONVERSATION_ID } as unknown as MJAIAgentSessionEntity;
|
|
325
|
+
const conversationReads = (provider: IMetadataProvider) =>
|
|
326
|
+
vi.mocked(provider.GetEntityObject).mock.calls.filter(([name]) => name === 'MJ: Conversations').length;
|
|
327
|
+
|
|
328
|
+
it('does not read the conversation for a named owner without a scope, the usual signed-in caller', async () => {
|
|
329
|
+
const provider = fakeProvider({ conversation: { ExternalID: 'scope-1' } });
|
|
330
|
+
expect(await AuthorizeLoadedSession(sessionWithConversation, USER, provider)).toEqual({ Allowed: true });
|
|
331
|
+
expect(conversationReads(provider)).toBe(0);
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
it('does not read the conversation for a caller who does not own the session', async () => {
|
|
335
|
+
const provider = fakeProvider({ conversation: { ExternalID: 'scope-1' } });
|
|
336
|
+
expect(await AuthorizeLoadedSession(sessionWithConversation, { ID: OTHER } as UserInfo, provider)).toEqual({ Allowed: false, Reason: 'not_owner' });
|
|
337
|
+
expect(conversationReads(provider)).toBe(0);
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
it('reads the conversation once for a caller with a scope identity', async () => {
|
|
341
|
+
const provider = fakeProvider({ conversation: { ExternalID: 'scope-1' } });
|
|
342
|
+
const guest = { ID: OWNER, IsMagicLinkAnonymous: true, AnonymousVisitorID: 'scope-1', MagicLinkScope: { ResourceID: 'scope-1' } } as unknown as UserInfo;
|
|
343
|
+
expect(await AuthorizeLoadedSession(sessionWithConversation, guest, provider)).toEqual({ Allowed: true });
|
|
344
|
+
expect(conversationReads(provider)).toBe(1);
|
|
345
|
+
});
|
|
346
|
+
});
|