@memberjunction/server 6.2.0-edge.6 → 6.2.0-edge.7

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 (407) hide show
  1. package/README.md +69 -0
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/agentSessions/BridgeHostInstance.d.ts +8 -3
  4. package/dist/agentSessions/BridgeHostInstance.d.ts.map +1 -1
  5. package/dist/agentSessions/BridgeHostInstance.js +14 -4
  6. package/dist/agentSessions/BridgeHostInstance.js.map +1 -1
  7. package/dist/agentSessions/HandoffOfferExpiry.d.ts +10 -0
  8. package/dist/agentSessions/HandoffOfferExpiry.d.ts.map +1 -0
  9. package/dist/agentSessions/HandoffOfferExpiry.js +18 -0
  10. package/dist/agentSessions/HandoffOfferExpiry.js.map +1 -0
  11. package/dist/agentSessions/ReturningVisitorRecap.js +1 -0
  12. package/dist/agentSessions/ReturningVisitorRecap.js.map +1 -1
  13. package/dist/agentSessions/SessionJanitor.d.ts +31 -8
  14. package/dist/agentSessions/SessionJanitor.d.ts.map +1 -1
  15. package/dist/agentSessions/SessionJanitor.js +85 -41
  16. package/dist/agentSessions/SessionJanitor.js.map +1 -1
  17. package/dist/agentSessions/SessionManager.d.ts +14 -4
  18. package/dist/agentSessions/SessionManager.d.ts.map +1 -1
  19. package/dist/agentSessions/SessionManager.js +28 -4
  20. package/dist/agentSessions/SessionManager.js.map +1 -1
  21. package/dist/agentSessions/conversationBrowsers.d.ts +61 -0
  22. package/dist/agentSessions/conversationBrowsers.d.ts.map +1 -0
  23. package/dist/agentSessions/conversationBrowsers.js +118 -0
  24. package/dist/agentSessions/conversationBrowsers.js.map +1 -0
  25. package/dist/agentSessions/sessionDeadline.d.ts +43 -0
  26. package/dist/agentSessions/sessionDeadline.d.ts.map +1 -0
  27. package/dist/agentSessions/sessionDeadline.js +68 -0
  28. package/dist/agentSessions/sessionDeadline.js.map +1 -0
  29. package/dist/auth/APIKeyScopeAuth.d.ts.map +1 -1
  30. package/dist/auth/APIKeyScopeAuth.js +5 -4
  31. package/dist/auth/APIKeyScopeAuth.js.map +1 -1
  32. package/dist/auth/loginEmailResolution.d.ts +41 -0
  33. package/dist/auth/loginEmailResolution.d.ts.map +1 -0
  34. package/dist/auth/loginEmailResolution.js +47 -0
  35. package/dist/auth/loginEmailResolution.js.map +1 -0
  36. package/dist/auth/magicLink/MagicLinkKeys.d.ts +50 -2
  37. package/dist/auth/magicLink/MagicLinkKeys.d.ts.map +1 -1
  38. package/dist/auth/magicLink/MagicLinkKeys.js +129 -12
  39. package/dist/auth/magicLink/MagicLinkKeys.js.map +1 -1
  40. package/dist/auth/magicLink/MagicLinkRouter.d.ts +3 -0
  41. package/dist/auth/magicLink/MagicLinkRouter.d.ts.map +1 -1
  42. package/dist/auth/magicLink/MagicLinkRouter.js +23 -5
  43. package/dist/auth/magicLink/MagicLinkRouter.js.map +1 -1
  44. package/dist/auth/magicLink/MagicLinkService.d.ts +39 -5
  45. package/dist/auth/magicLink/MagicLinkService.d.ts.map +1 -1
  46. package/dist/auth/magicLink/MagicLinkService.js +221 -9
  47. package/dist/auth/magicLink/MagicLinkService.js.map +1 -1
  48. package/dist/auth/magicLink/index.d.ts +1 -1
  49. package/dist/auth/magicLink/index.d.ts.map +1 -1
  50. package/dist/auth/magicLink/index.js +1 -1
  51. package/dist/auth/magicLink/index.js.map +1 -1
  52. package/dist/auth/magicLink/redeemLanding.d.ts +2 -2
  53. package/dist/auth/magicLink/redeemLanding.d.ts.map +1 -1
  54. package/dist/auth/magicLink/redeemLanding.js +7 -4
  55. package/dist/auth/magicLink/redeemLanding.js.map +1 -1
  56. package/dist/auth/magicLink/types.d.ts +27 -3
  57. package/dist/auth/magicLink/types.d.ts.map +1 -1
  58. package/dist/auth/magicLink/types.js +0 -4
  59. package/dist/auth/magicLink/types.js.map +1 -1
  60. package/dist/auth/newUsers.d.ts.map +1 -1
  61. package/dist/auth/newUsers.js +1 -0
  62. package/dist/auth/newUsers.js.map +1 -1
  63. package/dist/backgroundSweepConfigUnits.d.ts +231 -0
  64. package/dist/backgroundSweepConfigUnits.d.ts.map +1 -0
  65. package/dist/backgroundSweepConfigUnits.js +180 -0
  66. package/dist/backgroundSweepConfigUnits.js.map +1 -0
  67. package/dist/config.d.ts +906 -8
  68. package/dist/config.d.ts.map +1 -1
  69. package/dist/config.js +59 -0
  70. package/dist/config.js.map +1 -1
  71. package/dist/context.d.ts +2 -0
  72. package/dist/context.d.ts.map +1 -1
  73. package/dist/context.js +35 -4
  74. package/dist/context.js.map +1 -1
  75. package/dist/coreRoutes.d.ts +3 -1
  76. package/dist/coreRoutes.d.ts.map +1 -1
  77. package/dist/coreRoutes.js +6 -4
  78. package/dist/coreRoutes.js.map +1 -1
  79. package/dist/generated/graphql-schemas/__mj.d.ts +228 -0
  80. package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
  81. package/dist/generated/graphql-schemas/__mj.js +1163 -6
  82. package/dist/generated/graphql-schemas/__mj.js.map +1 -1
  83. package/dist/generic/OrphanedConversationDetailReconciler.d.ts +11 -0
  84. package/dist/generic/OrphanedConversationDetailReconciler.d.ts.map +1 -1
  85. package/dist/generic/OrphanedConversationDetailReconciler.js +27 -1
  86. package/dist/generic/OrphanedConversationDetailReconciler.js.map +1 -1
  87. package/dist/generic/ResolverBase.d.ts +50 -5
  88. package/dist/generic/ResolverBase.d.ts.map +1 -1
  89. package/dist/generic/ResolverBase.js +321 -132
  90. package/dist/generic/ResolverBase.js.map +1 -1
  91. package/dist/generic/RunViewResolver.d.ts +4 -0
  92. package/dist/generic/RunViewResolver.d.ts.map +1 -1
  93. package/dist/generic/RunViewResolver.js +47 -7
  94. package/dist/generic/RunViewResolver.js.map +1 -1
  95. package/dist/index.d.ts +1 -0
  96. package/dist/index.d.ts.map +1 -1
  97. package/dist/index.js +157 -35
  98. package/dist/index.js.map +1 -1
  99. package/dist/integration/ConnectionPause.d.ts +121 -0
  100. package/dist/integration/ConnectionPause.d.ts.map +1 -0
  101. package/dist/integration/ConnectionPause.js +183 -0
  102. package/dist/integration/ConnectionPause.js.map +1 -0
  103. package/dist/integration/CustomColumnPromoter.d.ts +72 -6
  104. package/dist/integration/CustomColumnPromoter.d.ts.map +1 -1
  105. package/dist/integration/CustomColumnPromoter.js +87 -46
  106. package/dist/integration/CustomColumnPromoter.js.map +1 -1
  107. package/dist/integration/EntityMapLifecycle.d.ts +78 -4
  108. package/dist/integration/EntityMapLifecycle.d.ts.map +1 -1
  109. package/dist/integration/EntityMapLifecycle.js +199 -5
  110. package/dist/integration/EntityMapLifecycle.js.map +1 -1
  111. package/dist/integration/InactiveRowWarnings.d.ts +31 -0
  112. package/dist/integration/InactiveRowWarnings.d.ts.map +1 -1
  113. package/dist/integration/InactiveRowWarnings.js +29 -0
  114. package/dist/integration/InactiveRowWarnings.js.map +1 -1
  115. package/dist/integration/ObjectNamesByID.d.ts +28 -0
  116. package/dist/integration/ObjectNamesByID.d.ts.map +1 -0
  117. package/dist/integration/ObjectNamesByID.js +87 -0
  118. package/dist/integration/ObjectNamesByID.js.map +1 -0
  119. package/dist/integration/PostRestartIntrospect.d.ts +26 -0
  120. package/dist/integration/PostRestartIntrospect.d.ts.map +1 -0
  121. package/dist/integration/PostRestartIntrospect.js +27 -0
  122. package/dist/integration/PostRestartIntrospect.js.map +1 -0
  123. package/dist/integration/RSUPostRestartProgress.d.ts +176 -0
  124. package/dist/integration/RSUPostRestartProgress.d.ts.map +1 -0
  125. package/dist/integration/RSUPostRestartProgress.js +323 -0
  126. package/dist/integration/RSUPostRestartProgress.js.map +1 -0
  127. package/dist/integration/RSUProgressBridge.d.ts +41 -2
  128. package/dist/integration/RSUProgressBridge.d.ts.map +1 -1
  129. package/dist/integration/RSUProgressBridge.js +84 -1
  130. package/dist/integration/RSUProgressBridge.js.map +1 -1
  131. package/dist/integration/RunArtifactAuthorization.d.ts +39 -0
  132. package/dist/integration/RunArtifactAuthorization.d.ts.map +1 -0
  133. package/dist/integration/RunArtifactAuthorization.js +45 -0
  134. package/dist/integration/RunArtifactAuthorization.js.map +1 -0
  135. package/dist/integration/SchemaRefreshLaunch.d.ts +9 -0
  136. package/dist/integration/SchemaRefreshLaunch.d.ts.map +1 -1
  137. package/dist/integration/SchemaRefreshLaunch.js +3 -2
  138. package/dist/integration/SchemaRefreshLaunch.js.map +1 -1
  139. package/dist/integration/ScopedFieldsForBuild.d.ts +46 -0
  140. package/dist/integration/ScopedFieldsForBuild.d.ts.map +1 -0
  141. package/dist/integration/ScopedFieldsForBuild.js +91 -0
  142. package/dist/integration/ScopedFieldsForBuild.js.map +1 -0
  143. package/dist/integration/TargetConfigGuard.d.ts +40 -0
  144. package/dist/integration/TargetConfigGuard.d.ts.map +1 -0
  145. package/dist/integration/TargetConfigGuard.js +40 -0
  146. package/dist/integration/TargetConfigGuard.js.map +1 -0
  147. package/dist/realtimeSessions/ownedRealtimeSession.d.ts.map +1 -1
  148. package/dist/realtimeSessions/ownedRealtimeSession.js +2 -3
  149. package/dist/realtimeSessions/ownedRealtimeSession.js.map +1 -1
  150. package/dist/realtimeSessions/sessionAccess.d.ts +15 -0
  151. package/dist/realtimeSessions/sessionAccess.d.ts.map +1 -1
  152. package/dist/realtimeSessions/sessionAccess.js +22 -0
  153. package/dist/realtimeSessions/sessionAccess.js.map +1 -1
  154. package/dist/realtimeWidget/WidgetSessionService.d.ts.map +1 -1
  155. package/dist/realtimeWidget/WidgetSessionService.js +2 -0
  156. package/dist/realtimeWidget/WidgetSessionService.js.map +1 -1
  157. package/dist/realtimeWidget/visitorIdentity.d.ts.map +1 -1
  158. package/dist/realtimeWidget/visitorIdentity.js +1 -0
  159. package/dist/realtimeWidget/visitorIdentity.js.map +1 -1
  160. package/dist/realtimeWidget/widgetCore.d.ts.map +1 -1
  161. package/dist/realtimeWidget/widgetCore.js +4 -7
  162. package/dist/realtimeWidget/widgetCore.js.map +1 -1
  163. package/dist/realtimeWidget/widgetGuestElevation.js +1 -0
  164. package/dist/realtimeWidget/widgetGuestElevation.js.map +1 -1
  165. package/dist/requestOrigin.d.ts +22 -0
  166. package/dist/requestOrigin.d.ts.map +1 -0
  167. package/dist/requestOrigin.js +60 -0
  168. package/dist/requestOrigin.js.map +1 -0
  169. package/dist/resolvers/AgentSessionResolver.d.ts.map +1 -1
  170. package/dist/resolvers/AgentSessionResolver.js +6 -2
  171. package/dist/resolvers/AgentSessionResolver.js.map +1 -1
  172. package/dist/resolvers/AutotagPipelineResolver.d.ts.map +1 -1
  173. package/dist/resolvers/AutotagPipelineResolver.js +2 -1
  174. package/dist/resolvers/AutotagPipelineResolver.js.map +1 -1
  175. package/dist/resolvers/ConversationRunEventsResolver.d.ts.map +1 -1
  176. package/dist/resolvers/ConversationRunEventsResolver.js +2 -0
  177. package/dist/resolvers/ConversationRunEventsResolver.js.map +1 -1
  178. package/dist/resolvers/ExecuteRemoteOperationResolver.d.ts +6 -0
  179. package/dist/resolvers/ExecuteRemoteOperationResolver.d.ts.map +1 -1
  180. package/dist/resolvers/ExecuteRemoteOperationResolver.js +30 -7
  181. package/dist/resolvers/ExecuteRemoteOperationResolver.js.map +1 -1
  182. package/dist/resolvers/GetDataResolver.d.ts.map +1 -1
  183. package/dist/resolvers/GetDataResolver.js +34 -13
  184. package/dist/resolvers/GetDataResolver.js.map +1 -1
  185. package/dist/resolvers/IntegrationDiscoveryResolver.d.ts +401 -11
  186. package/dist/resolvers/IntegrationDiscoveryResolver.d.ts.map +1 -1
  187. package/dist/resolvers/IntegrationDiscoveryResolver.js +2690 -1305
  188. package/dist/resolvers/IntegrationDiscoveryResolver.js.map +1 -1
  189. package/dist/resolvers/MCPResolver.d.ts.map +1 -1
  190. package/dist/resolvers/MCPResolver.js +13 -2
  191. package/dist/resolvers/MCPResolver.js.map +1 -1
  192. package/dist/resolvers/QuerySystemUserResolver.d.ts +36 -0
  193. package/dist/resolvers/QuerySystemUserResolver.d.ts.map +1 -1
  194. package/dist/resolvers/QuerySystemUserResolver.js +174 -87
  195. package/dist/resolvers/QuerySystemUserResolver.js.map +1 -1
  196. package/dist/resolvers/RealtimeBridgeResolver.d.ts.map +1 -1
  197. package/dist/resolvers/RealtimeBridgeResolver.js +1 -0
  198. package/dist/resolvers/RealtimeBridgeResolver.js.map +1 -1
  199. package/dist/resolvers/RealtimeClientSessionResolver.d.ts +6 -0
  200. package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
  201. package/dist/resolvers/RealtimeClientSessionResolver.js +36 -5
  202. package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
  203. package/dist/resolvers/RemoteBrowserActionResolver.d.ts +62 -10
  204. package/dist/resolvers/RemoteBrowserActionResolver.d.ts.map +1 -1
  205. package/dist/resolvers/RemoteBrowserActionResolver.js +217 -34
  206. package/dist/resolvers/RemoteBrowserActionResolver.js.map +1 -1
  207. package/dist/resolvers/RunAIAgentResolver.d.ts +24 -8
  208. package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
  209. package/dist/resolvers/RunAIAgentResolver.js +185 -67
  210. package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
  211. package/dist/resolvers/RunAIPromptResolver.d.ts +5 -3
  212. package/dist/resolvers/RunAIPromptResolver.d.ts.map +1 -1
  213. package/dist/resolvers/RunAIPromptResolver.js +19 -18
  214. package/dist/resolvers/RunAIPromptResolver.js.map +1 -1
  215. package/dist/resolvers/RunTemplateResolver.d.ts.map +1 -1
  216. package/dist/resolvers/RunTemplateResolver.js +3 -1
  217. package/dist/resolvers/RunTemplateResolver.js.map +1 -1
  218. package/dist/resolvers/meetingRecordingRegistration.d.ts.map +1 -1
  219. package/dist/resolvers/meetingRecordingRegistration.js +2 -0
  220. package/dist/resolvers/meetingRecordingRegistration.js.map +1 -1
  221. package/dist/rest/MediaAccessKeys.d.ts.map +1 -1
  222. package/dist/rest/MediaAccessKeys.js +6 -2
  223. package/dist/rest/MediaAccessKeys.js.map +1 -1
  224. package/dist/rest/OAuthCallbackHandler.d.ts.map +1 -1
  225. package/dist/rest/OAuthCallbackHandler.js +11 -5
  226. package/dist/rest/OAuthCallbackHandler.js.map +1 -1
  227. package/dist/rest/RealtimeRecordingHandler.d.ts +3 -3
  228. package/dist/rest/RealtimeRecordingHandler.d.ts.map +1 -1
  229. package/dist/rest/RealtimeRecordingHandler.js +2 -4
  230. package/dist/rest/RealtimeRecordingHandler.js.map +1 -1
  231. package/dist/rest/ViewOperationsHandler.d.ts.map +1 -1
  232. package/dist/rest/ViewOperationsHandler.js +2 -1
  233. package/dist/rest/ViewOperationsHandler.js.map +1 -1
  234. package/dist/rest/setupRESTEndpoints.d.ts +3 -1
  235. package/dist/rest/setupRESTEndpoints.d.ts.map +1 -1
  236. package/dist/rest/setupRESTEndpoints.js +11 -2
  237. package/dist/rest/setupRESTEndpoints.js.map +1 -1
  238. package/dist/services/DurableEntityActionTaskSubmitter.d.ts.map +1 -1
  239. package/dist/services/DurableEntityActionTaskSubmitter.js +6 -1
  240. package/dist/services/DurableEntityActionTaskSubmitter.js.map +1 -1
  241. package/dist/services/TaskGraphActionRunner.d.ts.map +1 -1
  242. package/dist/services/TaskGraphActionRunner.js +2 -26
  243. package/dist/services/TaskGraphActionRunner.js.map +1 -1
  244. package/dist/services/TaskGraphAgentRunner.d.ts +6 -0
  245. package/dist/services/TaskGraphAgentRunner.d.ts.map +1 -1
  246. package/dist/services/TaskGraphAgentRunner.js +38 -0
  247. package/dist/services/TaskGraphAgentRunner.js.map +1 -1
  248. package/dist/services/TaskGraphContinuationDeliverer.d.ts.map +1 -1
  249. package/dist/services/TaskGraphContinuationDeliverer.js +1 -0
  250. package/dist/services/TaskGraphContinuationDeliverer.js.map +1 -1
  251. package/dist/services/WorkQueueHostService.d.ts +2 -1
  252. package/dist/services/WorkQueueHostService.d.ts.map +1 -1
  253. package/dist/services/WorkQueueHostService.js +19 -4
  254. package/dist/services/WorkQueueHostService.js.map +1 -1
  255. package/dist/services/workQueueConfig.d.ts +2 -2
  256. package/dist/services/workQueueConfig.js +5 -5
  257. package/dist/services/workQueueConfig.js.map +1 -1
  258. package/dist/sharedCache.js +1 -1
  259. package/dist/sharedCache.js.map +1 -1
  260. package/dist/types.d.ts +17 -0
  261. package/dist/types.d.ts.map +1 -1
  262. package/dist/types.js.map +1 -1
  263. package/package.json +98 -98
  264. package/src/__tests__/APIKeyScopeAuth.test.ts +4 -8
  265. package/src/__tests__/ActiveOperations.test.ts +68 -0
  266. package/src/__tests__/AgentSessionResolver.ownership.test.ts +13 -0
  267. package/src/__tests__/AssertTargetConfigs.test.ts +75 -0
  268. package/src/__tests__/BatchApplyParity.test.ts +109 -0
  269. package/src/__tests__/BridgeHostInstance.test.ts +42 -0
  270. package/src/__tests__/CollectInactiveRowWarnings.test.ts +115 -0
  271. package/src/__tests__/ConnectionPause.test.ts +192 -0
  272. package/src/__tests__/ConnectionPauseResolver.test.ts +166 -0
  273. package/src/__tests__/ConnectionPauseSave.test.ts +143 -0
  274. package/src/__tests__/CustomColumnPromoterCompletion.test.ts +142 -0
  275. package/src/__tests__/DeleteConnectionRemovesPerConnectionCatalog.test.ts +51 -0
  276. package/src/__tests__/DetachedDiscoveryStart.test.ts +65 -0
  277. package/src/__tests__/DurableEntityActionTaskSubmitter.test.ts +73 -0
  278. package/src/__tests__/EntityMapLifecycle.rekey.test.ts +252 -0
  279. package/src/__tests__/ExecuteRemoteOperationResolver.test.ts +213 -0
  280. package/src/__tests__/HandoffOfferExpiry.test.ts +40 -0
  281. package/src/__tests__/IntegrationRunArtifactAuthorization.test.ts +126 -0
  282. package/src/__tests__/MagicLinkKeys.test.ts +102 -0
  283. package/src/__tests__/MediaAccessKeys.test.ts +57 -10
  284. package/src/__tests__/NewArrivalsDefaultDisabled.test.ts +50 -0
  285. package/src/__tests__/ObjectNamesByID.test.ts +176 -0
  286. package/src/__tests__/PostRestartMapsInCatalogScope.test.ts +146 -0
  287. package/src/__tests__/QuerySystemUserResolver.childRows.test.ts +80 -0
  288. package/src/__tests__/QuerySystemUserResolver.collision.test.ts +200 -0
  289. package/src/__tests__/QuerySystemUserResolver.permissions.test.ts +132 -0
  290. package/src/__tests__/RSUPostRestartProgress.test.ts +343 -0
  291. package/src/__tests__/RSUProgressBridge.test.ts +188 -5
  292. package/src/__tests__/RSUStartupSweepWiring.test.ts +30 -0
  293. package/src/__tests__/ReactivateRefreshMessage.test.ts +10 -3
  294. package/src/__tests__/RealtimeClientSessionResolver.test.ts +22 -0
  295. package/src/__tests__/{RefreshAdoptsNewColumns.test.ts → RefreshReportsNewColumns.test.ts} +25 -16
  296. package/src/__tests__/RemoteBrowserActionRecovery.test.ts +287 -0
  297. package/src/__tests__/RemoteBrowserActionResolver.goalRunLink.test.ts +3 -0
  298. package/src/__tests__/RemoteBrowserActionResolver.ownership.test.ts +6 -4
  299. package/src/__tests__/RemoteBrowserAudioStream.test.ts +3 -0
  300. package/src/__tests__/RemoteBrowserSnapshot.test.ts +3 -0
  301. package/src/__tests__/ResolverBase.clientOldValues.test.ts +323 -26
  302. package/src/__tests__/ResolverBase.encryptedApiZeroReencrypt.test.ts +307 -0
  303. package/src/__tests__/ResolverBase.entityMessageRelay.test.ts +53 -2
  304. package/src/__tests__/ResolverBase.includeBinaryFields.test.ts +60 -0
  305. package/src/__tests__/RunAIAgentResolver.branch.test.ts +4 -0
  306. package/src/__tests__/RunAIAgentResolver.consultPredecessor.test.ts +289 -0
  307. package/src/__tests__/RunAIAgentResolver.handoff.test.ts +649 -0
  308. package/src/__tests__/RunAIAgentResolver.historyFrom.test.ts +2 -0
  309. package/src/__tests__/RunAIAgentResolver.runDataGuard.test.ts +2 -0
  310. package/src/__tests__/RunAIAgentResolver.stoppedPredecessor.test.ts +87 -12
  311. package/src/__tests__/SchemaEvolutionCustomColumns.test.ts +119 -0
  312. package/src/__tests__/SchemaRefreshLaunch.test.ts +24 -2
  313. package/src/__tests__/ScopedFieldsForBuild.test.ts +192 -0
  314. package/src/__tests__/SessionJanitor.test.ts +195 -4
  315. package/src/__tests__/SessionManager.test.ts +69 -0
  316. package/src/__tests__/TaskGraphActionRunner.durablePayload.test.ts +2 -2
  317. package/src/__tests__/TaskGraphAgentRunner.test.ts +272 -16
  318. package/src/__tests__/ViewOperationsHandler.test.ts +2 -5
  319. package/src/__tests__/WorkQueueHostService.test.ts +25 -7
  320. package/src/__tests__/backgroundSweepConfigUnits.test.ts +153 -0
  321. package/src/__tests__/contextRequestOrigin.test.ts +105 -0
  322. package/src/__tests__/conversationBrowsers.test.ts +62 -0
  323. package/src/__tests__/magicLinkExtensionCapabilities.test.ts +413 -0
  324. package/src/__tests__/orphanedConversationDetailReconciler.start.test.ts +82 -0
  325. package/src/__tests__/ownedRealtimeSession.test.ts +14 -0
  326. package/src/__tests__/realtimeRecordingHandler.test.ts +7 -2
  327. package/src/__tests__/realtimeSessions/sessionAccess.test.ts +79 -1
  328. package/src/__tests__/requestOrigin.test.ts +104 -0
  329. package/src/__tests__/securityHardeningPr6034.test.ts +120 -0
  330. package/src/__tests__/sessionDeadline.test.ts +51 -0
  331. package/src/__tests__/sharedCache.test.ts +11 -0
  332. package/src/__tests__/unifiedAuth.test.ts +73 -1
  333. package/src/agentSessions/BridgeHostInstance.ts +20 -5
  334. package/src/agentSessions/HandoffOfferExpiry.ts +18 -0
  335. package/src/agentSessions/ReturningVisitorRecap.ts +1 -0
  336. package/src/agentSessions/SessionJanitor.ts +92 -42
  337. package/src/agentSessions/SessionManager.ts +38 -8
  338. package/src/agentSessions/conversationBrowsers.ts +143 -0
  339. package/src/agentSessions/sessionDeadline.ts +81 -0
  340. package/src/auth/APIKeyScopeAuth.ts +5 -4
  341. package/src/auth/__tests__/loginEmailResolution.test.ts +67 -0
  342. package/src/auth/loginEmailResolution.ts +67 -0
  343. package/src/auth/magicLink/MagicLinkKeys.ts +146 -14
  344. package/src/auth/magicLink/MagicLinkRouter.ts +33 -6
  345. package/src/auth/magicLink/MagicLinkService.ts +250 -10
  346. package/src/auth/magicLink/index.ts +1 -1
  347. package/src/auth/magicLink/redeemLanding.ts +7 -4
  348. package/src/auth/magicLink/types.ts +28 -3
  349. package/src/auth/newUsers.ts +1 -0
  350. package/src/backgroundSweepConfigUnits.ts +213 -0
  351. package/src/config.ts +69 -0
  352. package/src/context.ts +39 -6
  353. package/src/coreRoutes.ts +14 -7
  354. package/src/generated/graphql-schemas/__mj.ts +822 -7
  355. package/src/generic/OrphanedConversationDetailReconciler.ts +32 -1
  356. package/src/generic/ResolverBase.ts +414 -143
  357. package/src/generic/RunViewResolver.ts +96 -49
  358. package/src/index.ts +201 -35
  359. package/src/integration/ConnectionPause.ts +222 -0
  360. package/src/integration/CustomColumnPromoter.ts +123 -53
  361. package/src/integration/EntityMapLifecycle.ts +229 -6
  362. package/src/integration/InactiveRowWarnings.ts +50 -0
  363. package/src/integration/ObjectNamesByID.ts +97 -0
  364. package/src/integration/PostRestartIntrospect.ts +49 -0
  365. package/src/integration/RSUPostRestartProgress.ts +359 -0
  366. package/src/integration/RSUProgressBridge.ts +85 -2
  367. package/src/integration/RunArtifactAuthorization.ts +65 -0
  368. package/src/integration/SchemaRefreshLaunch.ts +12 -2
  369. package/src/integration/ScopedFieldsForBuild.ts +113 -0
  370. package/src/integration/TargetConfigGuard.ts +71 -0
  371. package/src/realtimeSessions/ownedRealtimeSession.ts +2 -6
  372. package/src/realtimeSessions/sessionAccess.ts +30 -0
  373. package/src/realtimeWidget/WidgetSessionService.ts +2 -0
  374. package/src/realtimeWidget/visitorIdentity.ts +1 -0
  375. package/src/realtimeWidget/widgetCore.ts +4 -8
  376. package/src/realtimeWidget/widgetGuestElevation.ts +1 -0
  377. package/src/requestOrigin.ts +64 -0
  378. package/src/resolvers/AgentSessionResolver.ts +6 -2
  379. package/src/resolvers/AutotagPipelineResolver.ts +2 -1
  380. package/src/resolvers/ConversationRunEventsResolver.ts +2 -0
  381. package/src/resolvers/ExecuteRemoteOperationResolver.ts +41 -9
  382. package/src/resolvers/GetDataResolver.ts +35 -13
  383. package/src/resolvers/IntegrationDiscoveryResolver.ts +1522 -203
  384. package/src/resolvers/MCPResolver.ts +15 -2
  385. package/src/resolvers/QuerySystemUserResolver.ts +197 -98
  386. package/src/resolvers/RealtimeBridgeResolver.ts +1 -0
  387. package/src/resolvers/RealtimeClientSessionResolver.ts +40 -7
  388. package/src/resolvers/RemoteBrowserActionResolver.ts +242 -32
  389. package/src/resolvers/RunAIAgentResolver.ts +265 -86
  390. package/src/resolvers/RunAIPromptResolver.ts +18 -18
  391. package/src/resolvers/RunTemplateResolver.ts +3 -1
  392. package/src/resolvers/__tests__/GetDataResolver.tokens.test.ts +52 -0
  393. package/src/resolvers/meetingRecordingRegistration.ts +2 -0
  394. package/src/rest/MediaAccessKeys.ts +6 -2
  395. package/src/rest/OAuthCallbackHandler.ts +13 -5
  396. package/src/rest/RealtimeRecordingHandler.ts +5 -9
  397. package/src/rest/ViewOperationsHandler.ts +2 -1
  398. package/src/rest/setupRESTEndpoints.ts +11 -2
  399. package/src/services/DurableEntityActionTaskSubmitter.ts +10 -2
  400. package/src/services/TaskGraphActionRunner.ts +2 -23
  401. package/src/services/TaskGraphAgentRunner.ts +43 -0
  402. package/src/services/TaskGraphContinuationDeliverer.ts +1 -0
  403. package/src/services/WorkQueueHostService.ts +26 -4
  404. package/src/services/workQueueConfig.ts +5 -5
  405. package/src/sharedCache.ts +1 -1
  406. package/src/types.ts +18 -0
  407. package/src/services/TaskOrchestration-Integration.md +0 -188
