@prosopo/provider 5.0.0 → 5.0.3
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 +15 -15
- package/.turbo/turbo-build$colon$tsc.log +24 -24
- package/.turbo/turbo-build.log +16 -16
- package/CHANGELOG.md +99 -0
- package/dist/api/captcha/assignDetectorBundle.d.ts.map +1 -1
- package/dist/api/captcha/assignDetectorBundle.js +2 -3
- package/dist/api/captcha/assignDetectorBundle.js.map +1 -1
- package/dist/api/captcha/getFrictionlessCaptchaChallenge/constants.d.ts +2 -0
- package/dist/api/captcha/getFrictionlessCaptchaChallenge/constants.d.ts.map +1 -1
- package/dist/api/captcha/getFrictionlessCaptchaChallenge/constants.js.map +1 -1
- package/dist/api/captcha/getFrictionlessCaptchaChallenge/decisionMachine.d.ts +1 -0
- package/dist/api/captcha/getFrictionlessCaptchaChallenge/decisionMachine.d.ts.map +1 -1
- package/dist/api/captcha/getFrictionlessCaptchaChallenge/decisionMachine.js +38 -0
- 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 +9 -6
- package/dist/api/captcha/getFrictionlessCaptchaChallenge/handler.js.map +1 -1
- package/dist/api/captcha/getFrictionlessCaptchaChallenge/shortCircuit.d.ts +2 -2
- package/dist/api/captcha/getFrictionlessCaptchaChallenge/shortCircuit.d.ts.map +1 -1
- package/dist/api/captcha/getFrictionlessCaptchaChallenge/shortCircuit.js +34 -37
- package/dist/api/captcha/getFrictionlessCaptchaChallenge/shortCircuit.js.map +1 -1
- package/dist/api/captcha/getFrictionlessCaptchaChallenge/shortCircuit.powFallback.test.js +11 -20
- package/dist/api/captcha/getFrictionlessCaptchaChallenge/shortCircuit.powFallback.test.js.map +1 -1
- package/dist/api/captcha/submitPoWCaptchaSolution.d.ts.map +1 -1
- package/dist/api/captcha/submitPoWCaptchaSolution.js +1 -1
- package/dist/api/captcha/submitPoWCaptchaSolution.js.map +1 -1
- package/dist/api/startProviderApi.d.ts.map +1 -1
- package/dist/api/startProviderApi.js +29 -14
- package/dist/api/startProviderApi.js.map +1 -1
- package/dist/api/verify.d.ts.map +1 -1
- package/dist/api/verify.js +1 -1
- package/dist/api/verify.js.map +1 -1
- package/dist/cjs/api/captcha/assignDetectorBundle.cjs +2 -3
- package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge/decisionMachine.cjs +38 -0
- package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge/handler.cjs +8 -5
- package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge/shortCircuit.cjs +34 -37
- package/dist/cjs/api/captcha/submitPoWCaptchaSolution.cjs +1 -1
- package/dist/cjs/api/startProviderApi.cjs +29 -14
- package/dist/cjs/api/verify.cjs +1 -1
- package/dist/cjs/tasks/captchaManager.cjs +60 -14
- package/dist/cjs/tasks/frictionless/frictionlessTasks.cjs +2 -1
- package/dist/cjs/tasks/imgCaptcha/imgCaptchaTasks.cjs +32 -2
- package/dist/cjs/tasks/powCaptcha/powTasks.cjs +32 -2
- package/dist/cjs/tasks/puzzleCaptcha/puzzleTasks.cjs +35 -3
- package/dist/cjs/tasks/spam/evaluateEmailSpamRules.cjs +23 -0
- package/dist/tasks/captchaManager.d.ts +1 -0
- package/dist/tasks/captchaManager.d.ts.map +1 -1
- package/dist/tasks/captchaManager.js +60 -14
- package/dist/tasks/captchaManager.js.map +1 -1
- package/dist/tasks/frictionless/frictionlessTasks.d.ts +1 -1
- package/dist/tasks/frictionless/frictionlessTasks.d.ts.map +1 -1
- package/dist/tasks/frictionless/frictionlessTasks.js +2 -1
- package/dist/tasks/frictionless/frictionlessTasks.js.map +1 -1
- package/dist/tasks/imgCaptcha/imgCaptchaTasks.d.ts.map +1 -1
- package/dist/tasks/imgCaptcha/imgCaptchaTasks.js +33 -3
- package/dist/tasks/imgCaptcha/imgCaptchaTasks.js.map +1 -1
- package/dist/tasks/powCaptcha/powTasks.d.ts.map +1 -1
- package/dist/tasks/powCaptcha/powTasks.js +33 -3
- package/dist/tasks/powCaptcha/powTasks.js.map +1 -1
- package/dist/tasks/puzzleCaptcha/puzzleTasks.d.ts +2 -2
- package/dist/tasks/puzzleCaptcha/puzzleTasks.d.ts.map +1 -1
- package/dist/tasks/puzzleCaptcha/puzzleTasks.js +35 -3
- package/dist/tasks/puzzleCaptcha/puzzleTasks.js.map +1 -1
- package/dist/tasks/spam/evaluateEmailSpamRules.d.ts +1 -0
- package/dist/tasks/spam/evaluateEmailSpamRules.d.ts.map +1 -1
- package/dist/tasks/spam/evaluateEmailSpamRules.js +23 -1
- package/dist/tasks/spam/evaluateEmailSpamRules.js.map +1 -1
- package/dist/tests/integration/clientSettingsPersistence.integration.test.js +23 -1
- package/dist/tests/integration/clientSettingsPersistence.integration.test.js.map +1 -1
- package/dist/tests/integration/decisionMachines.integration.test.js +3 -56
- package/dist/tests/integration/decisionMachines.integration.test.js.map +1 -1
- package/dist/tests/integration/escalationPeekBeforeConsume.integration.test.js +2 -2
- package/dist/tests/integration/escalationPeekBeforeConsume.integration.test.js.map +1 -1
- package/dist/tests/integration/imgCaptcha.integration.test.js +3 -56
- package/dist/tests/integration/imgCaptcha.integration.test.js.map +1 -1
- package/dist/tests/integration/maintenanceModeAdmin.integration.test.js +2 -1
- package/dist/tests/integration/maintenanceModeAdmin.integration.test.js.map +1 -1
- package/dist/tests/integration/powCaptcha.integration.test.js +3 -56
- package/dist/tests/integration/powCaptcha.integration.test.js.map +1 -1
- package/dist/tests/integration/routingDecisionMachines.integration.test.js +2 -2
- package/dist/tests/integration/routingDecisionMachines.integration.test.js.map +1 -1
- package/dist/tests/integration/testUtils.d.ts +1 -0
- package/dist/tests/integration/testUtils.d.ts.map +1 -1
- package/dist/tests/integration/testUtils.js +14 -0
- package/dist/tests/integration/testUtils.js.map +1 -1
- package/dist/tests/integration/testUtils.unit.test.d.ts +2 -0
- package/dist/tests/integration/testUtils.unit.test.d.ts.map +1 -0
- package/dist/tests/integration/testUtils.unit.test.js +30 -0
- package/dist/tests/integration/testUtils.unit.test.js.map +1 -0
- package/dist/tests/unit/api/captcha/getFrictionlessCaptchaChallenge/decisionMachine.unit.test.js +50 -0
- package/dist/tests/unit/api/captcha/getFrictionlessCaptchaChallenge/decisionMachine.unit.test.js.map +1 -1
- package/dist/tests/unit/api/captcha/getFrictionlessCaptchaChallenge/shortCircuit.unit.test.js +53 -1
- package/dist/tests/unit/api/captcha/getFrictionlessCaptchaChallenge/shortCircuit.unit.test.js.map +1 -1
- package/dist/tests/unit/tasks/captchaManager.unit.test.js +178 -0
- package/dist/tests/unit/tasks/captchaManager.unit.test.js.map +1 -1
- package/dist/tests/unit/tasks/imgCaptcha/imgCaptchaTasks.unit.test.js +99 -0
- package/dist/tests/unit/tasks/imgCaptcha/imgCaptchaTasks.unit.test.js.map +1 -1
- package/dist/tests/unit/tasks/powCaptcha/powTasks.unit.test.js +108 -0
- package/dist/tests/unit/tasks/powCaptcha/powTasks.unit.test.js.map +1 -1
- package/dist/tests/unit/tasks/puzzleCaptcha/puzzleTasks.unit.test.js +102 -0
- package/dist/tests/unit/tasks/puzzleCaptcha/puzzleTasks.unit.test.js.map +1 -1
- package/dist/tests/unit/tasks/spam/evaluateEmailSpamRules.unit.test.js +37 -1
- package/dist/tests/unit/tasks/spam/evaluateEmailSpamRules.unit.test.js.map +1 -1
- package/package.json +19 -19
|
@@ -13,6 +13,44 @@ var runDecisionMachine = async (input, handle) => {
|
|
|
13
13
|
const { tasks, env, clientRecord, dapp, userSitekeyIpHash, flatHeaders, ipInfo } = input;
|
|
14
14
|
const { req, res } = handle;
|
|
15
15
|
let { botScore, scoreComponents } = input;
|
|
16
|
+
if (!input.token) {
|
|
17
|
+
req.logger.info(() => ({
|
|
18
|
+
msg: "Frictionless decision",
|
|
19
|
+
data: {
|
|
20
|
+
decision: "missing_token",
|
|
21
|
+
captchaType: _prosopo_types.CaptchaType.image
|
|
22
|
+
}
|
|
23
|
+
}));
|
|
24
|
+
require_metrics.recordFrictionlessDecision("missing_token");
|
|
25
|
+
require_honeypotResponse.attachHoneypot(res, clientRecord);
|
|
26
|
+
return res.json(await tasks.frictionlessManager.sendImageCaptcha({
|
|
27
|
+
solvedImagesCount: Math.min(3, clientRecord.settings.imageMaxRounds),
|
|
28
|
+
userSitekeyIpHash,
|
|
29
|
+
reason: _prosopo_types.FrictionlessReason.MISSING_TOKEN,
|
|
30
|
+
siteKey: dapp,
|
|
31
|
+
ipInfo,
|
|
32
|
+
headers: flatHeaders
|
|
33
|
+
}));
|
|
34
|
+
}
|
|
35
|
+
if (!input.headHash) {
|
|
36
|
+
req.logger.info(() => ({
|
|
37
|
+
msg: "Frictionless decision",
|
|
38
|
+
data: {
|
|
39
|
+
decision: "missing_head_hash",
|
|
40
|
+
captchaType: _prosopo_types.CaptchaType.image
|
|
41
|
+
}
|
|
42
|
+
}));
|
|
43
|
+
require_metrics.recordFrictionlessDecision("missing_head_hash");
|
|
44
|
+
require_honeypotResponse.attachHoneypot(res, clientRecord);
|
|
45
|
+
return res.json(await tasks.frictionlessManager.sendImageCaptcha({
|
|
46
|
+
solvedImagesCount: Math.min(2, clientRecord.settings.imageMaxRounds),
|
|
47
|
+
userSitekeyIpHash,
|
|
48
|
+
reason: _prosopo_types.FrictionlessReason.MISSING_HEAD_HASH,
|
|
49
|
+
siteKey: dapp,
|
|
50
|
+
ipInfo,
|
|
51
|
+
headers: flatHeaders
|
|
52
|
+
}));
|
|
53
|
+
}
|
|
16
54
|
if (input.decryptionFailed) {
|
|
17
55
|
req.logger.info(() => ({
|
|
18
56
|
msg: "Frictionless decision",
|
|
@@ -20,6 +20,7 @@ const require_shortCircuit = require("./shortCircuit.cjs");
|
|
|
20
20
|
let _prosopo_common = require("@prosopo/common");
|
|
21
21
|
let _prosopo_types = require("@prosopo/types");
|
|
22
22
|
let _prosopo_util = require("@prosopo/util");
|
|
23
|
+
let uuid = require("uuid");
|
|
23
24
|
let _prosopo_user_access_policy = require("@prosopo/user-access-policy");
|
|
24
25
|
//#region src/api/captcha/getFrictionlessCaptchaChallenge/handler.ts
|
|
25
26
|
var handler_default = (env, userAccessRulesStorage) => async (req, res, next) => {
|
|
@@ -34,10 +35,11 @@ var handler_default = (env, userAccessRulesStorage) => async (req, res, next) =>
|
|
|
34
35
|
}
|
|
35
36
|
}));
|
|
36
37
|
});
|
|
37
|
-
const { token, headHash, dapp, user, mode, simdReadings, detectorSessionId,
|
|
38
|
+
const { token, headHash, dapp, user, mode, simdReadings, detectorSessionId, currentUrl: reportedCurrentUrl, iframeUrl: reportedIframeUrl } = _prosopo_types.GetFrictionlessCaptchaChallengeRequestBody.parse(req.body);
|
|
38
39
|
const currentUrl = (0, _prosopo_util.sanitisePageUrl)(reportedCurrentUrl);
|
|
39
40
|
const iframeUrl = (0, _prosopo_util.sanitisePageUrl)(reportedIframeUrl);
|
|
40
41
|
const isProtect = (0, _prosopo_util.isProtectDeployment)(currentUrl, iframeUrl);
|
|
42
|
+
const sessionToken = token || `notoken-${(0, uuid.v4)()}`;
|
|
41
43
|
const normalizedIp = require_normalizeRequestIp.normalizeRequestIp(req.ip, req.logger);
|
|
42
44
|
const sessionMode = mode === _prosopo_types.ModeEnum.invisible ? _prosopo_types.ModeEnum.invisible : void 0;
|
|
43
45
|
req.logger.info(() => ({
|
|
@@ -195,14 +197,14 @@ var handler_default = (env, userAccessRulesStorage) => async (req, res, next) =>
|
|
|
195
197
|
userSitekeyIpHash,
|
|
196
198
|
requestId: req.requestId,
|
|
197
199
|
logger: req.logger,
|
|
198
|
-
|
|
200
|
+
...detectorSessionId && { detectorSessionId },
|
|
199
201
|
...req.tcpToChelloUs !== void 0 && { tcpToChelloUs: req.tcpToChelloUs },
|
|
200
202
|
...req.chelloToHandshakeUs !== void 0 && { chelloToHandshakeUs: req.chelloToHandshakeUs }
|
|
201
203
|
};
|
|
202
204
|
const shortCircuitResponse = await require_shortCircuit.runConfiguredCaptchaTypeShortCircuit(shortCircuitInput, res);
|
|
203
205
|
if (shortCircuitResponse) return shortCircuitResponse;
|
|
204
|
-
const
|
|
205
|
-
if (
|
|
206
|
+
const emptyPoolResponse = await require_shortCircuit.runEmptyDetectorPoolPowFallback(shortCircuitInput, res);
|
|
207
|
+
if (emptyPoolResponse) return emptyPoolResponse;
|
|
206
208
|
const lScore = tasks.frictionlessManager.checkLangRules(req.headers["accept-language"] || "");
|
|
207
209
|
const userScope = require_blacklistRequestInspector.getRequestUserScope((0, _prosopo_util.flatten)(req.headers), req.ja4, normalizedIp, user, void 0, void 0, countryCode, asn);
|
|
208
210
|
const [decryptedPayload, validation, accessPolicies] = await Promise.all([
|
|
@@ -249,7 +251,7 @@ var handler_default = (env, userAccessRulesStorage) => async (req, res, next) =>
|
|
|
249
251
|
...shadowDomPenalty !== void 0 && { shadowDomPenalty }
|
|
250
252
|
};
|
|
251
253
|
tasks.frictionlessManager.setSessionParams({
|
|
252
|
-
token,
|
|
254
|
+
token: sessionToken,
|
|
253
255
|
score: botScore,
|
|
254
256
|
threshold: botThreshold,
|
|
255
257
|
scoreComponents,
|
|
@@ -329,6 +331,7 @@ var handler_default = (env, userAccessRulesStorage) => async (req, res, next) =>
|
|
|
329
331
|
botScore,
|
|
330
332
|
scoreComponents,
|
|
331
333
|
token,
|
|
334
|
+
headHash,
|
|
332
335
|
botThreshold,
|
|
333
336
|
currentUrl,
|
|
334
337
|
iframeUrl
|
|
@@ -4,58 +4,55 @@ require("./constants.cjs");
|
|
|
4
4
|
let _prosopo_types = require("@prosopo/types");
|
|
5
5
|
let uuid = require("uuid");
|
|
6
6
|
//#region src/api/captcha/getFrictionlessCaptchaChallenge/shortCircuit.ts
|
|
7
|
-
var buildBypassSessionParams = (input) =>
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
7
|
+
var buildBypassSessionParams = async (input) => {
|
|
8
|
+
const bundleId = input.detectorSessionId ? (await input.tasks.frictionlessManager.resolveBundleByDetectorSession(input.detectorSessionId))?.bundleId : void 0;
|
|
9
|
+
return {
|
|
10
|
+
token: input.token || `nodetector-${(0, uuid.v4)()}`,
|
|
11
|
+
score: 0,
|
|
12
|
+
threshold: input.clientRecord.settings?.frictionlessThreshold ?? .5,
|
|
13
|
+
scoreComponents: { baseScore: 0 },
|
|
14
|
+
ipAddress: input.ipAddress,
|
|
15
|
+
webView: false,
|
|
16
|
+
iFrame: false,
|
|
17
|
+
decryptedHeadHash: "",
|
|
18
|
+
siteKey: input.dapp,
|
|
19
|
+
ipInfo: input.ipInfo,
|
|
20
|
+
headers: input.flatHeaders,
|
|
21
|
+
mode: input.sessionMode,
|
|
22
|
+
userSitekeyIpHash: input.userSitekeyIpHash,
|
|
23
|
+
...bundleId && { bundleId },
|
|
24
|
+
...input.tcpToChelloUs !== void 0 && { tcpToChelloUs: input.tcpToChelloUs },
|
|
25
|
+
...input.chelloToHandshakeUs !== void 0 && { chelloToHandshakeUs: input.chelloToHandshakeUs }
|
|
26
|
+
};
|
|
27
|
+
};
|
|
24
28
|
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* a real PoW challenge rather than the request
|
|
29
|
-
*
|
|
30
|
-
* Fires when EITHER:
|
|
31
|
-
* - the pool is empty (missing dir, empty dir, or all bundles failed to
|
|
32
|
-
* load) — no provider has a bundle to assign; or
|
|
33
|
-
* - the client reported `detectorUnavailable` (it couldn't fetch/run a
|
|
34
|
-
* bundle this session) — even if the pool itself is populated.
|
|
29
|
+
* Empty-pool PoW fallback. The detector lives only in the provider-served pool
|
|
30
|
+
* bundles; when this provider has none to assign (missing dir, empty dir, or
|
|
31
|
+
* all bundles failed to load) no client could have run detection, so it serves
|
|
32
|
+
* a real PoW challenge rather than failing the request.
|
|
35
33
|
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
34
|
+
* This is a provider-side condition only. Nothing a client sends — an empty
|
|
35
|
+
* token included — can reach this path; a client that ran no detector is
|
|
36
|
+
* handled by the decision machine's missing-token gate.
|
|
38
37
|
*/
|
|
39
|
-
var
|
|
38
|
+
var runEmptyDetectorPoolPowFallback = async (input, res) => {
|
|
40
39
|
const pool = require_bundlePool.getDetectorBundlePool();
|
|
41
|
-
|
|
42
|
-
if (!poolEmpty && !input.detectorUnavailable) return null;
|
|
43
|
-
const reason = poolEmpty ? "empty_detector_pool_pow_fallback" : "client_detector_unavailable_pow_fallback";
|
|
40
|
+
if (pool && pool.size() > 0) return null;
|
|
44
41
|
input.logger.warn(() => ({
|
|
45
42
|
msg: "Frictionless decision",
|
|
46
43
|
data: {
|
|
47
44
|
requestId: input.requestId,
|
|
48
|
-
decision:
|
|
45
|
+
decision: "empty_detector_pool_pow_fallback",
|
|
49
46
|
captchaType: _prosopo_types.CaptchaType.pow
|
|
50
47
|
}
|
|
51
48
|
}));
|
|
52
49
|
require_honeypotResponse.attachHoneypot(res, input.clientRecord);
|
|
53
|
-
return res.json(await input.tasks.frictionlessManager.sendPowCaptcha(buildBypassSessionParams(input)));
|
|
50
|
+
return res.json(await input.tasks.frictionlessManager.sendPowCaptcha(await buildBypassSessionParams(input)));
|
|
54
51
|
};
|
|
55
52
|
var runConfiguredCaptchaTypeShortCircuit = async (input, res) => {
|
|
56
53
|
const configuredType = input.clientRecord.settings?.captchaType;
|
|
57
54
|
if (!configuredType || configuredType === _prosopo_types.CaptchaType.frictionless) return null;
|
|
58
|
-
const sessionParams = buildBypassSessionParams(input);
|
|
55
|
+
const sessionParams = await buildBypassSessionParams(input);
|
|
59
56
|
input.logger.info(() => ({
|
|
60
57
|
msg: "Frictionless decision",
|
|
61
58
|
data: {
|
|
@@ -76,4 +73,4 @@ var runConfiguredCaptchaTypeShortCircuit = async (input, res) => {
|
|
|
76
73
|
};
|
|
77
74
|
//#endregion
|
|
78
75
|
exports.runConfiguredCaptchaTypeShortCircuit = runConfiguredCaptchaTypeShortCircuit;
|
|
79
|
-
exports.
|
|
76
|
+
exports.runEmptyDetectorPoolPowFallback = runEmptyDetectorPoolPowFallback;
|
|
@@ -131,7 +131,7 @@ var buildEscalation = async (tasks, result, challenge, handshakeTiming) => {
|
|
|
131
131
|
if (!originSession) return void 0;
|
|
132
132
|
const routed = result.routingOutput;
|
|
133
133
|
const selectionReason = routed.reason ?? originSession.reason;
|
|
134
|
-
const newSession = await tasks.frictionlessManager.createSession(originSession.token, originSession.score, originSession.threshold, originSession.scoreComponents, originSession.ipAddress, routed.captchaType, originSession.siteKey ?? powRecord.dappAccount, routed.captchaType === _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, handshakeTiming?.tcpToChelloUs, handshakeTiming?.chelloToHandshakeUs, true, originSession.iframeUrl, originSession.isProtect);
|
|
134
|
+
const newSession = await tasks.frictionlessManager.createSession(originSession.token, originSession.score, originSession.threshold, originSession.scoreComponents, originSession.ipAddress, routed.captchaType, originSession.siteKey ?? powRecord.dappAccount, routed.captchaType === _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, handshakeTiming?.tcpToChelloUs, handshakeTiming?.chelloToHandshakeUs, true, originSession.iframeUrl, originSession.isProtect, originSession.sessionId);
|
|
135
135
|
if (tasks.writeQueue) await tasks.writeQueue.cacheSessionEscalation(powRecord.sessionId, newSession.sessionId);
|
|
136
136
|
return {
|
|
137
137
|
captchaType: routed.captchaType,
|
|
@@ -76,6 +76,19 @@ var getUserFromJWT = (req) => {
|
|
|
76
76
|
}
|
|
77
77
|
};
|
|
78
78
|
/**
|
|
79
|
+
* `.listen()` returns before the socket is bound, so a bind failure such as
|
|
80
|
+
* EADDRINUSE arrives later as an 'error' event — an uncaught exception unless
|
|
81
|
+
* something is waiting on it. Settle on whichever event comes first so the
|
|
82
|
+
* caller gets a rejection it can handle.
|
|
83
|
+
*/
|
|
84
|
+
var awaitListening = (server) => new Promise((resolve, reject) => {
|
|
85
|
+
server.once("error", reject);
|
|
86
|
+
server.once("listening", () => {
|
|
87
|
+
server.removeListener("error", reject);
|
|
88
|
+
resolve();
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
/**
|
|
79
92
|
* Start the Prosopo Provider API server
|
|
80
93
|
*
|
|
81
94
|
* This function creates and configures an Express server with all necessary middleware
|
|
@@ -223,22 +236,24 @@ async function startProviderApi(env, admin = false, port) {
|
|
|
223
236
|
key: node_fs.default.readFileSync(keyPath),
|
|
224
237
|
cert: node_fs.default.readFileSync(crtPath)
|
|
225
238
|
};
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
data: {
|
|
229
|
-
apiPort,
|
|
230
|
-
protocol: "https"
|
|
231
|
-
},
|
|
232
|
-
msg: "Prosopo app listening with HTTPS"
|
|
233
|
-
}));
|
|
234
|
-
});
|
|
235
|
-
}
|
|
236
|
-
return apiApp.listen(apiPort, () => {
|
|
239
|
+
const httpsServer = node_https.default.createServer(httpsOptions, apiApp).listen(apiPort);
|
|
240
|
+
await awaitListening(httpsServer);
|
|
237
241
|
env.logger.info(() => ({
|
|
238
|
-
data: {
|
|
239
|
-
|
|
242
|
+
data: {
|
|
243
|
+
apiPort,
|
|
244
|
+
protocol: "https"
|
|
245
|
+
},
|
|
246
|
+
msg: "Prosopo app listening with HTTPS"
|
|
240
247
|
}));
|
|
241
|
-
|
|
248
|
+
return httpsServer;
|
|
249
|
+
}
|
|
250
|
+
const server = apiApp.listen(apiPort);
|
|
251
|
+
await awaitListening(server);
|
|
252
|
+
env.logger.info(() => ({
|
|
253
|
+
data: { apiPort },
|
|
254
|
+
msg: "Prosopo app listening"
|
|
255
|
+
}));
|
|
256
|
+
return server;
|
|
242
257
|
}
|
|
243
258
|
//#endregion
|
|
244
259
|
exports.getClientApiPathsExpectingProsopoHeaders = getClientApiPathsExpectingProsopoHeaders;
|
package/dist/cjs/api/verify.cjs
CHANGED
|
@@ -317,7 +317,7 @@ function prosopoVerifyRouter(env) {
|
|
|
317
317
|
}
|
|
318
318
|
const dappPair = env.keyring.addFromAddress(dapp);
|
|
319
319
|
(0, _prosopo_api_express_router.verifySignature)(dappSignature, timestamp.toString(), dappPair);
|
|
320
|
-
const { verified, score } = await tasks.puzzleCaptchaManager.serverVerifyPuzzleCaptchaSolution(dapp, challenge, clientRecord.settings.verifiedTimeout, env, ip, userAccessRulesStorage, email, clientRecord.settings.spamEmailDomainCheckEnabled, clientRecord.settings.trafficFilter, clientRecord.settings.storeMetadata);
|
|
320
|
+
const { verified, score } = await tasks.puzzleCaptchaManager.serverVerifyPuzzleCaptchaSolution(dapp, challenge, clientRecord.settings.verifiedTimeout, env, ip, userAccessRulesStorage, email, clientRecord.settings.spamEmailDomainCheckEnabled, clientRecord.settings.spamFilter, clientRecord.settings.trafficFilter, clientRecord.settings.storeMetadata);
|
|
321
321
|
const verificationResponse = tasks.puzzleCaptchaManager.getVerificationResponse(verified, clientRecord, req.i18n.t, score);
|
|
322
322
|
return res.json(verificationResponse);
|
|
323
323
|
} catch (err) {
|
|
@@ -71,6 +71,51 @@ var CaptchaManager = class CaptchaManager {
|
|
|
71
71
|
return readings;
|
|
72
72
|
}
|
|
73
73
|
/**
|
|
74
|
+
* Read a session and, if it's an escalation missing one of the
|
|
75
|
+
* inherently-origin-populated fields, walk to `originSessionId` and fill
|
|
76
|
+
* the gap. Intended for the decision-machine input read path only —
|
|
77
|
+
* `simdReadings`, `dnsEvent`, `entropyMathRandomFingerprint` etc. are
|
|
78
|
+
* populated on the origin session (SIMD via pow-submit's fire-and-forget
|
|
79
|
+
* attach; DNS via the sidecar's origin-TLS-scoped patch) and don't
|
|
80
|
+
* automatically end up on the escalation record because
|
|
81
|
+
* `buildEscalation` reads the origin from Mongo before the async writes
|
|
82
|
+
* settle. Rather than duplicating the origin's state onto every
|
|
83
|
+
* escalation at write time, keep the origin as the source of truth and
|
|
84
|
+
* fall back to it at read time.
|
|
85
|
+
*
|
|
86
|
+
* Fields intentionally NOT walked:
|
|
87
|
+
* - captchaType / sessionId / score / threshold / scoreComponents /
|
|
88
|
+
* ipAddress / ipInfo / headers — these describe the escalation
|
|
89
|
+
* itself and must never be overridden by the origin.
|
|
90
|
+
* - Anything already present on the escalation — first-write wins.
|
|
91
|
+
*
|
|
92
|
+
* Non-escalation sessions and escalations without `originSessionId`
|
|
93
|
+
* (older records) fall through as no-op.
|
|
94
|
+
*/
|
|
95
|
+
async getSessionRecordWithOriginFallback(sessionId) {
|
|
96
|
+
const session = await this.db.getSessionRecordBySessionId(sessionId);
|
|
97
|
+
if (!session) return void 0;
|
|
98
|
+
if (!session.originSessionId) return session;
|
|
99
|
+
const needsSimd = session.simdReadings === void 0 || session.simdReadings === null;
|
|
100
|
+
const needsDns = session.dnsEvent === void 0 || session.dnsEvent === null;
|
|
101
|
+
const needsEntropyMath = session.entropyMathRandomFingerprint === void 0;
|
|
102
|
+
const needsEntropyCrypto = session.entropyCryptoFingerprint === void 0;
|
|
103
|
+
const needsEntropyWall = session.entropyWallClockOffsetMs === void 0;
|
|
104
|
+
const needsEntropyFirst = session.entropyMathRandomFirst === void 0;
|
|
105
|
+
if (!needsSimd && !needsDns && !needsEntropyMath && !needsEntropyCrypto && !needsEntropyWall && !needsEntropyFirst) return session;
|
|
106
|
+
const origin = await this.db.getSessionRecordBySessionId(session.originSessionId);
|
|
107
|
+
if (!origin) return session;
|
|
108
|
+
return {
|
|
109
|
+
...session,
|
|
110
|
+
...needsSimd && origin.simdReadings && { simdReadings: origin.simdReadings },
|
|
111
|
+
...needsDns && origin.dnsEvent && { dnsEvent: origin.dnsEvent },
|
|
112
|
+
...needsEntropyMath && origin.entropyMathRandomFingerprint !== void 0 && { entropyMathRandomFingerprint: origin.entropyMathRandomFingerprint },
|
|
113
|
+
...needsEntropyCrypto && origin.entropyCryptoFingerprint !== void 0 && { entropyCryptoFingerprint: origin.entropyCryptoFingerprint },
|
|
114
|
+
...needsEntropyWall && origin.entropyWallClockOffsetMs !== void 0 && { entropyWallClockOffsetMs: origin.entropyWallClockOffsetMs },
|
|
115
|
+
...needsEntropyFirst && origin.entropyMathRandomFirst !== void 0 && { entropyMathRandomFirst: origin.entropyMathRandomFirst }
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
74
119
|
* Decrypt + first-hop-wins attach. Resolves the session's detector pool
|
|
75
120
|
* bundle (promoted onto the session record at frictionless time) to decrypt.
|
|
76
121
|
* No-op on decrypt failure / no bundle.
|
|
@@ -130,20 +175,6 @@ var CaptchaManager = class CaptchaManager {
|
|
|
130
175
|
sessionId
|
|
131
176
|
}
|
|
132
177
|
}));
|
|
133
|
-
if (userAccessPolicy?.captchaType !== void 0 && userAccessPolicy.captchaType !== requestedCaptchaType) {
|
|
134
|
-
this.logger.warn(() => ({
|
|
135
|
-
msg: "Invalid captcha type for user access policy",
|
|
136
|
-
data: {
|
|
137
|
-
account: clientSettings.account,
|
|
138
|
-
captchaType: userAccessPolicy.captchaType
|
|
139
|
-
}
|
|
140
|
-
}));
|
|
141
|
-
return {
|
|
142
|
-
valid: false,
|
|
143
|
-
reason: _prosopo_types.ResultReason.INCORRECT_CAPTCHA_TYPE,
|
|
144
|
-
type: requestedCaptchaType
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
178
|
if (sessionId) {
|
|
148
179
|
const cachedBeforeRemove = this.writeQueue ? await this.writeQueue.getCachedSession(sessionId) : null;
|
|
149
180
|
const cachedHash = typeof cachedBeforeRemove?.userSitekeyIpHash === "string" ? cachedBeforeRemove.userSitekeyIpHash : void 0;
|
|
@@ -246,6 +277,21 @@ var CaptchaManager = class CaptchaManager {
|
|
|
246
277
|
ipInfo: sessionRecord.ipInfo
|
|
247
278
|
};
|
|
248
279
|
}
|
|
280
|
+
if (userAccessPolicy?.captchaType !== void 0 && userAccessPolicy.captchaType !== requestedCaptchaType) {
|
|
281
|
+
this.logger.warn(() => ({
|
|
282
|
+
msg: "Invalid captcha type for user access policy (sessionless)",
|
|
283
|
+
data: {
|
|
284
|
+
account: clientSettings.account,
|
|
285
|
+
captchaType: userAccessPolicy.captchaType,
|
|
286
|
+
requestedCaptchaType
|
|
287
|
+
}
|
|
288
|
+
}));
|
|
289
|
+
return {
|
|
290
|
+
valid: false,
|
|
291
|
+
reason: _prosopo_types.ResultReason.INCORRECT_CAPTCHA_TYPE,
|
|
292
|
+
type: requestedCaptchaType
|
|
293
|
+
};
|
|
294
|
+
}
|
|
249
295
|
if (clientSettings?.settings?.captchaType !== requestedCaptchaType) {
|
|
250
296
|
this.logger.warn(() => ({
|
|
251
297
|
msg: `Invalid ${requestedCaptchaType} request`,
|
|
@@ -82,7 +82,7 @@ var FrictionlessManager = class extends require_captchaManager.CaptchaManager {
|
|
|
82
82
|
checkLangRules(acceptLanguage) {
|
|
83
83
|
return require_lang.checkLangRules(this.config, acceptLanguage);
|
|
84
84
|
}
|
|
85
|
-
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) {
|
|
85
|
+
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) {
|
|
86
86
|
const sessionRecord = {
|
|
87
87
|
sessionId: `${getSessionIDPrefix(this.config.host)}-${(0, uuid.v4)()}`,
|
|
88
88
|
createdAt: /* @__PURE__ */ new Date(),
|
|
@@ -99,6 +99,7 @@ var FrictionlessManager = class extends require_captchaManager.CaptchaManager {
|
|
|
99
99
|
webView,
|
|
100
100
|
iFrame,
|
|
101
101
|
...isEscalation && { isEscalation: true },
|
|
102
|
+
...originSessionId && { originSessionId },
|
|
102
103
|
decryptedHeadHash,
|
|
103
104
|
bundleId,
|
|
104
105
|
reason,
|
|
@@ -394,7 +394,34 @@ var ImgCaptchaManager = class extends require_captchaManager.CaptchaManager {
|
|
|
394
394
|
failStatus = _prosopo_types.ResultReason.SPAM_EMAIL_RULE;
|
|
395
395
|
}
|
|
396
396
|
}
|
|
397
|
-
const
|
|
397
|
+
const maxEmailSubmissionCount = spamFilter?.enabled && spamFilter.emailRules?.enabled ? spamFilter.emailRules.maxEmailSubmissionCount : void 0;
|
|
398
|
+
let emailNormalised;
|
|
399
|
+
if (!failStatus && maxEmailSubmissionCount !== void 0 && email && storeMetadata) {
|
|
400
|
+
emailNormalised = require_evaluateEmailSpamRules.normaliseEmailForMatching(email);
|
|
401
|
+
if (emailNormalised) try {
|
|
402
|
+
const priorCount = await this.db.countCommitmentsByNormalisedEmail(dapp, emailNormalised);
|
|
403
|
+
if (priorCount >= maxEmailSubmissionCount) {
|
|
404
|
+
logger.info(() => ({
|
|
405
|
+
msg: "Email submission count exceeded in image verification",
|
|
406
|
+
data: {
|
|
407
|
+
priorCount,
|
|
408
|
+
maxEmailSubmissionCount
|
|
409
|
+
}
|
|
410
|
+
}));
|
|
411
|
+
commitmentUpdates.result = {
|
|
412
|
+
status: _prosopo_types.CaptchaStatus.disapproved,
|
|
413
|
+
reason: _prosopo_types.ResultReason.SPAM_EMAIL_COUNT_EXCEEDED
|
|
414
|
+
};
|
|
415
|
+
failStatus = _prosopo_types.ResultReason.SPAM_EMAIL_COUNT_EXCEEDED;
|
|
416
|
+
}
|
|
417
|
+
} catch (error) {
|
|
418
|
+
logger.warn(() => ({
|
|
419
|
+
msg: "Failed to check email submission count in image verification",
|
|
420
|
+
error
|
|
421
|
+
}));
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
const sessionRecord = solution.sessionId ? await this.getSessionRecordWithOriginFallback(solution.sessionId) : void 0;
|
|
398
425
|
const enrichedDnsEvent = await require_enrichDnsEvent.enrichDnsEvent(sessionRecord?.dnsEvent, env.ipInfoService, ip ?? solution.ipInfo?.ip);
|
|
399
426
|
if (!failStatus) {
|
|
400
427
|
const check = await this.resolveTrafficFilterCheck(env, solution.ipInfo, trafficFilter, ip, enrichedDnsEvent);
|
|
@@ -418,7 +445,10 @@ var ImgCaptchaManager = class extends require_captchaManager.CaptchaManager {
|
|
|
418
445
|
failStatus = check.reason;
|
|
419
446
|
}
|
|
420
447
|
}
|
|
421
|
-
if (storeMetadata && email) commitmentUpdates.metadata = {
|
|
448
|
+
if (storeMetadata && email) commitmentUpdates.metadata = {
|
|
449
|
+
email,
|
|
450
|
+
emailNormalised: emailNormalised ?? require_evaluateEmailSpamRules.normaliseEmailForMatching(email)
|
|
451
|
+
};
|
|
422
452
|
if (ip) {
|
|
423
453
|
const solutionIpAddress = require_compositeIpAddress.getIpAddressFromComposite(solution.ipAddress);
|
|
424
454
|
const ipValidationRules = (await this.db.getClientRecord(dapp))?.settings?.ipValidationRules;
|
|
@@ -353,7 +353,34 @@ var PowCaptchaManager = class extends require_captchaManager.CaptchaManager {
|
|
|
353
353
|
failReason = "API.SPAM_EMAIL_RULE";
|
|
354
354
|
}
|
|
355
355
|
}
|
|
356
|
-
const
|
|
356
|
+
const maxEmailSubmissionCount = spamFilter?.enabled && spamFilter.emailRules?.enabled ? spamFilter.emailRules.maxEmailSubmissionCount : void 0;
|
|
357
|
+
let emailNormalised;
|
|
358
|
+
if (!failResult && maxEmailSubmissionCount !== void 0 && email && storeMetadata) {
|
|
359
|
+
emailNormalised = require_evaluateEmailSpamRules.normaliseEmailForMatching(email);
|
|
360
|
+
if (emailNormalised) try {
|
|
361
|
+
const priorCount = await this.db.countCommitmentsByNormalisedEmail(dappAccount, emailNormalised);
|
|
362
|
+
if (priorCount >= maxEmailSubmissionCount) {
|
|
363
|
+
logger.info(() => ({
|
|
364
|
+
msg: "Email submission count exceeded in PoW verification",
|
|
365
|
+
data: {
|
|
366
|
+
priorCount,
|
|
367
|
+
maxEmailSubmissionCount
|
|
368
|
+
}
|
|
369
|
+
}));
|
|
370
|
+
failResult = {
|
|
371
|
+
status: _prosopo_types.CaptchaStatus.disapproved,
|
|
372
|
+
reason: _prosopo_types.ResultReason.SPAM_EMAIL_COUNT_EXCEEDED
|
|
373
|
+
};
|
|
374
|
+
failReason = "API.SPAM_EMAIL_COUNT_EXCEEDED";
|
|
375
|
+
}
|
|
376
|
+
} catch (error) {
|
|
377
|
+
logger.warn(() => ({
|
|
378
|
+
msg: "Failed to check email submission count in PoW verification",
|
|
379
|
+
error
|
|
380
|
+
}));
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
const sessionRecord = challengeRecord.sessionId ? await this.getSessionRecordWithOriginFallback(challengeRecord.sessionId) : void 0;
|
|
357
384
|
const enrichedDnsEvent = await require_enrichDnsEvent.enrichDnsEvent(sessionRecord?.dnsEvent, env.ipInfoService, ip ?? challengeRecord.ipInfo?.ip);
|
|
358
385
|
if (!failResult) {
|
|
359
386
|
const check = await this.resolveTrafficFilterCheck(env, challengeRecord.ipInfo, trafficFilter, ip, enrichedDnsEvent);
|
|
@@ -377,7 +404,10 @@ var PowCaptchaManager = class extends require_captchaManager.CaptchaManager {
|
|
|
377
404
|
failReason = check.reason;
|
|
378
405
|
}
|
|
379
406
|
}
|
|
380
|
-
if (storeMetadata && email) powRecordUpdates.metadata = {
|
|
407
|
+
if (storeMetadata && email) powRecordUpdates.metadata = {
|
|
408
|
+
email,
|
|
409
|
+
emailNormalised: emailNormalised ?? require_evaluateEmailSpamRules.normaliseEmailForMatching(email)
|
|
410
|
+
};
|
|
381
411
|
if (!failResult && ip) {
|
|
382
412
|
const challengeIpAddress = require_compositeIpAddress.getIpAddressFromComposite(challengeRecord.ipAddress);
|
|
383
413
|
const ipValidationRules = (await this.db.getClientRecord(dappAccount))?.settings?.ipValidationRules;
|
|
@@ -5,6 +5,7 @@ const require_enrichDnsEvent = require("../dnsEvent/enrichDnsEvent.cjs");
|
|
|
5
5
|
const require_captchaManager = require("../captchaManager.cjs");
|
|
6
6
|
const require_util = require("../../util.cjs");
|
|
7
7
|
const require_frictionlessTasksUtils = require("../frictionless/frictionlessTasksUtils.cjs");
|
|
8
|
+
const require_evaluateEmailSpamRules = require("../spam/evaluateEmailSpamRules.cjs");
|
|
8
9
|
const require_powTasksUtils = require("../powCaptcha/powTasksUtils.cjs");
|
|
9
10
|
const require_puzzleTasksUtils = require("./puzzleTasksUtils.cjs");
|
|
10
11
|
let _prosopo_common = require("@prosopo/common");
|
|
@@ -184,11 +185,12 @@ var PuzzleCaptchaManager = class extends require_captchaManager.CaptchaManager {
|
|
|
184
185
|
* @param userAccessRulesStorage - storage for querying user access policies
|
|
185
186
|
* @param email
|
|
186
187
|
* @param spamEmailDomainCheckingEnabled
|
|
188
|
+
* @param spamFilter
|
|
187
189
|
* @param trafficFilter
|
|
188
190
|
* @param storeMetadata - when true, persists the dapp-server-provided
|
|
189
191
|
* `email` on the captcha record for spam-rate analysis.
|
|
190
192
|
*/
|
|
191
|
-
async serverVerifyPuzzleCaptchaSolution(dappAccount, challenge, timeout, env, ip, userAccessRulesStorage, email, spamEmailDomainCheckingEnabled = false, trafficFilter, storeMetadata = false) {
|
|
193
|
+
async serverVerifyPuzzleCaptchaSolution(dappAccount, challenge, timeout, env, ip, userAccessRulesStorage, email, spamEmailDomainCheckingEnabled = false, spamFilter, trafficFilter, storeMetadata = false) {
|
|
192
194
|
const notVerifiedResponse = { verified: false };
|
|
193
195
|
const logger = this.logger.with({
|
|
194
196
|
challenge,
|
|
@@ -274,7 +276,34 @@ var PuzzleCaptchaManager = class extends require_captchaManager.CaptchaManager {
|
|
|
274
276
|
error
|
|
275
277
|
}));
|
|
276
278
|
}
|
|
277
|
-
const
|
|
279
|
+
const maxEmailSubmissionCount = spamFilter?.enabled && spamFilter.emailRules?.enabled ? spamFilter.emailRules.maxEmailSubmissionCount : void 0;
|
|
280
|
+
let emailNormalised;
|
|
281
|
+
if (maxEmailSubmissionCount !== void 0 && email && storeMetadata) {
|
|
282
|
+
emailNormalised = require_evaluateEmailSpamRules.normaliseEmailForMatching(email);
|
|
283
|
+
if (emailNormalised) try {
|
|
284
|
+
const priorCount = await this.db.countCommitmentsByNormalisedEmail(dappAccount, emailNormalised);
|
|
285
|
+
if (priorCount >= maxEmailSubmissionCount) {
|
|
286
|
+
logger.info(() => ({
|
|
287
|
+
msg: "Email submission count exceeded in server puzzle verification",
|
|
288
|
+
data: {
|
|
289
|
+
priorCount,
|
|
290
|
+
maxEmailSubmissionCount
|
|
291
|
+
}
|
|
292
|
+
}));
|
|
293
|
+
await this.db.updatePuzzleCaptchaRecord(challengeRecord.challenge, { result: {
|
|
294
|
+
status: _prosopo_types.CaptchaStatus.disapproved,
|
|
295
|
+
reason: _prosopo_types.ResultReason.SPAM_EMAIL_COUNT_EXCEEDED
|
|
296
|
+
} });
|
|
297
|
+
return notVerifiedResponse;
|
|
298
|
+
}
|
|
299
|
+
} catch (error) {
|
|
300
|
+
logger.warn(() => ({
|
|
301
|
+
msg: "Failed to check email submission count in server puzzle verification",
|
|
302
|
+
error
|
|
303
|
+
}));
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
const sessionRecord = challengeRecord.sessionId ? await this.getSessionRecordWithOriginFallback(challengeRecord.sessionId) : void 0;
|
|
278
307
|
const enrichedDnsEvent = await require_enrichDnsEvent.enrichDnsEvent(sessionRecord?.dnsEvent, env.ipInfoService, ip ?? challengeRecord.ipInfo?.ip);
|
|
279
308
|
{
|
|
280
309
|
const check = await this.resolveTrafficFilterCheck(env, challengeRecord.ipInfo, trafficFilter, ip, enrichedDnsEvent);
|
|
@@ -303,7 +332,10 @@ var PuzzleCaptchaManager = class extends require_captchaManager.CaptchaManager {
|
|
|
303
332
|
return notVerifiedResponse;
|
|
304
333
|
}
|
|
305
334
|
}
|
|
306
|
-
if (storeMetadata && email) await this.db.updatePuzzleCaptchaRecord(challengeRecord.challenge, { metadata: {
|
|
335
|
+
if (storeMetadata && email) await this.db.updatePuzzleCaptchaRecord(challengeRecord.challenge, { metadata: {
|
|
336
|
+
email,
|
|
337
|
+
emailNormalised: emailNormalised ?? require_evaluateEmailSpamRules.normaliseEmailForMatching(email)
|
|
338
|
+
} });
|
|
307
339
|
if (ip) {
|
|
308
340
|
const challengeIpAddress = require_compositeIpAddress.getIpAddressFromComposite(challengeRecord.ipAddress);
|
|
309
341
|
const ipValidationRules = (await this.db.getClientRecord(dappAccount))?.settings?.ipValidationRules;
|
|
@@ -28,6 +28,28 @@ var normaliseGmailAddress = (email) => {
|
|
|
28
28
|
if (!GMAIL_DOMAINS.has(parts.domain)) return `${parts.local}@${parts.domain}`;
|
|
29
29
|
return `${parts.local.replace(/\./g, "").split("+")[0] ?? ""}@gmail.com`;
|
|
30
30
|
};
|
|
31
|
+
/**
|
|
32
|
+
* Normalises an email for equality-based rate-limit matching:
|
|
33
|
+
* - lowercases and trims
|
|
34
|
+
* - strips `+tag` from the local part for every domain (RFC 5233
|
|
35
|
+
* subaddressing is widely honoured — Gmail, Fastmail, iCloud, Outlook,
|
|
36
|
+
* ProtonMail — and rotating `+tag` suffixes is the cheapest evasion
|
|
37
|
+
* against per-address rate limits)
|
|
38
|
+
* - additionally removes dots from the local part for gmail /
|
|
39
|
+
* googlemail (Gmail is the only major provider that treats dots as
|
|
40
|
+
* insignificant)
|
|
41
|
+
*
|
|
42
|
+
* Returns the input trimmed/lowercased if it cannot be split into
|
|
43
|
+
* local@domain. Never returns an empty string for a non-empty input.
|
|
44
|
+
*/
|
|
45
|
+
var normaliseEmailForMatching = (email) => {
|
|
46
|
+
const parts = splitEmail(email);
|
|
47
|
+
if (!parts) return email.trim().toLowerCase();
|
|
48
|
+
const beforePlus = parts.local.split("+")[0];
|
|
49
|
+
const withoutPlusTag = beforePlus ? beforePlus : parts.local;
|
|
50
|
+
if (GMAIL_DOMAINS.has(parts.domain)) return `${withoutPlusTag.replace(/\./g, "") || withoutPlusTag}@gmail.com`;
|
|
51
|
+
return `${withoutPlusTag}@${parts.domain}`;
|
|
52
|
+
};
|
|
31
53
|
var countDots = (s) => {
|
|
32
54
|
let n = 0;
|
|
33
55
|
for (const c of s) if (c === ".") n += 1;
|
|
@@ -80,4 +102,5 @@ var evaluateEmailSpamRules = (email, rules) => {
|
|
|
80
102
|
//#endregion
|
|
81
103
|
exports.DEFAULT_EMAIL_SPAM_PATTERNS = DEFAULT_EMAIL_SPAM_PATTERNS;
|
|
82
104
|
exports.evaluateEmailSpamRules = evaluateEmailSpamRules;
|
|
105
|
+
exports.normaliseEmailForMatching = normaliseEmailForMatching;
|
|
83
106
|
exports.normaliseGmailAddress = normaliseGmailAddress;
|
|
@@ -21,6 +21,7 @@ export declare class CaptchaManager {
|
|
|
21
21
|
constructor(db: IProviderDatabase, pair: KeyringPair, config: ProsopoConfigOutput, logger?: Logger, writeQueue?: RedisWriteQueue | null);
|
|
22
22
|
updateSessionRecordWithCache(sessionId: string, updates: Partial<Session>, streamToCentral?: boolean): Promise<void>;
|
|
23
23
|
decryptSimdReadingsForAttach(simdReadingsCiphertext: string, bundle: PoolBundleDecrypt | undefined): Promise<NonNullable<Session["simdReadings"]> | undefined>;
|
|
24
|
+
getSessionRecordWithOriginFallback(sessionId: string): Promise<Session | undefined>;
|
|
24
25
|
decryptAndAttachSimdReadingsIfAbsent(sessionId: string, simdReadingsCiphertext: string, stage: SimdReadingsStage): Promise<void>;
|
|
25
26
|
recordSessionSimdReadingsIfAbsentWithCache(sessionId: string, readings: NonNullable<Session["simdReadings"]>, stage: SimdReadingsStage): Promise<void>;
|
|
26
27
|
scheduleMongoSessionUpdate(sessionId: string, updates: Partial<Session>, streamToCentral?: boolean): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"captchaManager.d.ts","sourceRoot":"","sources":["../../src/tasks/captchaManager.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EAAE,KAAK,MAAM,EAAa,MAAM,iBAAiB,CAAC;AACzD,OAAO,EAEN,KAAK,WAAW,EAChB,KAAK,gBAAgB,EACrB,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,mBAAmB,EACxB,KAAK,cAAc,EAEnB,KAAK,OAAO,EACZ,KAAK,iBAAiB,EACtB,IAAI,EACJ,KAAK,cAAc,EACnB,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EACX,YAAY,EACZ,iBAAiB,EACjB,aAAa,EACb,gBAAgB,EAChB,mBAAmB,EACnB,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,EACN,KAAK,YAAY,EAEjB,KAAK,kBAAkB,EACvB,KAAK,SAAS,EACd,KAAK,eAAe,EACpB,MAAM,6BAA6B,CAAC;AAOrC,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AAC1E,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAGpE,OAAO,EACN,KAAK,kBAAkB,EAEvB,MAAM,8BAA8B,CAAC;AAStC,MAAM,WAAW,iBAAiB;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,MAAM,CAAC;CACpB;AAkCD,qBAAa,cAAc;IAC1B,IAAI,EAAE,WAAW,CAAC;IAClB,EAAE,EAAE,iBAAiB,CAAC;IACtB,MAAM,EAAE,mBAAmB,CAAC;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,eAAe,GAAG,IAAI,CAAC;gBAGlC,EAAE,EAAE,iBAAiB,EACrB,IAAI,EAAE,WAAW,EACjB,MAAM,EAAE,mBAAmB,EAC3B,MAAM,CAAC,EAAE,MAAM,EACf,UAAU,CAAC,EAAE,eAAe,GAAG,IAAI;IAqBvB,4BAA4B,CACxC,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,EACzB,eAAe,CAAC,EAAE,OAAO,GACvB,OAAO,CAAC,IAAI,CAAC;IAkBH,4BAA4B,CACxC,sBAAsB,EAAE,MAAM,EAC9B,MAAM,EAAE,iBAAiB,GAAG,SAAS,GACnC,OAAO,CAAC,WAAW,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC,GAAG,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"captchaManager.d.ts","sourceRoot":"","sources":["../../src/tasks/captchaManager.ts"],"names":[],"mappings":"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"}
|