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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (543) hide show
  1. package/README.md +20 -0
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/agentSessions/BridgeHostInstance.d.ts +25 -0
  4. package/dist/agentSessions/BridgeHostInstance.d.ts.map +1 -0
  5. package/dist/agentSessions/BridgeHostInstance.js +30 -0
  6. package/dist/agentSessions/BridgeHostInstance.js.map +1 -0
  7. package/dist/agentSessions/HostInstance.d.ts +75 -11
  8. package/dist/agentSessions/HostInstance.d.ts.map +1 -1
  9. package/dist/agentSessions/HostInstance.js +99 -28
  10. package/dist/agentSessions/HostInstance.js.map +1 -1
  11. package/dist/agentSessions/ReturningVisitorRecap.d.ts.map +1 -1
  12. package/dist/agentSessions/ReturningVisitorRecap.js +7 -3
  13. package/dist/agentSessions/ReturningVisitorRecap.js.map +1 -1
  14. package/dist/agentSessions/SessionJanitor.d.ts +236 -27
  15. package/dist/agentSessions/SessionJanitor.d.ts.map +1 -1
  16. package/dist/agentSessions/SessionJanitor.js +682 -54
  17. package/dist/agentSessions/SessionJanitor.js.map +1 -1
  18. package/dist/agentSessions/SessionManager.d.ts +48 -3
  19. package/dist/agentSessions/SessionManager.d.ts.map +1 -1
  20. package/dist/agentSessions/SessionManager.js +112 -9
  21. package/dist/agentSessions/SessionManager.js.map +1 -1
  22. package/dist/agentSessions/SessionRunIDVerifier.d.ts +63 -0
  23. package/dist/agentSessions/SessionRunIDVerifier.d.ts.map +1 -0
  24. package/dist/agentSessions/SessionRunIDVerifier.js +129 -0
  25. package/dist/agentSessions/SessionRunIDVerifier.js.map +1 -0
  26. package/dist/agentSessions/index.d.ts +2 -0
  27. package/dist/agentSessions/index.d.ts.map +1 -1
  28. package/dist/agentSessions/index.js +10 -3
  29. package/dist/agentSessions/index.js.map +1 -1
  30. package/dist/apiRoot.d.ts +32 -0
  31. package/dist/apiRoot.d.ts.map +1 -0
  32. package/dist/apiRoot.js +67 -0
  33. package/dist/apiRoot.js.map +1 -0
  34. package/dist/auth/AuthProviderCatalogRouter.d.ts.map +1 -1
  35. package/dist/auth/AuthProviderCatalogRouter.js +25 -0
  36. package/dist/auth/AuthProviderCatalogRouter.js.map +1 -1
  37. package/dist/auth/LoginScreenEngine.d.ts +22 -0
  38. package/dist/auth/LoginScreenEngine.d.ts.map +1 -0
  39. package/dist/auth/LoginScreenEngine.js +57 -0
  40. package/dist/auth/LoginScreenEngine.js.map +1 -0
  41. package/dist/auth/actionAuthorization.d.ts +40 -0
  42. package/dist/auth/actionAuthorization.d.ts.map +1 -0
  43. package/dist/auth/actionAuthorization.js +94 -0
  44. package/dist/auth/actionAuthorization.js.map +1 -0
  45. package/dist/auth/index.d.ts +1 -0
  46. package/dist/auth/index.d.ts.map +1 -1
  47. package/dist/auth/index.js +4 -10
  48. package/dist/auth/index.js.map +1 -1
  49. package/dist/auth/magicLink/MagicLinkRouter.d.ts.map +1 -1
  50. package/dist/auth/magicLink/MagicLinkRouter.js +3 -2
  51. package/dist/auth/magicLink/MagicLinkRouter.js.map +1 -1
  52. package/dist/auth/magicLink/MagicLinkService.d.ts.map +1 -1
  53. package/dist/auth/magicLink/MagicLinkService.js +6 -3
  54. package/dist/auth/magicLink/MagicLinkService.js.map +1 -1
  55. package/dist/auth/newUsers.d.ts +10 -0
  56. package/dist/auth/newUsers.d.ts.map +1 -1
  57. package/dist/auth/newUsers.js +80 -11
  58. package/dist/auth/newUsers.js.map +1 -1
  59. package/dist/config.d.ts +2514 -22
  60. package/dist/config.d.ts.map +1 -1
  61. package/dist/config.js +166 -4
  62. package/dist/config.js.map +1 -1
  63. package/dist/context.d.ts +8 -0
  64. package/dist/context.d.ts.map +1 -1
  65. package/dist/context.js +14 -11
  66. package/dist/context.js.map +1 -1
  67. package/dist/coreRoutes.d.ts +29 -0
  68. package/dist/coreRoutes.d.ts.map +1 -0
  69. package/dist/coreRoutes.js +142 -0
  70. package/dist/coreRoutes.js.map +1 -0
  71. package/dist/generated/graphql-schemas/__mj.d.ts +504 -3
  72. package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
  73. package/dist/generated/graphql-schemas/__mj.js +2456 -7
  74. package/dist/generated/graphql-schemas/__mj.js.map +1 -1
  75. package/dist/generic/CacheInvalidationResolver.d.ts +43 -8
  76. package/dist/generic/CacheInvalidationResolver.d.ts.map +1 -1
  77. package/dist/generic/CacheInvalidationResolver.js +59 -19
  78. package/dist/generic/CacheInvalidationResolver.js.map +1 -1
  79. package/dist/generic/OrphanedConversationDetailReconciler.js +3 -3
  80. package/dist/generic/OrphanedConversationDetailReconciler.js.map +1 -1
  81. package/dist/generic/PushStatusResolver.js +1 -1
  82. package/dist/generic/PushStatusResolver.js.map +1 -1
  83. package/dist/generic/ResolverBase.d.ts +123 -48
  84. package/dist/generic/ResolverBase.d.ts.map +1 -1
  85. package/dist/generic/ResolverBase.js +293 -305
  86. package/dist/generic/ResolverBase.js.map +1 -1
  87. package/dist/generic/RunViewResolver.d.ts.map +1 -1
  88. package/dist/generic/RunViewResolver.js +16 -0
  89. package/dist/generic/RunViewResolver.js.map +1 -1
  90. package/dist/globalCors.d.ts +33 -0
  91. package/dist/globalCors.d.ts.map +1 -0
  92. package/dist/globalCors.js +58 -0
  93. package/dist/globalCors.js.map +1 -0
  94. package/dist/index.d.ts +17 -0
  95. package/dist/index.d.ts.map +1 -1
  96. package/dist/index.js +219 -278
  97. package/dist/index.js.map +1 -1
  98. package/dist/isolatedProvider.d.ts +20 -0
  99. package/dist/isolatedProvider.d.ts.map +1 -0
  100. package/dist/isolatedProvider.js +22 -0
  101. package/dist/isolatedProvider.js.map +1 -0
  102. package/dist/multiTenancy/index.d.ts.map +1 -1
  103. package/dist/multiTenancy/index.js +22 -2
  104. package/dist/multiTenancy/index.js.map +1 -1
  105. package/dist/realtimeProxy/RealtimeProxyServer.d.ts +44 -4
  106. package/dist/realtimeProxy/RealtimeProxyServer.d.ts.map +1 -1
  107. package/dist/realtimeProxy/RealtimeProxyServer.js +121 -16
  108. package/dist/realtimeProxy/RealtimeProxyServer.js.map +1 -1
  109. package/dist/realtimeProxy/RealtimeRelayTunnel.d.ts +171 -0
  110. package/dist/realtimeProxy/RealtimeRelayTunnel.d.ts.map +1 -0
  111. package/dist/realtimeProxy/RealtimeRelayTunnel.js +532 -0
  112. package/dist/realtimeProxy/RealtimeRelayTunnel.js.map +1 -0
  113. package/dist/realtimeSessions/RealtimeSessionEventService.d.ts +148 -0
  114. package/dist/realtimeSessions/RealtimeSessionEventService.d.ts.map +1 -0
  115. package/dist/realtimeSessions/RealtimeSessionEventService.js +191 -0
  116. package/dist/realtimeSessions/RealtimeSessionEventService.js.map +1 -0
  117. package/dist/realtimeSessions/RealtimeSessionVerificationService.d.ts +66 -0
  118. package/dist/realtimeSessions/RealtimeSessionVerificationService.d.ts.map +1 -0
  119. package/dist/realtimeSessions/RealtimeSessionVerificationService.js +139 -0
  120. package/dist/realtimeSessions/RealtimeSessionVerificationService.js.map +1 -0
  121. package/dist/realtimeSessions/RealtimeVerifyRouter.d.ts +38 -0
  122. package/dist/realtimeSessions/RealtimeVerifyRouter.d.ts.map +1 -0
  123. package/dist/realtimeSessions/RealtimeVerifyRouter.js +99 -0
  124. package/dist/realtimeSessions/RealtimeVerifyRouter.js.map +1 -0
  125. package/dist/realtimeSessions/RecordingAssemblyScheduler.d.ts +100 -0
  126. package/dist/realtimeSessions/RecordingAssemblyScheduler.d.ts.map +1 -0
  127. package/dist/realtimeSessions/RecordingAssemblyScheduler.js +173 -0
  128. package/dist/realtimeSessions/RecordingAssemblyScheduler.js.map +1 -0
  129. package/dist/realtimeSessions/SessionVerificationPortsBase.d.ts +61 -0
  130. package/dist/realtimeSessions/SessionVerificationPortsBase.d.ts.map +1 -0
  131. package/dist/realtimeSessions/SessionVerificationPortsBase.js +149 -0
  132. package/dist/realtimeSessions/SessionVerificationPortsBase.js.map +1 -0
  133. package/dist/realtimeSessions/SlidingWindowRateLimiter.d.ts +57 -0
  134. package/dist/realtimeSessions/SlidingWindowRateLimiter.d.ts.map +1 -0
  135. package/dist/realtimeSessions/SlidingWindowRateLimiter.js +69 -0
  136. package/dist/realtimeSessions/SlidingWindowRateLimiter.js.map +1 -0
  137. package/dist/realtimeSessions/index.d.ts +19 -0
  138. package/dist/realtimeSessions/index.d.ts.map +1 -0
  139. package/dist/realtimeSessions/index.js +19 -0
  140. package/dist/realtimeSessions/index.js.map +1 -0
  141. package/dist/realtimeSessions/ownedRealtimeSession.d.ts +30 -0
  142. package/dist/realtimeSessions/ownedRealtimeSession.d.ts.map +1 -0
  143. package/dist/realtimeSessions/ownedRealtimeSession.js +47 -0
  144. package/dist/realtimeSessions/ownedRealtimeSession.js.map +1 -0
  145. package/dist/realtimeSessions/sessionAccess.d.ts +92 -0
  146. package/dist/realtimeSessions/sessionAccess.d.ts.map +1 -0
  147. package/dist/realtimeSessions/sessionAccess.js +94 -0
  148. package/dist/realtimeSessions/sessionAccess.js.map +1 -0
  149. package/dist/realtimeSessions/sessionConfigStamps.d.ts +70 -0
  150. package/dist/realtimeSessions/sessionConfigStamps.d.ts.map +1 -0
  151. package/dist/realtimeSessions/sessionConfigStamps.js +93 -0
  152. package/dist/realtimeSessions/sessionConfigStamps.js.map +1 -0
  153. package/dist/realtimeSessions/subscriptionAuthorization.d.ts +65 -0
  154. package/dist/realtimeSessions/subscriptionAuthorization.d.ts.map +1 -0
  155. package/dist/realtimeSessions/subscriptionAuthorization.js +71 -0
  156. package/dist/realtimeSessions/subscriptionAuthorization.js.map +1 -0
  157. package/dist/realtimeSessions/verificationCore.d.ts +243 -0
  158. package/dist/realtimeSessions/verificationCore.d.ts.map +1 -0
  159. package/dist/realtimeSessions/verificationCore.js +356 -0
  160. package/dist/realtimeSessions/verificationCore.js.map +1 -0
  161. package/dist/realtimeSessions/verificationPages.d.ts +66 -0
  162. package/dist/realtimeSessions/verificationPages.d.ts.map +1 -0
  163. package/dist/realtimeSessions/verificationPages.js +110 -0
  164. package/dist/realtimeSessions/verificationPages.js.map +1 -0
  165. package/dist/realtimeSessions/verificationPolicy.d.ts +147 -0
  166. package/dist/realtimeSessions/verificationPolicy.d.ts.map +1 -0
  167. package/dist/realtimeSessions/verificationPolicy.js +249 -0
  168. package/dist/realtimeSessions/verificationPolicy.js.map +1 -0
  169. package/dist/realtimeSessions/verificationWorkflow.d.ts +263 -0
  170. package/dist/realtimeSessions/verificationWorkflow.d.ts.map +1 -0
  171. package/dist/realtimeSessions/verificationWorkflow.js +435 -0
  172. package/dist/realtimeSessions/verificationWorkflow.js.map +1 -0
  173. package/dist/realtimeWidget/widgetGuestElevation.d.ts +20 -2
  174. package/dist/realtimeWidget/widgetGuestElevation.d.ts.map +1 -1
  175. package/dist/realtimeWidget/widgetGuestElevation.js +31 -2
  176. package/dist/realtimeWidget/widgetGuestElevation.js.map +1 -1
  177. package/dist/resolvers/ActionResolver.d.ts +31 -5
  178. package/dist/resolvers/ActionResolver.d.ts.map +1 -1
  179. package/dist/resolvers/ActionResolver.js +75 -14
  180. package/dist/resolvers/ActionResolver.js.map +1 -1
  181. package/dist/resolvers/AdhocQueryResolver.d.ts +14 -1
  182. package/dist/resolvers/AdhocQueryResolver.d.ts.map +1 -1
  183. package/dist/resolvers/AdhocQueryResolver.js +21 -5
  184. package/dist/resolvers/AdhocQueryResolver.js.map +1 -1
  185. package/dist/resolvers/ArtifactFileResolver.d.ts +5 -1
  186. package/dist/resolvers/ArtifactFileResolver.d.ts.map +1 -1
  187. package/dist/resolvers/ArtifactFileResolver.js +9 -7
  188. package/dist/resolvers/ArtifactFileResolver.js.map +1 -1
  189. package/dist/resolvers/CacheStatsResolver.d.ts +27 -0
  190. package/dist/resolvers/CacheStatsResolver.d.ts.map +1 -1
  191. package/dist/resolvers/CacheStatsResolver.js +113 -2
  192. package/dist/resolvers/CacheStatsResolver.js.map +1 -1
  193. package/dist/resolvers/ClientToolRequestResolver.d.ts +4 -2
  194. package/dist/resolvers/ClientToolRequestResolver.d.ts.map +1 -1
  195. package/dist/resolvers/ClientToolRequestResolver.js +28 -10
  196. package/dist/resolvers/ClientToolRequestResolver.js.map +1 -1
  197. package/dist/resolvers/ConversationRunEventsResolver.d.ts +8 -4
  198. package/dist/resolvers/ConversationRunEventsResolver.d.ts.map +1 -1
  199. package/dist/resolvers/ConversationRunEventsResolver.js +6 -6
  200. package/dist/resolvers/ConversationRunEventsResolver.js.map +1 -1
  201. package/dist/resolvers/DatasetResolver.d.ts +24 -3
  202. package/dist/resolvers/DatasetResolver.d.ts.map +1 -1
  203. package/dist/resolvers/DatasetResolver.js +57 -8
  204. package/dist/resolvers/DatasetResolver.js.map +1 -1
  205. package/dist/resolvers/EntityRecordNameResolver.d.ts +2 -1
  206. package/dist/resolvers/EntityRecordNameResolver.d.ts.map +1 -1
  207. package/dist/resolvers/EntityRecordNameResolver.js +3 -2
  208. package/dist/resolvers/EntityRecordNameResolver.js.map +1 -1
  209. package/dist/resolvers/FetchEntityVectorsResolver.d.ts +27 -1
  210. package/dist/resolvers/FetchEntityVectorsResolver.d.ts.map +1 -1
  211. package/dist/resolvers/FetchEntityVectorsResolver.js +148 -2
  212. package/dist/resolvers/FetchEntityVectorsResolver.js.map +1 -1
  213. package/dist/resolvers/FileResolver.d.ts +82 -11
  214. package/dist/resolvers/FileResolver.d.ts.map +1 -1
  215. package/dist/resolvers/FileResolver.js +279 -101
  216. package/dist/resolvers/FileResolver.js.map +1 -1
  217. package/dist/resolvers/GetDataContextDataResolver.d.ts +33 -7
  218. package/dist/resolvers/GetDataContextDataResolver.d.ts.map +1 -1
  219. package/dist/resolvers/GetDataContextDataResolver.js +117 -38
  220. package/dist/resolvers/GetDataContextDataResolver.js.map +1 -1
  221. package/dist/resolvers/GetDataResolver.d.ts.map +1 -1
  222. package/dist/resolvers/GetDataResolver.js +6 -3
  223. package/dist/resolvers/GetDataResolver.js.map +1 -1
  224. package/dist/resolvers/HumanHandoffResolver.d.ts.map +1 -1
  225. package/dist/resolvers/HumanHandoffResolver.js +7 -1
  226. package/dist/resolvers/HumanHandoffResolver.js.map +1 -1
  227. package/dist/resolvers/IntegrationDiscoveryResolver.d.ts.map +1 -1
  228. package/dist/resolvers/IntegrationDiscoveryResolver.js +4 -2
  229. package/dist/resolvers/IntegrationDiscoveryResolver.js.map +1 -1
  230. package/dist/resolvers/MCPResolver.d.ts.map +1 -1
  231. package/dist/resolvers/MCPResolver.js +9 -4
  232. package/dist/resolvers/MCPResolver.js.map +1 -1
  233. package/dist/resolvers/QueryResolver.d.ts +2 -0
  234. package/dist/resolvers/QueryResolver.d.ts.map +1 -1
  235. package/dist/resolvers/QueryResolver.js +13 -0
  236. package/dist/resolvers/QueryResolver.js.map +1 -1
  237. package/dist/resolvers/RealtimeBridgeResolver.d.ts +54 -2
  238. package/dist/resolvers/RealtimeBridgeResolver.d.ts.map +1 -1
  239. package/dist/resolvers/RealtimeBridgeResolver.js +155 -10
  240. package/dist/resolvers/RealtimeBridgeResolver.js.map +1 -1
  241. package/dist/resolvers/RealtimeClientSessionResolver.d.ts +157 -33
  242. package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
  243. package/dist/resolvers/RealtimeClientSessionResolver.js +404 -116
  244. package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
  245. package/dist/resolvers/RealtimeSessionEventsResolver.d.ts +61 -0
  246. package/dist/resolvers/RealtimeSessionEventsResolver.d.ts.map +1 -0
  247. package/dist/resolvers/RealtimeSessionEventsResolver.js +130 -0
  248. package/dist/resolvers/RealtimeSessionEventsResolver.js.map +1 -0
  249. package/dist/resolvers/RealtimeSessionVerificationResolver.d.ts +85 -0
  250. package/dist/resolvers/RealtimeSessionVerificationResolver.d.ts.map +1 -0
  251. package/dist/resolvers/RealtimeSessionVerificationResolver.js +188 -0
  252. package/dist/resolvers/RealtimeSessionVerificationResolver.js.map +1 -0
  253. package/dist/resolvers/RemoteBrowserActionResolver.d.ts +2 -0
  254. package/dist/resolvers/RemoteBrowserActionResolver.d.ts.map +1 -1
  255. package/dist/resolvers/RemoteBrowserActionResolver.js +5 -1
  256. package/dist/resolvers/RemoteBrowserActionResolver.js.map +1 -1
  257. package/dist/resolvers/RunAIAgentResolver.d.ts +10 -0
  258. package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
  259. package/dist/resolvers/RunAIAgentResolver.js +100 -14
  260. package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
  261. package/dist/resolvers/RunTemplateResolver.d.ts.map +1 -1
  262. package/dist/resolvers/RunTemplateResolver.js +12 -11
  263. package/dist/resolvers/RunTemplateResolver.js.map +1 -1
  264. package/dist/resolvers/SearchKnowledgeStreamResolver.d.ts +12 -1
  265. package/dist/resolvers/SearchKnowledgeStreamResolver.d.ts.map +1 -1
  266. package/dist/resolvers/SearchKnowledgeStreamResolver.js +15 -2
  267. package/dist/resolvers/SearchKnowledgeStreamResolver.js.map +1 -1
  268. package/dist/resolvers/SqlLoggingConfigResolver.d.ts.map +1 -1
  269. package/dist/resolvers/SqlLoggingConfigResolver.js +2 -3
  270. package/dist/resolvers/SqlLoggingConfigResolver.js.map +1 -1
  271. package/dist/resolvers/SyncDataResolver.d.ts.map +1 -1
  272. package/dist/resolvers/SyncDataResolver.js +6 -2
  273. package/dist/resolvers/SyncDataResolver.js.map +1 -1
  274. package/dist/resolvers/SyncRolesUsersResolver.d.ts +2 -0
  275. package/dist/resolvers/SyncRolesUsersResolver.d.ts.map +1 -1
  276. package/dist/resolvers/SyncRolesUsersResolver.js +23 -6
  277. package/dist/resolvers/SyncRolesUsersResolver.js.map +1 -1
  278. package/dist/resolvers/TaskGraphFrameResolver.d.ts +2 -1
  279. package/dist/resolvers/TaskGraphFrameResolver.d.ts.map +1 -1
  280. package/dist/resolvers/TaskGraphFrameResolver.js +28 -1
  281. package/dist/resolvers/TaskGraphFrameResolver.js.map +1 -1
  282. package/dist/resolvers/TestQuerySQLResolver.d.ts +24 -0
  283. package/dist/resolvers/TestQuerySQLResolver.d.ts.map +1 -1
  284. package/dist/resolvers/TestQuerySQLResolver.js +65 -13
  285. package/dist/resolvers/TestQuerySQLResolver.js.map +1 -1
  286. package/dist/resolvers/TransactionGroupResolver.d.ts +19 -2
  287. package/dist/resolvers/TransactionGroupResolver.d.ts.map +1 -1
  288. package/dist/resolvers/TransactionGroupResolver.js +102 -24
  289. package/dist/resolvers/TransactionGroupResolver.js.map +1 -1
  290. package/dist/resolvers/UserAvatarResolver.d.ts +16 -0
  291. package/dist/resolvers/UserAvatarResolver.d.ts.map +1 -0
  292. package/dist/resolvers/UserAvatarResolver.js +124 -0
  293. package/dist/resolvers/UserAvatarResolver.js.map +1 -0
  294. package/dist/resolvers/UserResolver.d.ts.map +1 -1
  295. package/dist/resolvers/UserResolver.js +2 -17
  296. package/dist/resolvers/UserResolver.js.map +1 -1
  297. package/dist/resolvers/agent-run-data-guard.d.ts +31 -0
  298. package/dist/resolvers/agent-run-data-guard.d.ts.map +1 -0
  299. package/dist/resolvers/agent-run-data-guard.js +72 -0
  300. package/dist/resolvers/agent-run-data-guard.js.map +1 -0
  301. package/dist/resolvers/agentVisionConsentAudit.d.ts +37 -0
  302. package/dist/resolvers/agentVisionConsentAudit.d.ts.map +1 -0
  303. package/dist/resolvers/agentVisionConsentAudit.js +63 -0
  304. package/dist/resolvers/agentVisionConsentAudit.js.map +1 -0
  305. package/dist/resolvers/avatarInputValidation.d.ts +40 -0
  306. package/dist/resolvers/avatarInputValidation.d.ts.map +1 -0
  307. package/dist/resolvers/avatarInputValidation.js +97 -0
  308. package/dist/resolvers/avatarInputValidation.js.map +1 -0
  309. package/dist/resolvers/currentUserRoles.d.ts +29 -0
  310. package/dist/resolvers/currentUserRoles.d.ts.map +1 -1
  311. package/dist/resolvers/currentUserRoles.js +30 -0
  312. package/dist/resolvers/currentUserRoles.js.map +1 -1
  313. package/dist/resolvers/meetings/meetingMutations.d.ts +6 -0
  314. package/dist/resolvers/meetings/meetingMutations.d.ts.map +1 -1
  315. package/dist/resolvers/meetings/meetingMutations.js +21 -0
  316. package/dist/resolvers/meetings/meetingMutations.js.map +1 -1
  317. package/dist/rest/RealtimeRecordingHandler.d.ts +49 -0
  318. package/dist/rest/RealtimeRecordingHandler.d.ts.map +1 -0
  319. package/dist/rest/RealtimeRecordingHandler.js +383 -0
  320. package/dist/rest/RealtimeRecordingHandler.js.map +1 -0
  321. package/dist/rest/ViewOperationsHandler.d.ts +3 -1
  322. package/dist/rest/ViewOperationsHandler.d.ts.map +1 -1
  323. package/dist/rest/ViewOperationsHandler.js +21 -12
  324. package/dist/rest/ViewOperationsHandler.js.map +1 -1
  325. package/dist/rest/setupRESTEndpoints.d.ts +6 -0
  326. package/dist/rest/setupRESTEndpoints.d.ts.map +1 -1
  327. package/dist/rest/setupRESTEndpoints.js +6 -0
  328. package/dist/rest/setupRESTEndpoints.js.map +1 -1
  329. package/dist/serverExtensionReservedRoots.d.ts +12 -10
  330. package/dist/serverExtensionReservedRoots.d.ts.map +1 -1
  331. package/dist/serverExtensionReservedRoots.js +14 -17
  332. package/dist/serverExtensionReservedRoots.js.map +1 -1
  333. package/dist/services/MJStorageBlobStore.d.ts +8 -1
  334. package/dist/services/MJStorageBlobStore.d.ts.map +1 -1
  335. package/dist/services/MJStorageBlobStore.js +25 -19
  336. package/dist/services/MJStorageBlobStore.js.map +1 -1
  337. package/dist/services/StartTaskGraphDispatcher.js +2 -2
  338. package/dist/services/StartTaskGraphDispatcher.js.map +1 -1
  339. package/dist/services/TaskGraphActionRunner.d.ts +5 -3
  340. package/dist/services/TaskGraphActionRunner.d.ts.map +1 -1
  341. package/dist/services/TaskGraphActionRunner.js +27 -5
  342. package/dist/services/TaskGraphActionRunner.js.map +1 -1
  343. package/dist/services/TaskGraphAgentRunner.d.ts +14 -2
  344. package/dist/services/TaskGraphAgentRunner.d.ts.map +1 -1
  345. package/dist/services/TaskGraphAgentRunner.js +157 -9
  346. package/dist/services/TaskGraphAgentRunner.js.map +1 -1
  347. package/dist/services/TaskGraphContinuationDeliverer.d.ts +57 -4
  348. package/dist/services/TaskGraphContinuationDeliverer.d.ts.map +1 -1
  349. package/dist/services/TaskGraphContinuationDeliverer.js +607 -18
  350. package/dist/services/TaskGraphContinuationDeliverer.js.map +1 -1
  351. package/dist/sharedCache.d.ts +105 -0
  352. package/dist/sharedCache.d.ts.map +1 -0
  353. package/dist/sharedCache.js +279 -0
  354. package/dist/sharedCache.js.map +1 -0
  355. package/dist/types.d.ts +6 -0
  356. package/dist/types.d.ts.map +1 -1
  357. package/dist/types.js.map +1 -1
  358. package/package.json +99 -96
  359. package/src/__tests__/ActionResolver.authorization.test.ts +353 -0
  360. package/src/__tests__/AdhocQueryResolver.sqlAuthorizer.test.ts +104 -0
  361. package/src/__tests__/BridgeHostInstance.test.ts +232 -0
  362. package/src/__tests__/ClientToolRequestResolver.test.ts +116 -0
  363. package/src/__tests__/DatasetResolver.itemFilters.test.ts +201 -0
  364. package/src/__tests__/EntityRecordNameResolver.rowSecurity.test.ts +152 -0
  365. package/src/__tests__/FetchEntityVectorsResolver.test.ts +433 -0
  366. package/src/__tests__/FileResolver.storageAccess.test.ts +562 -0
  367. package/src/__tests__/GetDataContextDataResolver.sqlItems.test.ts +416 -0
  368. package/src/__tests__/HostInstance.test.ts +84 -0
  369. package/src/__tests__/HumanHandoffResolver.test.ts +17 -0
  370. package/src/__tests__/ListOperationsResolver.adhocScreen.test.ts +137 -0
  371. package/src/__tests__/LoginScreenEngine.test.ts +86 -0
  372. package/src/__tests__/MJStorageBlobStore.storageGate.test.ts +63 -0
  373. package/src/__tests__/MeetingResolver.test.ts +75 -1
  374. package/src/__tests__/RealtimeBridgeResolver.test.ts +415 -6
  375. package/src/__tests__/RealtimeClientSessionResolver.configTamper.test.ts +383 -0
  376. package/src/__tests__/RealtimeClientSessionResolver.test.ts +682 -58
  377. package/src/__tests__/RealtimeProxyServer.test.ts +16 -1
  378. package/src/__tests__/RealtimeRelay.test.ts +876 -0
  379. package/src/__tests__/RecordingAssemblyScheduler.test.ts +178 -0
  380. package/src/__tests__/RemoteBrowserActionResolver.goalRunLink.test.ts +129 -0
  381. package/src/__tests__/ResolverBase.adhocReadScope.test.ts +133 -2
  382. package/src/__tests__/ResolverBase.aggregateScreen.test.ts +174 -0
  383. package/src/__tests__/ResolverBase.balancedClause.test.ts +155 -0
  384. package/src/__tests__/ResolverBase.clauseUnscopedRead.test.ts +127 -0
  385. package/src/__tests__/ResolverBase.clientOldValues.test.ts +212 -6
  386. package/src/__tests__/ResolverBase.filterEscaping.test.ts +5 -2
  387. package/src/__tests__/ReturningVisitorRecap.test.ts +115 -0
  388. package/src/__tests__/RunAIAgentResolver.branch.test.ts +170 -0
  389. package/src/__tests__/RunAIAgentResolver.historyFrom.test.ts +6 -4
  390. package/src/__tests__/RunAIAgentResolver.runDataGuard.test.ts +258 -0
  391. package/src/__tests__/RunAIAgentResolver.stoppedPredecessor.test.ts +166 -0
  392. package/src/__tests__/RunTemplateResolver.test.ts +132 -0
  393. package/src/__tests__/RunViewResolver.cacheCheckForwarding.test.ts +63 -0
  394. package/src/__tests__/RunViewResolver.cacheCheckScreen.test.ts +165 -0
  395. package/src/__tests__/SearchKnowledgeStreamResolver.test.ts +161 -0
  396. package/src/__tests__/SessionJanitor.test.ts +1203 -21
  397. package/src/__tests__/SessionManager.test.ts +240 -17
  398. package/src/__tests__/SessionRunIDVerifier.test.ts +251 -0
  399. package/src/__tests__/SyncRolesUsersResolver.userRoles.test.ts +97 -0
  400. package/src/__tests__/TaskGraphActionRunner.durablePayload.test.ts +25 -0
  401. package/src/__tests__/TaskGraphAgentRunner.test.ts +266 -0
  402. package/src/__tests__/TaskGraphContinuationDeliverer.test.ts +403 -22
  403. package/src/__tests__/TaskGraphFrameResolver.test.ts +22 -0
  404. package/src/__tests__/TestQuerySQLResolver.test.ts +274 -0
  405. package/src/__tests__/TransactionGroupResolver.refusals.test.ts +98 -1
  406. package/src/__tests__/UpdateMJDataContextItem.oldValues.test.ts +220 -0
  407. package/src/__tests__/UserAvatarResolver.test.ts +255 -0
  408. package/src/__tests__/UserResolver.currentUser.test.ts +16 -1
  409. package/src/__tests__/ViewOperationsHandler.clauseScreen.test.ts +157 -0
  410. package/src/__tests__/actionAuthorization.test.ts +130 -0
  411. package/src/__tests__/agent-run-data-guard.test.ts +130 -0
  412. package/src/__tests__/agentVisionConsentAudit.test.ts +118 -0
  413. package/src/__tests__/apiRootBoot.test.ts +202 -0
  414. package/src/__tests__/avatarInputValidation.test.ts +160 -0
  415. package/src/__tests__/cacheInvalidationRecordData.test.ts +64 -0
  416. package/src/__tests__/conversationRunEvents.test.ts +56 -14
  417. package/src/__tests__/globalCors.test.ts +217 -0
  418. package/src/__tests__/isolatedProviderTransactions.test.ts +169 -0
  419. package/src/__tests__/magicLinkService.consume.test.ts +6 -0
  420. package/src/__tests__/magicLinkService.provisionLog.test.ts +5 -0
  421. package/src/__tests__/multiTenancy.postgresRewrite.test.ts +181 -0
  422. package/src/__tests__/multiTenancy.security.test.ts +51 -0
  423. package/src/__tests__/multiTenancy.test.ts +1 -1
  424. package/src/__tests__/newUsers.template.test.ts +151 -0
  425. package/src/__tests__/newUsers.test.ts +74 -5
  426. package/src/__tests__/orphanedConversationDetailReconciler.test.ts +17 -2
  427. package/src/__tests__/ownedRealtimeSession.test.ts +39 -0
  428. package/src/__tests__/pushStatusResolver.filter.test.ts +7 -0
  429. package/src/__tests__/realtimeRecordingHandler.test.ts +652 -0
  430. package/src/__tests__/realtimeSessions/RealtimeSessionEventService.test.ts +201 -0
  431. package/src/__tests__/realtimeSessions/RealtimeSessionEventsResolver.test.ts +215 -0
  432. package/src/__tests__/realtimeSessions/RealtimeSessionVerificationResolver.test.ts +102 -0
  433. package/src/__tests__/realtimeSessions/RealtimeSessionVerificationService.test.ts +349 -0
  434. package/src/__tests__/realtimeSessions/RealtimeVerifyRouter.test.ts +131 -0
  435. package/src/__tests__/realtimeSessions/SlidingWindowRateLimiter.test.ts +56 -0
  436. package/src/__tests__/realtimeSessions/helpers.ts +4 -0
  437. package/src/__tests__/realtimeSessions/sessionAccess.test.ts +129 -0
  438. package/src/__tests__/realtimeSessions/sessionConfigStamps.test.ts +124 -0
  439. package/src/__tests__/realtimeSessions/verificationCore.test.ts +355 -0
  440. package/src/__tests__/realtimeSessions/verificationPages.test.ts +84 -0
  441. package/src/__tests__/realtimeSessions/verificationPolicy.test.ts +226 -0
  442. package/src/__tests__/realtimeSessions/verificationWorkflow.test.ts +633 -0
  443. package/src/__tests__/resolverBase.fls.test.ts +87 -4
  444. package/src/__tests__/serverExtensionHostOverlay.test.ts +66 -0
  445. package/src/__tests__/serverExtensionReservedRoots.test.ts +11 -96
  446. package/src/__tests__/sharedCache.test.ts +312 -0
  447. package/src/__tests__/widgetGuestElevation.test.ts +44 -0
  448. package/src/agentSessions/BridgeHostInstance.ts +32 -0
  449. package/src/agentSessions/HostInstance.ts +124 -28
  450. package/src/agentSessions/ReturningVisitorRecap.ts +7 -3
  451. package/src/agentSessions/SessionJanitor.ts +821 -59
  452. package/src/agentSessions/SessionManager.ts +138 -26
  453. package/src/agentSessions/SessionRunIDVerifier.ts +171 -0
  454. package/src/agentSessions/index.ts +10 -3
  455. package/src/apiRoot.ts +78 -0
  456. package/src/auth/AuthProviderCatalogRouter.ts +26 -0
  457. package/src/auth/LoginScreenEngine.ts +50 -0
  458. package/src/auth/actionAuthorization.ts +116 -0
  459. package/src/auth/index.ts +4 -8
  460. package/src/auth/magicLink/MagicLinkRouter.ts +3 -2
  461. package/src/auth/magicLink/MagicLinkService.ts +6 -3
  462. package/src/auth/newUsers.ts +99 -11
  463. package/src/config.ts +171 -5
  464. package/src/context.ts +15 -10
  465. package/src/coreRoutes.ts +166 -0
  466. package/src/generated/graphql-schemas/__mj.ts +1815 -57
  467. package/src/generic/CacheInvalidationResolver.ts +73 -18
  468. package/src/generic/OrphanedConversationDetailReconciler.ts +3 -3
  469. package/src/generic/PushStatusResolver.ts +1 -1
  470. package/src/generic/ResolverBase.ts +335 -326
  471. package/src/generic/RunViewResolver.ts +21 -0
  472. package/src/globalCors.ts +71 -0
  473. package/src/index.ts +231 -300
  474. package/src/isolatedProvider.ts +22 -0
  475. package/src/multiTenancy/index.ts +24 -2
  476. package/src/realtimeProxy/RealtimeProxyServer.ts +148 -16
  477. package/src/realtimeProxy/RealtimeRelayTunnel.ts +645 -0
  478. package/src/realtimeSessions/README.md +179 -0
  479. package/src/realtimeSessions/RealtimeSessionEventService.ts +274 -0
  480. package/src/realtimeSessions/RealtimeSessionVerificationService.ts +168 -0
  481. package/src/realtimeSessions/RealtimeVerifyRouter.ts +109 -0
  482. package/src/realtimeSessions/RecordingAssemblyScheduler.ts +234 -0
  483. package/src/realtimeSessions/SessionVerificationPortsBase.ts +189 -0
  484. package/src/realtimeSessions/SlidingWindowRateLimiter.ts +92 -0
  485. package/src/realtimeSessions/index.ts +18 -0
  486. package/src/realtimeSessions/ownedRealtimeSession.ts +55 -0
  487. package/src/realtimeSessions/sessionAccess.ts +145 -0
  488. package/src/realtimeSessions/sessionConfigStamps.ts +123 -0
  489. package/src/realtimeSessions/subscriptionAuthorization.ts +116 -0
  490. package/src/realtimeSessions/verificationCore.ts +502 -0
  491. package/src/realtimeSessions/verificationPages.ts +149 -0
  492. package/src/realtimeSessions/verificationPolicy.ts +321 -0
  493. package/src/realtimeSessions/verificationWorkflow.ts +702 -0
  494. package/src/realtimeWidget/widgetGuestElevation.ts +34 -2
  495. package/src/resolvers/ActionResolver.ts +84 -21
  496. package/src/resolvers/AdhocQueryResolver.ts +24 -6
  497. package/src/resolvers/ArtifactFileResolver.ts +9 -8
  498. package/src/resolvers/CacheStatsResolver.ts +89 -2
  499. package/src/resolvers/ClientToolRequestResolver.ts +25 -7
  500. package/src/resolvers/ConversationRunEventsResolver.ts +15 -11
  501. package/src/resolvers/DatasetResolver.ts +70 -10
  502. package/src/resolvers/EntityRecordNameResolver.ts +3 -2
  503. package/src/resolvers/FetchEntityVectorsResolver.ts +179 -2
  504. package/src/resolvers/FileResolver.ts +321 -106
  505. package/src/resolvers/GetDataContextDataResolver.ts +139 -41
  506. package/src/resolvers/GetDataResolver.ts +6 -3
  507. package/src/resolvers/HumanHandoffResolver.ts +8 -0
  508. package/src/resolvers/IntegrationDiscoveryResolver.ts +4 -2
  509. package/src/resolvers/MCPResolver.ts +8 -5
  510. package/src/resolvers/QueryResolver.ts +11 -0
  511. package/src/resolvers/RealtimeBridgeResolver.ts +163 -11
  512. package/src/resolvers/RealtimeClientSessionResolver.ts +511 -129
  513. package/src/resolvers/RealtimeSessionEventsResolver.ts +133 -0
  514. package/src/resolvers/RealtimeSessionVerificationResolver.ts +174 -0
  515. package/src/resolvers/RemoteBrowserActionResolver.ts +11 -1
  516. package/src/resolvers/RunAIAgentResolver.ts +116 -14
  517. package/src/resolvers/RunTemplateResolver.ts +13 -12
  518. package/src/resolvers/SearchKnowledgeStreamResolver.ts +19 -3
  519. package/src/resolvers/SqlLoggingConfigResolver.ts +2 -4
  520. package/src/resolvers/SyncDataResolver.ts +6 -2
  521. package/src/resolvers/SyncRolesUsersResolver.ts +24 -6
  522. package/src/resolvers/TaskGraphFrameResolver.ts +33 -1
  523. package/src/resolvers/TestQuerySQLResolver.ts +68 -13
  524. package/src/resolvers/TransactionGroupResolver.ts +131 -27
  525. package/src/resolvers/UserAvatarResolver.ts +111 -0
  526. package/src/resolvers/UserResolver.ts +2 -16
  527. package/src/resolvers/__tests__/GetDataResolver.validation.test.ts +95 -0
  528. package/src/resolvers/agent-run-data-guard.ts +89 -0
  529. package/src/resolvers/agentVisionConsentAudit.ts +82 -0
  530. package/src/resolvers/avatarInputValidation.ts +123 -0
  531. package/src/resolvers/currentUserRoles.ts +32 -1
  532. package/src/resolvers/meetings/meetingMutations.ts +21 -0
  533. package/src/rest/RealtimeRecordingHandler.ts +464 -0
  534. package/src/rest/ViewOperationsHandler.ts +29 -13
  535. package/src/rest/setupRESTEndpoints.ts +6 -0
  536. package/src/serverExtensionReservedRoots.ts +14 -17
  537. package/src/services/MJStorageBlobStore.ts +31 -30
  538. package/src/services/StartTaskGraphDispatcher.ts +2 -2
  539. package/src/services/TaskGraphActionRunner.ts +25 -5
  540. package/src/services/TaskGraphAgentRunner.ts +188 -11
  541. package/src/services/TaskGraphContinuationDeliverer.ts +713 -17
  542. package/src/sharedCache.ts +305 -0
  543. package/src/types.ts +7 -0
