@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,130 @@
1
+ /**
2
+ * The policy that decides who may run an action through a public entry point. It reads the
3
+ * `MJ: Action Authorizations` rows for the action, the caller's roles, one provider's authorizations
4
+ * and role grants, and whether the action's class authorizes its caller itself. It fails closed: an
5
+ * action that no authorization grants, and whose class does not authorize its caller, runs for
6
+ * Owners only.
7
+ */
8
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
9
+ import { AuthorizationInfo, AuthorizationRoleInfo, Metadata, UserInfo, UserRoleInfo } from '@memberjunction/core';
10
+ import { EvaluateActionRunAuthorization, IsOwner, type AuthorizationMetadata } from '../auth/actionAuthorization.js';
11
+
12
+ const ANALYST_ROLE = 'A11C0000-0000-4000-8000-0000000000F1';
13
+ const AUDITOR_ROLE = 'A11C0000-0000-4000-8000-0000000000F2';
14
+ const BLOCKED_ROLE = 'A11C0000-0000-4000-8000-0000000000F3';
15
+
16
+ const DATA_TOOLS = new AuthorizationInfo({ ID: 'auth-data-tools', Name: 'Data Tools', IsActive: true });
17
+ const RUN_QUERIES = new AuthorizationInfo({ ID: 'auth-run-queries', Name: 'Run Queries', IsActive: true, ParentID: DATA_TOOLS.ID });
18
+ const RETIRED_TOOL = new AuthorizationInfo({ ID: 'auth-retired-tool', Name: 'Retired Tool', IsActive: false, ParentID: DATA_TOOLS.ID });
19
+ const SWITCHED_OFF = new AuthorizationInfo({ ID: 'auth-switched-off', Name: 'Switched Off', IsActive: false });
20
+ const AUTHORIZATIONS = [DATA_TOOLS, RUN_QUERIES, RETIRED_TOOL, SWITCHED_OFF];
21
+ const AUTHORIZATION_ROLES = [
22
+ new AuthorizationRoleInfo({ ID: 'ar-1', AuthorizationID: RUN_QUERIES.ID, RoleID: ANALYST_ROLE, Type: 'Allow' }),
23
+ new AuthorizationRoleInfo({ ID: 'ar-2', AuthorizationID: DATA_TOOLS.ID, RoleID: AUDITOR_ROLE, Type: 'Allow' }),
24
+ new AuthorizationRoleInfo({ ID: 'ar-3', AuthorizationID: RUN_QUERIES.ID, RoleID: BLOCKED_ROLE, Type: 'Deny' }),
25
+ new AuthorizationRoleInfo({ ID: 'ar-4', AuthorizationID: SWITCHED_OFF.ID, RoleID: ANALYST_ROLE, Type: 'Allow' }),
26
+ ];
27
+ /** The provider metadata the decision reads. */
28
+ const METADATA: AuthorizationMetadata = { Authorizations: AUTHORIZATIONS, AuthorizationRoles: AUTHORIZATION_ROLES };
29
+
30
+ const user = (type: string, roleIDs: string[]): UserInfo =>
31
+ new UserInfo(null, { ID: `user-${type}-${roleIDs.join('-')}`, Type: type, UserRoles: roleIDs.map((RoleID) => new UserRoleInfo({ RoleID })) });
32
+
33
+ /** Decides for an action whose class does not authorize its caller. */
34
+ const decide = (caller: UserInfo, linkedAuthorizationIDs: string[]) =>
35
+ EvaluateActionRunAuthorization(caller, linkedAuthorizationIDs, METADATA, false);
36
+
37
+ beforeEach(() => {
38
+ vi.restoreAllMocks();
39
+ // The decision reads only the metadata it is given, so any read of the global provider fails the test.
40
+ vi.spyOn(Metadata, 'Provider', 'get').mockImplementation(() => {
41
+ throw new Error('The decision read the global metadata provider');
42
+ });
43
+ });
44
+
45
+ describe('IsOwner', () => {
46
+ it('recognises the Owner type, padded as the nchar column stores it', () => {
47
+ expect(IsOwner(user('Owner ', []))).toBe(true);
48
+ expect(IsOwner(user('User', [ANALYST_ROLE]))).toBe(false);
49
+ });
50
+ });
51
+
52
+ describe('EvaluateActionRunAuthorization', () => {
53
+ describe('Owners', () => {
54
+ it('lets an Owner run any action, linked or not', () => {
55
+ expect(decide(user('Owner ', []), []).Allowed).toBe(true);
56
+ expect(decide(user('Owner ', []), [RUN_QUERIES.ID]).Allowed).toBe(true);
57
+ });
58
+ });
59
+
60
+ describe('an action with no Action Authorization rows', () => {
61
+ it('is refused to any other user when its class does not authorize its caller', () => {
62
+ const decision = decide(user('User', [ANALYST_ROLE]), []);
63
+
64
+ expect(decision.Allowed).toBe(false);
65
+ expect(decision.Reason).toMatch(/no action authorization/i);
66
+ });
67
+
68
+ it('runs for any user when its class authorizes its caller, so the action\'s own check decides', () => {
69
+ expect(EvaluateActionRunAuthorization(user('User', [ANALYST_ROLE]), [], METADATA, true).Allowed).toBe(true);
70
+ });
71
+ });
72
+
73
+ describe('an action with Action Authorization rows', () => {
74
+ it('is decided by its links, even when its class authorizes its caller', () => {
75
+ expect(EvaluateActionRunAuthorization(user('User', ['some-other-role']), [RUN_QUERIES.ID], METADATA, true).Allowed).toBe(false);
76
+ });
77
+
78
+ it('runs for a user whose role holds a linked authorization', () => {
79
+ expect(decide(user('User', [ANALYST_ROLE]), [RUN_QUERIES.ID]).Allowed).toBe(true);
80
+ });
81
+
82
+ it('runs for a user whose role holds an ancestor of a linked authorization', () => {
83
+ expect(decide(user('User', [AUDITOR_ROLE]), [RUN_QUERIES.ID]).Allowed).toBe(true);
84
+ });
85
+
86
+ it('is refused to a user whose roles hold none of the linked authorizations', () => {
87
+ expect(decide(user('User', ['some-other-role']), [RUN_QUERIES.ID]).Allowed).toBe(false);
88
+ });
89
+
90
+ it('is refused when the linked authorization is inactive', () => {
91
+ expect(decide(user('User', [ANALYST_ROLE]), [SWITCHED_OFF.ID]).Allowed).toBe(false);
92
+ });
93
+
94
+ it('is refused when the linked authorization is inactive, even to a holder of its parent', () => {
95
+ expect(decide(user('User', [AUDITOR_ROLE]), [RETIRED_TOOL.ID]).Allowed).toBe(false);
96
+ });
97
+
98
+ it('is refused when one of the user\'s roles is denied the linked authorization', () => {
99
+ expect(decide(user('User', [ANALYST_ROLE, BLOCKED_ROLE]), [RUN_QUERIES.ID]).Allowed).toBe(false);
100
+ });
101
+
102
+ it('is refused when a role is denied the linked authorization, even if another role holds its parent', () => {
103
+ expect(decide(user('User', [AUDITOR_ROLE, BLOCKED_ROLE]), [RUN_QUERIES.ID]).Allowed).toBe(false);
104
+ });
105
+
106
+ it('ignores a linked authorization that no longer exists in metadata', () => {
107
+ expect(decide(user('User', [ANALYST_ROLE]), ['auth-deleted']).Allowed).toBe(false);
108
+ });
109
+
110
+ it('matches authorization IDs regardless of case', () => {
111
+ expect(decide(user('User', [ANALYST_ROLE]), [RUN_QUERIES.ID.toUpperCase()]).Allowed).toBe(true);
112
+ });
113
+ });
114
+
115
+ describe('the metadata it reads', () => {
116
+ it('decides from the role grants it is given, never from the global provider', () => {
117
+ const sameTreeWithoutGrants: AuthorizationMetadata = { Authorizations: AUTHORIZATIONS, AuthorizationRoles: [] };
118
+
119
+ expect(decide(user('User', [ANALYST_ROLE]), [RUN_QUERIES.ID]).Allowed).toBe(true);
120
+ expect(EvaluateActionRunAuthorization(user('User', [ANALYST_ROLE]), [RUN_QUERIES.ID], sameTreeWithoutGrants, false).Allowed).toBe(false);
121
+ });
122
+
123
+ it('walks to the parent within the authorizations it is given', () => {
124
+ const treeWithoutParent: AuthorizationMetadata = { Authorizations: [RUN_QUERIES], AuthorizationRoles: AUTHORIZATION_ROLES };
125
+
126
+ expect(decide(user('User', [AUDITOR_ROLE]), [RUN_QUERIES.ID]).Allowed).toBe(true);
127
+ expect(EvaluateActionRunAuthorization(user('User', [AUDITOR_ROLE]), [RUN_QUERIES.ID], treeWithoutParent, false).Allowed).toBe(false);
128
+ });
129
+ });
130
+ });
@@ -0,0 +1,26 @@
1
+ /**
2
+ * An ad-hoc query's timeout never exceeds the server's requestTimeout, so a caller cannot hold a
3
+ * read-only connection longer than any other request may.
4
+ */
5
+ import { describe, it, expect } from 'vitest';
6
+ import { ClampAdhocTimeoutSeconds } from '../resolvers/adhocTimeout';
7
+
8
+ describe('ClampAdhocTimeoutSeconds', () => {
9
+ it('caps a longer request at requestTimeout', () => {
10
+ expect(ClampAdhocTimeoutSeconds(600, 30000)).toBe(30);
11
+ });
12
+
13
+ it('keeps a shorter request', () => {
14
+ expect(ClampAdhocTimeoutSeconds(5, 30000)).toBe(5);
15
+ });
16
+
17
+ it('uses 30 seconds when the caller names none, within the cap', () => {
18
+ expect(ClampAdhocTimeoutSeconds(undefined, 30000)).toBe(30);
19
+ expect(ClampAdhocTimeoutSeconds(undefined, 10000)).toBe(10);
20
+ expect(ClampAdhocTimeoutSeconds(0, 30000)).toBe(30);
21
+ });
22
+
23
+ it('leaves the request alone when the server sets no limit', () => {
24
+ expect(ClampAdhocTimeoutSeconds(600, 0)).toBe(600);
25
+ });
26
+ });
@@ -0,0 +1,118 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import type { IMetadataProvider, UserInfo } from '@memberjunction/core';
3
+
4
+ const h = vi.hoisted(() => ({
5
+ systemUser: { ID: 'SYS', Name: 'System' } as { ID: string; Name: string } | undefined,
6
+ logError: vi.fn(),
7
+ }));
8
+
9
+ vi.mock('@memberjunction/core', async (importOriginal) => {
10
+ const actual = await importOriginal<Record<string, unknown>>();
11
+ return { ...actual, LogError: h.logError };
12
+ });
13
+
14
+ vi.mock('@memberjunction/generic-database-provider', () => ({
15
+ UserCache: { Instance: { GetSystemUser: () => h.systemUser } },
16
+ }));
17
+
18
+ import { WriteAgentVisionConsentAudit, REALTIME_AGENT_VISION_CONSENT_AUDIT_TYPE } from '../resolvers/agentVisionConsentAudit';
19
+
20
+ /** A fake `MJ: Audit Logs` row that records what is set on it. */
21
+ class FakeAuditRow {
22
+ public UserID = '';
23
+ public AuditLogTypeID = '';
24
+ public Status = '';
25
+ public EntityID: string | null = null;
26
+ public RecordID: string | null = null;
27
+ public Description: string | null = null;
28
+ public Details: string | null = null;
29
+ public IsNew = false;
30
+ constructor(private readonly saveResult: boolean) {}
31
+ NewRecord() {
32
+ this.IsNew = true;
33
+ }
34
+ async Save() {
35
+ return this.saveResult;
36
+ }
37
+ get LatestResult() {
38
+ return { CompleteMessage: 'save failed' };
39
+ }
40
+ }
41
+
42
+ const user = { ID: 'U1', Name: 'Test User' } as unknown as UserInfo;
43
+
44
+ /** A provider with the consent audit type seeded (unless told otherwise), handing out one fake row. */
45
+ function fakeProvider(o: { seeded?: boolean; saveResult?: boolean; getThrows?: boolean } = {}) {
46
+ const row = new FakeAuditRow(o.saveResult ?? true);
47
+ const getEntityObject = vi.fn(async () => {
48
+ if (o.getThrows) {
49
+ throw new Error('database unavailable');
50
+ }
51
+ return row;
52
+ });
53
+ const provider = {
54
+ AuditLogTypes: o.seeded === false ? [] : [{ ID: 'TYPE-1', Name: REALTIME_AGENT_VISION_CONSENT_AUDIT_TYPE }],
55
+ EntityByName: (name: string) => (name === 'MJ: Users' ? { ID: 'USERS-ENTITY' } : undefined),
56
+ GetEntityObject: getEntityObject,
57
+ } as unknown as IMetadataProvider;
58
+ return { provider, row, getEntityObject };
59
+ }
60
+
61
+ describe('WriteAgentVisionConsentAudit', () => {
62
+ beforeEach(() => {
63
+ h.logError.mockClear();
64
+ h.systemUser = { ID: 'SYS', Name: 'System' };
65
+ });
66
+
67
+ it('records an applied choice, saved by the system user, about the person', async () => {
68
+ const { provider, row, getEntityObject } = fakeProvider();
69
+ await WriteAgentVisionConsentAudit({ User: user, RoomName: 'room-1', Allow: true, Applied: true }, provider);
70
+ expect(getEntityObject).toHaveBeenCalledWith('MJ: Audit Logs', h.systemUser);
71
+ expect(row).toMatchObject({
72
+ IsNew: true,
73
+ UserID: 'U1',
74
+ AuditLogTypeID: 'TYPE-1',
75
+ Status: 'Success',
76
+ EntityID: 'USERS-ENTITY',
77
+ RecordID: 'U1',
78
+ Description: 'Let agents see their camera and shared screen in room room-1.',
79
+ });
80
+ expect(JSON.parse(row.Details ?? '')).toEqual({ roomName: 'room-1', allow: true, error: null });
81
+ expect(h.logError).not.toHaveBeenCalled();
82
+ });
83
+
84
+ it('records a refused change as Failed, with the reason', async () => {
85
+ const { provider, row } = fakeProvider();
86
+ await WriteAgentVisionConsentAudit({ User: user, RoomName: 'room-1', Allow: false, Applied: false, ErrorMessage: 'not in the room' }, provider);
87
+ expect(row.Status).toBe('Failed');
88
+ expect(row.Description).toBe('Stopped letting agents see their camera and shared screen in room room-1 (not applied).');
89
+ expect(JSON.parse(row.Details ?? '')).toEqual({ roomName: 'room-1', allow: false, error: 'not in the room' });
90
+ });
91
+
92
+ it('logs the choice instead when the audit log type is not seeded', async () => {
93
+ const { provider, getEntityObject } = fakeProvider({ seeded: false });
94
+ await WriteAgentVisionConsentAudit({ User: user, RoomName: 'room-1', Allow: true, Applied: true }, provider);
95
+ expect(getEntityObject).not.toHaveBeenCalled();
96
+ expect(h.logError).toHaveBeenCalledWith(expect.stringMatching(/is not seeded.*user U1: Let agents see their camera and shared screen in room room-1\./));
97
+ });
98
+
99
+ it('logs the choice instead when there is no system user or no provider', async () => {
100
+ h.systemUser = undefined;
101
+ const { provider, getEntityObject } = fakeProvider();
102
+ await WriteAgentVisionConsentAudit({ User: user, RoomName: 'room-1', Allow: true, Applied: true }, provider);
103
+ await WriteAgentVisionConsentAudit({ User: user, RoomName: 'room-1', Allow: true, Applied: true }, null);
104
+ expect(getEntityObject).not.toHaveBeenCalled();
105
+ expect(h.logError.mock.calls.map((c) => String(c[0]).match(/\((.*?)\)/)?.[1])).toEqual(['no system user', 'no provider']);
106
+ });
107
+
108
+ it('logs a row that does not save, and never throws when the write fails', async () => {
109
+ await WriteAgentVisionConsentAudit({ User: user, RoomName: 'room-1', Allow: true, Applied: true }, fakeProvider({ saveResult: false }).provider);
110
+ await expect(
111
+ WriteAgentVisionConsentAudit({ User: user, RoomName: 'room-1', Allow: true, Applied: true }, fakeProvider({ getThrows: true }).provider),
112
+ ).resolves.toBeUndefined();
113
+ expect(h.logError.mock.calls.map((c) => String(c[0]))).toEqual([
114
+ 'Agent vision consent audit row not saved for user U1: save failed',
115
+ 'Agent vision consent audit write threw for user U1: database unavailable',
116
+ ]);
117
+ });
118
+ });
@@ -0,0 +1,160 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import {
3
+ ValidateAvatarInput,
4
+ AVATAR_MAX_IMAGE_BYTES,
5
+ AVATAR_MAX_URL_LENGTH,
6
+ AVATAR_MAX_ICON_CLASS_LENGTH,
7
+ } from '../resolvers/avatarInputValidation.js';
8
+
9
+ /** A valid base64 data URI whose payload decodes to exactly `bytes` bytes. */
10
+ function dataUri(bytes: number, type = 'png'): string {
11
+ return `data:image/${type};base64,${Buffer.alloc(bytes, 7).toString('base64')}`;
12
+ }
13
+
14
+ function expectRefused(imageURL: string | null, iconClass: string | null = null): string {
15
+ const result = ValidateAvatarInput(imageURL, iconClass);
16
+ expect(result.Valid).toBe(false);
17
+ expect(result.ErrorMessage).toBeTruthy();
18
+ return result.ErrorMessage!;
19
+ }
20
+
21
+ describe('ValidateAvatarInput', () => {
22
+ describe('clearing', () => {
23
+ it('accepts both null as a revert to default', () => {
24
+ expect(ValidateAvatarInput(null, null)).toEqual({ Valid: true, ImageURL: null, IconClass: null });
25
+ });
26
+
27
+ it('treats undefined and empty strings as clear', () => {
28
+ expect(ValidateAvatarInput(undefined, undefined)).toEqual({ Valid: true, ImageURL: null, IconClass: null });
29
+ expect(ValidateAvatarInput('', '')).toEqual({ Valid: true, ImageURL: null, IconClass: null });
30
+ });
31
+ });
32
+
33
+ describe('data URIs', () => {
34
+ it.each(['png', 'jpeg', 'jpg', 'gif', 'webp'])('accepts image/%s', (type) => {
35
+ const uri = dataUri(10, type);
36
+ expect(ValidateAvatarInput(uri, null)).toEqual({ Valid: true, ImageURL: uri, IconClass: null });
37
+ });
38
+
39
+ it('accepts every padding variant', () => {
40
+ for (const bytes of [3, 4, 5]) {
41
+ expect(ValidateAvatarInput(dataUri(bytes), null).Valid).toBe(true);
42
+ }
43
+ });
44
+
45
+ it('accepts a decoded payload of exactly the cap', () => {
46
+ expect(ValidateAvatarInput(dataUri(AVATAR_MAX_IMAGE_BYTES), null).Valid).toBe(true);
47
+ });
48
+
49
+ it('refuses a decoded payload one byte over the cap', () => {
50
+ expect(expectRefused(dataUri(AVATAR_MAX_IMAGE_BYTES + 1))).toContain('200KB');
51
+ });
52
+
53
+ it('refuses an oversized data URI on length alone, even one the pattern would reject anyway', () => {
54
+ const huge = `data:image/png;base64,${'A'.repeat(2 * 1024 * 1024)}!`;
55
+ expect(expectRefused(huge)).toContain('200KB');
56
+ });
57
+
58
+ it('accepts the longest prefix with a payload of exactly the cap', () => {
59
+ expect(ValidateAvatarInput(dataUri(AVATAR_MAX_IMAGE_BYTES, 'jpeg'), null).Valid).toBe(true);
60
+ });
61
+
62
+ it('refuses SVG, which can carry script', () => {
63
+ expectRefused(`data:image/svg+xml;base64,${Buffer.from('<svg onload="alert(1)"/>').toString('base64')}`);
64
+ });
65
+
66
+ it.each([
67
+ 'data:text/html;base64,PHNjcmlwdD4=',
68
+ 'data:application/octet-stream;base64,AAAA',
69
+ 'data:image/png,rawdata',
70
+ 'data:image/png;base64,',
71
+ 'data:image/png;base64,AAA',
72
+ 'data:image/png;base64,AA=A',
73
+ 'data:image/png;base64,AA AA',
74
+ 'data:image/png;base64,AA*A',
75
+ 'data:image/PNG;base64,AAAA',
76
+ `${dataUri(10)}\n`,
77
+ ])('refuses %j', (value) => {
78
+ expectRefused(value);
79
+ });
80
+ });
81
+
82
+ describe('http(s) URLs', () => {
83
+ it.each(['https://example.com/me.png', 'http://example.com/a/b?c=d#e', 'HTTPS://EXAMPLE.COM/x.png'])('accepts %s', (url) => {
84
+ expect(ValidateAvatarInput(url, null)).toEqual({ Valid: true, ImageURL: url, IconClass: null });
85
+ });
86
+
87
+ it('accepts a URL of exactly the length cap', () => {
88
+ const base = 'https://example.com/';
89
+ const url = base + 'a'.repeat(AVATAR_MAX_URL_LENGTH - base.length);
90
+ expect(url).toHaveLength(AVATAR_MAX_URL_LENGTH);
91
+ expect(ValidateAvatarInput(url, null).Valid).toBe(true);
92
+ });
93
+
94
+ it('refuses a URL one character over the length cap', () => {
95
+ const base = 'https://example.com/';
96
+ expectRefused(base + 'a'.repeat(AVATAR_MAX_URL_LENGTH - base.length + 1));
97
+ });
98
+
99
+ it.each([
100
+ 'javascript:alert(1)',
101
+ 'JavaScript:alert(document.cookie)',
102
+ 'vbscript:msgbox(1)',
103
+ 'file:///etc/passwd',
104
+ 'ftp://example.com/me.png',
105
+ 'blob:https://example.com/abc',
106
+ '//example.com/me.png',
107
+ '/assets/user.png',
108
+ 'me.png',
109
+ 'https://exa mple.com/me.png',
110
+ ' https://example.com/me.png',
111
+ 'https://example.com/me.png\t',
112
+ 'https://',
113
+ 'http:example.com',
114
+ ])('refuses %j', (value) => {
115
+ expectRefused(value);
116
+ });
117
+ });
118
+
119
+ describe('icon classes', () => {
120
+ it.each(['fa-solid fa-user', 'fa-regular fa-circle-user', 'fa-user', 'fa-solid fa-user-astronaut fa-2x'])('accepts %j', (icon) => {
121
+ expect(ValidateAvatarInput(null, icon)).toEqual({ Valid: true, ImageURL: null, IconClass: icon });
122
+ });
123
+
124
+ it('accepts an icon class of exactly the length cap', () => {
125
+ const icon = 'fa-' + 'a'.repeat(AVATAR_MAX_ICON_CLASS_LENGTH - 3);
126
+ expect(ValidateAvatarInput(null, icon).Valid).toBe(true);
127
+ });
128
+
129
+ it('refuses an icon class one character over the length cap', () => {
130
+ expectRefused(null, 'fa-' + 'a'.repeat(AVATAR_MAX_ICON_CLASS_LENGTH - 2));
131
+ });
132
+
133
+ it.each([
134
+ 'solid user',
135
+ 'fa-',
136
+ 'fa-solid fa-User',
137
+ 'FA-SOLID FA-USER',
138
+ 'fa-solid fa-user" onmouseover="alert(1)',
139
+ 'fa-solid fa-user<script>',
140
+ 'fa-solid;fa-user',
141
+ 'fa_solid fa_user',
142
+ 'fa-solid\tfa-user',
143
+ 'fa-solid\nfa-user',
144
+ ])('refuses %j', (icon) => {
145
+ expectRefused(null, icon);
146
+ });
147
+ });
148
+
149
+ describe('both values', () => {
150
+ it('accepts a valid image and a valid icon together', () => {
151
+ const uri = dataUri(10);
152
+ expect(ValidateAvatarInput(uri, 'fa-solid fa-user')).toEqual({ Valid: true, ImageURL: uri, IconClass: 'fa-solid fa-user' });
153
+ });
154
+
155
+ it('refuses the whole request when either value is bad, and returns no values to store', () => {
156
+ expect(ValidateAvatarInput('javascript:alert(1)', 'fa-solid fa-user')).toMatchObject({ Valid: false, ImageURL: null, IconClass: null });
157
+ expect(ValidateAvatarInput(dataUri(10), 'bad icon')).toMatchObject({ Valid: false, ImageURL: null, IconClass: null });
158
+ });
159
+ });
160
+ });
@@ -0,0 +1,169 @@
1
+ /**
2
+ * Transactions opened outside a GraphQL request run on their OWN provider, never the shared
3
+ * `Metadata.Provider`.
4
+ *
5
+ * A provider's ambient transaction is per INSTANCE, and the global provider is shared by every
6
+ * request, scheduled job and background service. A transaction opened there nests into any other
7
+ * caller's, and while it is open every global-provider write from anywhere lands inside it. One left
8
+ * open swallows them all until the process restarts (MJ#2140). These tests pin the two call sites
9
+ * that used to open their transaction on the global provider: magic-link provisioning and
10
+ * SyncData's delete-with-filter. New-user creation is pinned in newUsers.test.ts.
11
+ */
12
+ import 'reflect-metadata';
13
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
14
+
15
+ vi.mock('@memberjunction/generic-database-provider', () => ({
16
+ UserCache: { Instance: { Users: [] } },
17
+ }));
18
+ vi.mock('@memberjunction/communication-engine', () => ({
19
+ CommunicationEngine: { Instance: {} },
20
+ }));
21
+ vi.mock('@memberjunction/communication-types', () => ({
22
+ Message: class {},
23
+ }));
24
+ vi.mock('../config.js', () => ({
25
+ configInfo: {},
26
+ }));
27
+
28
+ const factory = vi.hoisted(() => ({ create: vi.fn() }));
29
+ vi.mock('../isolatedProvider.js', () => ({ CreateIsolatedProvider: factory.create }));
30
+
31
+ import { Metadata, RunView, type IMetadataProvider, type RoleInfo, type UserInfo } from '@memberjunction/core';
32
+ import type { MJMagicLinkInviteEntity } from '@memberjunction/core-entities';
33
+ import type { MagicLinkConfig } from '../config.js';
34
+ import { MagicLinkService } from '../auth/magicLink/MagicLinkService.js';
35
+ import { SyncDataResolver, type ActionItemOutputType } from '../resolvers/SyncDataResolver.js';
36
+
37
+ /** A provider that records every call made on it. */
38
+ function recordingProvider(calls: string[], saveSucceeds = true) {
39
+ return {
40
+ BeginTransaction: vi.fn(async () => { calls.push('BeginTransaction'); }),
41
+ CommitTransaction: vi.fn(async () => { calls.push('CommitTransaction'); }),
42
+ RollbackTransaction: vi.fn(async () => { calls.push('RollbackTransaction'); }),
43
+ GetEntityObject: vi.fn(async (entityName: string) => {
44
+ calls.push(`GetEntityObject:${entityName}`);
45
+ const entity: Record<string, unknown> = {
46
+ NewRecord: () => { entity.ID = `id-${entityName}`; },
47
+ Save: async () => saveSucceeds,
48
+ GetAll: () => ({ ID: entity.ID }),
49
+ LatestResult: { CompleteMessage: 'save failed' },
50
+ };
51
+ return entity;
52
+ }),
53
+ };
54
+ }
55
+
56
+ let globalCalls: string[];
57
+ let isolatedCalls: string[];
58
+
59
+ beforeEach(() => {
60
+ vi.spyOn(console, 'log').mockImplementation(() => undefined);
61
+ vi.spyOn(console, 'error').mockImplementation(() => undefined);
62
+ globalCalls = [];
63
+ isolatedCalls = [];
64
+ Metadata.Provider = recordingProvider(globalCalls) as unknown as IMetadataProvider;
65
+ });
66
+
67
+ afterEach(() => {
68
+ Metadata.Provider = null as unknown as IMetadataProvider;
69
+ vi.restoreAllMocks();
70
+ factory.create.mockReset();
71
+ });
72
+
73
+ describe('MagicLinkService.createScopedUser', () => {
74
+ const invite = { Email: 'guest@example.com', RoleID: 'role-1', ApplicationID: 'app-1' } as unknown as MJMagicLinkInviteEntity;
75
+ const role = { ID: 'role-1', Name: 'External Guest' } as RoleInfo;
76
+ const contextUser = { ID: 'ctx-user' } as UserInfo;
77
+ const provision = () =>
78
+ new MagicLinkService('https://mj.example.com', {} as MagicLinkConfig)['createScopedUser'](invite, role, contextUser);
79
+
80
+ it('provisions inside a transaction on its own provider', async () => {
81
+ factory.create.mockResolvedValue(recordingProvider(isolatedCalls));
82
+
83
+ expect((await provision()).success).toBe(true);
84
+ expect(isolatedCalls).toEqual([
85
+ 'BeginTransaction',
86
+ 'GetEntityObject:MJ: Users',
87
+ 'GetEntityObject:MJ: User Roles',
88
+ 'GetEntityObject:MJ: User Applications',
89
+ 'CommitTransaction',
90
+ ]);
91
+ expect(globalCalls).toEqual([]);
92
+ });
93
+
94
+ it('rolls back on its own provider when a save fails', async () => {
95
+ factory.create.mockResolvedValue(recordingProvider(isolatedCalls, false));
96
+
97
+ expect((await provision()).success).toBe(false);
98
+ expect(isolatedCalls).toEqual(['BeginTransaction', 'GetEntityObject:MJ: Users', 'RollbackTransaction']);
99
+ expect(globalCalls).toEqual([]);
100
+ });
101
+ });
102
+
103
+ describe('SyncDataResolver.SyncSingleItemDeleteWithFilter', () => {
104
+ const user = { ID: 'system' } as UserInfo;
105
+
106
+ /** Rows the filtered view returns; each records its Delete on the shared call log. */
107
+ function stubView(rows: number, deleteSucceeds = true) {
108
+ const fromProvider = vi.spyOn(RunView, 'FromMetadataProvider').mockImplementation(
109
+ () =>
110
+ ({
111
+ RunView: async () => ({
112
+ Success: true,
113
+ Results: Array.from({ length: rows }, (_, i) => ({
114
+ Delete: async () => { isolatedCalls.push(`Delete:${i}`); return deleteSucceeds; },
115
+ LatestResult: { CompleteMessage: 'delete failed' },
116
+ })),
117
+ }),
118
+ }) as unknown as RunView,
119
+ );
120
+ return fromProvider;
121
+ }
122
+
123
+ const deleteWithFilter = (providerOverride?: IMetadataProvider) => {
124
+ const result = { Success: false } as ActionItemOutputType;
125
+ const resolver = new SyncDataResolver() as unknown as {
126
+ SyncSingleItemDeleteWithFilter: (...args: unknown[]) => Promise<void>;
127
+ };
128
+ return resolver
129
+ .SyncSingleItemDeleteWithFilter('MJ: Things', "Name = 'x'", result, user, {}, providerOverride)
130
+ .then(() => result);
131
+ };
132
+
133
+ it('with no request provider, loads AND deletes on its own provider inside one transaction', async () => {
134
+ const isolated = recordingProvider(isolatedCalls);
135
+ factory.create.mockResolvedValue(isolated);
136
+ const fromProvider = stubView(2);
137
+
138
+ const result = await deleteWithFilter();
139
+
140
+ expect(result.Success).toBe(true);
141
+ expect(fromProvider).toHaveBeenCalledWith(isolated);
142
+ expect(isolatedCalls).toEqual(['BeginTransaction', 'Delete:0', 'Delete:1', 'CommitTransaction']);
143
+ expect(globalCalls).toEqual([]);
144
+ });
145
+
146
+ it('with a request provider, uses it for both the read and the transaction', async () => {
147
+ const requestProvider = recordingProvider(isolatedCalls);
148
+ const fromProvider = stubView(1);
149
+
150
+ const result = await deleteWithFilter(requestProvider as unknown as IMetadataProvider);
151
+
152
+ expect(result.Success).toBe(true);
153
+ expect(factory.create).not.toHaveBeenCalled();
154
+ expect(fromProvider).toHaveBeenCalledWith(requestProvider);
155
+ expect(isolatedCalls).toEqual(['BeginTransaction', 'Delete:0', 'CommitTransaction']);
156
+ expect(globalCalls).toEqual([]);
157
+ });
158
+
159
+ it('rolls back on the same provider when a delete fails', async () => {
160
+ factory.create.mockResolvedValue(recordingProvider(isolatedCalls));
161
+ stubView(2, false);
162
+
163
+ const result = await deleteWithFilter();
164
+
165
+ expect(result.Success).toBe(false);
166
+ expect(isolatedCalls).toEqual(['BeginTransaction', 'Delete:0', 'RollbackTransaction']);
167
+ expect(globalCalls).toEqual([]);
168
+ });
169
+ });
@@ -15,6 +15,12 @@ vi.mock('@memberjunction/communication-types', () => ({
15
15
  vi.mock('../config.js', () => ({
16
16
  configInfo: {},
17
17
  }));
18
+ // Provisioning is stubbed in this suite; keep the real provider stack out of the module graph.
19
+ vi.mock('../isolatedProvider.js', () => ({
20
+ CreateIsolatedProvider: async () => {
21
+ throw new Error('provisioning is stubbed in this suite');
22
+ },
23
+ }));
18
24
 
19
25
  import { Metadata, RunView, type DatabaseProviderBase, type IMetadataProvider, type UserInfo } from '@memberjunction/core';
20
26
  import type { MJMagicLinkInviteEntity } from '@memberjunction/core-entities';
@@ -16,6 +16,10 @@ vi.mock('@memberjunction/communication-types', () => ({
16
16
  vi.mock('../config.js', () => ({
17
17
  configInfo: {},
18
18
  }));
19
+ const isolated = vi.hoisted(() => ({ provider: null as unknown }));
20
+ vi.mock('../isolatedProvider.js', () => ({
21
+ CreateIsolatedProvider: async () => isolated.provider,
22
+ }));
19
23
 
20
24
  import { Metadata, type IMetadataProvider, type RoleInfo, type UserInfo } from '@memberjunction/core';
21
25
  import type { MJMagicLinkInviteEntity } from '@memberjunction/core-entities';
@@ -62,6 +66,7 @@ describe('MagicLinkService provisioning log policy', () => {
62
66
  RollbackTransaction: async () => undefined,
63
67
  GetEntityObject: async (entityName: string) => makeMockEntity(`mock-${entityName}`),
64
68
  };
69
+ isolated.provider = provider;
65
70
  Metadata.Provider = provider as unknown as IMetadataProvider;
66
71
 
67
72
  service = new MagicLinkService('https://mj.example.com', {} as MagicLinkConfig);