@prosopo/provider 3.15.0 → 4.7.2

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 (623) hide show
  1. package/.turbo/turbo-build$colon$cjs.log +104 -57
  2. package/.turbo/turbo-build$colon$tsc.log +30 -24
  3. package/.turbo/turbo-build.log +104 -58
  4. package/CHANGELOG.md +1328 -0
  5. package/dist/api/admin/apiAdminRoutesProvider.d.ts.map +1 -1
  6. package/dist/api/admin/apiAdminRoutesProvider.js +31 -1
  7. package/dist/api/admin/apiAdminRoutesProvider.js.map +1 -1
  8. package/dist/api/admin/apiClearAllCountersEndpoint.d.ts +14 -0
  9. package/dist/api/admin/apiClearAllCountersEndpoint.d.ts.map +1 -0
  10. package/dist/api/admin/apiClearAllCountersEndpoint.js +59 -0
  11. package/dist/api/admin/apiClearAllCountersEndpoint.js.map +1 -0
  12. package/dist/api/admin/apiDnsEventEndpoint.d.ts +15 -0
  13. package/dist/api/admin/apiDnsEventEndpoint.d.ts.map +1 -0
  14. package/dist/api/admin/apiDnsEventEndpoint.js +64 -0
  15. package/dist/api/admin/apiDnsEventEndpoint.js.map +1 -0
  16. package/dist/api/admin/apiGetAllDecisionMachinesEndpoint.d.ts +13 -0
  17. package/dist/api/admin/apiGetAllDecisionMachinesEndpoint.d.ts.map +1 -0
  18. package/dist/api/admin/apiGetAllDecisionMachinesEndpoint.js +49 -0
  19. package/dist/api/admin/apiGetAllDecisionMachinesEndpoint.js.map +1 -0
  20. package/dist/api/admin/apiGetDecisionMachineEndpoint.d.ts +14 -0
  21. package/dist/api/admin/apiGetDecisionMachineEndpoint.d.ts.map +1 -0
  22. package/dist/api/admin/apiGetDecisionMachineEndpoint.js +42 -0
  23. package/dist/api/admin/apiGetDecisionMachineEndpoint.js.map +1 -0
  24. package/dist/api/admin/apiRegisterSiteKeyEndpoint.d.ts +1 -1
  25. package/dist/api/admin/apiRegisterSiteKeyEndpoint.js +1 -1
  26. package/dist/api/admin/apiRegisterSiteKeysEndpoint.d.ts +14 -0
  27. package/dist/api/admin/apiRegisterSiteKeysEndpoint.d.ts.map +1 -0
  28. package/dist/api/admin/apiRegisterSiteKeysEndpoint.js +33 -0
  29. package/dist/api/admin/apiRegisterSiteKeysEndpoint.js.map +1 -0
  30. package/dist/api/admin/apiRemoveAllDecisionMachinesEndpoint.d.ts +13 -0
  31. package/dist/api/admin/apiRemoveAllDecisionMachinesEndpoint.d.ts.map +1 -0
  32. package/dist/api/admin/apiRemoveAllDecisionMachinesEndpoint.js +41 -0
  33. package/dist/api/admin/apiRemoveAllDecisionMachinesEndpoint.js.map +1 -0
  34. package/dist/api/admin/apiRemoveDecisionMachineEndpoint.d.ts +14 -0
  35. package/dist/api/admin/apiRemoveDecisionMachineEndpoint.d.ts.map +1 -0
  36. package/dist/api/admin/apiRemoveDecisionMachineEndpoint.js +42 -0
  37. package/dist/api/admin/apiRemoveDecisionMachineEndpoint.js.map +1 -0
  38. package/dist/api/admin/apiRemoveDetectorKeyEndpoint.d.ts +2 -2
  39. package/dist/api/admin/apiRemoveDetectorKeyEndpoint.d.ts.map +1 -1
  40. package/dist/api/admin/apiRemoveDetectorKeyEndpoint.js +1 -1
  41. package/dist/api/admin/apiRemoveDetectorKeyEndpoint.js.map +1 -1
  42. package/dist/api/admin/apiRemoveSiteKeyEndpoint.d.ts +14 -0
  43. package/dist/api/admin/apiRemoveSiteKeyEndpoint.d.ts.map +1 -0
  44. package/dist/api/admin/apiRemoveSiteKeyEndpoint.js +32 -0
  45. package/dist/api/admin/apiRemoveSiteKeyEndpoint.js.map +1 -0
  46. package/dist/api/admin/apiRemoveSiteKeysEndpoint.d.ts +14 -0
  47. package/dist/api/admin/apiRemoveSiteKeysEndpoint.d.ts.map +1 -0
  48. package/dist/api/admin/apiRemoveSiteKeysEndpoint.js +34 -0
  49. package/dist/api/admin/apiRemoveSiteKeysEndpoint.js.map +1 -0
  50. package/dist/api/admin/apiToggleMaintenanceModeEndpoint.d.ts +1 -1
  51. package/dist/api/admin/apiToggleMaintenanceModeEndpoint.js +1 -1
  52. package/dist/api/admin/apiUpdateDecisionMachineEndpoint.d.ts +14 -0
  53. package/dist/api/admin/apiUpdateDecisionMachineEndpoint.d.ts.map +1 -0
  54. package/dist/api/admin/apiUpdateDecisionMachineEndpoint.js +60 -0
  55. package/dist/api/admin/apiUpdateDecisionMachineEndpoint.js.map +1 -0
  56. package/dist/api/admin/apiUpdateDetectorKeyEndpoint.d.ts +1 -1
  57. package/dist/api/admin/apiUpdateDetectorKeyEndpoint.d.ts.map +1 -1
  58. package/dist/api/admin/apiUpdateDetectorKeyEndpoint.js +1 -1
  59. package/dist/api/admin/apiUpdateDetectorKeyEndpoint.js.map +1 -1
  60. package/dist/api/blacklistRequestInspector.d.ts +4 -3
  61. package/dist/api/blacklistRequestInspector.d.ts.map +1 -1
  62. package/dist/api/blacklistRequestInspector.js +18 -5
  63. package/dist/api/blacklistRequestInspector.js.map +1 -1
  64. package/dist/api/block.d.ts +2 -1
  65. package/dist/api/block.d.ts.map +1 -1
  66. package/dist/api/block.js +19 -8
  67. package/dist/api/block.js.map +1 -1
  68. package/dist/api/captcha/checkSpamEmail.d.ts +6 -0
  69. package/dist/api/captcha/checkSpamEmail.d.ts.map +1 -0
  70. package/dist/api/captcha/checkSpamEmail.js +80 -0
  71. package/dist/api/captcha/checkSpamEmail.js.map +1 -0
  72. package/dist/api/captcha/getFrictionlessCaptchaChallenge/accessPolicy.d.ts +31 -0
  73. package/dist/api/captcha/getFrictionlessCaptchaChallenge/accessPolicy.d.ts.map +1 -0
  74. package/dist/api/captcha/getFrictionlessCaptchaChallenge/accessPolicy.js +123 -0
  75. package/dist/api/captcha/getFrictionlessCaptchaChallenge/accessPolicy.js.map +1 -0
  76. package/dist/api/captcha/getFrictionlessCaptchaChallenge/constants.d.ts +3 -0
  77. package/dist/api/captcha/getFrictionlessCaptchaChallenge/constants.d.ts.map +1 -0
  78. package/dist/api/captcha/getFrictionlessCaptchaChallenge/constants.js +13 -0
  79. package/dist/api/captcha/getFrictionlessCaptchaChallenge/constants.js.map +1 -0
  80. package/dist/api/captcha/getFrictionlessCaptchaChallenge/decisionMachine.d.ts +36 -0
  81. package/dist/api/captcha/getFrictionlessCaptchaChallenge/decisionMachine.d.ts.map +1 -0
  82. package/dist/api/captcha/getFrictionlessCaptchaChallenge/decisionMachine.js +287 -0
  83. package/dist/api/captcha/getFrictionlessCaptchaChallenge/decisionMachine.js.map +1 -0
  84. package/dist/api/captcha/getFrictionlessCaptchaChallenge/decryptSimdReadings.d.ts +4 -0
  85. package/dist/api/captcha/getFrictionlessCaptchaChallenge/decryptSimdReadings.d.ts.map +1 -0
  86. package/dist/api/captcha/getFrictionlessCaptchaChallenge/decryptSimdReadings.js +14 -0
  87. package/dist/api/captcha/getFrictionlessCaptchaChallenge/decryptSimdReadings.js.map +1 -0
  88. package/dist/api/captcha/getFrictionlessCaptchaChallenge/handler.d.ts +7 -0
  89. package/dist/api/captcha/getFrictionlessCaptchaChallenge/handler.d.ts.map +1 -0
  90. package/dist/api/captcha/getFrictionlessCaptchaChallenge/handler.js +316 -0
  91. package/dist/api/captcha/getFrictionlessCaptchaChallenge/handler.js.map +1 -0
  92. package/dist/api/captcha/getFrictionlessCaptchaChallenge/honeypotResponse.d.ts +5 -0
  93. package/dist/api/captcha/getFrictionlessCaptchaChallenge/honeypotResponse.d.ts.map +1 -0
  94. package/dist/api/captcha/getFrictionlessCaptchaChallenge/honeypotResponse.js +17 -0
  95. package/dist/api/captcha/getFrictionlessCaptchaChallenge/honeypotResponse.js.map +1 -0
  96. package/dist/api/captcha/getFrictionlessCaptchaChallenge/sessionDedup.d.ts +14 -0
  97. package/dist/api/captcha/getFrictionlessCaptchaChallenge/sessionDedup.d.ts.map +1 -0
  98. package/dist/api/captcha/getFrictionlessCaptchaChallenge/sessionDedup.js +28 -0
  99. package/dist/api/captcha/getFrictionlessCaptchaChallenge/sessionDedup.js.map +1 -0
  100. package/dist/api/captcha/getFrictionlessCaptchaChallenge/shortCircuit.d.ts +23 -0
  101. package/dist/api/captcha/getFrictionlessCaptchaChallenge/shortCircuit.d.ts.map +1 -0
  102. package/dist/api/captcha/getFrictionlessCaptchaChallenge/shortCircuit.js +61 -0
  103. package/dist/api/captcha/getFrictionlessCaptchaChallenge/shortCircuit.js.map +1 -0
  104. package/dist/api/captcha/getFrictionlessCaptchaChallenge.d.ts +1 -7
  105. package/dist/api/captcha/getFrictionlessCaptchaChallenge.d.ts.map +1 -1
  106. package/dist/api/captcha/getFrictionlessCaptchaChallenge.js +2 -364
  107. package/dist/api/captcha/getFrictionlessCaptchaChallenge.js.map +1 -1
  108. package/dist/api/captcha/getImageCaptchaChallenge.d.ts.map +1 -1
  109. package/dist/api/captcha/getImageCaptchaChallenge.js +36 -9
  110. package/dist/api/captcha/getImageCaptchaChallenge.js.map +1 -1
  111. package/dist/api/captcha/getPoWCaptchaChallenge.d.ts.map +1 -1
  112. package/dist/api/captcha/getPoWCaptchaChallenge.js +49 -7
  113. package/dist/api/captcha/getPoWCaptchaChallenge.js.map +1 -1
  114. package/dist/api/captcha/getPuzzleCaptchaChallenge.d.ts +7 -0
  115. package/dist/api/captcha/getPuzzleCaptchaChallenge.d.ts.map +1 -0
  116. package/dist/api/captcha/getPuzzleCaptchaChallenge.js +201 -0
  117. package/dist/api/captcha/getPuzzleCaptchaChallenge.js.map +1 -0
  118. package/dist/api/captcha/maintenanceModeResponses.d.ts +5 -0
  119. package/dist/api/captcha/maintenanceModeResponses.d.ts.map +1 -0
  120. package/dist/api/captcha/maintenanceModeResponses.js +42 -0
  121. package/dist/api/captcha/maintenanceModeResponses.js.map +1 -0
  122. package/dist/api/captcha/submitImageCaptchaSolution.d.ts +1 -2
  123. package/dist/api/captcha/submitImageCaptchaSolution.d.ts.map +1 -1
  124. package/dist/api/captcha/submitImageCaptchaSolution.js +19 -2
  125. package/dist/api/captcha/submitImageCaptchaSolution.js.map +1 -1
  126. package/dist/api/captcha/submitPoWCaptchaSolution.d.ts.map +1 -1
  127. package/dist/api/captcha/submitPoWCaptchaSolution.js +84 -7
  128. package/dist/api/captcha/submitPoWCaptchaSolution.js.map +1 -1
  129. package/dist/api/captcha/submitPuzzleCaptchaSolution.d.ts +6 -0
  130. package/dist/api/captcha/submitPuzzleCaptchaSolution.d.ts.map +1 -0
  131. package/dist/api/captcha/submitPuzzleCaptchaSolution.js +108 -0
  132. package/dist/api/captcha/submitPuzzleCaptchaSolution.js.map +1 -0
  133. package/dist/api/captcha.d.ts.map +1 -1
  134. package/dist/api/captcha.js +28 -3
  135. package/dist/api/captcha.js.map +1 -1
  136. package/dist/api/dnsEventUrl.d.ts +3 -0
  137. package/dist/api/dnsEventUrl.d.ts.map +1 -0
  138. package/dist/api/dnsEventUrl.js +25 -0
  139. package/dist/api/dnsEventUrl.js.map +1 -0
  140. package/dist/api/domainMiddleware.d.ts.map +1 -1
  141. package/dist/api/domainMiddleware.js +36 -5
  142. package/dist/api/domainMiddleware.js.map +1 -1
  143. package/dist/api/ipInfoMiddleware.d.ts +4 -0
  144. package/dist/api/ipInfoMiddleware.d.ts.map +1 -0
  145. package/dist/api/ipInfoMiddleware.js +20 -0
  146. package/dist/api/ipInfoMiddleware.js.map +1 -0
  147. package/dist/api/ja4Middleware.d.ts +1 -1
  148. package/dist/api/ja4Middleware.d.ts.map +1 -1
  149. package/dist/api/ja4Middleware.js +1 -1
  150. package/dist/api/ja4Middleware.js.map +1 -1
  151. package/dist/api/startProviderApi.d.ts +9 -0
  152. package/dist/api/startProviderApi.d.ts.map +1 -0
  153. package/dist/api/startProviderApi.js +217 -0
  154. package/dist/api/startProviderApi.js.map +1 -0
  155. package/dist/api/testSiteKey.d.ts +4 -0
  156. package/dist/api/testSiteKey.d.ts.map +1 -0
  157. package/dist/api/testSiteKey.js +17 -0
  158. package/dist/api/testSiteKey.js.map +1 -0
  159. package/dist/api/validateAddress.d.ts +1 -1
  160. package/dist/api/validateAddress.d.ts.map +1 -1
  161. package/dist/api/validateAddress.js.map +1 -1
  162. package/dist/api/verify.d.ts.map +1 -1
  163. package/dist/api/verify.js +145 -10
  164. package/dist/api/verify.js.map +1 -1
  165. package/dist/cjs/api/admin/apiAdminRoutesProvider.cjs +31 -1
  166. package/dist/cjs/api/admin/apiClearAllCountersEndpoint.cjs +59 -0
  167. package/dist/cjs/api/admin/apiDnsEventEndpoint.cjs +65 -0
  168. package/dist/cjs/api/admin/apiGetAllDecisionMachinesEndpoint.cjs +49 -0
  169. package/dist/cjs/api/admin/apiGetDecisionMachineEndpoint.cjs +42 -0
  170. package/dist/cjs/api/admin/apiRegisterSiteKeyEndpoint.cjs +5 -5
  171. package/dist/cjs/api/admin/apiRegisterSiteKeysEndpoint.cjs +34 -0
  172. package/dist/cjs/api/admin/apiRemoveAllDecisionMachinesEndpoint.cjs +41 -0
  173. package/dist/cjs/api/admin/apiRemoveDecisionMachineEndpoint.cjs +42 -0
  174. package/dist/cjs/api/admin/apiRemoveDetectorKeyEndpoint.cjs +6 -6
  175. package/dist/cjs/api/admin/apiRemoveSiteKeyEndpoint.cjs +33 -0
  176. package/dist/cjs/api/admin/apiRemoveSiteKeysEndpoint.cjs +35 -0
  177. package/dist/cjs/api/admin/apiToggleMaintenanceModeEndpoint.cjs +5 -5
  178. package/dist/cjs/api/admin/apiUpdateDecisionMachineEndpoint.cjs +60 -0
  179. package/dist/cjs/api/admin/apiUpdateDetectorKeyEndpoint.cjs +7 -7
  180. package/dist/cjs/api/blacklistRequestInspector.cjs +18 -5
  181. package/dist/cjs/api/block.cjs +19 -8
  182. package/dist/cjs/api/captcha/checkSpamEmail.cjs +79 -0
  183. package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge/accessPolicy.cjs +123 -0
  184. package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge/constants.cjs +13 -0
  185. package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge/decisionMachine.cjs +287 -0
  186. package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge/decryptSimdReadings.cjs +14 -0
  187. package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge/handler.cjs +315 -0
  188. package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge/honeypotResponse.cjs +17 -0
  189. package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge/sessionDedup.cjs +28 -0
  190. package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge/shortCircuit.cjs +61 -0
  191. package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge.cjs +2 -364
  192. package/dist/cjs/api/captcha/getImageCaptchaChallenge.cjs +35 -8
  193. package/dist/cjs/api/captcha/getPoWCaptchaChallenge.cjs +48 -6
  194. package/dist/cjs/api/captcha/getPuzzleCaptchaChallenge.cjs +200 -0
  195. package/dist/cjs/api/captcha/maintenanceModeResponses.cjs +42 -0
  196. package/dist/cjs/api/captcha/submitImageCaptchaSolution.cjs +19 -2
  197. package/dist/cjs/api/captcha/submitPoWCaptchaSolution.cjs +82 -5
  198. package/dist/cjs/api/captcha/submitPuzzleCaptchaSolution.cjs +107 -0
  199. package/dist/cjs/api/captcha.cjs +29 -4
  200. package/dist/cjs/api/dnsEventUrl.cjs +25 -0
  201. package/dist/cjs/api/domainMiddleware.cjs +36 -5
  202. package/dist/cjs/api/ipInfoMiddleware.cjs +20 -0
  203. package/dist/cjs/api/ja4Middleware.cjs +7 -7
  204. package/dist/cjs/api/startProviderApi.cjs +240 -0
  205. package/dist/cjs/api/testSiteKey.cjs +17 -0
  206. package/dist/cjs/api/verify.cjs +144 -9
  207. package/dist/cjs/compositeIpAddress.cjs +6 -6
  208. package/dist/cjs/index.cjs +15 -0
  209. package/dist/cjs/rules/lang.cjs +1 -1
  210. package/dist/cjs/schedulers/updateSpamEmailDomains.cjs +46 -0
  211. package/dist/cjs/services/ipComparison.cjs +9 -10
  212. package/dist/cjs/tasks/captchaManager.cjs +338 -66
  213. package/dist/cjs/tasks/client/clientTasks.cjs +115 -4
  214. package/dist/cjs/tasks/decisionMachine/decisionMachineRunner.cjs +285 -0
  215. package/dist/cjs/tasks/detection/decodeBehavior.cjs +225 -228
  216. package/dist/cjs/tasks/detection/decodeBehavior.js +1 -1
  217. package/dist/cjs/tasks/detection/decodePayload.cjs +707 -635
  218. package/dist/cjs/tasks/detection/decodePayload.js +1 -1
  219. package/dist/cjs/tasks/detection/decodeSimd.cjs +348 -0
  220. package/dist/cjs/tasks/detection/decodeSimd.js +15 -0
  221. package/dist/cjs/tasks/detection/getBotScore.cjs +6 -2
  222. package/dist/cjs/tasks/frictionless/frictionlessTasks.cjs +149 -43
  223. package/dist/cjs/tasks/frictionless/frictionlessTasksUtils.cjs +10 -7
  224. package/dist/cjs/tasks/frictionless/routingMachine.cjs +58 -0
  225. package/dist/cjs/tasks/imgCaptcha/imgCaptchaTasks.cjs +371 -57
  226. package/dist/cjs/tasks/powCaptcha/powTasks.cjs +357 -167
  227. package/dist/cjs/tasks/puzzleCaptcha/puzzleTasks.cjs +525 -0
  228. package/dist/cjs/tasks/puzzleCaptcha/puzzleTasksUtils.cjs +7 -0
  229. package/dist/cjs/tasks/spam/checkSpamEmail.cjs +147 -0
  230. package/dist/cjs/tasks/spam/checkTrafficFilter.cjs +41 -0
  231. package/dist/cjs/tasks/spam/evaluateEmailSpamRules.cjs +92 -0
  232. package/dist/cjs/tasks/spam/updateSpamEmailDomains.cjs +58 -0
  233. package/dist/cjs/tasks/tasks.cjs +111 -13
  234. package/dist/cjs/util/usageCounters.cjs +201 -0
  235. package/dist/cjs/util.cjs +3 -4
  236. package/dist/cjs/utils/devicePlatform.cjs +10 -0
  237. package/dist/cjs/utils/dns.cjs +102 -0
  238. package/dist/cjs/utils/honeypot/encoders.cjs +86 -0
  239. package/dist/cjs/utils/honeypot/phraseBank.cjs +47 -0
  240. package/dist/cjs/utils/normalizeRequestIp.cjs +27 -0
  241. package/dist/compositeIpAddress.d.ts +1 -1
  242. package/dist/compositeIpAddress.d.ts.map +1 -1
  243. package/dist/compositeIpAddress.js +1 -1
  244. package/dist/compositeIpAddress.js.map +1 -1
  245. package/dist/index.d.ts +4 -0
  246. package/dist/index.d.ts.map +1 -1
  247. package/dist/index.js +12 -0
  248. package/dist/index.js.map +1 -1
  249. package/dist/rules/lang.js +1 -1
  250. package/dist/rules/lang.js.map +1 -1
  251. package/dist/schedulers/updateSpamEmailDomains.d.ts +4 -0
  252. package/dist/schedulers/updateSpamEmailDomains.d.ts.map +1 -0
  253. package/dist/schedulers/updateSpamEmailDomains.js +46 -0
  254. package/dist/schedulers/updateSpamEmailDomains.js.map +1 -0
  255. package/dist/services/ipComparison.d.ts +2 -1
  256. package/dist/services/ipComparison.d.ts.map +1 -1
  257. package/dist/services/ipComparison.js +3 -4
  258. package/dist/services/ipComparison.js.map +1 -1
  259. package/dist/tasks/captchaManager.d.ts +24 -6
  260. package/dist/tasks/captchaManager.d.ts.map +1 -1
  261. package/dist/tasks/captchaManager.js +336 -64
  262. package/dist/tasks/captchaManager.js.map +1 -1
  263. package/dist/tasks/client/clientTasks.d.ts +49 -2
  264. package/dist/tasks/client/clientTasks.d.ts.map +1 -1
  265. package/dist/tasks/client/clientTasks.js +116 -5
  266. package/dist/tasks/client/clientTasks.js.map +1 -1
  267. package/dist/tasks/dataset/datasetTasks.d.ts +1 -1
  268. package/dist/tasks/dataset/datasetTasks.d.ts.map +1 -1
  269. package/dist/tasks/dataset/datasetTasks.js.map +1 -1
  270. package/dist/tasks/decisionMachine/decisionMachineRunner.d.ts +20 -0
  271. package/dist/tasks/decisionMachine/decisionMachineRunner.d.ts.map +1 -0
  272. package/dist/tasks/decisionMachine/decisionMachineRunner.js +285 -0
  273. package/dist/tasks/decisionMachine/decisionMachineRunner.js.map +1 -0
  274. package/dist/tasks/detection/decodeBehavior.d.ts +2 -2
  275. package/dist/tasks/detection/decodeBehavior.d.ts.map +1 -1
  276. package/dist/tasks/detection/decodeBehavior.js +1 -1
  277. package/dist/tasks/detection/decodeBehavior.js.map +1 -1
  278. package/dist/tasks/detection/decodePayload.d.ts +2 -2
  279. package/dist/tasks/detection/decodePayload.d.ts.map +1 -1
  280. package/dist/tasks/detection/decodePayload.js +1 -1
  281. package/dist/tasks/detection/decodePayload.js.map +1 -1
  282. package/dist/tasks/detection/decodeSimd.d.ts +3 -0
  283. package/dist/tasks/detection/decodeSimd.d.ts.map +1 -0
  284. package/dist/tasks/detection/decodeSimd.js +15 -0
  285. package/dist/tasks/detection/decodeSimd.js.map +1 -0
  286. package/dist/tasks/detection/getBehavioralData.d.ts +1 -1
  287. package/dist/tasks/detection/getBotScore.d.ts +4 -0
  288. package/dist/tasks/detection/getBotScore.d.ts.map +1 -1
  289. package/dist/tasks/detection/getBotScore.js +8 -4
  290. package/dist/tasks/detection/getBotScore.js.map +1 -1
  291. package/dist/tasks/frictionless/frictionlessTasks.d.ts +20 -13
  292. package/dist/tasks/frictionless/frictionlessTasks.d.ts.map +1 -1
  293. package/dist/tasks/frictionless/frictionlessTasks.js +147 -43
  294. package/dist/tasks/frictionless/frictionlessTasks.js.map +1 -1
  295. package/dist/tasks/frictionless/frictionlessTasksUtils.d.ts +2 -2
  296. package/dist/tasks/frictionless/frictionlessTasksUtils.d.ts.map +1 -1
  297. package/dist/tasks/frictionless/frictionlessTasksUtils.js +10 -7
  298. package/dist/tasks/frictionless/frictionlessTasksUtils.js.map +1 -1
  299. package/dist/tasks/frictionless/routingMachine.d.ts +15 -0
  300. package/dist/tasks/frictionless/routingMachine.d.ts.map +1 -0
  301. package/dist/tasks/frictionless/routingMachine.js +58 -0
  302. package/dist/tasks/frictionless/routingMachine.js.map +1 -0
  303. package/dist/tasks/imgCaptcha/imgCaptchaTasks.d.ts +14 -10
  304. package/dist/tasks/imgCaptcha/imgCaptchaTasks.d.ts.map +1 -1
  305. package/dist/tasks/imgCaptcha/imgCaptchaTasks.js +371 -57
  306. package/dist/tasks/imgCaptcha/imgCaptchaTasks.js.map +1 -1
  307. package/dist/tasks/powCaptcha/powTasks.d.ts +24 -8
  308. package/dist/tasks/powCaptcha/powTasks.d.ts.map +1 -1
  309. package/dist/tasks/powCaptcha/powTasks.js +358 -168
  310. package/dist/tasks/powCaptcha/powTasks.js.map +1 -1
  311. package/dist/tasks/puzzleCaptcha/puzzleTasks.d.ts +32 -0
  312. package/dist/tasks/puzzleCaptcha/puzzleTasks.d.ts.map +1 -0
  313. package/dist/tasks/puzzleCaptcha/puzzleTasks.js +525 -0
  314. package/dist/tasks/puzzleCaptcha/puzzleTasks.js.map +1 -0
  315. package/dist/tasks/puzzleCaptcha/puzzleTasksUtils.d.ts +2 -0
  316. package/dist/tasks/puzzleCaptcha/puzzleTasksUtils.d.ts.map +1 -0
  317. package/dist/tasks/puzzleCaptcha/puzzleTasksUtils.js +7 -0
  318. package/dist/tasks/puzzleCaptcha/puzzleTasksUtils.js.map +1 -0
  319. package/dist/tasks/spam/checkSpamEmail.d.ts +5 -0
  320. package/dist/tasks/spam/checkSpamEmail.d.ts.map +1 -0
  321. package/dist/tasks/spam/checkSpamEmail.js +147 -0
  322. package/dist/tasks/spam/checkSpamEmail.js.map +1 -0
  323. package/dist/tasks/spam/checkTrafficFilter.d.ts +10 -0
  324. package/dist/tasks/spam/checkTrafficFilter.d.ts.map +1 -0
  325. package/dist/tasks/spam/checkTrafficFilter.js +41 -0
  326. package/dist/tasks/spam/checkTrafficFilter.js.map +1 -0
  327. package/dist/tasks/spam/evaluateEmailSpamRules.d.ts +16 -0
  328. package/dist/tasks/spam/evaluateEmailSpamRules.d.ts.map +1 -0
  329. package/dist/tasks/spam/evaluateEmailSpamRules.js +92 -0
  330. package/dist/tasks/spam/evaluateEmailSpamRules.js.map +1 -0
  331. package/dist/tasks/spam/updateSpamEmailDomains.d.ts +4 -0
  332. package/dist/tasks/spam/updateSpamEmailDomains.d.ts.map +1 -0
  333. package/dist/tasks/spam/updateSpamEmailDomains.js +58 -0
  334. package/dist/tasks/spam/updateSpamEmailDomains.js.map +1 -0
  335. package/dist/tasks/tasks.d.ts +12 -1
  336. package/dist/tasks/tasks.d.ts.map +1 -1
  337. package/dist/tasks/tasks.js +102 -4
  338. package/dist/tasks/tasks.js.map +1 -1
  339. package/dist/tests/integration/api/admin/apiRegisterSiteKeyEndpoint.integration.test.js +4 -0
  340. package/dist/tests/integration/api/admin/apiRegisterSiteKeyEndpoint.integration.test.js.map +1 -1
  341. package/dist/tests/integration/api/admin/apiRegisterSiteKeysEndpoint.integration.test.d.ts +2 -0
  342. package/dist/tests/integration/api/admin/apiRegisterSiteKeysEndpoint.integration.test.d.ts.map +1 -0
  343. package/dist/tests/integration/api/admin/apiRegisterSiteKeysEndpoint.integration.test.js +107 -0
  344. package/dist/tests/integration/api/admin/apiRegisterSiteKeysEndpoint.integration.test.js.map +1 -0
  345. package/dist/tests/integration/api/blacklistRequestInspector.integration.test.js +45 -6
  346. package/dist/tests/integration/api/blacklistRequestInspector.integration.test.js.map +1 -1
  347. package/dist/tests/integration/clientSettingsPersistence.integration.test.d.ts +2 -0
  348. package/dist/tests/integration/clientSettingsPersistence.integration.test.d.ts.map +1 -0
  349. package/dist/tests/integration/clientSettingsPersistence.integration.test.js +165 -0
  350. package/dist/tests/integration/clientSettingsPersistence.integration.test.js.map +1 -0
  351. package/dist/tests/integration/decisionMachines.integration.test.d.ts +2 -0
  352. package/dist/tests/integration/decisionMachines.integration.test.d.ts.map +1 -0
  353. package/dist/tests/integration/decisionMachines.integration.test.js +511 -0
  354. package/dist/tests/integration/decisionMachines.integration.test.js.map +1 -0
  355. package/dist/tests/integration/imgCaptcha.integration.test.js +418 -44
  356. package/dist/tests/integration/imgCaptcha.integration.test.js.map +1 -1
  357. package/dist/tests/integration/ipValidation.integration.test.js +10 -4
  358. package/dist/tests/integration/ipValidation.integration.test.js.map +1 -1
  359. package/dist/tests/integration/mocks/solvedTestCaptchas.js +16 -16
  360. package/dist/tests/integration/mocks/solvedTestCaptchas.js.map +1 -1
  361. package/dist/tests/integration/powCaptcha.integration.test.js +218 -23
  362. package/dist/tests/integration/powCaptcha.integration.test.js.map +1 -1
  363. package/dist/tests/integration/registerSitekey.d.ts.map +1 -1
  364. package/dist/tests/integration/registerSitekey.js +2 -0
  365. package/dist/tests/integration/registerSitekey.js.map +1 -1
  366. package/dist/tests/integration/routingDecisionMachines.integration.test.d.ts +2 -0
  367. package/dist/tests/integration/routingDecisionMachines.integration.test.d.ts.map +1 -0
  368. package/dist/tests/integration/routingDecisionMachines.integration.test.js +276 -0
  369. package/dist/tests/integration/routingDecisionMachines.integration.test.js.map +1 -0
  370. package/dist/tests/integration/testUtils.d.ts +4 -0
  371. package/dist/tests/integration/testUtils.d.ts.map +1 -0
  372. package/dist/tests/integration/testUtils.js +15 -0
  373. package/dist/tests/integration/testUtils.js.map +1 -0
  374. package/dist/tests/integration/usageCounters.integration.test.d.ts +2 -0
  375. package/dist/tests/integration/usageCounters.integration.test.d.ts.map +1 -0
  376. package/dist/tests/integration/usageCounters.integration.test.js +103 -0
  377. package/dist/tests/integration/usageCounters.integration.test.js.map +1 -0
  378. package/dist/tests/unit/api/admin/apiClearAllCountersEndpoint.unit.test.d.ts +2 -0
  379. package/dist/tests/unit/api/admin/apiClearAllCountersEndpoint.unit.test.d.ts.map +1 -0
  380. package/dist/tests/unit/api/admin/apiClearAllCountersEndpoint.unit.test.js +63 -0
  381. package/dist/tests/unit/api/admin/apiClearAllCountersEndpoint.unit.test.js.map +1 -0
  382. package/dist/tests/unit/api/admin/apiRegisterSiteKeyEndpoint.unit.test.d.ts +2 -0
  383. package/dist/tests/unit/api/admin/apiRegisterSiteKeyEndpoint.unit.test.d.ts.map +1 -0
  384. package/dist/tests/unit/api/admin/apiRegisterSiteKeyEndpoint.unit.test.js +55 -0
  385. package/dist/tests/unit/api/admin/apiRegisterSiteKeyEndpoint.unit.test.js.map +1 -0
  386. package/dist/tests/unit/api/admin/apiRegisterSiteKeysEndpoint.unit.test.d.ts +2 -0
  387. package/dist/tests/unit/api/admin/apiRegisterSiteKeysEndpoint.unit.test.d.ts.map +1 -0
  388. package/dist/tests/unit/api/admin/apiRegisterSiteKeysEndpoint.unit.test.js +67 -0
  389. package/dist/tests/unit/api/admin/apiRegisterSiteKeysEndpoint.unit.test.js.map +1 -0
  390. package/dist/tests/unit/api/admin/apiRemoveDetectorKeyEndpoint.unit.test.d.ts +2 -0
  391. package/dist/tests/unit/api/admin/apiRemoveDetectorKeyEndpoint.unit.test.d.ts.map +1 -0
  392. package/dist/tests/unit/api/admin/apiRemoveDetectorKeyEndpoint.unit.test.js +56 -0
  393. package/dist/tests/unit/api/admin/apiRemoveDetectorKeyEndpoint.unit.test.js.map +1 -0
  394. package/dist/tests/unit/api/admin/apiToggleMaintenanceModeEndpoint.unit.test.d.ts +2 -0
  395. package/dist/tests/unit/api/admin/apiToggleMaintenanceModeEndpoint.unit.test.d.ts.map +1 -0
  396. package/dist/tests/unit/api/admin/apiToggleMaintenanceModeEndpoint.unit.test.js +90 -0
  397. package/dist/tests/unit/api/admin/apiToggleMaintenanceModeEndpoint.unit.test.js.map +1 -0
  398. package/dist/tests/unit/api/admin/apiUpdateDetectorKeyEndpoint.unit.test.d.ts +2 -0
  399. package/dist/tests/unit/api/admin/apiUpdateDetectorKeyEndpoint.unit.test.d.ts.map +1 -0
  400. package/dist/tests/unit/api/admin/apiUpdateDetectorKeyEndpoint.unit.test.js +59 -0
  401. package/dist/tests/unit/api/admin/apiUpdateDetectorKeyEndpoint.unit.test.js.map +1 -0
  402. package/dist/tests/unit/api/adminRoutes.unit.test.d.ts +2 -0
  403. package/dist/tests/unit/api/adminRoutes.unit.test.d.ts.map +1 -0
  404. package/dist/tests/unit/api/adminRoutes.unit.test.js +131 -0
  405. package/dist/tests/unit/api/adminRoutes.unit.test.js.map +1 -0
  406. package/dist/tests/unit/api/blacklistRequestInspector.unit.test.js +79 -2
  407. package/dist/tests/unit/api/blacklistRequestInspector.unit.test.js.map +1 -1
  408. package/dist/tests/unit/api/block.unit.test.d.ts +2 -0
  409. package/dist/tests/unit/api/block.unit.test.d.ts.map +1 -0
  410. package/dist/tests/unit/api/block.unit.test.js +60 -0
  411. package/dist/tests/unit/api/block.unit.test.js.map +1 -0
  412. package/dist/tests/unit/api/captcha/getFrictionlessCaptchaChallenge/accessPolicy.unit.test.d.ts +2 -0
  413. package/dist/tests/unit/api/captcha/getFrictionlessCaptchaChallenge/accessPolicy.unit.test.d.ts.map +1 -0
  414. package/dist/tests/unit/api/captcha/getFrictionlessCaptchaChallenge/accessPolicy.unit.test.js +118 -0
  415. package/dist/tests/unit/api/captcha/getFrictionlessCaptchaChallenge/accessPolicy.unit.test.js.map +1 -0
  416. package/dist/tests/unit/api/captcha/getFrictionlessCaptchaChallenge/constants.unit.test.d.ts +2 -0
  417. package/dist/tests/unit/api/captcha/getFrictionlessCaptchaChallenge/constants.unit.test.d.ts.map +1 -0
  418. package/dist/tests/unit/api/captcha/getFrictionlessCaptchaChallenge/constants.unit.test.js +37 -0
  419. package/dist/tests/unit/api/captcha/getFrictionlessCaptchaChallenge/constants.unit.test.js.map +1 -0
  420. package/dist/tests/unit/api/captcha/getFrictionlessCaptchaChallenge/decisionMachine.unit.test.d.ts +2 -0
  421. package/dist/tests/unit/api/captcha/getFrictionlessCaptchaChallenge/decisionMachine.unit.test.d.ts.map +1 -0
  422. package/dist/tests/unit/api/captcha/getFrictionlessCaptchaChallenge/decisionMachine.unit.test.js +154 -0
  423. package/dist/tests/unit/api/captcha/getFrictionlessCaptchaChallenge/decisionMachine.unit.test.js.map +1 -0
  424. package/dist/tests/unit/api/captcha/getFrictionlessCaptchaChallenge/decryptSimdReadings.unit.test.d.ts +2 -0
  425. package/dist/tests/unit/api/captcha/getFrictionlessCaptchaChallenge/decryptSimdReadings.unit.test.d.ts.map +1 -0
  426. package/dist/tests/unit/api/captcha/getFrictionlessCaptchaChallenge/decryptSimdReadings.unit.test.js +46 -0
  427. package/dist/tests/unit/api/captcha/getFrictionlessCaptchaChallenge/decryptSimdReadings.unit.test.js.map +1 -0
  428. package/dist/tests/unit/api/captcha/getFrictionlessCaptchaChallenge/sessionDedup.unit.test.d.ts +2 -0
  429. package/dist/tests/unit/api/captcha/getFrictionlessCaptchaChallenge/sessionDedup.unit.test.d.ts.map +1 -0
  430. package/dist/tests/unit/api/captcha/getFrictionlessCaptchaChallenge/sessionDedup.unit.test.js +69 -0
  431. package/dist/tests/unit/api/captcha/getFrictionlessCaptchaChallenge/sessionDedup.unit.test.js.map +1 -0
  432. package/dist/tests/unit/api/captcha/getFrictionlessCaptchaChallenge/shortCircuit.unit.test.d.ts +2 -0
  433. package/dist/tests/unit/api/captcha/getFrictionlessCaptchaChallenge/shortCircuit.unit.test.d.ts.map +1 -0
  434. package/dist/tests/unit/api/captcha/getFrictionlessCaptchaChallenge/shortCircuit.unit.test.js +98 -0
  435. package/dist/tests/unit/api/captcha/getFrictionlessCaptchaChallenge/shortCircuit.unit.test.js.map +1 -0
  436. package/dist/tests/unit/api/captcha/maintenanceModeResponses.unit.test.d.ts +2 -0
  437. package/dist/tests/unit/api/captcha/maintenanceModeResponses.unit.test.d.ts.map +1 -0
  438. package/dist/tests/unit/api/captcha/maintenanceModeResponses.unit.test.js +60 -0
  439. package/dist/tests/unit/api/captcha/maintenanceModeResponses.unit.test.js.map +1 -0
  440. package/dist/tests/unit/api/captcha/maintenanceModeShortCircuit.unit.test.d.ts +2 -0
  441. package/dist/tests/unit/api/captcha/maintenanceModeShortCircuit.unit.test.d.ts.map +1 -0
  442. package/dist/tests/unit/api/captcha/maintenanceModeShortCircuit.unit.test.js +134 -0
  443. package/dist/tests/unit/api/captcha/maintenanceModeShortCircuit.unit.test.js.map +1 -0
  444. package/dist/tests/unit/api/captcha.unit.test.d.ts +2 -0
  445. package/dist/tests/unit/api/captcha.unit.test.d.ts.map +1 -0
  446. package/dist/tests/unit/api/captcha.unit.test.js +39 -0
  447. package/dist/tests/unit/api/captcha.unit.test.js.map +1 -0
  448. package/dist/tests/unit/api/getFrictionlessCaptchaChallenge.unit.test.js +195 -2
  449. package/dist/tests/unit/api/getFrictionlessCaptchaChallenge.unit.test.js.map +1 -1
  450. package/dist/tests/unit/api/headerCheckMiddleware.unit.test.d.ts +2 -0
  451. package/dist/tests/unit/api/headerCheckMiddleware.unit.test.d.ts.map +1 -0
  452. package/dist/tests/unit/api/headerCheckMiddleware.unit.test.js +126 -0
  453. package/dist/tests/unit/api/headerCheckMiddleware.unit.test.js.map +1 -0
  454. package/dist/tests/unit/api/ignoreMiddleware.unit.test.js +103 -33
  455. package/dist/tests/unit/api/ignoreMiddleware.unit.test.js.map +1 -1
  456. package/dist/tests/unit/api/ja4Middleware.unit.test.js +130 -49
  457. package/dist/tests/unit/api/ja4Middleware.unit.test.js.map +1 -1
  458. package/dist/tests/unit/api/public.unit.test.d.ts +2 -0
  459. package/dist/tests/unit/api/public.unit.test.d.ts.map +1 -0
  460. package/dist/tests/unit/api/public.unit.test.js +198 -0
  461. package/dist/tests/unit/api/public.unit.test.js.map +1 -0
  462. package/dist/tests/unit/api/robotsMiddleware.unit.test.d.ts +2 -0
  463. package/dist/tests/unit/api/robotsMiddleware.unit.test.d.ts.map +1 -0
  464. package/dist/tests/unit/api/robotsMiddleware.unit.test.js +50 -0
  465. package/dist/tests/unit/api/robotsMiddleware.unit.test.js.map +1 -0
  466. package/dist/tests/unit/api/testSiteKey.unit.test.d.ts +2 -0
  467. package/dist/tests/unit/api/testSiteKey.unit.test.d.ts.map +1 -0
  468. package/dist/tests/unit/api/testSiteKey.unit.test.js +51 -0
  469. package/dist/tests/unit/api/testSiteKey.unit.test.js.map +1 -0
  470. package/dist/tests/unit/api/validateAddress.unit.test.d.ts +2 -0
  471. package/dist/tests/unit/api/validateAddress.unit.test.d.ts.map +1 -0
  472. package/dist/tests/unit/api/validateAddress.unit.test.js +141 -0
  473. package/dist/tests/unit/api/validateAddress.unit.test.js.map +1 -0
  474. package/dist/tests/unit/compositeIpAddress.unit.test.js +132 -60
  475. package/dist/tests/unit/compositeIpAddress.unit.test.js.map +1 -1
  476. package/dist/tests/unit/pairs.unit.test.js +174 -28
  477. package/dist/tests/unit/pairs.unit.test.js.map +1 -1
  478. package/dist/tests/unit/rules/lang.unit.test.d.ts +2 -0
  479. package/dist/tests/unit/rules/lang.unit.test.d.ts.map +1 -0
  480. package/dist/tests/unit/rules/lang.unit.test.js +207 -0
  481. package/dist/tests/unit/rules/lang.unit.test.js.map +1 -0
  482. package/dist/tests/unit/schedulers/captchaScheduler.unit.test.js +2 -2
  483. package/dist/tests/unit/schedulers/captchaScheduler.unit.test.js.map +1 -1
  484. package/dist/tests/unit/schedulers/getClientList.unit.test.d.ts +2 -0
  485. package/dist/tests/unit/schedulers/getClientList.unit.test.d.ts.map +1 -0
  486. package/dist/tests/unit/schedulers/getClientList.unit.test.js +114 -0
  487. package/dist/tests/unit/schedulers/getClientList.unit.test.js.map +1 -0
  488. package/dist/tests/unit/schedulers/setClientEntropy.unit.test.d.ts +2 -0
  489. package/dist/tests/unit/schedulers/setClientEntropy.unit.test.d.ts.map +1 -0
  490. package/dist/tests/unit/schedulers/setClientEntropy.unit.test.js +114 -0
  491. package/dist/tests/unit/schedulers/setClientEntropy.unit.test.js.map +1 -0
  492. package/dist/tests/unit/services/ipComparison.unit.test.js +49 -35
  493. package/dist/tests/unit/services/ipComparison.unit.test.js.map +1 -1
  494. package/dist/tests/unit/tasks/captchaManager.unit.test.js +260 -5
  495. package/dist/tests/unit/tasks/captchaManager.unit.test.js.map +1 -1
  496. package/dist/tests/unit/tasks/client/clientTasks.unit.test.js +16 -5
  497. package/dist/tests/unit/tasks/client/clientTasks.unit.test.js.map +1 -1
  498. package/dist/tests/unit/tasks/dataset/datasetTasks.unit.test.js +1 -1
  499. package/dist/tests/unit/tasks/dataset/datasetTasks.unit.test.js.map +1 -1
  500. package/dist/tests/unit/tasks/decisionMachine/decisionMachineCustomHeaders.unit.test.d.ts +2 -0
  501. package/dist/tests/unit/tasks/decisionMachine/decisionMachineCustomHeaders.unit.test.d.ts.map +1 -0
  502. package/dist/tests/unit/tasks/decisionMachine/decisionMachineCustomHeaders.unit.test.js +213 -0
  503. package/dist/tests/unit/tasks/decisionMachine/decisionMachineCustomHeaders.unit.test.js.map +1 -0
  504. package/dist/tests/unit/tasks/decisionMachine/decisionMachineRunner.unit.test.d.ts +2 -0
  505. package/dist/tests/unit/tasks/decisionMachine/decisionMachineRunner.unit.test.d.ts.map +1 -0
  506. package/dist/tests/unit/tasks/decisionMachine/decisionMachineRunner.unit.test.js +304 -0
  507. package/dist/tests/unit/tasks/decisionMachine/decisionMachineRunner.unit.test.js.map +1 -0
  508. package/dist/tests/unit/tasks/detection/getBotScore.unit.test.d.ts +2 -0
  509. package/dist/tests/unit/tasks/detection/getBotScore.unit.test.d.ts.map +1 -0
  510. package/dist/tests/unit/tasks/detection/getBotScore.unit.test.js +115 -0
  511. package/dist/tests/unit/tasks/detection/getBotScore.unit.test.js.map +1 -0
  512. package/dist/tests/unit/tasks/frictionless/frictionlessTasks.unit.test.js +207 -2
  513. package/dist/tests/unit/tasks/frictionless/frictionlessTasks.unit.test.js.map +1 -1
  514. package/dist/tests/unit/tasks/frictionless/frictionlessTasksUtils.unit.test.js +83 -48
  515. package/dist/tests/unit/tasks/frictionless/frictionlessTasksUtils.unit.test.js.map +1 -1
  516. package/dist/tests/unit/tasks/frictionless/routingMachine.unit.test.d.ts +2 -0
  517. package/dist/tests/unit/tasks/frictionless/routingMachine.unit.test.d.ts.map +1 -0
  518. package/dist/tests/unit/tasks/frictionless/routingMachine.unit.test.js +169 -0
  519. package/dist/tests/unit/tasks/frictionless/routingMachine.unit.test.js.map +1 -0
  520. package/dist/tests/unit/tasks/imgCaptcha/imgCaptchaTasks.unit.test.js +719 -9
  521. package/dist/tests/unit/tasks/imgCaptcha/imgCaptchaTasks.unit.test.js.map +1 -1
  522. package/dist/tests/unit/tasks/imgCaptcha/imgCaptchaTasksUtils.unit.test.js +9 -3
  523. package/dist/tests/unit/tasks/imgCaptcha/imgCaptchaTasksUtils.unit.test.js.map +1 -1
  524. package/dist/tests/unit/tasks/powCaptcha/powTasks.unit.test.js +1284 -68
  525. package/dist/tests/unit/tasks/powCaptcha/powTasks.unit.test.js.map +1 -1
  526. package/dist/tests/unit/tasks/powCaptcha/powTasksUtils.unit.test.js +152 -52
  527. package/dist/tests/unit/tasks/powCaptcha/powTasksUtils.unit.test.js.map +1 -1
  528. package/dist/tests/unit/tasks/puzzleCaptcha/puzzleTasks.unit.test.d.ts +2 -0
  529. package/dist/tests/unit/tasks/puzzleCaptcha/puzzleTasks.unit.test.d.ts.map +1 -0
  530. package/dist/tests/unit/tasks/puzzleCaptcha/puzzleTasks.unit.test.js +313 -0
  531. package/dist/tests/unit/tasks/puzzleCaptcha/puzzleTasks.unit.test.js.map +1 -0
  532. package/dist/tests/unit/tasks/puzzleCaptcha/puzzleTasksUtils.unit.test.d.ts +2 -0
  533. package/dist/tests/unit/tasks/puzzleCaptcha/puzzleTasksUtils.unit.test.d.ts.map +1 -0
  534. package/dist/tests/unit/tasks/puzzleCaptcha/puzzleTasksUtils.unit.test.js +29 -0
  535. package/dist/tests/unit/tasks/puzzleCaptcha/puzzleTasksUtils.unit.test.js.map +1 -0
  536. package/dist/tests/unit/tasks/spam/checkSpamEmail.unit.test.d.ts +2 -0
  537. package/dist/tests/unit/tasks/spam/checkSpamEmail.unit.test.d.ts.map +1 -0
  538. package/dist/tests/unit/tasks/spam/checkSpamEmail.unit.test.js +434 -0
  539. package/dist/tests/unit/tasks/spam/checkSpamEmail.unit.test.js.map +1 -0
  540. package/dist/tests/unit/tasks/spam/checkTrafficFilter.unit.test.d.ts +2 -0
  541. package/dist/tests/unit/tasks/spam/checkTrafficFilter.unit.test.d.ts.map +1 -0
  542. package/dist/tests/unit/tasks/spam/checkTrafficFilter.unit.test.js +112 -0
  543. package/dist/tests/unit/tasks/spam/checkTrafficFilter.unit.test.js.map +1 -0
  544. package/dist/tests/unit/tasks/spam/evaluateEmailSpamRules.unit.test.d.ts +2 -0
  545. package/dist/tests/unit/tasks/spam/evaluateEmailSpamRules.unit.test.d.ts.map +1 -0
  546. package/dist/tests/unit/tasks/spam/evaluateEmailSpamRules.unit.test.js +94 -0
  547. package/dist/tests/unit/tasks/spam/evaluateEmailSpamRules.unit.test.js.map +1 -0
  548. package/dist/tests/unit/tasks/streaming/providerDbStreaming.unit.test.d.ts +2 -0
  549. package/dist/tests/unit/tasks/streaming/providerDbStreaming.unit.test.d.ts.map +1 -0
  550. package/dist/tests/unit/tasks/streaming/providerDbStreaming.unit.test.js +94 -0
  551. package/dist/tests/unit/tasks/streaming/providerDbStreaming.unit.test.js.map +1 -0
  552. package/dist/tests/unit/tasks/writeQueueIntegration.unit.test.d.ts +2 -0
  553. package/dist/tests/unit/tasks/writeQueueIntegration.unit.test.d.ts.map +1 -0
  554. package/dist/tests/unit/tasks/writeQueueIntegration.unit.test.js +208 -0
  555. package/dist/tests/unit/tasks/writeQueueIntegration.unit.test.js.map +1 -0
  556. package/dist/tests/unit/testUtils/mockProviderEnv.d.ts +26 -0
  557. package/dist/tests/unit/testUtils/mockProviderEnv.d.ts.map +1 -0
  558. package/dist/tests/unit/testUtils/mockProviderEnv.js +149 -0
  559. package/dist/tests/unit/testUtils/mockProviderEnv.js.map +1 -0
  560. package/dist/tests/unit/util/redisCache.unit.test.d.ts +2 -0
  561. package/dist/tests/unit/util/redisCache.unit.test.d.ts.map +1 -0
  562. package/dist/tests/unit/util/redisCache.unit.test.js +257 -0
  563. package/dist/tests/unit/util/redisCache.unit.test.js.map +1 -0
  564. package/dist/tests/unit/util/usageCounters.unit.test.d.ts +2 -0
  565. package/dist/tests/unit/util/usageCounters.unit.test.d.ts.map +1 -0
  566. package/dist/tests/unit/util/usageCounters.unit.test.js +242 -0
  567. package/dist/tests/unit/util/usageCounters.unit.test.js.map +1 -0
  568. package/dist/tests/unit/util.evaluateIpValidationRules.unit.test.js +2 -0
  569. package/dist/tests/unit/util.evaluateIpValidationRules.unit.test.js.map +1 -1
  570. package/dist/tests/unit/util.ipDistance.unit.test.js +9 -3
  571. package/dist/tests/unit/util.ipDistance.unit.test.js.map +1 -1
  572. package/dist/tests/unit/util.unit.test.js +152 -155
  573. package/dist/tests/unit/util.unit.test.js.map +1 -1
  574. package/dist/tests/unit/utils/devicePlatform.unit.test.d.ts +2 -0
  575. package/dist/tests/unit/utils/devicePlatform.unit.test.d.ts.map +1 -0
  576. package/dist/tests/unit/utils/devicePlatform.unit.test.js +58 -0
  577. package/dist/tests/unit/utils/devicePlatform.unit.test.js.map +1 -0
  578. package/dist/tests/unit/utils/hashUserAgent.unit.test.d.ts +2 -0
  579. package/dist/tests/unit/utils/hashUserAgent.unit.test.d.ts.map +1 -0
  580. package/dist/tests/unit/utils/hashUserAgent.unit.test.js +52 -0
  581. package/dist/tests/unit/utils/hashUserAgent.unit.test.js.map +1 -0
  582. package/dist/tests/unit/utils/hashUserIp.unit.test.d.ts +2 -0
  583. package/dist/tests/unit/utils/hashUserIp.unit.test.d.ts.map +1 -0
  584. package/dist/tests/unit/utils/hashUserIp.unit.test.js +81 -0
  585. package/dist/tests/unit/utils/hashUserIp.unit.test.js.map +1 -0
  586. package/dist/util/usageCounters.d.ts +22 -0
  587. package/dist/util/usageCounters.d.ts.map +1 -0
  588. package/dist/util/usageCounters.js +201 -0
  589. package/dist/util/usageCounters.js.map +1 -0
  590. package/dist/util.d.ts +3 -2
  591. package/dist/util.d.ts.map +1 -1
  592. package/dist/util.js +3 -4
  593. package/dist/util.js.map +1 -1
  594. package/dist/utils/devicePlatform.d.ts +5 -0
  595. package/dist/utils/devicePlatform.d.ts.map +1 -0
  596. package/dist/utils/devicePlatform.js +10 -0
  597. package/dist/utils/devicePlatform.js.map +1 -0
  598. package/dist/utils/dns.d.ts +22 -0
  599. package/dist/utils/dns.d.ts.map +1 -0
  600. package/dist/utils/dns.js +84 -0
  601. package/dist/utils/dns.js.map +1 -0
  602. package/dist/utils/honeypot/encoders.d.ts +3 -0
  603. package/dist/utils/honeypot/encoders.d.ts.map +1 -0
  604. package/dist/utils/honeypot/encoders.js +86 -0
  605. package/dist/utils/honeypot/encoders.js.map +1 -0
  606. package/dist/utils/honeypot/phraseBank.d.ts +3 -0
  607. package/dist/utils/honeypot/phraseBank.d.ts.map +1 -0
  608. package/dist/utils/honeypot/phraseBank.js +47 -0
  609. package/dist/utils/honeypot/phraseBank.js.map +1 -0
  610. package/dist/utils/normalizeRequestIp.d.ts +3 -0
  611. package/dist/utils/normalizeRequestIp.d.ts.map +1 -0
  612. package/dist/utils/normalizeRequestIp.js +27 -0
  613. package/dist/utils/normalizeRequestIp.js.map +1 -0
  614. package/package.json +27 -19
  615. package/dist/cjs/services/ipInfo.cjs +0 -87
  616. package/dist/services/ipInfo.d.ts +0 -3
  617. package/dist/services/ipInfo.d.ts.map +0 -1
  618. package/dist/services/ipInfo.js +0 -87
  619. package/dist/services/ipInfo.js.map +0 -1
  620. package/dist/tests/unit/services/ipInfo.unit.test.d.ts +0 -2
  621. package/dist/tests/unit/services/ipInfo.unit.test.d.ts.map +0 -1
  622. package/dist/tests/unit/services/ipInfo.unit.test.js +0 -210
  623. package/dist/tests/unit/services/ipInfo.unit.test.js.map +0 -1
