@prosopo/provider 4.15.14 → 4.15.16

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 +63 -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,807 +1,556 @@
1
- "use strict";
2
- Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const util$1 = require("@polkadot/util");
4
- const common = require("@prosopo/common");
5
- const datasets = require("@prosopo/datasets");
6
- const types = require("@prosopo/types");
7
- const util$2 = require("@prosopo/util");
8
- const utilCrypto = require("@prosopo/util-crypto");
9
- const compositeIpAddress = require("../../compositeIpAddress.cjs");
10
- const pairs = require("../../pairs.cjs");
11
- const lang = require("../../rules/lang.cjs");
12
- const util = require("../../util.cjs");
13
- const usageCounters = require("../../util/usageCounters.cjs");
14
- const captchaManager = require("../captchaManager.cjs");
15
- const decisionMachineRunner = require("../decisionMachine/decisionMachineRunner.cjs");
16
- const enrichDnsEvent = require("../dnsEvent/enrichDnsEvent.cjs");
17
- require("../frictionless/frictionlessTasks.cjs");
18
- const frictionlessTasksUtils = require("../frictionless/frictionlessTasksUtils.cjs");
19
- const evaluateEmailSpamRules = require("../spam/evaluateEmailSpamRules.cjs");
20
- const imgCaptchaTasksUtils = require("./imgCaptchaTasksUtils.cjs");
21
- class ImgCaptchaManager extends captchaManager.CaptchaManager {
22
- constructor(db, pair, config, logger, writeQueue, usageCounters2) {
23
- super(db, pair, config, logger, writeQueue);
24
- this.config = config;
25
- this.decisionMachineRunner = new decisionMachineRunner.DecisionMachineRunner(db);
26
- this.usageCounters = usageCounters2 ?? null;
27
- }
28
- async getCaptchaWithProof(datasetId, solved, size) {
29
- const captchaDocs = await this.db.getRandomCaptcha(solved, datasetId, size);
30
- if (!captchaDocs) {
31
- throw new common.ProsopoEnvError("DATABASE.CAPTCHA_GET_FAILED", {
32
- context: {
33
- failedFuncName: this.getCaptchaWithProof.name,
34
- datasetId,
35
- solved,
36
- size
37
- }
38
- });
39
- }
40
- return captchaDocs;
41
- }
42
- async getRandomCaptchasAndRequestHash(datasetId, userAccount, ipAddress, captchaConfig, threshold, sessionId, ipInfo) {
43
- const dataset = await this.db.getDatasetDetails(datasetId);
44
- if (!dataset) {
45
- throw new common.ProsopoEnvError("DATABASE.DATASET_GET_FAILED", {
46
- context: {
47
- failedFuncName: this.getRandomCaptchasAndRequestHash.name,
48
- dataset,
49
- datasetId
50
- }
51
- });
52
- }
53
- const unsolvedCount = Math.abs(
54
- Math.trunc(captchaConfig.unsolved.count)
55
- );
56
- const solvedCount = Math.abs(
57
- Math.trunc(captchaConfig.solved.count)
58
- );
59
- if (!solvedCount) {
60
- throw new common.ProsopoEnvError("CONFIG.INVALID_CAPTCHA_NUMBER");
61
- }
62
- const solved = await this.getCaptchaWithProof(datasetId, true, solvedCount);
63
- let unsolved = [];
64
- if (unsolvedCount) {
65
- unsolved = await this.getCaptchaWithProof(
66
- datasetId,
67
- false,
68
- unsolvedCount
69
- );
70
- }
71
- const captchas = util.shuffleArray([...solved, ...unsolved]);
72
- const salt = utilCrypto.randomAsHex();
73
- const requestHash = datasets.computePendingRequestHash(
74
- captchas.map((c) => c.captchaId),
75
- userAccount,
76
- salt
77
- );
78
- const currentTime = Date.now();
79
- const signedRequestHash = util$1.u8aToHex(
80
- this.pair.sign(util$1.stringToHex(requestHash))
81
- );
82
- const timeLimit = captchas.map((captcha) => captcha.timeLimitMs || types.DEFAULT_IMAGE_CAPTCHA_TIMEOUT).reduce((a, b) => a + b, 0);
83
- const deadlineDate = new Date(timeLimit + currentTime);
84
- await this.db.storePendingImageCommitment(
85
- userAccount,
86
- requestHash,
87
- salt,
88
- deadlineDate,
89
- new Date(currentTime),
90
- compositeIpAddress.getCompositeIpAddress(ipAddress),
91
- threshold,
92
- sessionId,
93
- ipInfo
94
- );
95
- return {
96
- captchas,
97
- requestHash,
98
- timestamp: currentTime,
99
- signedRequestHash
100
- };
101
- }
102
- /**
103
- * Validate and store the text captcha solution(s) from the Dapp User in a web2 environment
104
- * @param {string} userAccount
105
- * @param {string} dappAccount
106
- * @param {string} requestHash
107
- * @param {JSON} captchas
108
- * @param userTimestampSignature
109
- * @param timestamp
110
- * @param providerRequestHashSignature
111
- * @param ipAddress
112
- * @param headers
113
- * @param ja4
114
- * @param behavioralData
115
- * @return {Promise<DappUserSolutionResult>} result containing the contract event
116
- */
117
- async dappUserSolution(userAccount, dappAccount, requestHash, captchas, userTimestampSignature, timestamp, providerRequestHashSignature, ipAddress, headers, ja4, behavioralData, ipInfo, simdReadings, clientMetaData) {
118
- const decodedSimdReadings = simdReadings ? await this.decryptSimdReadingsForAttach(simdReadings) : void 0;
119
- const pushSimdAttachIfAny = (sessionId, writes) => {
120
- if (decodedSimdReadings) {
121
- writes.push(
122
- this.recordSessionSimdReadingsIfAbsentWithCache(
123
- sessionId,
124
- decodedSimdReadings,
125
- types.SimdReadingsStage.submit
126
- )
127
- );
128
- }
129
- };
130
- const verification = utilCrypto.signatureVerify(
131
- util$1.stringToHex(timestamp.toString()),
132
- userTimestampSignature,
133
- userAccount
134
- );
135
- if (!verification.isValid) {
136
- const err = new common.ProsopoEnvError("GENERAL.INVALID_SIGNATURE", {
137
- context: { failedFuncName: this.dappUserSolution.name, userAccount }
138
- });
139
- this.logger.info(() => ({
140
- err,
141
- msg: "Invalid user timestamp signature"
142
- }));
143
- throw err;
144
- }
145
- const providerRequestHashSignatureVerify = utilCrypto.signatureVerify(
146
- util$1.stringToHex(requestHash.toString()),
147
- providerRequestHashSignature,
148
- this.pair.address
149
- );
150
- if (!providerRequestHashSignatureVerify.isValid) {
151
- const err = new common.ProsopoEnvError("GENERAL.INVALID_SIGNATURE", {
152
- context: {
153
- failedFuncName: this.dappUserSolution.name,
154
- userAccount,
155
- error: "requestHash signature is invalid"
156
- }
157
- });
158
- this.logger.info(() => ({
159
- err,
160
- msg: "Invalid provider requestHash signature"
161
- }));
162
- throw err;
163
- }
164
- let response = {
165
- captchas: [],
166
- verified: false
167
- };
168
- const pendingRecord = await this.db.getPendingImageCommitment(requestHash);
169
- const unverifiedCaptchaIds = captchas.map((captcha) => captcha.captchaId);
170
- const pendingRequest = await this.validateDappUserSolutionRequestIsPending(
171
- requestHash,
172
- pendingRecord,
173
- userAccount,
174
- unverifiedCaptchaIds
175
- );
176
- if (pendingRequest) {
177
- const { storedCaptchas, receivedCaptchas, captchaIds } = await this.validateReceivedCaptchasAgainstStoredCaptchas(captchas);
178
- const rawFlat = receivedCaptchas.map((c) => util$2.extractData(c.salt));
179
- const { checkbox: checkboxCoordPair, flat } = pairs.peelCheckboxPrefix(
180
- rawFlat,
181
- receivedCaptchas.map((c) => c.solution.length)
182
- );
183
- const shapePairs = flat.map((list) => pairs.constructPairList(list));
184
- const pairs$1 = checkboxCoordPair ? [[checkboxCoordPair], ...shapePairs] : shapePairs;
185
- const { tree, commitmentId } = imgCaptchaTasksUtils.buildTreeAndGetCommitmentId(receivedCaptchas);
186
- const datasetId = util$2.at(storedCaptchas, 0).datasetId;
187
- if (!datasetId) {
188
- throw new common.ProsopoEnvError("CAPTCHA.ID_MISMATCH", {
189
- context: { failedFuncName: this.dappUserSolution.name }
190
- });
191
- }
192
- await this.db.updatePendingImageCommitmentStatus(requestHash);
193
- let behavioralDataPacked;
194
- let deviceCapability;
195
- if (behavioralData) {
196
- try {
197
- const decryptKeys = [
198
- // Process DB keys first, then env var key last as env key will likely be invalid
199
- ...await this.getDetectorKeys(),
200
- process.env.BOT_DECRYPTION_KEY
201
- ];
202
- const decryptedData = await this.decryptBehavioralData(
203
- behavioralData,
204
- decryptKeys
205
- );
206
- if (decryptedData) {
207
- this.logger?.info(() => ({
208
- msg: "Behavioral analysis completed",
209
- data: {
210
- userAccount,
211
- dappAccount,
212
- requestHash,
213
- mouseEventsCount: decryptedData.collector1?.length || 0,
214
- touchEventsCount: decryptedData.collector2?.length || 0,
215
- clickEventsCount: decryptedData.collector3?.length || 0,
216
- deviceCapability: decryptedData.deviceCapability
217
- }
218
- }));
219
- behavioralDataPacked = {
220
- c1: decryptedData.collector1 || [],
221
- c2: decryptedData.collector2 || [],
222
- c3: decryptedData.collector3 || [],
223
- d: decryptedData.deviceCapability
224
- };
225
- deviceCapability = decryptedData.deviceCapability;
226
- }
227
- } catch (error) {
228
- this.logger?.error(() => ({
229
- msg: "Failed to process behavioral data",
230
- err: error
231
- }));
232
- }
233
- }
234
- const commit = {
235
- id: commitmentId,
236
- userAccount,
237
- dappAccount,
238
- providerAccount: this.pair.address,
239
- datasetId,
240
- result: { status: types.CaptchaStatus.pending },
241
- userSignature: userTimestampSignature,
242
- userSubmitted: true,
243
- serverChecked: false,
244
- requestedAtTimestamp: new Date(timestamp),
245
- submittedAtTimestamp: /* @__PURE__ */ new Date(),
246
- ipAddress: compositeIpAddress.getCompositeIpAddress(ipAddress),
247
- headers,
248
- sessionId: pendingRecord.sessionId,
249
- ja4,
250
- ipInfo,
251
- pending: false,
252
- salt: pendingRecord.salt,
253
- requestHash: pendingRecord[types.ApiParams.requestHash],
254
- threshold: pendingRecord.threshold,
255
- deadlineTimestamp: pendingRecord.deadlineTimestamp,
256
- ...behavioralDataPacked && { behavioralDataPacked },
257
- ...deviceCapability && { deviceCapability },
258
- ...clientMetaData?.hp && {
259
- clientMetaData: { hp: clientMetaData.hp }
260
- }
261
- };
262
- await this.db.storeUserImageCaptchaSolution(receivedCaptchas, commit);
263
- const solutionRecords = await Promise.all(
264
- storedCaptchas.map(async (captcha) => {
265
- const solutionRecord = await this.db.getSolutionByCaptchaId(
266
- captcha.captchaId
267
- );
268
- if (!solutionRecord) {
269
- throw new common.ProsopoEnvError("CAPTCHA.SOLUTION_NOT_FOUND", {
270
- context: { failedFuncName: this.dappUserSolution.name }
271
- });
272
- }
273
- return solutionRecord;
274
- })
275
- );
276
- const totalImages = storedCaptchas[0]?.items.length || 0;
277
- if (pairs.containsIdenticalPairs(pairs$1) && process.env.NODE_ENV !== "test") {
278
- const writePromises = [
279
- this.db.disapproveDappUserCommitment(
280
- commitmentId,
281
- "CAPTCHA.INVALID_SOLUTION",
282
- pairs$1
283
- )
284
- ];
285
- if (pendingRecord.sessionId) {
286
- writePromises.push(
287
- this.updateSessionRecordWithCache(pendingRecord.sessionId, {
288
- userSubmitted: true,
289
- result: {
290
- status: types.CaptchaStatus.disapproved,
291
- reason: types.ResultReason.CAPTCHA_INVALID_SOLUTION
292
- }
293
- })
294
- );
295
- pushSimdAttachIfAny(pendingRecord.sessionId, writePromises);
296
- }
297
- await Promise.all(writePromises);
298
- response = {
299
- captchas: captchaIds.map((id) => ({
300
- captchaId: id,
301
- proof: [[]]
302
- })),
303
- verified: false
304
- };
305
- return response;
306
- }
307
- if (datasets.compareCaptchaSolutions(
308
- receivedCaptchas,
309
- solutionRecords,
310
- totalImages,
311
- pendingRecord.threshold
312
- )) {
313
- response = {
314
- captchas: captchaIds.map((id) => ({
315
- captchaId: id,
316
- proof: tree.proof(id)
317
- })),
318
- verified: true
319
- };
320
- const writePromises = [
321
- this.db.approveDappUserCommitment(commitmentId, pairs$1)
322
- ];
323
- if (pendingRecord.sessionId) {
324
- writePromises.push(
325
- this.updateSessionRecordWithCache(pendingRecord.sessionId, {
326
- userSubmitted: true,
327
- result: { status: types.CaptchaStatus.approved }
328
- })
329
- );
330
- pushSimdAttachIfAny(pendingRecord.sessionId, writePromises);
331
- }
332
- await Promise.all(writePromises);
333
- } else {
334
- const writePromises = [
335
- this.db.disapproveDappUserCommitment(
336
- commitmentId,
337
- "CAPTCHA.INVALID_SOLUTION",
338
- pairs$1
339
- )
340
- ];
341
- if (pendingRecord.sessionId) {
342
- writePromises.push(
343
- this.updateSessionRecordWithCache(pendingRecord.sessionId, {
344
- userSubmitted: true,
345
- result: {
346
- status: types.CaptchaStatus.disapproved,
347
- reason: types.ResultReason.CAPTCHA_INVALID_SOLUTION
348
- }
349
- })
350
- );
351
- pushSimdAttachIfAny(pendingRecord.sessionId, writePromises);
352
- }
353
- await Promise.all(writePromises);
354
- response = {
355
- captchas: captchaIds.map((id) => ({
356
- captchaId: id,
357
- proof: [[]]
358
- })),
359
- verified: false
360
- };
361
- }
362
- } else {
363
- this.logger.info(() => ({
364
- msg: "Request hash not found"
365
- }));
366
- }
367
- return response;
368
- }
369
- /**
370
- * Validate length of received captchas array matches length of captchas found in database
371
- * Validate that the datasetId is the same for all captchas and is equal to the datasetId on the stored captchas
372
- */
373
- async validateReceivedCaptchasAgainstStoredCaptchas(captchas) {
374
- const receivedCaptchas = datasets.parseAndSortCaptchaSolutions(captchas);
375
- const captchaIds = receivedCaptchas.map((captcha) => captcha.captchaId);
376
- const storedCaptchas = await this.db.getCaptchaById(captchaIds);
377
- if (!storedCaptchas || receivedCaptchas.length !== storedCaptchas.length) {
378
- throw new common.ProsopoEnvError("CAPTCHA.INVALID_CAPTCHA_ID", {
379
- context: {
380
- failedFuncName: this.validateReceivedCaptchasAgainstStoredCaptchas.name,
381
- captchas
382
- }
383
- });
384
- }
385
- if (!storedCaptchas.every(
386
- (captcha) => captcha.datasetId === util$2.at(storedCaptchas, 0).datasetId
387
- )) {
388
- throw new common.ProsopoEnvError("CAPTCHA.DIFFERENT_DATASET_IDS", {
389
- context: {
390
- failedFuncName: this.validateReceivedCaptchasAgainstStoredCaptchas.name,
391
- captchas
392
- }
393
- });
394
- }
395
- return { storedCaptchas, receivedCaptchas, captchaIds };
396
- }
397
- /**
398
- * Validate that a Dapp User is responding to their own pending captcha request
399
- * @param {string} requestHash
400
- * @param {PendingImageCaptchaRequest} pendingRecord
401
- * @param {string} userAccount
402
- * @param {string[]} captchaIds
403
- */
404
- async validateDappUserSolutionRequestIsPending(requestHash, pendingRecord, userAccount, captchaIds) {
405
- const currentTime = /* @__PURE__ */ new Date();
406
- if (!pendingRecord) {
407
- this.logger.info(() => ({
408
- msg: "No pending record found"
409
- }));
410
- return false;
411
- }
412
- if (pendingRecord.deadlineTimestamp < currentTime) {
413
- this.logger.info(() => ({
414
- msg: "Deadline for responding to captcha has expired"
415
- }));
416
- return false;
417
- }
418
- if (pendingRecord) {
419
- const pendingHashComputed = datasets.computePendingRequestHash(
420
- captchaIds,
421
- userAccount,
422
- pendingRecord.salt
423
- );
424
- return requestHash === pendingHashComputed;
425
- }
426
- return false;
427
- }
428
- /*
429
- * Get dapp user solution from database
430
- */
431
- async getDappUserCommitmentById(commitmentId) {
432
- const dappUserSolution = await this.db.getDappUserCommitmentById(commitmentId);
433
- if (!dappUserSolution) {
434
- throw new common.ProsopoEnvError("CAPTCHA.DAPP_USER_SOLUTION_NOT_FOUND", {
435
- context: {
436
- failedFuncName: this.getDappUserCommitmentById.name,
437
- commitmentId
438
- }
439
- });
440
- }
441
- return dappUserSolution;
442
- }
443
- /* Check if dapp user has verified solution in cache */
444
- async getDappUserCommitmentByAccount(userAccount, dappAccount) {
445
- const dappUserSolutions = await this.db.getDappUserCommitmentByAccount(
446
- userAccount,
447
- dappAccount
448
- );
449
- if (dappUserSolutions.length > 0) {
450
- for (const dappUserSolution of dappUserSolutions) {
451
- if (dappUserSolution.result.status === types.CaptchaStatus.approved) {
452
- return dappUserSolution;
453
- }
454
- }
455
- }
456
- return void 0;
457
- }
458
- async verifyImageCaptchaSolution(user, dapp, commitmentId, env, maxVerifiedTime, ip, disallowWebView, contextAwareEnabled = false, userAccessRulesStorage, email, spamEmailDomainCheckingEnabled = false, spamFilter, trafficFilter, storeMetadata = false) {
459
- const logger = this.logger.with({ commitmentId, dapp });
460
- const solution = await (commitmentId ? this.getDappUserCommitmentById(commitmentId) : this.getDappUserCommitmentByAccount(user, dapp));
461
- if (!solution) {
462
- logger.debug(() => ({
463
- msg: "Not verified - no solution found"
464
- }));
465
- return {
466
- status: "API.USER_NOT_VERIFIED_NO_SOLUTION",
467
- verified: false
468
- };
469
- }
470
- if (solution.serverChecked) {
471
- return {
472
- status: "API.USER_ALREADY_VERIFIED",
473
- verified: false
474
- };
475
- }
476
- await this.db.markDappUserCommitmentsChecked([solution.id]);
477
- if (solution.result.status === types.CaptchaStatus.disapproved) {
478
- return {
479
- status: solution.result.reason || "API.USER_NOT_VERIFIED",
480
- verified: false
481
- };
482
- }
483
- maxVerifiedTime = maxVerifiedTime || 60 * 1e3;
484
- const currentTime = Date.now();
485
- const submittedAt = solution.submittedAtTimestamp;
486
- const timeSinceCompletion = submittedAt instanceof Date ? currentTime - submittedAt.getTime() : Number.POSITIVE_INFINITY;
487
- if (timeSinceCompletion > maxVerifiedTime) {
488
- logger.debug(() => ({
489
- msg: "Not verified - timed out"
490
- }));
491
- return {
492
- status: "API.USER_NOT_VERIFIED_TIME_EXPIRED",
493
- verified: false
494
- };
495
- }
496
- const commitmentUpdates = {};
497
- let failStatus;
498
- if (userAccessRulesStorage) {
499
- try {
500
- const blockPolicy = await this.checkForHardBlock(
501
- userAccessRulesStorage,
502
- solution,
503
- solution.userAccount,
504
- solution.headers,
505
- solution.coords,
506
- solution.ipInfo?.isValid ? solution.ipInfo.countryCode : void 0,
507
- solution.ipInfo?.isValid ? solution.ipInfo.asnNumber : void 0
508
- );
509
- if (blockPolicy) {
510
- logger.info(() => ({
511
- msg: "User blocked by access policy in server image verification",
512
- data: {
513
- userAccount: solution.userAccount,
514
- dappAccount: solution.dappAccount,
515
- policy: blockPolicy
516
- }
517
- }));
518
- commitmentUpdates.result = {
519
- status: types.CaptchaStatus.disapproved,
520
- reason: types.ResultReason.ACCESS_POLICY_BLOCK
521
- };
522
- failStatus = types.ResultReason.ACCESS_POLICY_BLOCK;
523
- }
524
- } catch (error) {
525
- logger.warn(() => ({
526
- msg: "Failed to check user access policies in server image verification",
527
- error
528
- }));
529
- }
530
- }
531
- if (!failStatus && email && spamEmailDomainCheckingEnabled) {
532
- try {
533
- const isSpam = await this.checkSpamEmail(email);
534
- if (isSpam) {
535
- const emailDomain = email.split("@")[1] || "unknown";
536
- logger.info(() => ({
537
- msg: "Spam email domain detected in server image verification",
538
- data: { emailDomain }
539
- }));
540
- commitmentUpdates.result = {
541
- status: types.CaptchaStatus.disapproved,
542
- reason: types.ResultReason.SPAM_EMAIL_DOMAIN
543
- };
544
- failStatus = types.ResultReason.SPAM_EMAIL_DOMAIN;
545
- }
546
- } catch (error) {
547
- logger.warn(() => ({
548
- msg: "Failed to check spam email domain in server image verification",
549
- error
550
- }));
551
- }
552
- }
553
- if (!failStatus && spamFilter?.enabled && spamFilter.emailRules?.enabled && email) {
554
- const result = evaluateEmailSpamRules.evaluateEmailSpamRules(email, spamFilter.emailRules);
555
- if (result.isSpam) {
556
- logger.info(() => ({
557
- msg: "Spam filter rejected email in image verification",
558
- data: { reason: result.reason }
559
- }));
560
- commitmentUpdates.result = {
561
- status: types.CaptchaStatus.disapproved,
562
- reason: types.ResultReason.SPAM_EMAIL_RULE
563
- };
564
- failStatus = types.ResultReason.SPAM_EMAIL_RULE;
565
- }
566
- }
567
- const sessionRecord = solution.sessionId ? await this.db.getSessionRecordBySessionId(solution.sessionId) : void 0;
568
- const enrichedDnsEvent = await enrichDnsEvent.enrichDnsEvent(
569
- sessionRecord?.dnsEvent,
570
- env.ipInfoService,
571
- ip ?? solution.ipInfo?.ip
572
- );
573
- if (!failStatus) {
574
- const check = await this.resolveTrafficFilterCheck(
575
- env,
576
- solution.ipInfo,
577
- trafficFilter,
578
- ip,
579
- enrichedDnsEvent
580
- );
581
- if (check.isBlocked) {
582
- logger.info(() => ({
583
- msg: "Traffic filter rejected request",
584
- data: {
585
- ip,
586
- reason: check.reason,
587
- dnsPeerIp: enrichedDnsEvent?.peerIp,
588
- dnsResolverIp: enrichedDnsEvent?.resolverIp,
589
- dnsPeerAsn: enrichDnsEvent.getIpInfoAsn(enrichedDnsEvent?.peerIpInfo),
590
- dnsResolverAsn: enrichDnsEvent.getIpInfoAsn(enrichedDnsEvent?.resolverIpInfo),
591
- dnsPathValid: enrichedDnsEvent?.pathValid
592
- }
593
- }));
594
- commitmentUpdates.result = {
595
- status: types.CaptchaStatus.disapproved,
596
- reason: check.reason
597
- };
598
- failStatus = check.reason;
599
- }
600
- }
601
- if (storeMetadata && email) {
602
- commitmentUpdates.metadata = { email };
603
- }
604
- if (ip) {
605
- const solutionIpAddress = compositeIpAddress.getIpAddressFromComposite(solution.ipAddress);
606
- const clientRecord = await this.db.getClientRecord(dapp);
607
- const ipValidationRules = clientRecord?.settings?.ipValidationRules;
608
- commitmentUpdates.providedIp = compositeIpAddress.getCompositeIpAddress(ip);
609
- if (!failStatus && ipValidationRules?.enabled === true) {
610
- const ipValidation = await util.deepValidateIpAddress(
611
- ip,
612
- solutionIpAddress,
613
- logger,
614
- env.ipInfoService,
615
- ipValidationRules,
616
- enrichedDnsEvent?.peerIp
617
- );
618
- if (!ipValidation.isValid) {
619
- logger.error(() => ({
620
- msg: "IP validation failed for image captcha",
621
- data: {
622
- ip,
623
- solutionIp: solutionIpAddress.address,
624
- error: ipValidation.errorMessage,
625
- distanceKm: ipValidation.distanceKm
626
- }
627
- }));
628
- failStatus = types.ResultReason.FAILED_IP_VALIDATION;
629
- }
630
- }
631
- }
632
- let isApproved = !failStatus && solution.result.status === types.CaptchaStatus.approved;
633
- let failureStatus = failStatus || types.ResultReason.USER_NOT_VERIFIED;
634
- if (!failStatus && solution.result.status === types.CaptchaStatus.approved && this.usageCounters) {
635
- const ipValue = ip ?? "";
636
- this.usageCounters.incrManyAsync(
637
- solution.dappAccount,
638
- usageCounters.buildAllWindowIncrements(
639
- "solved",
640
- types.CaptchaType.image,
641
- ipValue,
642
- solution.userAccount,
643
- enrichedDnsEvent?.peerIp
644
- )
645
- );
646
- }
647
- let score;
648
- if (sessionRecord) {
649
- const dnsAsymmetry = enrichDnsEvent.computeDnsAsymmetry(
650
- enrichedDnsEvent,
651
- solution.ipInfo,
652
- trafficFilter
653
- );
654
- if (dnsAsymmetry > 0) {
655
- sessionRecord.scoreComponents = {
656
- ...sessionRecord.scoreComponents,
657
- dnsAsymmetry
658
- };
659
- }
660
- score = frictionlessTasksUtils.computeFrictionlessScore(sessionRecord?.scoreComponents);
661
- logger.info(() => ({
662
- data: {
663
- scoreComponents: sessionRecord?.scoreComponents,
664
- score,
665
- dnsPeerAsn: enrichDnsEvent.getIpInfoAsn(enrichedDnsEvent?.peerIpInfo),
666
- dnsResolverAsn: enrichDnsEvent.getIpInfoAsn(enrichedDnsEvent?.resolverIpInfo)
667
- }
668
- }));
669
- if (!failStatus && disallowWebView === true && (sessionRecord.webView === true || (sessionRecord.scoreComponents.webView || 0) > 0)) {
670
- logger.info(() => ({
671
- msg: "Disallowing webview access - user not verified"
672
- }));
673
- commitmentUpdates.result = {
674
- status: types.CaptchaStatus.disapproved,
675
- reason: types.ResultReason.DISALLOWED_WEBVIEW
676
- };
677
- failStatus = types.ResultReason.DISALLOWED_WEBVIEW;
678
- isApproved = false;
679
- failureStatus = types.ResultReason.DISALLOWED_WEBVIEW;
680
- }
681
- if (contextAwareEnabled && sessionRecord.reason === types.FrictionlessReason.CONTEXT_AWARE_VALIDATION_FAILED) {
682
- logger.info(() => ({
683
- msg: "Context aware validation failed"
684
- }));
685
- }
686
- }
687
- if (isApproved) {
688
- const decisionInput = {
689
- userAccount: solution.userAccount,
690
- dappAccount: solution.dappAccount,
691
- captchaResult: "passed",
692
- headers: solution.headers,
693
- captchaType: types.CaptchaType.image,
694
- behavioralDataPacked: solution.behavioralDataPacked,
695
- deviceCapability: solution.deviceCapability,
696
- countryCode: solution.ipInfo?.isValid ? solution.ipInfo.countryCode : void 0,
697
- ipInfo: solution.ipInfo,
698
- dnsEvent: enrichedDnsEvent,
699
- score,
700
- threshold: sessionRecord?.threshold,
701
- scoreComponents: sessionRecord?.scoreComponents,
702
- decryptedHeadHash: sessionRecord?.decryptedHeadHash,
703
- userSitekeyIpHash: sessionRecord?.userSitekeyIpHash,
704
- simdReadings: sessionRecord?.simdReadings,
705
- frictionlessReason: sessionRecord?.reason,
706
- ruleType: sessionRecord?.ruleType,
707
- webView: sessionRecord?.webView,
708
- iFrame: sessionRecord?.iFrame,
709
- coords: solution.coords
710
- };
711
- try {
712
- const decision = await this.decisionMachineRunner.decide(
713
- decisionInput,
714
- logger
715
- );
716
- if (decision.decision === types.DecisionMachineDecision.Deny) {
717
- const dmReason = decision.reason || types.ResultReason.CAPTCHA_DECISION_MACHINE_DENIED;
718
- logger.info(() => ({
719
- msg: "Decision machine denied user verification",
720
- data: {
721
- reason: decision.reason
722
- }
723
- }));
724
- commitmentUpdates.result = {
725
- status: types.CaptchaStatus.disapproved,
726
- reason: dmReason
727
- };
728
- isApproved = false;
729
- failureStatus = dmReason;
730
- }
731
- } catch (error) {
732
- logger.error(() => ({
733
- msg: "Failed to process decision machine",
734
- err: error
735
- }));
736
- }
737
- }
738
- const writePromises = [];
739
- const sideUpdates = {};
740
- if (commitmentUpdates.providedIp) {
741
- sideUpdates.providedIp = commitmentUpdates.providedIp;
742
- }
743
- if (commitmentUpdates.metadata) {
744
- sideUpdates.metadata = commitmentUpdates.metadata;
745
- }
746
- if (Object.keys(sideUpdates).length > 0) {
747
- writePromises.push(
748
- this.db.updateDappUserCommitment(solution.id, sideUpdates)
749
- );
750
- }
751
- if (commitmentId) {
752
- if (isApproved) {
753
- writePromises.push(this.db.approveDappUserCommitment(commitmentId));
754
- } else if (commitmentUpdates.result) {
755
- writePromises.push(
756
- this.db.disapproveDappUserCommitment(
757
- commitmentId,
758
- commitmentUpdates.result.reason
759
- )
760
- );
761
- }
762
- }
763
- const finalResult = isApproved ? { status: types.CaptchaStatus.approved } : commitmentUpdates.result || {
764
- status: types.CaptchaStatus.disapproved,
765
- reason: failureStatus
766
- };
767
- if (solution.sessionId) {
768
- writePromises.push(
769
- this.updateSessionRecordWithCache(
770
- solution.sessionId,
771
- {
772
- serverChecked: true,
773
- result: finalResult
774
- },
775
- true
776
- )
777
- );
778
- }
779
- if (writePromises.length > 0) {
780
- await Promise.all(writePromises);
781
- }
782
- return {
783
- status: isApproved ? types.ResultReason.USER_VERIFIED : failureStatus,
784
- verified: isApproved,
785
- commitmentId: solution.id.toString(),
786
- ...score && { score }
787
- };
788
- }
789
- checkLangRules(acceptLanguage) {
790
- return lang.checkLangRules(this.config, acceptLanguage);
791
- }
792
- getVerificationResponse(verified, clientRecord, translateFn, score, commitmentId, reason) {
793
- return {
794
- ...super.getVerificationResponse(
795
- verified,
796
- clientRecord,
797
- translateFn,
798
- score,
799
- reason
800
- ),
801
- ...commitmentId && {
802
- [types.ApiParams.commitmentId]: commitmentId
803
- }
804
- };
805
- }
806
- }
1
+ const require_usageCounters = require("../../util/usageCounters.cjs");
2
+ const require_decisionMachineRunner = require("../decisionMachine/decisionMachineRunner.cjs");
3
+ const require_lang = require("../../rules/lang.cjs");
4
+ const require_compositeIpAddress = require("../../compositeIpAddress.cjs");
5
+ const require_enrichDnsEvent = require("../dnsEvent/enrichDnsEvent.cjs");
6
+ const require_captchaManager = require("../captchaManager.cjs");
7
+ const require_pairs = require("../../pairs.cjs");
8
+ const require_util = require("../../util.cjs");
9
+ const require_frictionlessTasksUtils = require("../frictionless/frictionlessTasksUtils.cjs");
10
+ const require_evaluateEmailSpamRules = require("../spam/evaluateEmailSpamRules.cjs");
11
+ const require_imgCaptchaTasksUtils = require("./imgCaptchaTasksUtils.cjs");
12
+ let _prosopo_common = require("@prosopo/common");
13
+ let _prosopo_types = require("@prosopo/types");
14
+ let _prosopo_util = require("@prosopo/util");
15
+ let _prosopo_util_crypto = require("@prosopo/util-crypto");
16
+ let _prosopo_datasets = require("@prosopo/datasets");
17
+ let _polkadot_util = require("@polkadot/util");
18
+ //#region src/tasks/imgCaptcha/imgCaptchaTasks.ts
19
+ var ImgCaptchaManager = class extends require_captchaManager.CaptchaManager {
20
+ constructor(db, pair, config, logger, writeQueue, usageCounters) {
21
+ super(db, pair, config, logger, writeQueue);
22
+ this.config = config;
23
+ this.decisionMachineRunner = new require_decisionMachineRunner.DecisionMachineRunner(db);
24
+ this.usageCounters = usageCounters ?? null;
25
+ }
26
+ async getCaptchaWithProof(datasetId, solved, size) {
27
+ const captchaDocs = await this.db.getRandomCaptcha(solved, datasetId, size);
28
+ if (!captchaDocs) throw new _prosopo_common.ProsopoEnvError("DATABASE.CAPTCHA_GET_FAILED", { context: {
29
+ failedFuncName: this.getCaptchaWithProof.name,
30
+ datasetId,
31
+ solved,
32
+ size
33
+ } });
34
+ return captchaDocs;
35
+ }
36
+ async getRandomCaptchasAndRequestHash(datasetId, userAccount, ipAddress, captchaConfig, threshold, sessionId, ipInfo) {
37
+ const dataset = await this.db.getDatasetDetails(datasetId);
38
+ if (!dataset) throw new _prosopo_common.ProsopoEnvError("DATABASE.DATASET_GET_FAILED", { context: {
39
+ failedFuncName: this.getRandomCaptchasAndRequestHash.name,
40
+ dataset,
41
+ datasetId
42
+ } });
43
+ const unsolvedCount = Math.abs(Math.trunc(captchaConfig.unsolved.count));
44
+ const solvedCount = Math.abs(Math.trunc(captchaConfig.solved.count));
45
+ if (!solvedCount) throw new _prosopo_common.ProsopoEnvError("CONFIG.INVALID_CAPTCHA_NUMBER");
46
+ const solved = await this.getCaptchaWithProof(datasetId, true, solvedCount);
47
+ let unsolved = [];
48
+ if (unsolvedCount) unsolved = await this.getCaptchaWithProof(datasetId, false, unsolvedCount);
49
+ const captchas = require_util.shuffleArray([...solved, ...unsolved]);
50
+ const salt = (0, _prosopo_util_crypto.randomAsHex)();
51
+ const requestHash = (0, _prosopo_datasets.computePendingRequestHash)(captchas.map((c) => c.captchaId), userAccount, salt);
52
+ const currentTime = Date.now();
53
+ const signedRequestHash = (0, _polkadot_util.u8aToHex)(this.pair.sign((0, _polkadot_util.stringToHex)(requestHash)));
54
+ const timeLimit = captchas.map((captcha) => captcha.timeLimitMs || _prosopo_types.DEFAULT_IMAGE_CAPTCHA_TIMEOUT).reduce((a, b) => a + b, 0);
55
+ const deadlineDate = new Date(timeLimit + currentTime);
56
+ await this.db.storePendingImageCommitment(userAccount, requestHash, salt, deadlineDate, new Date(currentTime), require_compositeIpAddress.getCompositeIpAddress(ipAddress), threshold, sessionId, ipInfo);
57
+ return {
58
+ captchas,
59
+ requestHash,
60
+ timestamp: currentTime,
61
+ signedRequestHash
62
+ };
63
+ }
64
+ /**
65
+ * Validate and store the text captcha solution(s) from the Dapp User in a web2 environment
66
+ * @param {string} userAccount
67
+ * @param {string} dappAccount
68
+ * @param {string} requestHash
69
+ * @param {JSON} captchas
70
+ * @param userTimestampSignature
71
+ * @param timestamp
72
+ * @param providerRequestHashSignature
73
+ * @param ipAddress
74
+ * @param headers
75
+ * @param ja4
76
+ * @param behavioralData
77
+ * @return {Promise<DappUserSolutionResult>} result containing the contract event
78
+ */
79
+ async dappUserSolution(userAccount, dappAccount, requestHash, captchas, userTimestampSignature, timestamp, providerRequestHashSignature, ipAddress, headers, ja4, behavioralData, ipInfo, simdReadings, clientMetaData) {
80
+ const decodedSimdReadings = simdReadings ? await this.decryptSimdReadingsForAttach(simdReadings) : void 0;
81
+ const pushSimdAttachIfAny = (sessionId, writes) => {
82
+ if (decodedSimdReadings) writes.push(this.recordSessionSimdReadingsIfAbsentWithCache(sessionId, decodedSimdReadings, _prosopo_types.SimdReadingsStage.submit));
83
+ };
84
+ if (!(0, _prosopo_util_crypto.signatureVerify)((0, _polkadot_util.stringToHex)(timestamp.toString()), userTimestampSignature, userAccount).isValid) {
85
+ const err = new _prosopo_common.ProsopoEnvError("GENERAL.INVALID_SIGNATURE", { context: {
86
+ failedFuncName: this.dappUserSolution.name,
87
+ userAccount
88
+ } });
89
+ this.logger.info(() => ({
90
+ err,
91
+ msg: "Invalid user timestamp signature"
92
+ }));
93
+ throw err;
94
+ }
95
+ if (!(0, _prosopo_util_crypto.signatureVerify)((0, _polkadot_util.stringToHex)(requestHash.toString()), providerRequestHashSignature, this.pair.address).isValid) {
96
+ const err = new _prosopo_common.ProsopoEnvError("GENERAL.INVALID_SIGNATURE", { context: {
97
+ failedFuncName: this.dappUserSolution.name,
98
+ userAccount,
99
+ error: "requestHash signature is invalid"
100
+ } });
101
+ this.logger.info(() => ({
102
+ err,
103
+ msg: "Invalid provider requestHash signature"
104
+ }));
105
+ throw err;
106
+ }
107
+ let response = {
108
+ captchas: [],
109
+ verified: false
110
+ };
111
+ const pendingRecord = await this.db.getPendingImageCommitment(requestHash);
112
+ const unverifiedCaptchaIds = captchas.map((captcha) => captcha.captchaId);
113
+ if (await this.validateDappUserSolutionRequestIsPending(requestHash, pendingRecord, userAccount, unverifiedCaptchaIds)) {
114
+ const { storedCaptchas, receivedCaptchas, captchaIds } = await this.validateReceivedCaptchasAgainstStoredCaptchas(captchas);
115
+ const rawFlat = receivedCaptchas.map((c) => (0, _prosopo_util.extractData)(c.salt));
116
+ const { checkbox: checkboxCoordPair, flat } = require_pairs.peelCheckboxPrefix(rawFlat, receivedCaptchas.map((c) => c.solution.length));
117
+ const shapePairs = flat.map((list) => require_pairs.constructPairList(list));
118
+ const pairs = checkboxCoordPair ? [[checkboxCoordPair], ...shapePairs] : shapePairs;
119
+ const { tree, commitmentId } = require_imgCaptchaTasksUtils.buildTreeAndGetCommitmentId(receivedCaptchas);
120
+ const datasetId = (0, _prosopo_util.at)(storedCaptchas, 0).datasetId;
121
+ if (!datasetId) throw new _prosopo_common.ProsopoEnvError("CAPTCHA.ID_MISMATCH", { context: { failedFuncName: this.dappUserSolution.name } });
122
+ await this.db.updatePendingImageCommitmentStatus(requestHash);
123
+ let behavioralDataPacked;
124
+ let deviceCapability;
125
+ if (behavioralData) try {
126
+ const decryptKeys = [...await this.getDetectorKeys(), process.env.BOT_DECRYPTION_KEY];
127
+ const decryptedData = await this.decryptBehavioralData(behavioralData, decryptKeys);
128
+ if (decryptedData) {
129
+ this.logger?.info(() => ({
130
+ msg: "Behavioral analysis completed",
131
+ data: {
132
+ userAccount,
133
+ dappAccount,
134
+ requestHash,
135
+ mouseEventsCount: decryptedData.collector1?.length || 0,
136
+ touchEventsCount: decryptedData.collector2?.length || 0,
137
+ clickEventsCount: decryptedData.collector3?.length || 0,
138
+ deviceCapability: decryptedData.deviceCapability
139
+ }
140
+ }));
141
+ behavioralDataPacked = {
142
+ c1: decryptedData.collector1 || [],
143
+ c2: decryptedData.collector2 || [],
144
+ c3: decryptedData.collector3 || [],
145
+ d: decryptedData.deviceCapability
146
+ };
147
+ deviceCapability = decryptedData.deviceCapability;
148
+ }
149
+ } catch (error) {
150
+ this.logger?.error(() => ({
151
+ msg: "Failed to process behavioral data",
152
+ err: error
153
+ }));
154
+ }
155
+ const commit = {
156
+ id: commitmentId,
157
+ userAccount,
158
+ dappAccount,
159
+ providerAccount: this.pair.address,
160
+ datasetId,
161
+ result: { status: _prosopo_types.CaptchaStatus.pending },
162
+ userSignature: userTimestampSignature,
163
+ userSubmitted: true,
164
+ serverChecked: false,
165
+ requestedAtTimestamp: new Date(timestamp),
166
+ submittedAtTimestamp: /* @__PURE__ */ new Date(),
167
+ ipAddress: require_compositeIpAddress.getCompositeIpAddress(ipAddress),
168
+ headers,
169
+ sessionId: pendingRecord.sessionId,
170
+ ja4,
171
+ ipInfo,
172
+ pending: false,
173
+ salt: pendingRecord.salt,
174
+ requestHash: pendingRecord[_prosopo_types.ApiParams.requestHash],
175
+ threshold: pendingRecord.threshold,
176
+ deadlineTimestamp: pendingRecord.deadlineTimestamp,
177
+ ...behavioralDataPacked && { behavioralDataPacked },
178
+ ...deviceCapability && { deviceCapability },
179
+ ...clientMetaData?.hp && { clientMetaData: { hp: clientMetaData.hp } }
180
+ };
181
+ await this.db.storeUserImageCaptchaSolution(receivedCaptchas, commit);
182
+ const solutionRecords = await Promise.all(storedCaptchas.map(async (captcha) => {
183
+ const solutionRecord = await this.db.getSolutionByCaptchaId(captcha.captchaId);
184
+ if (!solutionRecord) throw new _prosopo_common.ProsopoEnvError("CAPTCHA.SOLUTION_NOT_FOUND", { context: { failedFuncName: this.dappUserSolution.name } });
185
+ return solutionRecord;
186
+ }));
187
+ const totalImages = storedCaptchas[0]?.items.length || 0;
188
+ if (require_pairs.containsIdenticalPairs(pairs) && process.env.NODE_ENV !== "test") {
189
+ const writePromises = [this.db.disapproveDappUserCommitment(commitmentId, "CAPTCHA.INVALID_SOLUTION", pairs)];
190
+ if (pendingRecord.sessionId) {
191
+ writePromises.push(this.updateSessionRecordWithCache(pendingRecord.sessionId, {
192
+ userSubmitted: true,
193
+ result: {
194
+ status: _prosopo_types.CaptchaStatus.disapproved,
195
+ reason: _prosopo_types.ResultReason.CAPTCHA_INVALID_SOLUTION
196
+ }
197
+ }));
198
+ pushSimdAttachIfAny(pendingRecord.sessionId, writePromises);
199
+ }
200
+ await Promise.all(writePromises);
201
+ response = {
202
+ captchas: captchaIds.map((id) => ({
203
+ captchaId: id,
204
+ proof: [[]]
205
+ })),
206
+ verified: false
207
+ };
208
+ return response;
209
+ }
210
+ if ((0, _prosopo_datasets.compareCaptchaSolutions)(receivedCaptchas, solutionRecords, totalImages, pendingRecord.threshold)) {
211
+ response = {
212
+ captchas: captchaIds.map((id) => ({
213
+ captchaId: id,
214
+ proof: tree.proof(id)
215
+ })),
216
+ verified: true
217
+ };
218
+ const writePromises = [this.db.approveDappUserCommitment(commitmentId, pairs)];
219
+ if (pendingRecord.sessionId) {
220
+ writePromises.push(this.updateSessionRecordWithCache(pendingRecord.sessionId, {
221
+ userSubmitted: true,
222
+ result: { status: _prosopo_types.CaptchaStatus.approved }
223
+ }));
224
+ pushSimdAttachIfAny(pendingRecord.sessionId, writePromises);
225
+ }
226
+ await Promise.all(writePromises);
227
+ } else {
228
+ const writePromises = [this.db.disapproveDappUserCommitment(commitmentId, "CAPTCHA.INVALID_SOLUTION", pairs)];
229
+ if (pendingRecord.sessionId) {
230
+ writePromises.push(this.updateSessionRecordWithCache(pendingRecord.sessionId, {
231
+ userSubmitted: true,
232
+ result: {
233
+ status: _prosopo_types.CaptchaStatus.disapproved,
234
+ reason: _prosopo_types.ResultReason.CAPTCHA_INVALID_SOLUTION
235
+ }
236
+ }));
237
+ pushSimdAttachIfAny(pendingRecord.sessionId, writePromises);
238
+ }
239
+ await Promise.all(writePromises);
240
+ response = {
241
+ captchas: captchaIds.map((id) => ({
242
+ captchaId: id,
243
+ proof: [[]]
244
+ })),
245
+ verified: false
246
+ };
247
+ }
248
+ } else this.logger.info(() => ({ msg: "Request hash not found" }));
249
+ return response;
250
+ }
251
+ /**
252
+ * Validate length of received captchas array matches length of captchas found in database
253
+ * Validate that the datasetId is the same for all captchas and is equal to the datasetId on the stored captchas
254
+ */
255
+ async validateReceivedCaptchasAgainstStoredCaptchas(captchas) {
256
+ const receivedCaptchas = (0, _prosopo_datasets.parseAndSortCaptchaSolutions)(captchas);
257
+ const captchaIds = receivedCaptchas.map((captcha) => captcha.captchaId);
258
+ const storedCaptchas = await this.db.getCaptchaById(captchaIds);
259
+ if (!storedCaptchas || receivedCaptchas.length !== storedCaptchas.length) throw new _prosopo_common.ProsopoEnvError("CAPTCHA.INVALID_CAPTCHA_ID", { context: {
260
+ failedFuncName: this.validateReceivedCaptchasAgainstStoredCaptchas.name,
261
+ captchas
262
+ } });
263
+ if (!storedCaptchas.every((captcha) => captcha.datasetId === (0, _prosopo_util.at)(storedCaptchas, 0).datasetId)) throw new _prosopo_common.ProsopoEnvError("CAPTCHA.DIFFERENT_DATASET_IDS", { context: {
264
+ failedFuncName: this.validateReceivedCaptchasAgainstStoredCaptchas.name,
265
+ captchas
266
+ } });
267
+ return {
268
+ storedCaptchas,
269
+ receivedCaptchas,
270
+ captchaIds
271
+ };
272
+ }
273
+ /**
274
+ * Validate that a Dapp User is responding to their own pending captcha request
275
+ * @param {string} requestHash
276
+ * @param {PendingImageCaptchaRequest} pendingRecord
277
+ * @param {string} userAccount
278
+ * @param {string[]} captchaIds
279
+ */
280
+ async validateDappUserSolutionRequestIsPending(requestHash, pendingRecord, userAccount, captchaIds) {
281
+ const currentTime = /* @__PURE__ */ new Date();
282
+ if (!pendingRecord) {
283
+ this.logger.info(() => ({ msg: "No pending record found" }));
284
+ return false;
285
+ }
286
+ if (pendingRecord.deadlineTimestamp < currentTime) {
287
+ this.logger.info(() => ({ msg: "Deadline for responding to captcha has expired" }));
288
+ return false;
289
+ }
290
+ if (pendingRecord) return requestHash === (0, _prosopo_datasets.computePendingRequestHash)(captchaIds, userAccount, pendingRecord.salt);
291
+ return false;
292
+ }
293
+ async getDappUserCommitmentById(commitmentId) {
294
+ const dappUserSolution = await this.db.getDappUserCommitmentById(commitmentId);
295
+ if (!dappUserSolution) throw new _prosopo_common.ProsopoEnvError("CAPTCHA.DAPP_USER_SOLUTION_NOT_FOUND", { context: {
296
+ failedFuncName: this.getDappUserCommitmentById.name,
297
+ commitmentId
298
+ } });
299
+ return dappUserSolution;
300
+ }
301
+ async getDappUserCommitmentByAccount(userAccount, dappAccount) {
302
+ const dappUserSolutions = await this.db.getDappUserCommitmentByAccount(userAccount, dappAccount);
303
+ if (dappUserSolutions.length > 0) {
304
+ for (const dappUserSolution of dappUserSolutions) if (dappUserSolution.result.status === _prosopo_types.CaptchaStatus.approved) return dappUserSolution;
305
+ }
306
+ }
307
+ async verifyImageCaptchaSolution(user, dapp, commitmentId, env, maxVerifiedTime, ip, disallowWebView, contextAwareEnabled = false, userAccessRulesStorage, email, spamEmailDomainCheckingEnabled = false, spamFilter, trafficFilter, storeMetadata = false) {
308
+ const logger = this.logger.with({
309
+ commitmentId,
310
+ dapp
311
+ });
312
+ const solution = await (commitmentId ? this.getDappUserCommitmentById(commitmentId) : this.getDappUserCommitmentByAccount(user, dapp));
313
+ if (!solution) {
314
+ logger.debug(() => ({ msg: "Not verified - no solution found" }));
315
+ return {
316
+ status: "API.USER_NOT_VERIFIED_NO_SOLUTION",
317
+ verified: false
318
+ };
319
+ }
320
+ if (solution.serverChecked) return {
321
+ status: "API.USER_ALREADY_VERIFIED",
322
+ verified: false
323
+ };
324
+ await this.db.markDappUserCommitmentsChecked([solution.id]);
325
+ if (solution.result.status === _prosopo_types.CaptchaStatus.disapproved) return {
326
+ status: solution.result.reason || "API.USER_NOT_VERIFIED",
327
+ verified: false
328
+ };
329
+ maxVerifiedTime = maxVerifiedTime || 60 * 1e3;
330
+ const currentTime = Date.now();
331
+ const submittedAt = solution.submittedAtTimestamp;
332
+ if ((submittedAt instanceof Date ? currentTime - submittedAt.getTime() : Number.POSITIVE_INFINITY) > maxVerifiedTime) {
333
+ logger.debug(() => ({ msg: "Not verified - timed out" }));
334
+ return {
335
+ status: "API.USER_NOT_VERIFIED_TIME_EXPIRED",
336
+ verified: false
337
+ };
338
+ }
339
+ const commitmentUpdates = {};
340
+ let failStatus;
341
+ if (userAccessRulesStorage) try {
342
+ const blockPolicy = await this.checkForHardBlock(userAccessRulesStorage, solution, solution.userAccount, solution.headers, solution.coords, solution.ipInfo?.isValid ? solution.ipInfo.countryCode : void 0, solution.ipInfo?.isValid ? solution.ipInfo.asnNumber : void 0);
343
+ if (blockPolicy) {
344
+ logger.info(() => ({
345
+ msg: "User blocked by access policy in server image verification",
346
+ data: {
347
+ userAccount: solution.userAccount,
348
+ dappAccount: solution.dappAccount,
349
+ policy: blockPolicy
350
+ }
351
+ }));
352
+ commitmentUpdates.result = {
353
+ status: _prosopo_types.CaptchaStatus.disapproved,
354
+ reason: _prosopo_types.ResultReason.ACCESS_POLICY_BLOCK
355
+ };
356
+ failStatus = _prosopo_types.ResultReason.ACCESS_POLICY_BLOCK;
357
+ }
358
+ } catch (error) {
359
+ logger.warn(() => ({
360
+ msg: "Failed to check user access policies in server image verification",
361
+ error
362
+ }));
363
+ }
364
+ if (!failStatus && email && spamEmailDomainCheckingEnabled) try {
365
+ if (await this.checkSpamEmail(email)) {
366
+ const emailDomain = email.split("@")[1] || "unknown";
367
+ logger.info(() => ({
368
+ msg: "Spam email domain detected in server image verification",
369
+ data: { emailDomain }
370
+ }));
371
+ commitmentUpdates.result = {
372
+ status: _prosopo_types.CaptchaStatus.disapproved,
373
+ reason: _prosopo_types.ResultReason.SPAM_EMAIL_DOMAIN
374
+ };
375
+ failStatus = _prosopo_types.ResultReason.SPAM_EMAIL_DOMAIN;
376
+ }
377
+ } catch (error) {
378
+ logger.warn(() => ({
379
+ msg: "Failed to check spam email domain in server image verification",
380
+ error
381
+ }));
382
+ }
383
+ if (!failStatus && spamFilter?.enabled && spamFilter.emailRules?.enabled && email) {
384
+ const result = require_evaluateEmailSpamRules.evaluateEmailSpamRules(email, spamFilter.emailRules);
385
+ if (result.isSpam) {
386
+ logger.info(() => ({
387
+ msg: "Spam filter rejected email in image verification",
388
+ data: { reason: result.reason }
389
+ }));
390
+ commitmentUpdates.result = {
391
+ status: _prosopo_types.CaptchaStatus.disapproved,
392
+ reason: _prosopo_types.ResultReason.SPAM_EMAIL_RULE
393
+ };
394
+ failStatus = _prosopo_types.ResultReason.SPAM_EMAIL_RULE;
395
+ }
396
+ }
397
+ const sessionRecord = solution.sessionId ? await this.db.getSessionRecordBySessionId(solution.sessionId) : void 0;
398
+ const enrichedDnsEvent = await require_enrichDnsEvent.enrichDnsEvent(sessionRecord?.dnsEvent, env.ipInfoService, ip ?? solution.ipInfo?.ip);
399
+ if (!failStatus) {
400
+ const check = await this.resolveTrafficFilterCheck(env, solution.ipInfo, trafficFilter, ip, enrichedDnsEvent);
401
+ if (check.isBlocked) {
402
+ logger.info(() => ({
403
+ msg: "Traffic filter rejected request",
404
+ data: {
405
+ ip,
406
+ reason: check.reason,
407
+ dnsPeerIp: enrichedDnsEvent?.peerIp,
408
+ dnsResolverIp: enrichedDnsEvent?.resolverIp,
409
+ dnsPeerAsn: require_enrichDnsEvent.getIpInfoAsn(enrichedDnsEvent?.peerIpInfo),
410
+ dnsResolverAsn: require_enrichDnsEvent.getIpInfoAsn(enrichedDnsEvent?.resolverIpInfo),
411
+ dnsPathValid: enrichedDnsEvent?.pathValid
412
+ }
413
+ }));
414
+ commitmentUpdates.result = {
415
+ status: _prosopo_types.CaptchaStatus.disapproved,
416
+ reason: check.reason
417
+ };
418
+ failStatus = check.reason;
419
+ }
420
+ }
421
+ if (storeMetadata && email) commitmentUpdates.metadata = { email };
422
+ if (ip) {
423
+ const solutionIpAddress = require_compositeIpAddress.getIpAddressFromComposite(solution.ipAddress);
424
+ const ipValidationRules = (await this.db.getClientRecord(dapp))?.settings?.ipValidationRules;
425
+ commitmentUpdates.providedIp = require_compositeIpAddress.getCompositeIpAddress(ip);
426
+ if (!failStatus && ipValidationRules?.enabled === true) {
427
+ const ipValidation = await require_util.deepValidateIpAddress(ip, solutionIpAddress, logger, env.ipInfoService, ipValidationRules, enrichedDnsEvent?.peerIp);
428
+ if (!ipValidation.isValid) {
429
+ logger.error(() => ({
430
+ msg: "IP validation failed for image captcha",
431
+ data: {
432
+ ip,
433
+ solutionIp: solutionIpAddress.address,
434
+ error: ipValidation.errorMessage,
435
+ distanceKm: ipValidation.distanceKm
436
+ }
437
+ }));
438
+ failStatus = _prosopo_types.ResultReason.FAILED_IP_VALIDATION;
439
+ }
440
+ }
441
+ }
442
+ let isApproved = !failStatus && solution.result.status === _prosopo_types.CaptchaStatus.approved;
443
+ let failureStatus = failStatus || _prosopo_types.ResultReason.USER_NOT_VERIFIED;
444
+ if (!failStatus && solution.result.status === _prosopo_types.CaptchaStatus.approved && this.usageCounters) {
445
+ const ipValue = ip ?? "";
446
+ this.usageCounters.incrManyAsync(solution.dappAccount, require_usageCounters.buildAllWindowIncrements("solved", _prosopo_types.CaptchaType.image, ipValue, solution.userAccount, enrichedDnsEvent?.peerIp));
447
+ }
448
+ let score;
449
+ if (sessionRecord) {
450
+ const dnsAsymmetry = require_enrichDnsEvent.computeDnsAsymmetry(enrichedDnsEvent, solution.ipInfo, trafficFilter);
451
+ if (dnsAsymmetry > 0) sessionRecord.scoreComponents = {
452
+ ...sessionRecord.scoreComponents,
453
+ dnsAsymmetry
454
+ };
455
+ score = require_frictionlessTasksUtils.computeFrictionlessScore(sessionRecord?.scoreComponents);
456
+ logger.info(() => ({ data: {
457
+ scoreComponents: sessionRecord?.scoreComponents,
458
+ score,
459
+ dnsPeerAsn: require_enrichDnsEvent.getIpInfoAsn(enrichedDnsEvent?.peerIpInfo),
460
+ dnsResolverAsn: require_enrichDnsEvent.getIpInfoAsn(enrichedDnsEvent?.resolverIpInfo)
461
+ } }));
462
+ if (!failStatus && disallowWebView === true && (sessionRecord.webView === true || (sessionRecord.scoreComponents.webView || 0) > 0)) {
463
+ logger.info(() => ({ msg: "Disallowing webview access - user not verified" }));
464
+ commitmentUpdates.result = {
465
+ status: _prosopo_types.CaptchaStatus.disapproved,
466
+ reason: _prosopo_types.ResultReason.DISALLOWED_WEBVIEW
467
+ };
468
+ failStatus = _prosopo_types.ResultReason.DISALLOWED_WEBVIEW;
469
+ isApproved = false;
470
+ failureStatus = _prosopo_types.ResultReason.DISALLOWED_WEBVIEW;
471
+ }
472
+ if (contextAwareEnabled && sessionRecord.reason === _prosopo_types.FrictionlessReason.CONTEXT_AWARE_VALIDATION_FAILED) logger.info(() => ({ msg: "Context aware validation failed" }));
473
+ }
474
+ if (isApproved) {
475
+ const decisionInput = {
476
+ userAccount: solution.userAccount,
477
+ dappAccount: solution.dappAccount,
478
+ captchaResult: "passed",
479
+ headers: solution.headers,
480
+ captchaType: _prosopo_types.CaptchaType.image,
481
+ behavioralDataPacked: solution.behavioralDataPacked,
482
+ deviceCapability: solution.deviceCapability,
483
+ countryCode: solution.ipInfo?.isValid ? solution.ipInfo.countryCode : void 0,
484
+ ipInfo: solution.ipInfo,
485
+ dnsEvent: enrichedDnsEvent,
486
+ score,
487
+ threshold: sessionRecord?.threshold,
488
+ scoreComponents: sessionRecord?.scoreComponents,
489
+ decryptedHeadHash: sessionRecord?.decryptedHeadHash,
490
+ userSitekeyIpHash: sessionRecord?.userSitekeyIpHash,
491
+ simdReadings: sessionRecord?.simdReadings,
492
+ frictionlessReason: sessionRecord?.reason,
493
+ ruleType: sessionRecord?.ruleType,
494
+ webView: sessionRecord?.webView,
495
+ iFrame: sessionRecord?.iFrame,
496
+ coords: solution.coords
497
+ };
498
+ try {
499
+ const decision = await this.decisionMachineRunner.decide(decisionInput, logger);
500
+ if (decision.decision === _prosopo_types.DecisionMachineDecision.Deny) {
501
+ const dmReason = decision.reason || _prosopo_types.ResultReason.CAPTCHA_DECISION_MACHINE_DENIED;
502
+ logger.info(() => ({
503
+ msg: "Decision machine denied user verification",
504
+ data: { reason: decision.reason }
505
+ }));
506
+ commitmentUpdates.result = {
507
+ status: _prosopo_types.CaptchaStatus.disapproved,
508
+ reason: dmReason
509
+ };
510
+ isApproved = false;
511
+ failureStatus = dmReason;
512
+ }
513
+ } catch (error) {
514
+ logger.error(() => ({
515
+ msg: "Failed to process decision machine",
516
+ err: error
517
+ }));
518
+ }
519
+ }
520
+ const writePromises = [];
521
+ const sideUpdates = {};
522
+ if (commitmentUpdates.providedIp) sideUpdates.providedIp = commitmentUpdates.providedIp;
523
+ if (commitmentUpdates.metadata) sideUpdates.metadata = commitmentUpdates.metadata;
524
+ if (Object.keys(sideUpdates).length > 0) writePromises.push(this.db.updateDappUserCommitment(solution.id, sideUpdates));
525
+ if (commitmentId) {
526
+ if (isApproved) writePromises.push(this.db.approveDappUserCommitment(commitmentId));
527
+ else if (commitmentUpdates.result) writePromises.push(this.db.disapproveDappUserCommitment(commitmentId, commitmentUpdates.result.reason));
528
+ }
529
+ const finalResult = isApproved ? { status: _prosopo_types.CaptchaStatus.approved } : commitmentUpdates.result || {
530
+ status: _prosopo_types.CaptchaStatus.disapproved,
531
+ reason: failureStatus
532
+ };
533
+ if (solution.sessionId) writePromises.push(this.updateSessionRecordWithCache(solution.sessionId, {
534
+ serverChecked: true,
535
+ result: finalResult
536
+ }, true));
537
+ if (writePromises.length > 0) await Promise.all(writePromises);
538
+ return {
539
+ status: isApproved ? _prosopo_types.ResultReason.USER_VERIFIED : failureStatus,
540
+ verified: isApproved,
541
+ commitmentId: solution.id.toString(),
542
+ ...score && { score }
543
+ };
544
+ }
545
+ checkLangRules(acceptLanguage) {
546
+ return require_lang.checkLangRules(this.config, acceptLanguage);
547
+ }
548
+ getVerificationResponse(verified, clientRecord, translateFn, score, commitmentId, reason) {
549
+ return {
550
+ ...super.getVerificationResponse(verified, clientRecord, translateFn, score, reason),
551
+ ...commitmentId && { [_prosopo_types.ApiParams.commitmentId]: commitmentId }
552
+ };
553
+ }
554
+ };
555
+ //#endregion
807
556
  exports.ImgCaptchaManager = ImgCaptchaManager;