@prosopo/provider 3.12.3 → 3.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +214 -0
- package/dist/api/admin/apiAdminRoutesProvider.js +13 -18
- package/dist/api/admin/apiToggleMaintenanceModeEndpoint.js +40 -0
- package/dist/api/blacklistRequestInspector.js +4 -4
- package/dist/api/captcha/getFrictionlessCaptchaChallenge.js +338 -0
- package/dist/api/captcha/getImageCaptchaChallenge.js +150 -0
- package/dist/api/captcha/getPoWCaptchaChallenge.js +156 -0
- package/dist/api/captcha/submitImageCaptchaSolution.js +87 -0
- package/dist/api/captcha/submitPoWCaptchaSolution.js +77 -0
- package/dist/api/captcha.js +18 -606
- package/dist/api/verify.js +24 -1
- package/dist/cjs/api/admin/apiAdminRoutesProvider.cjs +13 -18
- package/dist/cjs/api/admin/apiRegisterSiteKeyEndpoint.cjs +2 -1
- package/dist/cjs/api/admin/apiRemoveDetectorKeyEndpoint.cjs +3 -2
- package/dist/cjs/api/admin/apiToggleMaintenanceModeEndpoint.cjs +41 -0
- package/dist/cjs/api/blacklistRequestInspector.cjs +3 -3
- package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge.cjs +337 -0
- package/dist/cjs/api/captcha/getImageCaptchaChallenge.cjs +149 -0
- package/dist/cjs/api/captcha/getPoWCaptchaChallenge.cjs +155 -0
- package/dist/cjs/api/captcha/submitImageCaptchaSolution.cjs +86 -0
- package/dist/cjs/api/captcha/submitPoWCaptchaSolution.cjs +76 -0
- package/dist/cjs/api/captcha.cjs +17 -605
- package/dist/cjs/api/ja4Middleware.cjs +2 -1
- package/dist/cjs/api/verify.cjs +24 -1
- package/dist/cjs/index.cjs +2 -0
- package/dist/cjs/schedulers/setClientEntropy.cjs +36 -0
- package/dist/cjs/tasks/captchaManager.cjs +7 -22
- package/dist/cjs/tasks/client/clientTasks.cjs +18 -36
- package/dist/cjs/tasks/detection/decodePayload.cjs +385 -714
- package/dist/cjs/tasks/detection/getBotScore.cjs +15 -2
- package/dist/cjs/tasks/frictionless/frictionlessTasks.cjs +136 -30
- package/dist/cjs/tasks/imgCaptcha/imgCaptchaTasks.cjs +25 -13
- package/dist/cjs/tasks/powCaptcha/powTasks.cjs +8 -8
- package/dist/cjs/tasks/tasks.cjs +1 -0
- package/dist/cjs/util.cjs +14 -1
- package/dist/cjs/utils/hashUserIp.cjs +9 -0
- package/dist/index.js +2 -0
- package/dist/schedulers/setClientEntropy.js +36 -0
- package/dist/tasks/captchaManager.js +5 -21
- package/dist/tasks/client/clientTasks.js +19 -37
- package/dist/tasks/detection/decodePayload.js +385 -714
- package/dist/tasks/detection/getBotScore.js +17 -4
- package/dist/tasks/frictionless/frictionlessTasks.js +137 -31
- package/dist/tasks/imgCaptcha/imgCaptchaTasks.js +25 -13
- package/dist/tasks/powCaptcha/powTasks.js +8 -8
- package/dist/tasks/tasks.js +1 -0
- package/dist/util.js +14 -1
- package/dist/utils/hashUserIp.js +9 -0
- package/package.json +24 -25
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
3
|
const decodePayload = require("./decodePayload.cjs");
|
|
4
4
|
const DEFAULT_ENTROPY = 13837;
|
|
5
|
-
const getBotScore = async (payload, privateKeyString) => {
|
|
5
|
+
const getBotScore = async (payload, headHash, privateKeyString) => {
|
|
6
6
|
const result = await decodePayload(
|
|
7
7
|
payload,
|
|
8
|
+
headHash,
|
|
8
9
|
privateKeyString
|
|
9
10
|
);
|
|
10
11
|
const baseBotScore = result.score;
|
|
@@ -12,6 +13,9 @@ const getBotScore = async (payload, privateKeyString) => {
|
|
|
12
13
|
const providerSelectEntropy = result.providerSelectEntropy;
|
|
13
14
|
const userId = result.userId;
|
|
14
15
|
const userAgent = result.userAgent;
|
|
16
|
+
const isWebView = result.isWebView ?? false;
|
|
17
|
+
const isIframe = result.isIframe ?? false;
|
|
18
|
+
const decryptedHeadHash = result.decryptedHeadHash;
|
|
15
19
|
if (baseBotScore === void 0) {
|
|
16
20
|
return {
|
|
17
21
|
baseBotScore: 1,
|
|
@@ -19,6 +23,15 @@ const getBotScore = async (payload, privateKeyString) => {
|
|
|
19
23
|
providerSelectEntropy: DEFAULT_ENTROPY
|
|
20
24
|
};
|
|
21
25
|
}
|
|
22
|
-
return {
|
|
26
|
+
return {
|
|
27
|
+
baseBotScore,
|
|
28
|
+
timestamp,
|
|
29
|
+
providerSelectEntropy,
|
|
30
|
+
userId,
|
|
31
|
+
userAgent,
|
|
32
|
+
isWebView,
|
|
33
|
+
isIframe,
|
|
34
|
+
decryptedHeadHash
|
|
35
|
+
};
|
|
23
36
|
};
|
|
24
37
|
exports.getBotScore = getBotScore;
|
|
@@ -17,22 +17,60 @@ const getDefaultEntropy = () => {
|
|
|
17
17
|
};
|
|
18
18
|
const DEFAULT_MAX_TIMESTAMP_AGE = 60 * 10 * 1e3;
|
|
19
19
|
const DEFAULT_ENTROPY = getDefaultEntropy();
|
|
20
|
+
var FrictionlessReason = /* @__PURE__ */ ((FrictionlessReason2) => {
|
|
21
|
+
FrictionlessReason2["CONTEXT_AWARE_VALIDATION_FAILED"] = "CONTEXT_AWARE_VALIDATION_FAILED";
|
|
22
|
+
FrictionlessReason2["USER_ACCESS_POLICY"] = "USER_ACCESS_POLICY";
|
|
23
|
+
FrictionlessReason2["USER_AGENT_MISMATCH"] = "USER_AGENT_MISMATCH";
|
|
24
|
+
FrictionlessReason2["OLD_TIMESTAMP"] = "OLD_TIMESTAMP";
|
|
25
|
+
FrictionlessReason2["BOT_SCORE_ABOVE_THRESHOLD"] = "BOT_SCORE_ABOVE_THRESHOLD";
|
|
26
|
+
FrictionlessReason2["WEBVIEW_DETECTED"] = "WEBVIEW_DETECTED";
|
|
27
|
+
return FrictionlessReason2;
|
|
28
|
+
})(FrictionlessReason || {});
|
|
20
29
|
class FrictionlessManager extends captchaManager.CaptchaManager {
|
|
21
30
|
constructor(db, pair, config, logger) {
|
|
22
|
-
super(db, pair, logger);
|
|
31
|
+
super(db, pair, config, logger);
|
|
23
32
|
this.config = config;
|
|
24
33
|
}
|
|
34
|
+
setSessionParams(params) {
|
|
35
|
+
this.sessionParams = {
|
|
36
|
+
token: params.token,
|
|
37
|
+
score: params.score,
|
|
38
|
+
threshold: params.threshold,
|
|
39
|
+
scoreComponents: params.scoreComponents,
|
|
40
|
+
providerSelectEntropy: params.providerSelectEntropy,
|
|
41
|
+
ipAddress: params.ipAddress,
|
|
42
|
+
webView: params.webView ?? false,
|
|
43
|
+
iFrame: params.iFrame ?? false,
|
|
44
|
+
decryptedHeadHash: params.decryptedHeadHash
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
updateScore(score, scoreComponents) {
|
|
48
|
+
if (this.sessionParams) {
|
|
49
|
+
this.sessionParams.score = score;
|
|
50
|
+
this.sessionParams.scoreComponents = scoreComponents;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
25
53
|
checkLangRules(acceptLanguage) {
|
|
26
54
|
return lang.checkLangRules(this.config, acceptLanguage);
|
|
27
55
|
}
|
|
28
|
-
async createSession(
|
|
56
|
+
async createSession(token, score, threshold, scoreComponents, providerSelectEntropy, ipAddress, captchaType, solvedImagesCount, powDifficulty, userSitekeyIpHash, webView = false, iFrame = false, decryptedHeadHash = "", reason) {
|
|
29
57
|
const sessionRecord = {
|
|
30
58
|
sessionId: uuid.v4(),
|
|
31
59
|
createdAt: /* @__PURE__ */ new Date(),
|
|
32
|
-
|
|
60
|
+
token,
|
|
61
|
+
score,
|
|
62
|
+
threshold,
|
|
63
|
+
scoreComponents,
|
|
64
|
+
providerSelectEntropy,
|
|
65
|
+
ipAddress,
|
|
33
66
|
captchaType,
|
|
34
67
|
solvedImagesCount,
|
|
35
|
-
powDifficulty
|
|
68
|
+
powDifficulty,
|
|
69
|
+
userSitekeyIpHash,
|
|
70
|
+
webView,
|
|
71
|
+
iFrame,
|
|
72
|
+
decryptedHeadHash,
|
|
73
|
+
reason
|
|
36
74
|
};
|
|
37
75
|
await this.db.storeSessionRecord(sessionRecord);
|
|
38
76
|
return sessionRecord;
|
|
@@ -55,11 +93,28 @@ class FrictionlessManager extends captchaManager.CaptchaManager {
|
|
|
55
93
|
}
|
|
56
94
|
return { verified: true, domain };
|
|
57
95
|
}
|
|
58
|
-
async sendImageCaptcha(
|
|
96
|
+
async sendImageCaptcha(params) {
|
|
97
|
+
const effectiveParams = { ...this.sessionParams, ...params };
|
|
98
|
+
if (!effectiveParams.token || effectiveParams.score === void 0 || effectiveParams.threshold === void 0 || !effectiveParams.scoreComponents || effectiveParams.providerSelectEntropy === void 0 || !effectiveParams.ipAddress) {
|
|
99
|
+
throw new Error(
|
|
100
|
+
"Session parameters must be set before calling sendImageCaptcha"
|
|
101
|
+
);
|
|
102
|
+
}
|
|
59
103
|
const sessionRecord = await this.createSession(
|
|
60
|
-
|
|
104
|
+
effectiveParams.token,
|
|
105
|
+
effectiveParams.score,
|
|
106
|
+
effectiveParams.threshold,
|
|
107
|
+
effectiveParams.scoreComponents,
|
|
108
|
+
effectiveParams.providerSelectEntropy,
|
|
109
|
+
effectiveParams.ipAddress,
|
|
61
110
|
types.CaptchaType.image,
|
|
62
|
-
solvedImagesCount
|
|
111
|
+
effectiveParams.solvedImagesCount,
|
|
112
|
+
void 0,
|
|
113
|
+
effectiveParams.userSitekeyIpHash,
|
|
114
|
+
effectiveParams.webView ?? false,
|
|
115
|
+
effectiveParams.iFrame ?? false,
|
|
116
|
+
effectiveParams.decryptedHeadHash,
|
|
117
|
+
effectiveParams.reason
|
|
63
118
|
);
|
|
64
119
|
return {
|
|
65
120
|
[types.ApiParams.captchaType]: types.CaptchaType.image,
|
|
@@ -67,11 +122,28 @@ class FrictionlessManager extends captchaManager.CaptchaManager {
|
|
|
67
122
|
[types.ApiParams.status]: "ok"
|
|
68
123
|
};
|
|
69
124
|
}
|
|
70
|
-
async sendPowCaptcha(
|
|
125
|
+
async sendPowCaptcha(params) {
|
|
126
|
+
const effectiveParams = { ...this.sessionParams, ...params };
|
|
127
|
+
if (!effectiveParams.token || effectiveParams.score === void 0 || effectiveParams.threshold === void 0 || !effectiveParams.scoreComponents || effectiveParams.providerSelectEntropy === void 0 || !effectiveParams.ipAddress) {
|
|
128
|
+
throw new Error(
|
|
129
|
+
"Session parameters must be set before calling sendPowCaptcha"
|
|
130
|
+
);
|
|
131
|
+
}
|
|
71
132
|
const sessionRecord = await this.createSession(
|
|
72
|
-
|
|
133
|
+
effectiveParams.token,
|
|
134
|
+
effectiveParams.score,
|
|
135
|
+
effectiveParams.threshold,
|
|
136
|
+
effectiveParams.scoreComponents,
|
|
137
|
+
effectiveParams.providerSelectEntropy,
|
|
138
|
+
effectiveParams.ipAddress,
|
|
73
139
|
types.CaptchaType.pow,
|
|
74
|
-
|
|
140
|
+
void 0,
|
|
141
|
+
effectiveParams.powDifficulty,
|
|
142
|
+
effectiveParams.userSitekeyIpHash,
|
|
143
|
+
effectiveParams.webView ?? false,
|
|
144
|
+
effectiveParams.iFrame ?? false,
|
|
145
|
+
effectiveParams.decryptedHeadHash,
|
|
146
|
+
effectiveParams.reason
|
|
75
147
|
);
|
|
76
148
|
return {
|
|
77
149
|
[types.ApiParams.captchaType]: types.CaptchaType.pow,
|
|
@@ -79,47 +151,57 @@ class FrictionlessManager extends captchaManager.CaptchaManager {
|
|
|
79
151
|
[types.ApiParams.status]: "ok"
|
|
80
152
|
};
|
|
81
153
|
}
|
|
82
|
-
|
|
154
|
+
scoreIncreaseAccessPolicy(accessPolicy, baseBotScore, botScore, scoreComponents) {
|
|
83
155
|
const accessPolicyPenalty = accessPolicy?.frictionlessScore || this.config.penalties.PENALTY_ACCESS_RULE;
|
|
84
156
|
botScore += accessPolicyPenalty;
|
|
85
|
-
|
|
157
|
+
return {
|
|
86
158
|
score: botScore,
|
|
87
159
|
scoreComponents: {
|
|
88
|
-
|
|
160
|
+
...scoreComponents,
|
|
89
161
|
accessPolicy: accessPolicyPenalty
|
|
90
162
|
}
|
|
91
|
-
}
|
|
92
|
-
return botScore;
|
|
163
|
+
};
|
|
93
164
|
}
|
|
94
|
-
|
|
165
|
+
scoreIncreaseUnverifiedHost(host, baseBotScore, botScore, scoreComponents) {
|
|
95
166
|
this.logger.info(() => ({
|
|
96
167
|
msg: "Host not verified",
|
|
97
168
|
data: { requested: this.config.host, selected: host }
|
|
98
169
|
}));
|
|
99
170
|
botScore += this.config.penalties.PENALTY_UNVERIFIED_HOST;
|
|
100
|
-
|
|
171
|
+
return {
|
|
101
172
|
score: botScore,
|
|
102
173
|
scoreComponents: {
|
|
103
|
-
|
|
174
|
+
...scoreComponents,
|
|
104
175
|
unverifiedHost: this.config.penalties.PENALTY_UNVERIFIED_HOST
|
|
105
176
|
}
|
|
106
|
-
}
|
|
107
|
-
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
scoreIncreaseWebView(baseBotScore, botScore, scoreComponents) {
|
|
180
|
+
this.logger.debug(() => ({
|
|
181
|
+
msg: "WebView detected"
|
|
182
|
+
}));
|
|
183
|
+
botScore += this.config.penalties.PENALTY_WEBVIEW;
|
|
184
|
+
return {
|
|
185
|
+
score: botScore,
|
|
186
|
+
scoreComponents: {
|
|
187
|
+
...scoreComponents,
|
|
188
|
+
webView: this.config.penalties.PENALTY_WEBVIEW
|
|
189
|
+
}
|
|
190
|
+
};
|
|
108
191
|
}
|
|
109
|
-
|
|
192
|
+
scoreIncreaseTimestamp(timestamp, baseBotScore, botScore, scoreComponents) {
|
|
110
193
|
this.logger.info(() => ({
|
|
111
194
|
msg: "Timestamp is older than 10 minutes",
|
|
112
195
|
data: { timestamp: new Date(timestamp) }
|
|
113
196
|
}));
|
|
114
197
|
botScore += this.config.penalties.PENALTY_OLD_TIMESTAMP;
|
|
115
|
-
|
|
198
|
+
return {
|
|
116
199
|
score: botScore,
|
|
117
200
|
scoreComponents: {
|
|
118
|
-
|
|
201
|
+
...scoreComponents,
|
|
119
202
|
timeout: this.config.penalties.PENALTY_OLD_TIMESTAMP
|
|
120
203
|
}
|
|
121
|
-
}
|
|
122
|
-
return botScore;
|
|
204
|
+
};
|
|
123
205
|
}
|
|
124
206
|
static timestampTooOld(timestamp) {
|
|
125
207
|
const now = Date.now();
|
|
@@ -141,7 +223,7 @@ class FrictionlessManager extends captchaManager.CaptchaManager {
|
|
|
141
223
|
const end = key.slice(-5);
|
|
142
224
|
return `${start}...${middle}...${end}`;
|
|
143
225
|
}
|
|
144
|
-
async decryptPayload(token) {
|
|
226
|
+
async decryptPayload(token, headHash) {
|
|
145
227
|
const decryptKeys = [
|
|
146
228
|
// Process DB keys first, then env var key last as env key will likely be invalid
|
|
147
229
|
...await this.getDetectorKeys(),
|
|
@@ -164,6 +246,9 @@ class FrictionlessManager extends captchaManager.CaptchaManager {
|
|
|
164
246
|
let providerSelectEntropy;
|
|
165
247
|
let userId;
|
|
166
248
|
let userAgent;
|
|
249
|
+
let webView;
|
|
250
|
+
let iFrame;
|
|
251
|
+
let decryptedHeadHash = "";
|
|
167
252
|
for (const [keyIndex, key] of decryptKeys.entries()) {
|
|
168
253
|
try {
|
|
169
254
|
this.logger.info(() => ({
|
|
@@ -172,12 +257,15 @@ class FrictionlessManager extends captchaManager.CaptchaManager {
|
|
|
172
257
|
key: this.redactKeyForLogging(key)
|
|
173
258
|
}
|
|
174
259
|
}));
|
|
175
|
-
const decrypted = await getBotScore.getBotScore(token, key);
|
|
260
|
+
const decrypted = await getBotScore.getBotScore(token, headHash, key);
|
|
261
|
+
decryptedHeadHash = decrypted.decryptedHeadHash || "";
|
|
176
262
|
const s = decrypted.baseBotScore;
|
|
177
263
|
const t = decrypted.timestamp;
|
|
178
264
|
const p = decrypted.providerSelectEntropy;
|
|
179
265
|
const a = decrypted.userId;
|
|
180
266
|
const u = decrypted.userAgent;
|
|
267
|
+
const w = decrypted.isWebView;
|
|
268
|
+
const i = decrypted.isIframe;
|
|
181
269
|
this.logger.debug(() => ({
|
|
182
270
|
msg: "Successfully decrypted score",
|
|
183
271
|
data: {
|
|
@@ -186,7 +274,9 @@ class FrictionlessManager extends captchaManager.CaptchaManager {
|
|
|
186
274
|
timestamp: t,
|
|
187
275
|
entropy: p,
|
|
188
276
|
userId: a,
|
|
189
|
-
userAgent: u
|
|
277
|
+
userAgent: u,
|
|
278
|
+
webView: w,
|
|
279
|
+
iFrame: i
|
|
190
280
|
}
|
|
191
281
|
}));
|
|
192
282
|
baseBotScore = s;
|
|
@@ -194,6 +284,8 @@ class FrictionlessManager extends captchaManager.CaptchaManager {
|
|
|
194
284
|
providerSelectEntropy = p;
|
|
195
285
|
userId = a;
|
|
196
286
|
userAgent = u;
|
|
287
|
+
webView = w;
|
|
288
|
+
iFrame = i;
|
|
197
289
|
break;
|
|
198
290
|
} catch (err) {
|
|
199
291
|
if (keyIndex === decryptKeys.length - 1) {
|
|
@@ -203,6 +295,7 @@ class FrictionlessManager extends captchaManager.CaptchaManager {
|
|
|
203
295
|
baseBotScore = 1;
|
|
204
296
|
timestamp = 0;
|
|
205
297
|
providerSelectEntropy = DEFAULT_ENTROPY + 1;
|
|
298
|
+
decryptedHeadHash = "";
|
|
206
299
|
}
|
|
207
300
|
}
|
|
208
301
|
}
|
|
@@ -217,13 +310,19 @@ class FrictionlessManager extends captchaManager.CaptchaManager {
|
|
|
217
310
|
baseBotScore = 1;
|
|
218
311
|
timestamp = 0;
|
|
219
312
|
providerSelectEntropy = DEFAULT_ENTROPY - undefinedCount;
|
|
313
|
+
decryptedHeadHash = "";
|
|
220
314
|
}
|
|
221
315
|
this.logger.info(() => ({
|
|
222
316
|
msg: "decryptPayload result",
|
|
223
317
|
data: {
|
|
224
318
|
baseBotScore,
|
|
225
319
|
timestamp,
|
|
226
|
-
entropy: providerSelectEntropy
|
|
320
|
+
entropy: providerSelectEntropy,
|
|
321
|
+
userId,
|
|
322
|
+
userAgent,
|
|
323
|
+
webView,
|
|
324
|
+
iFrame,
|
|
325
|
+
decryptedHeadHash
|
|
227
326
|
}
|
|
228
327
|
}));
|
|
229
328
|
return {
|
|
@@ -231,9 +330,16 @@ class FrictionlessManager extends captchaManager.CaptchaManager {
|
|
|
231
330
|
timestamp: Number(timestamp),
|
|
232
331
|
providerSelectEntropy: Number(providerSelectEntropy),
|
|
233
332
|
userId,
|
|
234
|
-
userAgent
|
|
333
|
+
userAgent,
|
|
334
|
+
webView: webView || false,
|
|
335
|
+
iFrame: iFrame || false,
|
|
336
|
+
decryptedHeadHash
|
|
235
337
|
};
|
|
236
338
|
}
|
|
339
|
+
async getClientEntropy(siteKey) {
|
|
340
|
+
return this.db.getClientEntropy(siteKey);
|
|
341
|
+
}
|
|
237
342
|
}
|
|
238
343
|
exports.DEFAULT_ENTROPY = DEFAULT_ENTROPY;
|
|
239
344
|
exports.FrictionlessManager = FrictionlessManager;
|
|
345
|
+
exports.FrictionlessReason = FrictionlessReason;
|
|
@@ -11,11 +11,12 @@ const pairs = require("../../pairs.cjs");
|
|
|
11
11
|
const lang = require("../../rules/lang.cjs");
|
|
12
12
|
const util = require("../../util.cjs");
|
|
13
13
|
const captchaManager = require("../captchaManager.cjs");
|
|
14
|
+
const frictionlessTasks = require("../frictionless/frictionlessTasks.cjs");
|
|
14
15
|
const frictionlessTasksUtils = require("../frictionless/frictionlessTasksUtils.cjs");
|
|
15
16
|
const imgCaptchaTasksUtils = require("./imgCaptchaTasksUtils.cjs");
|
|
16
17
|
class ImgCaptchaManager extends captchaManager.CaptchaManager {
|
|
17
18
|
constructor(db, pair, config, logger) {
|
|
18
|
-
super(db, pair, logger);
|
|
19
|
+
super(db, pair, config, logger);
|
|
19
20
|
this.config = config;
|
|
20
21
|
}
|
|
21
22
|
async getCaptchaWithProof(datasetId, solved, size) {
|
|
@@ -32,7 +33,7 @@ class ImgCaptchaManager extends captchaManager.CaptchaManager {
|
|
|
32
33
|
}
|
|
33
34
|
return captchaDocs;
|
|
34
35
|
}
|
|
35
|
-
async getRandomCaptchasAndRequestHash(datasetId, userAccount, ipAddress, captchaConfig, threshold,
|
|
36
|
+
async getRandomCaptchasAndRequestHash(datasetId, userAccount, ipAddress, captchaConfig, threshold, sessionId) {
|
|
36
37
|
const dataset = await this.db.getDatasetDetails(datasetId);
|
|
37
38
|
if (!dataset) {
|
|
38
39
|
throw new common.ProsopoEnvError("DATABASE.DATASET_GET_FAILED", {
|
|
@@ -82,7 +83,7 @@ class ImgCaptchaManager extends captchaManager.CaptchaManager {
|
|
|
82
83
|
currentTime,
|
|
83
84
|
compositeIpAddress.getCompositeIpAddress(ipAddress),
|
|
84
85
|
threshold,
|
|
85
|
-
|
|
86
|
+
sessionId
|
|
86
87
|
);
|
|
87
88
|
return {
|
|
88
89
|
captchas,
|
|
@@ -174,10 +175,10 @@ class ImgCaptchaManager extends captchaManager.CaptchaManager {
|
|
|
174
175
|
userSignature: userTimestampSignature,
|
|
175
176
|
userSubmitted: true,
|
|
176
177
|
serverChecked: false,
|
|
177
|
-
requestedAtTimestamp: timestamp,
|
|
178
|
+
requestedAtTimestamp: new Date(timestamp),
|
|
178
179
|
ipAddress: compositeIpAddress.getCompositeIpAddress(ipAddress),
|
|
179
180
|
headers,
|
|
180
|
-
|
|
181
|
+
sessionId: pendingRecord.sessionId,
|
|
181
182
|
ja4
|
|
182
183
|
};
|
|
183
184
|
await this.db.storeUserImageCaptchaSolution(receivedCaptchas, commit);
|
|
@@ -334,7 +335,7 @@ class ImgCaptchaManager extends captchaManager.CaptchaManager {
|
|
|
334
335
|
}
|
|
335
336
|
return void 0;
|
|
336
337
|
}
|
|
337
|
-
async verifyImageCaptchaSolution(user, dapp, commitmentId, env, maxVerifiedTime, ip) {
|
|
338
|
+
async verifyImageCaptchaSolution(user, dapp, commitmentId, env, maxVerifiedTime, ip, disallowWebView, contextAwareEnabled = false) {
|
|
338
339
|
const solution = await (commitmentId ? this.getDappUserCommitmentById(commitmentId) : this.getDappUserCommitmentByAccount(user, dapp));
|
|
339
340
|
if (!solution) {
|
|
340
341
|
this.logger.debug(() => ({
|
|
@@ -351,7 +352,7 @@ class ImgCaptchaManager extends captchaManager.CaptchaManager {
|
|
|
351
352
|
}
|
|
352
353
|
maxVerifiedTime = maxVerifiedTime || 60 * 1e3;
|
|
353
354
|
const currentTime = Date.now();
|
|
354
|
-
const timeSinceCompletion = currentTime - solution.requestedAtTimestamp;
|
|
355
|
+
const timeSinceCompletion = currentTime - solution.requestedAtTimestamp.getTime();
|
|
355
356
|
if (timeSinceCompletion > maxVerifiedTime) {
|
|
356
357
|
this.logger.debug(() => ({
|
|
357
358
|
msg: "Not verified - timed out"
|
|
@@ -391,18 +392,29 @@ class ImgCaptchaManager extends captchaManager.CaptchaManager {
|
|
|
391
392
|
}
|
|
392
393
|
const isApproved = solution.result.status === types.CaptchaStatus.approved;
|
|
393
394
|
let score;
|
|
394
|
-
if (solution.
|
|
395
|
-
const
|
|
396
|
-
solution.
|
|
395
|
+
if (solution.sessionId) {
|
|
396
|
+
const sessionRecord = await this.db.getSessionRecordBySessionId(
|
|
397
|
+
solution.sessionId
|
|
397
398
|
);
|
|
398
|
-
if (
|
|
399
|
-
score = frictionlessTasksUtils.computeFrictionlessScore(
|
|
399
|
+
if (sessionRecord) {
|
|
400
|
+
score = frictionlessTasksUtils.computeFrictionlessScore(sessionRecord?.scoreComponents);
|
|
400
401
|
this.logger.info(() => ({
|
|
401
402
|
data: {
|
|
402
|
-
|
|
403
|
+
scoreComponents: sessionRecord?.scoreComponents,
|
|
403
404
|
score
|
|
404
405
|
}
|
|
405
406
|
}));
|
|
407
|
+
if (disallowWebView === true && (sessionRecord.scoreComponents.webView || 0) > 0) {
|
|
408
|
+
this.logger.info(() => ({
|
|
409
|
+
msg: "Disallowing webview access - user not verified"
|
|
410
|
+
}));
|
|
411
|
+
return { status: "API.USER_NOT_VERIFIED", verified: false };
|
|
412
|
+
}
|
|
413
|
+
if (contextAwareEnabled && sessionRecord.reason === frictionlessTasks.FrictionlessReason.CONTEXT_AWARE_VALIDATION_FAILED) {
|
|
414
|
+
this.logger.info(() => ({
|
|
415
|
+
msg: "Context aware validation failed"
|
|
416
|
+
}));
|
|
417
|
+
}
|
|
406
418
|
}
|
|
407
419
|
}
|
|
408
420
|
return {
|
|
@@ -11,8 +11,8 @@ const frictionlessTasksUtils = require("../frictionless/frictionlessTasksUtils.c
|
|
|
11
11
|
const powTasksUtils = require("./powTasksUtils.cjs");
|
|
12
12
|
const DEFAULT_POW_DIFFICULTY = 4;
|
|
13
13
|
class PowCaptchaManager extends captchaManager.CaptchaManager {
|
|
14
|
-
constructor(db, pair, logger) {
|
|
15
|
-
super(db, pair, logger);
|
|
14
|
+
constructor(db, pair, config, logger) {
|
|
15
|
+
super(db, pair, config, logger);
|
|
16
16
|
this.POW_SEPARATOR = types.POW_SEPARATOR;
|
|
17
17
|
}
|
|
18
18
|
/**
|
|
@@ -179,15 +179,15 @@ class PowCaptchaManager extends captchaManager.CaptchaManager {
|
|
|
179
179
|
}
|
|
180
180
|
}
|
|
181
181
|
let score;
|
|
182
|
-
if (challengeRecord.
|
|
183
|
-
const
|
|
184
|
-
challengeRecord.
|
|
182
|
+
if (challengeRecord.sessionId) {
|
|
183
|
+
const sessionRecord = await this.db.getSessionRecordBySessionId(
|
|
184
|
+
challengeRecord.sessionId
|
|
185
185
|
);
|
|
186
|
-
if (
|
|
187
|
-
score = frictionlessTasksUtils.computeFrictionlessScore(
|
|
186
|
+
if (sessionRecord) {
|
|
187
|
+
score = frictionlessTasksUtils.computeFrictionlessScore(sessionRecord?.scoreComponents);
|
|
188
188
|
this.logger.info(() => ({
|
|
189
189
|
data: {
|
|
190
|
-
|
|
190
|
+
scoreComponents: { ...sessionRecord?.scoreComponents || {} },
|
|
191
191
|
score
|
|
192
192
|
}
|
|
193
193
|
}));
|
package/dist/cjs/tasks/tasks.cjs
CHANGED
package/dist/cjs/util.cjs
CHANGED
|
@@ -34,7 +34,7 @@ async function checkIfTaskIsRunning(taskName, db) {
|
|
|
34
34
|
types.ScheduledTaskStatus.Running
|
|
35
35
|
);
|
|
36
36
|
const twoMinutesAgo = (/* @__PURE__ */ new Date()).getTime() - 1e3 * 60 * 2;
|
|
37
|
-
if (runningTask && runningTask.datetime > twoMinutesAgo) {
|
|
37
|
+
if (runningTask && runningTask.datetime.getTime() > twoMinutesAgo) {
|
|
38
38
|
const completedTask = await db.getScheduledTaskStatus(
|
|
39
39
|
runningTask._id,
|
|
40
40
|
types.ScheduledTaskStatus.Completed
|
|
@@ -231,6 +231,19 @@ const deepValidateIpAddress = async (ip, challengeIpAddress, logger, apiKey, api
|
|
|
231
231
|
if (standardValidation.errorMessage?.includes("Invalid IP address")) {
|
|
232
232
|
return standardValidation;
|
|
233
233
|
}
|
|
234
|
+
if (ipValidationRules?.forceConsistentIp === true) {
|
|
235
|
+
logger.info(() => ({
|
|
236
|
+
msg: "IP validation failed - forceConsistentIp is true",
|
|
237
|
+
data: {
|
|
238
|
+
challengeIp: challengeIpAddress.address,
|
|
239
|
+
providedIp: ip
|
|
240
|
+
}
|
|
241
|
+
}));
|
|
242
|
+
return {
|
|
243
|
+
isValid: false,
|
|
244
|
+
errorMessage: standardValidation.errorMessage
|
|
245
|
+
};
|
|
246
|
+
}
|
|
234
247
|
} else {
|
|
235
248
|
return { isValid: true };
|
|
236
249
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const node_crypto = require("node:crypto");
|
|
4
|
+
function hashUserIp(user, ip, sitekey) {
|
|
5
|
+
const hash = node_crypto.createHash("sha256");
|
|
6
|
+
hash.update(`${user}:${ip}:${sitekey}`, "utf8");
|
|
7
|
+
return hash.digest("hex");
|
|
8
|
+
}
|
|
9
|
+
exports.hashUserIp = hashUserIp;
|
package/dist/index.js
CHANGED
|
@@ -8,6 +8,7 @@ import { publicRouter } from "./api/public.js";
|
|
|
8
8
|
import { domainMiddleware } from "./api/domainMiddleware.js";
|
|
9
9
|
import { storeCaptchasExternally } from "./schedulers/captchaScheduler.js";
|
|
10
10
|
import { getClientList } from "./schedulers/getClientList.js";
|
|
11
|
+
import { setClientEntropy } from "./schedulers/setClientEntropy.js";
|
|
11
12
|
import { headerCheckMiddleware } from "./api/headerCheckMiddleware.js";
|
|
12
13
|
import { createApiAdminRoutesProvider } from "./api/admin/createApiAdminRoutesProvider.js";
|
|
13
14
|
import { ignoreMiddleware } from "./api/ignoreMiddleware.js";
|
|
@@ -39,6 +40,7 @@ export {
|
|
|
39
40
|
prosopoVerifyRouter,
|
|
40
41
|
publicRouter,
|
|
41
42
|
robotsMiddleware,
|
|
43
|
+
setClientEntropy,
|
|
42
44
|
shuffleArray,
|
|
43
45
|
storeCaptchasExternally,
|
|
44
46
|
validateIpAddress
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { ProviderEnvironment } from "@prosopo/env";
|
|
2
|
+
import { ScheduledTaskNames } from "@prosopo/types";
|
|
3
|
+
import { CronJob } from "cron";
|
|
4
|
+
import { Tasks } from "../tasks/tasks.js";
|
|
5
|
+
import { checkIfTaskIsRunning } from "../util.js";
|
|
6
|
+
async function setClientEntropy(pair, cronSchedule, config) {
|
|
7
|
+
const env = new ProviderEnvironment(config, pair);
|
|
8
|
+
await env.isReady();
|
|
9
|
+
const tasks = new Tasks(env);
|
|
10
|
+
const job = new CronJob(cronSchedule, async () => {
|
|
11
|
+
const taskRunning = await checkIfTaskIsRunning(
|
|
12
|
+
ScheduledTaskNames.SetClientEntropy,
|
|
13
|
+
env.getDb()
|
|
14
|
+
);
|
|
15
|
+
env.logger.info(() => ({
|
|
16
|
+
msg: `${ScheduledTaskNames.SetClientEntropy} task running: ${taskRunning}`,
|
|
17
|
+
data: { taskRunning }
|
|
18
|
+
}));
|
|
19
|
+
if (!taskRunning) {
|
|
20
|
+
env.logger.info(() => ({
|
|
21
|
+
msg: `${ScheduledTaskNames.SetClientEntropy} task....`,
|
|
22
|
+
data: {}
|
|
23
|
+
}));
|
|
24
|
+
await tasks.clientTaskManager.calculateClientEntropy().catch((err) => {
|
|
25
|
+
env.logger.error(() => ({
|
|
26
|
+
err,
|
|
27
|
+
msg: "Error setting client entropy"
|
|
28
|
+
}));
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
job.start();
|
|
33
|
+
}
|
|
34
|
+
export {
|
|
35
|
+
setClientEntropy
|
|
36
|
+
};
|
|
@@ -2,28 +2,13 @@ import { getLogger } from "@prosopo/common";
|
|
|
2
2
|
import { CaptchaType, ApiParams, Tier } from "@prosopo/types";
|
|
3
3
|
import { getPrioritisedAccessRule } from "../api/blacklistRequestInspector.js";
|
|
4
4
|
class CaptchaManager {
|
|
5
|
-
constructor(db, pair, logger) {
|
|
5
|
+
constructor(db, pair, config, logger) {
|
|
6
6
|
this.pair = pair;
|
|
7
7
|
this.db = db;
|
|
8
|
+
this.config = config;
|
|
8
9
|
this.logger = logger || getLogger("info", import.meta.url);
|
|
9
10
|
}
|
|
10
|
-
async
|
|
11
|
-
const tokenRecord = await this.db.getFrictionlessTokenRecordByTokenId(
|
|
12
|
-
sessionRecord.tokenId
|
|
13
|
-
);
|
|
14
|
-
return tokenRecord ? tokenRecord._id : void 0;
|
|
15
|
-
}
|
|
16
|
-
async validateFrictionlessTokenIP(sessionRecord, currentIP, env) {
|
|
17
|
-
const tokenRecord = await this.db.getFrictionlessTokenRecordByTokenId(
|
|
18
|
-
sessionRecord.tokenId
|
|
19
|
-
);
|
|
20
|
-
if (!tokenRecord) {
|
|
21
|
-
this.logger.info(() => ({
|
|
22
|
-
msg: "No frictionless token found for session",
|
|
23
|
-
data: { sessionId: sessionRecord.sessionId }
|
|
24
|
-
}));
|
|
25
|
-
return { valid: false, reason: "CAPTCHA.NO_SESSION_FOUND" };
|
|
26
|
-
}
|
|
11
|
+
async validateSessionIP(sessionRecord, currentIP, env) {
|
|
27
12
|
return { valid: true };
|
|
28
13
|
}
|
|
29
14
|
async isValidRequest(clientSettings, requestedCaptchaType, env, sessionId, userAccessPolicy, currentIP) {
|
|
@@ -66,7 +51,7 @@ class CaptchaManager {
|
|
|
66
51
|
};
|
|
67
52
|
}
|
|
68
53
|
if (currentIP) {
|
|
69
|
-
const ipValidation = await this.
|
|
54
|
+
const ipValidation = await this.validateSessionIP(
|
|
70
55
|
sessionRecord,
|
|
71
56
|
currentIP,
|
|
72
57
|
env
|
|
@@ -79,7 +64,6 @@ class CaptchaManager {
|
|
|
79
64
|
};
|
|
80
65
|
}
|
|
81
66
|
}
|
|
82
|
-
const frictionlessTokenId = await this.getFrictionlessTokenIdFromSession(sessionRecord);
|
|
83
67
|
if (sessionRecord.captchaType !== requestedCaptchaType) {
|
|
84
68
|
this.logger.warn(() => ({
|
|
85
69
|
msg: "Invalid frictionless request",
|
|
@@ -96,7 +80,7 @@ class CaptchaManager {
|
|
|
96
80
|
}
|
|
97
81
|
return {
|
|
98
82
|
valid: true,
|
|
99
|
-
|
|
83
|
+
sessionId: sessionRecord.sessionId,
|
|
100
84
|
type: requestedCaptchaType,
|
|
101
85
|
...sessionRecord.powDifficulty && {
|
|
102
86
|
powDifficulty: sessionRecord.powDifficulty
|