@prosopo/provider 4.15.14 → 4.15.15

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 (232) hide show
  1. package/.turbo/turbo-build$colon$cjs.log +106 -106
  2. package/.turbo/turbo-build$colon$tsc.log +24 -24
  3. package/.turbo/turbo-build.log +107 -104
  4. package/CHANGELOG.md +49 -0
  5. package/dist/_virtual/_rolldown/runtime.js +3 -0
  6. package/dist/api/admin/apiAdminRoutesProvider.js +27 -46
  7. package/dist/api/admin/apiClearAllCountersEndpoint.js +62 -57
  8. package/dist/api/admin/apiDnsEventEndpoint.js +79 -92
  9. package/dist/api/admin/apiGetAllDecisionMachinesEndpoint.js +43 -45
  10. package/dist/api/admin/apiGetDecisionMachineEndpoint.js +40 -40
  11. package/dist/api/admin/apiRegisterSiteKeyEndpoint.js +23 -22
  12. package/dist/api/admin/apiRegisterSiteKeysEndpoint.js +26 -30
  13. package/dist/api/admin/apiRemoveAllDecisionMachinesEndpoint.js +35 -37
  14. package/dist/api/admin/apiRemoveDecisionMachineEndpoint.js +40 -40
  15. package/dist/api/admin/apiRemoveDetectorKeyEndpoint.d.ts.map +1 -1
  16. package/dist/api/admin/apiRemoveDetectorKeyEndpoint.js +29 -31
  17. package/dist/api/admin/apiRemoveSiteKeyEndpoint.js +33 -29
  18. package/dist/api/admin/apiRemoveSiteKeysEndpoint.js +32 -31
  19. package/dist/api/admin/apiToggleMaintenanceModeEndpoint.js +43 -35
  20. package/dist/api/admin/apiUpdateDecisionMachineEndpoint.js +43 -63
  21. package/dist/api/admin/apiUpdateDetectorKeyEndpoint.js +36 -35
  22. package/dist/api/admin/createApiAdminRoutesProvider.js +6 -8
  23. package/dist/api/blacklistRequestInspector.js +285 -377
  24. package/dist/api/block.js +17 -30
  25. package/dist/api/captcha/checkSpamEmail.js +88 -98
  26. package/dist/api/captcha/contextAwareValidation.js +21 -15
  27. package/dist/api/captcha/getFrictionlessCaptchaChallenge/accessPolicy.js +126 -140
  28. package/dist/api/captcha/getFrictionlessCaptchaChallenge/constants.js +11 -12
  29. package/dist/api/captcha/getFrictionlessCaptchaChallenge/decisionMachine.js +236 -299
  30. package/dist/api/captcha/getFrictionlessCaptchaChallenge/decryptSimdReadings.js +10 -13
  31. package/dist/api/captcha/getFrictionlessCaptchaChallenge/handler.js +336 -462
  32. package/dist/api/captcha/getFrictionlessCaptchaChallenge/honeypotResponse.js +17 -14
  33. package/dist/api/captcha/getFrictionlessCaptchaChallenge/sessionDedup.js +27 -27
  34. package/dist/api/captcha/getFrictionlessCaptchaChallenge/shortCircuit.js +42 -63
  35. package/dist/api/captcha/getFrictionlessCaptchaChallenge.js +2 -4
  36. package/dist/api/captcha/getImageCaptchaChallenge.js +136 -207
  37. package/dist/api/captcha/getPoWCaptchaChallenge.js +128 -198
  38. package/dist/api/captcha/getPuzzleCaptchaChallenge.js +132 -201
  39. package/dist/api/captcha/maintenanceModeResponses.js +38 -47
  40. package/dist/api/captcha/submitImageCaptchaSolution.js +75 -101
  41. package/dist/api/captcha/submitPoWCaptchaSolution.js +137 -207
  42. package/dist/api/captcha/submitPuzzleCaptchaSolution.js +73 -105
  43. package/dist/api/captcha.js +76 -58
  44. package/dist/api/dnsEventUrl.js +20 -22
  45. package/dist/api/domainMiddleware.js +104 -115
  46. package/dist/api/forwardVerify.js +62 -48
  47. package/dist/api/handshakeTimingMiddleware.js +43 -62
  48. package/dist/api/hardBlockVerdictCache.js +114 -117
  49. package/dist/api/headerCheckMiddleware.js +34 -34
  50. package/dist/api/ignoreMiddleware.js +13 -15
  51. package/dist/api/ipInfoMiddleware.js +17 -19
  52. package/dist/api/ja4.js +149 -188
  53. package/dist/api/ja4Middleware.js +39 -54
  54. package/dist/api/metrics.js +245 -257
  55. package/dist/api/public.js +52 -54
  56. package/dist/api/robotsMiddleware.js +10 -10
  57. package/dist/api/startProviderApi.js +205 -232
  58. package/dist/api/testSiteKey.js +16 -16
  59. package/dist/api/validateAddress.js +23 -21
  60. package/dist/api/verify.js +334 -377
  61. package/dist/cjs/_virtual/_rolldown/runtime.cjs +23 -0
  62. package/dist/cjs/api/admin/apiAdminRoutesProvider.cjs +40 -59
  63. package/dist/cjs/api/admin/apiClearAllCountersEndpoint.cjs +63 -58
  64. package/dist/cjs/api/admin/apiDnsEventEndpoint.cjs +82 -93
  65. package/dist/cjs/api/admin/apiGetAllDecisionMachinesEndpoint.cjs +46 -48
  66. package/dist/cjs/api/admin/apiGetDecisionMachineEndpoint.cjs +41 -41
  67. package/dist/cjs/api/admin/apiRegisterSiteKeyEndpoint.cjs +25 -24
  68. package/dist/cjs/api/admin/apiRegisterSiteKeysEndpoint.cjs +28 -32
  69. package/dist/cjs/api/admin/apiRemoveAllDecisionMachinesEndpoint.cjs +38 -40
  70. package/dist/cjs/api/admin/apiRemoveDecisionMachineEndpoint.cjs +41 -41
  71. package/dist/cjs/api/admin/apiRemoveDetectorKeyEndpoint.cjs +31 -33
  72. package/dist/cjs/api/admin/apiRemoveSiteKeyEndpoint.cjs +35 -31
  73. package/dist/cjs/api/admin/apiRemoveSiteKeysEndpoint.cjs +34 -33
  74. package/dist/cjs/api/admin/apiToggleMaintenanceModeEndpoint.cjs +45 -35
  75. package/dist/cjs/api/admin/apiUpdateDecisionMachineEndpoint.cjs +45 -65
  76. package/dist/cjs/api/admin/apiUpdateDetectorKeyEndpoint.cjs +38 -37
  77. package/dist/cjs/api/admin/createApiAdminRoutesProvider.cjs +7 -8
  78. package/dist/cjs/api/blacklistRequestInspector.cjs +288 -376
  79. package/dist/cjs/api/block.cjs +18 -30
  80. package/dist/cjs/api/captcha/checkSpamEmail.cjs +90 -100
  81. package/dist/cjs/api/captcha/contextAwareValidation.cjs +21 -14
  82. package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge/accessPolicy.cjs +125 -140
  83. package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge/constants.cjs +10 -10
  84. package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge/decisionMachine.cjs +238 -301
  85. package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge/decryptSimdReadings.cjs +9 -12
  86. package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge/handler.cjs +349 -473
  87. package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge/honeypotResponse.cjs +18 -14
  88. package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge/sessionDedup.cjs +26 -26
  89. package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge/shortCircuit.cjs +42 -63
  90. package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge.cjs +2 -3
  91. package/dist/cjs/api/captcha/getImageCaptchaChallenge.cjs +138 -208
  92. package/dist/cjs/api/captcha/getPoWCaptchaChallenge.cjs +131 -200
  93. package/dist/cjs/api/captcha/getPuzzleCaptchaChallenge.cjs +135 -203
  94. package/dist/cjs/api/captcha/maintenanceModeResponses.cjs +38 -44
  95. package/dist/cjs/api/captcha/submitImageCaptchaSolution.cjs +77 -102
  96. package/dist/cjs/api/captcha/submitPoWCaptchaSolution.cjs +140 -209
  97. package/dist/cjs/api/captcha/submitPuzzleCaptchaSolution.cjs +76 -107
  98. package/dist/cjs/api/captcha.cjs +79 -58
  99. package/dist/cjs/api/dnsEventUrl.cjs +20 -21
  100. package/dist/cjs/api/domainMiddleware.cjs +106 -117
  101. package/dist/cjs/api/forwardVerify.cjs +63 -49
  102. package/dist/cjs/api/handshakeTimingMiddleware.cjs +43 -61
  103. package/dist/cjs/api/hardBlockVerdictCache.cjs +114 -114
  104. package/dist/cjs/api/headerCheckMiddleware.cjs +34 -34
  105. package/dist/cjs/api/ignoreMiddleware.cjs +12 -14
  106. package/dist/cjs/api/ipInfoMiddleware.cjs +16 -18
  107. package/dist/cjs/api/ja4.cjs +149 -187
  108. package/dist/cjs/api/ja4Middleware.cjs +40 -53
  109. package/dist/cjs/api/metrics.cjs +229 -228
  110. package/dist/cjs/api/public.cjs +57 -57
  111. package/dist/cjs/api/robotsMiddleware.cjs +9 -9
  112. package/dist/cjs/api/startProviderApi.cjs +219 -237
  113. package/dist/cjs/api/testSiteKey.cjs +15 -14
  114. package/dist/cjs/api/validateAddress.cjs +24 -21
  115. package/dist/cjs/api/verify.cjs +338 -379
  116. package/dist/cjs/compositeIpAddress.cjs +40 -47
  117. package/dist/cjs/index.cjs +65 -71
  118. package/dist/cjs/pairs.cjs +31 -35
  119. package/dist/cjs/rules/lang.cjs +10 -14
  120. package/dist/cjs/schedulers/captchaScheduler.cjs +26 -32
  121. package/dist/cjs/schedulers/getClientList.cjs +35 -33
  122. package/dist/cjs/schedulers/setClientEntropy.cjs +35 -33
  123. package/dist/cjs/schedulers/updateSpamEmailDomains.cjs +39 -43
  124. package/dist/cjs/services/ipComparison.cjs +122 -119
  125. package/dist/cjs/tasks/captchaManager.cjs +407 -486
  126. package/dist/cjs/tasks/client/clientTasks.cjs +347 -470
  127. package/dist/cjs/tasks/dataset/datasetTasks.cjs +25 -29
  128. package/dist/cjs/tasks/dataset/datasetTasksUtils.cjs +16 -32
  129. package/dist/cjs/tasks/decisionMachine/decisionMachineRunner.cjs +220 -307
  130. package/dist/cjs/tasks/detection/decodeBehavior.cjs +408 -317
  131. package/dist/cjs/tasks/detection/decodePayload.cjs +686 -468
  132. package/dist/cjs/tasks/detection/decodeSimd.cjs +462 -304
  133. package/dist/cjs/tasks/detection/getBotScore.cjs +37 -43
  134. package/dist/cjs/tasks/dnsEvent/enrichDnsEvent.cjs +88 -71
  135. package/dist/cjs/tasks/frictionless/frictionlessTasks.cjs +348 -486
  136. package/dist/cjs/tasks/frictionless/frictionlessTasksUtils.cjs +13 -30
  137. package/dist/cjs/tasks/frictionless/routingMachine.cjs +71 -55
  138. package/dist/cjs/tasks/imgCaptcha/imgCaptchaTasks.cjs +555 -806
  139. package/dist/cjs/tasks/imgCaptcha/imgCaptchaTasksUtils.cjs +22 -23
  140. package/dist/cjs/tasks/index.cjs +2 -4
  141. package/dist/cjs/tasks/powCaptcha/powTasks.cjs +491 -687
  142. package/dist/cjs/tasks/powCaptcha/powTasksUtils.cjs +17 -29
  143. package/dist/cjs/tasks/puzzleCaptcha/puzzleTasks.cjs +422 -591
  144. package/dist/cjs/tasks/puzzleCaptcha/puzzleTasksUtils.cjs +8 -5
  145. package/dist/cjs/tasks/spam/checkSpamEmail.cjs +133 -141
  146. package/dist/cjs/tasks/spam/checkTrafficFilter.cjs +118 -76
  147. package/dist/cjs/tasks/spam/evaluateEmailSpamRules.cjs +75 -84
  148. package/dist/cjs/tasks/spam/updateSpamEmailDomains.cjs +40 -56
  149. package/dist/cjs/tasks/tasks.cjs +107 -158
  150. package/dist/cjs/util/usageCounters.cjs +220 -211
  151. package/dist/cjs/util.cjs +329 -359
  152. package/dist/cjs/utils/devicePlatform.cjs +14 -8
  153. package/dist/cjs/utils/dns.cjs +68 -93
  154. package/dist/cjs/utils/hashUserAgent.cjs +14 -7
  155. package/dist/cjs/utils/hashUserIp.cjs +14 -6
  156. package/dist/cjs/utils/honeypot/encoders.cjs +74 -80
  157. package/dist/cjs/utils/honeypot/phraseBank.cjs +51 -44
  158. package/dist/cjs/utils/normalizeRequestIp.cjs +33 -25
  159. package/dist/compositeIpAddress.js +38 -46
  160. package/dist/index.js +12 -56
  161. package/dist/pairs.js +31 -37
  162. package/dist/rules/lang.js +11 -15
  163. package/dist/schedulers/captchaScheduler.js +25 -31
  164. package/dist/schedulers/getClientList.js +34 -32
  165. package/dist/schedulers/setClientEntropy.js +34 -32
  166. package/dist/schedulers/updateSpamEmailDomains.js +38 -42
  167. package/dist/services/ipComparison.js +122 -119
  168. package/dist/tasks/captchaManager.js +403 -483
  169. package/dist/tasks/client/clientTasks.js +343 -467
  170. package/dist/tasks/dataset/datasetTasks.js +24 -28
  171. package/dist/tasks/dataset/datasetTasksUtils.js +15 -31
  172. package/dist/tasks/decisionMachine/decisionMachineRunner.js +217 -306
  173. package/dist/tasks/detection/getBotScore.js +37 -43
  174. package/dist/tasks/dnsEvent/enrichDnsEvent.d.ts.map +1 -1
  175. package/dist/tasks/dnsEvent/enrichDnsEvent.js +88 -74
  176. package/dist/tasks/dnsEvent/enrichDnsEvent.js.map +1 -1
  177. package/dist/tasks/frictionless/frictionlessTasks.js +342 -479
  178. package/dist/tasks/frictionless/frictionlessTasksUtils.js +14 -32
  179. package/dist/tasks/frictionless/routingMachine.js +72 -56
  180. package/dist/tasks/imgCaptcha/imgCaptchaTasks.js +550 -801
  181. package/dist/tasks/imgCaptcha/imgCaptchaTasksUtils.js +21 -22
  182. package/dist/tasks/index.js +1 -3
  183. package/dist/tasks/powCaptcha/powTasks.d.ts.map +1 -1
  184. package/dist/tasks/powCaptcha/powTasks.js +486 -682
  185. package/dist/tasks/powCaptcha/powTasks.js.map +1 -1
  186. package/dist/tasks/powCaptcha/powTasksUtils.js +15 -28
  187. package/dist/tasks/puzzleCaptcha/puzzleTasks.d.ts.map +1 -1
  188. package/dist/tasks/puzzleCaptcha/puzzleTasks.js +417 -586
  189. package/dist/tasks/puzzleCaptcha/puzzleTasks.js.map +1 -1
  190. package/dist/tasks/puzzleCaptcha/puzzleTasksUtils.js +9 -6
  191. package/dist/tasks/spam/checkSpamEmail.js +133 -141
  192. package/dist/tasks/spam/checkTrafficFilter.d.ts +1 -0
  193. package/dist/tasks/spam/checkTrafficFilter.d.ts.map +1 -1
  194. package/dist/tasks/spam/checkTrafficFilter.js +117 -77
  195. package/dist/tasks/spam/checkTrafficFilter.js.map +1 -1
  196. package/dist/tasks/spam/evaluateEmailSpamRules.js +75 -86
  197. package/dist/tasks/spam/updateSpamEmailDomains.js +36 -54
  198. package/dist/tasks/tasks.js +98 -150
  199. package/dist/tests/integration/api/blacklistRequestInspector.integration.test.js +4 -1
  200. package/dist/tests/integration/api/blacklistRequestInspector.integration.test.js.map +1 -1
  201. package/dist/tests/unit/api/block.unit.test.js +8 -4
  202. package/dist/tests/unit/api/block.unit.test.js.map +1 -1
  203. package/dist/tests/unit/api/captcha/maintenanceModeShortCircuit.unit.test.js +8 -6
  204. package/dist/tests/unit/api/captcha/maintenanceModeShortCircuit.unit.test.js.map +1 -1
  205. package/dist/tests/unit/api/forwardVerify.unit.test.js +3 -1
  206. package/dist/tests/unit/api/forwardVerify.unit.test.js.map +1 -1
  207. package/dist/tests/unit/api/getFrictionlessCaptchaChallenge.unit.test.js +15 -7
  208. package/dist/tests/unit/api/getFrictionlessCaptchaChallenge.unit.test.js.map +1 -1
  209. package/dist/tests/unit/schedulers/captchaScheduler.unit.test.js +24 -18
  210. package/dist/tests/unit/schedulers/captchaScheduler.unit.test.js.map +1 -1
  211. package/dist/tests/unit/schedulers/getClientList.unit.test.js +19 -13
  212. package/dist/tests/unit/schedulers/getClientList.unit.test.js.map +1 -1
  213. package/dist/tests/unit/schedulers/setClientEntropy.unit.test.js +19 -13
  214. package/dist/tests/unit/schedulers/setClientEntropy.unit.test.js.map +1 -1
  215. package/dist/tests/unit/tasks/client/clientTasks.unit.test.js.map +1 -1
  216. package/dist/tests/unit/tasks/imgCaptcha/imgCaptchaTasksUtils.unit.test.js +22 -16
  217. package/dist/tests/unit/tasks/imgCaptcha/imgCaptchaTasksUtils.unit.test.js.map +1 -1
  218. package/dist/tests/unit/tasks/spam/checkTrafficFilter.unit.test.js +140 -0
  219. package/dist/tests/unit/tasks/spam/checkTrafficFilter.unit.test.js.map +1 -1
  220. package/dist/tests/unit/testUtils/mockProviderEnv.d.ts +7 -7
  221. package/dist/util/usageCounters.js +220 -212
  222. package/dist/util.d.ts.map +1 -1
  223. package/dist/util.js +327 -364
  224. package/dist/util.js.map +1 -1
  225. package/dist/utils/devicePlatform.js +15 -9
  226. package/dist/utils/dns.js +64 -77
  227. package/dist/utils/hashUserAgent.js +14 -7
  228. package/dist/utils/hashUserIp.js +14 -6
  229. package/dist/utils/honeypot/encoders.js +74 -80
  230. package/dist/utils/honeypot/phraseBank.js +49 -44
  231. package/dist/utils/normalizeRequestIp.js +34 -26
  232. package/package.json +24 -24
