@memberjunction/server 6.2.0-edge.3 → 6.2.0-edge.4

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.
Files changed (346) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/agentSessions/BridgeHostInstance.d.ts +25 -0
  3. package/dist/agentSessions/BridgeHostInstance.d.ts.map +1 -0
  4. package/dist/agentSessions/BridgeHostInstance.js +30 -0
  5. package/dist/agentSessions/BridgeHostInstance.js.map +1 -0
  6. package/dist/agentSessions/HostInstance.d.ts +75 -11
  7. package/dist/agentSessions/HostInstance.d.ts.map +1 -1
  8. package/dist/agentSessions/HostInstance.js +99 -28
  9. package/dist/agentSessions/HostInstance.js.map +1 -1
  10. package/dist/agentSessions/ReturningVisitorRecap.d.ts.map +1 -1
  11. package/dist/agentSessions/ReturningVisitorRecap.js +7 -3
  12. package/dist/agentSessions/ReturningVisitorRecap.js.map +1 -1
  13. package/dist/agentSessions/SessionJanitor.d.ts +173 -25
  14. package/dist/agentSessions/SessionJanitor.d.ts.map +1 -1
  15. package/dist/agentSessions/SessionJanitor.js +452 -54
  16. package/dist/agentSessions/SessionJanitor.js.map +1 -1
  17. package/dist/agentSessions/SessionManager.d.ts +43 -2
  18. package/dist/agentSessions/SessionManager.d.ts.map +1 -1
  19. package/dist/agentSessions/SessionManager.js +99 -8
  20. package/dist/agentSessions/SessionManager.js.map +1 -1
  21. package/dist/agentSessions/SessionRunIDVerifier.d.ts +63 -0
  22. package/dist/agentSessions/SessionRunIDVerifier.d.ts.map +1 -0
  23. package/dist/agentSessions/SessionRunIDVerifier.js +129 -0
  24. package/dist/agentSessions/SessionRunIDVerifier.js.map +1 -0
  25. package/dist/agentSessions/index.d.ts +2 -0
  26. package/dist/agentSessions/index.d.ts.map +1 -1
  27. package/dist/agentSessions/index.js +10 -3
  28. package/dist/agentSessions/index.js.map +1 -1
  29. package/dist/auth/actionAuthorization.d.ts +40 -0
  30. package/dist/auth/actionAuthorization.d.ts.map +1 -0
  31. package/dist/auth/actionAuthorization.js +94 -0
  32. package/dist/auth/actionAuthorization.js.map +1 -0
  33. package/dist/auth/index.d.ts.map +1 -1
  34. package/dist/auth/index.js +3 -10
  35. package/dist/auth/index.js.map +1 -1
  36. package/dist/auth/magicLink/MagicLinkService.d.ts.map +1 -1
  37. package/dist/auth/magicLink/MagicLinkService.js +6 -3
  38. package/dist/auth/magicLink/MagicLinkService.js.map +1 -1
  39. package/dist/auth/newUsers.d.ts +10 -0
  40. package/dist/auth/newUsers.d.ts.map +1 -1
  41. package/dist/auth/newUsers.js +80 -11
  42. package/dist/auth/newUsers.js.map +1 -1
  43. package/dist/config.d.ts +2378 -0
  44. package/dist/config.d.ts.map +1 -1
  45. package/dist/config.js +123 -0
  46. package/dist/config.js.map +1 -1
  47. package/dist/context.d.ts +8 -0
  48. package/dist/context.d.ts.map +1 -1
  49. package/dist/context.js +14 -11
  50. package/dist/context.js.map +1 -1
  51. package/dist/generated/graphql-schemas/__mj.d.ts +135 -0
  52. package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
  53. package/dist/generated/graphql-schemas/__mj.js +706 -0
  54. package/dist/generated/graphql-schemas/__mj.js.map +1 -1
  55. package/dist/generic/OrphanedConversationDetailReconciler.js +3 -3
  56. package/dist/generic/OrphanedConversationDetailReconciler.js.map +1 -1
  57. package/dist/generic/ResolverBase.d.ts +20 -4
  58. package/dist/generic/ResolverBase.d.ts.map +1 -1
  59. package/dist/generic/ResolverBase.js +39 -10
  60. package/dist/generic/ResolverBase.js.map +1 -1
  61. package/dist/generic/RunViewResolver.d.ts.map +1 -1
  62. package/dist/generic/RunViewResolver.js +4 -0
  63. package/dist/generic/RunViewResolver.js.map +1 -1
  64. package/dist/index.d.ts +6 -0
  65. package/dist/index.d.ts.map +1 -1
  66. package/dist/index.js +120 -131
  67. package/dist/index.js.map +1 -1
  68. package/dist/isolatedProvider.d.ts +20 -0
  69. package/dist/isolatedProvider.d.ts.map +1 -0
  70. package/dist/isolatedProvider.js +22 -0
  71. package/dist/isolatedProvider.js.map +1 -0
  72. package/dist/realtimeProxy/RealtimeProxyServer.d.ts +44 -4
  73. package/dist/realtimeProxy/RealtimeProxyServer.d.ts.map +1 -1
  74. package/dist/realtimeProxy/RealtimeProxyServer.js +121 -16
  75. package/dist/realtimeProxy/RealtimeProxyServer.js.map +1 -1
  76. package/dist/realtimeProxy/RealtimeRelayTunnel.d.ts +171 -0
  77. package/dist/realtimeProxy/RealtimeRelayTunnel.d.ts.map +1 -0
  78. package/dist/realtimeProxy/RealtimeRelayTunnel.js +532 -0
  79. package/dist/realtimeProxy/RealtimeRelayTunnel.js.map +1 -0
  80. package/dist/realtimeSessions/RealtimeSessionEventService.d.ts +148 -0
  81. package/dist/realtimeSessions/RealtimeSessionEventService.d.ts.map +1 -0
  82. package/dist/realtimeSessions/RealtimeSessionEventService.js +191 -0
  83. package/dist/realtimeSessions/RealtimeSessionEventService.js.map +1 -0
  84. package/dist/realtimeSessions/RealtimeSessionVerificationService.d.ts +66 -0
  85. package/dist/realtimeSessions/RealtimeSessionVerificationService.d.ts.map +1 -0
  86. package/dist/realtimeSessions/RealtimeSessionVerificationService.js +138 -0
  87. package/dist/realtimeSessions/RealtimeSessionVerificationService.js.map +1 -0
  88. package/dist/realtimeSessions/RealtimeVerifyRouter.d.ts +38 -0
  89. package/dist/realtimeSessions/RealtimeVerifyRouter.d.ts.map +1 -0
  90. package/dist/realtimeSessions/RealtimeVerifyRouter.js +98 -0
  91. package/dist/realtimeSessions/RealtimeVerifyRouter.js.map +1 -0
  92. package/dist/realtimeSessions/SessionVerificationPortsBase.d.ts +61 -0
  93. package/dist/realtimeSessions/SessionVerificationPortsBase.d.ts.map +1 -0
  94. package/dist/realtimeSessions/SessionVerificationPortsBase.js +149 -0
  95. package/dist/realtimeSessions/SessionVerificationPortsBase.js.map +1 -0
  96. package/dist/realtimeSessions/SlidingWindowRateLimiter.d.ts +57 -0
  97. package/dist/realtimeSessions/SlidingWindowRateLimiter.d.ts.map +1 -0
  98. package/dist/realtimeSessions/SlidingWindowRateLimiter.js +69 -0
  99. package/dist/realtimeSessions/SlidingWindowRateLimiter.js.map +1 -0
  100. package/dist/realtimeSessions/index.d.ts +17 -0
  101. package/dist/realtimeSessions/index.d.ts.map +1 -0
  102. package/dist/realtimeSessions/index.js +17 -0
  103. package/dist/realtimeSessions/index.js.map +1 -0
  104. package/dist/realtimeSessions/sessionAccess.d.ts +86 -0
  105. package/dist/realtimeSessions/sessionAccess.d.ts.map +1 -0
  106. package/dist/realtimeSessions/sessionAccess.js +90 -0
  107. package/dist/realtimeSessions/sessionAccess.js.map +1 -0
  108. package/dist/realtimeSessions/sessionConfigStamps.d.ts +70 -0
  109. package/dist/realtimeSessions/sessionConfigStamps.d.ts.map +1 -0
  110. package/dist/realtimeSessions/sessionConfigStamps.js +93 -0
  111. package/dist/realtimeSessions/sessionConfigStamps.js.map +1 -0
  112. package/dist/realtimeSessions/subscriptionAuthorization.d.ts +65 -0
  113. package/dist/realtimeSessions/subscriptionAuthorization.d.ts.map +1 -0
  114. package/dist/realtimeSessions/subscriptionAuthorization.js +71 -0
  115. package/dist/realtimeSessions/subscriptionAuthorization.js.map +1 -0
  116. package/dist/realtimeSessions/verificationCore.d.ts +243 -0
  117. package/dist/realtimeSessions/verificationCore.d.ts.map +1 -0
  118. package/dist/realtimeSessions/verificationCore.js +356 -0
  119. package/dist/realtimeSessions/verificationCore.js.map +1 -0
  120. package/dist/realtimeSessions/verificationPages.d.ts +66 -0
  121. package/dist/realtimeSessions/verificationPages.d.ts.map +1 -0
  122. package/dist/realtimeSessions/verificationPages.js +110 -0
  123. package/dist/realtimeSessions/verificationPages.js.map +1 -0
  124. package/dist/realtimeSessions/verificationPolicy.d.ts +147 -0
  125. package/dist/realtimeSessions/verificationPolicy.d.ts.map +1 -0
  126. package/dist/realtimeSessions/verificationPolicy.js +249 -0
  127. package/dist/realtimeSessions/verificationPolicy.js.map +1 -0
  128. package/dist/realtimeSessions/verificationWorkflow.d.ts +263 -0
  129. package/dist/realtimeSessions/verificationWorkflow.d.ts.map +1 -0
  130. package/dist/realtimeSessions/verificationWorkflow.js +435 -0
  131. package/dist/realtimeSessions/verificationWorkflow.js.map +1 -0
  132. package/dist/realtimeWidget/widgetGuestElevation.d.ts +20 -2
  133. package/dist/realtimeWidget/widgetGuestElevation.d.ts.map +1 -1
  134. package/dist/realtimeWidget/widgetGuestElevation.js +31 -2
  135. package/dist/realtimeWidget/widgetGuestElevation.js.map +1 -1
  136. package/dist/resolvers/ActionResolver.d.ts +31 -5
  137. package/dist/resolvers/ActionResolver.d.ts.map +1 -1
  138. package/dist/resolvers/ActionResolver.js +75 -14
  139. package/dist/resolvers/ActionResolver.js.map +1 -1
  140. package/dist/resolvers/AdhocQueryResolver.d.ts +14 -1
  141. package/dist/resolvers/AdhocQueryResolver.d.ts.map +1 -1
  142. package/dist/resolvers/AdhocQueryResolver.js +21 -5
  143. package/dist/resolvers/AdhocQueryResolver.js.map +1 -1
  144. package/dist/resolvers/CacheStatsResolver.d.ts +27 -0
  145. package/dist/resolvers/CacheStatsResolver.d.ts.map +1 -1
  146. package/dist/resolvers/CacheStatsResolver.js +113 -2
  147. package/dist/resolvers/CacheStatsResolver.js.map +1 -1
  148. package/dist/resolvers/ClientToolRequestResolver.d.ts +4 -2
  149. package/dist/resolvers/ClientToolRequestResolver.d.ts.map +1 -1
  150. package/dist/resolvers/ClientToolRequestResolver.js +28 -10
  151. package/dist/resolvers/ClientToolRequestResolver.js.map +1 -1
  152. package/dist/resolvers/FetchEntityVectorsResolver.d.ts +12 -1
  153. package/dist/resolvers/FetchEntityVectorsResolver.d.ts.map +1 -1
  154. package/dist/resolvers/FetchEntityVectorsResolver.js +83 -2
  155. package/dist/resolvers/FetchEntityVectorsResolver.js.map +1 -1
  156. package/dist/resolvers/HumanHandoffResolver.d.ts.map +1 -1
  157. package/dist/resolvers/HumanHandoffResolver.js +7 -1
  158. package/dist/resolvers/HumanHandoffResolver.js.map +1 -1
  159. package/dist/resolvers/MCPResolver.d.ts.map +1 -1
  160. package/dist/resolvers/MCPResolver.js +9 -4
  161. package/dist/resolvers/MCPResolver.js.map +1 -1
  162. package/dist/resolvers/RealtimeBridgeResolver.d.ts +54 -2
  163. package/dist/resolvers/RealtimeBridgeResolver.d.ts.map +1 -1
  164. package/dist/resolvers/RealtimeBridgeResolver.js +155 -10
  165. package/dist/resolvers/RealtimeBridgeResolver.js.map +1 -1
  166. package/dist/resolvers/RealtimeClientSessionResolver.d.ts +155 -25
  167. package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
  168. package/dist/resolvers/RealtimeClientSessionResolver.js +394 -90
  169. package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
  170. package/dist/resolvers/RealtimeSessionEventsResolver.d.ts +61 -0
  171. package/dist/resolvers/RealtimeSessionEventsResolver.d.ts.map +1 -0
  172. package/dist/resolvers/RealtimeSessionEventsResolver.js +130 -0
  173. package/dist/resolvers/RealtimeSessionEventsResolver.js.map +1 -0
  174. package/dist/resolvers/RealtimeSessionVerificationResolver.d.ts +85 -0
  175. package/dist/resolvers/RealtimeSessionVerificationResolver.d.ts.map +1 -0
  176. package/dist/resolvers/RealtimeSessionVerificationResolver.js +188 -0
  177. package/dist/resolvers/RealtimeSessionVerificationResolver.js.map +1 -0
  178. package/dist/resolvers/RemoteBrowserActionResolver.d.ts +2 -0
  179. package/dist/resolvers/RemoteBrowserActionResolver.d.ts.map +1 -1
  180. package/dist/resolvers/RemoteBrowserActionResolver.js +5 -1
  181. package/dist/resolvers/RemoteBrowserActionResolver.js.map +1 -1
  182. package/dist/resolvers/RunAIAgentResolver.d.ts +10 -0
  183. package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
  184. package/dist/resolvers/RunAIAgentResolver.js +68 -11
  185. package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
  186. package/dist/resolvers/RunTemplateResolver.d.ts.map +1 -1
  187. package/dist/resolvers/RunTemplateResolver.js +12 -11
  188. package/dist/resolvers/RunTemplateResolver.js.map +1 -1
  189. package/dist/resolvers/SyncDataResolver.d.ts.map +1 -1
  190. package/dist/resolvers/SyncDataResolver.js +6 -2
  191. package/dist/resolvers/SyncDataResolver.js.map +1 -1
  192. package/dist/resolvers/SyncRolesUsersResolver.d.ts +2 -0
  193. package/dist/resolvers/SyncRolesUsersResolver.d.ts.map +1 -1
  194. package/dist/resolvers/SyncRolesUsersResolver.js +23 -6
  195. package/dist/resolvers/SyncRolesUsersResolver.js.map +1 -1
  196. package/dist/resolvers/UserAvatarResolver.d.ts +16 -0
  197. package/dist/resolvers/UserAvatarResolver.d.ts.map +1 -0
  198. package/dist/resolvers/UserAvatarResolver.js +124 -0
  199. package/dist/resolvers/UserAvatarResolver.js.map +1 -0
  200. package/dist/resolvers/UserResolver.d.ts.map +1 -1
  201. package/dist/resolvers/UserResolver.js +2 -17
  202. package/dist/resolvers/UserResolver.js.map +1 -1
  203. package/dist/resolvers/agentVisionConsentAudit.d.ts +37 -0
  204. package/dist/resolvers/agentVisionConsentAudit.d.ts.map +1 -0
  205. package/dist/resolvers/agentVisionConsentAudit.js +63 -0
  206. package/dist/resolvers/agentVisionConsentAudit.js.map +1 -0
  207. package/dist/resolvers/avatarInputValidation.d.ts +40 -0
  208. package/dist/resolvers/avatarInputValidation.d.ts.map +1 -0
  209. package/dist/resolvers/avatarInputValidation.js +97 -0
  210. package/dist/resolvers/avatarInputValidation.js.map +1 -0
  211. package/dist/resolvers/currentUserRoles.d.ts +29 -0
  212. package/dist/resolvers/currentUserRoles.d.ts.map +1 -1
  213. package/dist/resolvers/currentUserRoles.js +30 -0
  214. package/dist/resolvers/currentUserRoles.js.map +1 -1
  215. package/dist/resolvers/meetings/meetingMutations.d.ts +6 -0
  216. package/dist/resolvers/meetings/meetingMutations.d.ts.map +1 -1
  217. package/dist/resolvers/meetings/meetingMutations.js +21 -0
  218. package/dist/resolvers/meetings/meetingMutations.js.map +1 -1
  219. package/dist/services/TaskGraphContinuationDeliverer.d.ts +4 -3
  220. package/dist/services/TaskGraphContinuationDeliverer.d.ts.map +1 -1
  221. package/dist/services/TaskGraphContinuationDeliverer.js +13 -7
  222. package/dist/services/TaskGraphContinuationDeliverer.js.map +1 -1
  223. package/dist/sharedCache.d.ts +105 -0
  224. package/dist/sharedCache.d.ts.map +1 -0
  225. package/dist/sharedCache.js +276 -0
  226. package/dist/sharedCache.js.map +1 -0
  227. package/dist/types.d.ts +6 -0
  228. package/dist/types.d.ts.map +1 -1
  229. package/dist/types.js.map +1 -1
  230. package/package.json +97 -95
  231. package/src/__tests__/ActionResolver.authorization.test.ts +353 -0
  232. package/src/__tests__/AdhocQueryResolver.sqlAuthorizer.test.ts +104 -0
  233. package/src/__tests__/BridgeHostInstance.test.ts +232 -0
  234. package/src/__tests__/ClientToolRequestResolver.test.ts +116 -0
  235. package/src/__tests__/FetchEntityVectorsResolver.test.ts +285 -0
  236. package/src/__tests__/HostInstance.test.ts +84 -0
  237. package/src/__tests__/HumanHandoffResolver.test.ts +17 -0
  238. package/src/__tests__/MeetingResolver.test.ts +75 -1
  239. package/src/__tests__/RealtimeBridgeResolver.test.ts +415 -6
  240. package/src/__tests__/RealtimeClientSessionResolver.configTamper.test.ts +383 -0
  241. package/src/__tests__/RealtimeClientSessionResolver.test.ts +682 -58
  242. package/src/__tests__/RealtimeProxyServer.test.ts +16 -1
  243. package/src/__tests__/RealtimeRelay.test.ts +876 -0
  244. package/src/__tests__/RemoteBrowserActionResolver.goalRunLink.test.ts +129 -0
  245. package/src/__tests__/ResolverBase.aggregateScreen.test.ts +174 -0
  246. package/src/__tests__/ResolverBase.clientOldValues.test.ts +6 -3
  247. package/src/__tests__/ReturningVisitorRecap.test.ts +115 -0
  248. package/src/__tests__/RunAIAgentResolver.branch.test.ts +169 -0
  249. package/src/__tests__/RunAIAgentResolver.historyFrom.test.ts +4 -4
  250. package/src/__tests__/RunAIAgentResolver.stoppedPredecessor.test.ts +166 -0
  251. package/src/__tests__/RunTemplateResolver.test.ts +132 -0
  252. package/src/__tests__/RunViewResolver.cacheCheckForwarding.test.ts +63 -0
  253. package/src/__tests__/SessionJanitor.test.ts +845 -18
  254. package/src/__tests__/SessionManager.test.ts +213 -17
  255. package/src/__tests__/SessionRunIDVerifier.test.ts +251 -0
  256. package/src/__tests__/SyncRolesUsersResolver.userRoles.test.ts +97 -0
  257. package/src/__tests__/TaskGraphContinuationDeliverer.test.ts +50 -3
  258. package/src/__tests__/UserAvatarResolver.test.ts +255 -0
  259. package/src/__tests__/UserResolver.currentUser.test.ts +16 -1
  260. package/src/__tests__/actionAuthorization.test.ts +130 -0
  261. package/src/__tests__/agentVisionConsentAudit.test.ts +118 -0
  262. package/src/__tests__/avatarInputValidation.test.ts +160 -0
  263. package/src/__tests__/isolatedProviderTransactions.test.ts +169 -0
  264. package/src/__tests__/magicLinkService.consume.test.ts +6 -0
  265. package/src/__tests__/magicLinkService.provisionLog.test.ts +5 -0
  266. package/src/__tests__/newUsers.template.test.ts +151 -0
  267. package/src/__tests__/newUsers.test.ts +74 -5
  268. package/src/__tests__/orphanedConversationDetailReconciler.test.ts +17 -2
  269. package/src/__tests__/realtimeSessions/RealtimeSessionEventService.test.ts +201 -0
  270. package/src/__tests__/realtimeSessions/RealtimeSessionEventsResolver.test.ts +215 -0
  271. package/src/__tests__/realtimeSessions/RealtimeSessionVerificationResolver.test.ts +102 -0
  272. package/src/__tests__/realtimeSessions/RealtimeSessionVerificationService.test.ts +349 -0
  273. package/src/__tests__/realtimeSessions/RealtimeVerifyRouter.test.ts +131 -0
  274. package/src/__tests__/realtimeSessions/SlidingWindowRateLimiter.test.ts +56 -0
  275. package/src/__tests__/realtimeSessions/helpers.ts +4 -0
  276. package/src/__tests__/realtimeSessions/sessionAccess.test.ts +129 -0
  277. package/src/__tests__/realtimeSessions/sessionConfigStamps.test.ts +124 -0
  278. package/src/__tests__/realtimeSessions/verificationCore.test.ts +355 -0
  279. package/src/__tests__/realtimeSessions/verificationPages.test.ts +84 -0
  280. package/src/__tests__/realtimeSessions/verificationPolicy.test.ts +226 -0
  281. package/src/__tests__/realtimeSessions/verificationWorkflow.test.ts +633 -0
  282. package/src/__tests__/resolverBase.fls.test.ts +87 -4
  283. package/src/__tests__/serverExtensionReservedRoots.test.ts +2 -0
  284. package/src/__tests__/sharedCache.test.ts +310 -0
  285. package/src/__tests__/widgetGuestElevation.test.ts +44 -0
  286. package/src/agentSessions/BridgeHostInstance.ts +32 -0
  287. package/src/agentSessions/HostInstance.ts +124 -28
  288. package/src/agentSessions/ReturningVisitorRecap.ts +7 -3
  289. package/src/agentSessions/SessionJanitor.ts +543 -57
  290. package/src/agentSessions/SessionManager.ts +125 -25
  291. package/src/agentSessions/SessionRunIDVerifier.ts +171 -0
  292. package/src/agentSessions/index.ts +10 -3
  293. package/src/auth/actionAuthorization.ts +116 -0
  294. package/src/auth/index.ts +3 -8
  295. package/src/auth/magicLink/MagicLinkService.ts +6 -3
  296. package/src/auth/newUsers.ts +99 -11
  297. package/src/config.ts +125 -0
  298. package/src/context.ts +15 -10
  299. package/src/generated/graphql-schemas/__mj.ts +496 -1
  300. package/src/generic/OrphanedConversationDetailReconciler.ts +3 -3
  301. package/src/generic/ResolverBase.ts +39 -9
  302. package/src/generic/RunViewResolver.ts +4 -0
  303. package/src/index.ts +132 -143
  304. package/src/isolatedProvider.ts +22 -0
  305. package/src/realtimeProxy/RealtimeProxyServer.ts +148 -16
  306. package/src/realtimeProxy/RealtimeRelayTunnel.ts +645 -0
  307. package/src/realtimeSessions/README.md +179 -0
  308. package/src/realtimeSessions/RealtimeSessionEventService.ts +274 -0
  309. package/src/realtimeSessions/RealtimeSessionVerificationService.ts +167 -0
  310. package/src/realtimeSessions/RealtimeVerifyRouter.ts +108 -0
  311. package/src/realtimeSessions/SessionVerificationPortsBase.ts +189 -0
  312. package/src/realtimeSessions/SlidingWindowRateLimiter.ts +92 -0
  313. package/src/realtimeSessions/index.ts +16 -0
  314. package/src/realtimeSessions/sessionAccess.ts +141 -0
  315. package/src/realtimeSessions/sessionConfigStamps.ts +123 -0
  316. package/src/realtimeSessions/subscriptionAuthorization.ts +116 -0
  317. package/src/realtimeSessions/verificationCore.ts +502 -0
  318. package/src/realtimeSessions/verificationPages.ts +149 -0
  319. package/src/realtimeSessions/verificationPolicy.ts +321 -0
  320. package/src/realtimeSessions/verificationWorkflow.ts +702 -0
  321. package/src/realtimeWidget/widgetGuestElevation.ts +34 -2
  322. package/src/resolvers/ActionResolver.ts +84 -21
  323. package/src/resolvers/AdhocQueryResolver.ts +24 -6
  324. package/src/resolvers/CacheStatsResolver.ts +89 -2
  325. package/src/resolvers/ClientToolRequestResolver.ts +25 -7
  326. package/src/resolvers/FetchEntityVectorsResolver.ts +97 -2
  327. package/src/resolvers/HumanHandoffResolver.ts +8 -0
  328. package/src/resolvers/MCPResolver.ts +8 -5
  329. package/src/resolvers/RealtimeBridgeResolver.ts +163 -11
  330. package/src/resolvers/RealtimeClientSessionResolver.ts +501 -96
  331. package/src/resolvers/RealtimeSessionEventsResolver.ts +133 -0
  332. package/src/resolvers/RealtimeSessionVerificationResolver.ts +174 -0
  333. package/src/resolvers/RemoteBrowserActionResolver.ts +11 -1
  334. package/src/resolvers/RunAIAgentResolver.ts +78 -11
  335. package/src/resolvers/RunTemplateResolver.ts +13 -12
  336. package/src/resolvers/SyncDataResolver.ts +6 -2
  337. package/src/resolvers/SyncRolesUsersResolver.ts +24 -6
  338. package/src/resolvers/UserAvatarResolver.ts +111 -0
  339. package/src/resolvers/UserResolver.ts +2 -16
  340. package/src/resolvers/agentVisionConsentAudit.ts +82 -0
  341. package/src/resolvers/avatarInputValidation.ts +123 -0
  342. package/src/resolvers/currentUserRoles.ts +32 -1
  343. package/src/resolvers/meetings/meetingMutations.ts +21 -0
  344. package/src/services/TaskGraphContinuationDeliverer.ts +13 -7
  345. package/src/sharedCache.ts +302 -0
  346. 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,167 @@
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
+
34
+ /** Purpose label for principal-resolution diagnostics. */
35
+ const PRINCIPAL_PURPOSE = 'RealtimeVerification';
36
+
37
+ /** The base URL emailed links are built from: explicit setting, else the server's public URL (same derivation magic links use). */
38
+ function resolvePublicBaseUrl(explicit: string | undefined): string | undefined {
39
+ const candidate =
40
+ explicit?.trim() || configInfo.publicUrl || `${configInfo.baseUrl}:${configInfo.graphqlPort}${configInfo.graphqlRootPath || ''}`;
41
+ return candidate ? TrimTrailingSlashes(candidate) : undefined;
42
+ }
43
+
44
+ /** The shortest `hmacSecret` accepted. */
45
+ const MIN_HMAC_SECRET_LENGTH = 16;
46
+
47
+ /** True when the configured HMAC secret is long enough to protect the stored code hash. */
48
+ function hasUsableHmacSecret(secret: string | undefined): boolean {
49
+ return !!secret && secret.length >= MIN_HMAC_SECRET_LENGTH;
50
+ }
51
+
52
+ /** Reads the live settings from server config. */
53
+ function readSettings(): VerificationSettings {
54
+ const cfg = configInfo.realtime?.identityVerification;
55
+ const limits = cfg?.rateLimits;
56
+ return {
57
+ // A feature that cannot send mail, or cannot protect the codes it issues, is not available — whatever `enabled` says.
58
+ Enabled: !!cfg?.enabled && !!cfg.communicationProvider && hasUsableHmacSecret(cfg.hmacSecret),
59
+ CodeHashKey: cfg?.hmacSecret ?? '',
60
+ PolicyDefaults: ReadIdentityVerificationPolicyLayer(cfg?.policy),
61
+ PublicBaseUrl: resolvePublicBaseUrl(cfg?.publicBaseUrl),
62
+ RateLimits: {
63
+ SendWindowMs: limits?.sendWindowMs ?? 3_600_000,
64
+ PerIpSends: limits?.perIpSends ?? 10,
65
+ PerEmailDomainSends: limits?.perEmailDomainSends ?? 30,
66
+ PerEmailSends: limits?.perEmailSends ?? 3,
67
+ CodeAttemptWindowMs: limits?.codeAttemptWindowMs ?? 600_000,
68
+ PerSessionCodeAttempts: limits?.perSessionCodeAttempts ?? 10,
69
+ },
70
+ };
71
+ }
72
+
73
+ /**
74
+ * The production ports: {@link SessionVerificationPortsBase} plus the server configuration — the settings
75
+ * read from `realtime.identityVerification`, the configured service principal, and delivery through the
76
+ * configured communication provider. Exported for tests and for hosts that want to compose their own
77
+ * workflow around them; application code should use {@link RealtimeSessionVerificationService}.
78
+ */
79
+ export class RuntimeVerificationPorts extends SessionVerificationPortsBase {
80
+ public GetSettings(): VerificationSettings {
81
+ return readSettings();
82
+ }
83
+
84
+ public async SendEmail(email: OutboundEmail): Promise<{ Success: boolean; ErrorMessage?: string }> {
85
+ const cfg = configInfo.realtime?.identityVerification;
86
+ const user = this.ResolveServiceUser();
87
+ if (!cfg?.communicationProvider || !user) {
88
+ return { Success: false, ErrorMessage: 'No communication provider or service principal is configured.' };
89
+ }
90
+ try {
91
+ const engine = CommunicationEngine.Instance;
92
+ await engine.Config(false, user);
93
+ const message = new Message();
94
+ // Rehearsal mode: the engine builds and LOGS the message but never delivers it (see the config docs).
95
+ message.DryRun = cfg.deliveryMode === 'dry-run';
96
+ message.From = cfg.fromAddress ?? '';
97
+ message.To = email.To;
98
+ message.Subject = email.Subject;
99
+ message.Body = email.Text;
100
+ message.HTMLBody = email.Html;
101
+ const result = await engine.SendSingleMessage(cfg.communicationProvider, 'Email', message);
102
+ return result?.Success ? { Success: true } : { Success: false, ErrorMessage: result?.Error ?? 'unknown provider error' };
103
+ } catch (error) {
104
+ return { Success: false, ErrorMessage: error instanceof Error ? error.message : String(error) };
105
+ }
106
+ }
107
+
108
+
109
+ /** The configured service principal (see {@link SessionVerificationPortsBase}). */
110
+ protected ResolveServiceUser(): UserInfo | null {
111
+ const configured = configInfo.realtime?.identityVerification?.contextUserForVerification;
112
+ return ResolveConfiguredPrincipal(configured, PRINCIPAL_PURPOSE) ?? UserCache.Instance.GetSystemUser() ?? null;
113
+ }
114
+ }
115
+
116
+ /**
117
+ * The process-wide verification service. Call it from the `RealtimeSessionVerificationResolver`
118
+ * (authenticated principal) and the public `/realtime/verify` router (link capability).
119
+ */
120
+ export class RealtimeSessionVerificationService extends BaseSingleton<RealtimeSessionVerificationService> {
121
+ private workflow: RealtimeSessionVerificationWorkflow | undefined;
122
+
123
+ protected constructor() {
124
+ super();
125
+ }
126
+
127
+ /** The process-wide instance. */
128
+ public static get Instance(): RealtimeSessionVerificationService {
129
+ return super.getInstance<RealtimeSessionVerificationService>();
130
+ }
131
+
132
+ /** Whether verification is switched on and able to send mail. */
133
+ public get IsEnabled(): boolean {
134
+ return readSettings().Enabled;
135
+ }
136
+
137
+ /** See {@link RealtimeSessionVerificationWorkflow.RequestVerification}. */
138
+ public RequestVerification(
139
+ input: { AgentSessionID: string; Name: string; Email: string },
140
+ caller: PrincipalCaller,
141
+ ): Promise<VerificationOperationResult> {
142
+ return this.getWorkflow().RequestVerification(input, caller);
143
+ }
144
+
145
+ /** See {@link RealtimeSessionVerificationWorkflow.SubmitCode}. */
146
+ public SubmitCode(input: { AgentSessionID: string; Code: string }, caller: PrincipalCaller): Promise<VerificationOperationResult> {
147
+ return this.getWorkflow().SubmitCode(input, caller);
148
+ }
149
+
150
+ /** See {@link RealtimeSessionVerificationWorkflow.RedeemLink}. */
151
+ public RedeemLink(token: string, clientIp?: string): Promise<VerificationOperationResult> {
152
+ return this.getWorkflow().RedeemLink(token, { Kind: 'link', ClientIp: clientIp });
153
+ }
154
+
155
+ /** See {@link RealtimeSessionVerificationWorkflow.GetStatus}. */
156
+ public GetStatus(input: { AgentSessionID: string }, caller: PrincipalCaller): Promise<VerificationOperationResult> {
157
+ return this.getWorkflow().GetStatus(input, caller);
158
+ }
159
+
160
+ /** The workflow is created on first use so its limiters pick up the loaded config. */
161
+ private getWorkflow(): RealtimeSessionVerificationWorkflow {
162
+ if (!this.workflow) {
163
+ this.workflow = new RealtimeSessionVerificationWorkflow(new RuntimeVerificationPorts());
164
+ }
165
+ return this.workflow;
166
+ }
167
+ }