@prosopo/provider 5.0.4 → 5.1.0

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 (95) hide show
  1. package/.turbo/turbo-build$colon$cjs.log +17 -16
  2. package/.turbo/turbo-build$colon$tsc.log +24 -24
  3. package/.turbo/turbo-build.log +18 -17
  4. package/CHANGELOG.md +74 -0
  5. package/dist/api/blacklistRequestInspector.d.ts.map +1 -1
  6. package/dist/api/blacklistRequestInspector.js +3 -2
  7. package/dist/api/blacklistRequestInspector.js.map +1 -1
  8. package/dist/api/captcha/getFrictionlessCaptchaChallenge/accessPolicy.d.ts +2 -2
  9. package/dist/api/captcha/getFrictionlessCaptchaChallenge/accessPolicy.d.ts.map +1 -1
  10. package/dist/api/captcha/getFrictionlessCaptchaChallenge/accessPolicy.js +2 -0
  11. package/dist/api/captcha/getFrictionlessCaptchaChallenge/accessPolicy.js.map +1 -1
  12. package/dist/api/captcha/getFrictionlessCaptchaChallenge/handler.d.ts.map +1 -1
  13. package/dist/api/captcha/getFrictionlessCaptchaChallenge/handler.js +12 -0
  14. package/dist/api/captcha/getFrictionlessCaptchaChallenge/handler.js.map +1 -1
  15. package/dist/api/captcha/getImageCaptchaChallenge.d.ts.map +1 -1
  16. package/dist/api/captcha/getImageCaptchaChallenge.js +13 -1
  17. package/dist/api/captcha/getImageCaptchaChallenge.js.map +1 -1
  18. package/dist/api/captcha/getPoWCaptchaChallenge.d.ts.map +1 -1
  19. package/dist/api/captcha/getPoWCaptchaChallenge.js +12 -1
  20. package/dist/api/captcha/getPoWCaptchaChallenge.js.map +1 -1
  21. package/dist/api/captcha/getPuzzleCaptchaChallenge.d.ts.map +1 -1
  22. package/dist/api/captcha/getPuzzleCaptchaChallenge.js +12 -1
  23. package/dist/api/captcha/getPuzzleCaptchaChallenge.js.map +1 -1
  24. package/dist/api/captcha/trafficFilterRequestTime.d.ts +38 -0
  25. package/dist/api/captcha/trafficFilterRequestTime.d.ts.map +1 -0
  26. package/dist/api/captcha/trafficFilterRequestTime.js +109 -0
  27. package/dist/api/captcha/trafficFilterRequestTime.js.map +1 -0
  28. package/dist/cjs/api/blacklistRequestInspector.cjs +2 -1
  29. package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge/accessPolicy.cjs +2 -0
  30. package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge/handler.cjs +13 -0
  31. package/dist/cjs/api/captcha/getImageCaptchaChallenge.cjs +13 -1
  32. package/dist/cjs/api/captcha/getPoWCaptchaChallenge.cjs +12 -1
  33. package/dist/cjs/api/captcha/getPuzzleCaptchaChallenge.cjs +12 -1
  34. package/dist/cjs/api/captcha/trafficFilterRequestTime.cjs +111 -0
  35. package/dist/cjs/tasks/captchaManager.cjs +13 -6
  36. package/dist/cjs/tasks/dnsEvent/enrichDnsEvent.cjs +10 -8
  37. package/dist/cjs/tasks/frictionless/frictionlessTasks.cjs +19 -4
  38. package/dist/cjs/tasks/imgCaptcha/imgCaptchaTasks.cjs +5 -1
  39. package/dist/cjs/tasks/powCaptcha/powTasks.cjs +5 -1
  40. package/dist/cjs/tasks/puzzleCaptcha/puzzleTasks.cjs +5 -4
  41. package/dist/cjs/tasks/spam/checkTrafficFilter.cjs +101 -75
  42. package/dist/tasks/captchaManager.d.ts +3 -3
  43. package/dist/tasks/captchaManager.d.ts.map +1 -1
  44. package/dist/tasks/captchaManager.js +14 -7
  45. package/dist/tasks/captchaManager.js.map +1 -1
  46. package/dist/tasks/dnsEvent/enrichDnsEvent.d.ts.map +1 -1
  47. package/dist/tasks/dnsEvent/enrichDnsEvent.js +10 -8
  48. package/dist/tasks/dnsEvent/enrichDnsEvent.js.map +1 -1
  49. package/dist/tasks/frictionless/frictionlessTasks.d.ts +2 -1
  50. package/dist/tasks/frictionless/frictionlessTasks.d.ts.map +1 -1
  51. package/dist/tasks/frictionless/frictionlessTasks.js +19 -4
  52. package/dist/tasks/frictionless/frictionlessTasks.js.map +1 -1
  53. package/dist/tasks/imgCaptcha/imgCaptchaTasks.d.ts +1 -1
  54. package/dist/tasks/imgCaptcha/imgCaptchaTasks.d.ts.map +1 -1
  55. package/dist/tasks/imgCaptcha/imgCaptchaTasks.js +5 -1
  56. package/dist/tasks/imgCaptcha/imgCaptchaTasks.js.map +1 -1
  57. package/dist/tasks/powCaptcha/powTasks.d.ts +1 -1
  58. package/dist/tasks/powCaptcha/powTasks.d.ts.map +1 -1
  59. package/dist/tasks/powCaptcha/powTasks.js +5 -1
  60. package/dist/tasks/powCaptcha/powTasks.js.map +1 -1
  61. package/dist/tasks/puzzleCaptcha/puzzleTasks.d.ts +1 -1
  62. package/dist/tasks/puzzleCaptcha/puzzleTasks.d.ts.map +1 -1
  63. package/dist/tasks/puzzleCaptcha/puzzleTasks.js +5 -4
  64. package/dist/tasks/puzzleCaptcha/puzzleTasks.js.map +1 -1
  65. package/dist/tasks/spam/checkTrafficFilter.d.ts +18 -1
  66. package/dist/tasks/spam/checkTrafficFilter.d.ts.map +1 -1
  67. package/dist/tasks/spam/checkTrafficFilter.js +101 -77
  68. package/dist/tasks/spam/checkTrafficFilter.js.map +1 -1
  69. package/dist/tests/integration/clientSettingsPersistence.integration.test.js +24 -17
  70. package/dist/tests/integration/clientSettingsPersistence.integration.test.js.map +1 -1
  71. package/dist/tests/integration/trafficFilterHierarchy.integration.test.d.ts +2 -0
  72. package/dist/tests/integration/trafficFilterHierarchy.integration.test.d.ts.map +1 -0
  73. package/dist/tests/integration/trafficFilterHierarchy.integration.test.js +215 -0
  74. package/dist/tests/integration/trafficFilterHierarchy.integration.test.js.map +1 -0
  75. package/dist/tests/unit/api/blacklistRequestInspector.unit.test.js +6 -0
  76. package/dist/tests/unit/api/blacklistRequestInspector.unit.test.js.map +1 -1
  77. package/dist/tests/unit/api/captcha/getFrictionlessCaptchaChallenge/accessPolicy.unit.test.js +58 -0
  78. package/dist/tests/unit/api/captcha/getFrictionlessCaptchaChallenge/accessPolicy.unit.test.js.map +1 -1
  79. package/dist/tests/unit/api/captcha/trafficFilterRequestTime.unit.test.d.ts +2 -0
  80. package/dist/tests/unit/api/captcha/trafficFilterRequestTime.unit.test.d.ts.map +1 -0
  81. package/dist/tests/unit/api/captcha/trafficFilterRequestTime.unit.test.js +255 -0
  82. package/dist/tests/unit/api/captcha/trafficFilterRequestTime.unit.test.js.map +1 -0
  83. package/dist/tests/unit/api/getFrictionlessCaptchaChallenge.unit.test.js +2 -0
  84. package/dist/tests/unit/api/getFrictionlessCaptchaChallenge.unit.test.js.map +1 -1
  85. package/dist/tests/unit/tasks/dnsEvent/enrichDnsEvent.unit.test.js +6 -9
  86. package/dist/tests/unit/tasks/dnsEvent/enrichDnsEvent.unit.test.js.map +1 -1
  87. package/dist/tests/unit/tasks/imgCaptcha/imgCaptchaTasks.unit.test.js +49 -0
  88. package/dist/tests/unit/tasks/imgCaptcha/imgCaptchaTasks.unit.test.js.map +1 -1
  89. package/dist/tests/unit/tasks/powCaptcha/powTasks.unit.test.js +7 -0
  90. package/dist/tests/unit/tasks/powCaptcha/powTasks.unit.test.js.map +1 -1
  91. package/dist/tests/unit/tasks/puzzleCaptcha/puzzleTasks.unit.test.js +130 -0
  92. package/dist/tests/unit/tasks/puzzleCaptcha/puzzleTasks.unit.test.js.map +1 -1
  93. package/dist/tests/unit/tasks/spam/checkTrafficFilter.unit.test.js +272 -98
  94. package/dist/tests/unit/tasks/spam/checkTrafficFilter.unit.test.js.map +1 -1
  95. package/package.json +14 -14