@@ -1,592 +1,423 @@
1
- "use strict";
2
- Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const util = require("@polkadot/util");
4
- const common = require("@prosopo/common");
5
- const types = require("@prosopo/types");
6
- const util$1 = require("@prosopo/util");
7
- const compositeIpAddress = require("../../compositeIpAddress.cjs");
8
- const util$2 = require("../../util.cjs");
9
- const usageCounters = require("../../util/usageCounters.cjs");
10
- const captchaManager = require("../captchaManager.cjs");
11
- const decisionMachineRunner = require("../decisionMachine/decisionMachineRunner.cjs");
12
- const enrichDnsEvent = require("../dnsEvent/enrichDnsEvent.cjs");
13
- const frictionlessTasksUtils = require("../frictionless/frictionlessTasksUtils.cjs");
14
- const powTasksUtils = require("../powCaptcha/powTasksUtils.cjs");
15
- const puzzleTasksUtils = require("./puzzleTasksUtils.cjs");
16
- class PuzzleCaptchaManager extends captchaManager.CaptchaManager {
17
- constructor(db, pair, config, logger, usageCounters2) {
18
- super(db, pair, config, logger);
19
- this.POW_SEPARATOR = types.POW_SEPARATOR;
20
- this.decisionMachineRunner = new decisionMachineRunner.DecisionMachineRunner(db);
21
- this.usageCounters = usageCounters2 ?? null;
22
- }
23
- /**
24
- * @description Generates a Puzzle Captcha challenge for a given user and dapp
25
- *
26
- * @param {string} userAccount - user that is solving the captcha
27
- * @param {string} dappAccount - dapp that is requesting the captcha
28
- * @param origin - not currently used
29
- * @param tolerance
30
- */
31
- async getPuzzleCaptchaChallenge(userAccount, dappAccount, origin, tolerance) {
32
- const resolvedTolerance = tolerance ?? types.puzzleToleranceDefault;
33
- const requestedAtTimestamp = Date.now();
34
- const nonce = Math.floor(Math.random() * 1e6);
35
- const challenge = `${requestedAtTimestamp}___${userAccount}___${dappAccount}___${nonce}`;
36
- const challengeSignature = util.u8aToHex(this.pair.sign(util.stringToHex(challenge)));
37
- const targetX = Math.floor(Math.random() * (280 - 150 + 1)) + 150;
38
- const targetY = Math.floor(Math.random() * (170 - 30 + 1)) + 30;
39
- const originX = Math.floor(Math.random() * (130 - 20 + 1)) + 20;
40
- const originY = Math.floor(Math.random() * (170 - 30 + 1)) + 30;
41
- return {
42
- challenge,
43
- targetX,
44
- targetY,
45
- originX,
46
- originY,
47
- tolerance: resolvedTolerance,
48
- providerSignature: challengeSignature,
49
- requestedAtTimestamp
50
- };
51
- }
52
- /**
53
- * @description Verifies a Puzzle Captcha solution for a given user and dapp
54
- *
55
- * @param {string} challenge - the challenge string
56
- * @param {string} providerChallengeSignature - proof that the Provider provided the challenge
57
- * @param {number} finalX - the final X coordinate of the puzzle
58
- * @param {number} finalY - the final Y coordinate of the puzzle
59
- * @param {PuzzleEvent[]} puzzleEvents - the puzzle event trail
60
- * @param {number} timeout - the time in milliseconds since the Provider was selected to provide the captcha
61
- * @param {string} userTimestampSignature
62
- * @param ipAddress
63
- * @param headers
64
- * @param behavioralData
65
- */
66
- async verifyPuzzleCaptchaSolution(challenge, providerChallengeSignature, finalX, finalY, puzzleEvents, timeout, userTimestampSignature, ipAddress, headers, behavioralData, salt, simdReadings, clientMetaData) {
67
- powTasksUtils.checkPowSignature(
68
- challenge,
69
- providerChallengeSignature,
70
- this.pair.address,
71
- types.ApiParams.challenge
72
- );
73
- const challengeSplit = challenge.split(this.POW_SEPARATOR);
74
- const timestamp = Number.parseInt(util$1.at(challengeSplit, 0));
75
- const userAccount = util$1.at(challengeSplit, 1);
76
- powTasksUtils.checkPowSignature(
77
- timestamp.toString(),
78
- userTimestampSignature,
79
- userAccount,
80
- types.ApiParams.timestamp
81
- );
82
- const challengeRecord = await this.db.getPuzzleCaptchaRecordByChallenge(challenge);
83
- if (!challengeRecord) {
84
- this.logger.debug(() => ({
85
- msg: `No record of this challenge: ${challenge}`
86
- }));
87
- return false;
88
- }
89
- let coords;
90
- let saltDecodeError;
91
- if (salt) {
92
- try {
93
- const extractedData = util$1.extractData(salt);
94
- if (extractedData.length >= 2) {
95
- const built = [
96
- [[extractedData[0], extractedData[1]]]
97
- ];
98
- util$1.assertCoordsSafe(built, "coords");
99
- coords = built;
100
- }
101
- } catch (error) {
102
- saltDecodeError = error;
103
- this.logger.warn(() => ({
104
- msg: "Failed to extract coordinates from salt",
105
- error,
106
- salt
107
- }));
108
- }
109
- }
110
- if (challengeRecord.userSubmitted) {
111
- this.logger.debug(() => ({
112
- msg: `Challenge already submitted: ${challenge}`
113
- }));
114
- return false;
115
- }
116
- if (saltDecodeError) {
117
- const badSaltResult = {
118
- status: types.CaptchaStatus.disapproved,
119
- reason: types.ResultReason.CAPTCHA_INVALID_SALT
120
- };
121
- await this.db.updatePuzzleCaptchaRecordResult(
122
- challenge,
123
- badSaltResult,
124
- false,
125
- // serverChecked
126
- true,
127
- // userSubmitted
128
- userTimestampSignature,
129
- void 0
130
- // never persist the bad coords
131
- );
132
- if (challengeRecord.sessionId) {
133
- await this.updateSessionRecordWithCache(challengeRecord.sessionId, {
134
- userSubmitted: true,
135
- result: badSaltResult
136
- });
137
- }
138
- return false;
139
- }
140
- if (!util$1.verifyRecency(challenge, timeout)) {
141
- const timeoutResult = {
142
- status: types.CaptchaStatus.disapproved,
143
- reason: types.ResultReason.CAPTCHA_INVALID_TIMESTAMP
144
- };
145
- await this.db.updatePuzzleCaptchaRecordResult(
146
- challenge,
147
- timeoutResult,
148
- false,
149
- //serverchecked
150
- true,
151
- // usersubmitted
152
- userTimestampSignature,
153
- coords
154
- );
155
- if (challengeRecord.sessionId) {
156
- await this.updateSessionRecordWithCache(challengeRecord.sessionId, {
157
- userSubmitted: true,
158
- result: timeoutResult
159
- });
160
- }
161
- return false;
162
- }
163
- const correct = puzzleTasksUtils.validatePuzzleSolution(
164
- finalX,
165
- finalY,
166
- challengeRecord.targetX,
167
- challengeRecord.targetY,
168
- challengeRecord.tolerance
169
- );
170
- let result = { status: types.CaptchaStatus.approved };
171
- if (!correct) {
172
- result = {
173
- status: types.CaptchaStatus.disapproved,
174
- reason: types.ResultReason.CAPTCHA_INVALID_SOLUTION
175
- };
176
- }
177
- if (correct && this.usageCounters) {
178
- const dappAccount = util$1.at(challengeSplit, 2);
179
- this.usageCounters.incrManyAsync(
180
- dappAccount,
181
- usageCounters.buildAllWindowIncrements(
182
- "solved",
183
- types.CaptchaType.puzzle,
184
- ipAddress.address,
185
- userAccount
186
- )
187
- );
188
- }
189
- if (behavioralData) {
190
- try {
191
- const decryptKeys = [
192
- // Process DB keys first, then env var key last as env key will likely be invalid
193
- ...await this.getDetectorKeys(),
194
- process.env.BOT_DECRYPTION_KEY
195
- ];
196
- const decryptedData = await this.decryptBehavioralData(
197
- behavioralData,
198
- decryptKeys
199
- );
200
- if (decryptedData) {
201
- const dappAccount = util$1.at(challengeSplit, 2);
202
- this.logger?.info(() => ({
203
- msg: "Behavioral analysis completed",
204
- data: {
205
- userAccount,
206
- dappAccount,
207
- challenge,
208
- mouseEventsCount: decryptedData.collector1?.length || 0,
209
- touchEventsCount: decryptedData.collector2?.length || 0,
210
- clickEventsCount: decryptedData.collector3?.length || 0,
211
- deviceCapability: decryptedData.deviceCapability,
212
- captchaResult: correct ? "passed" : "failed"
213
- }
214
- }));
215
- const packedData = {
216
- c1: decryptedData.collector1 || [],
217
- c2: decryptedData.collector2 || [],
218
- c3: decryptedData.collector3 || [],
219
- d: decryptedData.deviceCapability
220
- };
221
- await this.db.updatePuzzleCaptchaRecord(challenge, {
222
- behavioralDataPacked: packedData,
223
- deviceCapability: decryptedData.deviceCapability,
224
- puzzleEvents
225
- });
226
- }
227
- } catch (error) {
228
- this.logger?.error(() => ({
229
- msg: "Failed to process behavioral data",
230
- err: error
231
- }));
232
- }
233
- } else {
234
- await this.db.updatePuzzleCaptchaRecord(challenge, {
235
- puzzleEvents
236
- });
237
- }
238
- if (clientMetaData?.hp) {
239
- await this.db.updatePuzzleCaptchaRecord(challenge, {
240
- clientMetaData: { hp: clientMetaData.hp }
241
- });
242
- }
243
- await this.db.updatePuzzleCaptchaRecordResult(
244
- challenge,
245
- result,
246
- false,
247
- true,
248
- userTimestampSignature,
249
- coords
250
- );
251
- if (challengeRecord.sessionId) {
252
- const linkedSessionId = challengeRecord.sessionId;
253
- await this.updateSessionRecordWithCache(linkedSessionId, {
254
- userSubmitted: true,
255
- result
256
- });
257
- if (simdReadings) {
258
- await this.decryptAndAttachSimdReadingsIfAbsent(
259
- linkedSessionId,
260
- simdReadings,
261
- types.SimdReadingsStage.submit
262
- );
263
- }
264
- }
265
- return correct;
266
- }
267
- /**
268
- * @description Verifies a Puzzle Captcha for a given user and dapp. This is called by the server to verify the user's solution
269
- * and update the record in the database to show that the user has solved the captcha
270
- *
271
- * @param {string} dappAccount - the dapp that is requesting the captcha
272
- * @param {string} challenge - the challenge string
273
- * @param {number} timeout - the time in milliseconds since the Provider was selected to provide the captcha
274
- * @param env - provider environment
275
- * @param ip - optional IP address for validation
276
- * @param userAccessRulesStorage - storage for querying user access policies
277
- * @param email
278
- * @param spamEmailDomainCheckingEnabled
279
- * @param trafficFilter
280
- * @param storeMetadata - when true, persists the dapp-server-provided
281
- * `email` on the captcha record for spam-rate analysis.
282
- */
283
- async serverVerifyPuzzleCaptchaSolution(dappAccount, challenge, timeout, env, ip, userAccessRulesStorage, email, spamEmailDomainCheckingEnabled = false, trafficFilter, storeMetadata = false) {
284
- const notVerifiedResponse = { verified: false };
285
- const logger = this.logger.with({ challenge, dappAccount });
286
- const challengeRecord = await this.db.getPuzzleCaptchaRecordByChallenge(challenge);
287
- if (!challengeRecord) {
288
- logger.debug(() => ({
289
- msg: `No record of this challenge: ${challenge}`
290
- }));
291
- return notVerifiedResponse;
292
- }
293
- if (challengeRecord.result.status !== types.CaptchaStatus.approved) {
294
- throw new common.ProsopoApiError("CAPTCHA.INVALID_SOLUTION", {
295
- context: {
296
- failedFuncName: this.serverVerifyPuzzleCaptchaSolution.name,
297
- challenge
298
- }
299
- });
300
- }
301
- if (challengeRecord.serverChecked) return notVerifiedResponse;
302
- const challengeDappAccount = challengeRecord.dappAccount;
303
- if (dappAccount !== challengeDappAccount) {
304
- throw new common.ProsopoEnvError("CAPTCHA.DAPP_USER_SOLUTION_NOT_FOUND", {
305
- context: {
306
- failedFuncName: this.serverVerifyPuzzleCaptchaSolution.name,
307
- dappAccount,
308
- challengeDappAccount
309
- }
310
- });
311
- }
312
- await this.db.updatePuzzleCaptchaRecord(challengeRecord.challenge, {
313
- serverChecked: true,
314
- lastUpdatedTimestamp: /* @__PURE__ */ new Date()
315
- });
316
- const submittedAt = challengeRecord.submittedAtTimestamp;
317
- const submitToVerifyMs = submittedAt instanceof Date ? Date.now() - submittedAt.getTime() : Number.POSITIVE_INFINITY;
318
- if (submitToVerifyMs > timeout) {
319
- const disapprovedResult = {
320
- status: types.CaptchaStatus.disapproved,
321
- reason: types.ResultReason.TIMESTAMP_TOO_OLD
322
- };
323
- await this.db.updatePuzzleCaptchaRecord(challengeRecord.challenge, {
324
- result: disapprovedResult
325
- });
326
- if (challengeRecord.sessionId) {
327
- await this.updateSessionRecordWithCache(challengeRecord.sessionId, {
328
- serverChecked: true,
329
- result: disapprovedResult
330
- });
331
- }
332
- return notVerifiedResponse;
333
- }
334
- if (userAccessRulesStorage) {
335
- try {
336
- const blockPolicy = await this.checkForHardBlock(
337
- userAccessRulesStorage,
338
- challengeRecord,
339
- challengeRecord.userAccount,
340
- challengeRecord.headers,
341
- challengeRecord.coords,
342
- challengeRecord.ipInfo?.isValid ? challengeRecord.ipInfo.countryCode : void 0,
343
- challengeRecord.ipInfo?.isValid ? challengeRecord.ipInfo.asnNumber : void 0
344
- );
345
- if (blockPolicy) {
346
- logger.info(() => ({
347
- msg: "User blocked by access policy in server puzzle verification",
348
- data: {
349
- userAccount: challengeRecord.userAccount,
350
- policy: blockPolicy
351
- }
352
- }));
353
- const blockedResult = {
354
- status: types.CaptchaStatus.disapproved,
355
- reason: types.ResultReason.ACCESS_POLICY_BLOCK
356
- };
357
- await this.db.updatePuzzleCaptchaRecord(challengeRecord.challenge, {
358
- result: blockedResult
359
- });
360
- if (challengeRecord.sessionId) {
361
- await this.updateSessionRecordWithCache(challengeRecord.sessionId, {
362
- serverChecked: true,
363
- result: blockedResult
364
- });
365
- }
366
- return notVerifiedResponse;
367
- }
368
- } catch (error) {
369
- logger.warn(() => ({
370
- msg: "Failed to check user access policies in server puzzle verification",
371
- error
372
- }));
373
- }
374
- }
375
- if (email && spamEmailDomainCheckingEnabled) {
376
- try {
377
- const isSpam = await this.checkSpamEmail(email);
378
- if (isSpam) {
379
- const emailDomain = email.split("@")[1] || "unknown";
380
- logger.info(() => ({
381
- msg: "Spam email domain detected in server puzzle verification",
382
- data: { emailDomain }
383
- }));
384
- await this.db.updatePuzzleCaptchaRecord(challengeRecord.challenge, {
385
- result: {
386
- status: types.CaptchaStatus.disapproved,
387
- reason: types.ResultReason.SPAM_EMAIL_DOMAIN
388
- }
389
- });
390
- return notVerifiedResponse;
391
- }
392
- } catch (error) {
393
- logger.warn(() => ({
394
- msg: "Failed to check spam email domain in server puzzle verification",
395
- error
396
- }));
397
- }
398
- }
399
- const sessionRecord = challengeRecord.sessionId ? await this.db.getSessionRecordBySessionId(challengeRecord.sessionId) : void 0;
400
- const enrichedDnsEvent = await enrichDnsEvent.enrichDnsEvent(
401
- sessionRecord?.dnsEvent,
402
- env.ipInfoService,
403
- ip ?? challengeRecord.ipInfo?.ip
404
- );
405
- {
406
- const check = await this.resolveTrafficFilterCheck(
407
- env,
408
- challengeRecord.ipInfo,
409
- trafficFilter,
410
- ip,
411
- enrichedDnsEvent
412
- );
413
- if (check.isBlocked) {
414
- logger.info(() => ({
415
- msg: "Traffic filter rejected request in puzzle verification",
416
- data: {
417
- ip,
418
- reason: check.reason,
419
- dnsPeerIp: enrichedDnsEvent?.peerIp,
420
- dnsResolverIp: enrichedDnsEvent?.resolverIp,
421
- dnsPeerAsn: enrichDnsEvent.getIpInfoAsn(enrichedDnsEvent?.peerIpInfo),
422
- dnsResolverAsn: enrichDnsEvent.getIpInfoAsn(enrichedDnsEvent?.resolverIpInfo),
423
- dnsPathValid: enrichedDnsEvent?.pathValid
424
- }
425
- }));
426
- const blockedResult = {
427
- status: types.CaptchaStatus.disapproved,
428
- reason: check.reason
429
- };
430
- await this.db.updatePuzzleCaptchaRecord(challengeRecord.challenge, {
431
- result: blockedResult
432
- });
433
- if (challengeRecord.sessionId) {
434
- await this.updateSessionRecordWithCache(challengeRecord.sessionId, {
435
- serverChecked: true,
436
- result: blockedResult
437
- });
438
- }
439
- return notVerifiedResponse;
440
- }
441
- }
442
- if (storeMetadata && email) {
443
- await this.db.updatePuzzleCaptchaRecord(challengeRecord.challenge, {
444
- metadata: { email }
445
- });
446
- }
447
- if (ip) {
448
- const challengeIpAddress = compositeIpAddress.getIpAddressFromComposite(
449
- challengeRecord.ipAddress
450
- );
451
- const clientRecord = await this.db.getClientRecord(dappAccount);
452
- const ipValidationRules = clientRecord?.settings?.ipValidationRules;
453
- await this.db.updatePuzzleCaptchaRecord(challengeRecord.challenge, {
454
- providedIp: compositeIpAddress.getCompositeIpAddress(ip)
455
- });
456
- if (ipValidationRules?.enabled === true) {
457
- const ipValidation = await util$2.deepValidateIpAddress(
458
- ip,
459
- challengeIpAddress,
460
- logger,
461
- env.ipInfoService,
462
- ipValidationRules,
463
- enrichedDnsEvent?.peerIp
464
- );
465
- if (!ipValidation.isValid) {
466
- logger.error(() => ({
467
- msg: "IP validation failed for puzzle captcha",
468
- data: {
469
- ip,
470
- challengeIp: challengeIpAddress.address,
471
- error: ipValidation.errorMessage,
472
- distanceKm: ipValidation.distanceKm
473
- }
474
- }));
475
- const ipFailResult = {
476
- status: types.CaptchaStatus.disapproved,
477
- reason: types.ResultReason.FAILED_IP_VALIDATION
478
- };
479
- await this.db.updatePuzzleCaptchaRecord(challengeRecord.challenge, {
480
- result: ipFailResult
481
- });
482
- if (challengeRecord.sessionId) {
483
- await this.updateSessionRecordWithCache(challengeRecord.sessionId, {
484
- serverChecked: true,
485
- result: ipFailResult
486
- });
487
- }
488
- return notVerifiedResponse;
489
- }
490
- }
491
- }
492
- let score;
493
- if (sessionRecord) {
494
- const dnsAsymmetry = enrichDnsEvent.computeDnsAsymmetry(
495
- enrichedDnsEvent,
496
- challengeRecord.ipInfo,
497
- trafficFilter
498
- );
499
- if (dnsAsymmetry > 0) {
500
- sessionRecord.scoreComponents = {
501
- ...sessionRecord.scoreComponents,
502
- dnsAsymmetry
503
- };
504
- }
505
- score = frictionlessTasksUtils.computeFrictionlessScore(sessionRecord?.scoreComponents);
506
- logger.info(() => ({
507
- data: {
508
- scoreComponents: { ...sessionRecord?.scoreComponents || {} },
509
- score,
510
- dnsPeerAsn: enrichDnsEvent.getIpInfoAsn(enrichedDnsEvent?.peerIpInfo),
511
- dnsResolverAsn: enrichDnsEvent.getIpInfoAsn(enrichedDnsEvent?.resolverIpInfo)
512
- }
513
- }));
514
- }
515
- try {
516
- const decisionInput = {
517
- userAccount: challengeRecord.userAccount,
518
- dappAccount: challengeRecord.dappAccount,
519
- captchaResult: "passed",
520
- headers: challengeRecord.headers,
521
- captchaType: types.CaptchaType.puzzle,
522
- behavioralDataPacked: challengeRecord.behavioralDataPacked,
523
- deviceCapability: challengeRecord.deviceCapability,
524
- countryCode: challengeRecord.ipInfo?.isValid ? challengeRecord.ipInfo.countryCode : void 0,
525
- ipInfo: challengeRecord.ipInfo,
526
- dnsEvent: enrichedDnsEvent,
527
- score,
528
- threshold: sessionRecord?.threshold,
529
- scoreComponents: sessionRecord?.scoreComponents,
530
- decryptedHeadHash: sessionRecord?.decryptedHeadHash,
531
- userSitekeyIpHash: sessionRecord?.userSitekeyIpHash,
532
- simdReadings: sessionRecord?.simdReadings,
533
- frictionlessReason: sessionRecord?.reason,
534
- ruleType: sessionRecord?.ruleType,
535
- webView: sessionRecord?.webView,
536
- iFrame: sessionRecord?.iFrame,
537
- coords: challengeRecord.coords,
538
- puzzleEvents: challengeRecord.puzzleEvents
539
- };
540
- const decision = await this.decisionMachineRunner.decide(
541
- decisionInput,
542
- logger
543
- );
544
- if (decision.decision === types.DecisionMachineDecision.Deny) {
545
- logger.info(() => ({
546
- msg: "Decision machine denied puzzle captcha in server verification",
547
- data: {
548
- userAccount: challengeRecord.userAccount,
549
- reason: decision.reason,
550
- score: decision.score,
551
- tags: decision.tags
552
- }
553
- }));
554
- const dmResult = {
555
- status: types.CaptchaStatus.disapproved,
556
- reason: decision.reason || types.ResultReason.CAPTCHA_DECISION_MACHINE_DENIED
557
- };
558
- await this.db.updatePuzzleCaptchaRecord(challengeRecord.challenge, {
559
- result: dmResult
560
- });
561
- if (challengeRecord.sessionId) {
562
- await this.updateSessionRecordWithCache(challengeRecord.sessionId, {
563
- serverChecked: true,
564
- result: dmResult
565
- });
566
- }
567
- return notVerifiedResponse;
568
- }
569
- logger.debug(() => ({
570
- msg: "Decision machine allowed puzzle captcha",
571
- data: {
572
- reason: decision.reason,
573
- score: decision.score,
574
- tags: decision.tags
575
- }
576
- }));
577
- } catch (error) {
578
- logger.error(() => ({
579
- msg: "Failed to run decision machine in server puzzle verification",
580
- err: error
581
- }));
582
- }
583
- if (challengeRecord.sessionId) {
584
- await this.updateSessionRecordWithCache(challengeRecord.sessionId, {
585
- serverChecked: true,
586
- result: { status: types.CaptchaStatus.approved }
587
- });
588
- }
589
- return { verified: true, ...score ? { score } : {} };
590
- }
591
- }
1
+ const require_usageCounters = require("../../util/usageCounters.cjs");
2
+ const require_decisionMachineRunner = require("../decisionMachine/decisionMachineRunner.cjs");
3
+ const require_compositeIpAddress = require("../../compositeIpAddress.cjs");
4
+ const require_enrichDnsEvent = require("../dnsEvent/enrichDnsEvent.cjs");
5
+ const require_captchaManager = require("../captchaManager.cjs");
6
+ const require_util = require("../../util.cjs");
7
+ const require_frictionlessTasksUtils = require("../frictionless/frictionlessTasksUtils.cjs");
8
+ const require_powTasksUtils = require("../powCaptcha/powTasksUtils.cjs");
9
+ const require_puzzleTasksUtils = require("./puzzleTasksUtils.cjs");
10
+ let _prosopo_common = require("@prosopo/common");
11
+ let _prosopo_types = require("@prosopo/types");
12
+ let _prosopo_util = require("@prosopo/util");
13
+ let _polkadot_util = require("@polkadot/util");
14
+ //#region src/tasks/puzzleCaptcha/puzzleTasks.ts
15
+ var PuzzleCaptchaManager = class extends require_captchaManager.CaptchaManager {
16
+ constructor(db, pair, config, logger, usageCounters) {
17
+ super(db, pair, config, logger);
18
+ this.POW_SEPARATOR = _prosopo_types.POW_SEPARATOR;
19
+ this.decisionMachineRunner = new require_decisionMachineRunner.DecisionMachineRunner(db);
20
+ this.usageCounters = usageCounters ?? null;
21
+ }
22
+ /**
23
+ * @description Generates a Puzzle Captcha challenge for a given user and dapp
24
+ *
25
+ * @param {string} userAccount - user that is solving the captcha
26
+ * @param {string} dappAccount - dapp that is requesting the captcha
27
+ * @param origin - not currently used
28
+ * @param tolerance
29
+ */
30
+ async getPuzzleCaptchaChallenge(userAccount, dappAccount, origin, tolerance) {
31
+ const resolvedTolerance = tolerance ?? _prosopo_types.puzzleToleranceDefault;
32
+ const requestedAtTimestamp = Date.now();
33
+ const challenge = `${requestedAtTimestamp}___${userAccount}___${dappAccount}___${Math.floor(Math.random() * 1e6)}`;
34
+ const challengeSignature = (0, _polkadot_util.u8aToHex)(this.pair.sign((0, _polkadot_util.stringToHex)(challenge)));
35
+ return {
36
+ challenge,
37
+ targetX: Math.floor(Math.random() * 131) + 150,
38
+ targetY: Math.floor(Math.random() * 141) + 30,
39
+ originX: Math.floor(Math.random() * 111) + 20,
40
+ originY: Math.floor(Math.random() * 141) + 30,
41
+ tolerance: resolvedTolerance,
42
+ providerSignature: challengeSignature,
43
+ requestedAtTimestamp
44
+ };
45
+ }
46
+ /**
47
+ * @description Verifies a Puzzle Captcha solution for a given user and dapp
48
+ *
49
+ * @param {string} challenge - the challenge string
50
+ * @param {string} providerChallengeSignature - proof that the Provider provided the challenge
51
+ * @param {number} finalX - the final X coordinate of the puzzle
52
+ * @param {number} finalY - the final Y coordinate of the puzzle
53
+ * @param {PuzzleEvent[]} puzzleEvents - the puzzle event trail
54
+ * @param {number} timeout - the time in milliseconds since the Provider was selected to provide the captcha
55
+ * @param {string} userTimestampSignature
56
+ * @param ipAddress
57
+ * @param headers
58
+ * @param behavioralData
59
+ */
60
+ async verifyPuzzleCaptchaSolution(challenge, providerChallengeSignature, finalX, finalY, puzzleEvents, timeout, userTimestampSignature, ipAddress, headers, behavioralData, salt, simdReadings, clientMetaData) {
61
+ require_powTasksUtils.checkPowSignature(challenge, providerChallengeSignature, this.pair.address, _prosopo_types.ApiParams.challenge);
62
+ const challengeSplit = challenge.split(this.POW_SEPARATOR);
63
+ const timestamp = Number.parseInt((0, _prosopo_util.at)(challengeSplit, 0));
64
+ const userAccount = (0, _prosopo_util.at)(challengeSplit, 1);
65
+ require_powTasksUtils.checkPowSignature(timestamp.toString(), userTimestampSignature, userAccount, _prosopo_types.ApiParams.timestamp);
66
+ const challengeRecord = await this.db.getPuzzleCaptchaRecordByChallenge(challenge);
67
+ if (!challengeRecord) {
68
+ this.logger.debug(() => ({ msg: `No record of this challenge: ${challenge}` }));
69
+ return false;
70
+ }
71
+ let coords;
72
+ let saltDecodeError;
73
+ if (salt) try {
74
+ const extractedData = (0, _prosopo_util.extractData)(salt);
75
+ if (extractedData.length >= 2) {
76
+ const built = [[[extractedData[0], extractedData[1]]]];
77
+ (0, _prosopo_util.assertCoordsSafe)(built, "coords");
78
+ coords = built;
79
+ }
80
+ } catch (error) {
81
+ saltDecodeError = error;
82
+ this.logger.warn(() => ({
83
+ msg: "Failed to extract coordinates from salt",
84
+ error,
85
+ salt
86
+ }));
87
+ }
88
+ if (challengeRecord.userSubmitted) {
89
+ this.logger.debug(() => ({ msg: `Challenge already submitted: ${challenge}` }));
90
+ return false;
91
+ }
92
+ if (saltDecodeError) {
93
+ const badSaltResult = {
94
+ status: _prosopo_types.CaptchaStatus.disapproved,
95
+ reason: _prosopo_types.ResultReason.CAPTCHA_INVALID_SALT
96
+ };
97
+ await this.db.updatePuzzleCaptchaRecordResult(challenge, badSaltResult, false, true, userTimestampSignature, void 0);
98
+ if (challengeRecord.sessionId) await this.updateSessionRecordWithCache(challengeRecord.sessionId, {
99
+ userSubmitted: true,
100
+ result: badSaltResult
101
+ });
102
+ return false;
103
+ }
104
+ if (!(0, _prosopo_util.verifyRecency)(challenge, timeout)) {
105
+ const timeoutResult = {
106
+ status: _prosopo_types.CaptchaStatus.disapproved,
107
+ reason: _prosopo_types.ResultReason.CAPTCHA_INVALID_TIMESTAMP
108
+ };
109
+ await this.db.updatePuzzleCaptchaRecordResult(challenge, timeoutResult, false, true, userTimestampSignature, coords);
110
+ if (challengeRecord.sessionId) await this.updateSessionRecordWithCache(challengeRecord.sessionId, {
111
+ userSubmitted: true,
112
+ result: timeoutResult
113
+ });
114
+ return false;
115
+ }
116
+ const correct = require_puzzleTasksUtils.validatePuzzleSolution(finalX, finalY, challengeRecord.targetX, challengeRecord.targetY, challengeRecord.tolerance);
117
+ let result = { status: _prosopo_types.CaptchaStatus.approved };
118
+ if (!correct) result = {
119
+ status: _prosopo_types.CaptchaStatus.disapproved,
120
+ reason: _prosopo_types.ResultReason.CAPTCHA_INVALID_SOLUTION
121
+ };
122
+ if (correct && this.usageCounters) {
123
+ const dappAccount = (0, _prosopo_util.at)(challengeSplit, 2);
124
+ this.usageCounters.incrManyAsync(dappAccount, require_usageCounters.buildAllWindowIncrements("solved", _prosopo_types.CaptchaType.puzzle, ipAddress.address, userAccount));
125
+ }
126
+ if (behavioralData) try {
127
+ const decryptKeys = [...await this.getDetectorKeys(), process.env.BOT_DECRYPTION_KEY];
128
+ const decryptedData = await this.decryptBehavioralData(behavioralData, decryptKeys);
129
+ if (decryptedData) {
130
+ const dappAccount = (0, _prosopo_util.at)(challengeSplit, 2);
131
+ this.logger?.info(() => ({
132
+ msg: "Behavioral analysis completed",
133
+ data: {
134
+ userAccount,
135
+ dappAccount,
136
+ challenge,
137
+ mouseEventsCount: decryptedData.collector1?.length || 0,
138
+ touchEventsCount: decryptedData.collector2?.length || 0,
139
+ clickEventsCount: decryptedData.collector3?.length || 0,
140
+ deviceCapability: decryptedData.deviceCapability,
141
+ captchaResult: correct ? "passed" : "failed"
142
+ }
143
+ }));
144
+ const packedData = {
145
+ c1: decryptedData.collector1 || [],
146
+ c2: decryptedData.collector2 || [],
147
+ c3: decryptedData.collector3 || [],
148
+ d: decryptedData.deviceCapability
149
+ };
150
+ await this.db.updatePuzzleCaptchaRecord(challenge, {
151
+ behavioralDataPacked: packedData,
152
+ deviceCapability: decryptedData.deviceCapability,
153
+ puzzleEvents
154
+ });
155
+ }
156
+ } catch (error) {
157
+ this.logger?.error(() => ({
158
+ msg: "Failed to process behavioral data",
159
+ err: error
160
+ }));
161
+ }
162
+ else await this.db.updatePuzzleCaptchaRecord(challenge, { puzzleEvents });
163
+ if (clientMetaData?.hp) await this.db.updatePuzzleCaptchaRecord(challenge, { clientMetaData: { hp: clientMetaData.hp } });
164
+ await this.db.updatePuzzleCaptchaRecordResult(challenge, result, false, true, userTimestampSignature, coords);
165
+ if (challengeRecord.sessionId) {
166
+ const linkedSessionId = challengeRecord.sessionId;
167
+ await this.updateSessionRecordWithCache(linkedSessionId, {
168
+ userSubmitted: true,
169
+ result
170
+ });
171
+ if (simdReadings) await this.decryptAndAttachSimdReadingsIfAbsent(linkedSessionId, simdReadings, _prosopo_types.SimdReadingsStage.submit);
172
+ }
173
+ return correct;
174
+ }
175
+ /**
176
+ * @description Verifies a Puzzle Captcha for a given user and dapp. This is called by the server to verify the user's solution
177
+ * and update the record in the database to show that the user has solved the captcha
178
+ *
179
+ * @param {string} dappAccount - the dapp that is requesting the captcha
180
+ * @param {string} challenge - the challenge string
181
+ * @param {number} timeout - the time in milliseconds since the Provider was selected to provide the captcha
182
+ * @param env - provider environment
183
+ * @param ip - optional IP address for validation
184
+ * @param userAccessRulesStorage - storage for querying user access policies
185
+ * @param email
186
+ * @param spamEmailDomainCheckingEnabled
187
+ * @param trafficFilter
188
+ * @param storeMetadata - when true, persists the dapp-server-provided
189
+ * `email` on the captcha record for spam-rate analysis.
190
+ */
191
+ async serverVerifyPuzzleCaptchaSolution(dappAccount, challenge, timeout, env, ip, userAccessRulesStorage, email, spamEmailDomainCheckingEnabled = false, trafficFilter, storeMetadata = false) {
192
+ const notVerifiedResponse = { verified: false };
193
+ const logger = this.logger.with({
194
+ challenge,
195
+ dappAccount
196
+ });
197
+ const challengeRecord = await this.db.getPuzzleCaptchaRecordByChallenge(challenge);
198
+ if (!challengeRecord) {
199
+ logger.debug(() => ({ msg: `No record of this challenge: ${challenge}` }));
200
+ return notVerifiedResponse;
201
+ }
202
+ if (challengeRecord.result.status !== _prosopo_types.CaptchaStatus.approved) throw new _prosopo_common.ProsopoApiError("CAPTCHA.INVALID_SOLUTION", { context: {
203
+ code: 400,
204
+ failedFuncName: this.serverVerifyPuzzleCaptchaSolution.name,
205
+ challenge
206
+ } });
207
+ if (challengeRecord.serverChecked) return notVerifiedResponse;
208
+ const challengeDappAccount = challengeRecord.dappAccount;
209
+ if (dappAccount !== challengeDappAccount) throw new _prosopo_common.ProsopoEnvError("CAPTCHA.DAPP_USER_SOLUTION_NOT_FOUND", { context: {
210
+ failedFuncName: this.serverVerifyPuzzleCaptchaSolution.name,
211
+ dappAccount,
212
+ challengeDappAccount
213
+ } });
214
+ await this.db.updatePuzzleCaptchaRecord(challengeRecord.challenge, {
215
+ serverChecked: true,
216
+ lastUpdatedTimestamp: /* @__PURE__ */ new Date()
217
+ });
218
+ const submittedAt = challengeRecord.submittedAtTimestamp;
219
+ if ((submittedAt instanceof Date ? Date.now() - submittedAt.getTime() : Number.POSITIVE_INFINITY) > timeout) {
220
+ const disapprovedResult = {
221
+ status: _prosopo_types.CaptchaStatus.disapproved,
222
+ reason: _prosopo_types.ResultReason.TIMESTAMP_TOO_OLD
223
+ };
224
+ await this.db.updatePuzzleCaptchaRecord(challengeRecord.challenge, { result: disapprovedResult });
225
+ if (challengeRecord.sessionId) await this.updateSessionRecordWithCache(challengeRecord.sessionId, {
226
+ serverChecked: true,
227
+ result: disapprovedResult
228
+ });
229
+ return notVerifiedResponse;
230
+ }
231
+ if (userAccessRulesStorage) try {
232
+ const blockPolicy = await this.checkForHardBlock(userAccessRulesStorage, challengeRecord, challengeRecord.userAccount, challengeRecord.headers, challengeRecord.coords, challengeRecord.ipInfo?.isValid ? challengeRecord.ipInfo.countryCode : void 0, challengeRecord.ipInfo?.isValid ? challengeRecord.ipInfo.asnNumber : void 0);
233
+ if (blockPolicy) {
234
+ logger.info(() => ({
235
+ msg: "User blocked by access policy in server puzzle verification",
236
+ data: {
237
+ userAccount: challengeRecord.userAccount,
238
+ policy: blockPolicy
239
+ }
240
+ }));
241
+ const blockedResult = {
242
+ status: _prosopo_types.CaptchaStatus.disapproved,
243
+ reason: _prosopo_types.ResultReason.ACCESS_POLICY_BLOCK
244
+ };
245
+ await this.db.updatePuzzleCaptchaRecord(challengeRecord.challenge, { result: blockedResult });
246
+ if (challengeRecord.sessionId) await this.updateSessionRecordWithCache(challengeRecord.sessionId, {
247
+ serverChecked: true,
248
+ result: blockedResult
249
+ });
250
+ return notVerifiedResponse;
251
+ }
252
+ } catch (error) {
253
+ logger.warn(() => ({
254
+ msg: "Failed to check user access policies in server puzzle verification",
255
+ error
256
+ }));
257
+ }
258
+ if (email && spamEmailDomainCheckingEnabled) try {
259
+ if (await this.checkSpamEmail(email)) {
260
+ const emailDomain = email.split("@")[1] || "unknown";
261
+ logger.info(() => ({
262
+ msg: "Spam email domain detected in server puzzle verification",
263
+ data: { emailDomain }
264
+ }));
265
+ await this.db.updatePuzzleCaptchaRecord(challengeRecord.challenge, { result: {
266
+ status: _prosopo_types.CaptchaStatus.disapproved,
267
+ reason: _prosopo_types.ResultReason.SPAM_EMAIL_DOMAIN
268
+ } });
269
+ return notVerifiedResponse;
270
+ }
271
+ } catch (error) {
272
+ logger.warn(() => ({
273
+ msg: "Failed to check spam email domain in server puzzle verification",
274
+ error
275
+ }));
276
+ }
277
+ const sessionRecord = challengeRecord.sessionId ? await this.db.getSessionRecordBySessionId(challengeRecord.sessionId) : void 0;
278
+ const enrichedDnsEvent = await require_enrichDnsEvent.enrichDnsEvent(sessionRecord?.dnsEvent, env.ipInfoService, ip ?? challengeRecord.ipInfo?.ip);
279
+ {
280
+ const check = await this.resolveTrafficFilterCheck(env, challengeRecord.ipInfo, trafficFilter, ip, enrichedDnsEvent);
281
+ if (check.isBlocked) {
282
+ logger.info(() => ({
283
+ msg: "Traffic filter rejected request in puzzle verification",
284
+ data: {
285
+ ip,
286
+ reason: check.reason,
287
+ dnsPeerIp: enrichedDnsEvent?.peerIp,
288
+ dnsResolverIp: enrichedDnsEvent?.resolverIp,
289
+ dnsPeerAsn: require_enrichDnsEvent.getIpInfoAsn(enrichedDnsEvent?.peerIpInfo),
290
+ dnsResolverAsn: require_enrichDnsEvent.getIpInfoAsn(enrichedDnsEvent?.resolverIpInfo),
291
+ dnsPathValid: enrichedDnsEvent?.pathValid
292
+ }
293
+ }));
294
+ const blockedResult = {
295
+ status: _prosopo_types.CaptchaStatus.disapproved,
296
+ reason: check.reason
297
+ };
298
+ await this.db.updatePuzzleCaptchaRecord(challengeRecord.challenge, { result: blockedResult });
299
+ if (challengeRecord.sessionId) await this.updateSessionRecordWithCache(challengeRecord.sessionId, {
300
+ serverChecked: true,
301
+ result: blockedResult
302
+ });
303
+ return notVerifiedResponse;
304
+ }
305
+ }
306
+ if (storeMetadata && email) await this.db.updatePuzzleCaptchaRecord(challengeRecord.challenge, { metadata: { email } });
307
+ if (ip) {
308
+ const challengeIpAddress = require_compositeIpAddress.getIpAddressFromComposite(challengeRecord.ipAddress);
309
+ const ipValidationRules = (await this.db.getClientRecord(dappAccount))?.settings?.ipValidationRules;
310
+ await this.db.updatePuzzleCaptchaRecord(challengeRecord.challenge, { providedIp: require_compositeIpAddress.getCompositeIpAddress(ip) });
311
+ if (ipValidationRules?.enabled === true) {
312
+ const ipValidation = await require_util.deepValidateIpAddress(ip, challengeIpAddress, logger, env.ipInfoService, ipValidationRules, enrichedDnsEvent?.peerIp);
313
+ if (!ipValidation.isValid) {
314
+ logger.error(() => ({
315
+ msg: "IP validation failed for puzzle captcha",
316
+ data: {
317
+ ip,
318
+ challengeIp: challengeIpAddress.address,
319
+ error: ipValidation.errorMessage,
320
+ distanceKm: ipValidation.distanceKm
321
+ }
322
+ }));
323
+ const ipFailResult = {
324
+ status: _prosopo_types.CaptchaStatus.disapproved,
325
+ reason: _prosopo_types.ResultReason.FAILED_IP_VALIDATION
326
+ };
327
+ await this.db.updatePuzzleCaptchaRecord(challengeRecord.challenge, { result: ipFailResult });
328
+ if (challengeRecord.sessionId) await this.updateSessionRecordWithCache(challengeRecord.sessionId, {
329
+ serverChecked: true,
330
+ result: ipFailResult
331
+ });
332
+ return notVerifiedResponse;
333
+ }
334
+ }
335
+ }
336
+ let score;
337
+ if (sessionRecord) {
338
+ const dnsAsymmetry = require_enrichDnsEvent.computeDnsAsymmetry(enrichedDnsEvent, challengeRecord.ipInfo, trafficFilter);
339
+ if (dnsAsymmetry > 0) sessionRecord.scoreComponents = {
340
+ ...sessionRecord.scoreComponents,
341
+ dnsAsymmetry
342
+ };
343
+ score = require_frictionlessTasksUtils.computeFrictionlessScore(sessionRecord?.scoreComponents);
344
+ logger.info(() => ({ data: {
345
+ scoreComponents: { ...sessionRecord?.scoreComponents || {} },
346
+ score,
347
+ dnsPeerAsn: require_enrichDnsEvent.getIpInfoAsn(enrichedDnsEvent?.peerIpInfo),
348
+ dnsResolverAsn: require_enrichDnsEvent.getIpInfoAsn(enrichedDnsEvent?.resolverIpInfo)
349
+ } }));
350
+ }
351
+ try {
352
+ const decisionInput = {
353
+ userAccount: challengeRecord.userAccount,
354
+ dappAccount: challengeRecord.dappAccount,
355
+ captchaResult: "passed",
356
+ headers: challengeRecord.headers,
357
+ captchaType: _prosopo_types.CaptchaType.puzzle,
358
+ behavioralDataPacked: challengeRecord.behavioralDataPacked,
359
+ deviceCapability: challengeRecord.deviceCapability,
360
+ countryCode: challengeRecord.ipInfo?.isValid ? challengeRecord.ipInfo.countryCode : void 0,
361
+ ipInfo: challengeRecord.ipInfo,
362
+ dnsEvent: enrichedDnsEvent,
363
+ score,
364
+ threshold: sessionRecord?.threshold,
365
+ scoreComponents: sessionRecord?.scoreComponents,
366
+ decryptedHeadHash: sessionRecord?.decryptedHeadHash,
367
+ userSitekeyIpHash: sessionRecord?.userSitekeyIpHash,
368
+ simdReadings: sessionRecord?.simdReadings,
369
+ frictionlessReason: sessionRecord?.reason,
370
+ ruleType: sessionRecord?.ruleType,
371
+ webView: sessionRecord?.webView,
372
+ iFrame: sessionRecord?.iFrame,
373
+ coords: challengeRecord.coords,
374
+ puzzleEvents: challengeRecord.puzzleEvents
375
+ };
376
+ const decision = await this.decisionMachineRunner.decide(decisionInput, logger);
377
+ if (decision.decision === _prosopo_types.DecisionMachineDecision.Deny) {
378
+ logger.info(() => ({
379
+ msg: "Decision machine denied puzzle captcha in server verification",
380
+ data: {
381
+ userAccount: challengeRecord.userAccount,
382
+ reason: decision.reason,
383
+ score: decision.score,
384
+ tags: decision.tags
385
+ }
386
+ }));
387
+ const dmResult = {
388
+ status: _prosopo_types.CaptchaStatus.disapproved,
389
+ reason: decision.reason || _prosopo_types.ResultReason.CAPTCHA_DECISION_MACHINE_DENIED
390
+ };
391
+ await this.db.updatePuzzleCaptchaRecord(challengeRecord.challenge, { result: dmResult });
392
+ if (challengeRecord.sessionId) await this.updateSessionRecordWithCache(challengeRecord.sessionId, {
393
+ serverChecked: true,
394
+ result: dmResult
395
+ });
396
+ return notVerifiedResponse;
397
+ }
398
+ logger.debug(() => ({
399
+ msg: "Decision machine allowed puzzle captcha",
400
+ data: {
401
+ reason: decision.reason,
402
+ score: decision.score,
403
+ tags: decision.tags
404
+ }
405
+ }));
406
+ } catch (error) {
407
+ logger.error(() => ({
408
+ msg: "Failed to run decision machine in server puzzle verification",
409
+ err: error
410
+ }));
411
+ }
412
+ if (challengeRecord.sessionId) await this.updateSessionRecordWithCache(challengeRecord.sessionId, {
413
+ serverChecked: true,
414
+ result: { status: _prosopo_types.CaptchaStatus.approved }
415
+ });
416
+ return {
417
+ verified: true,
418
+ ...score ? { score } : {}
419
+ };
420
+ }
421
+ };
422
+ //#endregion
592
423
  exports.PuzzleCaptchaManager = PuzzleCaptchaManager;