@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.
Files changed (543) hide show
  1. package/README.md +20 -0
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/agentSessions/BridgeHostInstance.d.ts +25 -0
  4. package/dist/agentSessions/BridgeHostInstance.d.ts.map +1 -0
  5. package/dist/agentSessions/BridgeHostInstance.js +30 -0
  6. package/dist/agentSessions/BridgeHostInstance.js.map +1 -0
  7. package/dist/agentSessions/HostInstance.d.ts +75 -11
  8. package/dist/agentSessions/HostInstance.d.ts.map +1 -1
  9. package/dist/agentSessions/HostInstance.js +99 -28
  10. package/dist/agentSessions/HostInstance.js.map +1 -1
  11. package/dist/agentSessions/ReturningVisitorRecap.d.ts.map +1 -1
  12. package/dist/agentSessions/ReturningVisitorRecap.js +7 -3
  13. package/dist/agentSessions/ReturningVisitorRecap.js.map +1 -1
  14. package/dist/agentSessions/SessionJanitor.d.ts +236 -27
  15. package/dist/agentSessions/SessionJanitor.d.ts.map +1 -1
  16. package/dist/agentSessions/SessionJanitor.js +682 -54
  17. package/dist/agentSessions/SessionJanitor.js.map +1 -1
  18. package/dist/agentSessions/SessionManager.d.ts +48 -3
  19. package/dist/agentSessions/SessionManager.d.ts.map +1 -1
  20. package/dist/agentSessions/SessionManager.js +112 -9
  21. package/dist/agentSessions/SessionManager.js.map +1 -1
  22. package/dist/agentSessions/SessionRunIDVerifier.d.ts +63 -0
  23. package/dist/agentSessions/SessionRunIDVerifier.d.ts.map +1 -0
  24. package/dist/agentSessions/SessionRunIDVerifier.js +129 -0
  25. package/dist/agentSessions/SessionRunIDVerifier.js.map +1 -0
  26. package/dist/agentSessions/index.d.ts +2 -0
  27. package/dist/agentSessions/index.d.ts.map +1 -1
  28. package/dist/agentSessions/index.js +10 -3
  29. package/dist/agentSessions/index.js.map +1 -1
  30. package/dist/apiRoot.d.ts +32 -0
  31. package/dist/apiRoot.d.ts.map +1 -0
  32. package/dist/apiRoot.js +67 -0
  33. package/dist/apiRoot.js.map +1 -0
  34. package/dist/auth/AuthProviderCatalogRouter.d.ts.map +1 -1
  35. package/dist/auth/AuthProviderCatalogRouter.js +25 -0
  36. package/dist/auth/AuthProviderCatalogRouter.js.map +1 -1
  37. package/dist/auth/LoginScreenEngine.d.ts +22 -0
  38. package/dist/auth/LoginScreenEngine.d.ts.map +1 -0
  39. package/dist/auth/LoginScreenEngine.js +57 -0
  40. package/dist/auth/LoginScreenEngine.js.map +1 -0
  41. package/dist/auth/actionAuthorization.d.ts +40 -0
  42. package/dist/auth/actionAuthorization.d.ts.map +1 -0
  43. package/dist/auth/actionAuthorization.js +94 -0
  44. package/dist/auth/actionAuthorization.js.map +1 -0
  45. package/dist/auth/index.d.ts +1 -0
  46. package/dist/auth/index.d.ts.map +1 -1
  47. package/dist/auth/index.js +4 -10
  48. package/dist/auth/index.js.map +1 -1
  49. package/dist/auth/magicLink/MagicLinkRouter.d.ts.map +1 -1
  50. package/dist/auth/magicLink/MagicLinkRouter.js +3 -2
  51. package/dist/auth/magicLink/MagicLinkRouter.js.map +1 -1
  52. package/dist/auth/magicLink/MagicLinkService.d.ts.map +1 -1
  53. package/dist/auth/magicLink/MagicLinkService.js +6 -3
  54. package/dist/auth/magicLink/MagicLinkService.js.map +1 -1
  55. package/dist/auth/newUsers.d.ts +10 -0
  56. package/dist/auth/newUsers.d.ts.map +1 -1
  57. package/dist/auth/newUsers.js +80 -11
  58. package/dist/auth/newUsers.js.map +1 -1
  59. package/dist/config.d.ts +2514 -22
  60. package/dist/config.d.ts.map +1 -1
  61. package/dist/config.js +166 -4
  62. package/dist/config.js.map +1 -1
  63. package/dist/context.d.ts +8 -0
  64. package/dist/context.d.ts.map +1 -1
  65. package/dist/context.js +14 -11
  66. package/dist/context.js.map +1 -1
  67. package/dist/coreRoutes.d.ts +29 -0
  68. package/dist/coreRoutes.d.ts.map +1 -0
  69. package/dist/coreRoutes.js +142 -0
  70. package/dist/coreRoutes.js.map +1 -0
  71. package/dist/generated/graphql-schemas/__mj.d.ts +504 -3
  72. package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
  73. package/dist/generated/graphql-schemas/__mj.js +2456 -7
  74. package/dist/generated/graphql-schemas/__mj.js.map +1 -1
  75. package/dist/generic/CacheInvalidationResolver.d.ts +43 -8
  76. package/dist/generic/CacheInvalidationResolver.d.ts.map +1 -1
  77. package/dist/generic/CacheInvalidationResolver.js +59 -19
  78. package/dist/generic/CacheInvalidationResolver.js.map +1 -1
  79. package/dist/generic/OrphanedConversationDetailReconciler.js +3 -3
  80. package/dist/generic/OrphanedConversationDetailReconciler.js.map +1 -1
  81. package/dist/generic/PushStatusResolver.js +1 -1
  82. package/dist/generic/PushStatusResolver.js.map +1 -1
  83. package/dist/generic/ResolverBase.d.ts +123 -48
  84. package/dist/generic/ResolverBase.d.ts.map +1 -1
  85. package/dist/generic/ResolverBase.js +293 -305
  86. package/dist/generic/ResolverBase.js.map +1 -1
  87. package/dist/generic/RunViewResolver.d.ts.map +1 -1
  88. package/dist/generic/RunViewResolver.js +16 -0
  89. package/dist/generic/RunViewResolver.js.map +1 -1
  90. package/dist/globalCors.d.ts +33 -0
  91. package/dist/globalCors.d.ts.map +1 -0
  92. package/dist/globalCors.js +58 -0
  93. package/dist/globalCors.js.map +1 -0
  94. package/dist/index.d.ts +17 -0
  95. package/dist/index.d.ts.map +1 -1
  96. package/dist/index.js +219 -278
  97. package/dist/index.js.map +1 -1
  98. package/dist/isolatedProvider.d.ts +20 -0
  99. package/dist/isolatedProvider.d.ts.map +1 -0
  100. package/dist/isolatedProvider.js +22 -0
  101. package/dist/isolatedProvider.js.map +1 -0
  102. package/dist/multiTenancy/index.d.ts.map +1 -1
  103. package/dist/multiTenancy/index.js +22 -2
  104. package/dist/multiTenancy/index.js.map +1 -1
  105. package/dist/realtimeProxy/RealtimeProxyServer.d.ts +44 -4
  106. package/dist/realtimeProxy/RealtimeProxyServer.d.ts.map +1 -1
  107. package/dist/realtimeProxy/RealtimeProxyServer.js +121 -16
  108. package/dist/realtimeProxy/RealtimeProxyServer.js.map +1 -1
  109. package/dist/realtimeProxy/RealtimeRelayTunnel.d.ts +171 -0
  110. package/dist/realtimeProxy/RealtimeRelayTunnel.d.ts.map +1 -0
  111. package/dist/realtimeProxy/RealtimeRelayTunnel.js +532 -0
  112. package/dist/realtimeProxy/RealtimeRelayTunnel.js.map +1 -0
  113. package/dist/realtimeSessions/RealtimeSessionEventService.d.ts +148 -0
  114. package/dist/realtimeSessions/RealtimeSessionEventService.d.ts.map +1 -0
  115. package/dist/realtimeSessions/RealtimeSessionEventService.js +191 -0
  116. package/dist/realtimeSessions/RealtimeSessionEventService.js.map +1 -0
  117. package/dist/realtimeSessions/RealtimeSessionVerificationService.d.ts +66 -0
  118. package/dist/realtimeSessions/RealtimeSessionVerificationService.d.ts.map +1 -0
  119. package/dist/realtimeSessions/RealtimeSessionVerificationService.js +139 -0
  120. package/dist/realtimeSessions/RealtimeSessionVerificationService.js.map +1 -0
  121. package/dist/realtimeSessions/RealtimeVerifyRouter.d.ts +38 -0
  122. package/dist/realtimeSessions/RealtimeVerifyRouter.d.ts.map +1 -0
  123. package/dist/realtimeSessions/RealtimeVerifyRouter.js +99 -0
  124. package/dist/realtimeSessions/RealtimeVerifyRouter.js.map +1 -0
  125. package/dist/realtimeSessions/RecordingAssemblyScheduler.d.ts +100 -0
  126. package/dist/realtimeSessions/RecordingAssemblyScheduler.d.ts.map +1 -0
  127. package/dist/realtimeSessions/RecordingAssemblyScheduler.js +173 -0
  128. package/dist/realtimeSessions/RecordingAssemblyScheduler.js.map +1 -0
  129. package/dist/realtimeSessions/SessionVerificationPortsBase.d.ts +61 -0
  130. package/dist/realtimeSessions/SessionVerificationPortsBase.d.ts.map +1 -0
  131. package/dist/realtimeSessions/SessionVerificationPortsBase.js +149 -0
  132. package/dist/realtimeSessions/SessionVerificationPortsBase.js.map +1 -0
  133. package/dist/realtimeSessions/SlidingWindowRateLimiter.d.ts +57 -0
  134. package/dist/realtimeSessions/SlidingWindowRateLimiter.d.ts.map +1 -0
  135. package/dist/realtimeSessions/SlidingWindowRateLimiter.js +69 -0
  136. package/dist/realtimeSessions/SlidingWindowRateLimiter.js.map +1 -0
  137. package/dist/realtimeSessions/index.d.ts +19 -0
  138. package/dist/realtimeSessions/index.d.ts.map +1 -0
  139. package/dist/realtimeSessions/index.js +19 -0
  140. package/dist/realtimeSessions/index.js.map +1 -0
  141. package/dist/realtimeSessions/ownedRealtimeSession.d.ts +30 -0
  142. package/dist/realtimeSessions/ownedRealtimeSession.d.ts.map +1 -0
  143. package/dist/realtimeSessions/ownedRealtimeSession.js +47 -0
  144. package/dist/realtimeSessions/ownedRealtimeSession.js.map +1 -0
  145. package/dist/realtimeSessions/sessionAccess.d.ts +92 -0
  146. package/dist/realtimeSessions/sessionAccess.d.ts.map +1 -0
  147. package/dist/realtimeSessions/sessionAccess.js +94 -0
  148. package/dist/realtimeSessions/sessionAccess.js.map +1 -0
  149. package/dist/realtimeSessions/sessionConfigStamps.d.ts +70 -0
  150. package/dist/realtimeSessions/sessionConfigStamps.d.ts.map +1 -0
  151. package/dist/realtimeSessions/sessionConfigStamps.js +93 -0
  152. package/dist/realtimeSessions/sessionConfigStamps.js.map +1 -0
  153. package/dist/realtimeSessions/subscriptionAuthorization.d.ts +65 -0
  154. package/dist/realtimeSessions/subscriptionAuthorization.d.ts.map +1 -0
  155. package/dist/realtimeSessions/subscriptionAuthorization.js +71 -0
  156. package/dist/realtimeSessions/subscriptionAuthorization.js.map +1 -0
  157. package/dist/realtimeSessions/verificationCore.d.ts +243 -0
  158. package/dist/realtimeSessions/verificationCore.d.ts.map +1 -0
  159. package/dist/realtimeSessions/verificationCore.js +356 -0
  160. package/dist/realtimeSessions/verificationCore.js.map +1 -0
  161. package/dist/realtimeSessions/verificationPages.d.ts +66 -0
  162. package/dist/realtimeSessions/verificationPages.d.ts.map +1 -0
  163. package/dist/realtimeSessions/verificationPages.js +110 -0
  164. package/dist/realtimeSessions/verificationPages.js.map +1 -0
  165. package/dist/realtimeSessions/verificationPolicy.d.ts +147 -0
  166. package/dist/realtimeSessions/verificationPolicy.d.ts.map +1 -0
  167. package/dist/realtimeSessions/verificationPolicy.js +249 -0
  168. package/dist/realtimeSessions/verificationPolicy.js.map +1 -0
  169. package/dist/realtimeSessions/verificationWorkflow.d.ts +263 -0
  170. package/dist/realtimeSessions/verificationWorkflow.d.ts.map +1 -0
  171. package/dist/realtimeSessions/verificationWorkflow.js +435 -0
  172. package/dist/realtimeSessions/verificationWorkflow.js.map +1 -0
  173. package/dist/realtimeWidget/widgetGuestElevation.d.ts +20 -2
  174. package/dist/realtimeWidget/widgetGuestElevation.d.ts.map +1 -1
  175. package/dist/realtimeWidget/widgetGuestElevation.js +31 -2
  176. package/dist/realtimeWidget/widgetGuestElevation.js.map +1 -1
  177. package/dist/resolvers/ActionResolver.d.ts +31 -5
  178. package/dist/resolvers/ActionResolver.d.ts.map +1 -1
  179. package/dist/resolvers/ActionResolver.js +75 -14
  180. package/dist/resolvers/ActionResolver.js.map +1 -1
  181. package/dist/resolvers/AdhocQueryResolver.d.ts +14 -1
  182. package/dist/resolvers/AdhocQueryResolver.d.ts.map +1 -1
  183. package/dist/resolvers/AdhocQueryResolver.js +21 -5
  184. package/dist/resolvers/AdhocQueryResolver.js.map +1 -1
  185. package/dist/resolvers/ArtifactFileResolver.d.ts +5 -1
  186. package/dist/resolvers/ArtifactFileResolver.d.ts.map +1 -1
  187. package/dist/resolvers/ArtifactFileResolver.js +9 -7
  188. package/dist/resolvers/ArtifactFileResolver.js.map +1 -1
  189. package/dist/resolvers/CacheStatsResolver.d.ts +27 -0
  190. package/dist/resolvers/CacheStatsResolver.d.ts.map +1 -1
  191. package/dist/resolvers/CacheStatsResolver.js +113 -2
  192. package/dist/resolvers/CacheStatsResolver.js.map +1 -1
  193. package/dist/resolvers/ClientToolRequestResolver.d.ts +4 -2
  194. package/dist/resolvers/ClientToolRequestResolver.d.ts.map +1 -1
  195. package/dist/resolvers/ClientToolRequestResolver.js +28 -10
  196. package/dist/resolvers/ClientToolRequestResolver.js.map +1 -1
  197. package/dist/resolvers/ConversationRunEventsResolver.d.ts +8 -4
  198. package/dist/resolvers/ConversationRunEventsResolver.d.ts.map +1 -1
  199. package/dist/resolvers/ConversationRunEventsResolver.js +6 -6
  200. package/dist/resolvers/ConversationRunEventsResolver.js.map +1 -1
  201. package/dist/resolvers/DatasetResolver.d.ts +24 -3
  202. package/dist/resolvers/DatasetResolver.d.ts.map +1 -1
  203. package/dist/resolvers/DatasetResolver.js +57 -8
  204. package/dist/resolvers/DatasetResolver.js.map +1 -1
  205. package/dist/resolvers/EntityRecordNameResolver.d.ts +2 -1
  206. package/dist/resolvers/EntityRecordNameResolver.d.ts.map +1 -1
  207. package/dist/resolvers/EntityRecordNameResolver.js +3 -2
  208. package/dist/resolvers/EntityRecordNameResolver.js.map +1 -1
  209. package/dist/resolvers/FetchEntityVectorsResolver.d.ts +27 -1
  210. package/dist/resolvers/FetchEntityVectorsResolver.d.ts.map +1 -1
  211. package/dist/resolvers/FetchEntityVectorsResolver.js +148 -2
  212. package/dist/resolvers/FetchEntityVectorsResolver.js.map +1 -1
  213. package/dist/resolvers/FileResolver.d.ts +82 -11
  214. package/dist/resolvers/FileResolver.d.ts.map +1 -1
  215. package/dist/resolvers/FileResolver.js +279 -101
  216. package/dist/resolvers/FileResolver.js.map +1 -1
  217. package/dist/resolvers/GetDataContextDataResolver.d.ts +33 -7
  218. package/dist/resolvers/GetDataContextDataResolver.d.ts.map +1 -1
  219. package/dist/resolvers/GetDataContextDataResolver.js +117 -38
  220. package/dist/resolvers/GetDataContextDataResolver.js.map +1 -1
  221. package/dist/resolvers/GetDataResolver.d.ts.map +1 -1
  222. package/dist/resolvers/GetDataResolver.js +6 -3
  223. package/dist/resolvers/GetDataResolver.js.map +1 -1
  224. package/dist/resolvers/HumanHandoffResolver.d.ts.map +1 -1
  225. package/dist/resolvers/HumanHandoffResolver.js +7 -1
  226. package/dist/resolvers/HumanHandoffResolver.js.map +1 -1
  227. package/dist/resolvers/IntegrationDiscoveryResolver.d.ts.map +1 -1
  228. package/dist/resolvers/IntegrationDiscoveryResolver.js +4 -2
  229. package/dist/resolvers/IntegrationDiscoveryResolver.js.map +1 -1
  230. package/dist/resolvers/MCPResolver.d.ts.map +1 -1
  231. package/dist/resolvers/MCPResolver.js +9 -4
  232. package/dist/resolvers/MCPResolver.js.map +1 -1
  233. package/dist/resolvers/QueryResolver.d.ts +2 -0
  234. package/dist/resolvers/QueryResolver.d.ts.map +1 -1
  235. package/dist/resolvers/QueryResolver.js +13 -0
  236. package/dist/resolvers/QueryResolver.js.map +1 -1
  237. package/dist/resolvers/RealtimeBridgeResolver.d.ts +54 -2
  238. package/dist/resolvers/RealtimeBridgeResolver.d.ts.map +1 -1
  239. package/dist/resolvers/RealtimeBridgeResolver.js +155 -10
  240. package/dist/resolvers/RealtimeBridgeResolver.js.map +1 -1
  241. package/dist/resolvers/RealtimeClientSessionResolver.d.ts +157 -33
  242. package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
  243. package/dist/resolvers/RealtimeClientSessionResolver.js +404 -116
  244. package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
  245. package/dist/resolvers/RealtimeSessionEventsResolver.d.ts +61 -0
  246. package/dist/resolvers/RealtimeSessionEventsResolver.d.ts.map +1 -0
  247. package/dist/resolvers/RealtimeSessionEventsResolver.js +130 -0
  248. package/dist/resolvers/RealtimeSessionEventsResolver.js.map +1 -0
  249. package/dist/resolvers/RealtimeSessionVerificationResolver.d.ts +85 -0
  250. package/dist/resolvers/RealtimeSessionVerificationResolver.d.ts.map +1 -0
  251. package/dist/resolvers/RealtimeSessionVerificationResolver.js +188 -0
  252. package/dist/resolvers/RealtimeSessionVerificationResolver.js.map +1 -0
  253. package/dist/resolvers/RemoteBrowserActionResolver.d.ts +2 -0
  254. package/dist/resolvers/RemoteBrowserActionResolver.d.ts.map +1 -1
  255. package/dist/resolvers/RemoteBrowserActionResolver.js +5 -1
  256. package/dist/resolvers/RemoteBrowserActionResolver.js.map +1 -1
  257. package/dist/resolvers/RunAIAgentResolver.d.ts +10 -0
  258. package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
  259. package/dist/resolvers/RunAIAgentResolver.js +100 -14
  260. package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
  261. package/dist/resolvers/RunTemplateResolver.d.ts.map +1 -1
  262. package/dist/resolvers/RunTemplateResolver.js +12 -11
  263. package/dist/resolvers/RunTemplateResolver.js.map +1 -1
  264. package/dist/resolvers/SearchKnowledgeStreamResolver.d.ts +12 -1
  265. package/dist/resolvers/SearchKnowledgeStreamResolver.d.ts.map +1 -1
  266. package/dist/resolvers/SearchKnowledgeStreamResolver.js +15 -2
  267. package/dist/resolvers/SearchKnowledgeStreamResolver.js.map +1 -1
  268. package/dist/resolvers/SqlLoggingConfigResolver.d.ts.map +1 -1
  269. package/dist/resolvers/SqlLoggingConfigResolver.js +2 -3
  270. package/dist/resolvers/SqlLoggingConfigResolver.js.map +1 -1
  271. package/dist/resolvers/SyncDataResolver.d.ts.map +1 -1
  272. package/dist/resolvers/SyncDataResolver.js +6 -2
  273. package/dist/resolvers/SyncDataResolver.js.map +1 -1
  274. package/dist/resolvers/SyncRolesUsersResolver.d.ts +2 -0
  275. package/dist/resolvers/SyncRolesUsersResolver.d.ts.map +1 -1
  276. package/dist/resolvers/SyncRolesUsersResolver.js +23 -6
  277. package/dist/resolvers/SyncRolesUsersResolver.js.map +1 -1
  278. package/dist/resolvers/TaskGraphFrameResolver.d.ts +2 -1
  279. package/dist/resolvers/TaskGraphFrameResolver.d.ts.map +1 -1
  280. package/dist/resolvers/TaskGraphFrameResolver.js +28 -1
  281. package/dist/resolvers/TaskGraphFrameResolver.js.map +1 -1
  282. package/dist/resolvers/TestQuerySQLResolver.d.ts +24 -0
  283. package/dist/resolvers/TestQuerySQLResolver.d.ts.map +1 -1
  284. package/dist/resolvers/TestQuerySQLResolver.js +65 -13
  285. package/dist/resolvers/TestQuerySQLResolver.js.map +1 -1
  286. package/dist/resolvers/TransactionGroupResolver.d.ts +19 -2
  287. package/dist/resolvers/TransactionGroupResolver.d.ts.map +1 -1
  288. package/dist/resolvers/TransactionGroupResolver.js +102 -24
  289. package/dist/resolvers/TransactionGroupResolver.js.map +1 -1
  290. package/dist/resolvers/UserAvatarResolver.d.ts +16 -0
  291. package/dist/resolvers/UserAvatarResolver.d.ts.map +1 -0
  292. package/dist/resolvers/UserAvatarResolver.js +124 -0
  293. package/dist/resolvers/UserAvatarResolver.js.map +1 -0
  294. package/dist/resolvers/UserResolver.d.ts.map +1 -1
  295. package/dist/resolvers/UserResolver.js +2 -17
  296. package/dist/resolvers/UserResolver.js.map +1 -1
  297. package/dist/resolvers/agent-run-data-guard.d.ts +31 -0
  298. package/dist/resolvers/agent-run-data-guard.d.ts.map +1 -0
  299. package/dist/resolvers/agent-run-data-guard.js +72 -0
  300. package/dist/resolvers/agent-run-data-guard.js.map +1 -0
  301. package/dist/resolvers/agentVisionConsentAudit.d.ts +37 -0
  302. package/dist/resolvers/agentVisionConsentAudit.d.ts.map +1 -0
  303. package/dist/resolvers/agentVisionConsentAudit.js +63 -0
  304. package/dist/resolvers/agentVisionConsentAudit.js.map +1 -0
  305. package/dist/resolvers/avatarInputValidation.d.ts +40 -0
  306. package/dist/resolvers/avatarInputValidation.d.ts.map +1 -0
  307. package/dist/resolvers/avatarInputValidation.js +97 -0
  308. package/dist/resolvers/avatarInputValidation.js.map +1 -0
  309. package/dist/resolvers/currentUserRoles.d.ts +29 -0
  310. package/dist/resolvers/currentUserRoles.d.ts.map +1 -1
  311. package/dist/resolvers/currentUserRoles.js +30 -0
  312. package/dist/resolvers/currentUserRoles.js.map +1 -1
  313. package/dist/resolvers/meetings/meetingMutations.d.ts +6 -0
  314. package/dist/resolvers/meetings/meetingMutations.d.ts.map +1 -1
  315. package/dist/resolvers/meetings/meetingMutations.js +21 -0
  316. package/dist/resolvers/meetings/meetingMutations.js.map +1 -1
  317. package/dist/rest/RealtimeRecordingHandler.d.ts +49 -0
  318. package/dist/rest/RealtimeRecordingHandler.d.ts.map +1 -0
  319. package/dist/rest/RealtimeRecordingHandler.js +383 -0
  320. package/dist/rest/RealtimeRecordingHandler.js.map +1 -0
  321. package/dist/rest/ViewOperationsHandler.d.ts +3 -1
  322. package/dist/rest/ViewOperationsHandler.d.ts.map +1 -1
  323. package/dist/rest/ViewOperationsHandler.js +21 -12
  324. package/dist/rest/ViewOperationsHandler.js.map +1 -1
  325. package/dist/rest/setupRESTEndpoints.d.ts +6 -0
  326. package/dist/rest/setupRESTEndpoints.d.ts.map +1 -1
  327. package/dist/rest/setupRESTEndpoints.js +6 -0
  328. package/dist/rest/setupRESTEndpoints.js.map +1 -1
  329. package/dist/serverExtensionReservedRoots.d.ts +12 -10
  330. package/dist/serverExtensionReservedRoots.d.ts.map +1 -1
  331. package/dist/serverExtensionReservedRoots.js +14 -17
  332. package/dist/serverExtensionReservedRoots.js.map +1 -1
  333. package/dist/services/MJStorageBlobStore.d.ts +8 -1
  334. package/dist/services/MJStorageBlobStore.d.ts.map +1 -1
  335. package/dist/services/MJStorageBlobStore.js +25 -19
  336. package/dist/services/MJStorageBlobStore.js.map +1 -1
  337. package/dist/services/StartTaskGraphDispatcher.js +2 -2
  338. package/dist/services/StartTaskGraphDispatcher.js.map +1 -1
  339. package/dist/services/TaskGraphActionRunner.d.ts +5 -3
  340. package/dist/services/TaskGraphActionRunner.d.ts.map +1 -1
  341. package/dist/services/TaskGraphActionRunner.js +27 -5
  342. package/dist/services/TaskGraphActionRunner.js.map +1 -1
  343. package/dist/services/TaskGraphAgentRunner.d.ts +14 -2
  344. package/dist/services/TaskGraphAgentRunner.d.ts.map +1 -1
  345. package/dist/services/TaskGraphAgentRunner.js +157 -9
  346. package/dist/services/TaskGraphAgentRunner.js.map +1 -1
  347. package/dist/services/TaskGraphContinuationDeliverer.d.ts +57 -4
  348. package/dist/services/TaskGraphContinuationDeliverer.d.ts.map +1 -1
  349. package/dist/services/TaskGraphContinuationDeliverer.js +607 -18
  350. package/dist/services/TaskGraphContinuationDeliverer.js.map +1 -1
  351. package/dist/sharedCache.d.ts +105 -0
  352. package/dist/sharedCache.d.ts.map +1 -0
  353. package/dist/sharedCache.js +279 -0
  354. package/dist/sharedCache.js.map +1 -0
  355. package/dist/types.d.ts +6 -0
  356. package/dist/types.d.ts.map +1 -1
  357. package/dist/types.js.map +1 -1
  358. package/package.json +99 -96
  359. package/src/__tests__/ActionResolver.authorization.test.ts +353 -0
  360. package/src/__tests__/AdhocQueryResolver.sqlAuthorizer.test.ts +104 -0
  361. package/src/__tests__/BridgeHostInstance.test.ts +232 -0
  362. package/src/__tests__/ClientToolRequestResolver.test.ts +116 -0
  363. package/src/__tests__/DatasetResolver.itemFilters.test.ts +201 -0
  364. package/src/__tests__/EntityRecordNameResolver.rowSecurity.test.ts +152 -0
  365. package/src/__tests__/FetchEntityVectorsResolver.test.ts +433 -0
  366. package/src/__tests__/FileResolver.storageAccess.test.ts +562 -0
  367. package/src/__tests__/GetDataContextDataResolver.sqlItems.test.ts +416 -0
  368. package/src/__tests__/HostInstance.test.ts +84 -0
  369. package/src/__tests__/HumanHandoffResolver.test.ts +17 -0
  370. package/src/__tests__/ListOperationsResolver.adhocScreen.test.ts +137 -0
  371. package/src/__tests__/LoginScreenEngine.test.ts +86 -0
  372. package/src/__tests__/MJStorageBlobStore.storageGate.test.ts +63 -0
  373. package/src/__tests__/MeetingResolver.test.ts +75 -1
  374. package/src/__tests__/RealtimeBridgeResolver.test.ts +415 -6
  375. package/src/__tests__/RealtimeClientSessionResolver.configTamper.test.ts +383 -0
  376. package/src/__tests__/RealtimeClientSessionResolver.test.ts +682 -58
  377. package/src/__tests__/RealtimeProxyServer.test.ts +16 -1
  378. package/src/__tests__/RealtimeRelay.test.ts +876 -0
  379. package/src/__tests__/RecordingAssemblyScheduler.test.ts +178 -0
  380. package/src/__tests__/RemoteBrowserActionResolver.goalRunLink.test.ts +129 -0
  381. package/src/__tests__/ResolverBase.adhocReadScope.test.ts +133 -2
  382. package/src/__tests__/ResolverBase.aggregateScreen.test.ts +174 -0
  383. package/src/__tests__/ResolverBase.balancedClause.test.ts +155 -0
  384. package/src/__tests__/ResolverBase.clauseUnscopedRead.test.ts +127 -0
  385. package/src/__tests__/ResolverBase.clientOldValues.test.ts +212 -6
  386. package/src/__tests__/ResolverBase.filterEscaping.test.ts +5 -2
  387. package/src/__tests__/ReturningVisitorRecap.test.ts +115 -0
  388. package/src/__tests__/RunAIAgentResolver.branch.test.ts +170 -0
  389. package/src/__tests__/RunAIAgentResolver.historyFrom.test.ts +6 -4
  390. package/src/__tests__/RunAIAgentResolver.runDataGuard.test.ts +258 -0
  391. package/src/__tests__/RunAIAgentResolver.stoppedPredecessor.test.ts +166 -0
  392. package/src/__tests__/RunTemplateResolver.test.ts +132 -0
  393. package/src/__tests__/RunViewResolver.cacheCheckForwarding.test.ts +63 -0
  394. package/src/__tests__/RunViewResolver.cacheCheckScreen.test.ts +165 -0
  395. package/src/__tests__/SearchKnowledgeStreamResolver.test.ts +161 -0
  396. package/src/__tests__/SessionJanitor.test.ts +1203 -21
  397. package/src/__tests__/SessionManager.test.ts +240 -17
  398. package/src/__tests__/SessionRunIDVerifier.test.ts +251 -0
  399. package/src/__tests__/SyncRolesUsersResolver.userRoles.test.ts +97 -0
  400. package/src/__tests__/TaskGraphActionRunner.durablePayload.test.ts +25 -0
  401. package/src/__tests__/TaskGraphAgentRunner.test.ts +266 -0
  402. package/src/__tests__/TaskGraphContinuationDeliverer.test.ts +403 -22
  403. package/src/__tests__/TaskGraphFrameResolver.test.ts +22 -0
  404. package/src/__tests__/TestQuerySQLResolver.test.ts +274 -0
  405. package/src/__tests__/TransactionGroupResolver.refusals.test.ts +98 -1
  406. package/src/__tests__/UpdateMJDataContextItem.oldValues.test.ts +220 -0
  407. package/src/__tests__/UserAvatarResolver.test.ts +255 -0
  408. package/src/__tests__/UserResolver.currentUser.test.ts +16 -1
  409. package/src/__tests__/ViewOperationsHandler.clauseScreen.test.ts +157 -0
  410. package/src/__tests__/actionAuthorization.test.ts +130 -0
  411. package/src/__tests__/agent-run-data-guard.test.ts +130 -0
  412. package/src/__tests__/agentVisionConsentAudit.test.ts +118 -0
  413. package/src/__tests__/apiRootBoot.test.ts +202 -0
  414. package/src/__tests__/avatarInputValidation.test.ts +160 -0
  415. package/src/__tests__/cacheInvalidationRecordData.test.ts +64 -0
  416. package/src/__tests__/conversationRunEvents.test.ts +56 -14
  417. package/src/__tests__/globalCors.test.ts +217 -0
  418. package/src/__tests__/isolatedProviderTransactions.test.ts +169 -0
  419. package/src/__tests__/magicLinkService.consume.test.ts +6 -0
  420. package/src/__tests__/magicLinkService.provisionLog.test.ts +5 -0
  421. package/src/__tests__/multiTenancy.postgresRewrite.test.ts +181 -0
  422. package/src/__tests__/multiTenancy.security.test.ts +51 -0
  423. package/src/__tests__/multiTenancy.test.ts +1 -1
  424. package/src/__tests__/newUsers.template.test.ts +151 -0
  425. package/src/__tests__/newUsers.test.ts +74 -5
  426. package/src/__tests__/orphanedConversationDetailReconciler.test.ts +17 -2
  427. package/src/__tests__/ownedRealtimeSession.test.ts +39 -0
  428. package/src/__tests__/pushStatusResolver.filter.test.ts +7 -0
  429. package/src/__tests__/realtimeRecordingHandler.test.ts +652 -0
  430. package/src/__tests__/realtimeSessions/RealtimeSessionEventService.test.ts +201 -0
  431. package/src/__tests__/realtimeSessions/RealtimeSessionEventsResolver.test.ts +215 -0
  432. package/src/__tests__/realtimeSessions/RealtimeSessionVerificationResolver.test.ts +102 -0
  433. package/src/__tests__/realtimeSessions/RealtimeSessionVerificationService.test.ts +349 -0
  434. package/src/__tests__/realtimeSessions/RealtimeVerifyRouter.test.ts +131 -0
  435. package/src/__tests__/realtimeSessions/SlidingWindowRateLimiter.test.ts +56 -0
  436. package/src/__tests__/realtimeSessions/helpers.ts +4 -0
  437. package/src/__tests__/realtimeSessions/sessionAccess.test.ts +129 -0
  438. package/src/__tests__/realtimeSessions/sessionConfigStamps.test.ts +124 -0
  439. package/src/__tests__/realtimeSessions/verificationCore.test.ts +355 -0
  440. package/src/__tests__/realtimeSessions/verificationPages.test.ts +84 -0
  441. package/src/__tests__/realtimeSessions/verificationPolicy.test.ts +226 -0
  442. package/src/__tests__/realtimeSessions/verificationWorkflow.test.ts +633 -0
  443. package/src/__tests__/resolverBase.fls.test.ts +87 -4
  444. package/src/__tests__/serverExtensionHostOverlay.test.ts +66 -0
  445. package/src/__tests__/serverExtensionReservedRoots.test.ts +11 -96
  446. package/src/__tests__/sharedCache.test.ts +312 -0
  447. package/src/__tests__/widgetGuestElevation.test.ts +44 -0
  448. package/src/agentSessions/BridgeHostInstance.ts +32 -0
  449. package/src/agentSessions/HostInstance.ts +124 -28
  450. package/src/agentSessions/ReturningVisitorRecap.ts +7 -3
  451. package/src/agentSessions/SessionJanitor.ts +821 -59
  452. package/src/agentSessions/SessionManager.ts +138 -26
  453. package/src/agentSessions/SessionRunIDVerifier.ts +171 -0
  454. package/src/agentSessions/index.ts +10 -3
  455. package/src/apiRoot.ts +78 -0
  456. package/src/auth/AuthProviderCatalogRouter.ts +26 -0
  457. package/src/auth/LoginScreenEngine.ts +50 -0
  458. package/src/auth/actionAuthorization.ts +116 -0
  459. package/src/auth/index.ts +4 -8
  460. package/src/auth/magicLink/MagicLinkRouter.ts +3 -2
  461. package/src/auth/magicLink/MagicLinkService.ts +6 -3
  462. package/src/auth/newUsers.ts +99 -11
  463. package/src/config.ts +171 -5
  464. package/src/context.ts +15 -10
  465. package/src/coreRoutes.ts +166 -0
  466. package/src/generated/graphql-schemas/__mj.ts +1815 -57
  467. package/src/generic/CacheInvalidationResolver.ts +73 -18
  468. package/src/generic/OrphanedConversationDetailReconciler.ts +3 -3
  469. package/src/generic/PushStatusResolver.ts +1 -1
  470. package/src/generic/ResolverBase.ts +335 -326
  471. package/src/generic/RunViewResolver.ts +21 -0
  472. package/src/globalCors.ts +71 -0
  473. package/src/index.ts +231 -300
  474. package/src/isolatedProvider.ts +22 -0
  475. package/src/multiTenancy/index.ts +24 -2
  476. package/src/realtimeProxy/RealtimeProxyServer.ts +148 -16
  477. package/src/realtimeProxy/RealtimeRelayTunnel.ts +645 -0
  478. package/src/realtimeSessions/README.md +179 -0
  479. package/src/realtimeSessions/RealtimeSessionEventService.ts +274 -0
  480. package/src/realtimeSessions/RealtimeSessionVerificationService.ts +168 -0
  481. package/src/realtimeSessions/RealtimeVerifyRouter.ts +109 -0
  482. package/src/realtimeSessions/RecordingAssemblyScheduler.ts +234 -0
  483. package/src/realtimeSessions/SessionVerificationPortsBase.ts +189 -0
  484. package/src/realtimeSessions/SlidingWindowRateLimiter.ts +92 -0
  485. package/src/realtimeSessions/index.ts +18 -0
  486. package/src/realtimeSessions/ownedRealtimeSession.ts +55 -0
  487. package/src/realtimeSessions/sessionAccess.ts +145 -0
  488. package/src/realtimeSessions/sessionConfigStamps.ts +123 -0
  489. package/src/realtimeSessions/subscriptionAuthorization.ts +116 -0
  490. package/src/realtimeSessions/verificationCore.ts +502 -0
  491. package/src/realtimeSessions/verificationPages.ts +149 -0
  492. package/src/realtimeSessions/verificationPolicy.ts +321 -0
  493. package/src/realtimeSessions/verificationWorkflow.ts +702 -0
  494. package/src/realtimeWidget/widgetGuestElevation.ts +34 -2
  495. package/src/resolvers/ActionResolver.ts +84 -21
  496. package/src/resolvers/AdhocQueryResolver.ts +24 -6
  497. package/src/resolvers/ArtifactFileResolver.ts +9 -8
  498. package/src/resolvers/CacheStatsResolver.ts +89 -2
  499. package/src/resolvers/ClientToolRequestResolver.ts +25 -7
  500. package/src/resolvers/ConversationRunEventsResolver.ts +15 -11
  501. package/src/resolvers/DatasetResolver.ts +70 -10
  502. package/src/resolvers/EntityRecordNameResolver.ts +3 -2
  503. package/src/resolvers/FetchEntityVectorsResolver.ts +179 -2
  504. package/src/resolvers/FileResolver.ts +321 -106
  505. package/src/resolvers/GetDataContextDataResolver.ts +139 -41
  506. package/src/resolvers/GetDataResolver.ts +6 -3
  507. package/src/resolvers/HumanHandoffResolver.ts +8 -0
  508. package/src/resolvers/IntegrationDiscoveryResolver.ts +4 -2
  509. package/src/resolvers/MCPResolver.ts +8 -5
  510. package/src/resolvers/QueryResolver.ts +11 -0
  511. package/src/resolvers/RealtimeBridgeResolver.ts +163 -11
  512. package/src/resolvers/RealtimeClientSessionResolver.ts +511 -129
  513. package/src/resolvers/RealtimeSessionEventsResolver.ts +133 -0
  514. package/src/resolvers/RealtimeSessionVerificationResolver.ts +174 -0
  515. package/src/resolvers/RemoteBrowserActionResolver.ts +11 -1
  516. package/src/resolvers/RunAIAgentResolver.ts +116 -14
  517. package/src/resolvers/RunTemplateResolver.ts +13 -12
  518. package/src/resolvers/SearchKnowledgeStreamResolver.ts +19 -3
  519. package/src/resolvers/SqlLoggingConfigResolver.ts +2 -4
  520. package/src/resolvers/SyncDataResolver.ts +6 -2
  521. package/src/resolvers/SyncRolesUsersResolver.ts +24 -6
  522. package/src/resolvers/TaskGraphFrameResolver.ts +33 -1
  523. package/src/resolvers/TestQuerySQLResolver.ts +68 -13
  524. package/src/resolvers/TransactionGroupResolver.ts +131 -27
  525. package/src/resolvers/UserAvatarResolver.ts +111 -0
  526. package/src/resolvers/UserResolver.ts +2 -16
  527. package/src/resolvers/__tests__/GetDataResolver.validation.test.ts +95 -0
  528. package/src/resolvers/agent-run-data-guard.ts +89 -0
  529. package/src/resolvers/agentVisionConsentAudit.ts +82 -0
  530. package/src/resolvers/avatarInputValidation.ts +123 -0
  531. package/src/resolvers/currentUserRoles.ts +32 -1
  532. package/src/resolvers/meetings/meetingMutations.ts +21 -0
  533. package/src/rest/RealtimeRecordingHandler.ts +464 -0
  534. package/src/rest/ViewOperationsHandler.ts +29 -13
  535. package/src/rest/setupRESTEndpoints.ts +6 -0
  536. package/src/serverExtensionReservedRoots.ts +14 -17
  537. package/src/services/MJStorageBlobStore.ts +31 -30
  538. package/src/services/StartTaskGraphDispatcher.ts +2 -2
  539. package/src/services/TaskGraphActionRunner.ts +25 -5
  540. package/src/services/TaskGraphAgentRunner.ts +188 -11
  541. package/src/services/TaskGraphContinuationDeliverer.ts +713 -17
  542. package/src/sharedCache.ts +305 -0
  543. package/src/types.ts +7 -0