@@ -438,7 +438,10 @@ var CaptchaManager = class CaptchaManager {
438
438
  return require_checkSpamEmail.checkSpamEmail(email, this.db, this.config, this.logger);
439
439
  }
440
440
  /**
441
- * Resolves the IP info to feed to `checkTrafficFilter` and runs the check.
441
+ * Resolves the IP info to feed to `checkTrafficFilter` and runs the check
442
+ * at submit time. Only `action: "block"` matches produce `isBlocked:true`;
443
+ * `action: "challenge"` matches were already applied at request time and
444
+ * are ignored here.
442
445
  *
443
446
  * - The captcha record already carries the IPInfoResponse from request
444
447
  * time (ipInfoMiddleware → storeXxxRecord), so by default we reuse it
@@ -448,9 +451,10 @@ var CaptchaManager = class CaptchaManager {
448
451
  * and may differ from the IP that originally requested the captcha.
449
452
  * - When the session carries a `dnsEvent`, its `peerIp` and `resolverIp`
450
453
  * are enriched and passed alongside the primary IP.
451
- * - `blockAbuser` defaults to true so abusive networks are always
452
- * blocked even when the site hasn't configured a trafficFilter.
453
- * - Returns `{ isBlocked: false }` if every filter flag is off, without
454
+ * - The abuser category defaults to `{action:"block"}` so abusive
455
+ * networks are always blocked even when the site hasn't configured a
456
+ * trafficFilter.
457
+ * - Returns `{ isBlocked: false }` if no category is active, without
454
458
  * consulting the payload at all.
455
459
  *
456
460
  * Callers handle the "blocked" branch themselves (each verify path
@@ -459,10 +463,13 @@ var CaptchaManager = class CaptchaManager {
459
463
  */
460
464
  async resolveTrafficFilterCheck(env, recordIpInfo, trafficFilter, currentIp, enrichedDnsEvent) {
461
465
  const effective = {
462
- blockAbuser: true,
466
+ abuser: { action: _prosopo_types.TrafficFilterAction.Block },
463
467
  ...trafficFilter
464
468
  };
465
- if (!Object.values(effective).some((v) => v)) return { isBlocked: false };
469
+ if (!(effective.vpn !== void 0 || effective.proxy !== void 0 || effective.tor !== void 0 || effective.abuser !== void 0 || effective.datacenter !== void 0 || effective.mobile !== void 0 || effective.satellite !== void 0 || effective.crawler !== void 0)) return {
470
+ isBlocked: false,
471
+ matches: []
472
+ };
466
473
  const ipInfo = currentIp ? await env.ipInfoService.lookup(currentIp) : recordIpInfo;
467
474
  return require_checkTrafficFilter.checkTrafficFilter(ipInfo, effective, require_enrichDnsEvent.extraIpInfosFromEnrichedDnsEvent(enrichedDnsEvent), enrichedDnsEvent?.pathValid);
468
475
  }
