@prosopo/provider 3.2.5 → 3.12.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.
Files changed (52) hide show
  1. package/CHANGELOG.md +578 -0
  2. package/dist/api/admin/apiRemoveDetectorKeyEndpoint.js +7 -4
  3. package/dist/api/blacklistRequestInspector.js +26 -20
  4. package/dist/api/captcha.js +121 -33
  5. package/dist/api/domainMiddleware.js +8 -8
  6. package/dist/api/headerCheckMiddleware.js +4 -0
  7. package/dist/api/ignoreMiddleware.js +4 -1
  8. package/dist/api/ja4Middleware.js +5 -23
  9. package/dist/api/public.js +26 -3
  10. package/dist/api/verify.js +4 -2
  11. package/dist/cjs/api/admin/apiRemoveDetectorKeyEndpoint.cjs +6 -3
  12. package/dist/cjs/api/blacklistRequestInspector.cjs +25 -19
  13. package/dist/cjs/api/captcha.cjs +121 -33
  14. package/dist/cjs/api/domainMiddleware.cjs +8 -8
  15. package/dist/cjs/api/headerCheckMiddleware.cjs +4 -0
  16. package/dist/cjs/api/ignoreMiddleware.cjs +3 -0
  17. package/dist/cjs/api/ja4Middleware.cjs +4 -22
  18. package/dist/cjs/api/public.cjs +26 -3
  19. package/dist/cjs/api/verify.cjs +4 -2
  20. package/dist/cjs/compositeIpAddress.cjs +53 -0
  21. package/dist/cjs/index.cjs +7 -0
  22. package/dist/cjs/pairs.cjs +27 -0
  23. package/dist/cjs/services/ipComparison.cjs +123 -0
  24. package/dist/cjs/services/ipInfo.cjs +87 -0
  25. package/dist/cjs/tasks/captchaManager.cjs +49 -2
  26. package/dist/cjs/tasks/client/clientTasks.cjs +33 -12
  27. package/dist/cjs/tasks/detection/decodePayload.cjs +712 -278
  28. package/dist/cjs/tasks/detection/getBotScore.cjs +14 -3
  29. package/dist/cjs/tasks/frictionless/frictionlessTasks.cjs +128 -24
  30. package/dist/cjs/tasks/frictionless/frictionlessTasksUtils.cjs +17 -0
  31. package/dist/cjs/tasks/imgCaptcha/imgCaptchaTasks.cjs +62 -20
  32. package/dist/cjs/tasks/powCaptcha/powTasks.cjs +43 -15
  33. package/dist/cjs/util.cjs +248 -16
  34. package/dist/cjs/utils/hashUserAgent.cjs +10 -0
  35. package/dist/compositeIpAddress.js +53 -0
  36. package/dist/index.js +8 -1
  37. package/dist/pairs.js +27 -0
  38. package/dist/services/ipComparison.js +123 -0
  39. package/dist/services/ipInfo.js +87 -0
  40. package/dist/tasks/captchaManager.js +49 -2
  41. package/dist/tasks/client/clientTasks.js +33 -12
  42. package/dist/tasks/detection/decodePayload.js +712 -278
  43. package/dist/tasks/detection/getBotScore.js +15 -4
  44. package/dist/tasks/frictionless/frictionlessTasks.js +128 -24
  45. package/dist/tasks/frictionless/frictionlessTasksUtils.js +18 -1
  46. package/dist/tasks/imgCaptcha/imgCaptchaTasks.js +64 -22
  47. package/dist/tasks/powCaptcha/powTasks.js +44 -16
  48. package/dist/util.js +249 -17
  49. package/dist/utils/hashUserAgent.js +10 -0
  50. package/package.json +28 -25
  51. package/vite.test.config.ts +3 -2
  52. package/vite.threads.test.config.ts +33 -0