@@ -0,0 +1,67 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { DescribeUserEmailMap, EmailsMatch, ResolveUserLookupEmail } from '../loginEmailResolution.js';
3
+
4
+ describe('ResolveUserLookupEmail', () => {
5
+ it('looks a login up by its email claim when no map is set', () => {
6
+ expect(ResolveUserLookupEmail({ email: 'b@two.example', preferredUsername: 'b@two.example' }, undefined)).toEqual({
7
+ TokenEmail: 'b@two.example',
8
+ LookupEmail: 'b@two.example',
9
+ Remapped: false,
10
+ });
11
+ });
12
+
13
+ it('applies a USER_EMAIL_MAP entry and reports the rewrite', () => {
14
+ const result = ResolveUserLookupEmail({ email: 'b@two.example' }, { 'b@two.example': 'a@one.example' });
15
+ expect(result).toEqual({ TokenEmail: 'b@two.example', LookupEmail: 'a@one.example', Remapped: true });
16
+ });
17
+
18
+ it('keeps the map key match exact (case-sensitive), as before', () => {
19
+ const result = ResolveUserLookupEmail({ email: 'B@two.example' }, { 'b@two.example': 'a@one.example' });
20
+ expect(result.LookupEmail).toBe('B@two.example');
21
+ expect(result.Remapped).toBe(false);
22
+ });
23
+
24
+ it('does not count an entry that only changes case as a remap', () => {
25
+ const result = ResolveUserLookupEmail({ email: 'b@two.example' }, { 'b@two.example': 'B@Two.example' });
26
+ expect(result.LookupEmail).toBe('B@Two.example');
27
+ expect(result.Remapped).toBe(false);
28
+ });
29
+
30
+ it('falls back to the preferred username and never remaps it', () => {
31
+ const result = ResolveUserLookupEmail({ preferredUsername: 'b@two.example' }, { 'b@two.example': 'a@one.example' });
32
+ expect(result).toEqual({ TokenEmail: 'b@two.example', LookupEmail: 'b@two.example', Remapped: false });
33
+ });
34
+
35
+ it('returns no identity when the token has neither claim', () => {
36
+ expect(ResolveUserLookupEmail({}, undefined)).toEqual({ TokenEmail: undefined, LookupEmail: undefined, Remapped: false });
37
+ });
38
+ });
39
+
40
+ describe('EmailsMatch', () => {
41
+ it('ignores case and surrounding whitespace, like the User lookup', () => {
42
+ expect(EmailsMatch(' A@One.example ', 'a@one.example')).toBe(true);
43
+ });
44
+
45
+ it('treats a missing side as no match', () => {
46
+ expect(EmailsMatch(undefined, 'a@one.example')).toBe(false);
47
+ expect(EmailsMatch('a@one.example', '')).toBe(false);
48
+ });
49
+ });
50
+
51
+ describe('DescribeUserEmailMap', () => {
52
+ it('returns null when no map is configured', () => {
53
+ expect(DescribeUserEmailMap(undefined)).toBeNull();
54
+ expect(DescribeUserEmailMap({})).toBeNull();
55
+ });
56
+
57
+ it('returns null when every entry maps an address to itself', () => {
58
+ expect(DescribeUserEmailMap({ 'a@one.example': 'A@one.example' })).toBeNull();
59
+ });
60
+
61
+ it('lists every entry that sends a login to another record', () => {
62
+ const warning = DescribeUserEmailMap({ 'b@two.example': 'a@one.example', 'c@three.example': 'a@one.example' });
63
+ expect(warning).toContain('2 login email(s)');
64
+ expect(warning).toContain('b@two.example -> a@one.example');
65
+ expect(warning).toContain('c@three.example -> a@one.example');
66
+ });
67
+ });
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Which email a verified login is matched to a User record by, and whether that differs from the
3
+ * identity the token carries.
4
+ *
5
+ * The server links a login to a User row by email alone. `USER_EMAIL_MAP` can rewrite that email
6
+ * before the lookup, which makes a login run as a different person's record. That is a legitimate
7
+ * development aid, but it is silent, so a misconfigured map on a shared machine looks exactly like
8
+ * an authentication bug. These helpers keep the token's identity next to the one actually used, so
9
+ * the session audit and the server log can say when the two differ.
10
+ */
11
+
12
+ /** The identity fields of a verified token that the lookup can use. */
13
+ export interface LoginIdentityClaims {
14
+ email?: string; // case-violation-ok-legacy-back-compat: mirrors the provider's extracted user info shape
15
+ preferredUsername?: string; // case-violation-ok-legacy-back-compat: mirrors the provider's extracted user info shape
16
+ }
17
+
18
+ /** The email the token carries and the email the User lookup will use. */
19
+ export interface LoginEmailResolution {
20
+ /** The identity from the verified token: its email claim, or its preferred username when it has none. */
21
+ TokenEmail: string | undefined;
22
+ /** The email the User record is looked up by, after any `USER_EMAIL_MAP` rewrite. */
23
+ LookupEmail: string | undefined;
24
+ /** True when `USER_EMAIL_MAP` replaced the token's email with a different one. */
25
+ Remapped: boolean;
26
+ }
27
+
28
+ /**
29
+ * Resolves the lookup email for a verified token. The map applies only to the email claim, with an
30
+ * exact (case-sensitive) key match, as it always has; a preferred username is never remapped.
31
+ *
32
+ * Named without "login"/"auth"/"verify": CodeQL treats calls with those words as authentication
33
+ * actions and reports the `if (!token)` guard above this call in GetUserPayload as a user-controlled
34
+ * bypass. This is a plain lookup, not an authentication step.
35
+ */
36
+ export function ResolveUserLookupEmail(claims: LoginIdentityClaims, emailMap: Record<string, string> | undefined): LoginEmailResolution {
37
+ if (!claims.email) {
38
+ return { TokenEmail: claims.preferredUsername, LookupEmail: claims.preferredUsername, Remapped: false };
39
+ }
40
+ const mapped = emailMap?.[claims.email];
41
+ const lookupEmail = mapped ?? claims.email;
42
+ return { TokenEmail: claims.email, LookupEmail: lookupEmail, Remapped: mapped !== undefined && !EmailsMatch(mapped, claims.email) };
43
+ }
44
+
45
+ /** Case-insensitive, whitespace-trimmed comparison, matching how the User lookup compares emails. */
46
+ export function EmailsMatch(a: string | undefined, b: string | undefined): boolean {
47
+ if (!a || !b) {
48
+ return false;
49
+ }
50
+ return a.trim().toLowerCase() === b.trim().toLowerCase();
51
+ }
52
+
53
+ /**
54
+ * The startup warning for a configured `USER_EMAIL_MAP`, or null when the map is absent or rewrites
55
+ * nothing. Lists every entry so whoever starts the server can see whose record each login will use.
56
+ */
57
+ export function DescribeUserEmailMap(emailMap: Record<string, string> | undefined): string | null {
58
+ const entries = Object.entries(emailMap ?? {}).filter(([from, to]) => !EmailsMatch(from, to));
59
+ if (entries.length === 0) {
60
+ return null;
61
+ }
62
+ const lines = entries.map(([from, to]) => ` ${from} -> ${to}`).join('\n');
63
+ return (
64
+ `[Auth] USER_EMAIL_MAP is set: ${entries.length} login email(s) will be matched to a different user's record. ` +
65
+ `A login with the first address runs as the user with the second. Remove the setting unless this is intended.\n${lines}`
66
+ );
67
+ }
@@ -10,11 +10,28 @@
10
10
  */
