@memberjunction/server 6.2.0-edge.3 → 6.2.0-edge.5
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 +20 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/agentSessions/BridgeHostInstance.d.ts +25 -0
- package/dist/agentSessions/BridgeHostInstance.d.ts.map +1 -0
- package/dist/agentSessions/BridgeHostInstance.js +30 -0
- package/dist/agentSessions/BridgeHostInstance.js.map +1 -0
- package/dist/agentSessions/HostInstance.d.ts +75 -11
- package/dist/agentSessions/HostInstance.d.ts.map +1 -1
- package/dist/agentSessions/HostInstance.js +99 -28
- package/dist/agentSessions/HostInstance.js.map +1 -1
- package/dist/agentSessions/ReturningVisitorRecap.d.ts.map +1 -1
- package/dist/agentSessions/ReturningVisitorRecap.js +7 -3
- package/dist/agentSessions/ReturningVisitorRecap.js.map +1 -1
- package/dist/agentSessions/SessionJanitor.d.ts +236 -27
- package/dist/agentSessions/SessionJanitor.d.ts.map +1 -1
- package/dist/agentSessions/SessionJanitor.js +682 -54
- package/dist/agentSessions/SessionJanitor.js.map +1 -1
- package/dist/agentSessions/SessionManager.d.ts +48 -3
- package/dist/agentSessions/SessionManager.d.ts.map +1 -1
- package/dist/agentSessions/SessionManager.js +112 -9
- package/dist/agentSessions/SessionManager.js.map +1 -1
- package/dist/agentSessions/SessionRunIDVerifier.d.ts +63 -0
- package/dist/agentSessions/SessionRunIDVerifier.d.ts.map +1 -0
- package/dist/agentSessions/SessionRunIDVerifier.js +129 -0
- package/dist/agentSessions/SessionRunIDVerifier.js.map +1 -0
- package/dist/agentSessions/index.d.ts +2 -0
- package/dist/agentSessions/index.d.ts.map +1 -1
- package/dist/agentSessions/index.js +10 -3
- package/dist/agentSessions/index.js.map +1 -1
- package/dist/apiRoot.d.ts +32 -0
- package/dist/apiRoot.d.ts.map +1 -0
- package/dist/apiRoot.js +67 -0
- package/dist/apiRoot.js.map +1 -0
- package/dist/auth/AuthProviderCatalogRouter.d.ts.map +1 -1
- package/dist/auth/AuthProviderCatalogRouter.js +25 -0
- package/dist/auth/AuthProviderCatalogRouter.js.map +1 -1
- package/dist/auth/LoginScreenEngine.d.ts +22 -0
- package/dist/auth/LoginScreenEngine.d.ts.map +1 -0
- package/dist/auth/LoginScreenEngine.js +57 -0
- package/dist/auth/LoginScreenEngine.js.map +1 -0
- package/dist/auth/actionAuthorization.d.ts +40 -0
- package/dist/auth/actionAuthorization.d.ts.map +1 -0
- package/dist/auth/actionAuthorization.js +94 -0
- package/dist/auth/actionAuthorization.js.map +1 -0
- package/dist/auth/index.d.ts +1 -0
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +4 -10
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/magicLink/MagicLinkRouter.d.ts.map +1 -1
- package/dist/auth/magicLink/MagicLinkRouter.js +3 -2
- package/dist/auth/magicLink/MagicLinkRouter.js.map +1 -1
- package/dist/auth/magicLink/MagicLinkService.d.ts.map +1 -1
- package/dist/auth/magicLink/MagicLinkService.js +6 -3
- package/dist/auth/magicLink/MagicLinkService.js.map +1 -1
- package/dist/auth/newUsers.d.ts +10 -0
- package/dist/auth/newUsers.d.ts.map +1 -1
- package/dist/auth/newUsers.js +80 -11
- package/dist/auth/newUsers.js.map +1 -1
- package/dist/config.d.ts +2514 -22
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +166 -4
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts +8 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +14 -11
- package/dist/context.js.map +1 -1
- package/dist/coreRoutes.d.ts +29 -0
- package/dist/coreRoutes.d.ts.map +1 -0
- package/dist/coreRoutes.js +142 -0
- package/dist/coreRoutes.js.map +1 -0
- package/dist/generated/graphql-schemas/__mj.d.ts +504 -3
- package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
- package/dist/generated/graphql-schemas/__mj.js +2456 -7
- package/dist/generated/graphql-schemas/__mj.js.map +1 -1
- package/dist/generic/CacheInvalidationResolver.d.ts +43 -8
- package/dist/generic/CacheInvalidationResolver.d.ts.map +1 -1
- package/dist/generic/CacheInvalidationResolver.js +59 -19
- package/dist/generic/CacheInvalidationResolver.js.map +1 -1
- package/dist/generic/OrphanedConversationDetailReconciler.js +3 -3
- package/dist/generic/OrphanedConversationDetailReconciler.js.map +1 -1
- package/dist/generic/PushStatusResolver.js +1 -1
- package/dist/generic/PushStatusResolver.js.map +1 -1
- package/dist/generic/ResolverBase.d.ts +123 -48
- package/dist/generic/ResolverBase.d.ts.map +1 -1
- package/dist/generic/ResolverBase.js +293 -305
- package/dist/generic/ResolverBase.js.map +1 -1
- package/dist/generic/RunViewResolver.d.ts.map +1 -1
- package/dist/generic/RunViewResolver.js +16 -0
- package/dist/generic/RunViewResolver.js.map +1 -1
- package/dist/globalCors.d.ts +33 -0
- package/dist/globalCors.d.ts.map +1 -0
- package/dist/globalCors.js +58 -0
- package/dist/globalCors.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +219 -278
- package/dist/index.js.map +1 -1
- package/dist/isolatedProvider.d.ts +20 -0
- package/dist/isolatedProvider.d.ts.map +1 -0
- package/dist/isolatedProvider.js +22 -0
- package/dist/isolatedProvider.js.map +1 -0
- package/dist/multiTenancy/index.d.ts.map +1 -1
- package/dist/multiTenancy/index.js +22 -2
- package/dist/multiTenancy/index.js.map +1 -1
- package/dist/realtimeProxy/RealtimeProxyServer.d.ts +44 -4
- package/dist/realtimeProxy/RealtimeProxyServer.d.ts.map +1 -1
- package/dist/realtimeProxy/RealtimeProxyServer.js +121 -16
- package/dist/realtimeProxy/RealtimeProxyServer.js.map +1 -1
- package/dist/realtimeProxy/RealtimeRelayTunnel.d.ts +171 -0
- package/dist/realtimeProxy/RealtimeRelayTunnel.d.ts.map +1 -0
- package/dist/realtimeProxy/RealtimeRelayTunnel.js +532 -0
- package/dist/realtimeProxy/RealtimeRelayTunnel.js.map +1 -0
- package/dist/realtimeSessions/RealtimeSessionEventService.d.ts +148 -0
- package/dist/realtimeSessions/RealtimeSessionEventService.d.ts.map +1 -0
- package/dist/realtimeSessions/RealtimeSessionEventService.js +191 -0
- package/dist/realtimeSessions/RealtimeSessionEventService.js.map +1 -0
- package/dist/realtimeSessions/RealtimeSessionVerificationService.d.ts +66 -0
- package/dist/realtimeSessions/RealtimeSessionVerificationService.d.ts.map +1 -0
- package/dist/realtimeSessions/RealtimeSessionVerificationService.js +139 -0
- package/dist/realtimeSessions/RealtimeSessionVerificationService.js.map +1 -0
- package/dist/realtimeSessions/RealtimeVerifyRouter.d.ts +38 -0
- package/dist/realtimeSessions/RealtimeVerifyRouter.d.ts.map +1 -0
- package/dist/realtimeSessions/RealtimeVerifyRouter.js +99 -0
- package/dist/realtimeSessions/RealtimeVerifyRouter.js.map +1 -0
- package/dist/realtimeSessions/RecordingAssemblyScheduler.d.ts +100 -0
- package/dist/realtimeSessions/RecordingAssemblyScheduler.d.ts.map +1 -0
- package/dist/realtimeSessions/RecordingAssemblyScheduler.js +173 -0
- package/dist/realtimeSessions/RecordingAssemblyScheduler.js.map +1 -0
- package/dist/realtimeSessions/SessionVerificationPortsBase.d.ts +61 -0
- package/dist/realtimeSessions/SessionVerificationPortsBase.d.ts.map +1 -0
- package/dist/realtimeSessions/SessionVerificationPortsBase.js +149 -0
- package/dist/realtimeSessions/SessionVerificationPortsBase.js.map +1 -0
- package/dist/realtimeSessions/SlidingWindowRateLimiter.d.ts +57 -0
- package/dist/realtimeSessions/SlidingWindowRateLimiter.d.ts.map +1 -0
- package/dist/realtimeSessions/SlidingWindowRateLimiter.js +69 -0
- package/dist/realtimeSessions/SlidingWindowRateLimiter.js.map +1 -0
- package/dist/realtimeSessions/index.d.ts +19 -0
- package/dist/realtimeSessions/index.d.ts.map +1 -0
- package/dist/realtimeSessions/index.js +19 -0
- package/dist/realtimeSessions/index.js.map +1 -0
- package/dist/realtimeSessions/ownedRealtimeSession.d.ts +30 -0
- package/dist/realtimeSessions/ownedRealtimeSession.d.ts.map +1 -0
- package/dist/realtimeSessions/ownedRealtimeSession.js +47 -0
- package/dist/realtimeSessions/ownedRealtimeSession.js.map +1 -0
- package/dist/realtimeSessions/sessionAccess.d.ts +92 -0
- package/dist/realtimeSessions/sessionAccess.d.ts.map +1 -0
- package/dist/realtimeSessions/sessionAccess.js +94 -0
- package/dist/realtimeSessions/sessionAccess.js.map +1 -0
- package/dist/realtimeSessions/sessionConfigStamps.d.ts +70 -0
- package/dist/realtimeSessions/sessionConfigStamps.d.ts.map +1 -0
- package/dist/realtimeSessions/sessionConfigStamps.js +93 -0
- package/dist/realtimeSessions/sessionConfigStamps.js.map +1 -0
- package/dist/realtimeSessions/subscriptionAuthorization.d.ts +65 -0
- package/dist/realtimeSessions/subscriptionAuthorization.d.ts.map +1 -0
- package/dist/realtimeSessions/subscriptionAuthorization.js +71 -0
- package/dist/realtimeSessions/subscriptionAuthorization.js.map +1 -0
- package/dist/realtimeSessions/verificationCore.d.ts +243 -0
- package/dist/realtimeSessions/verificationCore.d.ts.map +1 -0
- package/dist/realtimeSessions/verificationCore.js +356 -0
- package/dist/realtimeSessions/verificationCore.js.map +1 -0
- package/dist/realtimeSessions/verificationPages.d.ts +66 -0
- package/dist/realtimeSessions/verificationPages.d.ts.map +1 -0
- package/dist/realtimeSessions/verificationPages.js +110 -0
- package/dist/realtimeSessions/verificationPages.js.map +1 -0
- package/dist/realtimeSessions/verificationPolicy.d.ts +147 -0
- package/dist/realtimeSessions/verificationPolicy.d.ts.map +1 -0
- package/dist/realtimeSessions/verificationPolicy.js +249 -0
- package/dist/realtimeSessions/verificationPolicy.js.map +1 -0
- package/dist/realtimeSessions/verificationWorkflow.d.ts +263 -0
- package/dist/realtimeSessions/verificationWorkflow.d.ts.map +1 -0
- package/dist/realtimeSessions/verificationWorkflow.js +435 -0
- package/dist/realtimeSessions/verificationWorkflow.js.map +1 -0
- package/dist/realtimeWidget/widgetGuestElevation.d.ts +20 -2
- package/dist/realtimeWidget/widgetGuestElevation.d.ts.map +1 -1
- package/dist/realtimeWidget/widgetGuestElevation.js +31 -2
- package/dist/realtimeWidget/widgetGuestElevation.js.map +1 -1
- package/dist/resolvers/ActionResolver.d.ts +31 -5
- package/dist/resolvers/ActionResolver.d.ts.map +1 -1
- package/dist/resolvers/ActionResolver.js +75 -14
- package/dist/resolvers/ActionResolver.js.map +1 -1
- package/dist/resolvers/AdhocQueryResolver.d.ts +14 -1
- package/dist/resolvers/AdhocQueryResolver.d.ts.map +1 -1
- package/dist/resolvers/AdhocQueryResolver.js +21 -5
- package/dist/resolvers/AdhocQueryResolver.js.map +1 -1
- package/dist/resolvers/ArtifactFileResolver.d.ts +5 -1
- package/dist/resolvers/ArtifactFileResolver.d.ts.map +1 -1
- package/dist/resolvers/ArtifactFileResolver.js +9 -7
- package/dist/resolvers/ArtifactFileResolver.js.map +1 -1
- package/dist/resolvers/CacheStatsResolver.d.ts +27 -0
- package/dist/resolvers/CacheStatsResolver.d.ts.map +1 -1
- package/dist/resolvers/CacheStatsResolver.js +113 -2
- package/dist/resolvers/CacheStatsResolver.js.map +1 -1
- package/dist/resolvers/ClientToolRequestResolver.d.ts +4 -2
- package/dist/resolvers/ClientToolRequestResolver.d.ts.map +1 -1
- package/dist/resolvers/ClientToolRequestResolver.js +28 -10
- package/dist/resolvers/ClientToolRequestResolver.js.map +1 -1
- package/dist/resolvers/ConversationRunEventsResolver.d.ts +8 -4
- package/dist/resolvers/ConversationRunEventsResolver.d.ts.map +1 -1
- package/dist/resolvers/ConversationRunEventsResolver.js +6 -6
- package/dist/resolvers/ConversationRunEventsResolver.js.map +1 -1
- package/dist/resolvers/DatasetResolver.d.ts +24 -3
- package/dist/resolvers/DatasetResolver.d.ts.map +1 -1
- package/dist/resolvers/DatasetResolver.js +57 -8
- package/dist/resolvers/DatasetResolver.js.map +1 -1
- package/dist/resolvers/EntityRecordNameResolver.d.ts +2 -1
- package/dist/resolvers/EntityRecordNameResolver.d.ts.map +1 -1
- package/dist/resolvers/EntityRecordNameResolver.js +3 -2
- package/dist/resolvers/EntityRecordNameResolver.js.map +1 -1
- package/dist/resolvers/FetchEntityVectorsResolver.d.ts +27 -1
- package/dist/resolvers/FetchEntityVectorsResolver.d.ts.map +1 -1
- package/dist/resolvers/FetchEntityVectorsResolver.js +148 -2
- package/dist/resolvers/FetchEntityVectorsResolver.js.map +1 -1
- package/dist/resolvers/FileResolver.d.ts +82 -11
- package/dist/resolvers/FileResolver.d.ts.map +1 -1
- package/dist/resolvers/FileResolver.js +279 -101
- package/dist/resolvers/FileResolver.js.map +1 -1
- package/dist/resolvers/GetDataContextDataResolver.d.ts +33 -7
- package/dist/resolvers/GetDataContextDataResolver.d.ts.map +1 -1
- package/dist/resolvers/GetDataContextDataResolver.js +117 -38
- package/dist/resolvers/GetDataContextDataResolver.js.map +1 -1
- package/dist/resolvers/GetDataResolver.d.ts.map +1 -1
- package/dist/resolvers/GetDataResolver.js +6 -3
- package/dist/resolvers/GetDataResolver.js.map +1 -1
- package/dist/resolvers/HumanHandoffResolver.d.ts.map +1 -1
- package/dist/resolvers/HumanHandoffResolver.js +7 -1
- package/dist/resolvers/HumanHandoffResolver.js.map +1 -1
- package/dist/resolvers/IntegrationDiscoveryResolver.d.ts.map +1 -1
- package/dist/resolvers/IntegrationDiscoveryResolver.js +4 -2
- package/dist/resolvers/IntegrationDiscoveryResolver.js.map +1 -1
- package/dist/resolvers/MCPResolver.d.ts.map +1 -1
- package/dist/resolvers/MCPResolver.js +9 -4
- package/dist/resolvers/MCPResolver.js.map +1 -1
- package/dist/resolvers/QueryResolver.d.ts +2 -0
- package/dist/resolvers/QueryResolver.d.ts.map +1 -1
- package/dist/resolvers/QueryResolver.js +13 -0
- package/dist/resolvers/QueryResolver.js.map +1 -1
- package/dist/resolvers/RealtimeBridgeResolver.d.ts +54 -2
- package/dist/resolvers/RealtimeBridgeResolver.d.ts.map +1 -1
- package/dist/resolvers/RealtimeBridgeResolver.js +155 -10
- package/dist/resolvers/RealtimeBridgeResolver.js.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts +157 -33
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.js +404 -116
- package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
- package/dist/resolvers/RealtimeSessionEventsResolver.d.ts +61 -0
- package/dist/resolvers/RealtimeSessionEventsResolver.d.ts.map +1 -0
- package/dist/resolvers/RealtimeSessionEventsResolver.js +130 -0
- package/dist/resolvers/RealtimeSessionEventsResolver.js.map +1 -0
- package/dist/resolvers/RealtimeSessionVerificationResolver.d.ts +85 -0
- package/dist/resolvers/RealtimeSessionVerificationResolver.d.ts.map +1 -0
- package/dist/resolvers/RealtimeSessionVerificationResolver.js +188 -0
- package/dist/resolvers/RealtimeSessionVerificationResolver.js.map +1 -0
- package/dist/resolvers/RemoteBrowserActionResolver.d.ts +2 -0
- package/dist/resolvers/RemoteBrowserActionResolver.d.ts.map +1 -1
- package/dist/resolvers/RemoteBrowserActionResolver.js +5 -1
- package/dist/resolvers/RemoteBrowserActionResolver.js.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.d.ts +10 -0
- package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.js +100 -14
- package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
- package/dist/resolvers/RunTemplateResolver.d.ts.map +1 -1
- package/dist/resolvers/RunTemplateResolver.js +12 -11
- package/dist/resolvers/RunTemplateResolver.js.map +1 -1
- package/dist/resolvers/SearchKnowledgeStreamResolver.d.ts +12 -1
- package/dist/resolvers/SearchKnowledgeStreamResolver.d.ts.map +1 -1
- package/dist/resolvers/SearchKnowledgeStreamResolver.js +15 -2
- package/dist/resolvers/SearchKnowledgeStreamResolver.js.map +1 -1
- package/dist/resolvers/SqlLoggingConfigResolver.d.ts.map +1 -1
- package/dist/resolvers/SqlLoggingConfigResolver.js +2 -3
- package/dist/resolvers/SqlLoggingConfigResolver.js.map +1 -1
- package/dist/resolvers/SyncDataResolver.d.ts.map +1 -1
- package/dist/resolvers/SyncDataResolver.js +6 -2
- package/dist/resolvers/SyncDataResolver.js.map +1 -1
- package/dist/resolvers/SyncRolesUsersResolver.d.ts +2 -0
- package/dist/resolvers/SyncRolesUsersResolver.d.ts.map +1 -1
- package/dist/resolvers/SyncRolesUsersResolver.js +23 -6
- package/dist/resolvers/SyncRolesUsersResolver.js.map +1 -1
- package/dist/resolvers/TaskGraphFrameResolver.d.ts +2 -1
- package/dist/resolvers/TaskGraphFrameResolver.d.ts.map +1 -1
- package/dist/resolvers/TaskGraphFrameResolver.js +28 -1
- package/dist/resolvers/TaskGraphFrameResolver.js.map +1 -1
- package/dist/resolvers/TestQuerySQLResolver.d.ts +24 -0
- package/dist/resolvers/TestQuerySQLResolver.d.ts.map +1 -1
- package/dist/resolvers/TestQuerySQLResolver.js +65 -13
- package/dist/resolvers/TestQuerySQLResolver.js.map +1 -1
- package/dist/resolvers/TransactionGroupResolver.d.ts +19 -2
- package/dist/resolvers/TransactionGroupResolver.d.ts.map +1 -1
- package/dist/resolvers/TransactionGroupResolver.js +102 -24
- package/dist/resolvers/TransactionGroupResolver.js.map +1 -1
- package/dist/resolvers/UserAvatarResolver.d.ts +16 -0
- package/dist/resolvers/UserAvatarResolver.d.ts.map +1 -0
- package/dist/resolvers/UserAvatarResolver.js +124 -0
- package/dist/resolvers/UserAvatarResolver.js.map +1 -0
- package/dist/resolvers/UserResolver.d.ts.map +1 -1
- package/dist/resolvers/UserResolver.js +2 -17
- package/dist/resolvers/UserResolver.js.map +1 -1
- package/dist/resolvers/agent-run-data-guard.d.ts +31 -0
- package/dist/resolvers/agent-run-data-guard.d.ts.map +1 -0
- package/dist/resolvers/agent-run-data-guard.js +72 -0
- package/dist/resolvers/agent-run-data-guard.js.map +1 -0
- package/dist/resolvers/agentVisionConsentAudit.d.ts +37 -0
- package/dist/resolvers/agentVisionConsentAudit.d.ts.map +1 -0
- package/dist/resolvers/agentVisionConsentAudit.js +63 -0
- package/dist/resolvers/agentVisionConsentAudit.js.map +1 -0
- package/dist/resolvers/avatarInputValidation.d.ts +40 -0
- package/dist/resolvers/avatarInputValidation.d.ts.map +1 -0
- package/dist/resolvers/avatarInputValidation.js +97 -0
- package/dist/resolvers/avatarInputValidation.js.map +1 -0
- package/dist/resolvers/currentUserRoles.d.ts +29 -0
- package/dist/resolvers/currentUserRoles.d.ts.map +1 -1
- package/dist/resolvers/currentUserRoles.js +30 -0
- package/dist/resolvers/currentUserRoles.js.map +1 -1
- package/dist/resolvers/meetings/meetingMutations.d.ts +6 -0
- package/dist/resolvers/meetings/meetingMutations.d.ts.map +1 -1
- package/dist/resolvers/meetings/meetingMutations.js +21 -0
- package/dist/resolvers/meetings/meetingMutations.js.map +1 -1
- package/dist/rest/RealtimeRecordingHandler.d.ts +49 -0
- package/dist/rest/RealtimeRecordingHandler.d.ts.map +1 -0
- package/dist/rest/RealtimeRecordingHandler.js +383 -0
- package/dist/rest/RealtimeRecordingHandler.js.map +1 -0
- package/dist/rest/ViewOperationsHandler.d.ts +3 -1
- package/dist/rest/ViewOperationsHandler.d.ts.map +1 -1
- package/dist/rest/ViewOperationsHandler.js +21 -12
- package/dist/rest/ViewOperationsHandler.js.map +1 -1
- package/dist/rest/setupRESTEndpoints.d.ts +6 -0
- package/dist/rest/setupRESTEndpoints.d.ts.map +1 -1
- package/dist/rest/setupRESTEndpoints.js +6 -0
- package/dist/rest/setupRESTEndpoints.js.map +1 -1
- package/dist/serverExtensionReservedRoots.d.ts +12 -10
- package/dist/serverExtensionReservedRoots.d.ts.map +1 -1
- package/dist/serverExtensionReservedRoots.js +14 -17
- package/dist/serverExtensionReservedRoots.js.map +1 -1
- package/dist/services/MJStorageBlobStore.d.ts +8 -1
- package/dist/services/MJStorageBlobStore.d.ts.map +1 -1
- package/dist/services/MJStorageBlobStore.js +25 -19
- package/dist/services/MJStorageBlobStore.js.map +1 -1
- package/dist/services/StartTaskGraphDispatcher.js +2 -2
- package/dist/services/StartTaskGraphDispatcher.js.map +1 -1
- package/dist/services/TaskGraphActionRunner.d.ts +5 -3
- package/dist/services/TaskGraphActionRunner.d.ts.map +1 -1
- package/dist/services/TaskGraphActionRunner.js +27 -5
- package/dist/services/TaskGraphActionRunner.js.map +1 -1
- package/dist/services/TaskGraphAgentRunner.d.ts +14 -2
- package/dist/services/TaskGraphAgentRunner.d.ts.map +1 -1
- package/dist/services/TaskGraphAgentRunner.js +157 -9
- package/dist/services/TaskGraphAgentRunner.js.map +1 -1
- package/dist/services/TaskGraphContinuationDeliverer.d.ts +57 -4
- package/dist/services/TaskGraphContinuationDeliverer.d.ts.map +1 -1
- package/dist/services/TaskGraphContinuationDeliverer.js +607 -18
- package/dist/services/TaskGraphContinuationDeliverer.js.map +1 -1
- package/dist/sharedCache.d.ts +105 -0
- package/dist/sharedCache.d.ts.map +1 -0
- package/dist/sharedCache.js +279 -0
- package/dist/sharedCache.js.map +1 -0
- package/dist/types.d.ts +6 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +99 -96
- package/src/__tests__/ActionResolver.authorization.test.ts +353 -0
- package/src/__tests__/AdhocQueryResolver.sqlAuthorizer.test.ts +104 -0
- package/src/__tests__/BridgeHostInstance.test.ts +232 -0
- package/src/__tests__/ClientToolRequestResolver.test.ts +116 -0
- package/src/__tests__/DatasetResolver.itemFilters.test.ts +201 -0
- package/src/__tests__/EntityRecordNameResolver.rowSecurity.test.ts +152 -0
- package/src/__tests__/FetchEntityVectorsResolver.test.ts +433 -0
- package/src/__tests__/FileResolver.storageAccess.test.ts +562 -0
- package/src/__tests__/GetDataContextDataResolver.sqlItems.test.ts +416 -0
- package/src/__tests__/HostInstance.test.ts +84 -0
- package/src/__tests__/HumanHandoffResolver.test.ts +17 -0
- package/src/__tests__/ListOperationsResolver.adhocScreen.test.ts +137 -0
- package/src/__tests__/LoginScreenEngine.test.ts +86 -0
- package/src/__tests__/MJStorageBlobStore.storageGate.test.ts +63 -0
- package/src/__tests__/MeetingResolver.test.ts +75 -1
- package/src/__tests__/RealtimeBridgeResolver.test.ts +415 -6
- package/src/__tests__/RealtimeClientSessionResolver.configTamper.test.ts +383 -0
- package/src/__tests__/RealtimeClientSessionResolver.test.ts +682 -58
- package/src/__tests__/RealtimeProxyServer.test.ts +16 -1
- package/src/__tests__/RealtimeRelay.test.ts +876 -0
- package/src/__tests__/RecordingAssemblyScheduler.test.ts +178 -0
- package/src/__tests__/RemoteBrowserActionResolver.goalRunLink.test.ts +129 -0
- package/src/__tests__/ResolverBase.adhocReadScope.test.ts +133 -2
- package/src/__tests__/ResolverBase.aggregateScreen.test.ts +174 -0
- package/src/__tests__/ResolverBase.balancedClause.test.ts +155 -0
- package/src/__tests__/ResolverBase.clauseUnscopedRead.test.ts +127 -0
- package/src/__tests__/ResolverBase.clientOldValues.test.ts +212 -6
- package/src/__tests__/ResolverBase.filterEscaping.test.ts +5 -2
- package/src/__tests__/ReturningVisitorRecap.test.ts +115 -0
- package/src/__tests__/RunAIAgentResolver.branch.test.ts +170 -0
- package/src/__tests__/RunAIAgentResolver.historyFrom.test.ts +6 -4
- package/src/__tests__/RunAIAgentResolver.runDataGuard.test.ts +258 -0
- package/src/__tests__/RunAIAgentResolver.stoppedPredecessor.test.ts +166 -0
- package/src/__tests__/RunTemplateResolver.test.ts +132 -0
- package/src/__tests__/RunViewResolver.cacheCheckForwarding.test.ts +63 -0
- package/src/__tests__/RunViewResolver.cacheCheckScreen.test.ts +165 -0
- package/src/__tests__/SearchKnowledgeStreamResolver.test.ts +161 -0
- package/src/__tests__/SessionJanitor.test.ts +1203 -21
- package/src/__tests__/SessionManager.test.ts +240 -17
- package/src/__tests__/SessionRunIDVerifier.test.ts +251 -0
- package/src/__tests__/SyncRolesUsersResolver.userRoles.test.ts +97 -0
- package/src/__tests__/TaskGraphActionRunner.durablePayload.test.ts +25 -0
- package/src/__tests__/TaskGraphAgentRunner.test.ts +266 -0
- package/src/__tests__/TaskGraphContinuationDeliverer.test.ts +403 -22
- package/src/__tests__/TaskGraphFrameResolver.test.ts +22 -0
- package/src/__tests__/TestQuerySQLResolver.test.ts +274 -0
- package/src/__tests__/TransactionGroupResolver.refusals.test.ts +98 -1
- package/src/__tests__/UpdateMJDataContextItem.oldValues.test.ts +220 -0
- package/src/__tests__/UserAvatarResolver.test.ts +255 -0
- package/src/__tests__/UserResolver.currentUser.test.ts +16 -1
- package/src/__tests__/ViewOperationsHandler.clauseScreen.test.ts +157 -0
- package/src/__tests__/actionAuthorization.test.ts +130 -0
- package/src/__tests__/agent-run-data-guard.test.ts +130 -0
- package/src/__tests__/agentVisionConsentAudit.test.ts +118 -0
- package/src/__tests__/apiRootBoot.test.ts +202 -0
- package/src/__tests__/avatarInputValidation.test.ts +160 -0
- package/src/__tests__/cacheInvalidationRecordData.test.ts +64 -0
- package/src/__tests__/conversationRunEvents.test.ts +56 -14
- package/src/__tests__/globalCors.test.ts +217 -0
- package/src/__tests__/isolatedProviderTransactions.test.ts +169 -0
- package/src/__tests__/magicLinkService.consume.test.ts +6 -0
- package/src/__tests__/magicLinkService.provisionLog.test.ts +5 -0
- package/src/__tests__/multiTenancy.postgresRewrite.test.ts +181 -0
- package/src/__tests__/multiTenancy.security.test.ts +51 -0
- package/src/__tests__/multiTenancy.test.ts +1 -1
- package/src/__tests__/newUsers.template.test.ts +151 -0
- package/src/__tests__/newUsers.test.ts +74 -5
- package/src/__tests__/orphanedConversationDetailReconciler.test.ts +17 -2
- package/src/__tests__/ownedRealtimeSession.test.ts +39 -0
- package/src/__tests__/pushStatusResolver.filter.test.ts +7 -0
- package/src/__tests__/realtimeRecordingHandler.test.ts +652 -0
- package/src/__tests__/realtimeSessions/RealtimeSessionEventService.test.ts +201 -0
- package/src/__tests__/realtimeSessions/RealtimeSessionEventsResolver.test.ts +215 -0
- package/src/__tests__/realtimeSessions/RealtimeSessionVerificationResolver.test.ts +102 -0
- package/src/__tests__/realtimeSessions/RealtimeSessionVerificationService.test.ts +349 -0
- package/src/__tests__/realtimeSessions/RealtimeVerifyRouter.test.ts +131 -0
- package/src/__tests__/realtimeSessions/SlidingWindowRateLimiter.test.ts +56 -0
- package/src/__tests__/realtimeSessions/helpers.ts +4 -0
- package/src/__tests__/realtimeSessions/sessionAccess.test.ts +129 -0
- package/src/__tests__/realtimeSessions/sessionConfigStamps.test.ts +124 -0
- package/src/__tests__/realtimeSessions/verificationCore.test.ts +355 -0
- package/src/__tests__/realtimeSessions/verificationPages.test.ts +84 -0
- package/src/__tests__/realtimeSessions/verificationPolicy.test.ts +226 -0
- package/src/__tests__/realtimeSessions/verificationWorkflow.test.ts +633 -0
- package/src/__tests__/resolverBase.fls.test.ts +87 -4
- package/src/__tests__/serverExtensionHostOverlay.test.ts +66 -0
- package/src/__tests__/serverExtensionReservedRoots.test.ts +11 -96
- package/src/__tests__/sharedCache.test.ts +312 -0
- package/src/__tests__/widgetGuestElevation.test.ts +44 -0
- package/src/agentSessions/BridgeHostInstance.ts +32 -0
- package/src/agentSessions/HostInstance.ts +124 -28
- package/src/agentSessions/ReturningVisitorRecap.ts +7 -3
- package/src/agentSessions/SessionJanitor.ts +821 -59
- package/src/agentSessions/SessionManager.ts +138 -26
- package/src/agentSessions/SessionRunIDVerifier.ts +171 -0
- package/src/agentSessions/index.ts +10 -3
- package/src/apiRoot.ts +78 -0
- package/src/auth/AuthProviderCatalogRouter.ts +26 -0
- package/src/auth/LoginScreenEngine.ts +50 -0
- package/src/auth/actionAuthorization.ts +116 -0
- package/src/auth/index.ts +4 -8
- package/src/auth/magicLink/MagicLinkRouter.ts +3 -2
- package/src/auth/magicLink/MagicLinkService.ts +6 -3
- package/src/auth/newUsers.ts +99 -11
- package/src/config.ts +171 -5
- package/src/context.ts +15 -10
- package/src/coreRoutes.ts +166 -0
- package/src/generated/graphql-schemas/__mj.ts +1815 -57
- package/src/generic/CacheInvalidationResolver.ts +73 -18
- package/src/generic/OrphanedConversationDetailReconciler.ts +3 -3
- package/src/generic/PushStatusResolver.ts +1 -1
- package/src/generic/ResolverBase.ts +335 -326
- package/src/generic/RunViewResolver.ts +21 -0
- package/src/globalCors.ts +71 -0
- package/src/index.ts +231 -300
- package/src/isolatedProvider.ts +22 -0
- package/src/multiTenancy/index.ts +24 -2
- package/src/realtimeProxy/RealtimeProxyServer.ts +148 -16
- package/src/realtimeProxy/RealtimeRelayTunnel.ts +645 -0
- package/src/realtimeSessions/README.md +179 -0
- package/src/realtimeSessions/RealtimeSessionEventService.ts +274 -0
- package/src/realtimeSessions/RealtimeSessionVerificationService.ts +168 -0
- package/src/realtimeSessions/RealtimeVerifyRouter.ts +109 -0
- package/src/realtimeSessions/RecordingAssemblyScheduler.ts +234 -0
- package/src/realtimeSessions/SessionVerificationPortsBase.ts +189 -0
- package/src/realtimeSessions/SlidingWindowRateLimiter.ts +92 -0
- package/src/realtimeSessions/index.ts +18 -0
- package/src/realtimeSessions/ownedRealtimeSession.ts +55 -0
- package/src/realtimeSessions/sessionAccess.ts +145 -0
- package/src/realtimeSessions/sessionConfigStamps.ts +123 -0
- package/src/realtimeSessions/subscriptionAuthorization.ts +116 -0
- package/src/realtimeSessions/verificationCore.ts +502 -0
- package/src/realtimeSessions/verificationPages.ts +149 -0
- package/src/realtimeSessions/verificationPolicy.ts +321 -0
- package/src/realtimeSessions/verificationWorkflow.ts +702 -0
- package/src/realtimeWidget/widgetGuestElevation.ts +34 -2
- package/src/resolvers/ActionResolver.ts +84 -21
- package/src/resolvers/AdhocQueryResolver.ts +24 -6
- package/src/resolvers/ArtifactFileResolver.ts +9 -8
- package/src/resolvers/CacheStatsResolver.ts +89 -2
- package/src/resolvers/ClientToolRequestResolver.ts +25 -7
- package/src/resolvers/ConversationRunEventsResolver.ts +15 -11
- package/src/resolvers/DatasetResolver.ts +70 -10
- package/src/resolvers/EntityRecordNameResolver.ts +3 -2
- package/src/resolvers/FetchEntityVectorsResolver.ts +179 -2
- package/src/resolvers/FileResolver.ts +321 -106
- package/src/resolvers/GetDataContextDataResolver.ts +139 -41
- package/src/resolvers/GetDataResolver.ts +6 -3
- package/src/resolvers/HumanHandoffResolver.ts +8 -0
- package/src/resolvers/IntegrationDiscoveryResolver.ts +4 -2
- package/src/resolvers/MCPResolver.ts +8 -5
- package/src/resolvers/QueryResolver.ts +11 -0
- package/src/resolvers/RealtimeBridgeResolver.ts +163 -11
- package/src/resolvers/RealtimeClientSessionResolver.ts +511 -129
- package/src/resolvers/RealtimeSessionEventsResolver.ts +133 -0
- package/src/resolvers/RealtimeSessionVerificationResolver.ts +174 -0
- package/src/resolvers/RemoteBrowserActionResolver.ts +11 -1
- package/src/resolvers/RunAIAgentResolver.ts +116 -14
- package/src/resolvers/RunTemplateResolver.ts +13 -12
- package/src/resolvers/SearchKnowledgeStreamResolver.ts +19 -3
- package/src/resolvers/SqlLoggingConfigResolver.ts +2 -4
- package/src/resolvers/SyncDataResolver.ts +6 -2
- package/src/resolvers/SyncRolesUsersResolver.ts +24 -6
- package/src/resolvers/TaskGraphFrameResolver.ts +33 -1
- package/src/resolvers/TestQuerySQLResolver.ts +68 -13
- package/src/resolvers/TransactionGroupResolver.ts +131 -27
- package/src/resolvers/UserAvatarResolver.ts +111 -0
- package/src/resolvers/UserResolver.ts +2 -16
- package/src/resolvers/__tests__/GetDataResolver.validation.test.ts +95 -0
- package/src/resolvers/agent-run-data-guard.ts +89 -0
- package/src/resolvers/agentVisionConsentAudit.ts +82 -0
- package/src/resolvers/avatarInputValidation.ts +123 -0
- package/src/resolvers/currentUserRoles.ts +32 -1
- package/src/resolvers/meetings/meetingMutations.ts +21 -0
- package/src/rest/RealtimeRecordingHandler.ts +464 -0
- package/src/rest/ViewOperationsHandler.ts +29 -13
- package/src/rest/setupRESTEndpoints.ts +6 -0
- package/src/serverExtensionReservedRoots.ts +14 -17
- package/src/services/MJStorageBlobStore.ts +31 -30
- package/src/services/StartTaskGraphDispatcher.ts +2 -2
- package/src/services/TaskGraphActionRunner.ts +25 -5
- package/src/services/TaskGraphAgentRunner.ts +188 -11
- package/src/services/TaskGraphContinuationDeliverer.ts +713 -17
- package/src/sharedCache.ts +305 -0
- package/src/types.ts +7 -0
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# Realtime session events and identity verification
|
|
2
|
+
|
|
3
|
+
Server side of two related capabilities of a realtime agent session:
|
|
4
|
+
|
|
5
|
+
1. **Session events** — a typed, authorized channel from the server to the session's own client
|
|
6
|
+
(`RealtimeSessionEvents` subscription). Apps publish with `RealtimeSessionEventService`.
|
|
7
|
+
2. **Mid-session identity verification** — a person (often an anonymous widget guest) proves they control
|
|
8
|
+
an email address *without leaving the conversation*. The server emails a one-time link and a typed
|
|
9
|
+
code, records the verification on the session, extends the session's limits, and publishes
|
|
10
|
+
`identity.verified`.
|
|
11
|
+
|
|
12
|
+
The event contract (types, payload map, wire format) is framework-neutral and lives in
|
|
13
|
+
`@memberjunction/ai-core-plus` (`realtime-session-events.ts`). Everything here is server-only.
|
|
14
|
+
|
|
15
|
+
Verification **never creates an MJ user and never changes the session's principal** (decision D6): the
|
|
16
|
+
session stays owned by whoever started it; verification only attaches a verified identity and relaxes the
|
|
17
|
+
session's limits.
|
|
18
|
+
|
|
19
|
+
## Flow
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
client MJServer inbox
|
|
23
|
+
│ RequestRealtimeSessionVerification(sessionId, name, email)
|
|
24
|
+
├──────────────────────────▶ policy + limits → store HASHED pending on Session.Config
|
|
25
|
+
│ └─ email {link, code} ────────────────────▶ person
|
|
26
|
+
│ (subscription open: RealtimeSessionEvents(sessionId))
|
|
27
|
+
│ link opened (any device) ◀──────┤
|
|
28
|
+
│ GET /realtime/verify/:token → confirm page (NO side effects)
|
|
29
|
+
│ POST /realtime/verify → redeem (single use)
|
|
30
|
+
│ — or, same device —
|
|
31
|
+
├─ SubmitRealtimeSessionVerificationCode(sessionId, code) ─▶ redeem (single use)
|
|
32
|
+
│ mark verified · extend deadline · audit · publish identity.verified
|
|
33
|
+
◀──────────────── event: identity.verified (only to the session's owner)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Event delivery has **no replay**. A client that was disconnected when the event fired recovers with
|
|
37
|
+
`RealtimeSessionVerificationStatus(sessionId)` (and by reading the session).
|
|
38
|
+
|
|
39
|
+
## Operations
|
|
40
|
+
|
|
41
|
+
| Operation | Kind | Auth | Notes |
|
|
42
|
+
|---|---|---|---|
|
|
43
|
+
| `RequestRealtimeSessionVerification(agentSessionId, name, email)` | mutation | session's own principal | `email` is `@NoLog`. Sends the email. |
|
|
44
|
+
| `SubmitRealtimeSessionVerificationCode(agentSessionId, code)` | mutation | session's own principal | `code` is `@NoLog`. Spaces/dashes ignored. |
|
|
45
|
+
| `RealtimeSessionVerificationStatus(agentSessionId)` | query | session's own principal | The durable backstop for a missed event. |
|
|
46
|
+
| `RealtimeSessionEvents(agentSessionId)` | subscription | session's own principal | See *Authorization*. |
|
|
47
|
+
| `GET /realtime/verify/:token` | HTTP, **public** | the token is the capability | Renders a one-button confirmation page. Never consumes the token. |
|
|
48
|
+
| `POST /realtime/verify` (form field `token`) | HTTP, **public** | the token is the capability | Redeems. Per-IP rate limited. |
|
|
49
|
+
|
|
50
|
+
Every verification result carries a machine-readable `ErrorCode` (`invalid_email`, `consumer_domain`,
|
|
51
|
+
`domain_blocked`, `rate_limited`, `send_limit_reached`, `invalid_code`, `attempts_exhausted`,
|
|
52
|
+
`code_expired`, `session_not_found`, `session_closed`, `verification_unavailable`, …).
|
|
53
|
+
|
|
54
|
+
The routes are only mounted, and the operations only act, when `realtime.identityVerification.enabled` is true
|
|
55
|
+
**and** a `communicationProvider` is configured **and** an `hmacSecret` of at least 16 characters is set. Without the
|
|
56
|
+
secret the feature reports itself unavailable (fail closed) and logs why at startup.
|
|
57
|
+
|
|
58
|
+
The browser-side transport for the three operations and the subscription is `GraphQLRealtimeSessionClient` in
|
|
59
|
+
`@memberjunction/graphql-dataprovider`.
|
|
60
|
+
|
|
61
|
+
## Authorization
|
|
62
|
+
|
|
63
|
+
The principal comes from the **server-authenticated** context (`userPayload.userRecord`), never from an
|
|
64
|
+
argument. An id argument only selects *which of the caller's own sessions*.
|
|
65
|
+
|
|
66
|
+
A caller may use a session when `AIAgentSession.UserID` equals their user (`UUIDsEqual`). Widget guests all
|
|
67
|
+
share one Anonymous user, so for an anonymous/magic-link principal that is **not enough**: the guest's
|
|
68
|
+
signed per-session scope (`MagicLinkScope.ResourceID`) must equal the session's conversation `ExternalID`.
|
|
69
|
+
An anonymous principal with no scope is refused outright.
|
|
70
|
+
|
|
71
|
+
- Refusals are uniform ("session not found") whether the session is missing or simply not yours, so ids
|
|
72
|
+
cannot be probed.
|
|
73
|
+
- The subscription is authorized **twice**: when it opens, and again per event (each event carries an
|
|
74
|
+
owner/scope snapshot of its session), so a replicated event from another instance is judged like a local one.
|
|
75
|
+
- State changes (`identityVerification`, `maxSessionDeadlineIso` on `AIAgentSession.Config`) are written by a
|
|
76
|
+
**service principal** inside a trusted-write scope, and `MJAIAgentSessionEntityServer` refuses any other
|
|
77
|
+
write to those keys — the session owner can update their own session row, so without that guard they
|
|
78
|
+
could forge verification or move their own deadline.
|
|
79
|
+
|
|
80
|
+
## Policy knobs
|
|
81
|
+
|
|
82
|
+
Set per deployment in `mj.config.cjs` → `realtime.identityVerification.policy`, and per agent/channel in the
|
|
83
|
+
config cascade (`channels.config.IdentityVerification`, plus `session.unverifiedMaxSeconds` /
|
|
84
|
+
`session.verifiedMaxSeconds`). The cascade layer is **snapshotted onto the session at mint**, so the rules a
|
|
85
|
+
person is held to are fixed server-side and cannot be chosen later.
|
|
86
|
+
|
|
87
|
+
| Knob | Default | Meaning |
|
|
88
|
+
|---|---|---|
|
|
89
|
+
| `requireBusinessDomain` | `false` | Refuse consumer mailbox providers (`consumer_domain`). |
|
|
90
|
+
| `consumerDomains` | curated list (`DEFAULT_CONSUMER_EMAIL_DOMAINS`) | Overrides the curated list. An empty list is treated as absent. |
|
|
91
|
+
| `blockedDomains` | `[]` | Always refused (`domain_blocked`). Matches subdomains. |
|
|
92
|
+
| `linkTtlMinutes` | 30 (1–1440) | Lifetime of the link and code. |
|
|
93
|
+
| `maxSendsPerSession` | 3 (1–20) | Verification emails one session may trigger (persisted, exact). |
|
|
94
|
+
| `maxCodeAttempts` | 5 (1–10) | Wrong codes before the pending verification is voided. |
|
|
95
|
+
| `unverifiedMaxSeconds` | none | Absolute session cap until verified. Applies to **any** principal. |
|
|
96
|
+
| `verifiedMaxSeconds` | none | Cap after verification, measured from session start. Only ever *extends*. |
|
|
97
|
+
|
|
98
|
+
Server-wide limits (`realtime.identityVerification.rateLimits`, in memory, per instance; `0` disables):
|
|
99
|
+
`perIpSends`, `perEmailDomainSends`, `perEmailSends` over `sendWindowMs`; `perSessionCodeAttempts` over
|
|
100
|
+
`codeAttemptWindowMs`; `redeemPerIp` over `redeemWindowMs`.
|
|
101
|
+
|
|
102
|
+
Other settings: `communicationProvider`, `fromAddress`, `publicBaseUrl` (defaults like magic links),
|
|
103
|
+
`contextUserForVerification` (the service principal; falls back to the system user), and:
|
|
104
|
+
|
|
105
|
+
- `hmacSecret` (env `MJ_REALTIME_VERIFICATION_SECRET`): **required**. The server-only key the typed code is HMAC'd
|
|
106
|
+
under before it is stored. Never sent to a client or written to a session. Rotating it voids pending verifications only.
|
|
107
|
+
- `deliveryMode`: `send` (default) delivers the email. `dry-run` builds and logs the message through the communication
|
|
108
|
+
provider **without delivering it**, for rehearsal and integration environments — the integration bundle reads the
|
|
109
|
+
link and code from the `MJ: Communication Logs` row. Never use it in production.
|
|
110
|
+
|
|
111
|
+
## Security properties
|
|
112
|
+
|
|
113
|
+
- **Hashed at rest, and the code hash is keyed.** The token and the code are never stored raw: a SHA-256 of the
|
|
114
|
+
token (256 random bits) and an **HMAC-SHA-256 of the code under the server-only `hmacSecret`**, salted and bound to the
|
|
115
|
+
session. The keying is what protects a six-digit code: the session's owner — the very person being verified — can
|
|
116
|
+
read their own `Session.Config`, and an unkeyed hash of a six-digit code is reversible offline in about a million
|
|
117
|
+
guesses, which would bypass the attempt cap and the inbox entirely. Verification compares digests with
|
|
118
|
+
`timingSafeEqual`.
|
|
119
|
+
- **Single use, expiring.** Redemption clears the pending verification; an expired or used token is
|
|
120
|
+
refused. A wrong code consumes an attempt; the cap voids the verification.
|
|
121
|
+
- **Scanner-safe link.** `GET` renders a confirmation button; only the human's `POST` consumes the token,
|
|
122
|
+
so mail scanners and previewers cannot burn it.
|
|
123
|
+
- **Leak-free pages.** Every refusal (unknown, malformed, expired, used, voided) renders the same page with
|
|
124
|
+
the same status. Responses are `no-store`, `no-referrer`, never framed, with a CSP that forbids all but
|
|
125
|
+
the page's own inline style.
|
|
126
|
+
- **Uniform errors.** Missing and not-yours sessions are indistinguishable.
|
|
127
|
+
- **Bounded.** Per-IP, per-email-domain and per-address send limits; per-session code-attempt limit; a body
|
|
128
|
+
size limit on the public POST; the in-memory limiters are size-bounded.
|
|
129
|
+
- **Audited.** A successful verification writes an `MJ: Audit Logs` row of type
|
|
130
|
+
*Realtime Identity Verified* (seeded by `metadata/audit-log-types`). If that type has not been seeded the
|
|
131
|
+
entry is written to the server log instead of being dropped silently.
|
|
132
|
+
- **No principal change.** No user is created or switched.
|
|
133
|
+
|
|
134
|
+
## Limitations to know about
|
|
135
|
+
|
|
136
|
+
- **The email body is logged by the communication engine.** `CommunicationEngine.SendSingleMessage` writes an
|
|
137
|
+
`MJ: Communication Logs` row whose `MessageContent` contains the full body — here, the live link and code (the same
|
|
138
|
+
is already true of magic-link emails). Keep read access to that entity restricted to administrators. A principal who
|
|
139
|
+
can read it can redeem a verification for an address they do not control; `deliveryMode: 'dry-run'` makes that the
|
|
140
|
+
intended way to read the email, which is why it must never be used in production.
|
|
141
|
+
|
|
142
|
+
- Rate limiters are per process. Across replicas the in-memory limits are best-effort; per-session send and
|
|
143
|
+
attempt counts are persisted and exact.
|
|
144
|
+
- Single-use redemption is serialised per session in one process. Across replicas it is best-effort (there
|
|
145
|
+
is no compare-and-swap on a JSON column without a schema change).
|
|
146
|
+
- The session janitor enforces `maxSessionDeadlineIso`; it closes with its normal `Janitor` reason (there
|
|
147
|
+
is no dedicated close reason, which would need a schema change).
|
|
148
|
+
- Events have no replay (see *Flow*).
|
|
149
|
+
|
|
150
|
+
## Publishing an event from an app
|
|
151
|
+
|
|
152
|
+
```ts
|
|
153
|
+
import { RealtimeSessionEventService } from '@memberjunction/server/realtime-sessions';
|
|
154
|
+
|
|
155
|
+
const result = await RealtimeSessionEventService.Instance.Publish({
|
|
156
|
+
AgentSessionID, Type: 'identity.verified', Payload, ContextUser, Provider,
|
|
157
|
+
});
|
|
158
|
+
if (!result.Success) { /* result.ErrorMessage — Publish never throws */ }
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Add new event types by extending `RealtimeSessionEventPayloadMap` (declaration merging) in
|
|
162
|
+
`@memberjunction/ai-core-plus`; clients must ignore types they do not know.
|
|
163
|
+
|
|
164
|
+
## Where things are
|
|
165
|
+
|
|
166
|
+
| File | Role |
|
|
167
|
+
|---|---|
|
|
168
|
+
| `sessionAccess.ts`, `subscriptionAuthorization.ts` | The authorization decision and its subscription/event gates (pure, decorator-free). |
|
|
169
|
+
| `RealtimeSessionEventService.ts` | Publisher + cross-instance fan-out hook. |
|
|
170
|
+
| `verificationPolicy.ts`, `verificationCore.ts` | Policy resolution; token/code/state/hash/expiry logic (pure). |
|
|
171
|
+
| `verificationWorkflow.ts` | The operations, written against injected ports. |
|
|
172
|
+
| `SessionVerificationPortsBase.ts` | Config-free production ports (session IO, trusted writes, publish, audit). |
|
|
173
|
+
| `RealtimeSessionVerificationService.ts` | Binds the ports to server configuration; process singleton. |
|
|
174
|
+
| `RealtimeVerifyRouter.ts`, `verificationPages.ts` | The public routes and their pages. |
|
|
175
|
+
| `sessionConfigStamps.ts` | What session start stamps (policy snapshot, unverified cap, inherited identity). |
|
|
176
|
+
| `core.ts` | The configuration-free surface, published as `@memberjunction/server/realtime-sessions`. |
|
|
177
|
+
|
|
178
|
+
The GraphQL resolvers are `../resolvers/RealtimeSessionVerificationResolver.ts` and
|
|
179
|
+
`../resolvers/RealtimeSessionEventsResolver.ts`.
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Server-side publisher for **realtime session events** — the hook an app (or MJ itself)
|
|
3
|
+
* calls to push a typed event into ONE live realtime session.
|
|
4
|
+
*
|
|
5
|
+
* The wire contract (event types, payload shapes, parsing) lives in `@memberjunction/ai-core-plus`
|
|
6
|
+
* (`realtime-session-events.ts`). This module owns the transport: it publishes onto MJServer's
|
|
7
|
+
* existing PubSub (`PubSubManager`) so the `RealtimeSessionEvents` GraphQL subscription can deliver it.
|
|
8
|
+
*
|
|
9
|
+
* ## Routing is part of the payload
|
|
10
|
+
*
|
|
11
|
+
* Every published event carries a snapshot of who may see it — the session's owner (`OwnerUserID`) and
|
|
12
|
+
* the session's anonymous scope id (`ScopeKey`). The subscription filter compares that snapshot with
|
|
13
|
+
* the **connection's** authenticated identity, so a subscriber who guesses another session's id still
|
|
14
|
+
* receives nothing. These two fields are server-internal: the GraphQL notification type does not
|
|
15
|
+
* expose them.
|
|
16
|
+
*
|
|
17
|
+
* ## Multiple server instances
|
|
18
|
+
*
|
|
19
|
+
* PubSub is in-process. Behind a load balancer the browser's WebSocket can live on a different replica
|
|
20
|
+
* than the one that handled the verification link. {@link RealtimeSessionEventService.SetReplicationHook}
|
|
21
|
+
* lets the host fan events out (the server wires it to Redis when `REDIS_URL` is set, exactly as it does
|
|
22
|
+
* for push-status updates), and {@link RealtimeSessionEventService.PublishReplicated} republishes an event
|
|
23
|
+
* that arrived from another instance.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```ts
|
|
27
|
+
* // From an Open App's server code, after it finished some work for a live session:
|
|
28
|
+
* await RealtimeSessionEventService.Instance.Publish({
|
|
29
|
+
* AgentSessionID: sessionId,
|
|
30
|
+
* Type: 'acme.quote.ready',
|
|
31
|
+
* Payload: { QuoteID: quote.ID },
|
|
32
|
+
* ContextUser: systemUser,
|
|
33
|
+
* });
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* @module @memberjunction/server/realtimeSessions
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
import { IMetadataProvider, LogError, Metadata, UserInfo } from '@memberjunction/core';
|
|
40
|
+
import { BaseSingleton, MJGlobal } from '@memberjunction/global';
|
|
41
|
+
import {
|
|
42
|
+
SerializeRealtimeSessionEvent,
|
|
43
|
+
type RealtimeSessionEventPayload,
|
|
44
|
+
type RealtimeSessionEventType,
|
|
45
|
+
} from '@memberjunction/ai-core-plus';
|
|
46
|
+
import { PubSubManager } from '../generic/PubSubManager.js';
|
|
47
|
+
import { LoadSessionForAccess } from './sessionAccess.js';
|
|
48
|
+
|
|
49
|
+
/** The PubSub topic every realtime session event is published on. */
|
|
50
|
+
export const REALTIME_SESSION_EVENTS_TOPIC = 'REALTIME_SESSION_EVENTS';
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* What is actually published on {@link REALTIME_SESSION_EVENTS_TOPIC}: the wire event plus the
|
|
54
|
+
* server-internal routing snapshot.
|
|
55
|
+
*/
|
|
56
|
+
export type RealtimeSessionEventTopicPayload = {
|
|
57
|
+
Type: string;
|
|
58
|
+
AgentSessionID: string;
|
|
59
|
+
OccurredAt: string;
|
|
60
|
+
/** The event payload, JSON-encoded. */
|
|
61
|
+
PayloadJson: string;
|
|
62
|
+
/** `AIAgentSession.UserID` — compared with the subscriber connection's user. Server-internal. */
|
|
63
|
+
OwnerUserID: string;
|
|
64
|
+
/** `Conversation.ExternalID` — compared with the subscriber's signed scope. Server-internal. */
|
|
65
|
+
ScopeKey: string | null;
|
|
66
|
+
/**
|
|
67
|
+
* `MJGlobal.ProcessUUID` of the instance that first published the event; lets an instance drop its
|
|
68
|
+
* own message when it echoes back from the replication bus.
|
|
69
|
+
*/
|
|
70
|
+
SourceServerId?: string;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/** Who may receive an event, resolved from the session at publish time. */
|
|
74
|
+
export interface RealtimeSessionEventRouting {
|
|
75
|
+
/** The session the event is addressed to. */
|
|
76
|
+
AgentSessionID: string;
|
|
77
|
+
/** The session's owner. */
|
|
78
|
+
OwnerUserID: string;
|
|
79
|
+
/** The session conversation's `ExternalID` (anonymous scope id), or null. */
|
|
80
|
+
ScopeKey: string | null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Input to {@link RealtimeSessionEventService.Publish}. */
|
|
84
|
+
export interface PublishRealtimeSessionEventInput<T extends RealtimeSessionEventType> {
|
|
85
|
+
/** The session to address. */
|
|
86
|
+
AgentSessionID: string;
|
|
87
|
+
/** Event type — a key of `RealtimeSessionEventPayloadMap`. */
|
|
88
|
+
Type: T;
|
|
89
|
+
/** Type-specific data; must be JSON-serializable. */
|
|
90
|
+
Payload: RealtimeSessionEventPayload<T>;
|
|
91
|
+
/**
|
|
92
|
+
* The identity to read the session under. Server-side publishers pass a system/service user — the
|
|
93
|
+
* session row is being addressed by the server, not on a caller's behalf.
|
|
94
|
+
*/
|
|
95
|
+
ContextUser: UserInfo;
|
|
96
|
+
/** Provider to read the session with. Defaults to the process-global provider (server bootstrap use). */
|
|
97
|
+
Provider?: IMetadataProvider;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Outcome of a publish. A failure is never silent: it is also logged. */
|
|
101
|
+
export interface PublishRealtimeSessionEventResult {
|
|
102
|
+
/** True when the event was handed to the PubSub. */
|
|
103
|
+
Success: boolean;
|
|
104
|
+
/** Why it was not, when it was not. */
|
|
105
|
+
ErrorMessage?: string;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Called for every locally-published event so the host can replicate it to other instances. */
|
|
109
|
+
export type RealtimeSessionEventReplicationHook = (payload: RealtimeSessionEventTopicPayload) => void;
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Publishes realtime session events. A {@link BaseSingleton}: there is one publisher per process, and
|
|
113
|
+
* the replication hook is process-wide state.
|
|
114
|
+
*/
|
|
115
|
+
export class RealtimeSessionEventService extends BaseSingleton<RealtimeSessionEventService> {
|
|
116
|
+
private replicationHook: RealtimeSessionEventReplicationHook | undefined;
|
|
117
|
+
|
|
118
|
+
protected constructor() {
|
|
119
|
+
super();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** The process-wide instance. */
|
|
123
|
+
public static get Instance(): RealtimeSessionEventService {
|
|
124
|
+
return super.getInstance<RealtimeSessionEventService>();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Publishes an event to one live session. Reads the session to snapshot its owner and scope, so the
|
|
129
|
+
* caller never supplies (and can never get wrong) who may receive the event.
|
|
130
|
+
*
|
|
131
|
+
* Does not require the session to be open: an event for a closed session simply reaches no
|
|
132
|
+
* subscriber.
|
|
133
|
+
*
|
|
134
|
+
* @returns a result rather than throwing — event delivery is best-effort, durable state lives
|
|
135
|
+
* elsewhere (the verification status query is the backstop for a missed `identity.verified`).
|
|
136
|
+
*/
|
|
137
|
+
public async Publish<T extends RealtimeSessionEventType>(
|
|
138
|
+
input: PublishRealtimeSessionEventInput<T>,
|
|
139
|
+
): Promise<PublishRealtimeSessionEventResult> {
|
|
140
|
+
try {
|
|
141
|
+
const provider = input.Provider ?? Metadata.Provider; // global-provider-ok: documented last-resort fallback for server-bootstrap publishers
|
|
142
|
+
const loaded = await LoadSessionForAccess(input.AgentSessionID, input.ContextUser, provider);
|
|
143
|
+
if (!loaded) {
|
|
144
|
+
return this.fail(`Realtime session ${input.AgentSessionID} not found; event '${input.Type}' not published.`);
|
|
145
|
+
}
|
|
146
|
+
return this.PublishRouted(
|
|
147
|
+
{
|
|
148
|
+
AgentSessionID: loaded.Session.ID,
|
|
149
|
+
OwnerUserID: loaded.Session.UserID,
|
|
150
|
+
ScopeKey: loaded.ConversationExternalID,
|
|
151
|
+
},
|
|
152
|
+
input.Type,
|
|
153
|
+
input.Payload,
|
|
154
|
+
);
|
|
155
|
+
} catch (error) {
|
|
156
|
+
return this.fail(`Publishing '${input.Type}' to session ${input.AgentSessionID} failed: ${errorText(error)}`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Publishes an event when the caller already holds the session's routing (the verification service
|
|
162
|
+
* has just loaded the session, so it must not pay a second read).
|
|
163
|
+
*/
|
|
164
|
+
public PublishRouted<T extends RealtimeSessionEventType>(
|
|
165
|
+
routing: RealtimeSessionEventRouting,
|
|
166
|
+
type: T,
|
|
167
|
+
payload: RealtimeSessionEventPayload<T>,
|
|
168
|
+
): PublishRealtimeSessionEventResult {
|
|
169
|
+
const wire = SerializeRealtimeSessionEvent({
|
|
170
|
+
Type: type,
|
|
171
|
+
AgentSessionID: routing.AgentSessionID,
|
|
172
|
+
OccurredAt: new Date().toISOString(),
|
|
173
|
+
Payload: payload,
|
|
174
|
+
});
|
|
175
|
+
const topicPayload: RealtimeSessionEventTopicPayload = {
|
|
176
|
+
...wire,
|
|
177
|
+
OwnerUserID: routing.OwnerUserID,
|
|
178
|
+
ScopeKey: routing.ScopeKey,
|
|
179
|
+
SourceServerId: MJGlobal.Instance.ProcessUUID,
|
|
180
|
+
};
|
|
181
|
+
if (!PubSubManager.Instance.PubSubEngine) {
|
|
182
|
+
return this.fail(`PubSub is not configured; event '${type}' for session ${routing.AgentSessionID} not published.`);
|
|
183
|
+
}
|
|
184
|
+
PubSubManager.Instance.Publish(REALTIME_SESSION_EVENTS_TOPIC, topicPayload);
|
|
185
|
+
this.replicate(topicPayload);
|
|
186
|
+
return { Success: true };
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Republishes an event that arrived from another server instance onto THIS instance's local topic
|
|
191
|
+
* only (never back through the replication hook, so there is no loop). The subscription filter still
|
|
192
|
+
* decides who receives it — a replica has no say in who sees what.
|
|
193
|
+
*/
|
|
194
|
+
public PublishReplicated(payload: RealtimeSessionEventTopicPayload): void {
|
|
195
|
+
PubSubManager.Instance.Publish(REALTIME_SESSION_EVENTS_TOPIC, payload);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Registers (or, with no argument, clears) the cross-instance fan-out hook. Inversion of control:
|
|
200
|
+
* this module must not know that Redis exists.
|
|
201
|
+
*/
|
|
202
|
+
public SetReplicationHook(hook?: RealtimeSessionEventReplicationHook): void {
|
|
203
|
+
this.replicationHook = hook;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** Hands a locally-published event to the replication hook, never letting a bus failure break local delivery. */
|
|
207
|
+
private replicate(payload: RealtimeSessionEventTopicPayload): void {
|
|
208
|
+
if (!this.replicationHook) {
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
try {
|
|
212
|
+
this.replicationHook(payload);
|
|
213
|
+
} catch (error) {
|
|
214
|
+
// Replication is an optimisation over the durable status query; local delivery already happened.
|
|
215
|
+
LogError(`RealtimeSessionEventService: replication hook failed: ${errorText(error)}`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
private fail(message: string): PublishRealtimeSessionEventResult {
|
|
220
|
+
LogError(`RealtimeSessionEventService: ${message}`);
|
|
221
|
+
return { Success: false, ErrorMessage: message };
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Validates an event received from the cross-instance message bus before it is republished locally.
|
|
227
|
+
*
|
|
228
|
+
* The bus carries messages from other processes, so every field is type-checked. Fails closed: an event
|
|
229
|
+
* with no routing identity can never be delivered safely, so it is dropped.
|
|
230
|
+
*
|
|
231
|
+
* @param raw - the JSON text taken off the bus
|
|
232
|
+
* @param localServerId - this process's `MJGlobal.ProcessUUID`, used to drop our own echo
|
|
233
|
+
* @returns the event to republish, or `null` to drop it
|
|
234
|
+
*/
|
|
235
|
+
export function ParseReplicatedRealtimeSessionEvent(raw: string, localServerId: string): RealtimeSessionEventTopicPayload | null {
|
|
236
|
+
let parsed: unknown;
|
|
237
|
+
try {
|
|
238
|
+
parsed = JSON.parse(raw);
|
|
239
|
+
} catch {
|
|
240
|
+
return null; // not JSON: nothing to deliver
|
|
241
|
+
}
|
|
242
|
+
if (typeof parsed !== 'object' || parsed === null) {
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
const record = parsed as Record<string, unknown>;
|
|
246
|
+
const { Type, AgentSessionID, OccurredAt, PayloadJson, OwnerUserID, ScopeKey, SourceServerId } = record;
|
|
247
|
+
if (SourceServerId === localServerId) {
|
|
248
|
+
return null; // our own message, echoed back
|
|
249
|
+
}
|
|
250
|
+
const requiredStrings = [Type, AgentSessionID, OccurredAt, PayloadJson, OwnerUserID];
|
|
251
|
+
if (requiredStrings.some((value) => typeof value !== 'string' || value.length === 0)) {
|
|
252
|
+
return null; // includes a missing owner: fail closed
|
|
253
|
+
}
|
|
254
|
+
if (ScopeKey !== null && ScopeKey !== undefined && typeof ScopeKey !== 'string') {
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
if (SourceServerId !== undefined && typeof SourceServerId !== 'string') {
|
|
258
|
+
return null;
|
|
259
|
+
}
|
|
260
|
+
return {
|
|
261
|
+
Type: Type as string,
|
|
262
|
+
AgentSessionID: AgentSessionID as string,
|
|
263
|
+
OccurredAt: OccurredAt as string,
|
|
264
|
+
PayloadJson: PayloadJson as string,
|
|
265
|
+
OwnerUserID: OwnerUserID as string,
|
|
266
|
+
ScopeKey: typeof ScopeKey === 'string' ? ScopeKey : null,
|
|
267
|
+
SourceServerId: typeof SourceServerId === 'string' ? SourceServerId : undefined,
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/** Message text of an unknown thrown value. */
|
|
272
|
+
function errorText(error: unknown): string {
|
|
273
|
+
return error instanceof Error ? error.message : String(error);
|
|
274
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview `RealtimeSessionVerificationService` — the production wiring of mid-session identity
|
|
3
|
+
* verification: the real {@link VerificationPorts} (session read/write, email, event publish, audit)
|
|
4
|
+
* behind a process-wide singleton the GraphQL resolver and the public verify route both call.
|
|
5
|
+
*
|
|
6
|
+
* The decisions live in `verificationWorkflow.ts`; this file is deliberately thin glue. See that
|
|
7
|
+
* module's header for the operation table, what verification does and does not do, and the
|
|
8
|
+
* concurrency contract; see `README.md` in this folder for the end-to-end flow and security model.
|
|
9
|
+
*
|
|
10
|
+
* The session-facing half of the ports (reads, trusted writes, event publish, audit — and the reasoning
|
|
11
|
+
* for which identity performs which) is {@link SessionVerificationPortsBase}; this file adds the server
|
|
12
|
+
* configuration to it.
|
|
13
|
+
*
|
|
14
|
+
* @module @memberjunction/server/realtimeSessions
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import type { UserInfo } from '@memberjunction/core';
|
|
18
|
+
import { BaseSingleton, TrimTrailingSlashes } from '@memberjunction/global';
|
|
19
|
+
import { CommunicationEngine } from '@memberjunction/communication-engine';
|
|
20
|
+
import { Message } from '@memberjunction/communication-types';
|
|
21
|
+
import { UserCache } from '@memberjunction/generic-database-provider';
|
|
22
|
+
import { configInfo } from '../config.js';
|
|
23
|
+
import { ResolveConfiguredPrincipal } from '../auth/principals.js';
|
|
24
|
+
import { SessionVerificationPortsBase } from './SessionVerificationPortsBase.js';
|
|
25
|
+
import { ReadIdentityVerificationPolicyLayer } from './verificationPolicy.js';
|
|
26
|
+
import {
|
|
27
|
+
RealtimeSessionVerificationWorkflow,
|
|
28
|
+
type OutboundEmail,
|
|
29
|
+
type PrincipalCaller,
|
|
30
|
+
type VerificationOperationResult,
|
|
31
|
+
type VerificationSettings,
|
|
32
|
+
} from './verificationWorkflow.js';
|
|
33
|
+
import { ConfiguredPublicUrl } from '../apiRoot.js';
|
|
34
|
+
|
|
35
|
+
/** Purpose label for principal-resolution diagnostics. */
|
|
36
|
+
const PRINCIPAL_PURPOSE = 'RealtimeVerification';
|
|
37
|
+
|
|
38
|
+
/** The base URL emailed links are built from: explicit setting, else the server's public URL (same derivation magic links use). */
|
|
39
|
+
function resolvePublicBaseUrl(explicit: string | undefined): string | undefined {
|
|
40
|
+
const candidate =
|
|
41
|
+
explicit?.trim() || ConfiguredPublicUrl();
|
|
42
|
+
return candidate ? TrimTrailingSlashes(candidate) : undefined;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** The shortest `hmacSecret` accepted. */
|
|
46
|
+
const MIN_HMAC_SECRET_LENGTH = 16;
|
|
47
|
+
|
|
48
|
+
/** True when the configured HMAC secret is long enough to protect the stored code hash. */
|
|
49
|
+
function hasUsableHmacSecret(secret: string | undefined): boolean {
|
|
50
|
+
return !!secret && secret.length >= MIN_HMAC_SECRET_LENGTH;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Reads the live settings from server config. */
|
|
54
|
+
function readSettings(): VerificationSettings {
|
|
55
|
+
const cfg = configInfo.realtime?.identityVerification;
|
|
56
|
+
const limits = cfg?.rateLimits;
|
|
57
|
+
return {
|
|
58
|
+
// A feature that cannot send mail, or cannot protect the codes it issues, is not available — whatever `enabled` says.
|
|
59
|
+
Enabled: !!cfg?.enabled && !!cfg.communicationProvider && hasUsableHmacSecret(cfg.hmacSecret),
|
|
60
|
+
CodeHashKey: cfg?.hmacSecret ?? '',
|
|
61
|
+
PolicyDefaults: ReadIdentityVerificationPolicyLayer(cfg?.policy),
|
|
62
|
+
PublicBaseUrl: resolvePublicBaseUrl(cfg?.publicBaseUrl),
|
|
63
|
+
RateLimits: {
|
|
64
|
+
SendWindowMs: limits?.sendWindowMs ?? 3_600_000,
|
|
65
|
+
PerIpSends: limits?.perIpSends ?? 10,
|
|
66
|
+
PerEmailDomainSends: limits?.perEmailDomainSends ?? 30,
|
|
67
|
+
PerEmailSends: limits?.perEmailSends ?? 3,
|
|
68
|
+
CodeAttemptWindowMs: limits?.codeAttemptWindowMs ?? 600_000,
|
|
69
|
+
PerSessionCodeAttempts: limits?.perSessionCodeAttempts ?? 10,
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* The production ports: {@link SessionVerificationPortsBase} plus the server configuration — the settings
|
|
76
|
+
* read from `realtime.identityVerification`, the configured service principal, and delivery through the
|
|
77
|
+
* configured communication provider. Exported for tests and for hosts that want to compose their own
|
|
78
|
+
* workflow around them; application code should use {@link RealtimeSessionVerificationService}.
|
|
79
|
+
*/
|
|
80
|
+
export class RuntimeVerificationPorts extends SessionVerificationPortsBase {
|
|
81
|
+
public GetSettings(): VerificationSettings {
|
|
82
|
+
return readSettings();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
public async SendEmail(email: OutboundEmail): Promise<{ Success: boolean; ErrorMessage?: string }> {
|
|
86
|
+
const cfg = configInfo.realtime?.identityVerification;
|
|
87
|
+
const user = this.ResolveServiceUser();
|
|
88
|
+
if (!cfg?.communicationProvider || !user) {
|
|
89
|
+
return { Success: false, ErrorMessage: 'No communication provider or service principal is configured.' };
|
|
90
|
+
}
|
|
91
|
+
try {
|
|
92
|
+
const engine = CommunicationEngine.Instance;
|
|
93
|
+
await engine.Config(false, user);
|
|
94
|
+
const message = new Message();
|
|
95
|
+
// Rehearsal mode: the engine builds and LOGS the message but never delivers it (see the config docs).
|
|
96
|
+
message.DryRun = cfg.deliveryMode === 'dry-run';
|
|
97
|
+
message.From = cfg.fromAddress ?? '';
|
|
98
|
+
message.To = email.To;
|
|
99
|
+
message.Subject = email.Subject;
|
|
100
|
+
message.Body = email.Text;
|
|
101
|
+
message.HTMLBody = email.Html;
|
|
102
|
+
const result = await engine.SendSingleMessage(cfg.communicationProvider, 'Email', message);
|
|
103
|
+
return result?.Success ? { Success: true } : { Success: false, ErrorMessage: result?.Error ?? 'unknown provider error' };
|
|
104
|
+
} catch (error) {
|
|
105
|
+
return { Success: false, ErrorMessage: error instanceof Error ? error.message : String(error) };
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
/** The configured service principal (see {@link SessionVerificationPortsBase}). */
|
|
111
|
+
protected ResolveServiceUser(): UserInfo | null {
|
|
112
|
+
const configured = configInfo.realtime?.identityVerification?.contextUserForVerification;
|
|
113
|
+
return ResolveConfiguredPrincipal(configured, PRINCIPAL_PURPOSE) ?? UserCache.Instance.GetSystemUser() ?? null;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* The process-wide verification service. Call it from the `RealtimeSessionVerificationResolver`
|
|
119
|
+
* (authenticated principal) and the public `/realtime/verify` router (link capability).
|
|
120
|
+
*/
|
|
121
|
+
export class RealtimeSessionVerificationService extends BaseSingleton<RealtimeSessionVerificationService> {
|
|
122
|
+
private workflow: RealtimeSessionVerificationWorkflow | undefined;
|
|
123
|
+
|
|
124
|
+
protected constructor() {
|
|
125
|
+
super();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** The process-wide instance. */
|
|
129
|
+
public static get Instance(): RealtimeSessionVerificationService {
|
|
130
|
+
return super.getInstance<RealtimeSessionVerificationService>();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Whether verification is switched on and able to send mail. */
|
|
134
|
+
public get IsEnabled(): boolean {
|
|
135
|
+
return readSettings().Enabled;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** See {@link RealtimeSessionVerificationWorkflow.RequestVerification}. */
|
|
139
|
+
public RequestVerification(
|
|
140
|
+
input: { AgentSessionID: string; Name: string; Email: string },
|
|
141
|
+
caller: PrincipalCaller,
|
|
142
|
+
): Promise<VerificationOperationResult> {
|
|
143
|
+
return this.getWorkflow().RequestVerification(input, caller);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** See {@link RealtimeSessionVerificationWorkflow.SubmitCode}. */
|
|
147
|
+
public SubmitCode(input: { AgentSessionID: string; Code: string }, caller: PrincipalCaller): Promise<VerificationOperationResult> {
|
|
148
|
+
return this.getWorkflow().SubmitCode(input, caller);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** See {@link RealtimeSessionVerificationWorkflow.RedeemLink}. */
|
|
152
|
+
public RedeemLink(token: string, clientIp?: string): Promise<VerificationOperationResult> {
|
|
153
|
+
return this.getWorkflow().RedeemLink(token, { Kind: 'link', ClientIp: clientIp });
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** See {@link RealtimeSessionVerificationWorkflow.GetStatus}. */
|
|
157
|
+
public GetStatus(input: { AgentSessionID: string }, caller: PrincipalCaller): Promise<VerificationOperationResult> {
|
|
158
|
+
return this.getWorkflow().GetStatus(input, caller);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** The workflow is created on first use so its limiters pick up the loaded config. */
|
|
162
|
+
private getWorkflow(): RealtimeSessionVerificationWorkflow {
|
|
163
|
+
if (!this.workflow) {
|
|
164
|
+
this.workflow = new RealtimeSessionVerificationWorkflow(new RuntimeVerificationPorts());
|
|
165
|
+
}
|
|
166
|
+
return this.workflow;
|
|
167
|
+
}
|
|
168
|
+
}
|