@prosopo/provider 5.6.2 → 5.6.4
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 +16 -13
- package/.turbo/turbo-build$colon$tsc.log +25 -25
- package/.turbo/turbo-build.log +17 -14
- package/CHANGELOG.md +92 -0
- package/dist/api/captcha/contextAwareValidation.d.ts +3 -2
- package/dist/api/captcha/contextAwareValidation.d.ts.map +1 -1
- package/dist/api/captcha/contextAwareValidation.js +32 -13
- package/dist/api/captcha/contextAwareValidation.js.map +1 -1
- package/dist/api/captcha/getFrictionlessCaptchaChallenge/accessPolicy.d.ts.map +1 -1
- package/dist/api/captcha/getFrictionlessCaptchaChallenge/accessPolicy.js +8 -5
- package/dist/api/captcha/getFrictionlessCaptchaChallenge/accessPolicy.js.map +1 -1
- package/dist/api/captcha/getFrictionlessCaptchaChallenge/decisionMachine.d.ts.map +1 -1
- package/dist/api/captcha/getFrictionlessCaptchaChallenge/decisionMachine.js +14 -20
- package/dist/api/captcha/getFrictionlessCaptchaChallenge/decisionMachine.js.map +1 -1
- package/dist/api/captcha/getFrictionlessCaptchaChallenge/handler.d.ts.map +1 -1
- package/dist/api/captcha/getFrictionlessCaptchaChallenge/handler.js +3 -0
- package/dist/api/captcha/getFrictionlessCaptchaChallenge/handler.js.map +1 -1
- package/dist/api/captcha/getFrictionlessCaptchaChallenge/shortCircuit.d.ts.map +1 -1
- package/dist/api/captcha/getFrictionlessCaptchaChallenge/shortCircuit.js +2 -2
- package/dist/api/captcha/getFrictionlessCaptchaChallenge/shortCircuit.js.map +1 -1
- package/dist/api/captcha/getImageCaptchaChallenge.d.ts.map +1 -1
- package/dist/api/captcha/getImageCaptchaChallenge.js +2 -3
- package/dist/api/captcha/getImageCaptchaChallenge.js.map +1 -1
- package/dist/api/captcha/submitPoWCaptchaSolution.d.ts +4 -1
- package/dist/api/captcha/submitPoWCaptchaSolution.d.ts.map +1 -1
- package/dist/api/captcha/submitPoWCaptchaSolution.js +23 -6
- package/dist/api/captcha/submitPoWCaptchaSolution.js.map +1 -1
- package/dist/api/captcha/trafficFilterRequestTime.d.ts.map +1 -1
- package/dist/api/captcha/trafficFilterRequestTime.js +2 -2
- package/dist/api/captcha/trafficFilterRequestTime.js.map +1 -1
- package/dist/cjs/api/captcha/contextAwareValidation.cjs +31 -11
- package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge/accessPolicy.cjs +7 -4
- package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge/decisionMachine.cjs +12 -18
- package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge/handler.cjs +3 -0
- package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge/shortCircuit.cjs +1 -1
- package/dist/cjs/api/captcha/getImageCaptchaChallenge.cjs +1 -1
- package/dist/cjs/api/captcha/submitPoWCaptchaSolution.cjs +22 -5
- package/dist/cjs/api/captcha/trafficFilterRequestTime.cjs +1 -1
- package/dist/cjs/tasks/captchaTypeSelection.cjs +53 -0
- package/dist/cjs/tasks/frictionless/frictionlessTasks.cjs +19 -8
- package/dist/cjs/tasks/puzzle/puzzleDifficulty.cjs +208 -0
- package/dist/cjs/tasks/puzzle/puzzleRenderer.cjs +13 -61
- package/dist/cjs/tasks/puzzle/stratifiedSampler.cjs +61 -0
- package/dist/tasks/captchaTypeSelection.d.ts +5 -0
- package/dist/tasks/captchaTypeSelection.d.ts.map +1 -0
- package/dist/tasks/captchaTypeSelection.js +53 -0
- package/dist/tasks/captchaTypeSelection.js.map +1 -0
- package/dist/tasks/frictionless/frictionlessTasks.d.ts.map +1 -1
- package/dist/tasks/frictionless/frictionlessTasks.js +20 -9
- package/dist/tasks/frictionless/frictionlessTasks.js.map +1 -1
- package/dist/tasks/frictionless/routingMachine.d.ts +4 -1
- package/dist/tasks/frictionless/routingMachine.d.ts.map +1 -1
- package/dist/tasks/frictionless/routingMachine.js.map +1 -1
- package/dist/tasks/puzzle/puzzleDifficulty.d.ts +25 -0
- package/dist/tasks/puzzle/puzzleDifficulty.d.ts.map +1 -0
- package/dist/tasks/puzzle/puzzleDifficulty.js +204 -0
- package/dist/tasks/puzzle/puzzleDifficulty.js.map +1 -0
- package/dist/tasks/puzzle/puzzleRenderer.d.ts +1 -3
- package/dist/tasks/puzzle/puzzleRenderer.d.ts.map +1 -1
- package/dist/tasks/puzzle/puzzleRenderer.js +15 -62
- package/dist/tasks/puzzle/puzzleRenderer.js.map +1 -1
- package/dist/tasks/puzzle/stratifiedSampler.d.ts +7 -0
- package/dist/tasks/puzzle/stratifiedSampler.d.ts.map +1 -0
- package/dist/tasks/puzzle/stratifiedSampler.js +61 -0
- package/dist/tasks/puzzle/stratifiedSampler.js.map +1 -0
- package/dist/tests/integration/api/admin/apiRegisterSiteKeyEndpoint.integration.test.js +4 -0
- package/dist/tests/integration/api/admin/apiRegisterSiteKeyEndpoint.integration.test.js.map +1 -1
- package/dist/tests/integration/clientSettingsPersistence.integration.test.js +3 -0
- package/dist/tests/integration/clientSettingsPersistence.integration.test.js.map +1 -1
- package/dist/tests/unit/api/admin/apiRegisterSiteKeyEndpoint.unit.test.js +2 -0
- package/dist/tests/unit/api/admin/apiRegisterSiteKeyEndpoint.unit.test.js.map +1 -1
- package/dist/tests/unit/api/captcha/submitPoWCaptchaSolution.escalation.unit.test.js +3 -0
- package/dist/tests/unit/api/captcha/submitPoWCaptchaSolution.escalation.unit.test.js.map +1 -1
- package/dist/tests/unit/api/contextAwareValidation.unit.test.js +88 -67
- package/dist/tests/unit/api/contextAwareValidation.unit.test.js.map +1 -1
- package/dist/tests/unit/api/getFrictionlessCaptchaChallenge.unit.test.js +55 -105
- package/dist/tests/unit/api/getFrictionlessCaptchaChallenge.unit.test.js.map +1 -1
- package/dist/tests/unit/tasks/captchaManager.unit.test.js +2 -0
- package/dist/tests/unit/tasks/captchaManager.unit.test.js.map +1 -1
- package/dist/tests/unit/tasks/captchaTypeSelection.unit.test.d.ts +2 -0
- package/dist/tests/unit/tasks/captchaTypeSelection.unit.test.d.ts.map +1 -0
- package/dist/tests/unit/tasks/captchaTypeSelection.unit.test.js +54 -0
- package/dist/tests/unit/tasks/captchaTypeSelection.unit.test.js.map +1 -0
- package/dist/tests/unit/tasks/frictionless/routerImageRoundsClamp.unit.test.js +24 -5
- package/dist/tests/unit/tasks/frictionless/routerImageRoundsClamp.unit.test.js.map +1 -1
- package/dist/tests/unit/tasks/puzzleDifficulty.unit.test.d.ts +2 -0
- package/dist/tests/unit/tasks/puzzleDifficulty.unit.test.d.ts.map +1 -0
- package/dist/tests/unit/tasks/puzzleDifficulty.unit.test.js +137 -0
- package/dist/tests/unit/tasks/puzzleDifficulty.unit.test.js.map +1 -0
- package/package.json +13 -13
|
@@ -1,27 +1,47 @@
|
|
|
1
1
|
let _prosopo_types = require("@prosopo/types");
|
|
2
2
|
//#region src/api/captcha/contextAwareValidation.ts
|
|
3
3
|
/**
|
|
4
|
-
* Determines the context
|
|
4
|
+
* Determines the context a request belongs to — its device family crossed
|
|
5
|
+
* with whether it is running in a webview.
|
|
6
|
+
*
|
|
7
|
+
* `userAgent` is the raw header value, not the hashed one carried on the
|
|
8
|
+
* decrypted payload. The entropy sweep classifies stored sessions from the
|
|
9
|
+
* same header through the same function, so the two sides agree on which
|
|
10
|
+
* bucket a session lands in.
|
|
11
|
+
*
|
|
12
|
+
* @param userAgent - Raw `user-agent` request header
|
|
5
13
|
* @param webView - Whether the request is from a WebView
|
|
6
|
-
* @returns The context type (Webview or Default)
|
|
7
14
|
*/
|
|
8
|
-
function determineContextType(webView) {
|
|
9
|
-
return
|
|
15
|
+
function determineContextType(userAgent, webView) {
|
|
16
|
+
return (0, _prosopo_types.contextTypeFromSession)(userAgent, webView);
|
|
10
17
|
}
|
|
11
18
|
/**
|
|
12
|
-
*
|
|
19
|
+
* Whether the client has this context configured.
|
|
20
|
+
*
|
|
21
|
+
* A context the customer has not enabled is not validated at all — the
|
|
22
|
+
* request passes through this stage untouched. That is a change from the
|
|
23
|
+
* pre-device-type behaviour, where configuring a single context validated
|
|
24
|
+
* *every* request against it: with six contexts, applying a tablet baseline
|
|
25
|
+
* to desktop traffic would reject real users wholesale.
|
|
26
|
+
*/
|
|
27
|
+
function isContextConfigured(settings, contextType) {
|
|
28
|
+
return (0, _prosopo_types.expandContexts)(settings.contextAware?.contexts)[contextType] !== void 0;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Gets the threshold for a specific context type from client settings.
|
|
32
|
+
*
|
|
33
|
+
* Falls back to the global default when the context is unconfigured, so a
|
|
34
|
+
* caller that skipped `isContextConfigured` still gets a sane number rather
|
|
35
|
+
* than NaN.
|
|
36
|
+
*
|
|
13
37
|
* @param settings - Client settings
|
|
14
38
|
* @param contextType - The context type to get the threshold for
|
|
15
39
|
* @returns The threshold for the context type, or the global threshold if not configured
|
|
16
40
|
*/
|
|
17
41
|
function getContextThreshold(settings, contextType) {
|
|
18
|
-
|
|
19
|
-
if (contextAware === void 0) return _prosopo_types.contextAwareThresholdDefault;
|
|
20
|
-
const contexts = contextAware.contexts;
|
|
21
|
-
let contextConfig;
|
|
22
|
-
if (contexts !== void 0) contextConfig = contexts[contextType];
|
|
23
|
-
return contextConfig?.threshold ?? _prosopo_types.contextAwareThresholdDefault;
|
|
42
|
+
return (0, _prosopo_types.expandContexts)(settings.contextAware?.contexts)[contextType]?.threshold ?? _prosopo_types.contextAwareThresholdDefault;
|
|
24
43
|
}
|
|
25
44
|
//#endregion
|
|
26
45
|
exports.determineContextType = determineContextType;
|
|
27
46
|
exports.getContextThreshold = getContextThreshold;
|
|
47
|
+
exports.isContextConfigured = isContextConfigured;
|
|
@@ -31,7 +31,7 @@ var handleAccessPolicy = async (input, res) => {
|
|
|
31
31
|
}
|
|
32
32
|
}));
|
|
33
33
|
await tasks.frictionlessManager.registerBlockedSession({
|
|
34
|
-
solvedImagesCount: clientRecord.settings.
|
|
34
|
+
solvedImagesCount: (0, _prosopo_types.resolveImageRoundsBounds)(clientRecord.settings).max,
|
|
35
35
|
userSitekeyIpHash: input.userSitekeyIpHash,
|
|
36
36
|
reason: _prosopo_types.FrictionlessReason.ACCESS_POLICY_BLOCK,
|
|
37
37
|
siteKey: input.dapp,
|
|
@@ -55,7 +55,7 @@ var handleAccessPolicy = async (input, res) => {
|
|
|
55
55
|
}
|
|
56
56
|
}));
|
|
57
57
|
await tasks.frictionlessManager.registerBlockedSession({
|
|
58
|
-
solvedImagesCount: clientRecord.settings.
|
|
58
|
+
solvedImagesCount: (0, _prosopo_types.resolveImageRoundsBounds)(clientRecord.settings).max,
|
|
59
59
|
userSitekeyIpHash: input.userSitekeyIpHash,
|
|
60
60
|
reason: _prosopo_types.FrictionlessReason.AUTO_BAN_SCORE,
|
|
61
61
|
siteKey: input.dapp,
|
|
@@ -87,7 +87,7 @@ var handleAccessPolicy = async (input, res) => {
|
|
|
87
87
|
handled: true,
|
|
88
88
|
response: res.json(await tasks.frictionlessManager.sendImageCaptcha({
|
|
89
89
|
...captchaTypeBaseParams,
|
|
90
|
-
solvedImagesCount: userAccessPolicy.solvedImagesCount ?
|
|
90
|
+
solvedImagesCount: userAccessPolicy.solvedImagesCount ? (0, _prosopo_types.clampImageRounds)(userAccessPolicy.solvedImagesCount, clientRecord.settings) : (0, _prosopo_types.resolveImageRoundsBounds)(clientRecord.settings).max
|
|
91
91
|
}))
|
|
92
92
|
};
|
|
93
93
|
}
|
|
@@ -116,7 +116,10 @@ var handleAccessPolicy = async (input, res) => {
|
|
|
116
116
|
require_honeypotResponse.attachHoneypot(res, clientRecord);
|
|
117
117
|
return {
|
|
118
118
|
handled: true,
|
|
119
|
-
response: res.json(await tasks.frictionlessManager.sendPuzzleCaptcha(
|
|
119
|
+
response: res.json(await tasks.frictionlessManager.sendPuzzleCaptcha({
|
|
120
|
+
...captchaTypeBaseParams,
|
|
121
|
+
...userAccessPolicy.solvedImagesCount !== void 0 && { solvedImagesCount: userAccessPolicy.solvedImagesCount }
|
|
122
|
+
}))
|
|
120
123
|
};
|
|
121
124
|
}
|
|
122
125
|
return {
|
|
@@ -24,7 +24,7 @@ var runDecisionMachine = async (input, handle) => {
|
|
|
24
24
|
require_metrics.recordFrictionlessDecision("missing_token");
|
|
25
25
|
require_honeypotResponse.attachHoneypot(res, clientRecord);
|
|
26
26
|
return res.json(await tasks.frictionlessManager.sendImageCaptcha({
|
|
27
|
-
solvedImagesCount:
|
|
27
|
+
solvedImagesCount: (0, _prosopo_types.clampImageRounds)(3, clientRecord.settings),
|
|
28
28
|
userSitekeyIpHash,
|
|
29
29
|
reason: _prosopo_types.FrictionlessReason.MISSING_TOKEN,
|
|
30
30
|
siteKey: dapp,
|
|
@@ -43,7 +43,7 @@ var runDecisionMachine = async (input, handle) => {
|
|
|
43
43
|
require_metrics.recordFrictionlessDecision("missing_head_hash");
|
|
44
44
|
require_honeypotResponse.attachHoneypot(res, clientRecord);
|
|
45
45
|
return res.json(await tasks.frictionlessManager.sendImageCaptcha({
|
|
46
|
-
solvedImagesCount:
|
|
46
|
+
solvedImagesCount: (0, _prosopo_types.clampImageRounds)(2, clientRecord.settings),
|
|
47
47
|
userSitekeyIpHash,
|
|
48
48
|
reason: _prosopo_types.FrictionlessReason.MISSING_HEAD_HASH,
|
|
49
49
|
siteKey: dapp,
|
|
@@ -62,7 +62,7 @@ var runDecisionMachine = async (input, handle) => {
|
|
|
62
62
|
require_metrics.recordFrictionlessDecision("decryption_failed");
|
|
63
63
|
require_honeypotResponse.attachHoneypot(res, clientRecord);
|
|
64
64
|
return res.json(await tasks.frictionlessManager.sendImageCaptcha({
|
|
65
|
-
solvedImagesCount:
|
|
65
|
+
solvedImagesCount: (0, _prosopo_types.clampImageRounds)(3, clientRecord.settings),
|
|
66
66
|
userSitekeyIpHash,
|
|
67
67
|
reason: _prosopo_types.FrictionlessReason.DECRYPTION_FAILED,
|
|
68
68
|
siteKey: dapp,
|
|
@@ -102,7 +102,7 @@ var runDecisionMachine = async (input, handle) => {
|
|
|
102
102
|
}));
|
|
103
103
|
require_metrics.recordFrictionlessDecision("auto_ban_score");
|
|
104
104
|
await tasks.frictionlessManager.registerBlockedSession({
|
|
105
|
-
solvedImagesCount: clientRecord.settings.
|
|
105
|
+
solvedImagesCount: (0, _prosopo_types.resolveImageRoundsBounds)(clientRecord.settings).max,
|
|
106
106
|
userSitekeyIpHash,
|
|
107
107
|
reason: _prosopo_types.FrictionlessReason.AUTO_BAN_SCORE,
|
|
108
108
|
siteKey: dapp,
|
|
@@ -122,7 +122,7 @@ var runDecisionMachine = async (input, handle) => {
|
|
|
122
122
|
require_metrics.recordFrictionlessDecision("webview_detected");
|
|
123
123
|
require_honeypotResponse.attachHoneypot(res, clientRecord);
|
|
124
124
|
return res.json(await tasks.frictionlessManager.sendImageCaptcha({
|
|
125
|
-
solvedImagesCount:
|
|
125
|
+
solvedImagesCount: (0, _prosopo_types.clampImageRounds)(env.config.captchas.solved.count * 2, clientRecord.settings),
|
|
126
126
|
userSitekeyIpHash,
|
|
127
127
|
reason: _prosopo_types.FrictionlessReason.WEBVIEW_DETECTED,
|
|
128
128
|
siteKey: dapp,
|
|
@@ -141,7 +141,7 @@ var runDecisionMachine = async (input, handle) => {
|
|
|
141
141
|
require_metrics.recordFrictionlessDecision("timestamp_too_old");
|
|
142
142
|
require_honeypotResponse.attachHoneypot(res, clientRecord);
|
|
143
143
|
return res.json(await tasks.frictionlessManager.sendImageCaptcha({
|
|
144
|
-
solvedImagesCount: require_frictionlessTasksUtils.timestampDecayFunction(input.timestamp, clientRecord.settings.imageMaxRounds),
|
|
144
|
+
solvedImagesCount: (0, _prosopo_types.clampImageRounds)(require_frictionlessTasksUtils.timestampDecayFunction(input.timestamp, clientRecord.settings.imageMaxRounds), clientRecord.settings),
|
|
145
145
|
userSitekeyIpHash,
|
|
146
146
|
reason: _prosopo_types.FrictionlessReason.OLD_TIMESTAMP,
|
|
147
147
|
siteKey: dapp,
|
|
@@ -149,7 +149,7 @@ var runDecisionMachine = async (input, handle) => {
|
|
|
149
149
|
headers: flatHeaders
|
|
150
150
|
}));
|
|
151
151
|
}
|
|
152
|
-
const scoredImageRounds =
|
|
152
|
+
const scoredImageRounds = (0, _prosopo_types.clampImageRounds)(require_constants.getRoundsFromTriggeredDetectors(env.config.captchas.solved.count, input.triggeredDetectors), clientRecord.settings);
|
|
153
153
|
const botImageThreshold = input.botImageThreshold;
|
|
154
154
|
if (botImageThreshold > input.botThreshold && Number(botScore) > input.botThreshold && Number(botScore) < botImageThreshold) {
|
|
155
155
|
req.logger.info(() => ({
|
|
@@ -215,7 +215,7 @@ var runDecisionMachine = async (input, handle) => {
|
|
|
215
215
|
require_metrics.recordFrictionlessDecision("missing_current_url");
|
|
216
216
|
require_honeypotResponse.attachHoneypot(res, clientRecord);
|
|
217
217
|
return res.json(await tasks.frictionlessManager.sendImageCaptcha({
|
|
218
|
-
solvedImagesCount:
|
|
218
|
+
solvedImagesCount: (0, _prosopo_types.clampImageRounds)(env.config.captchas.solved.count, clientRecord.settings),
|
|
219
219
|
userSitekeyIpHash,
|
|
220
220
|
reason: _prosopo_types.FrictionlessReason.MISSING_CURRENT_URL,
|
|
221
221
|
siteKey: dapp,
|
|
@@ -265,7 +265,7 @@ var runUserAgentMismatchCheck = async (input, handle) => {
|
|
|
265
265
|
require_metrics.recordFrictionlessDecision("user_agent_mismatch");
|
|
266
266
|
require_honeypotResponse.attachHoneypot(res, input.clientRecord);
|
|
267
267
|
return res.json(await input.tasks.frictionlessManager.sendImageCaptcha({
|
|
268
|
-
solvedImagesCount: require_frictionlessTasksUtils.timestampDecayFunction(input.timestamp, input.clientRecord.settings.imageMaxRounds),
|
|
268
|
+
solvedImagesCount: (0, _prosopo_types.clampImageRounds)(require_frictionlessTasksUtils.timestampDecayFunction(input.timestamp, input.clientRecord.settings.imageMaxRounds), input.clientRecord.settings),
|
|
269
269
|
userSitekeyIpHash: input.userSitekeyIpHash,
|
|
270
270
|
reason: _prosopo_types.FrictionlessReason.USER_AGENT_MISMATCH,
|
|
271
271
|
siteKey: input.dapp,
|
|
@@ -277,14 +277,8 @@ var runContextAwareValidation = async (input, handle) => {
|
|
|
277
277
|
const { tasks, clientRecord, dapp, user } = input;
|
|
278
278
|
const { req, res, next } = handle;
|
|
279
279
|
if (!clientRecord.settings.contextAware?.enabled) return null;
|
|
280
|
-
const
|
|
281
|
-
|
|
282
|
-
const hasWebview = contexts[_prosopo_types.ContextType.Webview] !== void 0;
|
|
283
|
-
let contextType;
|
|
284
|
-
if (hasDefault && hasWebview) contextType = require_contextAwareValidation.determineContextType(input.webView);
|
|
285
|
-
else if (hasDefault) contextType = _prosopo_types.ContextType.Default;
|
|
286
|
-
else if (hasWebview) contextType = _prosopo_types.ContextType.Webview;
|
|
287
|
-
if (!contextType) return null;
|
|
280
|
+
const contextType = require_contextAwareValidation.determineContextType(req.headers["user-agent"], input.webView);
|
|
281
|
+
if (!require_contextAwareValidation.isContextConfigured(clientRecord.settings, contextType)) return null;
|
|
288
282
|
const clientEntropy = await tasks.frictionlessManager.getClientContextEntropy(clientRecord.account, contextType);
|
|
289
283
|
if (!clientEntropy) return null;
|
|
290
284
|
if (!input.decryptedHeadHash) {
|
|
@@ -314,7 +308,7 @@ var runContextAwareValidation = async (input, handle) => {
|
|
|
314
308
|
require_metrics.recordFrictionlessDecision("context_aware_failed");
|
|
315
309
|
require_honeypotResponse.attachHoneypot(res, clientRecord);
|
|
316
310
|
return res.json(await tasks.frictionlessManager.sendImageCaptcha({
|
|
317
|
-
solvedImagesCount:
|
|
311
|
+
solvedImagesCount: (0, _prosopo_types.clampImageRounds)(require_constants.getRoundsFromSimScore(sim), clientRecord.settings),
|
|
318
312
|
userSitekeyIpHash: input.userSitekeyIpHash,
|
|
319
313
|
reason: _prosopo_types.FrictionlessReason.CONTEXT_AWARE_VALIDATION_FAILED,
|
|
320
314
|
siteKey: dapp,
|
|
@@ -316,6 +316,9 @@ var handler_default = (env, userAccessRulesStorage) => async (req, res, next) =>
|
|
|
316
316
|
countryCode,
|
|
317
317
|
score: botScore,
|
|
318
318
|
imageMaxRounds: clientRecord.settings.imageMaxRounds,
|
|
319
|
+
imageMinRounds: clientRecord.settings.imageMinRounds,
|
|
320
|
+
frictionlessTypes: clientRecord.settings.frictionlessTypes,
|
|
321
|
+
baseImageRounds: env.config.captchas.solved.count,
|
|
319
322
|
platform: require_devicePlatform.derivePlatform(safeUserAgent, webView, { ...typeof ipInfoMobile === "boolean" && { isMobile: ipInfoMobile } }),
|
|
320
323
|
raw: {
|
|
321
324
|
headers: flatHeaders,
|
|
@@ -64,7 +64,7 @@ var runConfiguredCaptchaTypeShortCircuit = async (input, res) => {
|
|
|
64
64
|
switch (configuredType) {
|
|
65
65
|
case _prosopo_types.CaptchaType.image: return res.json(await input.tasks.frictionlessManager.sendImageCaptcha({
|
|
66
66
|
...sessionParams,
|
|
67
|
-
solvedImagesCount:
|
|
67
|
+
solvedImagesCount: (0, _prosopo_types.clampImageRounds)(input.env.config.captchas.solved.count, input.clientRecord.settings)
|
|
68
68
|
}));
|
|
69
69
|
case _prosopo_types.CaptchaType.pow: return res.json(await input.tasks.frictionlessManager.sendPowCaptcha(sessionParams));
|
|
70
70
|
case _prosopo_types.CaptchaType.puzzle: return res.json(await input.tasks.frictionlessManager.sendPuzzleCaptcha(sessionParams));
|
|
@@ -101,7 +101,7 @@ var getImageCaptchaChallenge_default = (env, userAccessRulesStorage) => async (r
|
|
|
101
101
|
const trafficVerdict = require_trafficFilterRequestTime.applyTrafficFilterAtRequestTime(req.ipInfo, clientRecord.settings?.trafficFilter, req.logger);
|
|
102
102
|
const trafficSolvedImagesCount = trafficVerdict.kind === "challenge" ? trafficVerdict.solvedImagesCount : void 0;
|
|
103
103
|
const captchaConfig = {
|
|
104
|
-
solved: { count:
|
|
104
|
+
solved: { count: (0, _prosopo_types.clampImageRounds)(trafficSolvedImagesCount || solvedImagesCount || userAccessPolicy?.solvedImagesCount || deferredParamsPolicy?.solvedImagesCount || env.config.captchas.solved.count, clientRecord.settings) },
|
|
105
105
|
unsolved: { count: userAccessPolicy?.unsolvedImagesCount || deferredParamsPolicy?.unsolvedImagesCount || env.config.captchas.unsolved.count }
|
|
106
106
|
};
|
|
107
107
|
if (validSessionId && simdReadings) await tasks.frictionlessManager.decryptAndAttachSimdReadingsIfAbsent(validSessionId, simdReadings, _prosopo_types.SimdReadingsStage.challenge).catch((updateErr) => {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const require_validateAddress = require("../validateAddress.cjs");
|
|
2
|
-
const
|
|
2
|
+
const require_puzzleDifficulty = require("../../tasks/puzzle/puzzleDifficulty.cjs");
|
|
3
|
+
const require_captchaTypeSelection = require("../../tasks/captchaTypeSelection.cjs");
|
|
3
4
|
const require_devicePlatform = require("../../utils/devicePlatform.cjs");
|
|
4
5
|
const require_tasks = require("../../tasks/tasks.cjs");
|
|
5
6
|
const require_apiToggleMaintenanceModeEndpoint = require("../admin/apiToggleMaintenanceModeEndpoint.cjs");
|
|
@@ -8,6 +9,7 @@ const require_rawTlsSignalsMiddleware = require("../rawTlsSignalsMiddleware.cjs"
|
|
|
8
9
|
let _prosopo_common = require("@prosopo/common");
|
|
9
10
|
let _prosopo_types = require("@prosopo/types");
|
|
10
11
|
let _prosopo_util = require("@prosopo/util");
|
|
12
|
+
let _prosopo_puzzle_assets = require("@prosopo/puzzle-assets");
|
|
11
13
|
//#region src/api/captcha/submitPoWCaptchaSolution.ts
|
|
12
14
|
var submitPoWCaptchaSolution_default = (env) => async (req, res, next) => {
|
|
13
15
|
if (require_apiToggleMaintenanceModeEndpoint.getMaintenanceMode()) {
|
|
@@ -95,6 +97,9 @@ var submitPoWCaptchaSolution_default = (env) => async (req, res, next) => {
|
|
|
95
97
|
tcpToChelloUs: req.tcpToChelloUs,
|
|
96
98
|
chelloToHandshakeUs: req.chelloToHandshakeUs,
|
|
97
99
|
...require_rawTlsSignalsMiddleware.rawTlsSignalsForSession(req)
|
|
100
|
+
}, {
|
|
101
|
+
frictionlessTypes: clientRecord.settings?.frictionlessTypes,
|
|
102
|
+
imageMaxRounds: clientRecord.settings?.imageMaxRounds
|
|
98
103
|
});
|
|
99
104
|
const response = {
|
|
100
105
|
status: "ok",
|
|
@@ -128,7 +133,7 @@ var submitPoWCaptchaSolution_default = (env) => async (req, res, next) => {
|
|
|
128
133
|
* Exported for unit testing only — the handler above is the production entry
|
|
129
134
|
* point.
|
|
130
135
|
*/
|
|
131
|
-
var buildEscalation = async (tasks, result, challenge, perConnectionSignals) => {
|
|
136
|
+
var buildEscalation = async (tasks, result, challenge, perConnectionSignals, siteConstraints) => {
|
|
132
137
|
if (!result.verified || !result.routingOutput) return void 0;
|
|
133
138
|
const routedType = result.routingOutput.captchaType;
|
|
134
139
|
if (routedType !== _prosopo_types.CaptchaType.image && routedType !== _prosopo_types.CaptchaType.puzzle) return;
|
|
@@ -137,9 +142,21 @@ var buildEscalation = async (tasks, result, challenge, perConnectionSignals) =>
|
|
|
137
142
|
const originSession = await tasks.db.getSessionRecordBySessionId(powRecord.sessionId);
|
|
138
143
|
if (!originSession) return void 0;
|
|
139
144
|
const routed = result.routingOutput;
|
|
140
|
-
const escalatedType =
|
|
145
|
+
const escalatedType = require_captchaTypeSelection.coerceToEnabledCaptchaType(routed.captchaType, siteConstraints?.frictionlessTypes);
|
|
146
|
+
if (escalatedType === _prosopo_types.CaptchaType.pow) return void 0;
|
|
147
|
+
const imageMaxRounds = siteConstraints?.imageMaxRounds ?? _prosopo_types.imageMaxRoundsDefault;
|
|
148
|
+
const escalationPuzzleOverrides = (() => {
|
|
149
|
+
if (escalatedType !== _prosopo_types.CaptchaType.puzzle) return void 0;
|
|
150
|
+
const level = require_puzzleDifficulty.severityToPuzzleDifficulty(routed.solvedImagesCount ?? originSession.solvedImagesCount, tasks.config.captchas.solved.count);
|
|
151
|
+
if (level === 0) return void 0;
|
|
152
|
+
const difficulty = require_puzzleDifficulty.samplePuzzleDifficulty(level, _prosopo_puzzle_assets.DEFAULT_RENDER_SETTINGS.holeDarken);
|
|
153
|
+
return {
|
|
154
|
+
puzzleTolerance: difficulty.tolerance,
|
|
155
|
+
puzzle: difficulty.puzzle
|
|
156
|
+
};
|
|
157
|
+
})();
|
|
141
158
|
const selectionReason = routed.reason ?? originSession.reason;
|
|
142
|
-
const newSession = await tasks.frictionlessManager.createSession(originSession.token, originSession.score, originSession.threshold, originSession.scoreComponents, originSession.ipAddress, escalatedType, originSession.siteKey ?? powRecord.dappAccount, escalatedType === _prosopo_types.CaptchaType.image ? routed.solvedImagesCount ?? originSession.solvedImagesCount : void 0, void 0, originSession.userSitekeyIpHash, originSession.webView, originSession.iFrame, originSession.decryptedHeadHash, selectionReason, void 0, void 0, originSession.ipInfo, originSession.headers, originSession.mode, originSession.simdReadings, originSession.entropyMathRandomFingerprint, originSession.entropyCryptoFingerprint, originSession.entropyWallClockOffsetMs, originSession.entropyMathRandomFirst, originSession.bundleId, originSession.currentUrl, perConnectionSignals?.tcpToChelloUs, perConnectionSignals?.chelloToHandshakeUs, true, originSession.iframeUrl, originSession.isProtect, originSession.sessionId, originSession.g, void 0, originSession.i, originSession.sw, originSession.md, originSession.bn, originSession.fs, perConnectionSignals && {
|
|
159
|
+
const newSession = await tasks.frictionlessManager.createSession(originSession.token, originSession.score, originSession.threshold, originSession.scoreComponents, originSession.ipAddress, escalatedType, originSession.siteKey ?? powRecord.dappAccount, escalatedType === _prosopo_types.CaptchaType.image ? Math.min(routed.solvedImagesCount ?? originSession.solvedImagesCount ?? tasks.config.captchas.solved.count, imageMaxRounds) : void 0, void 0, originSession.userSitekeyIpHash, originSession.webView, originSession.iFrame, originSession.decryptedHeadHash, selectionReason, void 0, void 0, originSession.ipInfo, originSession.headers, originSession.mode, originSession.simdReadings, originSession.entropyMathRandomFingerprint, originSession.entropyCryptoFingerprint, originSession.entropyWallClockOffsetMs, originSession.entropyMathRandomFirst, originSession.bundleId, originSession.currentUrl, perConnectionSignals?.tcpToChelloUs, perConnectionSignals?.chelloToHandshakeUs, true, originSession.iframeUrl, originSession.isProtect, originSession.sessionId, originSession.g, void 0, originSession.i, originSession.sw, originSession.md, originSession.bn, originSession.fs, perConnectionSignals && {
|
|
143
160
|
synNs: perConnectionSignals.synNs,
|
|
144
161
|
synackNs: perConnectionSignals.synackNs,
|
|
145
162
|
ackNs: perConnectionSignals.ackNs,
|
|
@@ -149,7 +166,7 @@ var buildEscalation = async (tasks, result, challenge, perConnectionSignals) =>
|
|
|
149
166
|
tcpOptsFlags: perConnectionSignals.tcpOptsFlags,
|
|
150
167
|
tcpOptsOrder: perConnectionSignals.tcpOptsOrder,
|
|
151
168
|
tcpWindow: perConnectionSignals.tcpWindow
|
|
152
|
-
});
|
|
169
|
+
}, escalationPuzzleOverrides);
|
|
153
170
|
if (tasks.writeQueue) await tasks.writeQueue.cacheSessionEscalation(powRecord.sessionId, newSession.sessionId);
|
|
154
171
|
return {
|
|
155
172
|
captchaType: escalatedType,
|
|
@@ -72,7 +72,7 @@ var handleFrictionlessTrafficFilter = async (input, res) => {
|
|
|
72
72
|
logDispatch(_prosopo_types.CaptchaType.image);
|
|
73
73
|
response = await input.frictionlessManager.sendImageCaptcha({
|
|
74
74
|
...baseParams,
|
|
75
|
-
solvedImagesCount: verdict.solvedImagesCount ?
|
|
75
|
+
solvedImagesCount: verdict.solvedImagesCount ? (0, _prosopo_types.clampImageRounds)(verdict.solvedImagesCount, input.clientRecord.settings) : (0, _prosopo_types.resolveImageRoundsBounds)(input.clientRecord.settings).max
|
|
76
76
|
});
|
|
77
77
|
} else if (verdict.captchaType === _prosopo_types.CaptchaType.pow) {
|
|
78
78
|
logDispatch(_prosopo_types.CaptchaType.pow);
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
const require_puzzleRenderer = require("./puzzle/puzzleRenderer.cjs");
|
|
2
|
+
let _prosopo_types = require("@prosopo/types");
|
|
3
|
+
//#region src/tasks/captchaTypeSelection.ts
|
|
4
|
+
/**
|
|
5
|
+
* Resolve a requested captcha type against what the site permits and what
|
|
6
|
+
* this provider can render.
|
|
7
|
+
*
|
|
8
|
+
* Call this at every point a session's captchaType is DECIDED, never at the
|
|
9
|
+
* point one is served: `/captcha/puzzle` answers with a puzzle-shaped
|
|
10
|
+
* response and nothing else, so a session minted as a type we cannot fulfil
|
|
11
|
+
* strands the user on INCORRECT_CAPTCHA_TYPE. There are two such mint sites —
|
|
12
|
+
* `sendCaptcha` in frictionlessTasks (which every frictionless path funnels
|
|
13
|
+
* through, after the routing machine has had its say) and `buildEscalation`
|
|
14
|
+
* in submitPoWCaptchaSolution.
|
|
15
|
+
*
|
|
16
|
+
* Because it sits after routing, this covers every upstream selector
|
|
17
|
+
* transitively: the score ladder, the no-measurement gates, access-policy
|
|
18
|
+
* Restrict rules, traffic-filter category policies, routing-machine actions
|
|
19
|
+
* and detector-generated rules.
|
|
20
|
+
*
|
|
21
|
+
* Coercion only ever narrows. A disabled type falls back to the other
|
|
22
|
+
* interactive type if that is permitted, and to PoW otherwise — never the
|
|
23
|
+
* reverse, so this can't hand a user a harder challenge than was asked for.
|
|
24
|
+
*/
|
|
25
|
+
var coerceToEnabledCaptchaType = (requested, frictionlessTypes, logger) => {
|
|
26
|
+
const types = (0, _prosopo_types.resolveFrictionlessTypes)(frictionlessTypes);
|
|
27
|
+
const puzzleAllowed = types.puzzle && require_puzzleRenderer.isPuzzleRenderAvailable();
|
|
28
|
+
const imageAllowed = types.image;
|
|
29
|
+
const resolved = (() => {
|
|
30
|
+
switch (requested) {
|
|
31
|
+
case _prosopo_types.CaptchaType.pow: return _prosopo_types.CaptchaType.pow;
|
|
32
|
+
case _prosopo_types.CaptchaType.puzzle:
|
|
33
|
+
if (puzzleAllowed) return _prosopo_types.CaptchaType.puzzle;
|
|
34
|
+
return imageAllowed ? _prosopo_types.CaptchaType.image : _prosopo_types.CaptchaType.pow;
|
|
35
|
+
case _prosopo_types.CaptchaType.image:
|
|
36
|
+
if (imageAllowed) return _prosopo_types.CaptchaType.image;
|
|
37
|
+
return puzzleAllowed ? _prosopo_types.CaptchaType.puzzle : _prosopo_types.CaptchaType.pow;
|
|
38
|
+
}
|
|
39
|
+
})();
|
|
40
|
+
if (resolved !== requested) logger?.info(() => ({
|
|
41
|
+
msg: "Captcha type coerced to an enabled type",
|
|
42
|
+
data: {
|
|
43
|
+
requested,
|
|
44
|
+
served: resolved,
|
|
45
|
+
imageEnabled: types.image,
|
|
46
|
+
puzzleEnabled: types.puzzle,
|
|
47
|
+
puzzleRenderable: require_puzzleRenderer.isPuzzleRenderAvailable()
|
|
48
|
+
}
|
|
49
|
+
}));
|
|
50
|
+
return resolved;
|
|
51
|
+
};
|
|
52
|
+
//#endregion
|
|
53
|
+
exports.coerceToEnabledCaptchaType = coerceToEnabledCaptchaType;
|
|
@@ -3,10 +3,12 @@ const require_decisionMachineRunner = require("../decisionMachine/decisionMachin
|
|
|
3
3
|
const require_dnsEventUrl = require("../../api/dnsEventUrl.cjs");
|
|
4
4
|
const require_lang = require("../../rules/lang.cjs");
|
|
5
5
|
const require_captchaManager = require("../captchaManager.cjs");
|
|
6
|
+
const require_puzzleDifficulty = require("../puzzle/puzzleDifficulty.cjs");
|
|
7
|
+
const require_captchaTypeSelection = require("../captchaTypeSelection.cjs");
|
|
6
8
|
const require_getBotScore = require("../detection/getBotScore.cjs");
|
|
7
|
-
const require_puzzleRenderer = require("../puzzle/puzzleRenderer.cjs");
|
|
8
9
|
const require_routingMachine = require("./routingMachine.cjs");
|
|
9
10
|
let _prosopo_types = require("@prosopo/types");
|
|
11
|
+
let _prosopo_puzzle_assets = require("@prosopo/puzzle-assets");
|
|
10
12
|
let uuid = require("uuid");
|
|
11
13
|
//#region src/tasks/frictionless/frictionlessTasks.ts
|
|
12
14
|
var DEFAULT_MAX_TIMESTAMP_AGE = 600 * 1e3;
|
|
@@ -190,15 +192,24 @@ var FrictionlessManager = class extends require_captchaManager.CaptchaManager {
|
|
|
190
192
|
powDifficulty: captchaType === _prosopo_types.CaptchaType.pow ? effectiveParams.powDifficulty : void 0
|
|
191
193
|
};
|
|
192
194
|
const routed = this.routingContext ? await require_routingMachine.applyRouter(this.decisionMachineRunner, this.usageCounters, baseline, this.routingContext, this.logger) : baseline;
|
|
193
|
-
const finalCaptchaType =
|
|
195
|
+
const finalCaptchaType = require_captchaTypeSelection.coerceToEnabledCaptchaType(routed.captchaType, this.routingContext?.frictionlessTypes, this.logger);
|
|
194
196
|
const requestedSolvedImagesCount = routed.solvedImagesCount ?? effectiveParams.solvedImagesCount;
|
|
195
|
-
const
|
|
196
|
-
const finalSolvedImagesCount = finalCaptchaType === _prosopo_types.CaptchaType.image ? requestedSolvedImagesCount !== void 0
|
|
197
|
+
const imageRoundsBounds = this.routingContext ?? {};
|
|
198
|
+
const finalSolvedImagesCount = finalCaptchaType === _prosopo_types.CaptchaType.image ? requestedSolvedImagesCount !== void 0 ? (0, _prosopo_types.clampImageRounds)(requestedSolvedImagesCount, imageRoundsBounds) : requestedSolvedImagesCount : void 0;
|
|
197
199
|
const finalPowDifficulty = finalCaptchaType === _prosopo_types.CaptchaType.pow ? routed.powDifficulty ?? effectiveParams.powDifficulty : void 0;
|
|
198
|
-
const finalPuzzleOverrides = finalCaptchaType === _prosopo_types.CaptchaType.puzzle ? {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
200
|
+
const finalPuzzleOverrides = finalCaptchaType === _prosopo_types.CaptchaType.puzzle ? (() => {
|
|
201
|
+
const level = require_puzzleDifficulty.severityToPuzzleDifficulty(requestedSolvedImagesCount, this.routingContext?.baseImageRounds ?? this.config.captchas.solved.count);
|
|
202
|
+
const difficulty = level > 0 ? require_puzzleDifficulty.samplePuzzleDifficulty(level, _prosopo_puzzle_assets.DEFAULT_RENDER_SETTINGS.holeDarken) : void 0;
|
|
203
|
+
const tolerance = routed.puzzleTolerance ?? difficulty?.tolerance;
|
|
204
|
+
const puzzle = routed.puzzle || difficulty ? {
|
|
205
|
+
...difficulty?.puzzle ?? {},
|
|
206
|
+
...routed.puzzle ?? {}
|
|
207
|
+
} : void 0;
|
|
208
|
+
return {
|
|
209
|
+
...tolerance !== void 0 && { puzzleTolerance: tolerance },
|
|
210
|
+
...puzzle !== void 0 && { puzzle }
|
|
211
|
+
};
|
|
212
|
+
})() : {};
|
|
202
213
|
const finalReason = routed.reason ?? effectiveParams.reason;
|
|
203
214
|
const blocked = finalCaptchaType === _prosopo_types.CaptchaType.image ? effectiveParams.blocked : void 0;
|
|
204
215
|
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, finalReason, 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, effectiveParams.i, effectiveParams.sw, effectiveParams.md, effectiveParams.bn, effectiveParams.fs, {
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
const require_stratifiedSampler = require("./stratifiedSampler.cjs");
|
|
2
|
+
//#region src/tasks/puzzle/puzzleDifficulty.ts
|
|
3
|
+
/**
|
|
4
|
+
* Minimum gap that must remain between `decoyHoleDarken` and `holeDarken`.
|
|
5
|
+
*
|
|
6
|
+
* The real cutout has to stay the deepest region on the frame or a human
|
|
7
|
+
* cannot tell target from decoy at all. But pushed too far apart, a solver
|
|
8
|
+
* simply thresholds on brightness and ignores shape. The ladder walks
|
|
9
|
+
* `decoyHoleDarken` down toward `holeDarken` as difficulty rises; this floor
|
|
10
|
+
* is what stops it crossing over. Enforced by clamping every sampled value,
|
|
11
|
+
* not by trusting the band table to be authored correctly.
|
|
12
|
+
*/
|
|
13
|
+
var MIN_DECOY_HOLE_DARKEN_MARGIN = .04;
|
|
14
|
+
/**
|
|
15
|
+
* L0 reproduces the shipped defaults (decoyCount 5, decoyEdgeDarkness 20,
|
|
16
|
+
* decoyHoleDarken 0.7, tolerance 15, pieceScale 0.15-0.45) so a site that
|
|
17
|
+
* never escalates sees no change in behaviour.
|
|
18
|
+
*
|
|
19
|
+
* The numbers above L0 are STARTING POINTS, not calibrated values. They need
|
|
20
|
+
* validating against observed human first-attempt solve rate per level,
|
|
21
|
+
* segmented by device class — see the shadow-calibration note on
|
|
22
|
+
* `severityToPuzzleDifficulty`.
|
|
23
|
+
*/
|
|
24
|
+
var PUZZLE_DIFFICULTY_LEVELS = [
|
|
25
|
+
{
|
|
26
|
+
level: 0,
|
|
27
|
+
tolerance: {
|
|
28
|
+
min: 14,
|
|
29
|
+
max: 16
|
|
30
|
+
},
|
|
31
|
+
decoyCount: {
|
|
32
|
+
min: 4,
|
|
33
|
+
max: 6
|
|
34
|
+
},
|
|
35
|
+
decoyHoleDarken: {
|
|
36
|
+
min: .68,
|
|
37
|
+
max: .72
|
|
38
|
+
},
|
|
39
|
+
decoyEdgeDarkness: {
|
|
40
|
+
min: 18,
|
|
41
|
+
max: 22
|
|
42
|
+
},
|
|
43
|
+
pieceScale: {
|
|
44
|
+
min: .15,
|
|
45
|
+
max: .45
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
level: 1,
|
|
50
|
+
tolerance: {
|
|
51
|
+
min: 12,
|
|
52
|
+
max: 15
|
|
53
|
+
},
|
|
54
|
+
decoyCount: {
|
|
55
|
+
min: 6,
|
|
56
|
+
max: 10
|
|
57
|
+
},
|
|
58
|
+
decoyHoleDarken: {
|
|
59
|
+
min: .66,
|
|
60
|
+
max: .7
|
|
61
|
+
},
|
|
62
|
+
decoyEdgeDarkness: {
|
|
63
|
+
min: 20,
|
|
64
|
+
max: 24
|
|
65
|
+
},
|
|
66
|
+
pieceScale: {
|
|
67
|
+
min: .14,
|
|
68
|
+
max: .4
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
level: 2,
|
|
73
|
+
tolerance: {
|
|
74
|
+
min: 10,
|
|
75
|
+
max: 13
|
|
76
|
+
},
|
|
77
|
+
decoyCount: {
|
|
78
|
+
min: 10,
|
|
79
|
+
max: 16
|
|
80
|
+
},
|
|
81
|
+
decoyHoleDarken: {
|
|
82
|
+
min: .64,
|
|
83
|
+
max: .68
|
|
84
|
+
},
|
|
85
|
+
decoyEdgeDarkness: {
|
|
86
|
+
min: 22,
|
|
87
|
+
max: 27
|
|
88
|
+
},
|
|
89
|
+
pieceScale: {
|
|
90
|
+
min: .12,
|
|
91
|
+
max: .34
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
level: 3,
|
|
96
|
+
tolerance: {
|
|
97
|
+
min: 8,
|
|
98
|
+
max: 11
|
|
99
|
+
},
|
|
100
|
+
decoyCount: {
|
|
101
|
+
min: 16,
|
|
102
|
+
max: 26
|
|
103
|
+
},
|
|
104
|
+
decoyHoleDarken: {
|
|
105
|
+
min: .62,
|
|
106
|
+
max: .66
|
|
107
|
+
},
|
|
108
|
+
decoyEdgeDarkness: {
|
|
109
|
+
min: 25,
|
|
110
|
+
max: 30
|
|
111
|
+
},
|
|
112
|
+
pieceScale: {
|
|
113
|
+
min: .1,
|
|
114
|
+
max: .28
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
level: 4,
|
|
119
|
+
tolerance: {
|
|
120
|
+
min: 6,
|
|
121
|
+
max: 9
|
|
122
|
+
},
|
|
123
|
+
decoyCount: {
|
|
124
|
+
min: 26,
|
|
125
|
+
max: 40
|
|
126
|
+
},
|
|
127
|
+
decoyHoleDarken: {
|
|
128
|
+
min: .6,
|
|
129
|
+
max: .64
|
|
130
|
+
},
|
|
131
|
+
decoyEdgeDarkness: {
|
|
132
|
+
min: 28,
|
|
133
|
+
max: 34
|
|
134
|
+
},
|
|
135
|
+
pieceScale: {
|
|
136
|
+
min: .09,
|
|
137
|
+
max: .22
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
];
|
|
141
|
+
var toleranceSampler = require_stratifiedSampler.createStratifiedSampler();
|
|
142
|
+
var decoyCountSampler = require_stratifiedSampler.createStratifiedSampler();
|
|
143
|
+
var decoyHoleDarkenSampler = require_stratifiedSampler.createStratifiedSampler();
|
|
144
|
+
var decoyEdgeDarknessSampler = require_stratifiedSampler.createStratifiedSampler();
|
|
145
|
+
var clampDifficultyLevel = (level, maxLevel = 3) => {
|
|
146
|
+
const ceiling = Math.min(maxLevel, PUZZLE_DIFFICULTY_LEVELS.length - 1);
|
|
147
|
+
if (!Number.isFinite(level)) return 0;
|
|
148
|
+
return Math.max(0, Math.min(Math.floor(level), ceiling));
|
|
149
|
+
};
|
|
150
|
+
/**
|
|
151
|
+
* Map the severity currency every existing call-site already speaks — image
|
|
152
|
+
* rounds — onto a difficulty level.
|
|
153
|
+
*
|
|
154
|
+
* Severity is expressed as rounds ABOVE the site's ordinary count rather than
|
|
155
|
+
* an absolute, because the baseline (`env.config.captchas.solved.count`, or a
|
|
156
|
+
* rule's own `solvedImagesCount`) varies per site. "Two more rounds than
|
|
157
|
+
* normal" means the same thing everywhere; "four rounds" does not.
|
|
158
|
+
*
|
|
159
|
+
* Keeping one ordering here means the score ladder, the no-measurement gates,
|
|
160
|
+
* detector rules and traffic-filter policies cannot drift apart on what
|
|
161
|
+
* counts as more severe.
|
|
162
|
+
*/
|
|
163
|
+
var severityToPuzzleDifficulty = (requestedImageRounds, baseImageRounds, maxLevel = 3) => {
|
|
164
|
+
if (requestedImageRounds === void 0) return 0;
|
|
165
|
+
const excess = requestedImageRounds - baseImageRounds;
|
|
166
|
+
if (!Number.isFinite(excess) || excess <= 0) return clampDifficultyLevel(0, maxLevel);
|
|
167
|
+
if (excess <= 2) return clampDifficultyLevel(1, maxLevel);
|
|
168
|
+
if (excess <= 4) return clampDifficultyLevel(2, maxLevel);
|
|
169
|
+
if (excess <= 6) return clampDifficultyLevel(3, maxLevel);
|
|
170
|
+
return clampDifficultyLevel(4, maxLevel);
|
|
171
|
+
};
|
|
172
|
+
/**
|
|
173
|
+
* Draw a concrete puzzle config from a level's bands.
|
|
174
|
+
*
|
|
175
|
+
* Sampling is server-side and per-challenge, and is deliberately NOT derived
|
|
176
|
+
* from anything the client supplies (sessionId, token, ip, timestamp). A
|
|
177
|
+
* client-derived seed would let a bot replay a request to obtain the same
|
|
178
|
+
* render, or precompute one offline.
|
|
179
|
+
*
|
|
180
|
+
* `pieceScale` is passed through as a range rather than drawn here: the
|
|
181
|
+
* renderer's own stratified piece-size draw consumes it in
|
|
182
|
+
* `resolvePuzzlePieceSize`, and sampling twice would narrow the distribution.
|
|
183
|
+
*/
|
|
184
|
+
var samplePuzzleDifficulty = (level, holeDarken, maxLevel = 3) => {
|
|
185
|
+
const index = clampDifficultyLevel(level, maxLevel);
|
|
186
|
+
const band = PUZZLE_DIFFICULTY_LEVELS[index];
|
|
187
|
+
const decoyHoleDarkenFloor = holeDarken + MIN_DECOY_HOLE_DARKEN_MARGIN;
|
|
188
|
+
const sampledDecoyHoleDarken = decoyHoleDarkenSampler.sample(band.decoyHoleDarken.min, band.decoyHoleDarken.max);
|
|
189
|
+
return {
|
|
190
|
+
level: index,
|
|
191
|
+
tolerance: toleranceSampler.sampleInt(band.tolerance.min, band.tolerance.max),
|
|
192
|
+
puzzle: {
|
|
193
|
+
decoyCount: decoyCountSampler.sampleInt(band.decoyCount.min, band.decoyCount.max),
|
|
194
|
+
decoyEdgeDarkness: decoyEdgeDarknessSampler.sampleInt(band.decoyEdgeDarkness.min, band.decoyEdgeDarkness.max),
|
|
195
|
+
decoyHoleDarken: Math.min(1, Math.max(decoyHoleDarkenFloor, sampledDecoyHoleDarken)),
|
|
196
|
+
pieceScale: {
|
|
197
|
+
min: band.pieceScale.min,
|
|
198
|
+
max: band.pieceScale.max
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
};
|
|
203
|
+
//#endregion
|
|
204
|
+
exports.MIN_DECOY_HOLE_DARKEN_MARGIN = MIN_DECOY_HOLE_DARKEN_MARGIN;
|
|
205
|
+
exports.PUZZLE_DIFFICULTY_LEVELS = PUZZLE_DIFFICULTY_LEVELS;
|
|
206
|
+
exports.clampDifficultyLevel = clampDifficultyLevel;
|
|
207
|
+
exports.samplePuzzleDifficulty = samplePuzzleDifficulty;
|
|
208
|
+
exports.severityToPuzzleDifficulty = severityToPuzzleDifficulty;
|