11
11
 
12
12
  import { BaseSingleton } from '@memberjunction/global';
13
- import { LogStatusEx } from '@memberjunction/core';
13
+ import { LogStatusEx, LogWarning } from '@memberjunction/core';
14
14
  import { createPrivateKey, createPublicKey, generateKeyPairSync, createHash, type KeyObject } from 'node:crypto';
15
15
  import jwt from 'jsonwebtoken';
16
16
  import type { MagicLinkJWTClaims } from './types.js';
17
17
 
18
+ /**
19
+ * True when the process is NOT running with `NODE_ENV=production`. This is the same
20
+ * production/local-dev line MJServer already uses to forbid local dev auth in production.
21
+ */
22
+ export function IsMagicLinkLocalDevEnvironment(env: NodeJS.ProcessEnv = process.env): boolean {
23
+ return env.NODE_ENV?.trim().toLowerCase() !== 'production';
24
+ }
25
+
26
+ /** Warning logged when a non-local-dev server falls back to an ephemeral keypair. */
27
+ export const EPHEMERAL_MAGIC_LINK_KEY_WARNING =
28
+ '[MagicLink] No rsaPrivateKey configured and NODE_ENV=production — generated an EPHEMERAL RS256 keypair. ' +
29
+ 'Magic-link session tokens signed with it will stop validating when this process restarts, and they will not ' +
30
+ 'validate on any other MJAPI instance behind the same load balancer, because each process generates its own key. ' +
31
+ 'To fix, generate one RSA private key (e.g. `openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048`) and set it ' +
32
+ 'as magicLink.rsaPrivateKey in mj.config.cjs or as MJ_MAGIC_LINK_PRIVATE_KEY (raw or base64-encoded PEM), ' +
33
+ 'using the same key on every instance.';
34
+
18
35
  /** A single JSON Web Key plus the metadata jwks-rsa needs to match it. */
