@memberjunction/server 6.2.0-edge.2 → 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 (435) hide show
  1. package/README.md +30 -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 +173 -25
  15. package/dist/agentSessions/SessionJanitor.d.ts.map +1 -1
  16. package/dist/agentSessions/SessionJanitor.js +452 -54
  17. package/dist/agentSessions/SessionJanitor.js.map +1 -1
  18. package/dist/agentSessions/SessionManager.d.ts +43 -2
  19. package/dist/agentSessions/SessionManager.d.ts.map +1 -1
  20. package/dist/agentSessions/SessionManager.js +99 -8
  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/auth/actionAuthorization.d.ts +40 -0
  31. package/dist/auth/actionAuthorization.d.ts.map +1 -0
  32. package/dist/auth/actionAuthorization.js +94 -0
  33. package/dist/auth/actionAuthorization.js.map +1 -0
  34. package/dist/auth/index.d.ts.map +1 -1
  35. package/dist/auth/index.js +3 -10
  36. package/dist/auth/index.js.map +1 -1
  37. package/dist/auth/magicLink/MagicLinkService.d.ts.map +1 -1
  38. package/dist/auth/magicLink/MagicLinkService.js +6 -3
  39. package/dist/auth/magicLink/MagicLinkService.js.map +1 -1
  40. package/dist/auth/newUsers.d.ts +10 -0
  41. package/dist/auth/newUsers.d.ts.map +1 -1
  42. package/dist/auth/newUsers.js +80 -11
  43. package/dist/auth/newUsers.js.map +1 -1
  44. package/dist/config.d.ts +4567 -71
  45. package/dist/config.d.ts.map +1 -1
  46. package/dist/config.js +246 -3
  47. package/dist/config.js.map +1 -1
  48. package/dist/context.d.ts +8 -0
  49. package/dist/context.d.ts.map +1 -1
  50. package/dist/context.js +25 -34
  51. package/dist/context.js.map +1 -1
  52. package/dist/generated/graphql-schemas/__mj.d.ts +1073 -10
  53. package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
  54. package/dist/generated/graphql-schemas/__mj.js +9188 -3649
  55. package/dist/generated/graphql-schemas/__mj.js.map +1 -1
  56. package/dist/generic/OrphanedConversationDetailReconciler.js +3 -3
  57. package/dist/generic/OrphanedConversationDetailReconciler.js.map +1 -1
  58. package/dist/generic/ResolverBase.d.ts +62 -6
  59. package/dist/generic/ResolverBase.d.ts.map +1 -1
  60. package/dist/generic/ResolverBase.js +179 -35
  61. package/dist/generic/ResolverBase.js.map +1 -1
  62. package/dist/generic/RunViewResolver.d.ts +4 -0
  63. package/dist/generic/RunViewResolver.d.ts.map +1 -1
  64. package/dist/generic/RunViewResolver.js +37 -1
  65. package/dist/generic/RunViewResolver.js.map +1 -1
  66. package/dist/index.d.ts +7 -0
  67. package/dist/index.d.ts.map +1 -1
  68. package/dist/index.js +214 -159
  69. package/dist/index.js.map +1 -1
  70. package/dist/isolatedProvider.d.ts +20 -0
  71. package/dist/isolatedProvider.d.ts.map +1 -0
  72. package/dist/isolatedProvider.js +22 -0
  73. package/dist/isolatedProvider.js.map +1 -0
  74. package/dist/postgresqlCompat.d.ts +8 -0
  75. package/dist/postgresqlCompat.d.ts.map +1 -0
  76. package/dist/postgresqlCompat.js +19 -0
  77. package/dist/postgresqlCompat.js.map +1 -0
  78. package/dist/postgresqlPoolSettings.d.ts +108 -0
  79. package/dist/postgresqlPoolSettings.d.ts.map +1 -0
  80. package/dist/postgresqlPoolSettings.js +134 -0
  81. package/dist/postgresqlPoolSettings.js.map +1 -0
  82. package/dist/realtimeProxy/RealtimeProxyServer.d.ts +44 -4
  83. package/dist/realtimeProxy/RealtimeProxyServer.d.ts.map +1 -1
  84. package/dist/realtimeProxy/RealtimeProxyServer.js +121 -16
  85. package/dist/realtimeProxy/RealtimeProxyServer.js.map +1 -1
  86. package/dist/realtimeProxy/RealtimeRelayTunnel.d.ts +171 -0
  87. package/dist/realtimeProxy/RealtimeRelayTunnel.d.ts.map +1 -0
  88. package/dist/realtimeProxy/RealtimeRelayTunnel.js +532 -0
  89. package/dist/realtimeProxy/RealtimeRelayTunnel.js.map +1 -0
  90. package/dist/realtimeSessions/RealtimeSessionEventService.d.ts +148 -0
  91. package/dist/realtimeSessions/RealtimeSessionEventService.d.ts.map +1 -0
  92. package/dist/realtimeSessions/RealtimeSessionEventService.js +191 -0
  93. package/dist/realtimeSessions/RealtimeSessionEventService.js.map +1 -0
  94. package/dist/realtimeSessions/RealtimeSessionVerificationService.d.ts +66 -0
  95. package/dist/realtimeSessions/RealtimeSessionVerificationService.d.ts.map +1 -0
  96. package/dist/realtimeSessions/RealtimeSessionVerificationService.js +138 -0
  97. package/dist/realtimeSessions/RealtimeSessionVerificationService.js.map +1 -0
  98. package/dist/realtimeSessions/RealtimeVerifyRouter.d.ts +38 -0
  99. package/dist/realtimeSessions/RealtimeVerifyRouter.d.ts.map +1 -0
  100. package/dist/realtimeSessions/RealtimeVerifyRouter.js +98 -0
  101. package/dist/realtimeSessions/RealtimeVerifyRouter.js.map +1 -0
  102. package/dist/realtimeSessions/SessionVerificationPortsBase.d.ts +61 -0
  103. package/dist/realtimeSessions/SessionVerificationPortsBase.d.ts.map +1 -0
  104. package/dist/realtimeSessions/SessionVerificationPortsBase.js +149 -0
  105. package/dist/realtimeSessions/SessionVerificationPortsBase.js.map +1 -0
  106. package/dist/realtimeSessions/SlidingWindowRateLimiter.d.ts +57 -0
  107. package/dist/realtimeSessions/SlidingWindowRateLimiter.d.ts.map +1 -0
  108. package/dist/realtimeSessions/SlidingWindowRateLimiter.js +69 -0
  109. package/dist/realtimeSessions/SlidingWindowRateLimiter.js.map +1 -0
  110. package/dist/realtimeSessions/index.d.ts +17 -0
  111. package/dist/realtimeSessions/index.d.ts.map +1 -0
  112. package/dist/realtimeSessions/index.js +17 -0
  113. package/dist/realtimeSessions/index.js.map +1 -0
  114. package/dist/realtimeSessions/sessionAccess.d.ts +86 -0
  115. package/dist/realtimeSessions/sessionAccess.d.ts.map +1 -0
  116. package/dist/realtimeSessions/sessionAccess.js +90 -0
  117. package/dist/realtimeSessions/sessionAccess.js.map +1 -0
  118. package/dist/realtimeSessions/sessionConfigStamps.d.ts +70 -0
  119. package/dist/realtimeSessions/sessionConfigStamps.d.ts.map +1 -0
  120. package/dist/realtimeSessions/sessionConfigStamps.js +93 -0
  121. package/dist/realtimeSessions/sessionConfigStamps.js.map +1 -0
  122. package/dist/realtimeSessions/subscriptionAuthorization.d.ts +65 -0
  123. package/dist/realtimeSessions/subscriptionAuthorization.d.ts.map +1 -0
  124. package/dist/realtimeSessions/subscriptionAuthorization.js +71 -0
  125. package/dist/realtimeSessions/subscriptionAuthorization.js.map +1 -0
  126. package/dist/realtimeSessions/verificationCore.d.ts +243 -0
  127. package/dist/realtimeSessions/verificationCore.d.ts.map +1 -0
  128. package/dist/realtimeSessions/verificationCore.js +356 -0
  129. package/dist/realtimeSessions/verificationCore.js.map +1 -0
  130. package/dist/realtimeSessions/verificationPages.d.ts +66 -0
  131. package/dist/realtimeSessions/verificationPages.d.ts.map +1 -0
  132. package/dist/realtimeSessions/verificationPages.js +110 -0
  133. package/dist/realtimeSessions/verificationPages.js.map +1 -0
  134. package/dist/realtimeSessions/verificationPolicy.d.ts +147 -0
  135. package/dist/realtimeSessions/verificationPolicy.d.ts.map +1 -0
  136. package/dist/realtimeSessions/verificationPolicy.js +249 -0
  137. package/dist/realtimeSessions/verificationPolicy.js.map +1 -0
  138. package/dist/realtimeSessions/verificationWorkflow.d.ts +263 -0
  139. package/dist/realtimeSessions/verificationWorkflow.d.ts.map +1 -0
  140. package/dist/realtimeSessions/verificationWorkflow.js +435 -0
  141. package/dist/realtimeSessions/verificationWorkflow.js.map +1 -0
  142. package/dist/realtimeWidget/widgetGuestElevation.d.ts +20 -2
  143. package/dist/realtimeWidget/widgetGuestElevation.d.ts.map +1 -1
  144. package/dist/realtimeWidget/widgetGuestElevation.js +31 -2
  145. package/dist/realtimeWidget/widgetGuestElevation.js.map +1 -1
  146. package/dist/resolvers/ActionResolver.d.ts +31 -5
  147. package/dist/resolvers/ActionResolver.d.ts.map +1 -1
  148. package/dist/resolvers/ActionResolver.js +75 -14
  149. package/dist/resolvers/ActionResolver.js.map +1 -1
  150. package/dist/resolvers/AdhocQueryResolver.d.ts +35 -17
  151. package/dist/resolvers/AdhocQueryResolver.d.ts.map +1 -1
  152. package/dist/resolvers/AdhocQueryResolver.js +92 -166
  153. package/dist/resolvers/AdhocQueryResolver.js.map +1 -1
  154. package/dist/resolvers/CacheStatsResolver.d.ts +27 -0
  155. package/dist/resolvers/CacheStatsResolver.d.ts.map +1 -1
  156. package/dist/resolvers/CacheStatsResolver.js +113 -2
  157. package/dist/resolvers/CacheStatsResolver.js.map +1 -1
  158. package/dist/resolvers/ClientToolRequestResolver.d.ts +4 -2
  159. package/dist/resolvers/ClientToolRequestResolver.d.ts.map +1 -1
  160. package/dist/resolvers/ClientToolRequestResolver.js +28 -10
  161. package/dist/resolvers/ClientToolRequestResolver.js.map +1 -1
  162. package/dist/resolvers/EntityRecordNameResolver.d.ts +7 -23
  163. package/dist/resolvers/EntityRecordNameResolver.d.ts.map +1 -1
  164. package/dist/resolvers/EntityRecordNameResolver.js +9 -47
  165. package/dist/resolvers/EntityRecordNameResolver.js.map +1 -1
  166. package/dist/resolvers/FetchEntityVectorsResolver.d.ts +12 -1
  167. package/dist/resolvers/FetchEntityVectorsResolver.d.ts.map +1 -1
  168. package/dist/resolvers/FetchEntityVectorsResolver.js +83 -2
  169. package/dist/resolvers/FetchEntityVectorsResolver.js.map +1 -1
  170. package/dist/resolvers/GetDataResolver.d.ts.map +1 -1
  171. package/dist/resolvers/GetDataResolver.js +9 -0
  172. package/dist/resolvers/GetDataResolver.js.map +1 -1
  173. package/dist/resolvers/HumanHandoffResolver.d.ts +107 -0
  174. package/dist/resolvers/HumanHandoffResolver.d.ts.map +1 -0
  175. package/dist/resolvers/HumanHandoffResolver.js +330 -0
  176. package/dist/resolvers/HumanHandoffResolver.js.map +1 -0
  177. package/dist/resolvers/MCPResolver.d.ts.map +1 -1
  178. package/dist/resolvers/MCPResolver.js +9 -4
  179. package/dist/resolvers/MCPResolver.js.map +1 -1
  180. package/dist/resolvers/MeetingResolver.d.ts +69 -0
  181. package/dist/resolvers/MeetingResolver.d.ts.map +1 -0
  182. package/dist/resolvers/MeetingResolver.js +381 -0
  183. package/dist/resolvers/MeetingResolver.js.map +1 -0
  184. package/dist/resolvers/RealtimeBridgeResolver.d.ts +88 -3
  185. package/dist/resolvers/RealtimeBridgeResolver.d.ts.map +1 -1
  186. package/dist/resolvers/RealtimeBridgeResolver.js +352 -12
  187. package/dist/resolvers/RealtimeBridgeResolver.js.map +1 -1
  188. package/dist/resolvers/RealtimeClientSessionResolver.d.ts +156 -108
  189. package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
  190. package/dist/resolvers/RealtimeClientSessionResolver.js +395 -293
  191. package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
  192. package/dist/resolvers/RealtimeSessionEventsResolver.d.ts +61 -0
  193. package/dist/resolvers/RealtimeSessionEventsResolver.d.ts.map +1 -0
  194. package/dist/resolvers/RealtimeSessionEventsResolver.js +130 -0
  195. package/dist/resolvers/RealtimeSessionEventsResolver.js.map +1 -0
  196. package/dist/resolvers/RealtimeSessionVerificationResolver.d.ts +85 -0
  197. package/dist/resolvers/RealtimeSessionVerificationResolver.d.ts.map +1 -0
  198. package/dist/resolvers/RealtimeSessionVerificationResolver.js +188 -0
  199. package/dist/resolvers/RealtimeSessionVerificationResolver.js.map +1 -0
  200. package/dist/resolvers/RemoteBrowserActionResolver.d.ts +2 -0
  201. package/dist/resolvers/RemoteBrowserActionResolver.d.ts.map +1 -1
  202. package/dist/resolvers/RemoteBrowserActionResolver.js +5 -1
  203. package/dist/resolvers/RemoteBrowserActionResolver.js.map +1 -1
  204. package/dist/resolvers/RunAIAgentResolver.d.ts +10 -0
  205. package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
  206. package/dist/resolvers/RunAIAgentResolver.js +68 -11
  207. package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
  208. package/dist/resolvers/RunTemplateResolver.d.ts.map +1 -1
  209. package/dist/resolvers/RunTemplateResolver.js +12 -11
  210. package/dist/resolvers/RunTemplateResolver.js.map +1 -1
  211. package/dist/resolvers/SyncDataResolver.d.ts.map +1 -1
  212. package/dist/resolvers/SyncDataResolver.js +6 -2
  213. package/dist/resolvers/SyncDataResolver.js.map +1 -1
  214. package/dist/resolvers/SyncRolesUsersResolver.d.ts +2 -0
  215. package/dist/resolvers/SyncRolesUsersResolver.d.ts.map +1 -1
  216. package/dist/resolvers/SyncRolesUsersResolver.js +23 -6
  217. package/dist/resolvers/SyncRolesUsersResolver.js.map +1 -1
  218. package/dist/resolvers/UserAvatarResolver.d.ts +16 -0
  219. package/dist/resolvers/UserAvatarResolver.d.ts.map +1 -0
  220. package/dist/resolvers/UserAvatarResolver.js +124 -0
  221. package/dist/resolvers/UserAvatarResolver.js.map +1 -0
  222. package/dist/resolvers/UserResolver.d.ts.map +1 -1
  223. package/dist/resolvers/UserResolver.js +2 -17
  224. package/dist/resolvers/UserResolver.js.map +1 -1
  225. package/dist/resolvers/adhocTimeout.d.ts +7 -0
  226. package/dist/resolvers/adhocTimeout.d.ts.map +1 -0
  227. package/dist/resolvers/adhocTimeout.js +12 -0
  228. package/dist/resolvers/adhocTimeout.js.map +1 -0
  229. package/dist/resolvers/agentVisionConsentAudit.d.ts +37 -0
  230. package/dist/resolvers/agentVisionConsentAudit.d.ts.map +1 -0
  231. package/dist/resolvers/agentVisionConsentAudit.js +63 -0
  232. package/dist/resolvers/agentVisionConsentAudit.js.map +1 -0
  233. package/dist/resolvers/avatarInputValidation.d.ts +40 -0
  234. package/dist/resolvers/avatarInputValidation.d.ts.map +1 -0
  235. package/dist/resolvers/avatarInputValidation.js +97 -0
  236. package/dist/resolvers/avatarInputValidation.js.map +1 -0
  237. package/dist/resolvers/currentUserRoles.d.ts +29 -0
  238. package/dist/resolvers/currentUserRoles.d.ts.map +1 -1
  239. package/dist/resolvers/currentUserRoles.js +30 -0
  240. package/dist/resolvers/currentUserRoles.js.map +1 -1
  241. package/dist/resolvers/meetings/index.d.ts +5 -0
  242. package/dist/resolvers/meetings/index.d.ts.map +1 -0
  243. package/dist/resolvers/meetings/index.js +5 -0
  244. package/dist/resolvers/meetings/index.js.map +1 -0
  245. package/dist/resolvers/meetings/meetingHelpers.d.ts +17 -0
  246. package/dist/resolvers/meetings/meetingHelpers.d.ts.map +1 -0
  247. package/dist/resolvers/meetings/meetingHelpers.js +212 -0
  248. package/dist/resolvers/meetings/meetingHelpers.js.map +1 -0
  249. package/dist/resolvers/meetings/meetingMutations.d.ts +18 -0
  250. package/dist/resolvers/meetings/meetingMutations.d.ts.map +1 -0
  251. package/dist/resolvers/meetings/meetingMutations.js +439 -0
  252. package/dist/resolvers/meetings/meetingMutations.js.map +1 -0
  253. package/dist/resolvers/meetings/meetingQueries.d.ts +7 -0
  254. package/dist/resolvers/meetings/meetingQueries.d.ts.map +1 -0
  255. package/dist/resolvers/meetings/meetingQueries.js +113 -0
  256. package/dist/resolvers/meetings/meetingQueries.js.map +1 -0
  257. package/dist/resolvers/meetings/meetingTypes.d.ts +89 -0
  258. package/dist/resolvers/meetings/meetingTypes.d.ts.map +1 -0
  259. package/dist/resolvers/meetings/meetingTypes.js +345 -0
  260. package/dist/resolvers/meetings/meetingTypes.js.map +1 -0
  261. package/dist/rest/ViewOperationsHandler.d.ts.map +1 -1
  262. package/dist/rest/ViewOperationsHandler.js +8 -5
  263. package/dist/rest/ViewOperationsHandler.js.map +1 -1
  264. package/dist/services/TaskGraphActionRunner.d.ts +8 -0
  265. package/dist/services/TaskGraphActionRunner.d.ts.map +1 -1
  266. package/dist/services/TaskGraphActionRunner.js +27 -1
  267. package/dist/services/TaskGraphActionRunner.js.map +1 -1
  268. package/dist/services/TaskGraphContinuationDeliverer.d.ts +4 -3
  269. package/dist/services/TaskGraphContinuationDeliverer.d.ts.map +1 -1
  270. package/dist/services/TaskGraphContinuationDeliverer.js +13 -7
  271. package/dist/services/TaskGraphContinuationDeliverer.js.map +1 -1
  272. package/dist/services/WorkQueueHostService.d.ts +36 -0
  273. package/dist/services/WorkQueueHostService.d.ts.map +1 -0
  274. package/dist/services/WorkQueueHostService.js +97 -0
  275. package/dist/services/WorkQueueHostService.js.map +1 -0
  276. package/dist/services/workQueueConfig.d.ts +103 -0
  277. package/dist/services/workQueueConfig.d.ts.map +1 -0
  278. package/dist/services/workQueueConfig.js +48 -0
  279. package/dist/services/workQueueConfig.js.map +1 -0
  280. package/dist/sharedCache.d.ts +105 -0
  281. package/dist/sharedCache.d.ts.map +1 -0
  282. package/dist/sharedCache.js +276 -0
  283. package/dist/sharedCache.js.map +1 -0
  284. package/dist/types.d.ts +11 -0
  285. package/dist/types.d.ts.map +1 -1
  286. package/dist/types.js.map +1 -1
  287. package/package.json +97 -94
  288. package/src/__tests__/ActionResolver.authorization.test.ts +353 -0
  289. package/src/__tests__/AdhocQueryResolver.bugs.test.ts +14 -35
  290. package/src/__tests__/AdhocQueryResolver.pagination.test.ts +6 -2
  291. package/src/__tests__/AdhocQueryResolver.scopeGuard.test.ts +3 -3
  292. package/src/__tests__/AdhocQueryResolver.sqlAuthorizer.test.ts +104 -0
  293. package/src/__tests__/BridgeHostInstance.test.ts +232 -0
  294. package/src/__tests__/ClientToolRequestResolver.test.ts +116 -0
  295. package/src/__tests__/EntityRecordNameResolver.fieldSecurity.test.ts +27 -72
  296. package/src/__tests__/FetchEntityVectorsResolver.test.ts +285 -0
  297. package/src/__tests__/HostInstance.test.ts +84 -0
  298. package/src/__tests__/HumanHandoffResolver.test.ts +250 -0
  299. package/src/__tests__/MeetingResolver.test.ts +448 -0
  300. package/src/__tests__/RealtimeBridgeResolver.test.ts +651 -4
  301. package/src/__tests__/RealtimeClientSessionResolver.configTamper.test.ts +383 -0
  302. package/src/__tests__/RealtimeClientSessionResolver.test.ts +682 -58
  303. package/src/__tests__/RealtimeProxyServer.test.ts +16 -1
  304. package/src/__tests__/RealtimeRelay.test.ts +876 -0
  305. package/src/__tests__/RemoteBrowserActionResolver.goalRunLink.test.ts +129 -0
  306. package/src/__tests__/ResolverBase.adhocReadScope.test.ts +96 -0
  307. package/src/__tests__/ResolverBase.aggregateScreen.test.ts +174 -0
  308. package/src/__tests__/ResolverBase.clientOldValues.test.ts +6 -3
  309. package/src/__tests__/ResolverBase.filterEscaping.test.ts +21 -2
  310. package/src/__tests__/ResolverBase.includeBinaryFields.test.ts +291 -0
  311. package/src/__tests__/ReturningVisitorRecap.test.ts +115 -0
  312. package/src/__tests__/RunAIAgentResolver.branch.test.ts +169 -0
  313. package/src/__tests__/RunAIAgentResolver.historyFrom.test.ts +4 -4
  314. package/src/__tests__/RunAIAgentResolver.stoppedPredecessor.test.ts +166 -0
  315. package/src/__tests__/RunTemplateResolver.test.ts +132 -0
  316. package/src/__tests__/RunViewResolver.cacheCheckForwarding.test.ts +63 -0
  317. package/src/__tests__/SessionJanitor.test.ts +845 -18
  318. package/src/__tests__/SessionManager.test.ts +213 -17
  319. package/src/__tests__/SessionRunIDVerifier.test.ts +251 -0
  320. package/src/__tests__/SyncRolesUsersResolver.userRoles.test.ts +97 -0
  321. package/src/__tests__/TaskGraphActionRunner.durablePayload.test.ts +41 -0
  322. package/src/__tests__/TaskGraphContinuationDeliverer.test.ts +50 -3
  323. package/src/__tests__/UserAvatarResolver.test.ts +255 -0
  324. package/src/__tests__/UserResolver.currentUser.test.ts +16 -1
  325. package/src/__tests__/WorkQueueHostService.test.ts +133 -0
  326. package/src/__tests__/actionAuthorization.test.ts +130 -0
  327. package/src/__tests__/adhocTimeoutClamp.test.ts +26 -0
  328. package/src/__tests__/agentVisionConsentAudit.test.ts +118 -0
  329. package/src/__tests__/avatarInputValidation.test.ts +160 -0
  330. package/src/__tests__/isolatedProviderTransactions.test.ts +169 -0
  331. package/src/__tests__/magicLinkService.consume.test.ts +6 -0
  332. package/src/__tests__/magicLinkService.provisionLog.test.ts +5 -0
  333. package/src/__tests__/newUsers.template.test.ts +151 -0
  334. package/src/__tests__/newUsers.test.ts +74 -5
  335. package/src/__tests__/orphanedConversationDetailReconciler.test.ts +17 -2
  336. package/src/__tests__/postgresqlPoolSettings.test.ts +131 -0
  337. package/src/__tests__/realtimeSessions/RealtimeSessionEventService.test.ts +201 -0
  338. package/src/__tests__/realtimeSessions/RealtimeSessionEventsResolver.test.ts +215 -0
  339. package/src/__tests__/realtimeSessions/RealtimeSessionVerificationResolver.test.ts +102 -0
  340. package/src/__tests__/realtimeSessions/RealtimeSessionVerificationService.test.ts +349 -0
  341. package/src/__tests__/realtimeSessions/RealtimeVerifyRouter.test.ts +131 -0
  342. package/src/__tests__/realtimeSessions/SlidingWindowRateLimiter.test.ts +56 -0
  343. package/src/__tests__/realtimeSessions/helpers.ts +4 -0
  344. package/src/__tests__/realtimeSessions/sessionAccess.test.ts +129 -0
  345. package/src/__tests__/realtimeSessions/sessionConfigStamps.test.ts +124 -0
  346. package/src/__tests__/realtimeSessions/verificationCore.test.ts +355 -0
  347. package/src/__tests__/realtimeSessions/verificationPages.test.ts +84 -0
  348. package/src/__tests__/realtimeSessions/verificationPolicy.test.ts +226 -0
  349. package/src/__tests__/realtimeSessions/verificationWorkflow.test.ts +633 -0
  350. package/src/__tests__/resolverBase.fls.test.ts +87 -4
  351. package/src/__tests__/serverExtensionReservedRoots.test.ts +2 -0
  352. package/src/__tests__/sharedCache.test.ts +310 -0
  353. package/src/__tests__/translateBracketsToPG.test.ts +20 -0
  354. package/src/__tests__/widgetGuestElevation.test.ts +44 -0
  355. package/src/agentSessions/BridgeHostInstance.ts +32 -0
  356. package/src/agentSessions/HostInstance.ts +124 -28
  357. package/src/agentSessions/ReturningVisitorRecap.ts +7 -3
  358. package/src/agentSessions/SessionJanitor.ts +543 -57
  359. package/src/agentSessions/SessionManager.ts +125 -25
  360. package/src/agentSessions/SessionRunIDVerifier.ts +171 -0
  361. package/src/agentSessions/index.ts +10 -3
  362. package/src/auth/actionAuthorization.ts +116 -0
  363. package/src/auth/index.ts +3 -8
  364. package/src/auth/magicLink/MagicLinkService.ts +6 -3
  365. package/src/auth/newUsers.ts +99 -11
  366. package/src/config.ts +255 -3
  367. package/src/context.ts +26 -34
  368. package/src/generated/graphql-schemas/__mj.ts +22701 -18838
  369. package/src/generic/OrphanedConversationDetailReconciler.ts +3 -3
  370. package/src/generic/ResolverBase.ts +210 -36
  371. package/src/generic/RunViewResolver.ts +37 -2
  372. package/src/index.ts +236 -170
  373. package/src/isolatedProvider.ts +22 -0
  374. package/src/postgresqlCompat.ts +21 -0
  375. package/src/postgresqlPoolSettings.ts +213 -0
  376. package/src/realtimeProxy/RealtimeProxyServer.ts +148 -16
  377. package/src/realtimeProxy/RealtimeRelayTunnel.ts +645 -0
  378. package/src/realtimeSessions/README.md +179 -0
  379. package/src/realtimeSessions/RealtimeSessionEventService.ts +274 -0
  380. package/src/realtimeSessions/RealtimeSessionVerificationService.ts +167 -0
  381. package/src/realtimeSessions/RealtimeVerifyRouter.ts +108 -0
  382. package/src/realtimeSessions/SessionVerificationPortsBase.ts +189 -0
  383. package/src/realtimeSessions/SlidingWindowRateLimiter.ts +92 -0
  384. package/src/realtimeSessions/index.ts +16 -0
  385. package/src/realtimeSessions/sessionAccess.ts +141 -0
  386. package/src/realtimeSessions/sessionConfigStamps.ts +123 -0
  387. package/src/realtimeSessions/subscriptionAuthorization.ts +116 -0
  388. package/src/realtimeSessions/verificationCore.ts +502 -0
  389. package/src/realtimeSessions/verificationPages.ts +149 -0
  390. package/src/realtimeSessions/verificationPolicy.ts +321 -0
  391. package/src/realtimeSessions/verificationWorkflow.ts +702 -0
  392. package/src/realtimeWidget/widgetGuestElevation.ts +34 -2
  393. package/src/resolvers/ActionResolver.ts +84 -21
  394. package/src/resolvers/AdhocQueryResolver.ts +105 -187
  395. package/src/resolvers/CacheStatsResolver.ts +89 -2
  396. package/src/resolvers/ClientToolRequestResolver.ts +25 -7
  397. package/src/resolvers/EntityRecordNameResolver.ts +9 -51
  398. package/src/resolvers/FetchEntityVectorsResolver.ts +97 -2
  399. package/src/resolvers/GetDataResolver.ts +10 -0
  400. package/src/resolvers/HumanHandoffResolver.ts +312 -0
  401. package/src/resolvers/MCPResolver.ts +8 -5
  402. package/src/resolvers/MeetingResolver.ts +438 -0
  403. package/src/resolvers/RealtimeBridgeResolver.ts +385 -19
  404. package/src/resolvers/RealtimeClientSessionResolver.ts +503 -344
  405. package/src/resolvers/RealtimeSessionEventsResolver.ts +133 -0
  406. package/src/resolvers/RealtimeSessionVerificationResolver.ts +174 -0
  407. package/src/resolvers/RemoteBrowserActionResolver.ts +11 -1
  408. package/src/resolvers/RunAIAgentResolver.ts +78 -11
  409. package/src/resolvers/RunTemplateResolver.ts +13 -12
  410. package/src/resolvers/SyncDataResolver.ts +6 -2
  411. package/src/resolvers/SyncRolesUsersResolver.ts +24 -6
  412. package/src/resolvers/UserAvatarResolver.ts +111 -0
  413. package/src/resolvers/UserResolver.ts +2 -16
  414. package/src/resolvers/adhocTimeout.ts +12 -0
  415. package/src/resolvers/agentVisionConsentAudit.ts +82 -0
  416. package/src/resolvers/avatarInputValidation.ts +123 -0
  417. package/src/resolvers/currentUserRoles.ts +32 -1
  418. package/src/resolvers/meetings/index.ts +4 -0
  419. package/src/resolvers/meetings/meetingHelpers.ts +307 -0
  420. package/src/resolvers/meetings/meetingMutations.ts +556 -0
  421. package/src/resolvers/meetings/meetingQueries.ts +172 -0
  422. package/src/resolvers/meetings/meetingTypes.ts +238 -0
  423. package/src/rest/ViewOperationsHandler.ts +8 -5
  424. package/src/services/TaskGraphActionRunner.ts +29 -1
  425. package/src/services/TaskGraphContinuationDeliverer.ts +13 -7
  426. package/src/services/WorkQueueHostService.ts +129 -0
  427. package/src/services/workQueueConfig.ts +52 -0
  428. package/src/sharedCache.ts +302 -0
  429. package/src/types.ts +12 -0
  430. package/dist/resolvers/adhoc-query-helpers.d.ts +0 -33
  431. package/dist/resolvers/adhoc-query-helpers.d.ts.map +0 -1
  432. package/dist/resolvers/adhoc-query-helpers.js +0 -50
  433. package/dist/resolvers/adhoc-query-helpers.js.map +0 -1
  434. package/src/__tests__/adhoc-query-helpers.test.ts +0 -50
  435. package/src/resolvers/adhoc-query-helpers.ts +0 -67