@@ -4,8 +4,11 @@ import { parseCaptchaAssets } from "@prosopo/datasets";
4
4
  import { ClientApiPaths, CaptchaRequestBody, CaptchaType, ApiParams, CaptchaSolutionBody, GetPowCaptchaChallengeRequestBody, SubmitPowCaptchaSolutionBody, GetFrictionlessCaptchaChallengeRequestBody } from "@prosopo/types";
5
5
  import { getIPAddress, flatten } from "@prosopo/util";
6
6
  import express from "express";
7
+ import { getCompositeIpAddress } from "../compositeIpAddress.js";
7
8
  import { FrictionlessManager } from "../tasks/frictionless/frictionlessTasks.js";
9
+ import { timestampDecayFunction } from "../tasks/frictionless/frictionlessTasksUtils.js";
8
10
  import { Tasks } from "../tasks/tasks.js";
11
+ import { hashUserAgent } from "../utils/hashUserAgent.js";
9
12
  import { getRequestUserScope } from "./blacklistRequestInspector.js";
10
13
  import { validateSiteKey, validateAddr } from "./validateAddress.js";
11
14
  const DEFAULT_FRICTIONLESS_THRESHOLD = 0.5;
@@ -63,11 +66,13 @@ function prosopoRouter(env) {
63
66
  dapp,
64
67
  userScope
65
68
  ))[0];