19
36
  interface PublishedJWK extends JsonWebKey {
20
37
  kid: string;
@@ -34,6 +51,7 @@ export class MagicLinkKeyManager extends BaseSingleton<MagicLinkKeyManager> {
34
51
  private publicKey?: KeyObject;
35
52
  private _kid = '';
36
53
  private _initialized = false;
54
+ private _isEphemeral = false;
37
55
 
38
56
  // Public to satisfy BaseSingleton.getInstance's `this: new () => T` constraint
39
57
  // (matches the AuthProviderFactory singleton pattern).
@@ -49,6 +67,15 @@ export class MagicLinkKeyManager extends BaseSingleton<MagicLinkKeyManager> {
49
67
  return this._initialized;
50
68
  }
51
69
 
70
+ /**
71
+ * True when no private key was configured and the keypair was generated for this process
72
+ * only. Tokens signed with an ephemeral key do not survive a restart and do not validate on
73
+ * other instances. Downstream code should ask this rather than reading the raw config value.
74
+ */
75
+ public get IsEphemeral(): boolean {
76
+ return this._isEphemeral;
77
+ }
78
+
52
79
  public get Kid(): string {
53
80
  return this._kid;
54
81
  }
@@ -56,8 +83,11 @@ export class MagicLinkKeyManager extends BaseSingleton<MagicLinkKeyManager> {
56
83
  /**
57
84
  * Initializes the keypair from a provided PEM (raw or base64-encoded) or, if
58
85
  * none is supplied, generates an ephemeral 2048-bit RSA keypair. Idempotent.
86
+ *
87
+ * @param env - process environment used to decide whether the ephemeral fallback is a local-dev
88
+ * default (verbose-only notice) or a production misconfiguration (warning). Defaults to `process.env`.
59
89
  */
60
- public Initialize(privateKeyPem?: string): void {
90
+ public Initialize(privateKeyPem?: string, env: NodeJS.ProcessEnv = process.env): void {
61
91
  if (this._initialized) {
62
92
  return;
63
93
  }
@@ -69,17 +99,8 @@ export class MagicLinkKeyManager extends BaseSingleton<MagicLinkKeyManager> {
69
99
  } else {
70
100
  const { privateKey } = generateKeyPairSync('rsa', { modulusLength: 2048 });
71
101
  priv = privateKey;
72
- // Verbose-only: on dev boxes with no configured key this fires on EVERY boot, and the
73
- // ephemeral fallback is the designed behavior there — not a condition worth a log line in
74
- // the default startup output. Deployments that care set the key; operators diagnosing
75
- // magic-link session loss can re-run with verbose logging.
76
- LogStatusEx({
77
- message:
78
- '[MagicLink] No rsaPrivateKey configured — generated an EPHEMERAL RS256 keypair. ' +
79
- 'Outstanding magic-link sessions will be invalidated on restart. Set magicLink.rsaPrivateKey ' +
80
- '(or MJ_MAGIC_LINK_PRIVATE_KEY) for stable production keys.',
81
- verboseOnly: true
82
- });
102
+ this._isEphemeral = true;
103
+ this.reportEphemeralKey(env);
83
104
  }
84
105
 
85
106
  this.privateKeyPem = priv.export({ type: 'pkcs8', format: 'pem' }) as string;
@@ -92,11 +113,103 @@ export class MagicLinkKeyManager extends BaseSingleton<MagicLinkKeyManager> {
92
113
  * Signs a set of magic-link (or local-dev) claims and returns the compact JWT.
93
114
  * `iat`/`exp` are expected to already be set on the claims.
94
115
  */
95
- public Sign(claims: MagicLinkJWTClaims | Record<string, unknown>): string {
116
+ public Sign<T extends object>(claims: T): string {
96
117
  this.ensureInitialized();
97
118
  return jwt.sign(claims, this.privateKeyPem, { algorithm: 'RS256', keyid: this._kid });
98
119
  }
99
120
 
121
+ /**
122
+ * Verifies a magic-link session JWT signed by this key manager (`RS256`, `mj_magic_link === true`).
123
+ * Returns `null` if invalid, expired, or forged.
124
+ */
125
+ public VerifySessionToken(
126
+ token: string,
127
+ options?: { issuer?: string; audience?: string },
128
+ ): MagicLinkJWTClaims | null {
129
+ this.ensureInitialized();
130
+ try {
131
+ const publicPem = this.publicKey!.export({ type: 'spki', format: 'pem' }) as string;
132
+ const verifyOptions: jwt.VerifyOptions = { algorithms: ['RS256'] };
133
+ if (options?.issuer) {
134
+ verifyOptions.issuer = options.issuer;
135
+ }
136
+ if (options?.audience) {
137
+ verifyOptions.audience = options.audience;
138
+ }
139
+ const decoded = jwt.verify(token, publicPem, verifyOptions);
140
+ if (!decoded || typeof decoded !== 'object') {
141
+ return null;
142
+ }
143
+ const claims = decoded as MagicLinkJWTClaims;
144
+ if (claims.mj_magic_link !== true || typeof claims.sub !== 'string') {
145
+ return null;
146
+ }
147
+ return claims;
148
+ } catch {
149
+ return null;
150
+ }
151
+ }
152
+
153
+ /**
154
+ * Signs a redirect binding JWT (`mj_purpose = 'magic-link-redirect'`) that cryptographically
155
+ * binds a validated `redirectUrl` to a specific invite's `tokenHash`.
156
+ *
157
+ * Note: Expiration is intentionally omitted from the redirect binding JWT because the database
158
+ * invite row's `ExpiresAt` (enforced atomically during `RedeemInvite`) is the authoritative,
159
+ * mutable expiration — allowing `UpdateInviteExpiry()` to extend an invite without invalidating
160
+ * an already-delivered `&rt=` parameter.
161
+ */
162
+ public SignRedirectBinding(tokenHash: string, redirectUrl: string, _expiresAtEpochSeconds?: number): string {
163
+ this.ensureInitialized();
164
+ const nowSeconds = Math.floor(Date.now() / 1000);
165
+ return jwt.sign(
166
+ {
167
+ mj_purpose: 'magic-link-redirect',
168
+ mj_th: tokenHash,
169
+ mj_redirect: redirectUrl,
170
+ iat: nowSeconds,
171
+ },
172
+ this.privateKeyPem,
173
+ { algorithm: 'RS256', keyid: this._kid },
174
+ );
175
+ }
176
+
177
+ /**
178
+ * Verifies a redirect binding JWT produced by {@link SignRedirectBinding} and ensures it matches
179
+ * `expectedTokenHash` (preventing cross-invite substitution) and carries a valid `http(s)` URL.
180
+ * Ignores JWT `exp` so bindings minted before an `UpdateInviteExpiry()` extension remain valid
181
+ * while the underlying invite row is valid. Returns the bound URL or `null`.
182
+ */
183
+ public VerifyRedirectBinding(bindingToken: string, expectedTokenHash: string): string | null {
184
+ this.ensureInitialized();
185
+ try {
186
+ const publicPem = this.publicKey!.export({ type: 'spki', format: 'pem' }) as string;
187
+ const decoded = jwt.verify(bindingToken, publicPem, {
188
+ algorithms: ['RS256'],
189
+ ignoreExpiration: true,
190
+ });
191
+ if (!decoded || typeof decoded !== 'object') {
192
+ return null;
193
+ }
194
+ const record = decoded as { mj_purpose?: string; mj_th?: string; mj_redirect?: string };
195
+ if (
196
+ record.mj_purpose !== 'magic-link-redirect' ||
197
+ record.mj_th !== expectedTokenHash ||
198
+ typeof record.mj_redirect !== 'string' ||
199
+ !record.mj_redirect
200
+ ) {
201
+ return null;
202
+ }
203
+ const parsed = new URL(record.mj_redirect);
204
+ if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
205
+ return null;
206
+ }
207
+ return record.mj_redirect;
208
+ } catch {
209
+ return null;
210
+ }
211
+ }
212
+
100
213
  /**
101
214
  * Returns the JWKS document (public key set) for the JWKS endpoint.
102
215
  */
@@ -106,6 +219,25 @@ export class MagicLinkKeyManager extends BaseSingleton<MagicLinkKeyManager> {
106
219
  return { keys: [{ ...jwk, kid: this._kid, use: 'sig', alg: 'RS256' }] };
107
220
  }
108
221
 
222
+ /**
223
+ * Local dev (NODE_ENV not 'production'): the ephemeral fallback is the designed behavior and
224
+ * fires on every boot, so the notice stays verbose-only. Otherwise it is a data-loss-on-restart
225
+ * misconfiguration, so it is a warning in the default output.
226
+ */
227
+ private reportEphemeralKey(env: NodeJS.ProcessEnv): void {
228
+ if (!IsMagicLinkLocalDevEnvironment(env)) {
229
+ LogWarning(EPHEMERAL_MAGIC_LINK_KEY_WARNING);
230
+ return;
231
+ }
232
+ LogStatusEx({
233
+ message:
234
+ '[MagicLink] No rsaPrivateKey configured — generated an EPHEMERAL RS256 keypair. ' +
235
+ 'Outstanding magic-link sessions will be invalidated on restart. Set magicLink.rsaPrivateKey ' +
236
+ '(or MJ_MAGIC_LINK_PRIVATE_KEY) for stable production keys.',
237
+ verboseOnly: true
238
+ });
239
+ }
240
+
109
241
  private ensureInitialized(): void {
110
242
  if (!this._initialized) {
111
243
  throw new Error('MagicLinkKeyManager not initialized — call Initialize() during server startup.');
@@ -32,6 +32,7 @@ export const MAGIC_LINK_JWKS_PATH = `${MAGIC_LINK_MOUNT_PATH}/jwks.json`;
32
32
  export function CreateMagicLinkHandler(publicUrl: string, config: MagicLinkConfig): {
33
33
  publicRouter: Router;
34
34
  authenticatedRouter: Router;
35
+ service: MagicLinkService;
35
36
  } {
36
37
  // Initialize the signing keypair once.
37
38
  MagicLinkKeyManager.Instance.Initialize(config.rsaPrivateKey);
@@ -64,10 +65,27 @@ export function CreateMagicLinkHandler(publicUrl: string, config: MagicLinkConfi
64
65
  res.status(200).json(MagicLinkKeyManager.Instance.GetJWKS());
65
66
  });
66
67
 
67
- // Sends a completed redemption result: browser flow → 302 into Explorer with
68
+ // Sends a completed redemption result: browser flow → 302 into a verified per-invite
69
+ // RedirectUrl (when cryptographically bound + origin-allowed) or Explorer with
68
70
  // the session token in the URL fragment (never sent to a server, so it stays
69
71
  // out of access/proxy logs); API flow → JSON with an appropriate status.
70
- const sendRedeemResult = (res: Response, result: RedeemMagicLinkResult, wantsJson: boolean): void => {
72
+ const sendRedeemResult = (
73
+ res: Response,
74
+ result: RedeemMagicLinkResult,
75
+ wantsJson: boolean,
76
+ verifiedRedirectUrl?: string,
77
+ ): void => {
78
+ if (verifiedRedirectUrl && !wantsJson) {
79
+ const baseWithoutHash = verifiedRedirectUrl.split('#')[0];
80
+ if (result.success && result.token) {
81
+ res.redirect(302, `${baseWithoutHash}#token=${encodeURIComponent(result.token)}`);
82
+ } else {
83
+ const sep = baseWithoutHash.includes('?') ? '&' : '?';
84
+ res.redirect(302, `${baseWithoutHash}${sep}mlError=${encodeURIComponent(result.errorCode ?? 'invalid')}`);
85
+ }
86
+ return;
87
+ }
88
+
71
89
  const explorerUrl = config.explorerUrl?.replace(/\/$/, '');
72
90
  if (explorerUrl && !wantsJson) {
73
91
  if (result.success && result.token) {
@@ -102,6 +120,7 @@ export function CreateMagicLinkHandler(publicUrl: string, config: MagicLinkConfi
102
120
  // the actual redemption happens only on POST (below), gated by a user click.
103
121
  publicRouter.get('/redeem', redeemLimiter, (req: Request, res: Response) => {
104
122
  const token = typeof req.query.token === 'string' ? req.query.token : '';
123
+ const rt = typeof req.query.rt === 'string' ? req.query.rt : undefined;
105
124
  if (!token) {
106
125
  res.status(400).json({ success: false, error: 'Missing token.' });
107
126
  return;
@@ -114,16 +133,19 @@ export function CreateMagicLinkHandler(publicUrl: string, config: MagicLinkConfi
114
133
  res
115
134
  .status(200)
116
135
  .type('html')
117
- .send(BuildRedeemLandingHtml(token, ApiRoute(`${MAGIC_LINK_MOUNT_PATH}/redeem`)));
136
+ .send(BuildRedeemLandingHtml(token, ApiRoute(`${MAGIC_LINK_MOUNT_PATH}/redeem`), rt));
118
137
  });
119
138
 
120
139
  // POST /redeem performs the actual (side-effectful) redemption. Token arrives
121
140
  // from the interstitial form body, an API client's JSON body, or the query.
122
141
  publicRouter.post('/redeem', redeemLimiter, urlencoded({ extended: false }), json(), async (req: Request, res: Response) => {
123
- const body = (req.body ?? {}) as { token?: string };
142
+ const body = (req.body ?? {}) as { token?: string; rt?: string };
124
143
  const token =
125
144
  (typeof body.token === 'string' && body.token) ||
126
145
  (typeof req.query.token === 'string' ? req.query.token : '');
146
+ const rt =
147
+ (typeof body.rt === 'string' && body.rt) ||
148
+ (typeof req.query.rt === 'string' ? req.query.rt : undefined);
127
149
  const wantsJson = req.query.format === 'json' || req.is('application/json') === 'application/json';
128
150
 
129
151
  if (!token) {
@@ -131,6 +153,8 @@ export function CreateMagicLinkHandler(publicUrl: string, config: MagicLinkConfi
131
153
  return;
132
154
  }
133
155
 
156
+ const verifiedRedirectUrl = service.ResolveVerifiedRedirectUrl(token, rt);
157
+
134
158
  // Forensic context for the redemption audit trail. `req.ip` honors the app's
135
159
  // trust-proxy setting; headers are best-effort and may be absent for API clients.
136
160
  const result = await service.RedeemInvite(token, {
@@ -138,7 +162,7 @@ export function CreateMagicLinkHandler(publicUrl: string, config: MagicLinkConfi
138
162
  userAgent: req.get('user-agent') ?? undefined,
139
163
  origin: req.get('origin') ?? undefined,
140
164
  });
141
- sendRedeemResult(res, result, wantsJson);
165
+ sendRedeemResult(res, result, wantsJson, verifiedRedirectUrl);
142
166
  });
143
167
 
144
168
  // ── Authenticated (invite creation) ───────────────────────────────────────
@@ -166,6 +190,8 @@ export function CreateMagicLinkHandler(publicUrl: string, config: MagicLinkConfi
166
190
  MaxUses: body.MaxUses,
167
191
  firstName: body.firstName,
168
192
  lastName: body.lastName,
193
+ RedirectUrl: body.RedirectUrl,
194
+ SendEmail: body.SendEmail,
169
195
  },
170
196
  creatingUser,
171
197
  );
@@ -175,13 +201,14 @@ export function CreateMagicLinkHandler(publicUrl: string, config: MagicLinkConfi
175
201
  res.status(status).json(result);
176
202
  });
177
203
 
178
- return { publicRouter, authenticatedRouter };
204
+ return { publicRouter, authenticatedRouter, service };
179
205
  }
180
206
 
181
207
  /** @deprecated Use {@link CreateMagicLinkHandler}. */
182
208
  export function createMagicLinkHandler(publicUrl: string, config: MagicLinkConfig): {
183
209
  publicRouter: Router;
184
210
  authenticatedRouter: Router;
211
+ service: MagicLinkService;
185
212
  } {
186
213
  return CreateMagicLinkHandler(publicUrl, config);
187
214
  }