@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.
- package/.turbo/turbo-build$colon$cjs.log +106 -106
- package/.turbo/turbo-build$colon$tsc.log +24 -24
- package/.turbo/turbo-build.log +107 -104
- package/CHANGELOG.md +63 -0
- package/dist/_virtual/_rolldown/runtime.js +3 -0
- package/dist/api/admin/apiAdminRoutesProvider.js +27 -46
- package/dist/api/admin/apiClearAllCountersEndpoint.js +62 -57
- package/dist/api/admin/apiDnsEventEndpoint.js +79 -92
- package/dist/api/admin/apiGetAllDecisionMachinesEndpoint.js +43 -45
- package/dist/api/admin/apiGetDecisionMachineEndpoint.js +40 -40
- package/dist/api/admin/apiRegisterSiteKeyEndpoint.js +23 -22
- package/dist/api/admin/apiRegisterSiteKeysEndpoint.js +26 -30
- package/dist/api/admin/apiRemoveAllDecisionMachinesEndpoint.js +35 -37
- package/dist/api/admin/apiRemoveDecisionMachineEndpoint.js +40 -40
- package/dist/api/admin/apiRemoveDetectorKeyEndpoint.d.ts.map +1 -1
- package/dist/api/admin/apiRemoveDetectorKeyEndpoint.js +29 -31
- package/dist/api/admin/apiRemoveSiteKeyEndpoint.js +33 -29
- package/dist/api/admin/apiRemoveSiteKeysEndpoint.js +32 -31
- package/dist/api/admin/apiToggleMaintenanceModeEndpoint.js +43 -35
- package/dist/api/admin/apiUpdateDecisionMachineEndpoint.js +43 -63
- package/dist/api/admin/apiUpdateDetectorKeyEndpoint.js +36 -35
- package/dist/api/admin/createApiAdminRoutesProvider.js +6 -8
- package/dist/api/blacklistRequestInspector.js +285 -377
- package/dist/api/block.js +17 -30
- package/dist/api/captcha/checkSpamEmail.js +88 -98
- package/dist/api/captcha/contextAwareValidation.js +21 -15
- package/dist/api/captcha/getFrictionlessCaptchaChallenge/accessPolicy.js +126 -140
- package/dist/api/captcha/getFrictionlessCaptchaChallenge/constants.js +11 -12
- package/dist/api/captcha/getFrictionlessCaptchaChallenge/decisionMachine.js +236 -299
- package/dist/api/captcha/getFrictionlessCaptchaChallenge/decryptSimdReadings.js +10 -13
- package/dist/api/captcha/getFrictionlessCaptchaChallenge/handler.js +336 -462
- package/dist/api/captcha/getFrictionlessCaptchaChallenge/honeypotResponse.js +17 -14
- package/dist/api/captcha/getFrictionlessCaptchaChallenge/sessionDedup.js +27 -27
- package/dist/api/captcha/getFrictionlessCaptchaChallenge/shortCircuit.js +42 -63
- package/dist/api/captcha/getFrictionlessCaptchaChallenge.js +2 -4
- package/dist/api/captcha/getImageCaptchaChallenge.js +136 -207
- package/dist/api/captcha/getPoWCaptchaChallenge.js +128 -198
- package/dist/api/captcha/getPuzzleCaptchaChallenge.js +132 -201
- package/dist/api/captcha/maintenanceModeResponses.js +38 -47
- package/dist/api/captcha/submitImageCaptchaSolution.js +75 -101
- package/dist/api/captcha/submitPoWCaptchaSolution.js +137 -207
- package/dist/api/captcha/submitPuzzleCaptchaSolution.js +73 -105
- package/dist/api/captcha.js +76 -58
- package/dist/api/dnsEventUrl.js +20 -22
- package/dist/api/domainMiddleware.js +104 -115
- package/dist/api/forwardVerify.js +62 -48
- package/dist/api/handshakeTimingMiddleware.js +43 -62
- package/dist/api/hardBlockVerdictCache.js +114 -117
- package/dist/api/headerCheckMiddleware.js +34 -34
- package/dist/api/ignoreMiddleware.js +13 -15
- package/dist/api/ipInfoMiddleware.js +17 -19
- package/dist/api/ja4.js +149 -188
- package/dist/api/ja4Middleware.js +39 -54
- package/dist/api/metrics.js +245 -257
- package/dist/api/public.js +52 -54
- package/dist/api/robotsMiddleware.js +10 -10
- package/dist/api/startProviderApi.js +205 -232
- package/dist/api/testSiteKey.js +16 -16
- package/dist/api/validateAddress.js +23 -21
- package/dist/api/verify.js +334 -377
- package/dist/cjs/_virtual/_rolldown/runtime.cjs +23 -0
- package/dist/cjs/api/admin/apiAdminRoutesProvider.cjs +40 -59
- package/dist/cjs/api/admin/apiClearAllCountersEndpoint.cjs +63 -58
- package/dist/cjs/api/admin/apiDnsEventEndpoint.cjs +82 -93
- package/dist/cjs/api/admin/apiGetAllDecisionMachinesEndpoint.cjs +46 -48
- package/dist/cjs/api/admin/apiGetDecisionMachineEndpoint.cjs +41 -41
- package/dist/cjs/api/admin/apiRegisterSiteKeyEndpoint.cjs +25 -24
- package/dist/cjs/api/admin/apiRegisterSiteKeysEndpoint.cjs +28 -32
- package/dist/cjs/api/admin/apiRemoveAllDecisionMachinesEndpoint.cjs +38 -40
- package/dist/cjs/api/admin/apiRemoveDecisionMachineEndpoint.cjs +41 -41
- package/dist/cjs/api/admin/apiRemoveDetectorKeyEndpoint.cjs +31 -33
- package/dist/cjs/api/admin/apiRemoveSiteKeyEndpoint.cjs +35 -31
- package/dist/cjs/api/admin/apiRemoveSiteKeysEndpoint.cjs +34 -33
- package/dist/cjs/api/admin/apiToggleMaintenanceModeEndpoint.cjs +45 -35
- package/dist/cjs/api/admin/apiUpdateDecisionMachineEndpoint.cjs +45 -65
- package/dist/cjs/api/admin/apiUpdateDetectorKeyEndpoint.cjs +38 -37
- package/dist/cjs/api/admin/createApiAdminRoutesProvider.cjs +7 -8
- package/dist/cjs/api/blacklistRequestInspector.cjs +288 -376
- package/dist/cjs/api/block.cjs +18 -30
- package/dist/cjs/api/captcha/checkSpamEmail.cjs +90 -100
- package/dist/cjs/api/captcha/contextAwareValidation.cjs +21 -14
- package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge/accessPolicy.cjs +125 -140
- package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge/constants.cjs +10 -10
- package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge/decisionMachine.cjs +238 -301
- package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge/decryptSimdReadings.cjs +9 -12
- package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge/handler.cjs +349 -473
- package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge/honeypotResponse.cjs +18 -14
- package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge/sessionDedup.cjs +26 -26
- package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge/shortCircuit.cjs +42 -63
- package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge.cjs +2 -3
- package/dist/cjs/api/captcha/getImageCaptchaChallenge.cjs +138 -208
- package/dist/cjs/api/captcha/getPoWCaptchaChallenge.cjs +131 -200
- package/dist/cjs/api/captcha/getPuzzleCaptchaChallenge.cjs +135 -203
- package/dist/cjs/api/captcha/maintenanceModeResponses.cjs +38 -44
- package/dist/cjs/api/captcha/submitImageCaptchaSolution.cjs +77 -102
- package/dist/cjs/api/captcha/submitPoWCaptchaSolution.cjs +140 -209
- package/dist/cjs/api/captcha/submitPuzzleCaptchaSolution.cjs +76 -107
- package/dist/cjs/api/captcha.cjs +79 -58
- package/dist/cjs/api/dnsEventUrl.cjs +20 -21
- package/dist/cjs/api/domainMiddleware.cjs +106 -117
- package/dist/cjs/api/forwardVerify.cjs +63 -49
- package/dist/cjs/api/handshakeTimingMiddleware.cjs +43 -61
- package/dist/cjs/api/hardBlockVerdictCache.cjs +114 -114
- package/dist/cjs/api/headerCheckMiddleware.cjs +34 -34
- package/dist/cjs/api/ignoreMiddleware.cjs +12 -14
- package/dist/cjs/api/ipInfoMiddleware.cjs +16 -18
- package/dist/cjs/api/ja4.cjs +149 -187
- package/dist/cjs/api/ja4Middleware.cjs +40 -53
- package/dist/cjs/api/metrics.cjs +229 -228
- package/dist/cjs/api/public.cjs +57 -57
- package/dist/cjs/api/robotsMiddleware.cjs +9 -9
- package/dist/cjs/api/startProviderApi.cjs +219 -237
- package/dist/cjs/api/testSiteKey.cjs +15 -14
- package/dist/cjs/api/validateAddress.cjs +24 -21
- package/dist/cjs/api/verify.cjs +338 -379
- package/dist/cjs/compositeIpAddress.cjs +40 -47
- package/dist/cjs/index.cjs +65 -71
- package/dist/cjs/pairs.cjs +31 -35
- package/dist/cjs/rules/lang.cjs +10 -14
- package/dist/cjs/schedulers/captchaScheduler.cjs +26 -32
- package/dist/cjs/schedulers/getClientList.cjs +35 -33
- package/dist/cjs/schedulers/setClientEntropy.cjs +35 -33
- package/dist/cjs/schedulers/updateSpamEmailDomains.cjs +39 -43
- package/dist/cjs/services/ipComparison.cjs +122 -119
- package/dist/cjs/tasks/captchaManager.cjs +407 -486
- package/dist/cjs/tasks/client/clientTasks.cjs +347 -470
- package/dist/cjs/tasks/dataset/datasetTasks.cjs +25 -29
- package/dist/cjs/tasks/dataset/datasetTasksUtils.cjs +16 -32
- package/dist/cjs/tasks/decisionMachine/decisionMachineRunner.cjs +220 -307
- package/dist/cjs/tasks/detection/decodeBehavior.cjs +408 -317
- package/dist/cjs/tasks/detection/decodePayload.cjs +686 -468
- package/dist/cjs/tasks/detection/decodeSimd.cjs +462 -304
- package/dist/cjs/tasks/detection/getBotScore.cjs +37 -43
- package/dist/cjs/tasks/dnsEvent/enrichDnsEvent.cjs +88 -71
- package/dist/cjs/tasks/frictionless/frictionlessTasks.cjs +348 -486
- package/dist/cjs/tasks/frictionless/frictionlessTasksUtils.cjs +13 -30
- package/dist/cjs/tasks/frictionless/routingMachine.cjs +71 -55
- package/dist/cjs/tasks/imgCaptcha/imgCaptchaTasks.cjs +555 -806
- package/dist/cjs/tasks/imgCaptcha/imgCaptchaTasksUtils.cjs +22 -23
- package/dist/cjs/tasks/index.cjs +2 -4
- package/dist/cjs/tasks/powCaptcha/powTasks.cjs +491 -687
- package/dist/cjs/tasks/powCaptcha/powTasksUtils.cjs +17 -29
- package/dist/cjs/tasks/puzzleCaptcha/puzzleTasks.cjs +422 -591
- package/dist/cjs/tasks/puzzleCaptcha/puzzleTasksUtils.cjs +8 -5
- package/dist/cjs/tasks/spam/checkSpamEmail.cjs +133 -141
- package/dist/cjs/tasks/spam/checkTrafficFilter.cjs +118 -76
- package/dist/cjs/tasks/spam/evaluateEmailSpamRules.cjs +75 -84
- package/dist/cjs/tasks/spam/updateSpamEmailDomains.cjs +40 -56
- package/dist/cjs/tasks/tasks.cjs +107 -158
- package/dist/cjs/util/usageCounters.cjs +220 -211
- package/dist/cjs/util.cjs +329 -359
- package/dist/cjs/utils/devicePlatform.cjs +14 -8
- package/dist/cjs/utils/dns.cjs +68 -93
- package/dist/cjs/utils/hashUserAgent.cjs +14 -7
- package/dist/cjs/utils/hashUserIp.cjs +14 -6
- package/dist/cjs/utils/honeypot/encoders.cjs +74 -80
- package/dist/cjs/utils/honeypot/phraseBank.cjs +51 -44
- package/dist/cjs/utils/normalizeRequestIp.cjs +33 -25
- package/dist/compositeIpAddress.js +38 -46
- package/dist/index.js +12 -56
- package/dist/pairs.js +31 -37
- package/dist/rules/lang.js +11 -15
- package/dist/schedulers/captchaScheduler.js +25 -31
- package/dist/schedulers/getClientList.js +34 -32
- package/dist/schedulers/setClientEntropy.js +34 -32
- package/dist/schedulers/updateSpamEmailDomains.js +38 -42
- package/dist/services/ipComparison.js +122 -119
- package/dist/tasks/captchaManager.js +403 -483
- package/dist/tasks/client/clientTasks.js +343 -467
- package/dist/tasks/dataset/datasetTasks.js +24 -28
- package/dist/tasks/dataset/datasetTasksUtils.js +15 -31
- package/dist/tasks/decisionMachine/decisionMachineRunner.js +217 -306
- package/dist/tasks/detection/getBotScore.js +37 -43
- package/dist/tasks/dnsEvent/enrichDnsEvent.d.ts.map +1 -1
- package/dist/tasks/dnsEvent/enrichDnsEvent.js +88 -74
- package/dist/tasks/dnsEvent/enrichDnsEvent.js.map +1 -1
- package/dist/tasks/frictionless/frictionlessTasks.js +342 -479
- package/dist/tasks/frictionless/frictionlessTasksUtils.js +14 -32
- package/dist/tasks/frictionless/routingMachine.js +72 -56
- package/dist/tasks/imgCaptcha/imgCaptchaTasks.js +550 -801
- package/dist/tasks/imgCaptcha/imgCaptchaTasksUtils.js +21 -22
- package/dist/tasks/index.js +1 -3
- package/dist/tasks/powCaptcha/powTasks.d.ts.map +1 -1
- package/dist/tasks/powCaptcha/powTasks.js +486 -682
- package/dist/tasks/powCaptcha/powTasks.js.map +1 -1
- package/dist/tasks/powCaptcha/powTasksUtils.js +15 -28
- package/dist/tasks/puzzleCaptcha/puzzleTasks.d.ts.map +1 -1
- package/dist/tasks/puzzleCaptcha/puzzleTasks.js +417 -586
- package/dist/tasks/puzzleCaptcha/puzzleTasks.js.map +1 -1
- package/dist/tasks/puzzleCaptcha/puzzleTasksUtils.js +9 -6
- package/dist/tasks/spam/checkSpamEmail.js +133 -141
- package/dist/tasks/spam/checkTrafficFilter.d.ts +1 -0
- package/dist/tasks/spam/checkTrafficFilter.d.ts.map +1 -1
- package/dist/tasks/spam/checkTrafficFilter.js +117 -77
- package/dist/tasks/spam/checkTrafficFilter.js.map +1 -1
- package/dist/tasks/spam/evaluateEmailSpamRules.js +75 -86
- package/dist/tasks/spam/updateSpamEmailDomains.js +36 -54
- package/dist/tasks/tasks.js +98 -150
- package/dist/tests/integration/api/blacklistRequestInspector.integration.test.js +4 -1
- package/dist/tests/integration/api/blacklistRequestInspector.integration.test.js.map +1 -1
- package/dist/tests/unit/api/block.unit.test.js +8 -4
- package/dist/tests/unit/api/block.unit.test.js.map +1 -1
- package/dist/tests/unit/api/captcha/maintenanceModeShortCircuit.unit.test.js +8 -6
- package/dist/tests/unit/api/captcha/maintenanceModeShortCircuit.unit.test.js.map +1 -1
- package/dist/tests/unit/api/forwardVerify.unit.test.js +3 -1
- package/dist/tests/unit/api/forwardVerify.unit.test.js.map +1 -1
- package/dist/tests/unit/api/getFrictionlessCaptchaChallenge.unit.test.js +15 -7
- package/dist/tests/unit/api/getFrictionlessCaptchaChallenge.unit.test.js.map +1 -1
- package/dist/tests/unit/schedulers/captchaScheduler.unit.test.js +24 -18
- package/dist/tests/unit/schedulers/captchaScheduler.unit.test.js.map +1 -1
- package/dist/tests/unit/schedulers/getClientList.unit.test.js +19 -13
- package/dist/tests/unit/schedulers/getClientList.unit.test.js.map +1 -1
- package/dist/tests/unit/schedulers/setClientEntropy.unit.test.js +19 -13
- package/dist/tests/unit/schedulers/setClientEntropy.unit.test.js.map +1 -1
- package/dist/tests/unit/tasks/client/clientTasks.unit.test.js.map +1 -1
- package/dist/tests/unit/tasks/imgCaptcha/imgCaptchaTasksUtils.unit.test.js +22 -16
- package/dist/tests/unit/tasks/imgCaptcha/imgCaptchaTasksUtils.unit.test.js.map +1 -1
- package/dist/tests/unit/tasks/spam/checkTrafficFilter.unit.test.js +140 -0
- package/dist/tests/unit/tasks/spam/checkTrafficFilter.unit.test.js.map +1 -1
- package/dist/tests/unit/testUtils/mockProviderEnv.d.ts +7 -7
- package/dist/util/usageCounters.js +220 -212
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js +327 -364
- package/dist/util.js.map +1 -1
- package/dist/utils/devicePlatform.js +15 -9
- package/dist/utils/dns.js +64 -77
- package/dist/utils/hashUserAgent.js +14 -7
- package/dist/utils/hashUserIp.js +14 -6
- package/dist/utils/honeypot/encoders.js +74 -80
- package/dist/utils/honeypot/phraseBank.js +49 -44
- package/dist/utils/normalizeRequestIp.js +34 -26
- package/package.json +24 -24
|
@@ -1,488 +1,409 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
1
|
+
const require_compositeIpAddress = require("../compositeIpAddress.cjs");
|
|
2
|
+
const require_blacklistRequestInspector = require("../api/blacklistRequestInspector.cjs");
|
|
3
|
+
const require_checkTrafficFilter = require("./spam/checkTrafficFilter.cjs");
|
|
4
|
+
const require_enrichDnsEvent = require("./dnsEvent/enrichDnsEvent.cjs");
|
|
5
|
+
const require_checkSpamEmail = require("./spam/checkSpamEmail.cjs");
|
|
6
|
+
let _prosopo_logger = require("@prosopo/logger");
|
|
7
|
+
let _prosopo_types = require("@prosopo/types");
|
|
8
|
+
let _prosopo_user_access_policy = require("@prosopo/user-access-policy");
|
|
9
|
+
//#region src/tasks/captchaManager.ts
|
|
10
|
+
/**
|
|
11
|
+
* Finds a hard block policy from access policies.
|
|
12
|
+
*
|
|
13
|
+
* A hard block is either:
|
|
14
|
+
* (a) A Block policy with no captchaType (the historical "block all
|
|
15
|
+
* challenge types" case).
|
|
16
|
+
* (b) A Block policy with `deferToVerify: true` — request-time
|
|
17
|
+
* middleware skips these, so verify-time is the only place they
|
|
18
|
+
* can reject the commitment.
|
|
19
|
+
* (c) A Restrict policy with `deferToVerify: true` — the frictionless
|
|
20
|
+
* flow serves the rule's captchaType/solvedImagesCount normally
|
|
21
|
+
* (imposing the compute burden of the challenge), then verify
|
|
22
|
+
* marks the commitment disapproved regardless of solve outcome.
|
|
23
|
+
* Used by PROXY_POOL_TLS_NARROW to force a solving farm to burn
|
|
24
|
+
* 8 image rounds per session while still failing verify — bots
|
|
25
|
+
* solve at 100% so we can't stop them by demanding correctness,
|
|
26
|
+
* only by wasting their compute.
|
|
27
|
+
*
|
|
28
|
+
* Policies with captchaType and without `deferToVerify` are pure routing
|
|
29
|
+
* rules — they pick which challenge type to serve, not whether to reject.
|
|
30
|
+
*/
|
|
31
|
+
var findHardBlockPolicy = (accessPolicies) => {
|
|
32
|
+
return accessPolicies.find((policy) => {
|
|
33
|
+
if (policy.deferToVerify === true) return true;
|
|
34
|
+
return policy.type === _prosopo_user_access_policy.AccessPolicyType.Block && !policy.captchaType;
|
|
35
|
+
});
|
|
18
36
|
};
|
|
19
|
-
class CaptchaManager {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
}));
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
this.logger?.error(() => ({
|
|
395
|
-
msg: "Failed to decrypt behavioral data with all available keys",
|
|
396
|
-
data: {
|
|
397
|
-
totalKeysAttempted: validKeys.length
|
|
398
|
-
}
|
|
399
|
-
}));
|
|
400
|
-
return null;
|
|
401
|
-
}
|
|
402
|
-
/**
|
|
403
|
-
* Checks if a user should be hard blocked based on access policies
|
|
404
|
-
* Only checks for Block policies without captchaType
|
|
405
|
-
*
|
|
406
|
-
* @returns The blocking policy if user should be blocked, undefined otherwise
|
|
407
|
-
*/
|
|
408
|
-
async checkForHardBlock(userAccessRulesStorage, challengeRecord, userAccount, headers, coords, countryCode, asn) {
|
|
409
|
-
let headHash;
|
|
410
|
-
if (challengeRecord.sessionId) {
|
|
411
|
-
const sessionRecord = await this.db.getSessionRecordBySessionId(
|
|
412
|
-
challengeRecord.sessionId
|
|
413
|
-
);
|
|
414
|
-
headHash = sessionRecord?.decryptedHeadHash;
|
|
415
|
-
}
|
|
416
|
-
const coordsString = coords ? JSON.stringify(coords) : void 0;
|
|
417
|
-
const ipAddressRecord = compositeIpAddress.getIpAddressFromComposite(
|
|
418
|
-
challengeRecord.ipAddress
|
|
419
|
-
);
|
|
420
|
-
const userScope = blacklistRequestInspector.getRequestUserScope(
|
|
421
|
-
headers,
|
|
422
|
-
challengeRecord.ja4,
|
|
423
|
-
ipAddressRecord.address,
|
|
424
|
-
userAccount,
|
|
425
|
-
headHash,
|
|
426
|
-
coordsString,
|
|
427
|
-
countryCode,
|
|
428
|
-
asn
|
|
429
|
-
);
|
|
430
|
-
const accessPolicies = await this.getPrioritisedAccessPolicies(
|
|
431
|
-
userAccessRulesStorage,
|
|
432
|
-
challengeRecord.dappAccount,
|
|
433
|
-
userScope,
|
|
434
|
-
// Hard-block lookup only — restrict the Redis-side candidate
|
|
435
|
-
// pool to Block rules so the SERVER_SIDE_RANK_TOP_N cap can't
|
|
436
|
-
// crowd a hard-block out of the top-N with Restrict or
|
|
437
|
-
// routing-Block (captchaType-scoped) entries.
|
|
438
|
-
{ blockOnly: true }
|
|
439
|
-
);
|
|
440
|
-
return findHardBlockPolicy(accessPolicies);
|
|
441
|
-
}
|
|
442
|
-
/**
|
|
443
|
-
* Checks if the provided email address has a domain in the spam email domain list.
|
|
444
|
-
* Returns true if the email domain is spam (i.e. should be blocked), false otherwise.
|
|
445
|
-
* Handles formats: user@domain.com, @domain.com, domain.com
|
|
446
|
-
*/
|
|
447
|
-
async checkSpamEmail(email) {
|
|
448
|
-
return checkSpamEmail.checkSpamEmail(email, this.db, this.config, this.logger);
|
|
449
|
-
}
|
|
450
|
-
/**
|
|
451
|
-
* Resolves the IP info to feed to `checkTrafficFilter` and runs the check.
|
|
452
|
-
*
|
|
453
|
-
* - The captcha record already carries the IPInfoResponse from request
|
|
454
|
-
* time (ipInfoMiddleware → storeXxxRecord), so by default we reuse it
|
|
455
|
-
* instead of hitting the sidecar again.
|
|
456
|
-
* - If the dapp's server passed up the end user's current IP via the
|
|
457
|
-
* verify call, look that up fresh — it's the "now" IP for filtering
|
|
458
|
-
* and may differ from the IP that originally requested the captcha.
|
|
459
|
-
* - When the session carries a `dnsEvent`, its `peerIp` and `resolverIp`
|
|
460
|
-
* are enriched and passed alongside the primary IP.
|
|
461
|
-
* - `blockAbuser` defaults to true so abusive networks are always
|
|
462
|
-
* blocked even when the site hasn't configured a trafficFilter.
|
|
463
|
-
* - Returns `{ isBlocked: false }` if every filter flag is off, without
|
|
464
|
-
* consulting the payload at all.
|
|
465
|
-
*
|
|
466
|
-
* Callers handle the "blocked" branch themselves (each verify path
|
|
467
|
-
* updates record / session state differently); this helper just returns
|
|
468
|
-
* the verdict.
|
|
469
|
-
*/
|
|
470
|
-
async resolveTrafficFilterCheck(env, recordIpInfo, trafficFilter, currentIp, enrichedDnsEvent) {
|
|
471
|
-
const effective = { blockAbuser: true, ...trafficFilter };
|
|
472
|
-
const hasAny = Object.values(effective).some((v) => v);
|
|
473
|
-
if (!hasAny) {
|
|
474
|
-
return { isBlocked: false };
|
|
475
|
-
}
|
|
476
|
-
const ipInfo = currentIp ? await env.ipInfoService.lookup(currentIp) : recordIpInfo;
|
|
477
|
-
return checkTrafficFilter.checkTrafficFilter(
|
|
478
|
-
ipInfo,
|
|
479
|
-
effective,
|
|
480
|
-
enrichDnsEvent.extraIpInfosFromEnrichedDnsEvent(enrichedDnsEvent),
|
|
481
|
-
enrichedDnsEvent?.pathValid
|
|
482
|
-
);
|
|
483
|
-
}
|
|
484
|
-
static canClientSeeScore(tier, score) {
|
|
485
|
-
return score && tier && tier !== types.Tier.Free;
|
|
486
|
-
}
|
|
487
|
-
}
|
|
37
|
+
var CaptchaManager = class CaptchaManager {
|
|
38
|
+
constructor(db, pair, config, logger, writeQueue) {
|
|
39
|
+
this.pair = pair;
|
|
40
|
+
this.db = db;
|
|
41
|
+
this.config = config;
|
|
42
|
+
this.logger = logger || (0, _prosopo_logger.getLogger)("info", "provider:captcha-manager");
|
|
43
|
+
this.writeQueue = writeQueue ?? null;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Update a session record with cache-first / write-behind semantics:
|
|
47
|
+
* the Redis cache patch is awaited (so the in-request view of the
|
|
48
|
+
* session is up-to-date for any subsequent fast-path read), and the
|
|
49
|
+
* Mongo write is fire-and-forget so the response isn't gated on a
|
|
50
|
+
* round-trip to Mongo. Matches the `RedisWriteQueue` design where
|
|
51
|
+
* Redis is the source of truth for the in-flight request and Mongo is
|
|
52
|
+
* eventually consistent.
|
|
53
|
+
*
|
|
54
|
+
* Prefer this over `this.db.updateSessionRecord` in any in-request
|
|
55
|
+
* handler.
|
|
56
|
+
*/
|
|
57
|
+
async updateSessionRecordWithCache(sessionId, updates, streamToCentral) {
|
|
58
|
+
if (this.writeQueue) await this.writeQueue.patchCachedSession(sessionId, updates);
|
|
59
|
+
this.scheduleMongoSessionUpdate(sessionId, updates, streamToCentral);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Decrypt with the active detector keys and strip the throwaway
|
|
63
|
+
* `timestamp` field. Returns `undefined` when no key decrypts.
|
|
64
|
+
*/
|
|
65
|
+
async decryptSimdReadingsForAttach(simdReadingsCiphertext) {
|
|
66
|
+
const decryptKeys = [...await this.getDetectorKeys(), process.env.BOT_DECRYPTION_KEY];
|
|
67
|
+
const decrypted = await this.decryptSimdReadings(simdReadingsCiphertext, decryptKeys);
|
|
68
|
+
if (!decrypted) return void 0;
|
|
69
|
+
const { timestamp: _ignored, ...readings } = decrypted;
|
|
70
|
+
return readings;
|
|
71
|
+
}
|
|
72
|
+
/** Decrypt + first-hop-wins attach. No-op on decrypt failure. */
|
|
73
|
+
async decryptAndAttachSimdReadingsIfAbsent(sessionId, simdReadingsCiphertext, stage) {
|
|
74
|
+
const readings = await this.decryptSimdReadingsForAttach(simdReadingsCiphertext);
|
|
75
|
+
if (!readings) return;
|
|
76
|
+
await this.recordSessionSimdReadingsIfAbsentWithCache(sessionId, readings, stage);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* First-hop-wins SIMD attach with cache-first / Mongo-deferred
|
|
80
|
+
* semantics — mirrors `updateSessionRecordWithCache`.
|
|
81
|
+
*/
|
|
82
|
+
async recordSessionSimdReadingsIfAbsentWithCache(sessionId, readings, stage) {
|
|
83
|
+
if (this.writeQueue) await this.writeQueue.patchCachedSimdReadingsIfAbsent(sessionId, readings, stage);
|
|
84
|
+
this.scheduleMongoSimdReadingsUpdate(sessionId, readings, stage);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Fire-and-forget Mongo session update. Public so non-Task handlers
|
|
88
|
+
* (e.g. challenge GETs) can keep request response latency off the
|
|
89
|
+
* Mongo critical path while still ensuring eventual persistence.
|
|
90
|
+
*/
|
|
91
|
+
scheduleMongoSessionUpdate(sessionId, updates, streamToCentral) {
|
|
92
|
+
Promise.resolve(streamToCentral === void 0 ? this.db.updateSessionRecord(sessionId, updates) : this.db.updateSessionRecord(sessionId, updates, streamToCentral)).catch((err) => {
|
|
93
|
+
this.logger.warn(() => ({
|
|
94
|
+
err,
|
|
95
|
+
msg: "Background Mongo session update failed",
|
|
96
|
+
data: { sessionId }
|
|
97
|
+
}));
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Fire-and-forget Mongo SIMD-readings update. Public for the same
|
|
102
|
+
* reason as `scheduleMongoSessionUpdate`.
|
|
103
|
+
*/
|
|
104
|
+
scheduleMongoSimdReadingsUpdate(sessionId, readings, stage) {
|
|
105
|
+
Promise.resolve(this.db.recordSessionSimdReadingsIfAbsent(sessionId, readings, stage)).catch((err) => {
|
|
106
|
+
this.logger.warn(() => ({
|
|
107
|
+
err,
|
|
108
|
+
msg: "Background Mongo SIMD-readings update failed",
|
|
109
|
+
data: {
|
|
110
|
+
sessionId,
|
|
111
|
+
stage
|
|
112
|
+
}
|
|
113
|
+
}));
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
async validateSessionIP(sessionRecord, currentIP, env) {
|
|
117
|
+
return { valid: true };
|
|
118
|
+
}
|
|
119
|
+
async isValidRequest(clientSettings, requestedCaptchaType, env, sessionId, userAccessPolicy, currentIP) {
|
|
120
|
+
this.logger.debug(() => ({
|
|
121
|
+
msg: "Validating request",
|
|
122
|
+
data: {
|
|
123
|
+
captchaType: requestedCaptchaType,
|
|
124
|
+
sessionId
|
|
125
|
+
}
|
|
126
|
+
}));
|
|
127
|
+
if (userAccessPolicy?.captchaType !== void 0 && userAccessPolicy.captchaType !== requestedCaptchaType) {
|
|
128
|
+
this.logger.warn(() => ({
|
|
129
|
+
msg: "Invalid captcha type for user access policy",
|
|
130
|
+
data: {
|
|
131
|
+
account: clientSettings.account,
|
|
132
|
+
captchaType: userAccessPolicy.captchaType
|
|
133
|
+
}
|
|
134
|
+
}));
|
|
135
|
+
return {
|
|
136
|
+
valid: false,
|
|
137
|
+
reason: _prosopo_types.ResultReason.INCORRECT_CAPTCHA_TYPE,
|
|
138
|
+
type: requestedCaptchaType
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
if (sessionId) {
|
|
142
|
+
const cachedBeforeRemove = this.writeQueue ? await this.writeQueue.getCachedSession(sessionId) : null;
|
|
143
|
+
const cachedHash = typeof cachedBeforeRemove?.userSitekeyIpHash === "string" ? cachedBeforeRemove.userSitekeyIpHash : void 0;
|
|
144
|
+
await Promise.all([this.writeQueue?.invalidateCachedSession(sessionId), cachedHash ? this.writeQueue?.invalidateCachedSessionByHash(cachedHash) : Promise.resolve()]);
|
|
145
|
+
let sessionRecord = await this.db.checkAndRemoveSession(sessionId);
|
|
146
|
+
let resolvedSessionId = sessionId;
|
|
147
|
+
if (!sessionRecord && this.writeQueue) {
|
|
148
|
+
const escalationSessionId = await this.writeQueue.getCachedSessionEscalation(sessionId);
|
|
149
|
+
if (escalationSessionId && escalationSessionId !== sessionId) {
|
|
150
|
+
const peekedEscalationRecord = await this.db.getSessionRecordBySessionId(escalationSessionId);
|
|
151
|
+
if (peekedEscalationRecord) {
|
|
152
|
+
this.logger.info(() => ({
|
|
153
|
+
msg: "Resolving origin sessionId to escalation",
|
|
154
|
+
data: {
|
|
155
|
+
account: clientSettings.account,
|
|
156
|
+
originSessionId: sessionId,
|
|
157
|
+
escalationSessionId,
|
|
158
|
+
escalationCaptchaType: peekedEscalationRecord.captchaType,
|
|
159
|
+
requestedCaptchaType
|
|
160
|
+
}
|
|
161
|
+
}));
|
|
162
|
+
if (peekedEscalationRecord.captchaType === requestedCaptchaType) {
|
|
163
|
+
const consumedEscalationRecord = await this.db.checkAndRemoveSession(escalationSessionId);
|
|
164
|
+
if (consumedEscalationRecord) {
|
|
165
|
+
sessionRecord = consumedEscalationRecord;
|
|
166
|
+
resolvedSessionId = escalationSessionId;
|
|
167
|
+
}
|
|
168
|
+
} else {
|
|
169
|
+
this.logger.warn(() => ({
|
|
170
|
+
msg: "Escalation captcha type does not match requested type",
|
|
171
|
+
data: {
|
|
172
|
+
account: clientSettings.account,
|
|
173
|
+
originSessionId: sessionId,
|
|
174
|
+
escalationSessionId,
|
|
175
|
+
escalationCaptchaType: peekedEscalationRecord.captchaType,
|
|
176
|
+
requestedCaptchaType
|
|
177
|
+
}
|
|
178
|
+
}));
|
|
179
|
+
await this.writeQueue.invalidateCachedSessionEscalation(sessionId);
|
|
180
|
+
return {
|
|
181
|
+
valid: false,
|
|
182
|
+
reason: _prosopo_types.ResultReason.INCORRECT_CAPTCHA_TYPE,
|
|
183
|
+
type: requestedCaptchaType
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
await this.writeQueue.invalidateCachedSessionEscalation(sessionId);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (!sessionRecord) {
|
|
191
|
+
this.logger.warn(() => ({
|
|
192
|
+
msg: "No session found",
|
|
193
|
+
data: {
|
|
194
|
+
account: clientSettings.account,
|
|
195
|
+
sessionId
|
|
196
|
+
}
|
|
197
|
+
}));
|
|
198
|
+
if (this.writeQueue) {
|
|
199
|
+
const cachedHash = typeof cachedBeforeRemove?.userSitekeyIpHash === "string" ? cachedBeforeRemove.userSitekeyIpHash : void 0;
|
|
200
|
+
await Promise.all([this.writeQueue.invalidateCachedSession(sessionId), cachedHash ? this.writeQueue.invalidateCachedSessionByHash(cachedHash) : Promise.resolve()]);
|
|
201
|
+
}
|
|
202
|
+
return {
|
|
203
|
+
valid: false,
|
|
204
|
+
reason: _prosopo_types.ResultReason.CAPTCHA_NO_SESSION_FOUND,
|
|
205
|
+
type: requestedCaptchaType
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
sessionId = resolvedSessionId;
|
|
209
|
+
if (this.writeQueue) await Promise.all([this.writeQueue.invalidateCachedSession(sessionId), sessionRecord.userSitekeyIpHash ? this.writeQueue.invalidateCachedSessionByHash(sessionRecord.userSitekeyIpHash) : Promise.resolve()]);
|
|
210
|
+
if (currentIP) {
|
|
211
|
+
const ipValidation = await this.validateSessionIP(sessionRecord, currentIP, env);
|
|
212
|
+
if (!ipValidation.valid) return {
|
|
213
|
+
valid: false,
|
|
214
|
+
reason: ipValidation.reason,
|
|
215
|
+
type: requestedCaptchaType
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
if (sessionRecord.captchaType !== requestedCaptchaType) {
|
|
219
|
+
this.logger.warn(() => ({
|
|
220
|
+
msg: "Session captcha type does not match requested type",
|
|
221
|
+
data: {
|
|
222
|
+
account: clientSettings.account,
|
|
223
|
+
sessionId,
|
|
224
|
+
sessionCaptchaType: sessionRecord.captchaType,
|
|
225
|
+
requestedCaptchaType
|
|
226
|
+
}
|
|
227
|
+
}));
|
|
228
|
+
return {
|
|
229
|
+
valid: false,
|
|
230
|
+
reason: _prosopo_types.ResultReason.INCORRECT_CAPTCHA_TYPE,
|
|
231
|
+
type: requestedCaptchaType
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
return {
|
|
235
|
+
valid: true,
|
|
236
|
+
sessionId: sessionRecord.sessionId,
|
|
237
|
+
type: requestedCaptchaType,
|
|
238
|
+
...sessionRecord.powDifficulty && { powDifficulty: sessionRecord.powDifficulty },
|
|
239
|
+
...sessionRecord.solvedImagesCount && { solvedImagesCount: sessionRecord.solvedImagesCount },
|
|
240
|
+
ipInfo: sessionRecord.ipInfo
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
if (clientSettings?.settings?.captchaType !== requestedCaptchaType) {
|
|
244
|
+
this.logger.warn(() => ({
|
|
245
|
+
msg: `Invalid ${requestedCaptchaType} request`,
|
|
246
|
+
data: {
|
|
247
|
+
account: clientSettings.account,
|
|
248
|
+
requestedCaptchaType,
|
|
249
|
+
settingsCaptchaType: clientSettings?.settings?.captchaType
|
|
250
|
+
}
|
|
251
|
+
}));
|
|
252
|
+
return {
|
|
253
|
+
valid: false,
|
|
254
|
+
reason: _prosopo_types.ResultReason.INCORRECT_CAPTCHA_TYPE,
|
|
255
|
+
type: requestedCaptchaType
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
return {
|
|
259
|
+
valid: true,
|
|
260
|
+
type: requestedCaptchaType
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
getVerificationResponse(verified, clientRecord, translateFn, score, reason) {
|
|
264
|
+
return {
|
|
265
|
+
status: translateFn(verified ? "API.USER_VERIFIED" : "API.USER_NOT_VERIFIED"),
|
|
266
|
+
[_prosopo_types.ApiParams.verified]: verified,
|
|
267
|
+
...CaptchaManager.canClientSeeScore(clientRecord.tier, score) && { [_prosopo_types.ApiParams.score]: score },
|
|
268
|
+
...!verified && clientRecord.tier !== _prosopo_types.Tier.Free && reason && { [_prosopo_types.ApiParams.reason]: reason }
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
async getPrioritisedAccessPolicies(userAccessRulesStorage, clientId, userScope, options) {
|
|
272
|
+
return require_blacklistRequestInspector.getPrioritisedAccessRule(userAccessRulesStorage, userScope, clientId, options);
|
|
273
|
+
}
|
|
274
|
+
async getDetectorKeys() {
|
|
275
|
+
return await this.db.getDetectorKeys();
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Decrypt the catcher's WASM SIMD CPU fingerprint readings via the
|
|
279
|
+
* obfuscated `decodeSimd.js` bundle (source lives in the private
|
|
280
|
+
* @prosopo/catcher repo). Mirrors `decryptBehavioralData`: tries each
|
|
281
|
+
* detector key in turn (loaded by the caller, with the env-var key as
|
|
282
|
+
* fallback). Returns null when no key works so the caller can drop
|
|
283
|
+
* the field rather than fail the whole request.
|
|
284
|
+
*/
|
|
285
|
+
async decryptSimdReadings(encryptedData, decryptKeys) {
|
|
286
|
+
const decryptSimdReadings = (await Promise.resolve().then(() => require("./detection/decodeSimd.cjs"))).default;
|
|
287
|
+
const validKeys = decryptKeys.filter((k) => k);
|
|
288
|
+
if (validKeys.length === 0) {
|
|
289
|
+
this.logger?.error(() => ({ msg: "No decryption keys provided for SIMD readings" }));
|
|
290
|
+
return null;
|
|
291
|
+
}
|
|
292
|
+
for (const [keyIndex, key] of validKeys.entries()) try {
|
|
293
|
+
return await decryptSimdReadings(encryptedData, key);
|
|
294
|
+
} catch (err) {
|
|
295
|
+
this.logger?.debug(() => ({
|
|
296
|
+
msg: "Failed to decrypt SIMD readings with key, trying next",
|
|
297
|
+
data: {
|
|
298
|
+
keyIndex: keyIndex + 1,
|
|
299
|
+
totalKeys: validKeys.length,
|
|
300
|
+
err
|
|
301
|
+
}
|
|
302
|
+
}));
|
|
303
|
+
}
|
|
304
|
+
this.logger?.warn(() => ({
|
|
305
|
+
msg: "Failed to decrypt SIMD readings with all available keys",
|
|
306
|
+
data: { totalKeysAttempted: validKeys.length }
|
|
307
|
+
}));
|
|
308
|
+
return null;
|
|
309
|
+
}
|
|
310
|
+
async decryptBehavioralData(encryptedData, decryptKeys) {
|
|
311
|
+
const decryptBehavioralData = (await Promise.resolve().then(() => require("./detection/decodeBehavior.cjs"))).default;
|
|
312
|
+
const validKeys = decryptKeys.filter((k) => k);
|
|
313
|
+
if (validKeys.length === 0) {
|
|
314
|
+
this.logger?.error(() => ({ msg: "No decryption keys provided for behavioral data" }));
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
for (const [keyIndex, key] of validKeys.entries()) try {
|
|
318
|
+
this.logger?.debug(() => ({
|
|
319
|
+
msg: "Attempting to decrypt behavioral data",
|
|
320
|
+
data: {
|
|
321
|
+
keyIndex: keyIndex + 1,
|
|
322
|
+
totalKeys: validKeys.length
|
|
323
|
+
}
|
|
324
|
+
}));
|
|
325
|
+
const result = await decryptBehavioralData(encryptedData, key);
|
|
326
|
+
this.logger?.info(() => ({
|
|
327
|
+
msg: "Behavioral data decrypted successfully",
|
|
328
|
+
data: {
|
|
329
|
+
keyIndex: keyIndex + 1,
|
|
330
|
+
c1Length: result.collector1?.length || 0,
|
|
331
|
+
c2Length: result.collector2?.length || 0,
|
|
332
|
+
c3Length: result.collector3?.length || 0,
|
|
333
|
+
deviceCapability: result.deviceCapability
|
|
334
|
+
}
|
|
335
|
+
}));
|
|
336
|
+
return result;
|
|
337
|
+
} catch (error) {
|
|
338
|
+
this.logger?.debug(() => ({
|
|
339
|
+
msg: "Failed to decrypt with key, trying next",
|
|
340
|
+
data: {
|
|
341
|
+
keyIndex: keyIndex + 1,
|
|
342
|
+
totalKeys: validKeys.length,
|
|
343
|
+
err: error
|
|
344
|
+
}
|
|
345
|
+
}));
|
|
346
|
+
}
|
|
347
|
+
this.logger?.error(() => ({
|
|
348
|
+
msg: "Failed to decrypt behavioral data with all available keys",
|
|
349
|
+
data: { totalKeysAttempted: validKeys.length }
|
|
350
|
+
}));
|
|
351
|
+
return null;
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Checks if a user should be hard blocked based on access policies
|
|
355
|
+
* Only checks for Block policies without captchaType
|
|
356
|
+
*
|
|
357
|
+
* @returns The blocking policy if user should be blocked, undefined otherwise
|
|
358
|
+
*/
|
|
359
|
+
async checkForHardBlock(userAccessRulesStorage, challengeRecord, userAccount, headers, coords, countryCode, asn) {
|
|
360
|
+
let headHash;
|
|
361
|
+
if (challengeRecord.sessionId) headHash = (await this.db.getSessionRecordBySessionId(challengeRecord.sessionId))?.decryptedHeadHash;
|
|
362
|
+
const coordsString = coords ? JSON.stringify(coords) : void 0;
|
|
363
|
+
const ipAddressRecord = require_compositeIpAddress.getIpAddressFromComposite(challengeRecord.ipAddress);
|
|
364
|
+
const userScope = require_blacklistRequestInspector.getRequestUserScope(headers, challengeRecord.ja4, ipAddressRecord.address, userAccount, headHash, coordsString, countryCode, asn);
|
|
365
|
+
return findHardBlockPolicy(await this.getPrioritisedAccessPolicies(userAccessRulesStorage, challengeRecord.dappAccount, userScope, { blockOnly: true }));
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Checks if the provided email address has a domain in the spam email domain list.
|
|
369
|
+
* Returns true if the email domain is spam (i.e. should be blocked), false otherwise.
|
|
370
|
+
* Handles formats: user@domain.com, @domain.com, domain.com
|
|
371
|
+
*/
|
|
372
|
+
async checkSpamEmail(email) {
|
|
373
|
+
return require_checkSpamEmail.checkSpamEmail(email, this.db, this.config, this.logger);
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Resolves the IP info to feed to `checkTrafficFilter` and runs the check.
|
|
377
|
+
*
|
|
378
|
+
* - The captcha record already carries the IPInfoResponse from request
|
|
379
|
+
* time (ipInfoMiddleware → storeXxxRecord), so by default we reuse it
|
|
380
|
+
* instead of hitting the sidecar again.
|
|
381
|
+
* - If the dapp's server passed up the end user's current IP via the
|
|
382
|
+
* verify call, look that up fresh — it's the "now" IP for filtering
|
|
383
|
+
* and may differ from the IP that originally requested the captcha.
|
|
384
|
+
* - When the session carries a `dnsEvent`, its `peerIp` and `resolverIp`
|
|
385
|
+
* are enriched and passed alongside the primary IP.
|
|
386
|
+
* - `blockAbuser` defaults to true so abusive networks are always
|
|
387
|
+
* blocked even when the site hasn't configured a trafficFilter.
|
|
388
|
+
* - Returns `{ isBlocked: false }` if every filter flag is off, without
|
|
389
|
+
* consulting the payload at all.
|
|
390
|
+
*
|
|
391
|
+
* Callers handle the "blocked" branch themselves (each verify path
|
|
392
|
+
* updates record / session state differently); this helper just returns
|
|
393
|
+
* the verdict.
|
|
394
|
+
*/
|
|
395
|
+
async resolveTrafficFilterCheck(env, recordIpInfo, trafficFilter, currentIp, enrichedDnsEvent) {
|
|
396
|
+
const effective = {
|
|
397
|
+
blockAbuser: true,
|
|
398
|
+
...trafficFilter
|
|
399
|
+
};
|
|
400
|
+
if (!Object.values(effective).some((v) => v)) return { isBlocked: false };
|
|
401
|
+
const ipInfo = currentIp ? await env.ipInfoService.lookup(currentIp) : recordIpInfo;
|
|
402
|
+
return require_checkTrafficFilter.checkTrafficFilter(ipInfo, effective, require_enrichDnsEvent.extraIpInfosFromEnrichedDnsEvent(enrichedDnsEvent), enrichedDnsEvent?.pathValid);
|
|
403
|
+
}
|
|
404
|
+
static canClientSeeScore(tier, score) {
|
|
405
|
+
return score && tier && tier !== _prosopo_types.Tier.Free;
|
|
406
|
+
}
|
|
407
|
+
};
|
|
408
|
+
//#endregion
|
|
488
409
|
exports.CaptchaManager = CaptchaManager;
|