@@ -0,0 +1,502 @@
1
+ /**
2
+ * @fileoverview Pure functional core of mid-session identity verification — no DB, no email, no clock
3
+ * of its own. Everything here is deterministic given its inputs (modulo crypto randomness) and is
4
+ * unit-testable with plain assertions.
5
+ *
6
+ * ## What is stored, and where
7
+ *
8
+ * The verification state of a session lives in `AIAgentSession.Config` under the key
9
+ * `identityVerification` (a server-authoritative key — see `sessionConfigGuard` in
10
+ * `@memberjunction/core-entities-server`). It is an {@link IdentityVerificationState}: the policy
11
+ * snapshot taken at mint, how many emails the session has triggered, at most one *pending*
12
+ * verification, and — once done — the verified identity.
13
+ *
14
+ * ## Secrets
15
+ *
16
+ * Nothing redeemable is ever stored: only hashes.
17
+ *
18
+ * - The **link token** is `mj_rv_<session id>_<256 random bits>`. The session id is deliberately part of
19
+ * it so redemption is a primary-key lookup instead of a scan; it is not a secret. Only the SHA-256 of
20
+ * the whole token is stored, and it is compared in constant time.
21
+ * - The **typed code** is 6 digits (the fallback for opening the email on another device). Six digits
22
+ * cannot be made brute-force-proof by hashing — a million candidates is nothing — so its protection is
23
+ * structural: it expires, it is single-use, it is voided after {@link IdentityVerificationPolicy.maxCodeAttempts}
24
+ * wrong entries, only the session's own principal can submit it, and submissions are rate-limited.
25
+ * It is salted and bound to the session id so equal codes do not produce equal stored hashes.
26
+ *
27
+ * Token generation and hashing reuse the magic-link module's primitives (`HashToken`).
28
+ *
29
+ * @module @memberjunction/server/realtimeSessions
30
+ */
31
+
32
+ import { createHash, createHmac, randomBytes, randomInt, timingSafeEqual } from 'node:crypto';
33
+ import { HashToken } from '../auth/magicLink/magicLinkCore.js';
34
+ import type { IdentityVerificationPolicy, ResolvedIdentityVerificationPolicy } from './verificationPolicy.js';
35
+
36
+ /** Prefix of a verification link token (mirrors `mj_ml_` / `mj_sk_`). */
37
+ export const VERIFICATION_TOKEN_PREFIX = 'mj_rv_';
38
+
39
+ /** Number of digits in the typed code. */
40
+ export const VERIFICATION_CODE_LENGTH = 6;
41
+
42
+ /** Longest name accepted, in characters. */
43
+ export const MAX_VERIFICATION_NAME_LENGTH = 100;
44
+
45
+ /** A verification that has been requested and not yet redeemed. Holds hashes only. */
46
+ export interface PendingVerification {
47
+ /** SHA-256 (base64url) of the link token. */
48
+ TokenHash: string;
49
+ /** Salted, server-keyed HMAC-SHA-256 (base64url) of the typed code. */
50
+ CodeHash: string;
51
+ /** Random per-verification salt for {@link PendingVerification.CodeHash}. */
52
+ CodeSalt: string;
53
+ /** The address the link and code were sent to (normalised). */
54
+ Email: string;
55
+ /** The name given with the request (sanitised, length-capped). */
56
+ Name: string;
57
+ /** ISO instant the verification was requested. */
58
+ RequestedAt: string;
59
+ /** ISO instant after which neither the link nor the code redeems. */
60
+ ExpiresAt: string;
61
+ /** Wrong code entries so far. */
62
+ CodeAttempts: number;
63
+ }
64
+
65
+ /** The proven identity of a verified session. */
66
+ export interface VerifiedIdentity {
67
+ Email: string;
68
+ Name: string;
69
+ /** ISO instant the verification completed. */
70
+ VerifiedAt: string;
71
+ /** How it was proven. */
72
+ Method: 'link' | 'code';
73
+ }
74
+
75
+ /** The whole verification state of one session (the value of `Config.identityVerification`). */
76
+ export interface IdentityVerificationState {
77
+ /** The session-scoped policy layer, snapshotted at mint. Absent when the cascade carried none. */
78
+ Policy?: IdentityVerificationPolicy;
79
+ /** How many verification emails this session has triggered (a send that failed to deliver does not count). */
80
+ SendCount: number;
81
+ /** The outstanding verification, if any. A new request replaces it. */
82
+ Pending?: PendingVerification;
83
+ /** Set once verified. */
84
+ Verified?: VerifiedIdentity;
85
+ }
86
+
87
+ /** A state with nothing in it. */
88
+ export function EmptyIdentityVerificationState(): IdentityVerificationState {
89
+ return { SendCount: 0 };
90
+ }
91
+
92
+ /** True for a plain object. */
93
+ function isRecord(value: unknown): value is Record<string, unknown> {
94
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
95
+ }
96
+
97
+ /** True for a non-empty string. */
98
+ function isNonEmptyString(value: unknown): value is string {
99
+ return typeof value === 'string' && value.length > 0;
100
+ }
101
+
102
+ /** Reads a pending verification from untrusted JSON; undefined unless every field is well-formed. */
103
+ function readPending(value: unknown): PendingVerification | undefined {
104
+ if (!isRecord(value)) {
105
+ return undefined;
106
+ }
107
+ const { TokenHash, CodeHash, CodeSalt, Email, Name, RequestedAt, ExpiresAt, CodeAttempts } = value;
108
+ if (
109
+ !isNonEmptyString(TokenHash) || !isNonEmptyString(CodeHash) || !isNonEmptyString(CodeSalt) ||
110
+ !isNonEmptyString(Email) || typeof Name !== 'string' || !isNonEmptyString(RequestedAt) ||
111
+ !isNonEmptyString(ExpiresAt) || typeof CodeAttempts !== 'number' || !Number.isFinite(CodeAttempts)
112
+ ) {
113
+ return undefined;
114
+ }
115
+ return { TokenHash, CodeHash, CodeSalt, Email, Name, RequestedAt, ExpiresAt, CodeAttempts };
116
+ }
117
+
118
+ /** Reads a verified identity from untrusted JSON; undefined unless every field is well-formed. */
119
+ function readVerified(value: unknown): VerifiedIdentity | undefined {
120
+ if (!isRecord(value)) {
121
+ return undefined;
122
+ }
123
+ const { Email, Name, VerifiedAt, Method } = value;
124
+ if (!isNonEmptyString(Email) || typeof Name !== 'string' || !isNonEmptyString(VerifiedAt) || (Method !== 'link' && Method !== 'code')) {
125
+ return undefined;
126
+ }
127
+ return { Email, Name, VerifiedAt, Method };
128
+ }
129
+
130
+ /**
131
+ * Reads the verification state out of a session's `Config` JSON. Tolerant by design: a missing,
132
+ * malformed or partial value yields an empty / partial state rather than an exception, because a
133
+ * garbled state must never be the reason a session cannot be addressed.
134
+ *
135
+ * @param configRaw - `AIAgentSession.Config` as stored
136
+ * @param readPolicy - reads a policy layer from untrusted JSON (injected to keep this module free of policy wiring)
137
+ */
138
+ export function ReadIdentityVerificationState(
139
+ configRaw: string | null | undefined,
140
+ readPolicy: (raw: unknown) => IdentityVerificationPolicy,
141
+ ): IdentityVerificationState {
142
+ const config = ParseConfigObject(configRaw);
143
+ const stored = config['identityVerification'];
144
+ if (!isRecord(stored)) {
145
+ return EmptyIdentityVerificationState();
146
+ }
147
+ const sends = stored['SendCount'];
148
+ const state: IdentityVerificationState = {
149
+ SendCount: typeof sends === 'number' && Number.isFinite(sends) && sends >= 0 ? Math.floor(sends) : 0,
150
+ };
151
+ const policy = readPolicy(stored['Policy']);
152
+ if (Object.keys(policy).length > 0) {
153
+ state.Policy = policy;
154
+ }
155
+ const pending = readPending(stored['Pending']);
156
+ if (pending) {
157
+ state.Pending = pending;
158
+ }
159
+ const verified = readVerified(stored['Verified']);
160
+ if (verified) {
161
+ state.Verified = verified;
162
+ }
163
+ return state;
164
+ }
165
+
166
+ /** Parses a `Config` JSON string into a plain object; anything else is `{}`. */
167
+ export function ParseConfigObject(raw: string | null | undefined): Record<string, unknown> {
168
+ if (typeof raw !== 'string' || raw.trim().length === 0) {
169
+ return {};
170
+ }
171
+ try {
172
+ const parsed: unknown = JSON.parse(raw);
173
+ return isRecord(parsed) ? parsed : {};
174
+ } catch {
175
+ return {};
176
+ }
177
+ }
178
+
179
+ /**
180
+ * Returns `configRaw` with `identityVerification` (and, when given, `maxSessionDeadlineIso`) replaced,
181
+ * preserving every other key verbatim.
182
+ *
183
+ * @param configRaw - the stored Config
184
+ * @param state - the new verification state
185
+ * @param deadlineIso - a new absolute deadline to write, or undefined to leave the existing one alone
186
+ */
187
+ export function WriteIdentityVerificationState(
188
+ configRaw: string | null | undefined,
189
+ state: IdentityVerificationState,
190
+ deadlineIso?: string,
191
+ ): string {
192
+ const config = ParseConfigObject(configRaw);
193
+ config['identityVerification'] = state;
194
+ if (deadlineIso !== undefined) {
195
+ config['maxSessionDeadlineIso'] = deadlineIso;
196
+ }
197
+ return JSON.stringify(config);
198
+ }
199
+
200
+ // ───────────────────────── tokens, codes, hashing ─────────────────────────
201
+
202
+ /** Lower-case 32-hex form of a UUID (the dashes dropped), or null when it is not a UUID. */
203
+ function compactUUID(uuid: string): string | null {
204
+ const compact = uuid.replace(/-/g, '').toLowerCase();
205
+ return /^[0-9a-f]{32}$/.test(compact) ? compact : null;
206
+ }
207
+
208
+ /** Re-inserts the dashes of a compact UUID. */
209
+ function expandUUID(compact: string): string {
210
+ return `${compact.slice(0, 8)}-${compact.slice(8, 12)}-${compact.slice(12, 16)}-${compact.slice(16, 20)}-${compact.slice(20)}`;
211
+ }
212
+
213
+ /**
214
+ * Generates a verification link token bound (by construction) to one session.
215
+ *
216
+ * @param agentSessionId - the session the token redeems against
217
+ * @throws Error when `agentSessionId` is not a UUID — a programming error, never user input
218
+ */
219
+ export function GenerateVerificationToken(agentSessionId: string): string {
220
+ const compact = compactUUID(agentSessionId);
221
+ if (!compact) {
222
+ throw new Error(`GenerateVerificationToken: '${agentSessionId}' is not a session id`);
223
+ }
224
+ return `${VERIFICATION_TOKEN_PREFIX}${compact}_${randomBytes(32).toString('hex')}`;
225
+ }
226
+
227
+ /** The structure of a token, once parsed. */
228
+ export interface ParsedVerificationToken {
229
+ /** The session id embedded in the token (dashed, lower-case). Not a secret and not yet trusted. */
230
+ AgentSessionID: string;
231
+ }
232
+
233
+ /** Token shape: prefix, 32 hex of session id, `_`, 64 hex of randomness. */
234
+ const TOKEN_PATTERN = new RegExp(`^${VERIFICATION_TOKEN_PREFIX}([0-9a-f]{32})_[0-9a-f]{64}$`);
235
+
236
+ /**
237
+ * Parses a presented token's structure. Success means "well-formed", NOT "valid": validity is decided
238
+ * only by comparing hashes ({@link EvaluateRedemption}).
239
+ *
240
+ * @returns the embedded session id, or null for anything malformed
241
+ */
242
+ export function ParseVerificationToken(token: string): ParsedVerificationToken | null {
243
+ const match = typeof token === 'string' ? TOKEN_PATTERN.exec(token) : null;
244
+ return match ? { AgentSessionID: expandUUID(match[1]) } : null;
245
+ }
246
+
247
+ /** Generates a uniformly random numeric code of {@link VERIFICATION_CODE_LENGTH} digits. */
248
+ export function GenerateVerificationCode(): string {
249
+ return randomInt(0, 10 ** VERIFICATION_CODE_LENGTH).toString().padStart(VERIFICATION_CODE_LENGTH, '0');
250
+ }
251
+
252
+ /**
253
+ * Normalises a typed code: strips spaces and dashes (people type "123 456"). Returns null unless the
254
+ * result is exactly {@link VERIFICATION_CODE_LENGTH} digits.
255
+ */
256
+ export function NormalizeVerificationCode(input: string): string | null {
257
+ const stripped = (input ?? '').replace(/[\s-]/g, '');
258
+ return new RegExp(`^\\d{${VERIFICATION_CODE_LENGTH}}$`).test(stripped) ? stripped : null;
259
+ }
260
+
261
+ /** Hash stored for a link token. */
262
+ export function HashVerificationToken(token: string): string {
263
+ return HashToken(token);
264
+ }
265
+
266
+ /**
267
+ * Hash stored for a code: an HMAC-SHA-256 under a SERVER-ONLY key, salted and bound to the session so
268
+ * equal codes never hash equal.
269
+ *
270
+ * The key is what makes this safe. A code is only six digits, and the session's owner — the very person
271
+ * being verified — can read their own `Session.Config`, hash and salt included. A plain (even salted)
272
+ * hash would let them recover the code offline in about a million guesses without ever opening the
273
+ * inbox, bypassing the attempt cap entirely. Without the key the stored value is useless to a reader.
274
+ *
275
+ * @param code - the normalised six-digit code
276
+ * @param salt - the per-verification salt stored beside the hash
277
+ * @param agentSessionId - the session the code is bound to
278
+ * @param key - the server-only key (`realtime.identityVerification.hmacSecret`); must be non-empty
279
+ */
280
+ export function HashVerificationCode(code: string, salt: string, agentSessionId: string, key: string): string {
281
+ if (!key) {
282
+ throw new Error('HashVerificationCode requires a non-empty server key.');
283
+ }
284
+ return createHmac('sha256', key).update(`rv-code:${agentSessionId.toLowerCase()}:${salt}:${code}`).digest('base64url');
285
+ }
286
+
287
+ /**
288
+ * Constant-time equality of two hash strings. Both are re-hashed to a fixed length first, so neither
289
+ * the comparison time nor an early length check reveals how much of a guess was right.
290
+ */
291
+ export function HashesEqual(a: string, b: string): boolean {
292
+ const digestA = createHash('sha256').update(a).digest();
293
+ const digestB = createHash('sha256').update(b).digest();
294
+ return timingSafeEqual(digestA, digestB);
295
+ }
296
+
297
+ /**
298
+ * Sanitises a display name: collapses whitespace, strips control characters and the characters that
299
+ * could shape markup or a header (`<`, `>`, `"`), and caps the length. The result is for display
300
+ * only — it is still HTML-escaped wherever it is rendered.
301
+ *
302
+ * @returns the cleaned name, or null when nothing usable is left
303
+ */
304
+ export function SanitizeVerificationName(raw: string): string | null {
305
+ const cleaned = (raw ?? '')
306
+ // eslint-disable-next-line no-control-regex -- stripping control characters is the point
307
+ .replace(/[\u0000-\u001F\u007F-\u009F<>"]/g, ' ')
308
+ .replace(/\s+/g, ' ')
309
+ .trim()
310
+ .slice(0, MAX_VERIFICATION_NAME_LENGTH)
311
+ .trim();
312
+ return cleaned.length > 0 ? cleaned : null;
313
+ }
314
+
315
+ // ───────────────────────── issuing ─────────────────────────
316
+
317
+ /** What {@link IssueVerification} produces: the state to store and the secrets to send. */
318
+ export interface IssuedVerification {
319
+ /** Store this. Contains hashes only. */
320
+ Pending: PendingVerification;
321
+ /** Send this in the link. Never store it. */
322
+ Token: string;
323
+ /** Send this as the typed code. Never store it. */
324
+ Code: string;
325
+ }
326
+
327
+ /**
328
+ * Creates a fresh pending verification for a session.
329
+ *
330
+ * @param args.AgentSessionID - the session being verified
331
+ * @param args.Email - the normalised address the secrets will be sent to
332
+ * @param args.Name - the sanitised name
333
+ * @param args.TtlMinutes - how long the secrets stay redeemable
334
+ * @param args.NowMs - the current time, epoch ms
335
+ */
336
+ export function IssueVerification(args: {
337
+ AgentSessionID: string;
338
+ Email: string;
339
+ Name: string;
340
+ TtlMinutes: number;
341
+ NowMs: number;
342
+ /** The server-only code-hash key (see {@link HashVerificationCode}). */
343
+ CodeHashKey: string;
344
+ }): IssuedVerification {
345
+ const token = GenerateVerificationToken(args.AgentSessionID);
346
+ const code = GenerateVerificationCode();
347
+ const salt = randomBytes(16).toString('base64url');
348
+ return {
349
+ Token: token,
350
+ Code: code,
351
+ Pending: {
352
+ TokenHash: HashVerificationToken(token),
353
+ CodeHash: HashVerificationCode(code, salt, args.AgentSessionID, args.CodeHashKey),
354
+ CodeSalt: salt,
355
+ Email: args.Email,
356
+ Name: args.Name,
357
+ RequestedAt: new Date(args.NowMs).toISOString(),
358
+ ExpiresAt: new Date(args.NowMs + args.TtlMinutes * 60_000).toISOString(),
359
+ CodeAttempts: 0,
360
+ },
361
+ };
362
+ }
363
+
364
+ // ───────────────────────── redemption ─────────────────────────
365
+
366
+ /** What a person presents to redeem a verification. */
367
+ export type VerificationCredential =
368
+ | { Kind: 'token'; Token: string }
369
+ | { Kind: 'code'; Code: string; AgentSessionID: string };
370
+
371
+ /** Why a redemption was refused. */
372
+ export type RedemptionRefusal =
373
+ | 'already_verified' // the session is already verified (code path; idempotent for the caller)
374
+ | 'no_pending' // nothing outstanding to redeem
375
+ | 'expired' // past ExpiresAt; the pending verification has been cleared
376
+ | 'invalid' // the secret did not match
377
+ | 'attempts_exhausted'; // too many wrong codes; the pending verification has been voided
378
+
379
+ /** Outcome of {@link EvaluateRedemption}. `Next` is the state to persist in every case. */
380
+ export type RedemptionOutcome =
381
+ | { Outcome: 'verified'; Next: IdentityVerificationState; Verified: VerifiedIdentity }
382
+ | { Outcome: 'rejected'; Reason: RedemptionRefusal; Next: IdentityVerificationState; AttemptsRemaining?: number };
383
+
384
+ /** `state` with the pending verification removed. */
385
+ function withoutPending(state: IdentityVerificationState): IdentityVerificationState {
386
+ const { Pending: _removed, ...rest } = state;
387
+ return rest;
388
+ }
389
+
390
+ /**
391
+ * Decides a redemption attempt and returns the state to persist. Pure: the caller supplies the time.
392
+ *
393
+ * Properties this function guarantees (and the tests pin):
394
+ *
395
+ * - **Single use.** A success removes the pending verification, so neither the link nor the code works again.
396
+ * - **Expiry.** Past `ExpiresAt` nothing redeems, and the stale pending verification is cleared.
397
+ * - **Bounded guessing.** A wrong code increments `CodeAttempts`; reaching `maxCodeAttempts` voids the
398
+ * pending verification (a new request — itself capped by `maxSendsPerSession` — is needed). A wrong
399
+ * *token* changes nothing: 256 random bits are not guessable, and counting them would let a stranger
400
+ * burn a legitimate person's pending link just by hitting the URL.
401
+ * - **Constant-time compares.** Secrets are compared via {@link HashesEqual}.
402
+ *
403
+ * @param state - the session's current verification state
404
+ * @param credential - what was presented
405
+ * @param nowMs - the current time, epoch ms
406
+ * @param policy - the resolved policy (for `maxCodeAttempts`)
407
+ * @param codeHashKey - the server-only code-hash key (see {@link HashVerificationCode})
408
+ */
409
+ export function EvaluateRedemption(
410
+ state: IdentityVerificationState,
411
+ credential: VerificationCredential,
412
+ nowMs: number,
413
+ policy: Pick<ResolvedIdentityVerificationPolicy, 'maxCodeAttempts'>,
414
+ codeHashKey: string,
415
+ ): RedemptionOutcome {
416
+ if (state.Verified) {
417
+ return { Outcome: 'rejected', Reason: 'already_verified', Next: state };
418
+ }
419
+ const pending = state.Pending;
420
+ if (!pending) {
421
+ return { Outcome: 'rejected', Reason: 'no_pending', Next: state };
422
+ }
423
+ if (nowMs >= Date.parse(pending.ExpiresAt) || Number.isNaN(Date.parse(pending.ExpiresAt))) {
424
+ return { Outcome: 'rejected', Reason: 'expired', Next: withoutPending(state) };
425
+ }
426
+ if (credential.Kind === 'token') {
427
+ return HashesEqual(HashVerificationToken(credential.Token), pending.TokenHash)
428
+ ? verifiedOutcome(state, pending, 'link', nowMs)
429
+ : { Outcome: 'rejected', Reason: 'invalid', Next: state };
430
+ }
431
+ const presented = HashVerificationCode(credential.Code, pending.CodeSalt, credential.AgentSessionID, codeHashKey);
432
+ if (HashesEqual(presented, pending.CodeHash)) {
433
+ return verifiedOutcome(state, pending, 'code', nowMs);
434
+ }
435
+ return wrongCodeOutcome(state, pending, policy.maxCodeAttempts);
436
+ }
437
+
438
+ /** The success outcome: pending cleared, identity recorded. */
439
+ function verifiedOutcome(
440
+ state: IdentityVerificationState,
441
+ pending: PendingVerification,
442
+ method: VerifiedIdentity['Method'],
443
+ nowMs: number,
444
+ ): RedemptionOutcome {
445
+ const verified: VerifiedIdentity = {
446
+ Email: pending.Email,
447
+ Name: pending.Name,
448
+ VerifiedAt: new Date(nowMs).toISOString(),
449
+ Method: method,
450
+ };
451
+ return { Outcome: 'verified', Next: { ...withoutPending(state), Verified: verified }, Verified: verified };
452
+ }
453
+
454
+ /** The wrong-code outcome: one more attempt spent, voiding the pending verification at the cap. */
455
+ function wrongCodeOutcome(
456
+ state: IdentityVerificationState,
457
+ pending: PendingVerification,
458
+ maxCodeAttempts: number,
459
+ ): RedemptionOutcome {
460
+ const attempts = pending.CodeAttempts + 1;
461
+ if (attempts >= maxCodeAttempts) {
462
+ return { Outcome: 'rejected', Reason: 'attempts_exhausted', Next: withoutPending(state), AttemptsRemaining: 0 };
463
+ }
464
+ return {
465
+ Outcome: 'rejected',
466
+ Reason: 'invalid',
467
+ Next: { ...state, Pending: { ...pending, CodeAttempts: attempts } },
468
+ AttemptsRemaining: maxCodeAttempts - attempts,
469
+ };
470
+ }
471
+
472
+ // ───────────────────────── deadline ─────────────────────────
473
+
474
+ /**
475
+ * The session deadline after a successful verification, or `undefined` when verification should not
476
+ * change it.
477
+ *
478
+ * Verification only ever **extends** a deadline that exists: a session with no deadline stays
479
+ * uncapped, and a `verifiedMaxSeconds` shorter than what is already granted never pulls a deadline in.
480
+ * The new deadline is measured from the session's start, so `verifiedMaxSeconds` means "a verified
481
+ * session may run this long in total", the same meaning `unverifiedMaxSeconds` has.
482
+ *
483
+ * @param args.CurrentDeadlineIso - the stamped deadline, if any
484
+ * @param args.SessionStartedAtMs - when the session started, epoch ms
485
+ * @param args.VerifiedMaxSeconds - the verified cap from policy, if any
486
+ * @returns the new ISO deadline, or undefined for "leave it alone"
487
+ */
488
+ export function ComputeVerifiedDeadline(args: {
489
+ CurrentDeadlineIso: string | undefined;
490
+ SessionStartedAtMs: number;
491
+ VerifiedMaxSeconds: number | undefined;
492
+ }): string | undefined {
493
+ if (!args.VerifiedMaxSeconds || args.VerifiedMaxSeconds <= 0 || !args.CurrentDeadlineIso) {
494
+ return undefined;
495
+ }
496
+ const currentMs = Date.parse(args.CurrentDeadlineIso);
497
+ const candidateMs = args.SessionStartedAtMs + args.VerifiedMaxSeconds * 1000;
498
+ if (Number.isNaN(currentMs) || Number.isNaN(candidateMs) || candidateMs <= currentMs) {
499
+ return undefined;
500
+ }
501
+ return new Date(candidateMs).toISOString();
502
+ }
@@ -0,0 +1,149 @@
1
+ /**
2
+ * @fileoverview Pure builders for the two pieces of content identity verification puts in front of a
3
+ * person: the verification EMAIL and the self-contained HTML PAGES the verify link lands on. No DB, no
4
+ * network, no MJ runtime — deterministic strings in, strings out.
5
+ *
6
+ * ## What the pages never contain
7
+ *
8
+ * Nothing about the session: no agent name, no conversation, no email address, no name, no hint that a
9
+ * session with this id exists. The page for an unknown token, an expired token and a used token is the
10
+ * **same page**, so the link cannot be used to probe which sessions exist or what state they are in.
11
+ *
12
+ * ## Why GET does not redeem
13
+ *
14
+ * Mail security scanners, link previewers and browser prefetchers issue GET requests for every URL in a
15
+ * message. A GET that consumed the single-use token would let a scanner burn it before the human clicks
16
+ * — the person would then see "already used". So the link opens a page with one button ({@link
17
+ * BuildVerifyConfirmHtml}) and only the human's POST redeems. The magic-link redeem endpoint made the
18
+ * same choice for the same reason.
19
+ *
20
+ * @module @memberjunction/server/realtimeSessions
21
+ */
22
+
23
+ import { EscapeHtml } from '../auth/magicLink/redeemLanding.js';
24
+
25
+ /** Inputs to {@link BuildVerificationEmail}. */
26
+ export interface VerificationEmailInput {
27
+ /** The person's sanitised name. */
28
+ Name: string;
29
+ /** The typed code (fallback for opening the email on another device). */
30
+ Code: string;
31
+ /** The full link that opens the confirmation page. */
32
+ LinkUrl: string;
33
+ /** Minutes the link and code stay valid. */
34
+ TtlMinutes: number;
35
+ }
36
+
37
+ /** A ready-to-send email. */
38
+ export interface VerificationEmail {
39
+ Subject: string;
40
+ Text: string;
41
+ Html: string;
42
+ }
43
+
44
+ /**
45
+ * Builds the verification email.
46
+ *
47
+ * The subject never carries the name or the code (subjects surface on lock screens and in notification
48
+ * previews). The name is HTML-escaped in the HTML body and reduced to a single line in the text body.
49
+ */
50
+ export function BuildVerificationEmail(input: VerificationEmailInput): VerificationEmail {
51
+ const name = input.Name.replace(/\s+/g, ' ').trim();
52
+ const greeting = name ? `Hi ${name},` : 'Hi,';
53
+ const ttl = `${input.TtlMinutes} minute${input.TtlMinutes === 1 ? '' : 's'}`;
54
+ const text =
55
+ `${greeting}\n\n` +
56
+ `Confirm your email address to continue your conversation. Open this link:\n\n${input.LinkUrl}\n\n` +
57
+ `Or, if you opened this email on a different device, type this code into your conversation:\n\n${input.Code}\n\n` +
58
+ `The link and the code work once and expire in ${ttl}.\n\n` +
59
+ `If you did not ask for this, you can ignore this email — nothing happens unless the link is opened or the code is entered.`;
60
+ const html =
61
+ `<p>${EscapeHtml(greeting)}</p>` +
62
+ `<p>Confirm your email address to continue your conversation.</p>` +
63
+ `<p><a href="${EscapeHtml(input.LinkUrl)}">Confirm my email address</a></p>` +
64
+ `<p>Or, if you opened this email on a different device, type this code into your conversation:</p>` +
65
+ `<p style="font-size:1.5em;letter-spacing:.15em"><strong>${EscapeHtml(input.Code)}</strong></p>` +
66
+ `<p>The link and the code work once and expire in ${EscapeHtml(ttl)}.</p>` +
67
+ `<p>If you did not ask for this, you can ignore this email — nothing happens unless the link is opened or the code is entered.</p>`;
68
+ return { Subject: 'Confirm your email address', Text: text, Html: html };
69
+ }
70
+
71
+ /** Shared document shell for the verify pages. Hardcoded colours are intentional: standalone server-rendered HTML, not Angular CSS. */
72
+ function page(title: string, bodyInner: string): string {
73
+ return `<!doctype html>
74
+ <html lang="en">
75
+ <head>
76
+ <meta charset="utf-8">
77
+ <meta name="viewport" content="width=device-width, initial-scale=1">
78
+ <meta name="robots" content="noindex, nofollow">
79
+ <meta name="referrer" content="no-referrer">
80
+ <title>${EscapeHtml(title)}</title>
81
+ <style>
82
+ body { font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; background:#f5f5f5; margin:0; display:flex; min-height:100vh; align-items:center; justify-content:center; }
83
+ .card { background:#fff; padding:2.5rem; border-radius:12px; box-shadow:0 1px 4px rgba(0,0,0,.12); max-width:24rem; text-align:center; margin:1rem; }
84
+ h1 { font-size:1.25rem; margin:0 0 .5rem; color:#1f2937; }
85
+ p { color:#4b5563; margin:0 0 1.5rem; font-size:.95rem; }
86
+ p:last-child { margin-bottom:0; }
87
+ button { background:#264FAF; color:#fff; border:0; border-radius:8px; padding:.75rem 1.5rem; font-size:1rem; cursor:pointer; width:100%; }
88
+ button:hover { background:#1e3f8c; }
89
+ </style>
90
+ </head>
91
+ <body>
92
+ <div class="card">
93
+ ${bodyInner}
94
+ </div>
95
+ </body>
96
+ </html>`;
97
+ }
98
+
99
+ /**
100
+ * The page a verify link opens. Side-effect free: it renders a single confirmation button whose POST
101
+ * performs the redemption (see the module header for why GET must not).
102
+ *
103
+ * @param token - the raw token, embedded in a hidden form field (never in the POST URL)
104
+ * @param actionPath - the same-origin path the form POSTs to
105
+ */
106
+ export function BuildVerifyConfirmHtml(token: string, actionPath: string): string {
107
+ return page(
108
+ 'Confirm your email',
109
+ ` <h1>Confirm your email address</h1>
110
+ <p>Press the button to confirm. Then return to your conversation.</p>
111
+ <form method="POST" action="${EscapeHtml(actionPath)}">
112
+ <input type="hidden" name="token" value="${EscapeHtml(token)}">
113
+ <button type="submit">Confirm my email address</button>
114
+ </form>`,
115
+ );
116
+ }
117
+
118
+ /** The page shown after a successful redemption. Says nothing about the session. */
119
+ export function BuildVerifySuccessHtml(): string {
120
+ return page(
121
+ "You're verified",
122
+ ` <h1>You're verified</h1>
123
+ <p>Return to your conversation — it will carry on from where you left off. You can close this tab.</p>`,
124
+ );
125
+ }
126
+
127
+ /**
128
+ * The page shown for EVERY failed redemption — unknown, malformed, expired, used, or voided token alike —
129
+ * so the response cannot be used to learn anything about a session.
130
+ */
131
+ export function BuildVerifyFailureHtml(): string {
132
+ return page(
133
+ 'Link not valid',
134
+ ` <h1>This link is no longer valid</h1>
135
+ <p>It may have expired or already been used. Return to your conversation to request a new one.</p>`,
136
+ );
137
+ }
138
+
139
+ /**
140
+ * The page shown when the server hit a fault while recording a verification (the link was valid but
141
+ * nothing was saved, so trying the link again is correct). Only a holder of a valid token can reach it.
142
+ */
143
+ export function BuildVerifyErrorHtml(): string {
144
+ return page(
145
+ 'Something went wrong',
146
+ ` <h1>We could not complete this just now</h1>
147
+ <p>Please open the link from your email again in a moment.</p>`,
148
+ );
149
+ }