@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,702 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview The orchestration of mid-session identity verification, written against **ports** so the
|
|
3
|
+
* whole policy surface (every refusal, every limit, deadline extension, single use) is unit-testable
|
|
4
|
+
* without a database, a mail server or a PubSub.
|
|
5
|
+
*
|
|
6
|
+
* {@link RealtimeSessionVerificationWorkflow} owns the decisions and their order; the injected
|
|
7
|
+
* {@link VerificationPorts} own the side effects (read the session, write its `Config`, send the email,
|
|
8
|
+
* publish the event, write the audit row). `RealtimeSessionVerificationService` wires the real ports.
|
|
9
|
+
*
|
|
10
|
+
* ## Operations
|
|
11
|
+
*
|
|
12
|
+
* | Operation | Caller | What it does |
|
|
13
|
+
* |---|---|---|
|
|
14
|
+
* | {@link RealtimeSessionVerificationWorkflow.RequestVerification} | the session's own principal | validates, applies policy + limits, stores a pending verification, emails link + code |
|
|
15
|
+
* | {@link RealtimeSessionVerificationWorkflow.SubmitCode} | the session's own principal | redeems the typed code |
|
|
16
|
+
* | {@link RealtimeSessionVerificationWorkflow.RedeemLink} | anyone holding the emailed link (public route) | redeems the link token |
|
|
17
|
+
* | {@link RealtimeSessionVerificationWorkflow.GetStatus} | the session's own principal | reads the current state (the durable backstop for a missed event) |
|
|
18
|
+
*
|
|
19
|
+
* ## What a successful verification does — and does not do
|
|
20
|
+
*
|
|
21
|
+
* It records the verified identity on the session, **extends the session's deadline** when policy says
|
|
22
|
+
* to, publishes `identity.verified`, and writes an audit entry. It does **not** create an MJ user, swap
|
|
23
|
+
* the session's principal, or link a Person/User: the principal never changes, so ownership and
|
|
24
|
+
* row-level security are untouched. Linking the verified email to a record is an application decision,
|
|
25
|
+
* made by a server-side subscriber to the event.
|
|
26
|
+
*
|
|
27
|
+
* ## Concurrency
|
|
28
|
+
*
|
|
29
|
+
* Operations on one session are serialised in-process (a per-session mutex) so two simultaneous
|
|
30
|
+
* redemptions cannot both pass, and every write re-reads the session's current state first. Across
|
|
31
|
+
* *replicas* the database offers no compare-and-swap on `Config`, so strict single-use and attempt
|
|
32
|
+
* counting are exact per instance and best-effort across them: the worst a cross-replica race can do is
|
|
33
|
+
* let two simultaneous redemptions of the same valid secret both succeed (they converge on the same
|
|
34
|
+
* verified identity) or let a burst of parallel guesses count as one attempt. The per-session attempt
|
|
35
|
+
* rate limiter and the code's 6-digit space bound the latter.
|
|
36
|
+
*
|
|
37
|
+
* @module @memberjunction/server/realtimeSessions
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
import { LogError, LogStatus, type IMetadataProvider, type UserInfo } from '@memberjunction/core';
|
|
41
|
+
import { IsValidUUID } from '@memberjunction/global';
|
|
42
|
+
import type { IdentityVerifiedEventPayload } from '@memberjunction/ai-core-plus';
|
|
43
|
+
import { AuthorizeSessionAccess } from './sessionAccess.js';
|
|
44
|
+
import { SlidingWindowRateLimiter } from './SlidingWindowRateLimiter.js';
|
|
45
|
+
import {
|
|
46
|
+
EvaluateEmailDomainPolicy,
|
|
47
|
+
ParseEmailAddress,
|
|
48
|
+
ReadIdentityVerificationPolicyLayer,
|
|
49
|
+
ResolveIdentityVerificationPolicy,
|
|
50
|
+
type IdentityVerificationPolicy,
|
|
51
|
+
type ResolvedIdentityVerificationPolicy,
|
|
52
|
+
} from './verificationPolicy.js';
|
|
53
|
+
import {
|
|
54
|
+
ComputeVerifiedDeadline,
|
|
55
|
+
EvaluateRedemption,
|
|
56
|
+
IssueVerification,
|
|
57
|
+
NormalizeVerificationCode,
|
|
58
|
+
ParseConfigObject,
|
|
59
|
+
ParseVerificationToken,
|
|
60
|
+
ReadIdentityVerificationState,
|
|
61
|
+
SanitizeVerificationName,
|
|
62
|
+
WriteIdentityVerificationState,
|
|
63
|
+
type IdentityVerificationState,
|
|
64
|
+
type VerifiedIdentity,
|
|
65
|
+
} from './verificationCore.js';
|
|
66
|
+
import { BuildVerificationEmail } from './verificationPages.js';
|
|
67
|
+
|
|
68
|
+
// ───────────────────────── ports ─────────────────────────
|
|
69
|
+
|
|
70
|
+
/** Who is calling. A `principal` is an authenticated GraphQL caller; a `link` is the public verify route. */
|
|
71
|
+
export type VerificationCaller =
|
|
72
|
+
| {
|
|
73
|
+
Kind: 'principal';
|
|
74
|
+
/** The authenticated user the server built from the caller's token — the source of truth for ownership. */
|
|
75
|
+
ContextUser: UserInfo;
|
|
76
|
+
/** The request-scoped provider. */
|
|
77
|
+
Provider: IMetadataProvider;
|
|
78
|
+
/** The caller's network address, for abuse limits only. */
|
|
79
|
+
ClientIp?: string;
|
|
80
|
+
}
|
|
81
|
+
| { Kind: 'link'; ClientIp?: string };
|
|
82
|
+
|
|
83
|
+
/** The principal variant of {@link VerificationCaller}. */
|
|
84
|
+
export type PrincipalCaller = Extract<VerificationCaller, { Kind: 'principal' }>;
|
|
85
|
+
|
|
86
|
+
/** A read of a session, reduced to what verification needs. */
|
|
87
|
+
export interface SessionSnapshot {
|
|
88
|
+
AgentSessionID: string;
|
|
89
|
+
/** `AIAgentSession.UserID`. */
|
|
90
|
+
OwnerUserID: string;
|
|
91
|
+
/** `Conversation.ExternalID` (anonymous scope id), or null. */
|
|
92
|
+
ConversationExternalID: string | null;
|
|
93
|
+
Status: 'Active' | 'Idle' | 'Closed';
|
|
94
|
+
/** `AIAgentSession.Config` as stored. */
|
|
95
|
+
ConfigRaw: string | null;
|
|
96
|
+
/** When the session started, epoch ms. */
|
|
97
|
+
StartedAtMs: number;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** A read-modify-write of a session's `Config`: given the CURRENT snapshot, decide what to write. */
|
|
101
|
+
export type ConfigMutation<T> = (current: SessionSnapshot) => { NextConfigRaw: string | null; Outcome: T };
|
|
102
|
+
|
|
103
|
+
/** Result of {@link VerificationPorts.MutateSessionConfig}. */
|
|
104
|
+
export interface ConfigMutationResult<T> {
|
|
105
|
+
/** The session as read just before the mutation; undefined when it could not be read. */
|
|
106
|
+
Session?: SessionSnapshot;
|
|
107
|
+
/** What the mutation returned; undefined when it never ran. */
|
|
108
|
+
Outcome?: T;
|
|
109
|
+
/** True when a write was requested AND succeeded. */
|
|
110
|
+
Persisted: boolean;
|
|
111
|
+
/** Why a requested write (or the read) failed. */
|
|
112
|
+
ErrorMessage?: string;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** An email to send. */
|
|
116
|
+
export interface OutboundEmail {
|
|
117
|
+
To: string;
|
|
118
|
+
Subject: string;
|
|
119
|
+
Text: string;
|
|
120
|
+
Html: string;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** What the audit trail records for a completed verification. */
|
|
124
|
+
export interface VerificationAuditEntry {
|
|
125
|
+
AgentSessionID: string;
|
|
126
|
+
OwnerUserID: string;
|
|
127
|
+
Email: string;
|
|
128
|
+
Method: VerifiedIdentity['Method'];
|
|
129
|
+
ClientIp?: string;
|
|
130
|
+
/** The session's deadline after verification, when it was extended. */
|
|
131
|
+
MaxSessionDeadlineIso?: string;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Settings the workflow reads (resolved from server config by the service). */
|
|
135
|
+
export interface VerificationSettings {
|
|
136
|
+
/** Master switch. Must be false when no {@link VerificationSettings.CodeHashKey} is available. */
|
|
137
|
+
Enabled: boolean;
|
|
138
|
+
/**
|
|
139
|
+
* Server-only key the typed code is HMAC'd under before it is stored. It must never reach a client or a
|
|
140
|
+
* session row: the session owner can read their own `Config`, and without this key a six-digit code
|
|
141
|
+
* hash cannot be reversed offline. See `HashVerificationCode`.
|
|
142
|
+
*/
|
|
143
|
+
CodeHashKey: string;
|
|
144
|
+
/** Base policy layer from server config. */
|
|
145
|
+
PolicyDefaults: IdentityVerificationPolicy;
|
|
146
|
+
/** Base URL for the emailed link, no trailing slash; undefined ⇒ the feature cannot send. */
|
|
147
|
+
PublicBaseUrl: string | undefined;
|
|
148
|
+
/** Limits applied in memory (0 disables a limit). */
|
|
149
|
+
RateLimits: {
|
|
150
|
+
SendWindowMs: number;
|
|
151
|
+
PerIpSends: number;
|
|
152
|
+
PerEmailDomainSends: number;
|
|
153
|
+
PerEmailSends: number;
|
|
154
|
+
CodeAttemptWindowMs: number;
|
|
155
|
+
PerSessionCodeAttempts: number;
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** Every side effect the workflow needs. */
|
|
160
|
+
export interface VerificationPorts {
|
|
161
|
+
GetSettings(): VerificationSettings;
|
|
162
|
+
/** Current time, epoch ms. */
|
|
163
|
+
Now(): number;
|
|
164
|
+
/**
|
|
165
|
+
* Loads a session as the CALLER (so row-level security is the first gate). For a `link` caller there is
|
|
166
|
+
* no identity to load as, so the real port reads as the configured service principal.
|
|
167
|
+
*/
|
|
168
|
+
LoadSession(agentSessionId: string, caller: VerificationCaller): Promise<SessionSnapshot | null>;
|
|
169
|
+
/** Re-reads the session fresh, runs `mutate` on it, and persists the returned `Config` (trusted write). */
|
|
170
|
+
MutateSessionConfig<T>(agentSessionId: string, caller: VerificationCaller, mutate: ConfigMutation<T>): Promise<ConfigMutationResult<T>>;
|
|
171
|
+
/** Sends an email. Must not throw; reports failure in the result. */
|
|
172
|
+
SendEmail(email: OutboundEmail): Promise<{ Success: boolean; ErrorMessage?: string }>;
|
|
173
|
+
/** Publishes `identity.verified` to the session. Must not throw. */
|
|
174
|
+
PublishVerified(
|
|
175
|
+
routing: { AgentSessionID: string; OwnerUserID: string; ScopeKey: string | null },
|
|
176
|
+
payload: IdentityVerifiedEventPayload,
|
|
177
|
+
): { Success: boolean; ErrorMessage?: string };
|
|
178
|
+
/** Records the audit entry. Must not throw. */
|
|
179
|
+
WriteAudit(entry: VerificationAuditEntry): Promise<void>;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// ───────────────────────── results ─────────────────────────
|
|
183
|
+
|
|
184
|
+
/** Machine-readable failure reasons. Stable: clients branch on these. */
|
|
185
|
+
export type VerificationErrorCode =
|
|
186
|
+
| 'verification_unavailable' // feature off / not configured
|
|
187
|
+
| 'invalid_input' // missing/blank name or malformed session id
|
|
188
|
+
| 'invalid_email' // not an acceptable address
|
|
189
|
+
| 'session_not_found' // no such session, or not yours (indistinguishable on purpose)
|
|
190
|
+
| 'session_closed' // the session has ended
|
|
191
|
+
| 'domain_blocked' // the address's domain is on the block list
|
|
192
|
+
| 'consumer_domain' // policy requires a business address
|
|
193
|
+
| 'send_limit_reached' // the session has used all its verification emails
|
|
194
|
+
| 'rate_limited' // too many requests (IP / domain / address / attempts)
|
|
195
|
+
| 'email_send_failed' // the email could not be delivered; nothing was consumed
|
|
196
|
+
| 'no_pending_verification' // nothing to redeem
|
|
197
|
+
| 'code_expired' // the pending verification expired
|
|
198
|
+
| 'invalid_code' // wrong code
|
|
199
|
+
| 'attempts_exhausted' // too many wrong codes; request a new one
|
|
200
|
+
| 'invalid_link' // the link token is not redeemable (uniformly: unknown, malformed, expired, used)
|
|
201
|
+
| 'persist_failed'; // the server could not record the outcome
|
|
202
|
+
|
|
203
|
+
/** Where a session stands. */
|
|
204
|
+
export type VerificationStateName = 'unverified' | 'pending' | 'verified';
|
|
205
|
+
|
|
206
|
+
/** Result of every workflow operation. */
|
|
207
|
+
export interface VerificationOperationResult {
|
|
208
|
+
Success: boolean;
|
|
209
|
+
/** The session's verification state after the operation (best knowledge; `unverified` on early refusals). */
|
|
210
|
+
VerificationState: VerificationStateName;
|
|
211
|
+
ErrorCode?: VerificationErrorCode;
|
|
212
|
+
/** A message safe to show a person. */
|
|
213
|
+
Message?: string;
|
|
214
|
+
/** When the pending link/code expires (ISO). */
|
|
215
|
+
ExpiresAt?: string;
|
|
216
|
+
/** Verification emails this session may still trigger. */
|
|
217
|
+
SendsRemaining?: number;
|
|
218
|
+
/** Wrong code entries left before the pending verification is voided. */
|
|
219
|
+
AttemptsRemaining?: number;
|
|
220
|
+
/** Seconds to wait before retrying, when rate limited. */
|
|
221
|
+
RetryAfterSeconds?: number;
|
|
222
|
+
/** The verified address, once verified. */
|
|
223
|
+
VerifiedEmail?: string;
|
|
224
|
+
/** When verification completed (ISO). */
|
|
225
|
+
VerifiedAt?: string;
|
|
226
|
+
/** The session's deadline after verification, when it was extended (ISO). */
|
|
227
|
+
MaxSessionDeadlineIso?: string;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/** Builds a failure result. */
|
|
231
|
+
function failure(
|
|
232
|
+
code: VerificationErrorCode,
|
|
233
|
+
message: string,
|
|
234
|
+
extra: Partial<VerificationOperationResult> = {},
|
|
235
|
+
): VerificationOperationResult {
|
|
236
|
+
return { Success: false, VerificationState: 'unverified', ErrorCode: code, Message: message, ...extra };
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/** Result for a session that is already verified (idempotent success). */
|
|
240
|
+
function alreadyVerified(state: IdentityVerificationState): VerificationOperationResult {
|
|
241
|
+
return {
|
|
242
|
+
Success: true,
|
|
243
|
+
VerificationState: 'verified',
|
|
244
|
+
VerifiedEmail: state.Verified?.Email,
|
|
245
|
+
VerifiedAt: state.Verified?.VerifiedAt,
|
|
246
|
+
Message: 'Your email is already verified.',
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const SESSION_NOT_FOUND = 'Session not found.';
|
|
251
|
+
const UNAVAILABLE = 'Email verification is not available.';
|
|
252
|
+
|
|
253
|
+
// ───────────────────────── workflow ─────────────────────────
|
|
254
|
+
|
|
255
|
+
/** Mutation outcomes of the request step. */
|
|
256
|
+
type StoreOutcome = { Kind: 'stored'; SendCount: number } | { Kind: 'already_verified' } | { Kind: 'send_limit' };
|
|
257
|
+
|
|
258
|
+
/** Mutation outcome of a redemption: the engine's decision plus the deadline it wrote. */
|
|
259
|
+
interface RedemptionMutationOutcome {
|
|
260
|
+
Decision: ReturnType<typeof EvaluateRedemption>;
|
|
261
|
+
DeadlineIso: string | undefined;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* The decisions and their order. See the module header for the contract.
|
|
266
|
+
*/
|
|
267
|
+
export class RealtimeSessionVerificationWorkflow {
|
|
268
|
+
private readonly ipLimiter: SlidingWindowRateLimiter;
|
|
269
|
+
private readonly domainLimiter: SlidingWindowRateLimiter;
|
|
270
|
+
private readonly emailLimiter: SlidingWindowRateLimiter;
|
|
271
|
+
private readonly codeLimiter: SlidingWindowRateLimiter;
|
|
272
|
+
private readonly locks = new Map<string, Promise<void>>();
|
|
273
|
+
|
|
274
|
+
constructor(private readonly ports: VerificationPorts) {
|
|
275
|
+
const limits = ports.GetSettings().RateLimits;
|
|
276
|
+
this.ipLimiter = new SlidingWindowRateLimiter({ Limit: limits.PerIpSends, WindowMs: limits.SendWindowMs });
|
|
277
|
+
this.domainLimiter = new SlidingWindowRateLimiter({ Limit: limits.PerEmailDomainSends, WindowMs: limits.SendWindowMs });
|
|
278
|
+
this.emailLimiter = new SlidingWindowRateLimiter({ Limit: limits.PerEmailSends, WindowMs: limits.SendWindowMs });
|
|
279
|
+
this.codeLimiter = new SlidingWindowRateLimiter({ Limit: limits.PerSessionCodeAttempts, WindowMs: limits.CodeAttemptWindowMs });
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/** Whether the feature is switched on. */
|
|
283
|
+
public get IsEnabled(): boolean {
|
|
284
|
+
return this.ports.GetSettings().Enabled;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Asks to verify `Email` for the caller's session: validates, applies policy and limits, stores a
|
|
289
|
+
* pending verification (hashes only) and emails a one-time link plus a typed code.
|
|
290
|
+
*
|
|
291
|
+
* Nothing is consumed unless the email is actually sent: a delivery failure rolls the stored state back.
|
|
292
|
+
*/
|
|
293
|
+
public async RequestVerification(
|
|
294
|
+
input: { AgentSessionID: string; Name: string; Email: string },
|
|
295
|
+
caller: PrincipalCaller,
|
|
296
|
+
): Promise<VerificationOperationResult> {
|
|
297
|
+
const settings = this.ports.GetSettings();
|
|
298
|
+
if (!settings.Enabled || !settings.PublicBaseUrl) {
|
|
299
|
+
return failure('verification_unavailable', UNAVAILABLE);
|
|
300
|
+
}
|
|
301
|
+
const name = SanitizeVerificationName(input.Name);
|
|
302
|
+
if (!name) {
|
|
303
|
+
return failure('invalid_input', 'Please enter your name.');
|
|
304
|
+
}
|
|
305
|
+
const email = ParseEmailAddress(input.Email);
|
|
306
|
+
if (!email.Ok) {
|
|
307
|
+
return failure('invalid_email', 'Please enter a valid email address.');
|
|
308
|
+
}
|
|
309
|
+
return this.withSessionLock(input.AgentSessionID, async () => {
|
|
310
|
+
const access = await this.authorize(input.AgentSessionID, caller, false);
|
|
311
|
+
if (access.Ok === false) {
|
|
312
|
+
return access.Result;
|
|
313
|
+
}
|
|
314
|
+
const state = ReadIdentityVerificationState(access.Session.ConfigRaw, ReadIdentityVerificationPolicyLayer);
|
|
315
|
+
if (state.Verified) {
|
|
316
|
+
return alreadyVerified(state);
|
|
317
|
+
}
|
|
318
|
+
const policy = ResolveIdentityVerificationPolicy([settings.PolicyDefaults, state.Policy]);
|
|
319
|
+
const refusal = this.checkRequestRefusals(email, state, policy, caller);
|
|
320
|
+
return refusal ?? this.issueAndSend(access.Session, { Email: email.Email, Name: name }, policy, caller, settings);
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Redeems the typed code for the caller's session. Wrong entries are counted against the pending
|
|
326
|
+
* verification and void it at `maxCodeAttempts`; submissions are also rate limited per session.
|
|
327
|
+
*/
|
|
328
|
+
public async SubmitCode(
|
|
329
|
+
input: { AgentSessionID: string; Code: string },
|
|
330
|
+
caller: PrincipalCaller,
|
|
331
|
+
): Promise<VerificationOperationResult> {
|
|
332
|
+
const settings = this.ports.GetSettings();
|
|
333
|
+
if (!settings.Enabled) {
|
|
334
|
+
return failure('verification_unavailable', UNAVAILABLE);
|
|
335
|
+
}
|
|
336
|
+
if (!IsValidUUID(input.AgentSessionID)) {
|
|
337
|
+
return failure('session_not_found', SESSION_NOT_FOUND);
|
|
338
|
+
}
|
|
339
|
+
const gate = this.codeLimiter.TryConsume(input.AgentSessionID.toLowerCase(), this.ports.Now());
|
|
340
|
+
if (!gate.Allowed) {
|
|
341
|
+
return failure('rate_limited', 'Too many attempts. Please wait a moment and try again.', {
|
|
342
|
+
RetryAfterSeconds: Math.ceil(gate.RetryAfterMs / 1000),
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
const code = NormalizeVerificationCode(input.Code);
|
|
346
|
+
if (!code) {
|
|
347
|
+
// Cannot match any stored code, so it must not burn the person's attempts either.
|
|
348
|
+
return failure('invalid_code', 'That code is not valid. Check it and try again.');
|
|
349
|
+
}
|
|
350
|
+
return this.withSessionLock(input.AgentSessionID, async () => {
|
|
351
|
+
const access = await this.authorize(input.AgentSessionID, caller, false);
|
|
352
|
+
if (access.Ok === false) {
|
|
353
|
+
return access.Result;
|
|
354
|
+
}
|
|
355
|
+
return this.redeem(input.AgentSessionID, caller, { Kind: 'code', Code: code, AgentSessionID: input.AgentSessionID });
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Redeems an emailed link token. The capability IS the token, so there is no principal; every
|
|
361
|
+
* failure — malformed, unknown, expired, used, voided — is the same result so the response cannot
|
|
362
|
+
* be used to learn anything about a session.
|
|
363
|
+
*/
|
|
364
|
+
public async RedeemLink(token: string, caller: Extract<VerificationCaller, { Kind: 'link' }>): Promise<VerificationOperationResult> {
|
|
365
|
+
if (!this.ports.GetSettings().Enabled) {
|
|
366
|
+
return failure('verification_unavailable', UNAVAILABLE);
|
|
367
|
+
}
|
|
368
|
+
const parsed = ParseVerificationToken(token);
|
|
369
|
+
if (!parsed) {
|
|
370
|
+
return failure('invalid_link', 'This link is no longer valid.');
|
|
371
|
+
}
|
|
372
|
+
return this.withSessionLock(parsed.AgentSessionID, () =>
|
|
373
|
+
this.redeem(parsed.AgentSessionID, caller, { Kind: 'token', Token: token }),
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Reads the session's verification state. This is the **durable** counterpart of the
|
|
379
|
+
* `identity.verified` event: a subscriber that was disconnected when the event fired recovers by
|
|
380
|
+
* asking here.
|
|
381
|
+
*/
|
|
382
|
+
public async GetStatus(input: { AgentSessionID: string }, caller: PrincipalCaller): Promise<VerificationOperationResult> {
|
|
383
|
+
const settings = this.ports.GetSettings();
|
|
384
|
+
if (!settings.Enabled) {
|
|
385
|
+
return failure('verification_unavailable', UNAVAILABLE);
|
|
386
|
+
}
|
|
387
|
+
const access = await this.authorize(input.AgentSessionID, caller, true);
|
|
388
|
+
if (access.Ok === false) {
|
|
389
|
+
return access.Result;
|
|
390
|
+
}
|
|
391
|
+
const state = ReadIdentityVerificationState(access.Session.ConfigRaw, ReadIdentityVerificationPolicyLayer);
|
|
392
|
+
if (state.Verified) {
|
|
393
|
+
return alreadyVerified(state);
|
|
394
|
+
}
|
|
395
|
+
const policy = ResolveIdentityVerificationPolicy([settings.PolicyDefaults, state.Policy]);
|
|
396
|
+
const pendingLive = state.Pending && Date.parse(state.Pending.ExpiresAt) > this.ports.Now() ? state.Pending : undefined;
|
|
397
|
+
return {
|
|
398
|
+
Success: true,
|
|
399
|
+
VerificationState: pendingLive ? 'pending' : 'unverified',
|
|
400
|
+
ExpiresAt: pendingLive?.ExpiresAt,
|
|
401
|
+
SendsRemaining: Math.max(0, policy.maxSendsPerSession - state.SendCount),
|
|
402
|
+
AttemptsRemaining: pendingLive ? Math.max(0, policy.maxCodeAttempts - pendingLive.CodeAttempts) : undefined,
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// ───────────── request internals ─────────────
|
|
407
|
+
|
|
408
|
+
/** The policy and limit refusals of a request, in order; null when the request may proceed. */
|
|
409
|
+
private checkRequestRefusals(
|
|
410
|
+
email: { Email: string; Domain: string },
|
|
411
|
+
state: IdentityVerificationState,
|
|
412
|
+
policy: ResolvedIdentityVerificationPolicy,
|
|
413
|
+
caller: PrincipalCaller,
|
|
414
|
+
): VerificationOperationResult | null {
|
|
415
|
+
const domain = EvaluateEmailDomainPolicy(email.Domain, policy);
|
|
416
|
+
if (domain.Allowed === false) {
|
|
417
|
+
return domain.Reason === 'domain_blocked'
|
|
418
|
+
? failure('domain_blocked', 'Email addresses from that domain cannot be used here.')
|
|
419
|
+
: failure('consumer_domain', 'Please use your work email address.');
|
|
420
|
+
}
|
|
421
|
+
if (state.SendCount >= policy.maxSendsPerSession) {
|
|
422
|
+
return failure('send_limit_reached', 'The maximum number of verification emails for this conversation has been reached.', {
|
|
423
|
+
SendsRemaining: 0,
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
return this.checkSendRateLimits(email, caller.ClientIp);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/** Applies the per-IP, per-domain and per-address send limits; null when all admit the send. */
|
|
430
|
+
private checkSendRateLimits(email: { Email: string; Domain: string }, clientIp: string | undefined): VerificationOperationResult | null {
|
|
431
|
+
const now = this.ports.Now();
|
|
432
|
+
const decisions = [
|
|
433
|
+
clientIp ? this.ipLimiter.TryConsume(clientIp, now) : { Allowed: true, RetryAfterMs: 0 },
|
|
434
|
+
this.domainLimiter.TryConsume(email.Domain, now),
|
|
435
|
+
this.emailLimiter.TryConsume(email.Email, now),
|
|
436
|
+
];
|
|
437
|
+
const refused = decisions.find((decision) => !decision.Allowed);
|
|
438
|
+
if (!refused) {
|
|
439
|
+
return null;
|
|
440
|
+
}
|
|
441
|
+
return failure('rate_limited', 'Too many verification requests. Please try again later.', {
|
|
442
|
+
RetryAfterSeconds: Math.ceil(refused.RetryAfterMs / 1000),
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/** Stores the pending verification, emails it, and rolls the store back if the email cannot be sent. */
|
|
447
|
+
private async issueAndSend(
|
|
448
|
+
session: SessionSnapshot,
|
|
449
|
+
recipient: { Email: string; Name: string },
|
|
450
|
+
policy: ResolvedIdentityVerificationPolicy,
|
|
451
|
+
caller: PrincipalCaller,
|
|
452
|
+
settings: VerificationSettings,
|
|
453
|
+
): Promise<VerificationOperationResult> {
|
|
454
|
+
const issued = IssueVerification({
|
|
455
|
+
AgentSessionID: session.AgentSessionID,
|
|
456
|
+
Email: recipient.Email,
|
|
457
|
+
Name: recipient.Name,
|
|
458
|
+
TtlMinutes: policy.linkTtlMinutes,
|
|
459
|
+
NowMs: this.ports.Now(),
|
|
460
|
+
CodeHashKey: settings.CodeHashKey,
|
|
461
|
+
});
|
|
462
|
+
const stored = await this.ports.MutateSessionConfig<StoreOutcome>(session.AgentSessionID, caller, (current) => {
|
|
463
|
+
const state = ReadIdentityVerificationState(current.ConfigRaw, ReadIdentityVerificationPolicyLayer);
|
|
464
|
+
if (state.Verified) {
|
|
465
|
+
return { NextConfigRaw: null, Outcome: { Kind: 'already_verified' } };
|
|
466
|
+
}
|
|
467
|
+
if (state.SendCount >= policy.maxSendsPerSession) {
|
|
468
|
+
return { NextConfigRaw: null, Outcome: { Kind: 'send_limit' } };
|
|
469
|
+
}
|
|
470
|
+
const next: IdentityVerificationState = { ...state, SendCount: state.SendCount + 1, Pending: issued.Pending };
|
|
471
|
+
return { NextConfigRaw: WriteIdentityVerificationState(current.ConfigRaw, next), Outcome: { Kind: 'stored', SendCount: next.SendCount } };
|
|
472
|
+
});
|
|
473
|
+
const early = this.requestStoreRefusal(stored);
|
|
474
|
+
if (early || stored.Outcome?.Kind !== 'stored') {
|
|
475
|
+
return early ?? failure('persist_failed', 'We could not start verification. Please try again.');
|
|
476
|
+
}
|
|
477
|
+
const email = BuildVerificationEmail({
|
|
478
|
+
Name: recipient.Name,
|
|
479
|
+
Code: issued.Code,
|
|
480
|
+
LinkUrl: `${settings.PublicBaseUrl}/realtime/verify/${issued.Token}`,
|
|
481
|
+
TtlMinutes: policy.linkTtlMinutes,
|
|
482
|
+
});
|
|
483
|
+
const sent = await this.ports.SendEmail({ To: recipient.Email, Subject: email.Subject, Text: email.Text, Html: email.Html });
|
|
484
|
+
if (!sent.Success) {
|
|
485
|
+
LogError(`RealtimeSessionVerification: email to the verifier of session ${session.AgentSessionID} failed: ${sent.ErrorMessage ?? 'unknown error'}`);
|
|
486
|
+
await this.rollbackPending(session.AgentSessionID, caller, issued.Pending.TokenHash);
|
|
487
|
+
return failure('email_send_failed', 'We could not send the email. Please check the address and try again.');
|
|
488
|
+
}
|
|
489
|
+
return {
|
|
490
|
+
Success: true,
|
|
491
|
+
VerificationState: 'pending',
|
|
492
|
+
ExpiresAt: issued.Pending.ExpiresAt,
|
|
493
|
+
SendsRemaining: Math.max(0, policy.maxSendsPerSession - stored.Outcome.SendCount),
|
|
494
|
+
Message: 'We emailed you a link and a code.',
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
/** Maps a failed/short-circuited store into a result; null when the pending verification was stored. */
|
|
499
|
+
private requestStoreRefusal(stored: ConfigMutationResult<StoreOutcome>): VerificationOperationResult | null {
|
|
500
|
+
if (stored.Outcome?.Kind === 'already_verified') {
|
|
501
|
+
return { Success: true, VerificationState: 'verified', Message: 'Your email is already verified.' };
|
|
502
|
+
}
|
|
503
|
+
if (stored.Outcome?.Kind === 'send_limit') {
|
|
504
|
+
return failure('send_limit_reached', 'The maximum number of verification emails for this conversation has been reached.', {
|
|
505
|
+
SendsRemaining: 0,
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
if (!stored.Persisted) {
|
|
509
|
+
return failure('persist_failed', 'We could not start verification. Please try again.');
|
|
510
|
+
}
|
|
511
|
+
return null;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/** Removes the pending verification (and the send it counted) if it is still the one we stored. */
|
|
515
|
+
private async rollbackPending(agentSessionId: string, caller: PrincipalCaller, tokenHash: string): Promise<void> {
|
|
516
|
+
const result = await this.ports.MutateSessionConfig<void>(agentSessionId, caller, (current) => {
|
|
517
|
+
const state = ReadIdentityVerificationState(current.ConfigRaw, ReadIdentityVerificationPolicyLayer);
|
|
518
|
+
if (state.Pending?.TokenHash !== tokenHash) {
|
|
519
|
+
return { NextConfigRaw: null, Outcome: undefined };
|
|
520
|
+
}
|
|
521
|
+
const { Pending: _dropped, ...rest } = state;
|
|
522
|
+
return { NextConfigRaw: WriteIdentityVerificationState(current.ConfigRaw, { ...rest, SendCount: Math.max(0, state.SendCount - 1) }), Outcome: undefined };
|
|
523
|
+
});
|
|
524
|
+
if (!result.Persisted && result.ErrorMessage) {
|
|
525
|
+
LogError(`RealtimeSessionVerification: could not roll back the pending verification of session ${agentSessionId}: ${result.ErrorMessage}`);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// ───────────── redemption internals ─────────────
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* Shared by the code and link paths: re-read the session, decide the redemption on its CURRENT
|
|
533
|
+
* state, persist the resulting state (and the extended deadline on success), and on success
|
|
534
|
+
* publish the event and write the audit entry.
|
|
535
|
+
*/
|
|
536
|
+
private async redeem(
|
|
537
|
+
agentSessionId: string,
|
|
538
|
+
caller: VerificationCaller,
|
|
539
|
+
credential: Parameters<typeof EvaluateRedemption>[1],
|
|
540
|
+
): Promise<VerificationOperationResult> {
|
|
541
|
+
const settings = this.ports.GetSettings();
|
|
542
|
+
const nowMs = this.ports.Now();
|
|
543
|
+
const result = await this.ports.MutateSessionConfig<RedemptionMutationOutcome>(agentSessionId, caller, (current) => {
|
|
544
|
+
const state = ReadIdentityVerificationState(current.ConfigRaw, ReadIdentityVerificationPolicyLayer);
|
|
545
|
+
const policy = ResolveIdentityVerificationPolicy([settings.PolicyDefaults, state.Policy]);
|
|
546
|
+
const decision = EvaluateRedemption(state, credential, nowMs, policy, settings.CodeHashKey);
|
|
547
|
+
const deadlineIso =
|
|
548
|
+
decision.Outcome === 'verified' && current.Status !== 'Closed'
|
|
549
|
+
? ComputeVerifiedDeadline({
|
|
550
|
+
CurrentDeadlineIso: readDeadline(current.ConfigRaw),
|
|
551
|
+
SessionStartedAtMs: current.StartedAtMs,
|
|
552
|
+
VerifiedMaxSeconds: policy.verifiedMaxSeconds,
|
|
553
|
+
})
|
|
554
|
+
: undefined;
|
|
555
|
+
const changed = decision.Next !== state;
|
|
556
|
+
return {
|
|
557
|
+
NextConfigRaw: changed ? WriteIdentityVerificationState(current.ConfigRaw, decision.Next, deadlineIso) : null,
|
|
558
|
+
Outcome: { Decision: decision, DeadlineIso: deadlineIso },
|
|
559
|
+
};
|
|
560
|
+
});
|
|
561
|
+
if (!result.Session || !result.Outcome) {
|
|
562
|
+
// The session could not be read (or does not exist). For the link that is "invalid"; for the code
|
|
563
|
+
// path the caller was already authorized, so it is a server fault.
|
|
564
|
+
return caller.Kind === 'link'
|
|
565
|
+
? failure('invalid_link', 'This link is no longer valid.')
|
|
566
|
+
: failure('persist_failed', 'We could not check that code. Please try again.');
|
|
567
|
+
}
|
|
568
|
+
const { Decision, DeadlineIso } = result.Outcome;
|
|
569
|
+
if (Decision.Outcome === 'verified') {
|
|
570
|
+
return this.completeVerification(result.Session, result.Persisted, result.ErrorMessage, Decision.Verified, DeadlineIso, caller);
|
|
571
|
+
}
|
|
572
|
+
return this.refusalResult(Decision, caller);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
/** Success path: surface persistence failure, otherwise publish + audit and build the result. */
|
|
576
|
+
private async completeVerification(
|
|
577
|
+
session: SessionSnapshot,
|
|
578
|
+
persisted: boolean,
|
|
579
|
+
persistError: string | undefined,
|
|
580
|
+
verified: VerifiedIdentity,
|
|
581
|
+
deadlineIso: string | undefined,
|
|
582
|
+
caller: VerificationCaller,
|
|
583
|
+
): Promise<VerificationOperationResult> {
|
|
584
|
+
if (!persisted) {
|
|
585
|
+
LogError(`RealtimeSessionVerification: verified session ${session.AgentSessionID} but could not persist it: ${persistError ?? 'unknown error'}`);
|
|
586
|
+
return failure('persist_failed', 'We could not record your verification. Please try again.');
|
|
587
|
+
}
|
|
588
|
+
if (session.Status !== 'Closed') {
|
|
589
|
+
this.ports.PublishVerified(
|
|
590
|
+
{ AgentSessionID: session.AgentSessionID, OwnerUserID: session.OwnerUserID, ScopeKey: session.ConversationExternalID },
|
|
591
|
+
{
|
|
592
|
+
VerifiedEmail: verified.Email,
|
|
593
|
+
VerifiedName: verified.Name,
|
|
594
|
+
VerifiedAt: verified.VerifiedAt,
|
|
595
|
+
Method: verified.Method,
|
|
596
|
+
MaxSessionDeadlineIso: deadlineIso,
|
|
597
|
+
},
|
|
598
|
+
);
|
|
599
|
+
}
|
|
600
|
+
await this.ports.WriteAudit({
|
|
601
|
+
AgentSessionID: session.AgentSessionID,
|
|
602
|
+
OwnerUserID: session.OwnerUserID,
|
|
603
|
+
Email: verified.Email,
|
|
604
|
+
Method: verified.Method,
|
|
605
|
+
ClientIp: caller.ClientIp,
|
|
606
|
+
MaxSessionDeadlineIso: deadlineIso,
|
|
607
|
+
});
|
|
608
|
+
LogStatus(`RealtimeSessionVerification: session ${session.AgentSessionID} verified by ${verified.Method}${deadlineIso ? `; deadline extended to ${deadlineIso}` : ''}.`);
|
|
609
|
+
return {
|
|
610
|
+
Success: true,
|
|
611
|
+
VerificationState: 'verified',
|
|
612
|
+
VerifiedEmail: verified.Email,
|
|
613
|
+
VerifiedAt: verified.VerifiedAt,
|
|
614
|
+
MaxSessionDeadlineIso: deadlineIso,
|
|
615
|
+
Message: "You're verified.",
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
/** Maps a rejected redemption to a result (uniform for the link path). */
|
|
620
|
+
private refusalResult(
|
|
621
|
+
decision: Extract<ReturnType<typeof EvaluateRedemption>, { Outcome: 'rejected' }>,
|
|
622
|
+
caller: VerificationCaller,
|
|
623
|
+
): VerificationOperationResult {
|
|
624
|
+
if (caller.Kind === 'link') {
|
|
625
|
+
return failure('invalid_link', 'This link is no longer valid.');
|
|
626
|
+
}
|
|
627
|
+
switch (decision.Reason) {
|
|
628
|
+
case 'already_verified':
|
|
629
|
+
return alreadyVerified(decision.Next);
|
|
630
|
+
case 'no_pending':
|
|
631
|
+
return failure('no_pending_verification', 'There is no verification waiting. Request a new email.');
|
|
632
|
+
case 'expired':
|
|
633
|
+
return failure('code_expired', 'That code has expired. Request a new email.');
|
|
634
|
+
case 'attempts_exhausted':
|
|
635
|
+
return failure('attempts_exhausted', 'Too many incorrect codes. Request a new email.', { AttemptsRemaining: 0 });
|
|
636
|
+
case 'invalid':
|
|
637
|
+
default:
|
|
638
|
+
return failure('invalid_code', 'That code is not correct.', { VerificationState: 'pending', AttemptsRemaining: decision.AttemptsRemaining });
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
// ───────────── shared internals ─────────────
|
|
643
|
+
|
|
644
|
+
/**
|
|
645
|
+
* Loads the session as the caller and applies {@link AuthorizeSessionAccess}. Not-found and
|
|
646
|
+
* not-yours are the same result so a caller cannot enumerate other people's session ids; the real
|
|
647
|
+
* reason is logged server-side.
|
|
648
|
+
*/
|
|
649
|
+
private async authorize(
|
|
650
|
+
agentSessionId: string,
|
|
651
|
+
caller: PrincipalCaller,
|
|
652
|
+
allowClosed: boolean,
|
|
653
|
+
): Promise<{ Ok: true; Session: SessionSnapshot } | { Ok: false; Result: VerificationOperationResult }> {
|
|
654
|
+
const notFound = { Ok: false as const, Result: failure('session_not_found', SESSION_NOT_FOUND) };
|
|
655
|
+
if (!IsValidUUID(agentSessionId)) {
|
|
656
|
+
return notFound;
|
|
657
|
+
}
|
|
658
|
+
const session = await this.ports.LoadSession(agentSessionId, caller);
|
|
659
|
+
if (!session) {
|
|
660
|
+
return notFound;
|
|
661
|
+
}
|
|
662
|
+
const decision = AuthorizeSessionAccess(
|
|
663
|
+
{ SessionUserID: session.OwnerUserID, ConversationExternalID: session.ConversationExternalID },
|
|
664
|
+
caller.ContextUser,
|
|
665
|
+
);
|
|
666
|
+
if (decision.Allowed === false) {
|
|
667
|
+
LogError(`RealtimeSessionVerification: refused user ${caller.ContextUser.ID} on session ${agentSessionId} (${decision.Reason}).`);
|
|
668
|
+
return notFound;
|
|
669
|
+
}
|
|
670
|
+
if (!allowClosed && session.Status === 'Closed') {
|
|
671
|
+
return { Ok: false, Result: failure('session_closed', 'This conversation has ended.') };
|
|
672
|
+
}
|
|
673
|
+
return { Ok: true, Session: session };
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
/** Serialises operations on one session within this process (see the module header). */
|
|
677
|
+
private async withSessionLock<T>(agentSessionId: string, work: () => Promise<T>): Promise<T> {
|
|
678
|
+
const key = agentSessionId.toLowerCase();
|
|
679
|
+
const previous = this.locks.get(key) ?? Promise.resolve();
|
|
680
|
+
let release: () => void = () => undefined;
|
|
681
|
+
const gate = new Promise<void>((resolve) => {
|
|
682
|
+
release = resolve;
|
|
683
|
+
});
|
|
684
|
+
const tail = previous.then(() => gate);
|
|
685
|
+
this.locks.set(key, tail);
|
|
686
|
+
try {
|
|
687
|
+
await previous;
|
|
688
|
+
return await work();
|
|
689
|
+
} finally {
|
|
690
|
+
release();
|
|
691
|
+
if (this.locks.get(key) === tail) {
|
|
692
|
+
this.locks.delete(key);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
/** The stored `maxSessionDeadlineIso`, when it is a string. */
|
|
699
|
+
function readDeadline(configRaw: string | null): string | undefined {
|
|
700
|
+
const value = ParseConfigObject(configRaw)['maxSessionDeadlineIso'];
|
|
701
|
+
return typeof value === 'string' ? value : undefined;
|
|
702
|
+
}
|