@@ -2,7 +2,7 @@ import { stringToHex, u8aToHex } from "@polkadot/util";
2
2
  import { ApiParams, CaptchaStatus, CaptchaType, POW_SEPARATOR, } from "@prosopo/types";
3
3
  import { AccessPolicyType, } from "@prosopo/user-access-policy";
4
4
  import { getIPAddress, verifyRecency } from "@prosopo/util";
5
- import { beforeEach, describe, expect, it, vi } from "vitest";
5
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
6
6
  import { getCompositeIpAddress } from "../../../../compositeIpAddress.js";
7
7
  import { PowCaptchaManager } from "../../../../tasks/powCaptcha/powTasks.js";
8
8
  import { checkPowSignature, validateSolution, } from "../../../../tasks/powCaptcha/powTasksUtils.js";
@@ -29,6 +29,17 @@ describe("PowCaptchaManager", () => {
29
29
  let pair;
30
30
  let powCaptchaManager;
31
31
  let mockEnv;
32
+ let originalDecide;
33
+ const mockDecisionMachine = (mockFn) => {
34
+ originalDecide = powCaptchaManager.decisionMachineRunner.decide;
35
+ powCaptchaManager.decisionMachineRunner.decide = mockFn;
36
+ };
37
+ const restoreDecisionMachine = () => {
38
+ if (originalDecide) {
39
+ powCaptchaManager.decisionMachineRunner.decide = originalDecide;
40
+ originalDecide = undefined;
41
+ }
42
+ };
32
43
  beforeEach(() => {
33
44
  db = {
34
45
  storePowCaptchaRecord: vi.fn(),
@@ -36,6 +47,10 @@ describe("PowCaptchaManager", () => {
36
47
  updatePowCaptchaRecordResult: vi.fn(),
37
48
  updatePowCaptchaRecord: vi.fn(),
38
49
  markDappUserPoWCommitmentsChecked: vi.fn(),
50
+ getClientRecord: vi.fn(),
51
+ getSessionRecordBySessionId: vi.fn(),
52
+ updateSessionRecord: vi.fn(),
53
+ getSpamEmailDomain: vi.fn(),
39
54
  };
40
55
  pair = {
41
56
  sign: vi.fn(),
@@ -48,10 +63,22 @@ describe("PowCaptchaManager", () => {
48
63
  baseUrl: "https://api.ipapi.is",
49
64
  },
50
65
  },
66
+ ipInfoService: {
67
+ initialize: vi.fn().mockResolvedValue(undefined),
68
+ isAvailable: vi.fn().mockReturnValue(true),
69
+ lookup: vi.fn(async (ip) => ({
70
+ isValid: false,
71
+ error: "stubbed in test",
72
+ ip,
73
+ })),
74
+ },
51
75
  };
52
76
  powCaptchaManager = new PowCaptchaManager(db, pair, mockEnv.config);
53
77
  vi.clearAllMocks();
54
78
  });
79
+ afterEach(() => {
80
+ restoreDecisionMachine();
81
+ });
55
82
  describe("getPowCaptchaChallenge", () => {
56
83
  it("should generate a PoW captcha challenge", async () => {
57
84
  const userAccount = "userAccount";
@@ -111,7 +138,7 @@ describe("PowCaptchaManager", () => {
111
138
  undefined,
112
139
  ];
113
140
  const result = await powCaptchaManager.verifyPowCaptchaSolution(...verifyPowCaptchaSolutionArgs);
114
- expect(result).toBe(true);
141
+ expect(result.verified).toBe(true);
115
142
  const verifyRecencyArgs = [
116
143
  challenge,
117
144
  timeout,
@@ -176,7 +203,7 @@ describe("PowCaptchaManager", () => {
176
203
  });
177
204
  db.getPowCaptchaRecordByChallenge.mockResolvedValue(challengeRecord);
178
205
  validateSolution.mockImplementation(() => false);
179
- expect(await powCaptchaManager.verifyPowCaptchaSolution(challenge, signature, nonce, timeout, timestampSignature, ipAddress, headers, undefined)).toBe(false);
206
+ expect((await powCaptchaManager.verifyPowCaptchaSolution(challenge, signature, nonce, timeout, timestampSignature, ipAddress, headers, undefined)).verified).toBe(false);
180
207
  expect(verifyRecency).toHaveBeenCalledWith(challenge, timeout);
181
208
  });
182
209
  });
@@ -194,6 +221,7 @@ describe("PowCaptchaManager", () => {
194
221
  requestedAtTimestamp: new Date(timestamp),
195
222
  serverChecked: false,
196
223
  result: { status: CaptchaStatus.approved },
224
+ ipAddress: getCompositeIpAddress(getIPAddress("1.1.1.1")),
197
225
  };
198
226
  db.getPowCaptchaRecordByChallenge.mockResolvedValue(challengeRecord);
199
227
  verifyRecency.mockImplementation(() => true);
@@ -381,117 +409,1305 @@ describe("PowCaptchaManager", () => {
381
409
  expect(result.verified).toBe(true);
382
410
  });
383
411
  });
384
- describe("verifyPowCaptchaSolution with behavioral data", () => {
385
- beforeEach(() => {
386
- db.getDetectorKeys = vi.fn(() => Promise.resolve(["test-detector-key"]));
387
- db.updatePowCaptchaRecord = vi.fn(() => Promise.resolve());
388
- });
389
- it("should process behavioral data when provided", async () => {
390
- const requestedAtTimestamp = 123456789;
412
+ describe("serverVerifyPowCaptchaSolution with decision machine", () => {
413
+ it("should allow when decision machine returns allow", async () => {
414
+ const dappAccount = "dappAccount";
415
+ const timestamp = 123456789;
391
416
  const userAccount = "testUserAccount";
392
- const challenge = `${requestedAtTimestamp}${POW_SEPARATOR}${userAccount}${POW_SEPARATOR}${pair.address}`;
393
- const difficulty = 4;
394
- const providerSignature = "testSignature";
395
- const userSignature = "testTimestampSignature";
396
- const nonce = 12345;
417
+ const challenge = `${timestamp}${POW_SEPARATOR}${userAccount}${POW_SEPARATOR}${dappAccount}`;
397
418
  const timeout = 1000;
398
419
  const ipAddress = getIPAddress("1.1.1.1");
399
420
  const headers = { a: "1", b: "2", c: "3" };
400
- const behavioralData = "encrypted-behavioral-data";
421
+ const behavioralDataPacked = {
422
+ c1: [1, 2, 3],
423
+ c2: [4, 5, 6],
424
+ c3: [7, 8, 9],
425
+ d: "test-device",
426
+ };
401
427
  const challengeRecord = {
402
428
  challenge,
403
- difficulty,
404
- dappAccount: pair.address,
429
+ difficulty: 4,
430
+ dappAccount,
405
431
  userAccount,
406
- requestedAtTimestamp: new Date(requestedAtTimestamp),
407
- result: { status: CaptchaStatus.pending },
408
- userSubmitted: false,
432
+ requestedAtTimestamp: new Date(timestamp),
433
+ result: { status: CaptchaStatus.approved },
434
+ userSubmitted: true,
409
435
  serverChecked: false,
410
436
  ipAddress: getCompositeIpAddress(ipAddress),
411
437
  headers,
412
438
  ja4: "ja4",
413
- providerSignature,
439
+ providerSignature: "testSignature",
414
440
  lastUpdatedTimestamp: new Date(),
441
+ behavioralDataPacked,
442
+ deviceCapability: "test-device",
415
443
  };
416
- verifyRecency.mockImplementation(() => true);
417
- checkPowSignature.mockImplementation(() => true);
418
- validateSolution.mockImplementation(() => true);
419
444
  db.getPowCaptchaRecordByChallenge.mockResolvedValue(challengeRecord);
420
- db.updatePowCaptchaRecordResult.mockResolvedValue(true);
421
- db.markDappUserPoWCommitmentsChecked.mockResolvedValue(true);
422
- const result = await powCaptchaManager.verifyPowCaptchaSolution(challenge, providerSignature, nonce, timeout, userSignature, ipAddress, headers, behavioralData, undefined);
423
- expect(result).toBe(true);
445
+ verifyRecency.mockImplementation(() => true);
446
+ const result = await powCaptchaManager.serverVerifyPowCaptchaSolution(dappAccount, challenge, timeout, mockEnv);
447
+ expect(result.verified).toBe(true);
424
448
  });
425
- it("should handle behavioral data decryption failure gracefully", async () => {
426
- const requestedAtTimestamp = 123456789;
449
+ it("should deny when decision machine returns deny", async () => {
450
+ const dappAccount = "dappAccount";
451
+ const timestamp = 123456789;
427
452
  const userAccount = "testUserAccount";
428
- const challenge = `${requestedAtTimestamp}${POW_SEPARATOR}${userAccount}${POW_SEPARATOR}${pair.address}`;
429
- const difficulty = 4;
430
- const providerSignature = "testSignature";
431
- const userSignature = "testTimestampSignature";
432
- const nonce = 12345;
453
+ const challenge = `${timestamp}${POW_SEPARATOR}${userAccount}${POW_SEPARATOR}${dappAccount}`;
433
454
  const timeout = 1000;
434
455
  const ipAddress = getIPAddress("1.1.1.1");
435
456
  const headers = { a: "1", b: "2", c: "3" };
436
- const behavioralData = "invalid-encrypted-data";
457
+ const behavioralDataPacked = {
458
+ c1: [1, 2, 3],
459
+ c2: [4, 5, 6],
460
+ c3: [7, 8, 9],
461
+ d: "suspicious-device",
462
+ };
437
463
  const challengeRecord = {
438
464
  challenge,
439
- difficulty,
440
- dappAccount: pair.address,
465
+ difficulty: 4,
466
+ dappAccount,
441
467
  userAccount,
442
- requestedAtTimestamp: new Date(requestedAtTimestamp),
443
- result: { status: CaptchaStatus.pending },
444
- userSubmitted: false,
468
+ requestedAtTimestamp: new Date(timestamp),
469
+ result: { status: CaptchaStatus.approved },
470
+ userSubmitted: true,
445
471
  serverChecked: false,
446
472
  ipAddress: getCompositeIpAddress(ipAddress),
447
473
  headers,
448
474
  ja4: "ja4",
449
- providerSignature,
475
+ providerSignature: "testSignature",
450
476
  lastUpdatedTimestamp: new Date(),
477
+ behavioralDataPacked,
478
+ deviceCapability: "suspicious-device",
451
479
  };
480
+ db.getPowCaptchaRecordByChallenge.mockResolvedValue(challengeRecord);
481
+ db.updatePowCaptchaRecord.mockResolvedValue(undefined);
452
482
  verifyRecency.mockImplementation(() => true);
453
- checkPowSignature.mockImplementation(() => true);
454
- validateSolution.mockImplementation(() => true);
483
+ mockDecisionMachine(vi.fn().mockResolvedValue({
484
+ decision: "deny",
485
+ reason: "Suspicious device detected",
486
+ score: 0,
487
+ }));
488
+ try {
489
+ const result = await powCaptchaManager.serverVerifyPowCaptchaSolution(dappAccount, challenge, timeout, mockEnv);
490
+ expect(result.verified).toBe(false);
491
+ }
492
+ finally {
493
+ restoreDecisionMachine();
494
+ }
495
+ });
496
+ it("should allow when no behavioral data is present (no decision machine run)", async () => {
497
+ const dappAccount = "dappAccount";
498
+ const timestamp = 123456789;
499
+ const userAccount = "testUserAccount";
500
+ const challenge = `${timestamp}${POW_SEPARATOR}${userAccount}${POW_SEPARATOR}${dappAccount}`;
501
+ const timeout = 1000;
502
+ const ipAddress = getIPAddress("1.1.1.1");
503
+ const headers = { a: "1", b: "2", c: "3" };
504
+ const challengeRecord = {
505
+ challenge,
506
+ difficulty: 4,
507
+ dappAccount,
508
+ userAccount,
509
+ requestedAtTimestamp: new Date(timestamp),
510
+ result: { status: CaptchaStatus.approved },
511
+ userSubmitted: true,
512
+ serverChecked: false,
513
+ ipAddress: getCompositeIpAddress(ipAddress),
514
+ headers,
515
+ ja4: "ja4",
516
+ providerSignature: "testSignature",
517
+ lastUpdatedTimestamp: new Date(),
518
+ };
455
519
  db.getPowCaptchaRecordByChallenge.mockResolvedValue(challengeRecord);
456
- db.updatePowCaptchaRecordResult.mockResolvedValue(true);
457
- db.markDappUserPoWCommitmentsChecked.mockResolvedValue(true);
458
- const result = await powCaptchaManager.verifyPowCaptchaSolution(challenge, providerSignature, nonce, timeout, userSignature, ipAddress, headers, behavioralData, undefined);
459
- expect(result).toBe(true);
520
+ verifyRecency.mockImplementation(() => true);
521
+ const result = await powCaptchaManager.serverVerifyPowCaptchaSolution(dappAccount, challenge, timeout, mockEnv);
522
+ expect(result.verified).toBe(true);
460
523
  });
461
- it("should work without behavioral data", async () => {
462
- const requestedAtTimestamp = 123456789;
524
+ it("should default to allow if decision machine fails", async () => {
525
+ const dappAccount = "dappAccount";
526
+ const timestamp = 123456789;
463
527
  const userAccount = "testUserAccount";
464
- const challenge = `${requestedAtTimestamp}${POW_SEPARATOR}${userAccount}${POW_SEPARATOR}${pair.address}`;
465
- const difficulty = 4;
466
- const providerSignature = "testSignature";
467
- const userSignature = "testTimestampSignature";
468
- const nonce = 12345;
528
+ const challenge = `${timestamp}${POW_SEPARATOR}${userAccount}${POW_SEPARATOR}${dappAccount}`;
469
529
  const timeout = 1000;
470
530
  const ipAddress = getIPAddress("1.1.1.1");
471
531
  const headers = { a: "1", b: "2", c: "3" };
532
+ const behavioralDataPacked = {
533
+ c1: [1, 2, 3],
534
+ c2: [4, 5, 6],
535
+ c3: [7, 8, 9],
536
+ d: "test-device",
537
+ };
472
538
  const challengeRecord = {
473
539
  challenge,
474
- difficulty,
475
- dappAccount: pair.address,
540
+ difficulty: 4,
541
+ dappAccount,
476
542
  userAccount,
477
- requestedAtTimestamp: new Date(requestedAtTimestamp),
478
- result: { status: CaptchaStatus.pending },
479
- userSubmitted: false,
543
+ requestedAtTimestamp: new Date(timestamp),
544
+ result: { status: CaptchaStatus.approved },
545
+ userSubmitted: true,
480
546
  serverChecked: false,
481
547
  ipAddress: getCompositeIpAddress(ipAddress),
482
548
  headers,
483
549
  ja4: "ja4",
484
- providerSignature,
550
+ providerSignature: "testSignature",
485
551
  lastUpdatedTimestamp: new Date(),
552
+ behavioralDataPacked,
553
+ deviceCapability: "test-device",
486
554
  };
555
+ db.getPowCaptchaRecordByChallenge.mockResolvedValue(challengeRecord);
487
556
  verifyRecency.mockImplementation(() => true);
488
- checkPowSignature.mockImplementation(() => true);
489
- validateSolution.mockImplementation(() => true);
557
+ mockDecisionMachine(vi.fn().mockRejectedValue(new Error("Decision machine error")));
558
+ try {
559
+ const result = await powCaptchaManager.serverVerifyPowCaptchaSolution(dappAccount, challenge, timeout, mockEnv);
560
+ expect(result.verified).toBe(true);
561
+ }
562
+ finally {
563
+ restoreDecisionMachine();
564
+ }
565
+ });
566
+ });
567
+ describe("IP Validation Guard Conditions", () => {
568
+ it("should skip IP validation when ipValidationRules is undefined", async () => {
569
+ const dappAccount = "testDappAccount";
570
+ const userAccount = "testUserAccount";
571
+ const challenge = `123456789${POW_SEPARATOR}userAccount${POW_SEPARATOR}${pair.address}`;
572
+ const timeout = 1000;
573
+ const ip = "1.1.1.1";
574
+ const challengeRecord = {
575
+ dappAccount,
576
+ userAccount,
577
+ result: { status: CaptchaStatus.approved },
578
+ userSubmitted: true,
579
+ challenge,
580
+ serverChecked: false,
581
+ difficulty: 4,
582
+ requestedAtTimestamp: new Date(),
583
+ ipAddress: getCompositeIpAddress(ip),
584
+ headers: { a: "1", b: "2", c: "3" },
585
+ ja4: "ja4",
586
+ providerSignature: "testSignature",
587
+ lastUpdatedTimestamp: new Date(),
588
+ };
490
589
  db.getPowCaptchaRecordByChallenge.mockResolvedValue(challengeRecord);
491
- db.updatePowCaptchaRecordResult.mockResolvedValue(true);
492
- db.markDappUserPoWCommitmentsChecked.mockResolvedValue(true);
493
- const result = await powCaptchaManager.verifyPowCaptchaSolution(challenge, providerSignature, nonce, timeout, userSignature, ipAddress, headers, undefined, undefined);
494
- expect(result).toBe(true);
590
+ db.getClientRecord.mockResolvedValue({
591
+ settings: {},
592
+ });
593
+ checkPowSignature.mockReturnValue(true);
594
+ validateSolution.mockReturnValue(true);
595
+ verifyRecency.mockImplementation(() => true);
596
+ const result = await powCaptchaManager.serverVerifyPowCaptchaSolution(dappAccount, challenge, timeout, mockEnv, ip);
597
+ expect(result.verified).toBe(true);
598
+ expect(db.updatePowCaptchaRecord).toHaveBeenCalledWith(challenge, {
599
+ providedIp: getCompositeIpAddress(ip),
600
+ });
601
+ });
602
+ it("should skip IP validation when ipValidationRules.enabled is false", async () => {
603
+ const dappAccount = pair.address;
604
+ const challenge = `123456789${POW_SEPARATOR}userAccount${POW_SEPARATOR}${pair.address}`;
605
+ const timeout = 1000;
606
+ const ip = "1.1.1.1";
607
+ const challengeRecord = {
608
+ userAccount: "userAccount",
609
+ challenge,
610
+ serverChecked: false,
611
+ difficulty: 4,
612
+ dappAccount,
613
+ result: {
614
+ status: CaptchaStatus.approved,
615
+ },
616
+ requestedAtTimestamp: new Date(),
617
+ ipAddress: getCompositeIpAddress(ip),
618
+ headers: { a: "1", b: "2", c: "3" },
619
+ ja4: "ja4",
620
+ providerSignature: "testSignature",
621
+ userSubmitted: true,
622
+ lastUpdatedTimestamp: new Date(),
623
+ };
624
+ db.getPowCaptchaRecordByChallenge.mockResolvedValue(challengeRecord);
625
+ db.getClientRecord.mockResolvedValue({
626
+ settings: {
627
+ ipValidationRules: {
628
+ enabled: false,
629
+ actions: {
630
+ countryChangeAction: "reject",
631
+ cityChangeAction: "reject",
632
+ },
633
+ },
634
+ },
635
+ });
636
+ checkPowSignature.mockReturnValue(true);
637
+ validateSolution.mockReturnValue(true);
638
+ verifyRecency.mockImplementation(() => true);
639
+ const result = await powCaptchaManager.serverVerifyPowCaptchaSolution(dappAccount, challenge, timeout, mockEnv, ip);
640
+ expect(result.verified).toBe(true);
641
+ expect(db.updatePowCaptchaRecord).toHaveBeenCalledWith(challenge, {
642
+ providedIp: getCompositeIpAddress(ip),
643
+ });
644
+ });
645
+ it("should skip IP validation when ipValidationRules.enabled is undefined", async () => {
646
+ const dappAccount = pair.address;
647
+ const challenge = `123456789${POW_SEPARATOR}userAccount${POW_SEPARATOR}${pair.address}`;
648
+ const timeout = 1000;
649
+ const ip = "1.1.1.1";
650
+ const challengeRecord = {
651
+ dappAccount,
652
+ challenge,
653
+ serverChecked: false,
654
+ difficulty: 4,
655
+ result: {
656
+ status: CaptchaStatus.approved,
657
+ },
658
+ requestedAtTimestamp: new Date(),
659
+ ipAddress: getCompositeIpAddress(ip),
660
+ userAccount: "userAccount",
661
+ headers: { a: "1", b: "2", c: "3" },
662
+ ja4: "ja4",
663
+ providerSignature: "testSignature",
664
+ userSubmitted: true,
665
+ lastUpdatedTimestamp: new Date(),
666
+ };
667
+ db.getPowCaptchaRecordByChallenge.mockResolvedValue(challengeRecord);
668
+ db.getClientRecord.mockResolvedValue({
669
+ settings: {
670
+ ipValidationRules: {
671
+ actions: {
672
+ countryChangeAction: "reject",
673
+ cityChangeAction: "reject",
674
+ },
675
+ },
676
+ },
677
+ });
678
+ checkPowSignature.mockReturnValue(true);
679
+ validateSolution.mockReturnValue(true);
680
+ verifyRecency.mockImplementation(() => true);
681
+ const result = await powCaptchaManager.serverVerifyPowCaptchaSolution(dappAccount, challenge, timeout, mockEnv, ip);
682
+ expect(result.verified).toBe(true);
683
+ expect(db.updatePowCaptchaRecord).toHaveBeenCalledWith(challenge, {
684
+ providedIp: getCompositeIpAddress(ip),
685
+ });
686
+ });
687
+ it("should skip IP validation when clientRecord is null", async () => {
688
+ const dappAccount = pair.address;
689
+ const challenge = `123456789${POW_SEPARATOR}userAccount${POW_SEPARATOR}${pair.address}`;
690
+ const timeout = 1000;
691
+ const ip = "1.1.1.1";
692
+ const challengeRecord = {
693
+ dappAccount,
694
+ challenge,
695
+ serverChecked: false,
696
+ difficulty: 4,
697
+ result: {
698
+ status: CaptchaStatus.approved,
699
+ },
700
+ requestedAtTimestamp: new Date(),
701
+ ipAddress: getCompositeIpAddress(ip),
702
+ userAccount: "userAccount",
703
+ headers: { a: "1", b: "2", c: "3" },
704
+ ja4: "ja4",
705
+ providerSignature: "testSignature",
706
+ userSubmitted: true,
707
+ lastUpdatedTimestamp: new Date(),
708
+ };
709
+ db.getPowCaptchaRecordByChallenge.mockResolvedValue(challengeRecord);
710
+ db.getClientRecord.mockResolvedValue(null);
711
+ checkPowSignature.mockReturnValue(true);
712
+ validateSolution.mockReturnValue(true);
713
+ verifyRecency.mockImplementation(() => true);
714
+ const result = await powCaptchaManager.serverVerifyPowCaptchaSolution(dappAccount, challenge, timeout, mockEnv, ip);
715
+ expect(result.verified).toBe(true);
716
+ expect(db.updatePowCaptchaRecord).toHaveBeenCalledWith(challenge, {
717
+ providedIp: getCompositeIpAddress(ip),
718
+ });
719
+ });
720
+ it("should skip IP validation when no IP is provided", async () => {
721
+ const dappAccount = pair.address;
722
+ const challenge = `123456789${POW_SEPARATOR}userAccount${POW_SEPARATOR}${pair.address}`;
723
+ const timeout = 1000;
724
+ const challengeRecord = {
725
+ dappAccount,
726
+ challenge,
727
+ serverChecked: false,
728
+ difficulty: 4,
729
+ result: {
730
+ status: CaptchaStatus.approved,
731
+ },
732
+ requestedAtTimestamp: new Date(),
733
+ ipAddress: getCompositeIpAddress("1.1.1.1"),
734
+ userAccount: "userAccount",
735
+ headers: { a: "1", b: "2", c: "3" },
736
+ ja4: "ja4",
737
+ providerSignature: "testSignature",
738
+ userSubmitted: true,
739
+ lastUpdatedTimestamp: new Date(),
740
+ };
741
+ db.getPowCaptchaRecordByChallenge.mockResolvedValue(challengeRecord);
742
+ db.getClientRecord.mockResolvedValue({
743
+ settings: {
744
+ ipValidationRules: {
745
+ enabled: true,
746
+ actions: {
747
+ countryChangeAction: "reject",
748
+ },
749
+ },
750
+ },
751
+ });
752
+ checkPowSignature.mockReturnValue(true);
753
+ validateSolution.mockReturnValue(true);
754
+ verifyRecency.mockImplementation(() => true);
755
+ const result = await powCaptchaManager.serverVerifyPowCaptchaSolution(dappAccount, challenge, timeout, mockEnv);
756
+ expect(result.verified).toBe(true);
757
+ });
758
+ });
759
+ describe("Decision machine with no-cache header and no behavioral data", () => {
760
+ it("should deny when no-cache header is present and no behavioral data exists", async () => {
761
+ const dappAccount = "5EZVvsHMrKCFKp5NYNoTyDjTjetoVo1Z4UNNbTwJf1GfN6Xm";
762
+ const timestamp = 1770650564052;
763
+ const userAccount = "5CBFuSD5rgzhwVLLtDsA1WbLVkfrrAMEHdbiBBuZ78QvcEpv";
764
+ const challenge = `${timestamp}${POW_SEPARATOR}${userAccount}${POW_SEPARATOR}${dappAccount}${POW_SEPARATOR}351147`;
765
+ const timeout = 1000;
766
+ const ipAddress = getIPAddress("81.159.254.145");
767
+ const headers = {
768
+ host: "pronode2.prosopo.io",
769
+ "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36",
770
+ "content-length": "182",
771
+ accept: "*/*",
772
+ "accept-encoding": "gzip, deflate, br, zstd",
773
+ "accept-language": "de,de-DE;q=0.9,en;q=0.8",
774
+ "cache-control": "no-cache",
775
+ "content-type": "application/json",
776
+ dnt: "1",
777
+ origin: "https://www.twickets.live",
778
+ pragma: "no-cache",
779
+ priority: "u=1, i",
780
+ "prosopo-site-key": dappAccount,
781
+ "prosopo-user": userAccount,
782
+ referer: "https://www.twickets.live/",
783
+ "sec-ch-ua": '"Google Chrome";v="126", "Chromium";v="126", "Not_A Brand";v="24"',
784
+ "sec-ch-ua-mobile": "?0",
785
+ "sec-ch-ua-platform": '"Windows"',
786
+ "sec-fetch-dest": "empty",
787
+ "sec-fetch-mode": "cors",
788
+ "sec-fetch-site": "cross-site",
789
+ };
790
+ const challengeRecord = {
791
+ challenge,
792
+ difficulty: 4,
793
+ dappAccount,
794
+ userAccount,
795
+ requestedAtTimestamp: new Date(timestamp),
796
+ result: { status: CaptchaStatus.approved },
797
+ userSubmitted: true,
798
+ serverChecked: false,
799
+ ipAddress: getCompositeIpAddress(ipAddress),
800
+ headers,
801
+ ja4: "t13d1516h2_8daaf6152771_02713d6af862",
802
+ providerSignature: "testSignature",
803
+ lastUpdatedTimestamp: new Date(),
804
+ sessionId: "pronode2-6d5deeee-78f1-4af0-97b3-f070037438dd",
805
+ };
806
+ const decisionMachineSource = `
807
+ /**
808
+ * Decision machine for blocking German requests with no-cache header
809
+ */
810
+
811
+ function checkNoCacheNoBehavioural(headers, behavioralDataPacked) {
812
+ const cacheControl =
813
+ "cache-control" in headers ? headers["cache-control"] : "";
814
+
815
+ const hasNoCache = cacheControl.toLowerCase().includes("no-cache");
816
+
817
+ const hasNoBehavioralData =
818
+ !behavioralDataPacked || behavioralDataPacked === "0";
819
+
820
+ if (hasNoCache && hasNoBehavioralData) {
821
+ return {
822
+ decision: "deny",
823
+ reason: "no-cache request with no behavioral data",
824
+ score: 0,
825
+ tags: ["blocked"],
826
+ };
827
+ }
828
+
829
+ return null;
830
+ }
831
+
832
+ module.exports = (input) => {
833
+ const {
834
+ userAccount,
835
+ dappAccount,
836
+ captchaResult,
837
+ headers,
838
+ captchaType,
839
+ countryCode,
840
+ behavioralDataPacked,
841
+ } = input;
842
+
843
+ const noCacheNoBehavioural = checkNoCacheNoBehavioural(
844
+ headers,
845
+ behavioralDataPacked,
846
+ );
847
+ if (noCacheNoBehavioural) {
848
+ return noCacheNoBehavioural;
849
+ }
850
+
851
+ if (captchaResult === "passed") {
852
+ return {
853
+ decision: "allow",
854
+ reason: "Captcha verification successful",
855
+ score: 100,
856
+ tags: [\`captcha-type:\${captchaType || "unknown"}\`],
857
+ };
858
+ }
859
+
860
+ return {
861
+ decision: "deny",
862
+ reason: "Captcha verification failed",
863
+ score: 0,
864
+ tags: ["blocked"],
865
+ };
866
+ };
867
+ `;
868
+ db.getSessionRecordBySessionId.mockResolvedValue(undefined);
869
+ db.getPowCaptchaRecordByChallenge.mockResolvedValue(challengeRecord);
870
+ db.updatePowCaptchaRecord.mockResolvedValue(undefined);
871
+ verifyRecency.mockImplementation(() => true);
872
+ mockDecisionMachine(async (input) => {
873
+ const decideFn = eval(`(function() { const module = { exports: {} }; ${decisionMachineSource}; return module.exports; })()`);
874
+ return decideFn(input);
875
+ });
876
+ try {
877
+ const result = await powCaptchaManager.serverVerifyPowCaptchaSolution(dappAccount, challenge, timeout, mockEnv);
878
+ expect(result.verified).toBe(false);
879
+ expect(db.updatePowCaptchaRecord).toHaveBeenCalledWith(challenge, {
880
+ result: {
881
+ status: CaptchaStatus.disapproved,
882
+ reason: "no-cache request with no behavioral data",
883
+ },
884
+ });
885
+ }
886
+ finally {
887
+ restoreDecisionMachine();
888
+ }
889
+ });
890
+ it("should allow when no-cache header is present but behavioral data exists", async () => {
891
+ const dappAccount = "5EZVvsHMrKCFKp5NYNoTyDjTjetoVo1Z4UNNbTwJf1GfN6Xm";
892
+ const timestamp = 1770650564052;
893
+ const userAccount = "5CBFuSD5rgzhwVLLtDsA1WbLVkfrrAMEHdbiBBuZ78QvcEpv";
894
+ const challenge = `${timestamp}${POW_SEPARATOR}${userAccount}${POW_SEPARATOR}${dappAccount}${POW_SEPARATOR}351147`;
895
+ const timeout = 1000;
896
+ const ipAddress = getIPAddress("81.159.254.145");
897
+ const headers = {
898
+ "cache-control": "no-cache",
899
+ "user-agent": "Mozilla/5.0",
900
+ };
901
+ const behavioralDataPacked = {
902
+ c1: [1, 2, 3],
903
+ c2: [4, 5, 6],
904
+ c3: [7, 8, 9],
905
+ d: "test-device",
906
+ };
907
+ const challengeRecord = {
908
+ challenge,
909
+ difficulty: 4,
910
+ dappAccount,
911
+ userAccount,
912
+ requestedAtTimestamp: new Date(timestamp),
913
+ result: { status: CaptchaStatus.approved },
914
+ userSubmitted: true,
915
+ serverChecked: false,
916
+ ipAddress: getCompositeIpAddress(ipAddress),
917
+ headers,
918
+ ja4: "t13d1516h2_8daaf6152771_02713d6af862",
919
+ providerSignature: "testSignature",
920
+ lastUpdatedTimestamp: new Date(),
921
+ behavioralDataPacked,
922
+ deviceCapability: "test-device",
923
+ };
924
+ const decisionMachineSource = `
925
+ function checkNoCacheNoBehavioural(headers, behavioralDataPacked) {
926
+ const cacheControl =
927
+ "cache-control" in headers ? headers["cache-control"] : "";
928
+ const hasNoCache = cacheControl.toLowerCase().includes("no-cache");
929
+ const hasNoBehavioralData =
930
+ !behavioralDataPacked || behavioralDataPacked === "0";
931
+
932
+ if (hasNoCache && hasNoBehavioralData) {
933
+ return {
934
+ decision: "deny",
935
+ reason: "no-cache request with no behavioral data",
936
+ score: 0,
937
+ tags: ["blocked"],
938
+ };
939
+ }
940
+ return null;
941
+ }
942
+
943
+ module.exports = (input) => {
944
+ const noCacheNoBehavioural = checkNoCacheNoBehavioural(
945
+ input.headers,
946
+ input.behavioralDataPacked,
947
+ );
948
+ if (noCacheNoBehavioural) {
949
+ return noCacheNoBehavioural;
950
+ }
951
+
952
+ if (input.captchaResult === "passed") {
953
+ return {
954
+ decision: "allow",
955
+ reason: "Captcha verification successful",
956
+ score: 100,
957
+ tags: [\`captcha-type:\${input.captchaType || "unknown"}\`],
958
+ };
959
+ }
960
+
961
+ return {
962
+ decision: "deny",
963
+ reason: "Captcha verification failed",
964
+ score: 0,
965
+ tags: ["blocked"],
966
+ };
967
+ };
968
+ `;
969
+ db.getPowCaptchaRecordByChallenge.mockResolvedValue(challengeRecord);
970
+ verifyRecency.mockImplementation(() => true);
971
+ mockDecisionMachine(async (input) => {
972
+ const decideFn = eval(`(function() { const module = { exports: {} }; ${decisionMachineSource}; return module.exports; })()`);
973
+ return decideFn(input);
974
+ });
975
+ try {
976
+ const result = await powCaptchaManager.serverVerifyPowCaptchaSolution(dappAccount, challenge, timeout, mockEnv);
977
+ expect(result.verified).toBe(true);
978
+ }
979
+ finally {
980
+ restoreDecisionMachine();
981
+ }
982
+ });
983
+ it("should allow when behavioral data is missing but no no-cache header", async () => {
984
+ const dappAccount = "5EZVvsHMrKCFKp5NYNoTyDjTjetoVo1Z4UNNbTwJf1GfN6Xm";
985
+ const timestamp = 1770650564052;
986
+ const userAccount = "5CBFuSD5rgzhwVLLtDsA1WbLVkfrrAMEHdbiBBuZ78QvcEpv";
987
+ const challenge = `${timestamp}${POW_SEPARATOR}${userAccount}${POW_SEPARATOR}${dappAccount}${POW_SEPARATOR}351147`;
988
+ const timeout = 1000;
989
+ const ipAddress = getIPAddress("81.159.254.145");
990
+ const headers = {
991
+ "user-agent": "Mozilla/5.0",
992
+ };
993
+ const challengeRecord = {
994
+ challenge,
995
+ difficulty: 4,
996
+ dappAccount,
997
+ userAccount,
998
+ requestedAtTimestamp: new Date(timestamp),
999
+ result: { status: CaptchaStatus.approved },
1000
+ userSubmitted: true,
1001
+ serverChecked: false,
1002
+ ipAddress: getCompositeIpAddress(ipAddress),
1003
+ headers,
1004
+ ja4: "t13d1516h2_8daaf6152771_02713d6af862",
1005
+ providerSignature: "testSignature",
1006
+ lastUpdatedTimestamp: new Date(),
1007
+ };
1008
+ const decisionMachineSource = `
1009
+ function checkNoCacheNoBehavioural(headers, behavioralDataPacked) {
1010
+ const cacheControl =
1011
+ "cache-control" in headers ? headers["cache-control"] : "";
1012
+ const hasNoCache = cacheControl.toLowerCase().includes("no-cache");
1013
+ const hasNoBehavioralData =
1014
+ !behavioralDataPacked || behavioralDataPacked === "0";
1015
+
1016
+ if (hasNoCache && hasNoBehavioralData) {
1017
+ return {
1018
+ decision: "deny",
1019
+ reason: "no-cache request with no behavioral data",
1020
+ score: 0,
1021
+ tags: ["blocked"],
1022
+ };
1023
+ }
1024
+ return null;
1025
+ }
1026
+
1027
+ module.exports = (input) => {
1028
+ const noCacheNoBehavioural = checkNoCacheNoBehavioural(
1029
+ input.headers,
1030
+ input.behavioralDataPacked,
1031
+ );
1032
+ if (noCacheNoBehavioural) {
1033
+ return noCacheNoBehavioural;
1034
+ }
1035
+
1036
+ if (input.captchaResult === "passed") {
1037
+ return {
1038
+ decision: "allow",
1039
+ reason: "Captcha verification successful",
1040
+ score: 100,
1041
+ tags: [\`captcha-type:\${input.captchaType || "unknown"}\`],
1042
+ };
1043
+ }
1044
+
1045
+ return {
1046
+ decision: "deny",
1047
+ reason: "Captcha verification failed",
1048
+ score: 0,
1049
+ tags: ["blocked"],
1050
+ };
1051
+ };
1052
+ `;
1053
+ db.getPowCaptchaRecordByChallenge.mockResolvedValue(challengeRecord);
1054
+ verifyRecency.mockImplementation(() => true);
1055
+ mockDecisionMachine(async (input) => {
1056
+ const decideFn = eval(`(function() { const module = { exports: {} }; ${decisionMachineSource}; return module.exports; })()`);
1057
+ return decideFn(input);
1058
+ });
1059
+ try {
1060
+ const result = await powCaptchaManager.serverVerifyPowCaptchaSolution(dappAccount, challenge, timeout, mockEnv);
1061
+ expect(result.verified).toBe(true);
1062
+ }
1063
+ finally {
1064
+ restoreDecisionMachine();
1065
+ }
1066
+ });
1067
+ });
1068
+ describe("serverVerifyPowCaptchaSolution with spam email domain checking", () => {
1069
+ it("should disapprove when email domain is found in spam list", async () => {
1070
+ const dappAccount = "dappAccount";
1071
+ const timestamp = 123456789;
1072
+ const userAccount = "testUserAccount";
1073
+ const challenge = `${timestamp}${POW_SEPARATOR}${userAccount}${POW_SEPARATOR}${dappAccount}`;
1074
+ const timeout = 1000;
1075
+ const spamEmail = "user@spammydomain.com";
1076
+ const challengeRecord = {
1077
+ challenge,
1078
+ difficulty: 4,
1079
+ dappAccount,
1080
+ userAccount,
1081
+ requestedAtTimestamp: new Date(timestamp),
1082
+ result: { status: CaptchaStatus.approved },
1083
+ userSubmitted: true,
1084
+ serverChecked: false,
1085
+ ipAddress: getCompositeIpAddress(getIPAddress("1.1.1.1")),
1086
+ headers: {},
1087
+ ja4: "ja4",
1088
+ providerSignature: "testSignature",
1089
+ lastUpdatedTimestamp: new Date(),
1090
+ };
1091
+ db.getPowCaptchaRecordByChallenge.mockResolvedValue(challengeRecord);
1092
+ verifyRecency.mockImplementation(() => true);
1093
+ db.markDappUserPoWCommitmentsChecked.mockResolvedValue(undefined);
1094
+ db.updatePowCaptchaRecord.mockResolvedValue(undefined);
1095
+ db.getSpamEmailDomain.mockResolvedValue({
1096
+ domain: "spammydomain.com",
1097
+ });
1098
+ const result = await powCaptchaManager.serverVerifyPowCaptchaSolution(dappAccount, challenge, timeout, mockEnv, undefined, undefined, spamEmail, true);
1099
+ expect(result.verified).toBe(false);
1100
+ expect(db.getSpamEmailDomain).toHaveBeenCalledWith("spammydomain.com");
1101
+ expect(db.updatePowCaptchaRecord).toHaveBeenCalledWith(challengeRecord.challenge, {
1102
+ result: {
1103
+ status: CaptchaStatus.disapproved,
1104
+ reason: "API.SPAM_EMAIL_DOMAIN",
1105
+ },
1106
+ });
1107
+ });
1108
+ it("should allow when email domain is not in spam list", async () => {
1109
+ const dappAccount = "dappAccount";
1110
+ const timestamp = 123456789;
1111
+ const userAccount = "testUserAccount";
1112
+ const challenge = `${timestamp}${POW_SEPARATOR}${userAccount}${POW_SEPARATOR}${dappAccount}`;
1113
+ const timeout = 1000;
1114
+ const legitimateEmail = "user@legitimate.com";
1115
+ const challengeRecord = {
1116
+ challenge,
1117
+ difficulty: 4,
1118
+ dappAccount,
1119
+ userAccount,
1120
+ requestedAtTimestamp: new Date(timestamp),
1121
+ result: { status: CaptchaStatus.approved },
1122
+ userSubmitted: true,
1123
+ serverChecked: false,
1124
+ ipAddress: getCompositeIpAddress(getIPAddress("1.1.1.1")),
1125
+ headers: {},
1126
+ ja4: "ja4",
1127
+ providerSignature: "testSignature",
1128
+ lastUpdatedTimestamp: new Date(),
1129
+ };
1130
+ db.getPowCaptchaRecordByChallenge.mockResolvedValue(challengeRecord);
1131
+ verifyRecency.mockImplementation(() => true);
1132
+ db.markDappUserPoWCommitmentsChecked.mockResolvedValue(undefined);
1133
+ db.getSpamEmailDomain.mockResolvedValue(null);
1134
+ mockDecisionMachine(async () => ({
1135
+ decision: "allow",
1136
+ reason: "Passed all checks",
1137
+ score: 1,
1138
+ tags: [],
1139
+ }));
1140
+ try {
1141
+ const result = await powCaptchaManager.serverVerifyPowCaptchaSolution(dappAccount, challenge, timeout, mockEnv, undefined, undefined, legitimateEmail, true);
1142
+ expect(result.verified).toBe(true);
1143
+ expect(db.getSpamEmailDomain).toHaveBeenCalledWith("legitimate.com");
1144
+ expect(db.updatePowCaptchaRecord).not.toHaveBeenCalledWith(challengeRecord.challenge, expect.objectContaining({
1145
+ result: expect.objectContaining({
1146
+ reason: "API.SPAM_EMAIL_DOMAIN",
1147
+ }),
1148
+ }));
1149
+ }
1150
+ finally {
1151
+ restoreDecisionMachine();
1152
+ }
1153
+ });
1154
+ it("should skip spam check when no email is provided", async () => {
1155
+ const dappAccount = "dappAccount";
1156
+ const timestamp = 123456789;
1157
+ const userAccount = "testUserAccount";
1158
+ const challenge = `${timestamp}${POW_SEPARATOR}${userAccount}${POW_SEPARATOR}${dappAccount}`;
1159
+ const timeout = 1000;
1160
+ const challengeRecord = {
1161
+ challenge,
1162
+ difficulty: 4,
1163
+ dappAccount,
1164
+ userAccount,
1165
+ requestedAtTimestamp: new Date(timestamp),
1166
+ result: { status: CaptchaStatus.approved },
1167
+ userSubmitted: true,
1168
+ serverChecked: false,
1169
+ ipAddress: getCompositeIpAddress(getIPAddress("1.1.1.1")),
1170
+ headers: {},
1171
+ ja4: "ja4",
1172
+ providerSignature: "testSignature",
1173
+ lastUpdatedTimestamp: new Date(),
1174
+ };
1175
+ db.getPowCaptchaRecordByChallenge.mockResolvedValue(challengeRecord);
1176
+ verifyRecency.mockImplementation(() => true);
1177
+ db.markDappUserPoWCommitmentsChecked.mockResolvedValue(undefined);
1178
+ mockDecisionMachine(async () => ({
1179
+ decision: "allow",
1180
+ reason: "Passed all checks",
1181
+ score: 1,
1182
+ tags: [],
1183
+ }));
1184
+ try {
1185
+ const result = await powCaptchaManager.serverVerifyPowCaptchaSolution(dappAccount, challenge, timeout, mockEnv, undefined, undefined, undefined);
1186
+ expect(result.verified).toBe(true);
1187
+ expect(db.getSpamEmailDomain).not.toHaveBeenCalled();
1188
+ }
1189
+ finally {
1190
+ restoreDecisionMachine();
1191
+ }
1192
+ });
1193
+ it("should handle @domain.com email format correctly", async () => {
1194
+ const dappAccount = "dappAccount";
1195
+ const timestamp = 123456789;
1196
+ const userAccount = "testUserAccount";
1197
+ const challenge = `${timestamp}${POW_SEPARATOR}${userAccount}${POW_SEPARATOR}${dappAccount}`;
1198
+ const timeout = 1000;
1199
+ const atDomainEmail = "@spammydomain.com";
1200
+ const challengeRecord = {
1201
+ challenge,
1202
+ difficulty: 4,
1203
+ dappAccount,
1204
+ userAccount,
1205
+ requestedAtTimestamp: new Date(timestamp),
1206
+ result: { status: CaptchaStatus.approved },
1207
+ userSubmitted: true,
1208
+ serverChecked: false,
1209
+ ipAddress: getCompositeIpAddress(getIPAddress("1.1.1.1")),
1210
+ headers: {},
1211
+ ja4: "ja4",
1212
+ providerSignature: "testSignature",
1213
+ lastUpdatedTimestamp: new Date(),
1214
+ };
1215
+ db.getPowCaptchaRecordByChallenge.mockResolvedValue(challengeRecord);
1216
+ verifyRecency.mockImplementation(() => true);
1217
+ db.markDappUserPoWCommitmentsChecked.mockResolvedValue(undefined);
1218
+ db.updatePowCaptchaRecord.mockResolvedValue(undefined);
1219
+ db.getSpamEmailDomain.mockResolvedValue({
1220
+ domain: "spammydomain.com",
1221
+ });
1222
+ const result = await powCaptchaManager.serverVerifyPowCaptchaSolution(dappAccount, challenge, timeout, mockEnv, undefined, undefined, atDomainEmail, true);
1223
+ expect(result.verified).toBe(false);
1224
+ expect(db.getSpamEmailDomain).toHaveBeenCalledWith("spammydomain.com");
1225
+ expect(db.updatePowCaptchaRecord).toHaveBeenCalledWith(challengeRecord.challenge, {
1226
+ result: {
1227
+ status: CaptchaStatus.disapproved,
1228
+ reason: "API.SPAM_EMAIL_DOMAIN",
1229
+ },
1230
+ });
1231
+ });
1232
+ it("should handle domain-only format correctly", async () => {
1233
+ const dappAccount = "dappAccount";
1234
+ const timestamp = 123456789;
1235
+ const userAccount = "testUserAccount";
1236
+ const challenge = `${timestamp}${POW_SEPARATOR}${userAccount}${POW_SEPARATOR}${dappAccount}`;
1237
+ const timeout = 1000;
1238
+ const domainOnly = "spammydomain.com";
1239
+ const challengeRecord = {
1240
+ challenge,
1241
+ difficulty: 4,
1242
+ dappAccount,
1243
+ userAccount,
1244
+ requestedAtTimestamp: new Date(timestamp),
1245
+ result: { status: CaptchaStatus.approved },
1246
+ userSubmitted: true,
1247
+ serverChecked: false,
1248
+ ipAddress: getCompositeIpAddress(getIPAddress("1.1.1.1")),
1249
+ headers: {},
1250
+ ja4: "ja4",
1251
+ providerSignature: "testSignature",
1252
+ lastUpdatedTimestamp: new Date(),
1253
+ };
1254
+ db.getPowCaptchaRecordByChallenge.mockResolvedValue(challengeRecord);
1255
+ verifyRecency.mockImplementation(() => true);
1256
+ db.markDappUserPoWCommitmentsChecked.mockResolvedValue(undefined);
1257
+ db.updatePowCaptchaRecord.mockResolvedValue(undefined);
1258
+ db.getSpamEmailDomain.mockResolvedValue({
1259
+ domain: "spammydomain.com",
1260
+ });
1261
+ const result = await powCaptchaManager.serverVerifyPowCaptchaSolution(dappAccount, challenge, timeout, mockEnv, undefined, undefined, domainOnly, true);
1262
+ expect(result.verified).toBe(false);
1263
+ expect(db.getSpamEmailDomain).toHaveBeenCalledWith("spammydomain.com");
1264
+ });
1265
+ it("should continue verification if spam check fails (fail-safe)", async () => {
1266
+ const dappAccount = "dappAccount";
1267
+ const timestamp = 123456789;
1268
+ const userAccount = "testUserAccount";
1269
+ const challenge = `${timestamp}${POW_SEPARATOR}${userAccount}${POW_SEPARATOR}${dappAccount}`;
1270
+ const timeout = 1000;
1271
+ const email = "user@example.com";
1272
+ const challengeRecord = {
1273
+ challenge,
1274
+ difficulty: 4,
1275
+ dappAccount,
1276
+ userAccount,
1277
+ requestedAtTimestamp: new Date(timestamp),
1278
+ result: { status: CaptchaStatus.approved },
1279
+ userSubmitted: true,
1280
+ serverChecked: false,
1281
+ ipAddress: getCompositeIpAddress(getIPAddress("1.1.1.1")),
1282
+ headers: {},
1283
+ ja4: "ja4",
1284
+ providerSignature: "testSignature",
1285
+ lastUpdatedTimestamp: new Date(),
1286
+ };
1287
+ db.getPowCaptchaRecordByChallenge.mockResolvedValue(challengeRecord);
1288
+ verifyRecency.mockImplementation(() => true);
1289
+ db.markDappUserPoWCommitmentsChecked.mockResolvedValue(undefined);
1290
+ db.getSpamEmailDomain.mockRejectedValue(new Error("Database connection error"));
1291
+ mockDecisionMachine(async () => ({
1292
+ decision: "allow",
1293
+ reason: "Passed all checks",
1294
+ score: 1,
1295
+ tags: [],
1296
+ }));
1297
+ try {
1298
+ const result = await powCaptchaManager.serverVerifyPowCaptchaSolution(dappAccount, challenge, timeout, mockEnv, undefined, undefined, email, true);
1299
+ expect(result.verified).toBe(true);
1300
+ expect(db.getSpamEmailDomain).toHaveBeenCalled();
1301
+ }
1302
+ finally {
1303
+ restoreDecisionMachine();
1304
+ }
1305
+ });
1306
+ });
1307
+ describe("session result tracking", () => {
1308
+ const ipAddress = getIPAddress("1.1.1.1");
1309
+ const headers = { a: "1", b: "2", c: "3" };
1310
+ const sessionId = "test-session-for-result";
1311
+ it("should update session with approved result after successful user submission", async () => {
1312
+ const timestamp = 123456789;
1313
+ const userAccount = "testUserAccount";
1314
+ const challenge = `${timestamp}${POW_SEPARATOR}${userAccount}${POW_SEPARATOR}${pair.address}`;
1315
+ const challengeRecord = {
1316
+ challenge,
1317
+ difficulty: 4,
1318
+ dappAccount: pair.address,
1319
+ userAccount,
1320
+ requestedAtTimestamp: new Date(timestamp),
1321
+ result: { status: CaptchaStatus.pending },
1322
+ userSubmitted: false,
1323
+ serverChecked: false,
1324
+ ipAddress: getCompositeIpAddress(ipAddress),
1325
+ headers,
1326
+ ja4: "ja4",
1327
+ providerSignature: "sig",
1328
+ lastUpdatedTimestamp: new Date(),
1329
+ sessionId,
1330
+ };
1331
+ verifyRecency.mockImplementation(() => true);
1332
+ checkPowSignature.mockImplementation(() => true);
1333
+ validateSolution.mockImplementation(() => true);
1334
+ db.getPowCaptchaRecordByChallenge.mockResolvedValue(challengeRecord);
1335
+ db.updatePowCaptchaRecordResult.mockResolvedValue(undefined);
1336
+ db.updateSessionRecord = vi.fn().mockResolvedValue(undefined);
1337
+ await powCaptchaManager.verifyPowCaptchaSolution(challenge, "sig", 12345, 1000, "userSig", ipAddress, headers);
1338
+ expect(db.updateSessionRecord).toHaveBeenCalledWith(sessionId, {
1339
+ userSubmitted: true,
1340
+ result: { status: CaptchaStatus.approved },
1341
+ });
1342
+ });
1343
+ it("should update session with disapproved result on invalid solution", async () => {
1344
+ const timestamp = 123456789;
1345
+ const userAccount = "testUserAccount";
1346
+ const challenge = `${timestamp}${POW_SEPARATOR}${userAccount}${POW_SEPARATOR}${pair.address}`;
1347
+ const challengeRecord = {
1348
+ challenge,
1349
+ difficulty: 4,
1350
+ dappAccount: pair.address,
1351
+ userAccount,
1352
+ requestedAtTimestamp: new Date(timestamp),
1353
+ result: { status: CaptchaStatus.pending },
1354
+ userSubmitted: false,
1355
+ serverChecked: false,
1356
+ ipAddress: getCompositeIpAddress(ipAddress),
1357
+ headers,
1358
+ ja4: "ja4",
1359
+ providerSignature: "sig",
1360
+ lastUpdatedTimestamp: new Date(),
1361
+ sessionId,
1362
+ };
1363
+ verifyRecency.mockImplementation(() => true);
1364
+ checkPowSignature.mockImplementation(() => true);
1365
+ validateSolution.mockImplementation(() => false);
1366
+ db.getPowCaptchaRecordByChallenge.mockResolvedValue(challengeRecord);
1367
+ db.updatePowCaptchaRecordResult.mockResolvedValue(undefined);
1368
+ db.updateSessionRecord = vi.fn().mockResolvedValue(undefined);
1369
+ await powCaptchaManager.verifyPowCaptchaSolution(challenge, "sig", 12345, 1000, "userSig", ipAddress, headers);
1370
+ expect(db.updateSessionRecord).toHaveBeenCalledWith(sessionId, {
1371
+ userSubmitted: true,
1372
+ result: {
1373
+ status: CaptchaStatus.disapproved,
1374
+ reason: "CAPTCHA.INVALID_SOLUTION",
1375
+ },
1376
+ });
1377
+ });
1378
+ it("should update session with disapproved result on timeout during user submission", async () => {
1379
+ const timestamp = 123456789;
1380
+ const userAccount = "testUserAccount";
1381
+ const challenge = `${timestamp}${POW_SEPARATOR}${userAccount}${POW_SEPARATOR}${pair.address}`;
1382
+ const challengeRecord = {
1383
+ challenge,
1384
+ difficulty: 4,
1385
+ dappAccount: pair.address,
1386
+ userAccount,
1387
+ requestedAtTimestamp: new Date(timestamp),
1388
+ result: { status: CaptchaStatus.pending },
1389
+ userSubmitted: false,
1390
+ serverChecked: false,
1391
+ ipAddress: getCompositeIpAddress(ipAddress),
1392
+ headers,
1393
+ ja4: "ja4",
1394
+ providerSignature: "sig",
1395
+ lastUpdatedTimestamp: new Date(),
1396
+ sessionId,
1397
+ };
1398
+ verifyRecency.mockImplementation(() => false);
1399
+ checkPowSignature.mockImplementation(() => true);
1400
+ db.getPowCaptchaRecordByChallenge.mockResolvedValue(challengeRecord);
1401
+ db.updatePowCaptchaRecordResult.mockResolvedValue(undefined);
1402
+ db.updateSessionRecord = vi.fn().mockResolvedValue(undefined);
1403
+ await powCaptchaManager.verifyPowCaptchaSolution(challenge, "sig", 12345, 1000, "userSig", ipAddress, headers);
1404
+ expect(db.updateSessionRecord).toHaveBeenCalledWith(sessionId, {
1405
+ userSubmitted: true,
1406
+ result: {
1407
+ status: CaptchaStatus.disapproved,
1408
+ reason: "CAPTCHA.INVALID_TIMESTAMP",
1409
+ },
1410
+ });
1411
+ });
1412
+ it("should not update session when challengeRecord has no sessionId", async () => {
1413
+ const timestamp = 123456789;
1414
+ const userAccount = "testUserAccount";
1415
+ const challenge = `${timestamp}${POW_SEPARATOR}${userAccount}${POW_SEPARATOR}${pair.address}`;
1416
+ const challengeRecord = {
1417
+ challenge,
1418
+ difficulty: 4,
1419
+ dappAccount: pair.address,
1420
+ userAccount,
1421
+ requestedAtTimestamp: new Date(timestamp),
1422
+ result: { status: CaptchaStatus.pending },
1423
+ userSubmitted: false,
1424
+ serverChecked: false,
1425
+ ipAddress: getCompositeIpAddress(ipAddress),
1426
+ headers,
1427
+ ja4: "ja4",
1428
+ providerSignature: "sig",
1429
+ lastUpdatedTimestamp: new Date(),
1430
+ };
1431
+ verifyRecency.mockImplementation(() => true);
1432
+ checkPowSignature.mockImplementation(() => true);
1433
+ validateSolution.mockImplementation(() => true);
1434
+ db.getPowCaptchaRecordByChallenge.mockResolvedValue(challengeRecord);
1435
+ db.updatePowCaptchaRecordResult.mockResolvedValue(undefined);
1436
+ db.updateSessionRecord = vi.fn().mockResolvedValue(undefined);
1437
+ await powCaptchaManager.verifyPowCaptchaSolution(challenge, "sig", 12345, 1000, "userSig", ipAddress, headers);
1438
+ expect(db.updateSessionRecord).not.toHaveBeenCalled();
1439
+ });
1440
+ it("should update session as serverChecked and approved on successful server verification", async () => {
1441
+ const dappAccount = "dappAccount";
1442
+ const timestamp = 123456789;
1443
+ const userAccount = "testUserAccount";
1444
+ const challenge = `${timestamp}${POW_SEPARATOR}${userAccount}${POW_SEPARATOR}${dappAccount}`;
1445
+ const challengeRecord = {
1446
+ challenge,
1447
+ dappAccount,
1448
+ userAccount,
1449
+ requestedAtTimestamp: new Date(timestamp),
1450
+ serverChecked: false,
1451
+ result: { status: CaptchaStatus.approved },
1452
+ sessionId,
1453
+ ipAddress: getCompositeIpAddress(getIPAddress("1.1.1.1")),
1454
+ };
1455
+ db.getPowCaptchaRecordByChallenge.mockResolvedValue(challengeRecord);
1456
+ verifyRecency.mockImplementation(() => true);
1457
+ db.updateSessionRecord = vi.fn().mockResolvedValue(undefined);
1458
+ const result = await powCaptchaManager.serverVerifyPowCaptchaSolution(dappAccount, challenge, 1000, mockEnv);
1459
+ expect(result.verified).toBe(true);
1460
+ expect(db.updateSessionRecord).toHaveBeenCalledWith(sessionId, {
1461
+ serverChecked: true,
1462
+ result: { status: CaptchaStatus.approved },
1463
+ }, true);
1464
+ });
1465
+ it("should update session as serverChecked and disapproved on recency failure", async () => {
1466
+ const dappAccount = "dappAccount";
1467
+ const timestamp = 123456789;
1468
+ const userAccount = "testUserAccount";
1469
+ const challenge = `${timestamp}${POW_SEPARATOR}${userAccount}${POW_SEPARATOR}${dappAccount}`;
1470
+ const challengeRecord = {
1471
+ challenge,
1472
+ dappAccount,
1473
+ userAccount,
1474
+ requestedAtTimestamp: new Date(timestamp),
1475
+ serverChecked: false,
1476
+ result: { status: CaptchaStatus.approved },
1477
+ sessionId,
1478
+ };
1479
+ db.getPowCaptchaRecordByChallenge.mockResolvedValue(challengeRecord);
1480
+ verifyRecency.mockImplementation(() => false);
1481
+ db.updatePowCaptchaRecord.mockResolvedValue(undefined);
1482
+ db.updateSessionRecord = vi.fn().mockResolvedValue(undefined);
1483
+ const result = await powCaptchaManager.serverVerifyPowCaptchaSolution(dappAccount, challenge, 1000, mockEnv);
1484
+ expect(result.verified).toBe(false);
1485
+ expect(db.updateSessionRecord).toHaveBeenCalledWith(sessionId, {
1486
+ serverChecked: true,
1487
+ result: {
1488
+ status: CaptchaStatus.disapproved,
1489
+ reason: "API.TIMESTAMP_TOO_OLD",
1490
+ },
1491
+ }, true);
1492
+ });
1493
+ });
1494
+ describe("Write batching optimizations", () => {
1495
+ describe("verifyPowCaptchaSolution parallel writes", () => {
1496
+ it("should write result and session update for valid solution with session", async () => {
1497
+ const timestamp = Date.now();
1498
+ const userAccount = "testUserAccount";
1499
+ const challenge = `${timestamp}${POW_SEPARATOR}${userAccount}${POW_SEPARATOR}${pair.address}`;
1500
+ const sessionId = "test-session-id";
1501
+ const ipAddress = getIPAddress("1.1.1.1");
1502
+ const headers = { a: "1" };
1503
+ const challengeRecord = {
1504
+ challenge,
1505
+ difficulty: 4,
1506
+ dappAccount: pair.address,
1507
+ userAccount,
1508
+ requestedAtTimestamp: new Date(timestamp),
1509
+ result: { status: CaptchaStatus.pending },
1510
+ userSubmitted: false,
1511
+ serverChecked: false,
1512
+ ipAddress: getCompositeIpAddress(ipAddress),
1513
+ headers,
1514
+ ja4: "ja4",
1515
+ providerSignature: "sig",
1516
+ lastUpdatedTimestamp: new Date(),
1517
+ sessionId,
1518
+ };
1519
+ verifyRecency.mockReturnValue(true);
1520
+ checkPowSignature.mockImplementation(() => { });
1521
+ validateSolution.mockReturnValue(true);
1522
+ db.getPowCaptchaRecordByChallenge.mockResolvedValue(challengeRecord);
1523
+ db.updatePowCaptchaRecordResult.mockResolvedValue(undefined);
1524
+ db.updateSessionRecord.mockResolvedValue(undefined);
1525
+ await powCaptchaManager.verifyPowCaptchaSolution(challenge, "sig", 12345, 1000, "userSig", ipAddress, headers);
1526
+ expect(db.updatePowCaptchaRecordResult).toHaveBeenCalledOnce();
1527
+ expect(db.updateSessionRecord).toHaveBeenCalledWith(sessionId, {
1528
+ userSubmitted: true,
1529
+ result: { status: CaptchaStatus.approved },
1530
+ });
1531
+ });
1532
+ it("should write timeout result and session for timed-out solution with session", async () => {
1533
+ const timestamp = Date.now();
1534
+ const userAccount = "testUserAccount";
1535
+ const challenge = `${timestamp}${POW_SEPARATOR}${userAccount}${POW_SEPARATOR}${pair.address}`;
1536
+ const sessionId = "test-session-id";
1537
+ const ipAddress = getIPAddress("1.1.1.1");
1538
+ const headers = {};
1539
+ const challengeRecord = {
1540
+ challenge,
1541
+ difficulty: 4,
1542
+ dappAccount: pair.address,
1543
+ userAccount,
1544
+ requestedAtTimestamp: new Date(timestamp),
1545
+ result: { status: CaptchaStatus.pending },
1546
+ userSubmitted: false,
1547
+ serverChecked: false,
1548
+ ipAddress: getCompositeIpAddress(ipAddress),
1549
+ headers,
1550
+ ja4: "ja4",
1551
+ providerSignature: "sig",
1552
+ lastUpdatedTimestamp: new Date(),
1553
+ sessionId,
1554
+ };
1555
+ verifyRecency.mockReturnValue(false);
1556
+ checkPowSignature.mockImplementation(() => { });
1557
+ db.getPowCaptchaRecordByChallenge.mockResolvedValue(challengeRecord);
1558
+ db.updatePowCaptchaRecordResult.mockResolvedValue(undefined);
1559
+ db.updateSessionRecord.mockResolvedValue(undefined);
1560
+ const result = await powCaptchaManager.verifyPowCaptchaSolution(challenge, "sig", 12345, 1000, "userSig", ipAddress, headers);
1561
+ expect(result.verified).toBe(false);
1562
+ expect(db.updatePowCaptchaRecordResult).toHaveBeenCalledOnce();
1563
+ expect(db.updateSessionRecord).toHaveBeenCalledWith(sessionId, {
1564
+ userSubmitted: true,
1565
+ result: {
1566
+ status: CaptchaStatus.disapproved,
1567
+ reason: "CAPTCHA.INVALID_TIMESTAMP",
1568
+ },
1569
+ });
1570
+ });
1571
+ });
1572
+ describe("serverVerifyPowCaptchaSolution batched writes", () => {
1573
+ it("should accumulate pow record updates and write once on success", async () => {
1574
+ const dappAccount = "dappAccount";
1575
+ const timestamp = Date.now();
1576
+ const userAccount = "testUserAccount";
1577
+ const challenge = `${timestamp}${POW_SEPARATOR}${userAccount}${POW_SEPARATOR}${dappAccount}`;
1578
+ const sessionId = "test-session-id";
1579
+ const ipAddress = getIPAddress("1.1.1.1");
1580
+ const challengeRecord = {
1581
+ challenge,
1582
+ difficulty: 4,
1583
+ dappAccount,
1584
+ userAccount,
1585
+ requestedAtTimestamp: new Date(timestamp),
1586
+ result: { status: CaptchaStatus.approved },
1587
+ userSubmitted: true,
1588
+ serverChecked: false,
1589
+ ipAddress: getCompositeIpAddress(ipAddress),
1590
+ headers: {},
1591
+ ja4: "ja4",
1592
+ providerSignature: "sig",
1593
+ lastUpdatedTimestamp: new Date(),
1594
+ sessionId,
1595
+ };
1596
+ db.getPowCaptchaRecordByChallenge.mockResolvedValue(challengeRecord);
1597
+ verifyRecency.mockReturnValue(true);
1598
+ db.markDappUserPoWCommitmentsChecked.mockResolvedValue(undefined);
1599
+ db.updatePowCaptchaRecord.mockResolvedValue(undefined);
1600
+ db.updateSessionRecord.mockResolvedValue(undefined);
1601
+ db.getSessionRecordBySessionId.mockResolvedValue(undefined);
1602
+ const result = await powCaptchaManager.serverVerifyPowCaptchaSolution(dappAccount, challenge, 1000, mockEnv);
1603
+ expect(result.verified).toBe(true);
1604
+ expect(db.markDappUserPoWCommitmentsChecked).toHaveBeenCalledWith([
1605
+ challenge,
1606
+ ]);
1607
+ expect(db.updateSessionRecord).toHaveBeenCalledWith(sessionId, {
1608
+ serverChecked: true,
1609
+ result: { status: CaptchaStatus.approved },
1610
+ }, true);
1611
+ });
1612
+ it("should accumulate providedIp and write in single batch on success with IP", async () => {
1613
+ const dappAccount = "dappAccount";
1614
+ const timestamp = Date.now();
1615
+ const userAccount = "testUserAccount";
1616
+ const challenge = `${timestamp}${POW_SEPARATOR}${userAccount}${POW_SEPARATOR}${dappAccount}`;
1617
+ const sessionId = "test-session-id";
1618
+ const ipAddress = getIPAddress("1.1.1.1");
1619
+ const challengeRecord = {
1620
+ challenge,
1621
+ difficulty: 4,
1622
+ dappAccount,
1623
+ userAccount,
1624
+ requestedAtTimestamp: new Date(timestamp),
1625
+ result: { status: CaptchaStatus.approved },
1626
+ userSubmitted: true,
1627
+ serverChecked: false,
1628
+ ipAddress: getCompositeIpAddress(ipAddress),
1629
+ headers: {},
1630
+ ja4: "ja4",
1631
+ providerSignature: "sig",
1632
+ lastUpdatedTimestamp: new Date(),
1633
+ sessionId,
1634
+ };
1635
+ db.getPowCaptchaRecordByChallenge.mockResolvedValue(challengeRecord);
1636
+ verifyRecency.mockReturnValue(true);
1637
+ db.markDappUserPoWCommitmentsChecked.mockResolvedValue(undefined);
1638
+ db.getClientRecord.mockResolvedValue(undefined);
1639
+ db.updatePowCaptchaRecord.mockResolvedValue(undefined);
1640
+ db.updateSessionRecord.mockResolvedValue(undefined);
1641
+ db.getSessionRecordBySessionId.mockResolvedValue(undefined);
1642
+ await powCaptchaManager.serverVerifyPowCaptchaSolution(dappAccount, challenge, 1000, mockEnv, "2.2.2.2");
1643
+ expect(db.updatePowCaptchaRecord).toHaveBeenCalledOnce();
1644
+ const updateCall = vi.mocked(db.updatePowCaptchaRecord).mock
1645
+ .calls[0];
1646
+ expect(updateCall[0]).toBe(challenge);
1647
+ expect(updateCall[1]).toHaveProperty("providedIp");
1648
+ });
1649
+ it("should not run subsequent checks after first failure (recency)", async () => {
1650
+ const dappAccount = "dappAccount";
1651
+ const timestamp = Date.now();
1652
+ const userAccount = "testUserAccount";
1653
+ const challenge = `${timestamp}${POW_SEPARATOR}${userAccount}${POW_SEPARATOR}${dappAccount}`;
1654
+ const challengeRecord = {
1655
+ challenge,
1656
+ difficulty: 4,
1657
+ dappAccount,
1658
+ userAccount,
1659
+ requestedAtTimestamp: new Date(timestamp),
1660
+ result: { status: CaptchaStatus.approved },
1661
+ userSubmitted: true,
1662
+ serverChecked: false,
1663
+ ipAddress: getCompositeIpAddress(getIPAddress("1.1.1.1")),
1664
+ headers: {},
1665
+ ja4: "ja4",
1666
+ providerSignature: "sig",
1667
+ lastUpdatedTimestamp: new Date(),
1668
+ };
1669
+ db.getPowCaptchaRecordByChallenge.mockResolvedValue(challengeRecord);
1670
+ verifyRecency.mockReturnValue(false);
1671
+ db.markDappUserPoWCommitmentsChecked.mockResolvedValue(undefined);
1672
+ db.updatePowCaptchaRecord.mockResolvedValue(undefined);
1673
+ const result = await powCaptchaManager.serverVerifyPowCaptchaSolution(dappAccount, challenge, 1000, mockEnv, undefined, undefined, "user@spam.com", true);
1674
+ expect(result.verified).toBe(false);
1675
+ expect(result.reason).toBe("API.TIMESTAMP_TOO_OLD");
1676
+ expect(db.getSpamEmailDomain).not.toHaveBeenCalled();
1677
+ });
1678
+ it("should write accumulated failure result in single batch", async () => {
1679
+ const dappAccount = "dappAccount";
1680
+ const timestamp = Date.now();
1681
+ const userAccount = "testUserAccount";
1682
+ const challenge = `${timestamp}${POW_SEPARATOR}${userAccount}${POW_SEPARATOR}${dappAccount}`;
1683
+ const challengeRecord = {
1684
+ challenge,
1685
+ difficulty: 4,
1686
+ dappAccount,
1687
+ userAccount,
1688
+ requestedAtTimestamp: new Date(timestamp),
1689
+ result: { status: CaptchaStatus.approved },
1690
+ userSubmitted: true,
1691
+ serverChecked: false,
1692
+ ipAddress: getCompositeIpAddress(getIPAddress("1.1.1.1")),
1693
+ headers: {},
1694
+ ja4: "ja4",
1695
+ providerSignature: "sig",
1696
+ lastUpdatedTimestamp: new Date(),
1697
+ };
1698
+ db.getPowCaptchaRecordByChallenge.mockResolvedValue(challengeRecord);
1699
+ verifyRecency.mockReturnValue(false);
1700
+ db.markDappUserPoWCommitmentsChecked.mockResolvedValue(undefined);
1701
+ db.updatePowCaptchaRecord.mockResolvedValue(undefined);
1702
+ await powCaptchaManager.serverVerifyPowCaptchaSolution(dappAccount, challenge, 1000, mockEnv);
1703
+ expect(db.updatePowCaptchaRecord).toHaveBeenCalledOnce();
1704
+ expect(db.updatePowCaptchaRecord).toHaveBeenCalledWith(challenge, {
1705
+ result: {
1706
+ status: CaptchaStatus.disapproved,
1707
+ reason: "API.TIMESTAMP_TOO_OLD",
1708
+ },
1709
+ });
1710
+ });
495
1711
  });
496
1712
  });
497
1713
  });