@@ -0,0 +1,116 @@
1
+ /**
2
+ * @fileoverview The authorization of the `RealtimeSessionEvents` subscription, free of GraphQL schema
3
+ * decorators and server configuration so it can be exercised — and reused — without loading either.
4
+ *
5
+ * It is applied twice on purpose (see `RealtimeSessionEventsResolver`): once when the subscription is
6
+ * opened ({@link AuthorizeRealtimeSessionSubscription}) and once for every event
7
+ * ({@link RealtimeSessionEventsFilter}). In neither check does a client-supplied value decide who
8
+ * receives anything: `agentSessionId` only selects WHICH of the caller's own sessions to listen to; the
9
+ * identity it is checked against comes from the server-authenticated connection context.
10
+ *
11
+ * @module @memberjunction/server/realtimeSessions
12
+ */
13
+
14
+ import { IMetadataProvider, LogError, Metadata, UserInfo } from '@memberjunction/core';
15
+ import { IsValidUUID, UUIDsEqual } from '@memberjunction/global';
16
+ import type { UserPayload } from '../types.js';
17
+ import { AuthorizeSessionAccess, LoadSessionForAccess, type LoadedSessionForAccess } from './sessionAccess.js';
18
+ import type { RealtimeSessionEventTopicPayload } from './RealtimeSessionEventService.js';
19
+
20
+ /**
21
+ * Arguments of the subscription. `agentSessionId` is camelCase because it is the GraphQL argument name
22
+ * graphql-js hands to `subscribe` and to the filter — it is the wire contract, not an API of ours.
23
+ */
24
+ export interface RealtimeSessionEventsArgs {
25
+ agentSessionId?: string;
26
+ }
27
+
28
+ /**
29
+ * The slice of the WebSocket connection context the authorization reads. `userPayload` is camelCase to
30
+ * match `AppContext.userPayload`, the member graphql-ws populates and every resolver already destructures.
31
+ */
32
+ export interface RealtimeSessionEventsContext {
33
+ userPayload?: UserPayload;
34
+ }
35
+
36
+ /** Why a subscription request was refused (logged; the client always gets the same message). */
37
+ const REFUSAL_MESSAGE = 'Realtime session not found.';
38
+
39
+ /** The principal the connection authenticated as, or undefined. */
40
+ function connectionPrincipal(context: RealtimeSessionEventsContext | undefined): UserInfo | undefined {
41
+ return context?.userPayload?.userRecord as UserInfo | undefined;
42
+ }
43
+
44
+ /**
45
+ * Per-event filter. Exported because it is the security-critical gate and must be testable in isolation.
46
+ *
47
+ * An event reaches a subscriber only when BOTH hold:
48
+ * 1. it is addressed to the session the subscriber asked for, and
49
+ * 2. the event's owner/scope snapshot passes {@link AuthorizeSessionAccess} for the connection's
50
+ * authenticated principal.
51
+ *
52
+ * Fails closed: a missing connection identity, a missing owner on the event, or a missing argument never matches.
53
+ */
54
+ export function RealtimeSessionEventsFilter(data: {
55
+ payload: RealtimeSessionEventTopicPayload;
56
+ args: RealtimeSessionEventsArgs;
57
+ context: RealtimeSessionEventsContext | undefined;
58
+ }): boolean {
59
+ const { payload, args, context } = data;
60
+ if (typeof args?.agentSessionId !== 'string' || !payload?.AgentSessionID || !UUIDsEqual(payload.AgentSessionID, args.agentSessionId)) {
61
+ return false;
62
+ }
63
+ const decision = AuthorizeSessionAccess(
64
+ { SessionUserID: payload.OwnerUserID, ConversationExternalID: payload.ScopeKey },
65
+ connectionPrincipal(context),
66
+ );
67
+ return decision.Allowed;
68
+ }
69
+
70
+ /** Dependencies of {@link AuthorizeRealtimeSessionSubscription}, injectable for tests. */
71
+ export interface SubscriptionAuthorizationDeps {
72
+ /** Loads a session under the caller's identity. */
73
+ LoadSession: (agentSessionId: string, user: UserInfo, provider: IMetadataProvider) => Promise<LoadedSessionForAccess | null>;
74
+ /** The provider to read with (a WebSocket context carries no request-scoped providers). */
75
+ GetProvider: () => IMetadataProvider;
76
+ }
77
+
78
+ /** Production dependencies. */
79
+ export const DEFAULT_SUBSCRIPTION_AUTHORIZATION_DEPS: SubscriptionAuthorizationDeps = {
80
+ LoadSession: LoadSessionForAccess,
81
+ GetProvider: () => Metadata.Provider, // global-provider-ok: a WebSocket subscription context has no per-request providers
82
+ };
83
+
84
+ /**
85
+ * Subscribe-time authorization: loads the session as the connection's principal and requires
86
+ * {@link AuthorizeSessionAccess} to allow it. Throws {@link REFUSAL_MESSAGE} (uniformly) otherwise.
87
+ *
88
+ * @param args - the subscription arguments
89
+ * @param context - the connection context (server-authenticated)
90
+ * @param deps - injectable dependencies (tests)
91
+ * @returns the validated session id
92
+ */
93
+ export async function AuthorizeRealtimeSessionSubscription(
94
+ args: RealtimeSessionEventsArgs,
95
+ context: RealtimeSessionEventsContext | undefined,
96
+ deps: SubscriptionAuthorizationDeps = DEFAULT_SUBSCRIPTION_AUTHORIZATION_DEPS,
97
+ ): Promise<string> {
98
+ const principal = connectionPrincipal(context);
99
+ const agentSessionId = args?.agentSessionId;
100
+ if (!principal || typeof agentSessionId !== 'string' || !IsValidUUID(agentSessionId)) {
101
+ throw new Error(REFUSAL_MESSAGE);
102
+ }
103
+ const loaded = await deps.LoadSession(agentSessionId, principal, deps.GetProvider());
104
+ if (!loaded) {
105
+ throw new Error(REFUSAL_MESSAGE);
106
+ }
107
+ const decision = AuthorizeSessionAccess(
108
+ { SessionUserID: loaded.Session.UserID, ConversationExternalID: loaded.ConversationExternalID },
109
+ principal,
110
+ );
111
+ if (decision.Allowed === false) {
112
+ LogError(`RealtimeSessionEvents: refused subscription by user ${principal.ID} to session ${agentSessionId} (${decision.Reason}).`);
113
+ throw new Error(REFUSAL_MESSAGE);
114
+ }
115
+ return agentSessionId;
116
+ }
@@ -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
+ }