66
- const { valid, reason, frictionlessTokenId } = await tasks.imgCaptchaManager.isValidRequest(
69
+ const { valid, reason, frictionlessTokenId, solvedImagesCount } = await tasks.imgCaptchaManager.isValidRequest(
67
70
  clientRecord,
68
71
  CaptchaType.image,
72
+ env,
69
73
  sessionId,
70
- userAccessPolicy
74
+ userAccessPolicy,
75
+ req.ip
71
76
  );
72
77
  if (!valid) {
73
78
  return next(
@@ -84,7 +89,7 @@ function prosopoRouter(env) {
84
89
  }
85
90
  const captchaConfig = {
86
91
  solved: {
87
- count: userAccessPolicy?.solvedImagesCount || env.config.captchas.solved.count
92
+ count: solvedImagesCount || userAccessPolicy?.solvedImagesCount || env.config.captchas.solved.count
88
93
  },
89
94
  unsolved: {
90
95
  count: userAccessPolicy?.unsolvedImagesCount || env.config.captchas.unsolved.count
@@ -115,12 +120,23 @@ function prosopoRouter(env) {
115
120
  }
116
121
  }
117
122
  };
123
+ req.logger.info(() => ({
124
+ msg: "Image captcha challenge issued",
125
+ data: {
126
+ captchaType: CaptchaType.image,
127
+ requestHash: taskData.requestHash,
128
+ solvedImagesCount: captchaConfig.solved.count,
129
+ user,
130
+ dapp,
131
+ sessionId
132
+ }
133
+ }));
118
134
  return res.json(captchaResponse);
119
135
  } catch (err) {
120
136
  req.logger.error(() => ({
121
137
  err,
122
138
  data: req.params,
123
- msg: "Error in PoW captcha solution submission"
139
+ msg: "Error in image captcha challenge request"
124
140
  }));
125
141
  return next(
126
142
  new ProsopoApiError("API.BAD_REQUEST", {
@@ -175,7 +191,7 @@ function prosopoRouter(env) {
175
191
  parsed[ApiParams.signature].user.timestamp,
176
192
  Number.parseInt(parsed[ApiParams.timestamp]),
177
193
  parsed[ApiParams.signature].provider.requestHash,
178
- getIPAddress(req.ip || "").bigInt(),
194
+ getIPAddress(req.ip || ""),
179
195
  flatten(req.headers),
180
196
  req.ja4
181
197
  );
@@ -190,7 +206,7 @@ function prosopoRouter(env) {
190
206
  req.logger.error(() => ({
191
207
  err,
192
208
  body: req.body,
193
- msg: "Error in PoW captcha solution submission"
209
+ msg: "Error in image captcha solution submission"
194
210
  }));
195
211
  return next(
196
212
  new ProsopoApiError("API.BAD_REQUEST", {
@@ -246,11 +262,13 @@ function prosopoRouter(env) {
246
262
  dapp,
247
263
  userScope
248
264
  ))[0];
249
- const { valid, reason, frictionlessTokenId } = await tasks.powCaptchaManager.isValidRequest(
265
+ const { valid, reason, frictionlessTokenId, powDifficulty } = await tasks.powCaptchaManager.isValidRequest(
250
266
  clientSettings,
251
267
  CaptchaType.pow,
268
+ env,
252
269
  sessionId,
253
- userAccessPolicy
270
+ userAccessPolicy,
271
+ req.ip
254
272
  );
255
273
  if (!valid) {
256
274
  return next(
@@ -280,11 +298,12 @@ function prosopoRouter(env) {
280
298
  })
281
299
  );
282
300
  }
301
+ const difficulty = powDifficulty || userAccessPolicy?.powDifficulty || clientSettings?.settings?.powDifficulty;
283
302
  const challenge = await tasks.powCaptchaManager.getPowCaptchaChallenge(
284
303
  user,
285
304
  dapp,
286
305
  origin,
287
- userAccessPolicy?.powDifficulty || clientSettings?.settings?.powDifficulty
306
+ difficulty
288
307
  );
289
308
  await tasks.db.storePowCaptchaRecord(
290
309
  challenge.challenge,
@@ -295,7 +314,7 @@ function prosopoRouter(env) {
295
314
  },
296
315
  challenge.difficulty,
297
316
  challenge.providerSignature,
298
- getIPAddress(req.ip || "").bigInt(),
317
+ getCompositeIpAddress(req.ip || ""),
299
318
  flatten(req.headers),
300
319
  req.ja4,
301
320
  frictionlessTokenId
@@ -311,12 +330,23 @@ function prosopoRouter(env) {
311
330
  }
312
331
  }
313
332
  };
333
+ req.logger.info(() => ({
334
+ msg: "PoW captcha challenge issued",
335
+ data: {
336
+ captchaType: CaptchaType.pow,
337
+ challenge: challenge.challenge,
338
+ difficulty: challenge.difficulty,
339
+ user,
340
+ dapp,
341
+ session: sessionId
342
+ }
343
+ }));
314
344
  return res.json(getPowCaptchaResponse);
315
345
  } catch (err) {
316
346
  req.logger.error(() => ({
317
347
  err,
318
348
  body: req.body,
319
- msg: "Error in PoW captcha solution submission"
349
+ msg: "Error in PoW captcha challenge request"
320
350
  }));
321
351
  return next(
322
352
  new ProsopoApiError("API.BAD_REQUEST", {
@@ -348,15 +378,7 @@ function prosopoRouter(env) {
348
378
  })
349
379
  );
350
380
  }
351
- const {
352
- challenge,
353
- difficulty,
354
- signature,
355
- nonce,
356
- verifiedTimeout,
357
- dapp,
358
- user
359
- } = parsed;
381
+ const { challenge, signature, nonce, verifiedTimeout, dapp, user } = parsed;
360
382
  validateSiteKey(dapp);
361
383
  validateAddr(user);
362
384
  try {
@@ -372,7 +394,6 @@ function prosopoRouter(env) {
372
394
  }
373
395
  const verified = await tasks.powCaptchaManager.verifyPowCaptchaSolution(
374
396
  challenge,
375
- difficulty,
376
397
  signature.provider.challenge,
377
398
  nonce,
378
399
  verifiedTimeout,
@@ -414,17 +435,39 @@ function prosopoRouter(env) {
414
435
  token: existingToken,
415
436
  msg: "Token has already been used"
416
437
  }));
417
- return res.json(
418
- await tasks.frictionlessManager.sendImageCaptcha(
419
- existingToken._id
420
- )
438
+ return next(
439
+ new ProsopoApiError("API.BAD_REQUEST", {
440
+ context: {
441
+ code: 400,
442
+ siteKey: dapp,
443
+ user
444
+ },
445
+ i18n: req.i18n,
446
+ logger: req.logger
447
+ })
421
448
  );
422
449
  }
423
450
  const lScore = tasks.frictionlessManager.checkLangRules(
424
451
  req.headers["accept-language"] || ""
425
452
  );
426
- const { baseBotScore, timestamp } = await tasks.frictionlessManager.decryptPayload(token);
427
- const botScore = baseBotScore + lScore;
453
+ const {
454
+ baseBotScore,
455
+ timestamp,
456
+ providerSelectEntropy,
457
+ userId,
458
+ userAgent
459
+ } = await tasks.frictionlessManager.decryptPayload(token);
460
+ req.logger.debug(() => ({
461
+ msg: "Decrypted payload",
462
+ data: {
463
+ baseBotScore,
464
+ timestamp,
465
+ providerSelectEntropy,
466
+ userId,
467
+ userAgent
468
+ }
469
+ }));
470
+ let botScore = baseBotScore + lScore;
428
471
  const clientRecord = await tasks.db.getClientRecord(dapp);
429
472
  if (!clientRecord) {
430
473
  return next(
@@ -437,7 +480,8 @@ function prosopoRouter(env) {
437
480
  }
438
481
  const { valid, reason } = await tasks.frictionlessManager.isValidRequest(
439
482
  clientRecord,
440
- CaptchaType.frictionless
483
+ CaptchaType.frictionless,
484
+ env
441
485
  );
442
486
  if (!valid) {
443
487
  return next(
@@ -460,7 +504,9 @@ function prosopoRouter(env) {
460
504
  scoreComponents: {
461
505
  baseScore: baseBotScore,
462
506
  ...lScore && { lScore }
463
- }
507
+ },
508
+ providerSelectEntropy,
509
+ ipAddress: getCompositeIpAddress(req.ip || "")
464
510
  });
465
511
  const userScope = getRequestUserScope(
466
512
  flatten(req.headers),
@@ -473,6 +519,28 @@ function prosopoRouter(env) {
473
519
  dapp,
474
520
  userScope
475
521
  ))[0];
522
+ const headersUserAgent = req.headers["user-agent"];
523
+ const hashedHeadersUserAgent = headersUserAgent ? hashUserAgent(headersUserAgent) : "";
524
+ const headersProsopoUser = req.headers["prosopo-user"];
525
+ if (hashedHeadersUserAgent !== userAgent || headersProsopoUser !== userId) {
526
+ req.logger.info(() => ({
527
+ msg: "User agent or user id does not match",
528
+ data: {
529
+ headersUserAgent,
530
+ hashedHeadersUserAgent,
531
+ userAgent,
532
+ // This is the hashed user agent from the token
533
+ headersProsopoUser,
534
+ userId
535
+ }
536
+ }));
537
+ return res.json(
538
+ await tasks.frictionlessManager.sendImageCaptcha(
539
+ tokenId,
540
+ timestampDecayFunction(timestamp)
541
+ )
542
+ );
543
+ }
476
544
  if (userAccessPolicy) {
477
545
  await tasks.frictionlessManager.scoreIncreaseAccessPolicy(
478
546
  userAccessPolicy,
@@ -482,7 +550,10 @@ function prosopoRouter(env) {
482
550
  );
483
551
  if (userAccessPolicy.captchaType === CaptchaType.image) {
484
552
  return res.json(
485
- await tasks.frictionlessManager.sendImageCaptcha(tokenId)
553
+ await tasks.frictionlessManager.sendImageCaptcha(
554
+ tokenId,
555
+ userAccessPolicy.solvedImagesCount
556
+ )
486
557
  );
487
558
  }
488
559
  if (userAccessPolicy.captchaType === CaptchaType.pow) {
@@ -499,12 +570,26 @@ function prosopoRouter(env) {
499
570
  tokenId
500
571
  );
501
572
  return res.json(
502
- await tasks.frictionlessManager.sendImageCaptcha(tokenId)
573
+ await tasks.frictionlessManager.sendImageCaptcha(
574
+ tokenId,
575
+ timestampDecayFunction(timestamp)
576
+ )
577
+ );
578
+ }
579
+ const hostVerified = await tasks.frictionlessManager.hostVerified(
580
+ providerSelectEntropy
581
+ );
582
+ if (!hostVerified.verified) {
583
+ botScore = await tasks.frictionlessManager.scoreIncreaseUnverifiedHost(
584
+ hostVerified.domain,
585
+ baseBotScore,
586
+ botScore,
587
+ tokenId
503
588
  );
504
589
  }
505
590
  if (Number(botScore) > botThreshold) {
506
591
  req.logger.info(() => ({
507
- message: "Bot score is greater than threshold",
592
+ msg: "Bot score is greater than threshold",
508
593
  data: {
509
594
  botScore,
510
595
  botThreshold,
@@ -512,7 +597,10 @@ function prosopoRouter(env) {
512
597
  }
513
598
  }));
514
599
  return res.json(
515
- await tasks.frictionlessManager.sendImageCaptcha(tokenId)
600
+ await tasks.frictionlessManager.sendImageCaptcha(
601
+ tokenId,
602
+ env.config.captchas.solved.count
603
+ )
516
604
  );
517
605
  }
518
606
  return res.json(
@@ -8,26 +8,26 @@ const domainMiddleware = (env) => {
8
8
  const tasks = new Tasks(env);
9
9
  return async (req, res, next) => {
10
10
  try {
11
- const dapp = req.headers["prosopo-site-key"];
12
- if (!dapp)
11
+ const siteKey = req.headers["prosopo-site-key"];
12
+ if (!siteKey)
13
13
  throw siteKeyNotRegisteredError(
14
14
  req.i18n,
15
15
  "No sitekey provided",
16
16
  req.logger
17
17
  );
18
18
  try {
19
- validateAddress(dapp, false, 42);
19
+ validateAddress(siteKey, false, 42);
20
20
  } catch (err) {
21
- throw invalidSiteKeyError(req.i18n, dapp, req.logger);
21
+ throw invalidSiteKeyError(req.i18n, siteKey, req.logger);
22
22
  }
23
- const clientSettings = await tasks.db.getClientRecord(dapp);
23
+ const clientSettings = await tasks.db.getClientRecord(siteKey);
24
24
  if (!clientSettings)
25
- throw siteKeyNotRegisteredError(req.i18n, dapp, req.logger);
25
+ throw siteKeyNotRegisteredError(req.i18n, siteKey, req.logger);
26
26
  const allowedDomains = clientSettings.settings?.domains;
27
27
  if (!allowedDomains)
28
28
  throw siteKeyInvalidDomainError(
29
29
  req.i18n,
30
- dapp,
30
+ siteKey,
31
31
  req.hostname,
32
32
  req.logger
33
33
  );
@@ -35,7 +35,7 @@ const domainMiddleware = (env) => {
35
35
  if (!origin)
36
36
  throw unauthorizedOriginError(req.i18n, void 0, req.logger);
37
37
  for (const domain of allowedDomains) {
38
- if (tasks.clientTaskManager.isSubdomainOrExactMatch(origin, domain)) {
38
+ if (tasks.clientTaskManager.domainPatternMatcher(origin, domain)) {
39
39
  next();
40
40
  return;
41
41
  }
@@ -17,6 +17,10 @@ const headerCheckMiddleware = (env) => {
17
17
  validateAddr(user, void 0, req.logger);
18
18
  req.user = user;
19
19
  req.siteKey = siteKey;
20
+ req.logger = req.logger.with({
21
+ user,
22
+ siteKey
23
+ });
20
24
  next();
21
25
  } catch (err) {
22
26
  return handleErrors(err, req, res, next);
@@ -1,6 +1,9 @@
1
- import { ApiPrefix } from "@prosopo/types";
1
+ import { PublicApiPaths, ApiPrefix } from "@prosopo/types";
2
2
  function ignoreMiddleware() {
3
3
  return (req, res, next) => {
4
+ if (req.originalUrl.indexOf(PublicApiPaths.Healthz) !== -1) {
5
+ return next();
6
+ }
4
7
  if (req.originalUrl.indexOf(ApiPrefix) === -1) {
5
8
  res.statusCode = 404;
6
9
  res.send("Not Found");
@@ -1,9 +1,8 @@
1
- import { createHash } from "node:crypto";
2
1
  import { Readable } from "node:stream";
3
2
  import { handleErrors } from "@prosopo/api-express-router";
4
3
  import { getLogger } from "@prosopo/common";
5
4
  import { randomAsHex } from "@prosopo/util-crypto";
6
- import { readTlsClientHello } from "read-tls-client-hello";
5
+ import { readTlsClientHello, calculateJa4FromHelloData } from "read-tls-client-hello";
7
6
  const DEFAULT_JA4 = "ja4";
8
7
  const getJA4 = async (headers, logger) => {
9
8
  logger = logger || getLogger("info", import.meta.url);
@@ -15,7 +14,6 @@ const getJA4 = async (headers, logger) => {
15
14
  try {
16
15
  const xTlsClientHello = (headers["x-tls-clienthello"] || "").toString();
17
16
  const xTlsVersion = (headers["x-tls-version"] || "").toString().toLowerCase();
18
- const xTlsServerName = (headers["x-tls-server-name"] || "").toString();
19
17
  const clientHelloBuffer = Buffer.from(xTlsClientHello, "base64");
20
18
  logger.debug(() => ({
21
19
  msg: "ClientHello First Bytes:",
@@ -31,32 +29,13 @@ const getJA4 = async (headers, logger) => {
31
29
  msg: "Headers TLS Version:",
32
30
  data: { xTlsVersion }
33
31
  }));
34
- const tlsVersion = xTlsVersion.replace(/(tls)|\./g, "");
35
32
  const readableStream = new Readable({
36
33
  read() {
37
34
  this.push(clientHelloBuffer);
38
35
  }
39
36
  });
40
37
  const clientHello = await readTlsClientHello(readableStream);
41
- const { alpnProtocols } = clientHello;
42
- const [_tlsVersion, cipherSuites, extensions] = clientHello.fingerprintData;
43
- const transport = "t";
44
- const sniIndicator = xTlsServerName ? "d" : "i";
45
- const validCipherSuites = cipherSuites.filter(
46
- (cs) => (cs & 3855) !== 2570
47
- );
48
- const cipherCount = validCipherSuites.length;
49
- const validExtensions = extensions.filter(
50
- (ext) => (ext & 3855) !== 2570
51
- );
52
- const extensionCount = validExtensions.length;
53
- const alpn = alpnProtocols?.length ? alpnProtocols[0] : "";
54
- const alpnLabel = alpn ? `${alpn[0]}${alpn[alpn.length - 1]}` : "00";
55
- const sortedCiphers = validCipherSuites.map((cs) => cs.toString(16).padStart(4, "0")).sort().join(",");
56
- const cipherHash = createHash("sha256").update(sortedCiphers).digest("hex").slice(0, 12);
57
- const decimalString = extensions.sort((a, b) => a - b).map((ext) => ext.toString(10)).join("-");
58
- const extensionHash = createHash("sha256").update(decimalString).digest("hex").slice(0, 12);
59
- const ja4PlusFingerprint = `${transport}${tlsVersion}${sniIndicator}${cipherCount}${extensionCount}${alpnLabel}_${cipherHash}_${extensionHash}`;
38
+ const ja4PlusFingerprint = calculateJa4FromHelloData(clientHello);
60
39
  return { ja4PlusFingerprint };
61
40
  } catch (e) {
62
41
  logger.error(() => ({
@@ -72,6 +51,9 @@ const ja4Middleware = (env) => {
72
51
  req.logger.debug(() => ({ data: { url: req.url } }));
73
52
  const ja4 = await getJA4(req.headers, req.logger);
74
53
  req.ja4 = ja4.ja4PlusFingerprint || "";
54
+ req.logger = req.logger.with({
55
+ ja4: req.ja4
56
+ });
75
57
  next();
76
58
  } catch (err) {
77
59
  return handleErrors(err, req, res, next);
@@ -3,16 +3,39 @@ import { ProsopoApiError } from "@prosopo/common";
3
3
  import { PublicApiPaths } from "@prosopo/types";
4
4
  import { version } from "@prosopo/util";
5
5
  import express from "express";
6
- function publicRouter() {
6
+ function publicRouter(env) {
7
7
  const router = express.Router();
8
8
  router.get(PublicApiPaths.Healthz, (req, res) => {
9
9
  res.status(200).send("OK");
10
10
  });
11
11
  router.get(PublicApiPaths.GetProviderDetails, async (req, res, next) => {
12
12
  try {
13
- return res.json({ version, ...{ message: "Provider online" } });
13
+ const db = env.getDb();
14
+ const redisConnection = db.getRedisConnection();
15
+ const redisAccessRulesConnection = db.getRedisAccessRulesConnection();
16
+ const response = {
17
+ version,
18
+ message: "Provider online",
19
+ redis: [
20
+ {
21
+ actor: "General",
22
+ isReady: redisConnection.isReady(),
23
+ awaitingTimeSeconds: Math.ceil(
24
+ redisConnection.getAwaitingTimeMs() / 1e3
25
+ )
26
+ },
27
+ {
28
+ actor: "UAP",
29
+ isReady: redisAccessRulesConnection.isReady(),
30
+ awaitingTimeSeconds: Math.ceil(
31
+ redisAccessRulesConnection.getAwaitingTimeMs() / 1e3
32
+ )
33
+ }
34
+ ]
35
+ };
36
+ return res.json(response);
14
37
  } catch (err) {
15
- req.logger.error(() => ({
38
+ env.logger.error(() => ({
16
39
  err,
17
40
  data: { reqParams: req.params },
18
41
  msg: "Error getting provider details"
@@ -22,7 +22,7 @@ function prosopoVerifyRouter(env) {
22
22
  })
23
23
  );
24
24
  }
25
- const { dappSignature, token, ip } = parsed;
25
+ const { dappSignature, token, ip, maxVerifiedTime } = parsed;
26
26
  try {
27
27
  const { user, dapp, timestamp, commitmentId } = decodeProcaptchaOutput(token);
28
28
  validateAddress(dapp, false, 42);
@@ -43,7 +43,8 @@ function prosopoVerifyRouter(env) {
43
43
  user,
44
44
  dapp,
45
45
  commitmentId,
46
- parsed.maxVerifiedTime,
46
+ env,
47
+ maxVerifiedTime,
47
48
  ip
48
49
  );
49
50
  req.logger.debug(() => ({ data: { response } }));
@@ -111,6 +112,7 @@ function prosopoVerifyRouter(env) {
111
112
  dapp,
112
113
  challenge,
113
114
  verifiedTimeout,
115
+ env,
114
116
  ip
115
117
  );
116
118
  const verificationResponse = tasks.powCaptchaManager.getVerificationResponse(
@@ -10,10 +10,13 @@ class ApiRemoveDetectorKeyEndpoint {
10
10
  async processRequest(args, logger) {
11
11
  logger = logger || common.getLogger("info", module);
12
12
  try {
13
- const { detectorKey } = args;
13
+ const { detectorKey, expirationInSeconds } = args;
14
14
  logger = logger || common.getLogger("info", module);
15
15
  logger.info(() => ({ msg: "Removing detector key" }));
16
- await this.clientTaskManager.removeDetectorKey(detectorKey);
16
+ await this.clientTaskManager.removeDetectorKey(
17
+ detectorKey,
18
+ expirationInSeconds
19
+ );
17
20
  return {
18
21
  status: apiRoute.ApiEndpointResponseStatus.SUCCESS
19
22
  };
@@ -26,7 +29,7 @@ class ApiRemoveDetectorKeyEndpoint {
26
29
  }
27
30
  }
28
31
  getRequestArgsSchema() {
29
- return types.UpdateDetectorKeyBody;
32
+ return types.RemoveDetectorKeyBodySpec;
30
33
  }
31
34
  }
32
35
  exports.ApiRemoveDetectorKeyEndpoint = ApiRemoveDetectorKeyEndpoint;
@@ -10,13 +10,12 @@ const getRequestUserScope = (requestHeaders, ja4, ip, user) => {
10
10
  ...ja4 && { ja4Hash: ja4 },
11
11
  ...userAgent && { userAgent },
12
12
  ...ip && { ip }
13
+ // TODO more things with headers
13
14
  };
14
15
  };
15
- const getPrioritisedAccessRule = async (userAccessRulesStorage, userScope, clientId) => {
16
- const userScopeKeys = Object.keys(userScope).filter(
17
- (key) => userScope[key] !== void 0
18
- );
19
- const prioritisedUserScopes = util.uniqueSubsets(userScopeKeys).map(
16
+ const getPrioritisedUserScopes = (userScope) => {
17
+ const userScopeKeys = Object.keys(userScope);
18
+ return util.uniqueSubsets(userScopeKeys).map(
20
19
  (subset) => subset.reduce(
21
20
  (acc, key) => {
22
21
  acc[key] = userScope[key];
@@ -24,22 +23,29 @@ const getPrioritisedAccessRule = async (userAccessRulesStorage, userScope, clien
24
23
  },
25
24
  {}
26
25
  )
27
- ).filter((us) => Object.keys(us).length > 0).filter((us) => Object.values(us).some((value) => value !== void 0));
26
+ );
27
+ };
28
+ const getPrioritisedAccessRule = async (userAccessRulesStorage, userScope, clientId) => {
29
+ const prioritisedUserScopes = getPrioritisedUserScopes(userScope);
28
30
  const policyPromises = [];
29
- for (const clientOrUndefined of [clientId, void 0]) {
31
+ const clientLoop = clientId ? [clientId, void 0] : [void 0];
32
+ for (const clientOrUndefined of clientLoop) {
30
33
  for (const scope of prioritisedUserScopes) {
31
- policyPromises.push(
32
- userAccessRulesStorage.findRules({
33
- ...clientOrUndefined && {
34
- policyScope: {
35
- clientId: clientOrUndefined
36
- }
37
- },
38
- policyScopeMatch: userAccessPolicy.ScopeMatch.Exact,
39
- userScope: userAccessPolicy.userScopeInputSchema.parse(scope),
40
- userScopeMatch: userAccessPolicy.ScopeMatch.Exact
41
- })
42
- );
34
+ if (Object.values(scope).every((value) => value === void 0)) {
35
+ continue;
36
+ }
37
+ const parsedUserScope = userAccessPolicy.userScopeInputSchema.parse(scope);
38
+ const filter = {
39
+ ...clientOrUndefined && {
40
+ policyScope: {
41
+ clientId: clientOrUndefined
42
+ }
43
+ },
44
+ policyScopeMatch: userAccessPolicy.ScopeMatch.Exact,
45
+ userScope: parsedUserScope,
46
+ userScopeMatch: userAccessPolicy.ScopeMatch.Exact
47
+ };
48
+ policyPromises.push(userAccessRulesStorage.findRules(filter, true, true));
43
49
  }
44
50
  }
45
51
  return (await Promise.all(policyPromises)).flat();