@@ -46,15 +46,17 @@ var extraIpInfosFromEnrichedDnsEvent = (enriched) => {
46
46
  * - client IP is a consumer ISP but resolver is datacenter (compound)
47
47
  *
48
48
  * When `trafficFilter` is supplied, the datacenter and abuser contributions
49
- * are gated on the same rules `checkTrafficFilter` uses for the client IP:
49
+ * are gated on the same rules `checkTrafficFilter` uses for the client IP.
50
+ * A category counts here whenever the operator has configured any policy
51
+ * for it (block or challenge) — either intent signals suspicion.
50
52
  *
51
- * - `blockDatacenter` must be opted in (default off); IPs whose
53
+ * - datacenter category must be configured; IPs whose
52
54
  * `providerType === "isp"` short-circuit; named allowlist skips
53
- * - `blockAbuser` opt-in (default on) with the abuser-score threshold
55
+ * - abuser category defaults to configured, with the score threshold
54
56
  * - cross-category suppression: VPN / proxy / Tor / crawler IPs that also
55
57
  * carry `isDatacenter=true` are NOT counted as datacenter when the
56
- * operator hasn't opted into blocking that more specific category —
57
- * mirrors the `datacenterSuppressedByCategory` rule in evaluateIpInfo.
58
+ * operator hasn't configured that more specific category — mirrors
59
+ * the `datacenterSuppressedByCategory` rule in evaluateIpInfo.
58
60
  *
59
61
  * `pathValid` is a protocol signal, not a category — it always contributes
60
62
  * regardless of trafficFilter. When `trafficFilter` is omitted, all
@@ -68,16 +70,16 @@ var computeDnsAsymmetry = (enriched, clientIpInfo, trafficFilter) => {
68
70
  const countDc = (ip) => {
69
71
  if (!ip?.isValid || !ip.isDatacenter) return false;
70
72
  if (!trafficFilter) return true;
71
- if (trafficFilter.blockDatacenter !== true) return false;
73
+ if (trafficFilter.datacenter === void 0) return false;
72
74
  if (require_checkTrafficFilter.isDatacenterDenylisted(ip, trafficFilter.datacenterNameDenylist)) return true;
73
- if (ip.isVPN && trafficFilter.blockVpn !== true || ip.isProxy && trafficFilter.blockProxy !== true || ip.isTor && trafficFilter.blockTor !== true || ip.isCrawler && trafficFilter.blockCrawler !== true) return false;
75
+ if (ip.isVPN && trafficFilter.vpn === void 0 || ip.isProxy && trafficFilter.proxy === void 0 || ip.isTor && trafficFilter.tor === void 0 || ip.isCrawler && trafficFilter.crawler === void 0) return false;
74
76
  if (ip.providerType === "isp") return false;
75
77
  return !require_checkTrafficFilter.isDatacenterAllowlisted(ip, trafficFilter.datacenterNameAllowlist);
76
78
  };
77
79
  const countAbuser = (ip) => {
78
80
  if (!ip?.isValid || !ip.isAbuser) return false;
79
81
  if (!trafficFilter) return true;
80
- if (trafficFilter.blockAbuser === false) return false;
82
+ if (trafficFilter.abuser === void 0) return false;
81
83
  const threshold = trafficFilter.abuserScoreThreshold ?? _prosopo_types.trafficFilterAbuserScoreThresholdDefault;
82
84
  return Math.max(ip.abuserScore ?? 0, ip.companyAbuserScore ?? 0) >= threshold;
83
85
  };
@@ -74,6 +74,20 @@ var FrictionlessManager = class extends require_captchaManager.CaptchaManager {
74
74
  chelloToHandshakeUs: params.chelloToHandshakeUs
75
75
  };
76
76
  }
77
+ /**
78
+ * Record the access rule that matched this request, so every session this
79
+ * request goes on to write carries it.
80
+ *
81
+ * Set separately from `setSessionParams` (which runs before rules are
82
+ * evaluated) and applied at `createSession` time, so it reaches all the
83
+ * outcomes a matched rule can lead to: the 401'd block, the auto-ban a
84
+ * score bump triggered, the captcha type a Restrict rule forced, and the
85
+ * ordinary decision-machine session a score-only Restrict leaves behind.
86
+ * Mirrors `updateScore`'s after-the-fact mutation of the same bag.
87
+ */
88
+ setMatchedRule(matchedRule) {
89
+ if (this.sessionParams) this.sessionParams.matchedRule = matchedRule;
90
+ }
77
91
  updateScore(score, scoreComponents) {
78
92
  if (this.sessionParams) {
79
93
  this.sessionParams.score = score;
@@ -83,7 +97,7 @@ var FrictionlessManager = class extends require_captchaManager.CaptchaManager {
83
97
  checkLangRules(acceptLanguage) {
84
98
  return require_lang.checkLangRules(this.config, acceptLanguage);
85
99
  }
86
- async createSession(token, score, threshold, scoreComponents, ipAddress, captchaType, siteKey, solvedImagesCount, powDifficulty, userSitekeyIpHash, webView = false, iFrame = false, decryptedHeadHash = "", reason, blocked, deleted, ipInfo, headers, mode, simdReadings, entropyMathRandomFingerprint, entropyCryptoFingerprint, entropyWallClockOffsetMs, entropyMathRandomFirst, bundleId, currentUrl, tcpToChelloUs, chelloToHandshakeUs, isEscalation, iframeUrl, isProtect, originSessionId, g) {
100
+ async createSession(token, score, threshold, scoreComponents, ipAddress, captchaType, siteKey, solvedImagesCount, powDifficulty, userSitekeyIpHash, webView = false, iFrame = false, decryptedHeadHash = "", reason, blocked, deleted, ipInfo, headers, mode, simdReadings, entropyMathRandomFingerprint, entropyCryptoFingerprint, entropyWallClockOffsetMs, entropyMathRandomFirst, bundleId, currentUrl, tcpToChelloUs, chelloToHandshakeUs, isEscalation, iframeUrl, isProtect, originSessionId, g, matchedRule) {
87
101
  const sessionRecord = {
88
102
  sessionId: `${getSessionIDPrefix(this.config.host)}-${(0, uuid.v4)()}`,
89
103
  createdAt: /* @__PURE__ */ new Date(),
@@ -120,7 +134,8 @@ var FrictionlessManager = class extends require_captchaManager.CaptchaManager {
120
134
  entropyMathRandomFirst,
121
135
  g,
122
136
  tcpToChelloUs,
123
- chelloToHandshakeUs
137
+ chelloToHandshakeUs,
138
+ ...matchedRule && { matchedRule }
124
139
  };
125
140
  await this.db.storeSessionRecord(sessionRecord);
126
141
  if (this.writeQueue) {
@@ -156,7 +171,7 @@ var FrictionlessManager = class extends require_captchaManager.CaptchaManager {
156
171
  const finalSolvedImagesCount = finalCaptchaType === _prosopo_types.CaptchaType.image ? routed.solvedImagesCount ?? effectiveParams.solvedImagesCount : void 0;
157
172
  const finalPowDifficulty = finalCaptchaType === _prosopo_types.CaptchaType.pow ? routed.powDifficulty ?? effectiveParams.powDifficulty : void 0;
158
173
  const blocked = finalCaptchaType === _prosopo_types.CaptchaType.image ? effectiveParams.blocked : void 0;
159
- const sessionRecord = await this.createSession(effectiveParams.token, effectiveParams.score, effectiveParams.threshold, effectiveParams.scoreComponents, effectiveParams.ipAddress, finalCaptchaType, effectiveParams.siteKey, finalSolvedImagesCount, finalPowDifficulty, effectiveParams.userSitekeyIpHash, effectiveParams.webView ?? false, effectiveParams.iFrame ?? false, effectiveParams.decryptedHeadHash, effectiveParams.reason, blocked, void 0, effectiveParams.ipInfo, effectiveParams.headers, effectiveParams.mode, effectiveParams.simdReadings, effectiveParams.entropyMathRandomFingerprint, effectiveParams.entropyCryptoFingerprint, effectiveParams.entropyWallClockOffsetMs, effectiveParams.entropyMathRandomFirst, effectiveParams.bundleId, effectiveParams.currentUrl, effectiveParams.tcpToChelloUs, effectiveParams.chelloToHandshakeUs, void 0, effectiveParams.iframeUrl, effectiveParams.isProtect, void 0, effectiveParams.g);
174
+ const sessionRecord = await this.createSession(effectiveParams.token, effectiveParams.score, effectiveParams.threshold, effectiveParams.scoreComponents, effectiveParams.ipAddress, finalCaptchaType, effectiveParams.siteKey, finalSolvedImagesCount, finalPowDifficulty, effectiveParams.userSitekeyIpHash, effectiveParams.webView ?? false, effectiveParams.iFrame ?? false, effectiveParams.decryptedHeadHash, effectiveParams.reason, blocked, void 0, effectiveParams.ipInfo, effectiveParams.headers, effectiveParams.mode, effectiveParams.simdReadings, effectiveParams.entropyMathRandomFingerprint, effectiveParams.entropyCryptoFingerprint, effectiveParams.entropyWallClockOffsetMs, effectiveParams.entropyMathRandomFirst, effectiveParams.bundleId, effectiveParams.currentUrl, effectiveParams.tcpToChelloUs, effectiveParams.chelloToHandshakeUs, void 0, effectiveParams.iframeUrl, effectiveParams.isProtect, void 0, effectiveParams.g, effectiveParams.matchedRule);
160
175
  if (this.routingContext && this.usageCounters) this.usageCounters.incrManyAsync(this.routingContext.dappAccount, require_usageCounters.buildAllWindowIncrements("served", finalCaptchaType, this.routingContext.ip, this.routingContext.userAccount));
161
176
  return {
162
177
  [_prosopo_types.ApiParams.captchaType]: finalCaptchaType,
@@ -171,7 +186,7 @@ var FrictionlessManager = class extends require_captchaManager.CaptchaManager {
171
186
  ...params
172
187
  };
173
188
  if (!effectiveParams.token || effectiveParams.score === void 0 || effectiveParams.threshold === void 0 || !effectiveParams.scoreComponents || !effectiveParams.ipAddress || effectiveParams.siteKey === void 0) throw new Error("Session parameters must be set before calling registerBlockedSession");
174
- await this.createSession(effectiveParams.token, effectiveParams.score, effectiveParams.threshold, effectiveParams.scoreComponents, effectiveParams.ipAddress, _prosopo_types.CaptchaType.image, effectiveParams.siteKey, effectiveParams.solvedImagesCount, void 0, effectiveParams.userSitekeyIpHash, effectiveParams.webView ?? false, effectiveParams.iFrame ?? false, effectiveParams.decryptedHeadHash, effectiveParams.reason, true, true, effectiveParams.ipInfo, effectiveParams.headers, effectiveParams.mode, effectiveParams.simdReadings, effectiveParams.entropyMathRandomFingerprint, effectiveParams.entropyCryptoFingerprint, effectiveParams.entropyWallClockOffsetMs, effectiveParams.entropyMathRandomFirst, effectiveParams.bundleId, effectiveParams.currentUrl, effectiveParams.tcpToChelloUs, effectiveParams.chelloToHandshakeUs, void 0, effectiveParams.iframeUrl, effectiveParams.isProtect, void 0, effectiveParams.g);
189
+ await this.createSession(effectiveParams.token, effectiveParams.score, effectiveParams.threshold, effectiveParams.scoreComponents, effectiveParams.ipAddress, _prosopo_types.CaptchaType.image, effectiveParams.siteKey, effectiveParams.solvedImagesCount, void 0, effectiveParams.userSitekeyIpHash, effectiveParams.webView ?? false, effectiveParams.iFrame ?? false, effectiveParams.decryptedHeadHash, effectiveParams.reason, true, true, effectiveParams.ipInfo, effectiveParams.headers, effectiveParams.mode, effectiveParams.simdReadings, effectiveParams.entropyMathRandomFingerprint, effectiveParams.entropyCryptoFingerprint, effectiveParams.entropyWallClockOffsetMs, effectiveParams.entropyMathRandomFirst, effectiveParams.bundleId, effectiveParams.currentUrl, effectiveParams.tcpToChelloUs, effectiveParams.chelloToHandshakeUs, void 0, effectiveParams.iframeUrl, effectiveParams.isProtect, void 0, effectiveParams.g, effectiveParams.matchedRule);
175
190
  }
176
191
  scoreIncreaseAccessPolicy(accessPolicy, baseBotScore, botScore, scoreComponents) {
177
192
  const accessPolicyPenalty = accessPolicy?.frictionlessScore || this.config.penalties.PENALTY_ACCESS_RULE;
@@ -14,6 +14,7 @@ let _prosopo_types = require("@prosopo/types");
14
14
  let _prosopo_util = require("@prosopo/util");
15
15
  let _prosopo_util_crypto = require("@prosopo/util-crypto");
16
16
  let _prosopo_datasets = require("@prosopo/datasets");
17
+ let _prosopo_user_access_policy = require("@prosopo/user-access-policy");
17
18
  let _polkadot_util = require("@polkadot/util");
18
19
  //#region src/tasks/imgCaptcha/imgCaptchaTasks.ts
19
20
  var ImgCaptchaManager = class extends require_captchaManager.CaptchaManager {
@@ -338,6 +339,7 @@ var ImgCaptchaManager = class extends require_captchaManager.CaptchaManager {
338
339
  }
339
340
  const commitmentUpdates = {};
340
341
  let failStatus;
342
+ let matchedRule;
341
343
  if (userAccessRulesStorage) try {
342
344
  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
345
  if (blockPolicy) {
@@ -354,6 +356,7 @@ var ImgCaptchaManager = class extends require_captchaManager.CaptchaManager {
354
356
  reason: _prosopo_types.ResultReason.ACCESS_POLICY_BLOCK
355
357
  };
356
358
  failStatus = _prosopo_types.ResultReason.ACCESS_POLICY_BLOCK;
359
+ matchedRule = (0, _prosopo_user_access_policy.describeMatchedRule)(blockPolicy);
357
360
  }
358
361
  } catch (error) {
359
362
  logger.warn(() => ({
@@ -563,7 +566,8 @@ var ImgCaptchaManager = class extends require_captchaManager.CaptchaManager {
563
566
  if (solution.sessionId) writePromises.push(this.updateSessionRecordWithCache(solution.sessionId, {
564
567
  serverChecked: true,
565
568
  result: finalResult,
566
- ...(0, _prosopo_types.isBlockingCaptchaResult)(_prosopo_types.CaptchaType.image, finalResult) && { blocked: true }
569
+ ...(0, _prosopo_types.isBlockingCaptchaResult)(_prosopo_types.CaptchaType.image, finalResult) && { blocked: true },
570
+ ...matchedRule && { matchedRule }
567
571
  }, true));
568
572
  if (writePromises.length > 0) await Promise.all(writePromises);
569
573
  return {
@@ -11,6 +11,7 @@ const require_powTasksUtils = require("./powTasksUtils.cjs");
11
11
  let _prosopo_common = require("@prosopo/common");
12
12
  let _prosopo_types = require("@prosopo/types");
13
13
  let _prosopo_util = require("@prosopo/util");
14
+ let _prosopo_user_access_policy = require("@prosopo/user-access-policy");
14
15
  let _polkadot_util = require("@polkadot/util");
15
16
  //#region src/tasks/powCaptcha/powTasks.ts
16
17
  var DEFAULT_POW_DIFFICULTY = 4;
@@ -293,6 +294,7 @@ var PowCaptchaManager = class extends require_captchaManager.CaptchaManager {
293
294
  const powRecordUpdates = {};
294
295
  let failResult;
295
296
  let failReason;
297
+ let matchedRule;
296
298
  const submittedAt = challengeRecord.submittedAtTimestamp;
297
299
  if ((submittedAt instanceof Date ? Date.now() - submittedAt.getTime() : Number.POSITIVE_INFINITY) > timeout) {
298
300
  failResult = {
@@ -316,6 +318,7 @@ var PowCaptchaManager = class extends require_captchaManager.CaptchaManager {
316
318
  reason: _prosopo_types.ResultReason.ACCESS_POLICY_BLOCK
317
319
  };
318
320
  failReason = "API.ACCESS_POLICY_BLOCK";
321
+ matchedRule = (0, _prosopo_user_access_policy.describeMatchedRule)(blockPolicy);
319
322
  }
320
323
  } catch (error) {
321
324
  logger.warn(() => ({
@@ -515,7 +518,8 @@ var PowCaptchaManager = class extends require_captchaManager.CaptchaManager {
515
518
  if (challengeRecord.sessionId) writePromises.push(this.updateSessionRecordWithCache(challengeRecord.sessionId, {
516
519
  serverChecked: true,
517
520
  result: finalResult,
518
- ...(0, _prosopo_types.isBlockingCaptchaResult)(_prosopo_types.CaptchaType.pow, finalResult) && { blocked: true }
521
+ ...(0, _prosopo_types.isBlockingCaptchaResult)(_prosopo_types.CaptchaType.pow, finalResult) && { blocked: true },
522
+ ...matchedRule && { matchedRule }
519
523
  }, true));
520
524
  if (writePromises.length > 0) await Promise.all(writePromises);
521
525
  if (failReason) return notVerified(failReason);
@@ -11,6 +11,7 @@ const require_puzzleTasksUtils = require("./puzzleTasksUtils.cjs");
11
11
  let _prosopo_common = require("@prosopo/common");
12
12
  let _prosopo_types = require("@prosopo/types");
13
13
  let _prosopo_util = require("@prosopo/util");
14
+ let _prosopo_user_access_policy = require("@prosopo/user-access-policy");
14
15
  let _polkadot_util = require("@polkadot/util");
15
16
  //#region src/tasks/puzzleCaptcha/puzzleTasks.ts
16
17
  var PuzzleCaptchaManager = class extends require_captchaManager.CaptchaManager {
@@ -126,6 +127,7 @@ var PuzzleCaptchaManager = class extends require_captchaManager.CaptchaManager {
126
127
  const dappAccount = (0, _prosopo_util.at)(challengeSplit, 2);
127
128
  this.usageCounters.incrManyAsync(dappAccount, require_usageCounters.buildAllWindowIncrements("solved", _prosopo_types.CaptchaType.puzzle, ipAddress.address, userAccount));
128
129
  }
130
+ await this.db.updatePuzzleCaptchaRecord(challenge, { puzzleEvents });
129
131
  if (behavioralData) try {
130
132
  const bundle = await this.resolveBundleBySessionId(challengeRecord.sessionId);
131
133
  const decryptedData = await this.decryptBehavioralData(behavioralData, bundle);
@@ -152,8 +154,7 @@ var PuzzleCaptchaManager = class extends require_captchaManager.CaptchaManager {
152
154
  };
153
155
  await this.db.updatePuzzleCaptchaRecord(challenge, {
154
156
  behavioralDataPacked: packedData,
155
- deviceCapability: decryptedData.deviceCapability,
156
- puzzleEvents
157
+ deviceCapability: decryptedData.deviceCapability
157
158
  });
158
159
  }
159
160
  } catch (error) {
@@ -162,7 +163,6 @@ var PuzzleCaptchaManager = class extends require_captchaManager.CaptchaManager {
162
163
  err: error
163
164
  }));
164
165
  }
165
- else await this.db.updatePuzzleCaptchaRecord(challenge, { puzzleEvents });
166
166
  if (clientMetaData?.hp) await this.db.updatePuzzleCaptchaRecord(challenge, { clientMetaData: { hp: clientMetaData.hp } });
167
167
  await this.db.updatePuzzleCaptchaRecordResult(challenge, result, false, true, userTimestampSignature, coords);
168
168
  if (challengeRecord.sessionId) {
@@ -260,7 +260,8 @@ var PuzzleCaptchaManager = class extends require_captchaManager.CaptchaManager {
260
260
  if (challengeRecord.sessionId) await this.updateSessionRecordWithCache(challengeRecord.sessionId, {
261
261
  serverChecked: true,
262
262
  result: blockedResult,
263
- ...isBlocked && { blocked: true }
263
+ ...isBlocked && { blocked: true },
264
+ matchedRule: (0, _prosopo_user_access_policy.describeMatchedRule)(blockPolicy)
264
265
  });
265
266
  return notVerifiedResponse;
266
267
  }
@@ -1,5 +1,16 @@
1
1
  let _prosopo_types = require("@prosopo/types");
2
- //#region src/tasks/spam/checkTrafficFilter.ts
2
+ var CATEGORY_TO_REASON = {
3
+ vpn: _prosopo_types.ResultReason.VPN_BLOCKED,
4
+ proxy: _prosopo_types.ResultReason.PROXY_BLOCKED,
5
+ tor: _prosopo_types.ResultReason.TOR_BLOCKED,
6
+ abuser: _prosopo_types.ResultReason.ABUSER_BLOCKED,
7
+ datacenter: _prosopo_types.ResultReason.DATACENTER_BLOCKED,
8
+ mobile: _prosopo_types.ResultReason.MOBILE_BLOCKED,
9
+ satellite: _prosopo_types.ResultReason.SATELLITE_BLOCKED,
10
+ crawler: _prosopo_types.ResultReason.CRAWLER_BLOCKED
11
+ };
12
+ var categoryToReason = (category) => CATEGORY_TO_REASON[category];
13
+ var isActive = (policy) => policy !== void 0;
3
14
  var matchesDatacenterNameList = (ipInfo, list) => {
4
15
  if (!list || list.length === 0) return false;
5
16
  const candidates = [];
@@ -18,65 +29,43 @@ var matchesDatacenterNameList = (ipInfo, list) => {
18
29
  };
19
30
  var isDatacenterAllowlisted = (ipInfo, allowlist) => matchesDatacenterNameList(ipInfo, allowlist);
20
31
  var isDatacenterDenylisted = (ipInfo, denylist) => matchesDatacenterNameList(ipInfo, denylist);
32
+ var push = (matches, category, policy) => {
33
+ if (policy) matches.push({
34
+ category,
35
+ policy
36
+ });
37
+ };
21
38
  var evaluateIpInfo = (ipInfo, trafficFilter, isDnsExtra = false) => {
22
- if (!ipInfo || !ipInfo.isValid) return { isBlocked: false };
23
- if (trafficFilter.blockVpn && ipInfo.isVPN) return {
24
- isBlocked: true,
25
- reason: _prosopo_types.ResultReason.VPN_BLOCKED
26
- };
27
- if (trafficFilter.blockProxy && ipInfo.isProxy) return {
28
- isBlocked: true,
29
- reason: _prosopo_types.ResultReason.PROXY_BLOCKED
30
- };
31
- if (trafficFilter.blockTor && ipInfo.isTor) return {
32
- isBlocked: true,
33
- reason: _prosopo_types.ResultReason.TOR_BLOCKED
34
- };
35
- if ((trafficFilter.blockAbuser ?? true) && ipInfo.isAbuser) {
39
+ const matches = [];
40
+ if (!ipInfo || !ipInfo.isValid) return matches;
41
+ if (isActive(trafficFilter.vpn) && ipInfo.isVPN) push(matches, "vpn", trafficFilter.vpn);
42
+ if (isActive(trafficFilter.proxy) && ipInfo.isProxy) push(matches, "proxy", trafficFilter.proxy);
43
+ if (isActive(trafficFilter.tor) && ipInfo.isTor) push(matches, "tor", trafficFilter.tor);
44
+ if (isActive(trafficFilter.abuser) && ipInfo.isAbuser) {
36
45
  const threshold = trafficFilter.abuserScoreThreshold ?? _prosopo_types.trafficFilterAbuserScoreThresholdDefault;
37
- if (Math.max(ipInfo.abuserScore ?? 0, ipInfo.companyAbuserScore ?? 0) >= threshold) return {
38
- isBlocked: true,
39
- reason: _prosopo_types.ResultReason.ABUSER_BLOCKED
40
- };
46
+ if (Math.max(ipInfo.abuserScore ?? 0, ipInfo.companyAbuserScore ?? 0) >= threshold) push(matches, "abuser", trafficFilter.abuser);
41
47
  }
42
- const datacenterSuppressedByCategory = ipInfo.isVPN && !trafficFilter.blockVpn || ipInfo.isProxy && !trafficFilter.blockProxy || ipInfo.isTor && !trafficFilter.blockTor || ipInfo.isCrawler && !trafficFilter.blockCrawler;
43
- if (trafficFilter.blockDatacenter && ipInfo.isDatacenter) {
44
- if (isDatacenterDenylisted(ipInfo, trafficFilter.datacenterNameDenylist)) return {
45
- isBlocked: true,
46
- reason: _prosopo_types.ResultReason.DATACENTER_BLOCKED
47
- };
48
- if (ipInfo.providerType !== "isp" && !datacenterSuppressedByCategory && !isDatacenterAllowlisted(ipInfo, trafficFilter.datacenterNameAllowlist)) return {
49
- isBlocked: true,
50
- reason: _prosopo_types.ResultReason.DATACENTER_BLOCKED
51
- };
48
+ const datacenterSuppressedByCategory = ipInfo.isVPN && !isActive(trafficFilter.vpn) || ipInfo.isProxy && !isActive(trafficFilter.proxy) || ipInfo.isTor && !isActive(trafficFilter.tor) || ipInfo.isCrawler && !isActive(trafficFilter.crawler);
49
+ if (isActive(trafficFilter.datacenter) && ipInfo.isDatacenter) {
50
+ if (isDatacenterDenylisted(ipInfo, trafficFilter.datacenterNameDenylist)) push(matches, "datacenter", trafficFilter.datacenter);
51
+ else if (ipInfo.providerType !== "isp" && !datacenterSuppressedByCategory && !isDatacenterAllowlisted(ipInfo, trafficFilter.datacenterNameAllowlist)) push(matches, "datacenter", trafficFilter.datacenter);
52
52
  }
53
- if (trafficFilter.blockMobile && ipInfo.isMobile) return {
54
- isBlocked: true,
55
- reason: _prosopo_types.ResultReason.MOBILE_BLOCKED
56
- };
57
- if (trafficFilter.blockSatellite && ipInfo.isSatellite) return {
58
- isBlocked: true,
59
- reason: _prosopo_types.ResultReason.SATELLITE_BLOCKED
60
- };
61
- if (!isDnsExtra && trafficFilter.blockCrawler && ipInfo.isCrawler) return {
62
- isBlocked: true,
63
- reason: _prosopo_types.ResultReason.CRAWLER_BLOCKED
64
- };
65
- return { isBlocked: false };
53
+ if (isActive(trafficFilter.mobile) && ipInfo.isMobile) push(matches, "mobile", trafficFilter.mobile);
54
+ if (isActive(trafficFilter.satellite) && ipInfo.isSatellite) push(matches, "satellite", trafficFilter.satellite);
55
+ if (!isDnsExtra && isActive(trafficFilter.crawler) && ipInfo.isCrawler) push(matches, "crawler", trafficFilter.crawler);
56
+ return matches;
66
57
  };
67
58
  /**
68
- * Checks whether a request should be blocked based on traffic filter
69
- * settings and the request's already-resolved IP info (attached to
70
- * `req.ipInfo` by `ipInfoMiddleware`). Each filter (VPN, proxy, Tor,
71
- * abuser, etc.) is evaluated independently. A missing or invalid
72
- * `ipInfo` falls back to "not blocked" so an outage in the upstream
73
- * service can't block all traffic.
59
+ * Evaluate trafficFilter against the request's IPs and return every category
60
+ * that matched (with the operator's policy for that category). Each match
61
+ * carries the policy `action` — `block` fires at submit time; `challenge`
62
+ * fires at request time to override captcha type + params.
74
63
  *
75
- * Cross-filter rule: the datacenter rule is suppressed when the IP
76
- * carries a more specific category the operator has left unblocked —
77
- * VPN, proxy, Tor, or crawler. All four legitimately sit on datacenter
78
- * infrastructure, so "block datacenters" (a scraping rule) shouldn't
79
- * catch them out the back door. Applies to primary and DNS extras.
64
+ * Cross-category rule: the datacenter category is suppressed when the IP
65
+ * carries a more specific category the operator has not configured — VPN,
66
+ * proxy, Tor, or crawler. All four legitimately sit on datacenter
67
+ * infrastructure, so "datacenter" (a scraping rule) shouldn't catch them
68
+ * out the back door.
80
69
  *
81
70
  * Extras-only rule: the crawler check is skipped on DNS extras. Public
82
71
  * DNS resolvers share IP space with search crawlers.
@@ -84,40 +73,77 @@ var evaluateIpInfo = (ipInfo, trafficFilter, isDnsExtra = false) => {
84
73
  * The datacenter rule also honours `datacenterNameAllowlist`: consumer
85
74
  * relays route through datacenter ranges and are reported as
86
75
  * `is_datacenter=true` by upstream but the exiting users are real humans.
87
- * Operators can list the datacenter, provider, or ASN organisation names
88
- * they want to allow through — see `isDatacenterAllowlisted`.
89
76
  *
90
77
  * The datacenter rule also short-circuits when upstream classifies the
91
- * provider as an ISP (`providerType === "isp"`). Consumer ISPs like
92
- * Afrihost, Comcast, or BT are sometimes flagged `is_datacenter=true` by
93
- * upstream heuristics — usually because part of the ASN hosts B2B or
94
- * hosting services — but the eyeball ranges behind those ASNs carry
95
- * ordinary end-users. The ISP categorisation is stronger evidence of
96
- * consumer traffic than the datacenter boolean.
78
+ * provider as an ISP (`providerType === "isp"`). Consumer ISPs are
79
+ * sometimes flagged `is_datacenter=true` upstream, but the ranges behind
80
+ * those ASNs carry ordinary end-users.
97
81
  *
98
82
  * The ISP short-circuit and the allowlist are both overridden by
99
- * `datacenterNameDenylist`: if the IP's `datacenterName`, `providerName`,
100
- * or `asnOrganization` matches a denylist entry, the block fires. This
101
- * lets operators opt named providers (for example IP-leasing platforms
102
- * whose ranges are announced from carrier ASNs but exit as proxy pools)
103
- * back into the datacenter rule.
83
+ * `datacenterNameDenylist`.
104
84
  *
105
85
  * When `trafficFilter.skipExtrasOnValidDnsPath` is on and the catcher
106
86
  * confirmed the DNS path matched the connection path (`pathValid: true`),
107
- * the extras evaluation is skipped. This stops public-DoH resolver IPs
108
- * from tripping the datacenter rule for ordinary users.
87
+ * the extras evaluation is skipped.
88
+ *
89
+ * The `isBlocked` verdict is set when at least one match has
90
+ * `action === "block"`, with `reason` naming that category. Callers that
91
+ * only care about challenges can inspect `matches` directly and use
92
+ * `resolveChallengePolicy` to combine them.
109
93
  */
110
94
  var checkTrafficFilter = (ipInfo, trafficFilter, extraIpInfos, dnsPathValid) => {
111
- const primary = evaluateIpInfo(ipInfo, trafficFilter);
112
- if (primary.isBlocked) return primary;
113
- if (trafficFilter.skipExtrasOnValidDnsPath && dnsPathValid === true) return { isBlocked: false };
114
- for (const extra of extraIpInfos ?? []) {
115
- const result = evaluateIpInfo(extra, trafficFilter, true);
116
- if (result.isBlocked) return result;
95
+ const matches = [];
96
+ matches.push(...evaluateIpInfo(ipInfo, trafficFilter));
97
+ if (!(trafficFilter.skipExtrasOnValidDnsPath && dnsPathValid === true)) for (const extra of extraIpInfos ?? []) matches.push(...evaluateIpInfo(extra, trafficFilter, true));
98
+ const firstBlock = matches.find((m) => m.policy.action === _prosopo_types.TrafficFilterAction.Block);
99
+ if (firstBlock) return {
100
+ isBlocked: true,
101
+ reason: categoryToReason(firstBlock.category),
102
+ matches
103
+ };
104
+ return {
105
+ isBlocked: false,
106
+ matches
107
+ };
108
+ };
109
+ var CAPTCHA_TYPE_RANK = {
110
+ [_prosopo_types.CaptchaType.image]: 4,
111
+ [_prosopo_types.CaptchaType.puzzle]: 3,
112
+ [_prosopo_types.CaptchaType.pow]: 2,
113
+ [_prosopo_types.CaptchaType.frictionless]: 1
114
+ };
115
+ var rankCaptchaType = (t) => t === void 0 ? 0 : CAPTCHA_TYPE_RANK[t] ?? 0;
116
+ /**
117
+ * Combine the `challenge` matches into a single effective policy: pick the
118
+ * strictest captcha type, then take the hardest params (max difficulty,
119
+ * max image count, min puzzle tolerance). `block` matches are ignored —
120
+ * callers should short-circuit on the top-level `isBlocked` verdict
121
+ * first. Returns undefined when there are no challenge matches.
122
+ */
123
+ var resolveChallengePolicy = (matches) => {
124
+ const challenges = matches.filter((m) => m.policy.action === _prosopo_types.TrafficFilterAction.Challenge);
125
+ if (challenges.length === 0) return void 0;
126
+ let winningCaptchaType;
127
+ for (const m of challenges) if (rankCaptchaType(m.policy.captchaType) > rankCaptchaType(winningCaptchaType)) winningCaptchaType = m.policy.captchaType;
128
+ let powDifficulty;
129
+ let solvedImagesCount;
130
+ let puzzleTolerance;
131
+ for (const m of challenges) {
132
+ if (m.policy.powDifficulty !== void 0) powDifficulty = powDifficulty === void 0 ? m.policy.powDifficulty : Math.max(powDifficulty, m.policy.powDifficulty);
133
+ if (m.policy.solvedImagesCount !== void 0) solvedImagesCount = solvedImagesCount === void 0 ? m.policy.solvedImagesCount : Math.max(solvedImagesCount, m.policy.solvedImagesCount);
134
+ if (m.policy.puzzleTolerance !== void 0) puzzleTolerance = puzzleTolerance === void 0 ? m.policy.puzzleTolerance : Math.min(puzzleTolerance, m.policy.puzzleTolerance);
117
135
  }
118
- return { isBlocked: false };
136
+ return {
137
+ captchaType: winningCaptchaType,
138
+ powDifficulty,
139
+ solvedImagesCount,
140
+ puzzleTolerance,
141
+ sourceCategories: challenges.map((m) => m.category)
142
+ };
119
143
  };
120
144
  //#endregion
145
+ exports.categoryToReason = categoryToReason;
121
146
  exports.checkTrafficFilter = checkTrafficFilter;
122
147
  exports.isDatacenterAllowlisted = isDatacenterAllowlisted;
123
148
  exports.isDatacenterDenylisted = isDatacenterDenylisted;
149
+ exports.resolveChallengePolicy = resolveChallengePolicy;
@@ -4,7 +4,7 @@ import { type Logger } from "@prosopo/logger";
4
4
  import { type CaptchaType, type EnrichedDnsEvent, type IPInfoResponse, type ITrafficFilter, type KeyringPair, type ProsopoConfigOutput, type RequestHeaders, type Session, type SimdReadingsStage, Tier, type UserCommitment } from "@prosopo/types";
5
5
  import type { ClientRecord, IProviderDatabase, IUserDataSlim, PoWCaptchaRecord, PuzzleCaptchaRecord } from "@prosopo/types-database";
6
6
  import type { ProviderEnvironment } from "@prosopo/types-env";
7
- import { type AccessPolicy, type AccessRulesStorage, type UserScope, type UserScopeRecord } from "@prosopo/user-access-policy";
7
+ import { type AccessPolicy, type AccessRule, type AccessRulesStorage, type UserScope, type UserScopeRecord } from "@prosopo/user-access-policy";
8
8
  import type { BehavioralDataResult } from "./detection/decodeBehavior.js";
9
9
  import type { SimdReadingsResult } from "./detection/decodeSimd.js";
10
10
  import { type TrafficCheckResult } from "./spam/checkTrafficFilter.js";
@@ -48,7 +48,7 @@ export declare class CaptchaManager {
48
48
  getPrioritisedAccessPolicies(userAccessRulesStorage: AccessRulesStorage, clientId: string, userScope: UserScope | UserScopeRecord, options?: {
49
49
  blockOnly?: boolean;
50
50
  requestMemoHost?: object;
51
- }): Promise<import("@prosopo/user-access-policy").AccessRule[]>;
51
+ }): Promise<AccessRule[]>;
52
52
  resolveBundleById(bundleId?: string | null): PoolBundleDecrypt | undefined;
53
53
  resolveBundleByDetectorSession(detectorSessionId?: string): Promise<(PoolBundleDecrypt & {
54
54
  bundleId: string;
@@ -56,7 +56,7 @@ export declare class CaptchaManager {
56
56
  resolveBundleBySessionId(sessionId?: string): Promise<PoolBundleDecrypt | undefined>;
57
57
  decryptSimdReadings(encryptedData: string, bundle: PoolBundleDecrypt | undefined): Promise<SimdReadingsResult | null>;
58
58
  decryptBehavioralData(encryptedData: string, bundle: PoolBundleDecrypt | undefined): Promise<BehavioralDataResult | null>;
59
- checkForHardBlock(userAccessRulesStorage: AccessRulesStorage, challengeRecord: PoWCaptchaRecord | PuzzleCaptchaRecord | UserCommitment, userAccount: string, headers: RequestHeaders, coords?: [number, number][][], countryCode?: string, asn?: number): Promise<AccessPolicy | undefined>;
59
+ checkForHardBlock(userAccessRulesStorage: AccessRulesStorage, challengeRecord: PoWCaptchaRecord | PuzzleCaptchaRecord | UserCommitment, userAccount: string, headers: RequestHeaders, coords?: [number, number][][], countryCode?: string, asn?: number): Promise<AccessRule | undefined>;
60
60
  checkSpamEmail(email: string): Promise<boolean>;
61
61
  resolveTrafficFilterCheck(env: ProviderEnvironment, recordIpInfo: IPInfoResponse | undefined, trafficFilter: Partial<ITrafficFilter> | undefined, currentIp?: string, enrichedDnsEvent?: EnrichedDnsEvent): Promise<TrafficCheckResult>;
62
62
  static canClientSeeScore(tier: Tier, score?: number): boolean | 0 | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"captchaManager.d.ts","sourceRoot":"","sources":["../../src/tasks/captchaManager.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"captchaManager.d.ts","sourceRoot":"","sources":["../../src/tasks/captchaManager.ts"],"names":[],"mappings":"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"}
@@ -5,7 +5,7 @@ import { checkTrafficFilter } from "./spam/checkTrafficFilter.js";
5
5
  import { extraIpInfosFromEnrichedDnsEvent } from "./dnsEvent/enrichDnsEvent.js";
6
6
  import { checkSpamEmail } from "./spam/checkSpamEmail.js";
7
7
  import { getLogger } from "@prosopo/logger";
8
- import { ApiParams, ResultReason, Tier } from "@prosopo/types";
8
+ import { ApiParams, ResultReason, Tier, TrafficFilterAction } from "@prosopo/types";
9
9
  import { AccessPolicyType } from "@prosopo/user-access-policy";
10
10
  //#region src/tasks/captchaManager.ts
11
11
  /**
@@ -438,7 +438,10 @@ var CaptchaManager = class CaptchaManager {
438
438
  return checkSpamEmail(email, this.db, this.config, this.logger);
439
439
  }
440
440
  /**
441
- * Resolves the IP info to feed to `checkTrafficFilter` and runs the check.
441
+ * Resolves the IP info to feed to `checkTrafficFilter` and runs the check
442
+ * at submit time. Only `action: "block"` matches produce `isBlocked:true`;
443
+ * `action: "challenge"` matches were already applied at request time and
444
+ * are ignored here.
442
445
  *
443
446
  * - The captcha record already carries the IPInfoResponse from request
444
447
  * time (ipInfoMiddleware → storeXxxRecord), so by default we reuse it
@@ -448,9 +451,10 @@ var CaptchaManager = class CaptchaManager {
448
451
  * and may differ from the IP that originally requested the captcha.
449
452
  * - When the session carries a `dnsEvent`, its `peerIp` and `resolverIp`
450
453
  * are enriched and passed alongside the primary IP.
451
- * - `blockAbuser` defaults to true so abusive networks are always
452
- * blocked even when the site hasn't configured a trafficFilter.
453
- * - Returns `{ isBlocked: false }` if every filter flag is off, without
454
+ * - The abuser category defaults to `{action:"block"}` so abusive
455
+ * networks are always blocked even when the site hasn't configured a
456
+ * trafficFilter.
457
+ * - Returns `{ isBlocked: false }` if no category is active, without
454
458
  * consulting the payload at all.
455
459
  *
456
460
  * Callers handle the "blocked" branch themselves (each verify path
@@ -459,10 +463,13 @@ var CaptchaManager = class CaptchaManager {
459
463
  */
460
464
  async resolveTrafficFilterCheck(env, recordIpInfo, trafficFilter, currentIp, enrichedDnsEvent) {
461
465
  const effective = {
462
- blockAbuser: true,
466
+ abuser: { action: TrafficFilterAction.Block },
463
467
  ...trafficFilter
464
468
  };
465
- if (!Object.values(effective).some((v) => v)) return { isBlocked: false };
469
+ if (!(effective.vpn !== void 0 || effective.proxy !== void 0 || effective.tor !== void 0 || effective.abuser !== void 0 || effective.datacenter !== void 0 || effective.mobile !== void 0 || effective.satellite !== void 0 || effective.crawler !== void 0)) return {
470
+ isBlocked: false,
471
+ matches: []
472
+ };
466
473
  return checkTrafficFilter(currentIp ? await env.ipInfoService.lookup(currentIp) : recordIpInfo, effective, extraIpInfosFromEnrichedDnsEvent(enrichedDnsEvent), enrichedDnsEvent?.pathValid);
467
474
  }
468
475
  static canClientSeeScore(tier, score) {