@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.
Files changed (49) hide show
  1. package/CHANGELOG.md +214 -0
  2. package/dist/api/admin/apiAdminRoutesProvider.js +13 -18
  3. package/dist/api/admin/apiToggleMaintenanceModeEndpoint.js +40 -0
  4. package/dist/api/blacklistRequestInspector.js +4 -4
  5. package/dist/api/captcha/getFrictionlessCaptchaChallenge.js +338 -0
  6. package/dist/api/captcha/getImageCaptchaChallenge.js +150 -0
  7. package/dist/api/captcha/getPoWCaptchaChallenge.js +156 -0
  8. package/dist/api/captcha/submitImageCaptchaSolution.js +87 -0
  9. package/dist/api/captcha/submitPoWCaptchaSolution.js +77 -0
  10. package/dist/api/captcha.js +18 -606
  11. package/dist/api/verify.js +24 -1
  12. package/dist/cjs/api/admin/apiAdminRoutesProvider.cjs +13 -18
  13. package/dist/cjs/api/admin/apiRegisterSiteKeyEndpoint.cjs +2 -1
  14. package/dist/cjs/api/admin/apiRemoveDetectorKeyEndpoint.cjs +3 -2
  15. package/dist/cjs/api/admin/apiToggleMaintenanceModeEndpoint.cjs +41 -0
  16. package/dist/cjs/api/blacklistRequestInspector.cjs +3 -3
  17. package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge.cjs +337 -0
  18. package/dist/cjs/api/captcha/getImageCaptchaChallenge.cjs +149 -0
  19. package/dist/cjs/api/captcha/getPoWCaptchaChallenge.cjs +155 -0
  20. package/dist/cjs/api/captcha/submitImageCaptchaSolution.cjs +86 -0
  21. package/dist/cjs/api/captcha/submitPoWCaptchaSolution.cjs +76 -0
  22. package/dist/cjs/api/captcha.cjs +17 -605
  23. package/dist/cjs/api/ja4Middleware.cjs +2 -1
  24. package/dist/cjs/api/verify.cjs +24 -1
  25. package/dist/cjs/index.cjs +2 -0
  26. package/dist/cjs/schedulers/setClientEntropy.cjs +36 -0
  27. package/dist/cjs/tasks/captchaManager.cjs +7 -22
  28. package/dist/cjs/tasks/client/clientTasks.cjs +18 -36
  29. package/dist/cjs/tasks/detection/decodePayload.cjs +385 -714
  30. package/dist/cjs/tasks/detection/getBotScore.cjs +15 -2
  31. package/dist/cjs/tasks/frictionless/frictionlessTasks.cjs +136 -30
  32. package/dist/cjs/tasks/imgCaptcha/imgCaptchaTasks.cjs +25 -13
  33. package/dist/cjs/tasks/powCaptcha/powTasks.cjs +8 -8
  34. package/dist/cjs/tasks/tasks.cjs +1 -0
  35. package/dist/cjs/util.cjs +14 -1
  36. package/dist/cjs/utils/hashUserIp.cjs +9 -0
  37. package/dist/index.js +2 -0
  38. package/dist/schedulers/setClientEntropy.js +36 -0
  39. package/dist/tasks/captchaManager.js +5 -21
  40. package/dist/tasks/client/clientTasks.js +19 -37
  41. package/dist/tasks/detection/decodePayload.js +385 -714
  42. package/dist/tasks/detection/getBotScore.js +17 -4
  43. package/dist/tasks/frictionless/frictionlessTasks.js +137 -31
  44. package/dist/tasks/imgCaptcha/imgCaptchaTasks.js +25 -13
  45. package/dist/tasks/powCaptcha/powTasks.js +8 -8
  46. package/dist/tasks/tasks.js +1 -0
  47. package/dist/util.js +14 -1
  48. package/dist/utils/hashUserIp.js +9 -0
  49. 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 { baseBotScore, timestamp, providerSelectEntropy, userId, userAgent };
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(tokenId, captchaType, solvedImagesCount, powDifficulty) {
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
- tokenId,
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(tokenId, solvedImagesCount) {
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
- tokenId,
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(tokenId, powDifficulty) {
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
- tokenId,
133
+ effectiveParams.token,
134
+ effectiveParams.score,
135
+ effectiveParams.threshold,
136
+ effectiveParams.scoreComponents,
137
+ effectiveParams.providerSelectEntropy,
138
+ effectiveParams.ipAddress,
73
139
  types.CaptchaType.pow,
74
- powDifficulty
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
- async scoreIncreaseAccessPolicy(accessPolicy, baseBotScore, botScore, tokenId) {
154
+ scoreIncreaseAccessPolicy(accessPolicy, baseBotScore, botScore, scoreComponents) {
83
155
  const accessPolicyPenalty = accessPolicy?.frictionlessScore || this.config.penalties.PENALTY_ACCESS_RULE;
84
156
  botScore += accessPolicyPenalty;
85
- await this.db.updateFrictionlessTokenRecord(tokenId, {
157
+ return {
86
158
  score: botScore,
87
159
  scoreComponents: {
88
- baseScore: baseBotScore,
160
+ ...scoreComponents,
89
161
  accessPolicy: accessPolicyPenalty
90
162
  }
91
- });
92
- return botScore;
163
+ };
93
164
  }
94
- async scoreIncreaseUnverifiedHost(host, baseBotScore, botScore, tokenId) {
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
- await this.db.updateFrictionlessTokenRecord(tokenId, {
171
+ return {
101
172
  score: botScore,
102
173
  scoreComponents: {
103
- baseScore: baseBotScore,
174
+ ...scoreComponents,
104
175
  unverifiedHost: this.config.penalties.PENALTY_UNVERIFIED_HOST
105
176
  }
106
- });
107
- return botScore;
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
- async scoreIncreaseTimestamp(timestamp, baseBotScore, botScore, tokenId) {
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
- await this.db.updateFrictionlessTokenRecord(tokenId, {
198
+ return {
116
199
  score: botScore,
117
200
  scoreComponents: {
118
- baseScore: baseBotScore,
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, frictionlessTokenId) {
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
- frictionlessTokenId
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
- frictionlessTokenId: pendingRecord.frictionlessTokenId,
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.frictionlessTokenId) {
395
- const tokenRecord = await this.db.getFrictionlessTokenRecordByTokenId(
396
- solution.frictionlessTokenId
395
+ if (solution.sessionId) {
396
+ const sessionRecord = await this.db.getSessionRecordBySessionId(
397
+ solution.sessionId
397
398
  );
398
- if (tokenRecord) {
399
- score = frictionlessTasksUtils.computeFrictionlessScore(tokenRecord?.scoreComponents);
399
+ if (sessionRecord) {
400
+ score = frictionlessTasksUtils.computeFrictionlessScore(sessionRecord?.scoreComponents);
400
401
  this.logger.info(() => ({
401
402
  data: {
402
- tscoreComponents: tokenRecord?.scoreComponents,
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.frictionlessTokenId) {
183
- const tokenRecord = await this.db.getFrictionlessTokenRecordByTokenId(
184
- challengeRecord.frictionlessTokenId
182
+ if (challengeRecord.sessionId) {
183
+ const sessionRecord = await this.db.getSessionRecordBySessionId(
184
+ challengeRecord.sessionId
185
185
  );
186
- if (tokenRecord) {
187
- score = frictionlessTasksUtils.computeFrictionlessScore(tokenRecord?.scoreComponents);
186
+ if (sessionRecord) {
187
+ score = frictionlessTasksUtils.computeFrictionlessScore(sessionRecord?.scoreComponents);
188
188
  this.logger.info(() => ({
189
189
  data: {
190
- tscoreComponents: { ...tokenRecord?.scoreComponents || {} },
190
+ scoreComponents: { ...sessionRecord?.scoreComponents || {} },
191
191
  score
192
192
  }
193
193
  }));
@@ -21,6 +21,7 @@ class Tasks {
21
21
  this.powCaptchaManager = new powTasks.PowCaptchaManager(
22
22
  this.db,
23
23
  this.pair,
24
+ this.config,
24
25
  this.logger
25
26
  );
26
27
  this.datasetManager = new datasetTasks.DatasetManager(
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 getFrictionlessTokenIdFromSession(sessionRecord) {
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.validateFrictionlessTokenIP(
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
- frictionlessTokenId,
83
+ sessionId: sessionRecord.sessionId,
100
84
  type: requestedCaptchaType,
101
85
  ...sessionRecord.powDifficulty && {
102
86
  powDifficulty: sessionRecord.powDifficulty