@@ -0,0 +1,130 @@
1
+ /**
2
+ * @fileoverview Which agent-run resolver callers may set a run's scope or agent-type params through the `data`
3
+ * argument (A12.15): the value the resolvers pass as `ExecuteAgentParams.TrustReservedRunData`.
4
+ *
5
+ * Pins who is trusted (the system user, an API key that holds the `agent:trusted` scope) and who is not (an
6
+ * interactive user, a key without that scope, and every widget-guest run even though it executes as the system
7
+ * user). What an untrusted run then loses is pinned where the rule is applied: `WithAgentRunDataTrustApplied` in
8
+ * ai-core-plus and `BaseAgent.Execute` in ai-agents.
9
+ */
10
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
11
+ import type { UserInfo } from '@memberjunction/core';
12
+ import { UserCache } from '@memberjunction/generic-database-provider';
13
+
14
+ const hoisted = vi.hoisted(() => ({ authorize: vi.fn() }));
15
+
16
+ vi.mock('@memberjunction/api-keys', () => ({
17
+ GetAPIKeyEngine: () => ({ Authorize: hoisted.authorize }),
18
+ }));
19
+
20
+ import { IsTrustedAgentRunCaller, TRUSTED_RUN_DATA_SCOPE, type ClientAgentRunCaller } from '../resolvers/agent-run-data-guard.js';
21
+ import { ElevateUserPayload } from '../realtimeWidget/widgetGuestElevation.js';
22
+ import type { UserPayload } from '../types.js';
23
+
24
+ function payload(fields: Partial<UserPayload>): UserPayload {
25
+ return { email: 'someone@example.com', userRecord: { ID: 'user-1' }, sessionId: 'session-1', ...fields };
26
+ }
27
+
28
+ const SYSTEM_USER = { ID: 'system-user', Email: 'system@example.com' } as UserInfo;
29
+
30
+ /** A browser session (JWT): neither the system user nor an API key. */
31
+ const INTERACTIVE = payload({ email: 'browser.user@example.com' });
32
+ /** The system API key (x-mj-api-key), as context.ts builds it. */
33
+ const SYSTEM_KEY = payload({ email: 'system@example.com', isSystemUser: true, apiKey: 'system-key' });
34
+ /** An `mj_sk_*` user API key an administrator granted `agent:trusted`, as context.ts builds it. */
35
+ const TRUSTED_KEY = payload({ email: 'integration@example.com', apiKeyId: 'key-1', apiKeyHash: 'hash-trusted' });
36
+ /** An `mj_sk_*` key a user minted for themselves: it may run agents, nothing more. */
37
+ const PLAIN_KEY = payload({ email: 'browser.user@example.com', apiKeyId: 'key-2', apiKeyHash: 'hash-plain' });
38
+ /** A key holding `full_access`, which does not include `agent:trusted`. */
39
+ const FULL_ACCESS_KEY = payload({ email: 'admin@example.com', apiKeyId: 'key-3', apiKeyHash: 'hash-full' });
40
+ /** A public web-widget guest. */
41
+ const WIDGET_GUEST = payload({ email: 'anonymous@magic-link.local' });
42
+
43
+ /** Each key's `MJ: API Key Scopes` rows, by scope path. */
44
+ const KEY_SCOPES: Record<string, string[]> = {
45
+ 'hash-trusted': ['agent:execute', 'agent:trusted'],
46
+ 'hash-plain': ['agent:execute'],
47
+ 'hash-full': ['full_access'],
48
+ };
49
+
50
+ /** APIKeyEngine.Authorize as the scope evaluator answers it: allowed only by a matching key rule. */
51
+ async function authorizeByKeyScopes(apiKeyHash: string, _app: string, scopePath: string) {
52
+ return KEY_SCOPES[apiKeyHash]?.includes(scopePath)
53
+ ? { Allowed: true, Reason: 'Key allows access', MatchedKeyRule: { ScopePath: scopePath }, EvaluatedRules: [] }
54
+ : { Allowed: false, Reason: 'No matching key scope rules', EvaluatedRules: [] };
55
+ }
56
+
57
+ function caller(userPayload: UserPayload, isWidgetGuestRun = false): ClientAgentRunCaller {
58
+ return { UserPayload: userPayload, IsWidgetGuestRun: isWidgetGuestRun, AgentID: 'agent-1' };
59
+ }
60
+
61
+ beforeEach(() => {
62
+ hoisted.authorize.mockReset().mockImplementation(authorizeByKeyScopes);
63
+ vi.spyOn(UserCache.Instance, 'GetSystemUser').mockReturnValue(SYSTEM_USER);
64
+ vi.spyOn(console, 'error').mockImplementation(() => undefined);
65
+ });
66
+
67
+ afterEach(() => {
68
+ vi.restoreAllMocks();
69
+ });
70
+
71
+ describe('IsTrustedAgentRunCaller', () => {
72
+ it('names the agent:trusted scope', () => {
73
+ expect(TRUSTED_RUN_DATA_SCOPE).toBe('agent:trusted');
74
+ });
75
+
76
+ it('trusts the system user', async () => {
77
+ expect(await IsTrustedAgentRunCaller(caller(SYSTEM_KEY))).toBe(true);
78
+ });
79
+
80
+ it('trusts an API key that holds agent:trusted, checked for the agent being run', async () => {
81
+ expect(await IsTrustedAgentRunCaller(caller(TRUSTED_KEY))).toBe(true);
82
+ expect(hoisted.authorize).toHaveBeenCalledWith(
83
+ 'hash-trusted', 'MJAPI', 'agent:trusted', 'agent-1', SYSTEM_USER, expect.anything(), { skipLogging: true });
84
+ });
85
+
86
+ it('does not trust an API key without agent:trusted: it is treated like its user\'s browser session', async () => {
87
+ expect(await IsTrustedAgentRunCaller(caller(PLAIN_KEY))).toBe(false);
88
+ expect(await IsTrustedAgentRunCaller(caller(INTERACTIVE))).toBe(false);
89
+ });
90
+
91
+ it('does not trust a full_access key: the key must hold agent:trusted itself', async () => {
92
+ expect(await IsTrustedAgentRunCaller(caller(FULL_ACCESS_KEY))).toBe(false);
93
+ });
94
+
95
+ it('does not count an allow that matched no key rule (enforcement off, or a no-scopes default of allow)', async () => {
96
+ hoisted.authorize.mockResolvedValue({ Allowed: true, Reason: 'Enforcement disabled', EvaluatedRules: [] });
97
+
98
+ expect(await IsTrustedAgentRunCaller(caller(PLAIN_KEY))).toBe(false);
99
+ });
100
+
101
+ it('fails closed when the scope check cannot run', async () => {
102
+ hoisted.authorize.mockRejectedValue(new Error('engine not configured'));
103
+ expect(await IsTrustedAgentRunCaller(caller(TRUSTED_KEY))).toBe(false);
104
+
105
+ hoisted.authorize.mockImplementation(authorizeByKeyScopes);
106
+ vi.mocked(UserCache.Instance.GetSystemUser).mockReturnValue(undefined as unknown as UserInfo);
107
+ expect(await IsTrustedAgentRunCaller(caller(TRUSTED_KEY))).toBe(false);
108
+ });
109
+
110
+ it('does not trust an interactive (browser) user, and never asks the key engine', async () => {
111
+ expect(await IsTrustedAgentRunCaller(caller(INTERACTIVE))).toBe(false);
112
+ expect(hoisted.authorize).not.toHaveBeenCalled();
113
+ });
114
+
115
+ it('does not trust a widget-guest run even when the elevated principal is the system user', async () => {
116
+ const elevated = ElevateUserPayload(WIDGET_GUEST, SYSTEM_USER);
117
+ expect(elevated.isSystemUser).toBe(true); // the premise: elevation alone would look trusted
118
+
119
+ expect(await IsTrustedAgentRunCaller(caller(elevated, true))).toBe(false);
120
+ });
121
+
122
+ it('does not trust a widget-guest run on a trusted key either', async () => {
123
+ expect(await IsTrustedAgentRunCaller(caller(TRUSTED_KEY, true))).toBe(false);
124
+ });
125
+
126
+ it('treats an explicit isSystemUser:false, an empty key hash or a non-system api key as untrusted', async () => {
127
+ expect(await IsTrustedAgentRunCaller(caller(payload({ isSystemUser: false, apiKeyId: 'key-1', apiKeyHash: '' })))).toBe(false);
128
+ expect(await IsTrustedAgentRunCaller(caller(payload({ apiKey: 'not-the-system-key' })))).toBe(false);
129
+ });
130
+ });
@@ -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,202 @@
1
+ /**
2
+ * Boot test for the API-root rule (MJ #5674): mounts every core route through the same functions
3
+ * `serve()` calls, on a bare Express app, and checks where they landed.
4
+ *
5
+ * - `MJAPI_BASE_PATH=/api` → every registered path starts with `/api/`, and a real request to
6
+ * `/healthcheck` (origin root) 404s.
7
+ * - `MJAPI_BASE_PATH=/` → the paths are byte-identical to the root-mounted ones MJAPI always used, whatever
8
+ * `GRAPHQL_ROOT_PATH` is (it is only Apollo's path).
9
+ *
10
+ * The router factories are stubbed (they need keys, DB, providers); the MOUNT code is real.
11
+ */
12
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
13
+ import express, { type Application, type Router } from 'express';
14
+ import type { AddressInfo } from 'node:net';
15
+ import type { ServerExtensionLoader } from '@memberjunction/server-extensions-core';
16
+
17
+ const mockConfig = vi.hoisted(() => ({
18
+ configInfo: {
19
+ graphqlRootPath: '/',
20
+ apiBasePath: '/',
21
+ publicUrl: '',
22
+ baseUrl: 'http://localhost',
23
+ graphqlPort: 4000,
24
+ cors: {},
25
+ realtime: { enabled: true, identityVerification: { enabled: true } },
26
+ magicLink: { enabled: true },
27
+ widget: { enabled: true },
28
+ } as Record<string, unknown>,
29
+ }));
30
+ vi.mock('../config.js', () => mockConfig);
31
+
32
+ // vi.mock factories are hoisted above the imports, so the stubs must be hoisted with them.
33
+ const { stubRouter, stubHandler } = await vi.hoisted(async () => {
34
+ const { Router: makeRouter } = await import('express');
35
+ const router = (): Router => makeRouter();
36
+ return { stubRouter: router, stubHandler: () => ({ publicRouter: router(), authenticatedRouter: router() }) };
37
+ });
38
+ vi.mock('../rest/OAuthCallbackHandler.js', () => ({
39
+ CreateOAuthCallbackHandler: () => ({ callbackRouter: stubRouter(), authenticatedRouter: stubRouter() }),
40
+ }));
41
+ vi.mock('../rest/SignatureWebhookHandler.js', () => ({ CreateSignatureWebhookHandler: stubRouter }));
42
+ vi.mock('../rest/MediaStreamHandler.js', () => ({ CreateMediaStreamRouter: stubRouter }));
43
+ vi.mock('../rest/RealtimeSdpBrokerHandler.js', () => ({ CreateRealtimeSdpBrokerRouter: stubRouter }));
44
+ vi.mock('../auth/AuthProviderCatalogRouter.js', () => ({ CreateAuthProviderCatalogRouter: stubRouter, AUTH_CATALOG_MOUNT_PATH: '/auth' }));
45
+ vi.mock('../auth/magicLink/index.js', () => ({
46
+ CreateMagicLinkHandler: stubHandler,
47
+ CreateMagicLinkJwksRouter: stubRouter,
48
+ RegisterMagicLinkAuthProvider: () => undefined,
49
+ MAGIC_LINK_MOUNT_PATH: '/magic-link',
50
+ }));
51
+ vi.mock('../realtimeWidget/index.js', () => ({ CreateWidgetHandler: stubHandler, WIDGET_MOUNT_PATH: '/widget' }));
52
+ vi.mock('../realtimeSessions/index.js', () => ({
53
+ CreateRealtimeVerifyRouter: stubRouter,
54
+ REALTIME_VERIFY_MOUNT_PATH: '/realtime/verify',
55
+ RealtimeSessionVerificationService: { Instance: { IsEnabled: true } },
56
+ }));
57
+ vi.mock('@memberjunction/ai', () => ({ REALTIME_SDP_EXCHANGE_PATH: '/realtime/sdp-exchange' }));
58
+
59
+ import {
60
+ MountHealthcheckRoute,
61
+ MountPublicCoreRoutes,
62
+ MountExtensionHealthRoute,
63
+ MountAuthCatalogRoute,
64
+ MountAuthenticatedCoreRoutes,
65
+ } from '../coreRoutes.js';
66
+ import { ApiRoute } from '../apiRoot.js';
67
+ import { CoreReservedServerExtensionRoots } from '../serverExtensionReservedRoots.js';
68
+
69
+ const MOUNT_METHODS = ['use', 'get', 'post', 'put', 'patch', 'delete', 'all'] as const;
70
+ const log = { LogIf: () => undefined };
71
+ const loader = { HealthCheckAll: async () => [] } as unknown as ServerExtensionLoader;
72
+
73
+ /** Builds an app the way serve() does and returns it plus every path it registered, in order. */
74
+ function bootCoreRoutes(): { app: Application; paths: string[] } {
75
+ const app = express();
76
+ const paths: string[] = [];
77
+ for (const method of MOUNT_METHODS) {
78
+ const original = app[method].bind(app) as (...args: unknown[]) => Application;
79
+ (app as unknown as Record<string, unknown>)[method] = (...args: unknown[]) => {
80
+ if (typeof args[0] === 'string') paths.push(args[0]);
81
+ return original(...args);
82
+ };
83
+ }
84
+ MountHealthcheckRoute(app);
85
+ const routers = MountPublicCoreRoutes(app, 'http://localhost:4000/', log);
86
+ MountExtensionHealthRoute(app, loader);
87
+ MountAuthCatalogRoute(app, log);
88
+ MountAuthenticatedCoreRoutes(app, routers, log);
89
+ return { app, paths };
90
+ }
91
+
92
+ async function statusOf(app: Application, path: string): Promise<number> {
93
+ const server = app.listen(0);
94
+ try {
95
+ await new Promise<void>((resolve) => server.once('listening', () => resolve()));
96
+ const { port } = server.address() as AddressInfo;
97
+ const res = await fetch(`http://127.0.0.1:${port}${path}`);
98
+ return res.status;
99
+ } finally {
100
+ server.close();
101
+ }
102
+ }
103
+
104
+ const ROOT_MOUNTS_BEFORE_5674 = [
105
+ '/healthcheck',
106
+ '/oauth',
107
+ '/esignature',
108
+ '/media',
109
+ '/realtime/sdp-exchange',
110
+ '/magic-link',
111
+ '/widget',
112
+ '/realtime/verify',
113
+ '/health/extensions',
114
+ '/auth',
115
+ '/oauth',
116
+ '/magic-link',
117
+ '/widget',
118
+ ];
119
+
120
+ describe('core routes under MJAPI_BASE_PATH=/api', () => {
121
+ beforeEach(() => {
122
+ mockConfig.configInfo.apiBasePath = '/api';
123
+ mockConfig.configInfo.magicLink = { enabled: true };
124
+ });
125
+
126
+ it('registers every route under /api/', () => {
127
+ const { paths } = bootCoreRoutes();
128
+ expect(paths.length).toBe(ROOT_MOUNTS_BEFORE_5674.length);
129
+ for (const path of paths) {
130
+ expect(path, `${path} is outside MJAPI_BASE_PATH`).toMatch(/^\/api\//);
131
+ }
132
+ });
133
+
134
+ it('also mounts the widget JWKS fallback under /api/ when magic-link is off', () => {
135
+ mockConfig.configInfo.magicLink = { enabled: false };
136
+ const { paths } = bootCoreRoutes();
137
+ expect(paths).toContain('/api/magic-link');
138
+ expect(paths.every((p) => p.startsWith('/api/'))).toBe(true);
139
+ });
140
+
141
+ it('answers at /api/healthcheck and 404s at the origin root', async () => {
142
+ const { app } = bootCoreRoutes();
143
+ expect(await statusOf(app, '/api/healthcheck')).toBe(200);
144
+ expect(await statusOf(app, '/healthcheck')).toBe(404);
145
+ });
146
+
147
+ it('reserves every mounted prefix for extensions (relative to MJAPI_BASE_PATH)', () => {
148
+ const reserved = CoreReservedServerExtensionRoots();
149
+ for (const path of bootCoreRoutes().paths) {
150
+ const relative = path.slice('/api'.length);
151
+ const covered = reserved.some((r) => relative === r || relative.startsWith(`${r}/`));
152
+ expect(covered, `core mount ${path} is not in CoreReservedServerExtensionRoots()`).toBe(true);
153
+ }
154
+ });
155
+ });
156
+
157
+ describe('backward compatibility at MJAPI_BASE_PATH=/', () => {
158
+ beforeEach(() => {
159
+ mockConfig.configInfo.apiBasePath = '/';
160
+ mockConfig.configInfo.graphqlRootPath = '/';
161
+ mockConfig.configInfo.publicUrl = '';
162
+ mockConfig.configInfo.magicLink = { enabled: true };
163
+ });
164
+
165
+ it('mounts byte-identical paths', () => {
166
+ expect(bootCoreRoutes().paths).toEqual(ROOT_MOUNTS_BEFORE_5674);
167
+ });
168
+
169
+ it('ignores GRAPHQL_ROOT_PATH: it is only where Apollo listens', () => {
170
+ mockConfig.configInfo.graphqlRootPath = '/api/';
171
+ expect(bootCoreRoutes().paths).toEqual(ROOT_MOUNTS_BEFORE_5674);
172
+ });
173
+
174
+ it('builds byte-identical URLs', () => {
175
+ expect(ApiRoute('/magic-link/redeem')).toBe('/magic-link/redeem');
176
+ expect(ApiRoute('/media/upload-stage')).toBe('/media/upload-stage');
177
+ expect(ApiRoute('/media/abc?token=t', { absolute: true })).toBe('http://localhost:4000/media/abc?token=t');
178
+ mockConfig.configInfo.publicUrl = 'https://api.example.com';
179
+ expect(ApiRoute('/media/abc', { absolute: true })).toBe('https://api.example.com/media/abc');
180
+ });
181
+ });
182
+
183
+ describe('ApiRoute under a base path', () => {
184
+ it('prefixes paths and appends to MJAPI_PUBLIC_URL without doubling the root', () => {
185
+ mockConfig.configInfo.apiBasePath = '/api/';
186
+ mockConfig.configInfo.publicUrl = 'https://example.com/api/';
187
+ expect(ApiRoute('/media')).toBe('/api/media');
188
+ expect(ApiRoute('/media/abc', { absolute: true })).toBe('https://example.com/api/media/abc');
189
+ });
190
+
191
+ it('falls back to baseUrl:port + MJAPI_BASE_PATH when MJAPI_PUBLIC_URL is unset', () => {
192
+ mockConfig.configInfo.apiBasePath = '/api/';
193
+ mockConfig.configInfo.graphqlRootPath = '/';
194
+ mockConfig.configInfo.publicUrl = '';
195
+ expect(ApiRoute('/magic-link/redeem', { absolute: true })).toBe('http://localhost:4000/api/magic-link/redeem');
196
+ });
197
+
198
+ it('rejects a subpath without a leading slash', () => {
199
+ expect(() => ApiRoute('media')).toThrow(/must start with '\/'/);
200
+ expect(() => ApiRoute('media', { absolute: true })).toThrow(/must start with '\/'/);
201
+ });
202
+ });
@@ -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
+ });