@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
@@ -1,625 +1,37 @@
1
1
  import { handleErrors } from "@prosopo/api-express-router";
2
- import { ProsopoApiError } from "@prosopo/common";
3
- import { parseCaptchaAssets } from "@prosopo/datasets";
4
- import { ClientApiPaths, CaptchaRequestBody, CaptchaType, ApiParams, CaptchaSolutionBody, GetPowCaptchaChallengeRequestBody, SubmitPowCaptchaSolutionBody, GetFrictionlessCaptchaChallengeRequestBody } from "@prosopo/types";
5
- import { getIPAddress, flatten } from "@prosopo/util";
2
+ import { ClientApiPaths } from "@prosopo/types";
6
3
  import express from "express";
7
- import { getCompositeIpAddress } from "../compositeIpAddress.js";
8
- import { FrictionlessManager } from "../tasks/frictionless/frictionlessTasks.js";
9
- import { timestampDecayFunction } from "../tasks/frictionless/frictionlessTasksUtils.js";
10
- import { Tasks } from "../tasks/tasks.js";
11
- import { hashUserAgent } from "../utils/hashUserAgent.js";
12
- import { getRequestUserScope } from "./blacklistRequestInspector.js";
13
- import { validateSiteKey, validateAddr } from "./validateAddress.js";
14
- const DEFAULT_FRICTIONLESS_THRESHOLD = 0.5;
4
+ import getFrictionlessCaptchaChallenge from "./captcha/getFrictionlessCaptchaChallenge.js";
5
+ import getImageCaptchaChallenge from "./captcha/getImageCaptchaChallenge.js";
6
+ import getPoWCaptchaChallenge from "./captcha/getPoWCaptchaChallenge.js";
7
+ import submitImageCaptchaSolution from "./captcha/submitImageCaptchaSolution.js";
8
+ import submitPoWCaptchaSolution from "./captcha/submitPoWCaptchaSolution.js";
15
9
  function prosopoRouter(env) {
16
10
  const router = express.Router();
17
11
  const userAccessRulesStorage = env.getDb().getUserAccessRulesStorage();
18
12
  router.post(
19
13
  ClientApiPaths.GetImageCaptchaChallenge,
20
- async (req, res, next) => {
21
- const tasks = new Tasks(env, req.logger);
22
- let parsed;
23
- if (!req.ip) {
24
- return next(
25
- new ProsopoApiError("API.BAD_REQUEST", {
26
- context: { code: 400, error: "IP address not found" },
27
- i18n: req.i18n,
28
- logger: req.logger
29
- })
30
- );
31
- }
32
- const ipAddress = getIPAddress(req.ip || "");
33
- try {
34
- parsed = CaptchaRequestBody.parse(req.body);
35
- } catch (err) {
36
- return next(
37
- new ProsopoApiError("CAPTCHA.PARSE_ERROR", {
38
- context: { code: 400, error: err },
39
- i18n: req.i18n,
40
- logger: req.logger
41
- })
42
- );
43
- }
44
- const { datasetId, user, dapp, sessionId } = parsed;
45
- validateSiteKey(dapp);
46
- validateAddr(user);
47
- try {
48
- const clientRecord = await tasks.db.getClientRecord(dapp);
49
- if (!clientRecord) {
50
- return next(
51
- new ProsopoApiError("API.SITE_KEY_NOT_REGISTERED", {
52
- context: { code: 400, siteKey: dapp },
53
- i18n: req.i18n,
54
- logger: req.logger
55
- })
56
- );
57
- }
58
- const userScope = getRequestUserScope(
59
- flatten(req.headers),
60
- req.ja4,
61
- req.ip,
62
- user
63
- );
64
- const userAccessPolicy = (await tasks.imgCaptchaManager.getPrioritisedAccessPolicies(
65
- userAccessRulesStorage,
66
- dapp,
67
- userScope
68
- ))[0];
69
- const { valid, reason, frictionlessTokenId, solvedImagesCount } = await tasks.imgCaptchaManager.isValidRequest(
70
- clientRecord,
71
- CaptchaType.image,
72
- env,
73
- sessionId,
74
- userAccessPolicy,
75
- req.ip
76
- );
77
- if (!valid) {
78
- return next(
79
- new ProsopoApiError(reason || "API.BAD_REQUEST", {
80
- context: {
81
- code: 400,
82
- siteKey: dapp,
83
- user
84
- },
85
- i18n: req.i18n,
86
- logger: req.logger
87
- })
88
- );
89
- }
90
- const captchaConfig = {
91
- solved: {
92
- count: solvedImagesCount || userAccessPolicy?.solvedImagesCount || env.config.captchas.solved.count
93
- },
94
- unsolved: {
95
- count: userAccessPolicy?.unsolvedImagesCount || env.config.captchas.unsolved.count
96
- }
97
- };
98
- const taskData = await tasks.imgCaptchaManager.getRandomCaptchasAndRequestHash(
99
- datasetId,
100
- user,
101
- ipAddress,
102
- captchaConfig,
103
- clientRecord.settings.imageThreshold ?? 0.8,
104
- frictionlessTokenId
105
- );
106
- const captchaResponse = {
107
- [ApiParams.status]: "ok",
108
- [ApiParams.captchas]: taskData.captchas.map((captcha) => ({
109
- ...captcha,
110
- target: req.t(`TARGET.${captcha.target}`),
111
- items: captcha.items.map(
112
- (item) => parseCaptchaAssets(item, env.assetsResolver)
113
- )
114
- })),
115
- [ApiParams.requestHash]: taskData.requestHash,
116
- [ApiParams.timestamp]: taskData.timestamp.toString(),
117
- [ApiParams.signature]: {
118
- [ApiParams.provider]: {
119
- [ApiParams.requestHash]: taskData.signedRequestHash
120
- }
121
- }
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
- }));
134
- return res.json(captchaResponse);
135
- } catch (err) {
136
- req.logger.error(() => ({
137
- err,
138
- data: req.params,
139
- msg: "Error in image captcha challenge request"
140
- }));
141
- return next(
142
- new ProsopoApiError("API.BAD_REQUEST", {
143
- context: {
144
- error: err,
145
- code: 500,
146
- params: req.params,
147
- context: err
148
- },
149
- i18n: req.i18n,
150
- logger: req.logger
151
- })
152
- );
153
- }
154
- }
14
+ (req, res, next) => getImageCaptchaChallenge(env, userAccessRulesStorage)(req, res, next)
155
15
  );
156
16
  router.post(
157
17
  ClientApiPaths.SubmitImageCaptchaSolution,
158
- async (req, res, next) => {
159
- const tasks = new Tasks(env, req.logger);
160
- let parsed;
161
- try {
162
- parsed = CaptchaSolutionBody.parse(req.body);
163
- } catch (err) {
164
- return next(
165
- new ProsopoApiError("CAPTCHA.PARSE_ERROR", {
166
- context: { code: 400, error: err, body: req.body },
167
- i18n: req.i18n,
168
- logger: req.logger
169
- })
170
- );
171
- }
172
- const { user, dapp } = parsed;
173
- validateSiteKey(dapp);
174
- validateAddr(user);
175
- try {
176
- const clientRecord = await tasks.db.getClientRecord(parsed.dapp);
177
- if (!clientRecord) {
178
- return next(
179
- new ProsopoApiError("API.SITE_KEY_NOT_REGISTERED", {
180
- context: { code: 400, siteKey: dapp },
181
- i18n: req.i18n,
182
- logger: req.logger
183
- })
184
- );
185
- }
186
- const result = await tasks.imgCaptchaManager.dappUserSolution(
187
- user,
188
- dapp,
189
- parsed[ApiParams.requestHash],
190
- parsed[ApiParams.captchas],
191
- parsed[ApiParams.signature].user.timestamp,
192
- Number.parseInt(parsed[ApiParams.timestamp]),
193
- parsed[ApiParams.signature].provider.requestHash,
194
- getIPAddress(req.ip || ""),
195
- flatten(req.headers),
196
- req.ja4
197
- );
198
- const returnValue = {
199
- status: req.i18n.t(
200
- result.verified ? "API.CAPTCHA_PASSED" : "API.CAPTCHA_FAILED"
201
- ),
202
- ...result
203
- };
204
- return res.json(returnValue);
205
- } catch (err) {
206
- req.logger.error(() => ({
207
- err,
208
- body: req.body,
209
- msg: "Error in image captcha solution submission"
210
- }));
211
- return next(
212
- new ProsopoApiError("API.BAD_REQUEST", {
213
- context: {
214
- code: 500,
215
- siteKey: req.body.dapp,
216
- error: err
217
- },
218
- i18n: req.i18n,
219
- logger: req.logger
220
- })
221
- );
222
- }
223
- }
18
+ (req, res, next) => submitImageCaptchaSolution(env, userAccessRulesStorage)(req, res, next)
19
+ );
20
+ router.post(
21
+ ClientApiPaths.GetPowCaptchaChallenge,
22
+ (req, res, next) => getPoWCaptchaChallenge(env, userAccessRulesStorage)(req, res, next)
224
23
  );
225
- router.post(ClientApiPaths.GetPowCaptchaChallenge, async (req, res, next) => {
226
- let parsed;
227
- const tasks = new Tasks(env);
228
- tasks.setLogger(req.logger);
229
- try {
230
- parsed = GetPowCaptchaChallengeRequestBody.parse(req.body);
231
- } catch (err) {
232
- return next(
233
- new ProsopoApiError("CAPTCHA.PARSE_ERROR", {
234
- context: { code: 400, error: err },
235
- i18n: req.i18n,
236
- logger: req.logger
237
- })
238
- );
239
- }
240
- const { user, dapp, sessionId } = parsed;
241
- validateSiteKey(dapp);
242
- validateAddr(user);
243
- try {
244
- const clientSettings = await tasks.db.getClientRecord(dapp);
245
- if (!clientSettings) {
246
- return next(
247
- new ProsopoApiError("API.SITE_KEY_NOT_REGISTERED", {
248
- context: { code: 400, siteKey: dapp },
249
- i18n: req.i18n,
250
- logger: req.logger
251
- })
252
- );
253
- }
254
- const userScope = getRequestUserScope(
255
- flatten(req.headers),
256
- req.ja4,
257
- req.ip,
258
- user
259
- );
260
- const userAccessPolicy = (await tasks.powCaptchaManager.getPrioritisedAccessPolicies(
261
- userAccessRulesStorage,
262
- dapp,
263
- userScope
264
- ))[0];
265
- const { valid, reason, frictionlessTokenId, powDifficulty } = await tasks.powCaptchaManager.isValidRequest(
266
- clientSettings,
267
- CaptchaType.pow,
268
- env,
269
- sessionId,
270
- userAccessPolicy,
271
- req.ip
272
- );
273
- if (!valid) {
274
- return next(
275
- new ProsopoApiError(reason || "API.BAD_REQUEST", {
276
- context: {
277
- code: 400,
278
- siteKey: dapp,
279
- user
280
- },
281
- i18n: req.i18n,
282
- logger: req.logger
283
- })
284
- );
285
- }
286
- const origin = req.headers.origin;
287
- if (!origin) {
288
- return next(
289
- new ProsopoApiError("API.BAD_REQUEST", {
290
- context: {
291
- error: "Origin header not found",
292
- code: 400,
293
- siteKey: dapp,
294
- user
295
- },
296
- i18n: req.i18n,
297
- logger: req.logger
298
- })
299
- );
300
- }
301
- const difficulty = powDifficulty || userAccessPolicy?.powDifficulty || clientSettings?.settings?.powDifficulty;
302
- const challenge = await tasks.powCaptchaManager.getPowCaptchaChallenge(
303
- user,
304
- dapp,
305
- origin,
306
- difficulty
307
- );
308
- await tasks.db.storePowCaptchaRecord(
309
- challenge.challenge,
310
- {
311
- requestedAtTimestamp: challenge.requestedAtTimestamp,
312
- userAccount: user,
313
- dappAccount: dapp
314
- },
315
- challenge.difficulty,
316
- challenge.providerSignature,
317
- getCompositeIpAddress(req.ip || ""),
318
- flatten(req.headers),
319
- req.ja4,
320
- frictionlessTokenId
321
- );
322
- const getPowCaptchaResponse = {
323
- [ApiParams.status]: "ok",
324
- [ApiParams.challenge]: challenge.challenge,
325
- [ApiParams.difficulty]: challenge.difficulty,
326
- [ApiParams.timestamp]: challenge.requestedAtTimestamp.toString(),
327
- [ApiParams.signature]: {
328
- [ApiParams.provider]: {
329
- [ApiParams.challenge]: challenge.providerSignature
330
- }
331
- }
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
- }));
344
- return res.json(getPowCaptchaResponse);
345
- } catch (err) {
346
- req.logger.error(() => ({
347
- err,
348
- body: req.body,
349
- msg: "Error in PoW captcha challenge request"
350
- }));
351
- return next(
352
- new ProsopoApiError("API.BAD_REQUEST", {
353
- context: {
354
- code: 500,
355
- siteKey: req.body.dapp,
356
- user: req.body.user,
357
- error: err
358
- },
359
- i18n: req.i18n,
360
- logger: req.logger
361
- })
362
- );
363
- }
364
- });
365
24
  router.post(
366
25
  ClientApiPaths.SubmitPowCaptchaSolution,
367
- async (req, res, next) => {
368
- let parsed;
369
- const tasks = new Tasks(env, req.logger);
370
- try {
371
- parsed = SubmitPowCaptchaSolutionBody.parse(req.body);
372
- } catch (err) {
373
- return next(
374
- new ProsopoApiError("CAPTCHA.PARSE_ERROR", {
375
- context: { code: 400, error: err, body: req.body },
376
- i18n: req.i18n,
377
- logger: req.logger
378
- })
379
- );
380
- }
381
- const { challenge, signature, nonce, verifiedTimeout, dapp, user } = parsed;
382
- validateSiteKey(dapp);
383
- validateAddr(user);
384
- try {
385
- const clientRecord = await tasks.db.getClientRecord(dapp);
386
- if (!clientRecord) {
387
- return next(
388
- new ProsopoApiError("API.SITE_KEY_NOT_REGISTERED", {
389
- context: { code: 400, siteKey: dapp },
390
- i18n: req.i18n,
391
- logger: req.logger
392
- })
393
- );
394
- }
395
- const verified = await tasks.powCaptchaManager.verifyPowCaptchaSolution(
396
- challenge,
397
- signature.provider.challenge,
398
- nonce,
399
- verifiedTimeout,
400
- signature.user.timestamp,
401
- getIPAddress(req.ip || ""),
402
- flatten(req.headers)
403
- );
404
- const response = { status: "ok", verified };
405
- return res.json(response);
406
- } catch (err) {
407
- req.logger.error(() => ({
408
- err,
409
- body: req.body,
410
- msg: "Error in PoW captcha solution submission"
411
- }));
412
- return next(
413
- new ProsopoApiError("API.BAD_REQUEST", {
414
- context: {
415
- code: 500,
416
- siteKey: req.body.dapp,
417
- error: err
418
- },
419
- i18n: req.i18n,
420
- logger: req.logger
421
- })
422
- );
423
- }
424
- }
26
+ (req, res, next) => submitPoWCaptchaSolution(env)(req, res, next)
425
27
  );
426
28
  router.post(
427
29
  ClientApiPaths.GetFrictionlessCaptchaChallenge,
428
- async (req, res, next) => {
429
- try {
430
- const tasks = new Tasks(env, req.logger);
431
- const { token, dapp, user } = GetFrictionlessCaptchaChallengeRequestBody.parse(req.body);
432
- const existingToken = await tasks.db.getFrictionlessTokenRecordByToken(token);
433
- if (existingToken) {
434
- req.logger.info(() => ({
435
- token: existingToken,
436
- msg: "Token has already been used"
437
- }));
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
- })
448
- );
449
- }
450
- const lScore = tasks.frictionlessManager.checkLangRules(
451
- req.headers["accept-language"] || ""
452
- );
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;
471
- const clientRecord = await tasks.db.getClientRecord(dapp);
472
- if (!clientRecord) {
473
- return next(
474
- new ProsopoApiError("API.SITE_KEY_NOT_REGISTERED", {
475
- context: { code: 400, siteKey: dapp },
476
- i18n: req.i18n,
477
- logger: req.logger
478
- })
479
- );
480
- }
481
- const { valid, reason } = await tasks.frictionlessManager.isValidRequest(
482
- clientRecord,
483
- CaptchaType.frictionless,
484
- env
485
- );
486
- if (!valid) {
487
- return next(
488
- new ProsopoApiError(reason || "API.BAD_REQUEST", {
489
- context: {
490
- code: 400,
491
- siteKey: dapp,
492
- user
493
- },
494
- i18n: req.i18n,
495
- logger: req.logger
496
- })
497
- );
498
- }
499
- const botThreshold = clientRecord.settings?.frictionlessThreshold || DEFAULT_FRICTIONLESS_THRESHOLD;
500
- const tokenId = await tasks.db.storeFrictionlessTokenRecord({
501
- token,
502
- score: botScore,
503
- threshold: botThreshold,
504
- scoreComponents: {
505
- baseScore: baseBotScore,
506
- ...lScore && { lScore }
507
- },
508
- providerSelectEntropy,
509
- ipAddress: getCompositeIpAddress(req.ip || "")
510
- });
511
- const userScope = getRequestUserScope(
512
- flatten(req.headers),
513
- req.ja4,
514
- req.ip,
515
- user
516
- );
517
- const userAccessPolicy = (await tasks.frictionlessManager.getPrioritisedAccessPolicies(
518
- userAccessRulesStorage,
519
- dapp,
520
- userScope
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
- }
544
- if (userAccessPolicy) {
545
- await tasks.frictionlessManager.scoreIncreaseAccessPolicy(
546
- userAccessPolicy,
547
- baseBotScore,
548
- botScore,
549
- tokenId
550
- );
551
- if (userAccessPolicy.captchaType === CaptchaType.image) {
552
- return res.json(
553
- await tasks.frictionlessManager.sendImageCaptcha(
554
- tokenId,
555
- userAccessPolicy.solvedImagesCount
556
- )
557
- );
558
- }
559
- if (userAccessPolicy.captchaType === CaptchaType.pow) {
560
- return res.json(
561
- await tasks.frictionlessManager.sendPowCaptcha(tokenId)
562
- );
563
- }
564
- }
565
- if (FrictionlessManager.timestampTooOld(timestamp)) {
566
- await tasks.frictionlessManager.scoreIncreaseTimestamp(
567
- timestamp,
568
- baseBotScore,
569
- botScore,
570
- tokenId
571
- );
572
- return res.json(
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
588
- );
589
- }
590
- if (Number(botScore) > botThreshold) {
591
- req.logger.info(() => ({
592
- msg: "Bot score is greater than threshold",
593
- data: {
594
- botScore,
595
- botThreshold,
596
- tokenId
597
- }
598
- }));
599
- return res.json(
600
- await tasks.frictionlessManager.sendImageCaptcha(
601
- tokenId,
602
- env.config.captchas.solved.count
603
- )
604
- );
605
- }
606
- return res.json(
607
- await tasks.frictionlessManager.sendPowCaptcha(tokenId)
608
- );
609
- } catch (err) {
610
- req.logger.error(() => ({
611
- err,
612
- msg: "Error in frictionless captcha challenge"
613
- }));
614
- return next(
615
- new ProsopoApiError("API.BAD_REQUEST", {
616
- context: { code: 400, error: err },
617
- i18n: req.i18n,
618
- logger: req.logger
619
- })
620
- );
621
- }
622
- }
30
+ (req, res, next) => getFrictionlessCaptchaChallenge(env, userAccessRulesStorage)(
31
+ req,
32
+ res,
33
+ next
34
+ )
623
35
  );
624
36
  router.use(handleErrors);
625
37
  return router;
@@ -4,12 +4,23 @@ import { ClientApiPaths, VerifySolutionBody, decodeProcaptchaOutput, ApiParams,
4
4
  import { validateAddress } from "@prosopo/util-crypto";
5
5
  import express from "express";
6
6
  import { Tasks } from "../tasks/tasks.js";
7
+ import { getMaintenanceMode } from "./admin/apiToggleMaintenanceModeEndpoint.js";
7
8
  function prosopoVerifyRouter(env) {
8
9
  const router = express.Router();
9
10
  router.post(
10
11
  ClientApiPaths.VerifyImageCaptchaSolutionDapp,
11
12
  async (req, res, next) => {
12
13
  const tasks = new Tasks(env, req.logger);
14
+ if (getMaintenanceMode()) {
15
+ req.logger.info(() => ({
16
+ msg: "Maintenance mode active - returning verified for image captcha verification"
17
+ }));
18
+ const verificationResponse = {
19
+ status: "ok",
20
+ verified: true
21
+ };
22
+ return res.json(verificationResponse);
23
+ }
13
24
  let parsed;
14
25
  try {
15
26
  parsed = VerifySolutionBody.parse(req.body);
@@ -45,7 +56,9 @@ function prosopoVerifyRouter(env) {
45
56
  commitmentId,
46
57
  env,
47
58
  maxVerifiedTime,
48
- ip
59
+ ip,
60
+ clientRecord.settings.disallowWebView,
61
+ clientRecord.settings.contextAware?.enabled
49
62
  );
50
63
  req.logger.debug(() => ({ data: { response } }));
51
64
  const verificationResponse = tasks.imgCaptchaManager.getVerificationResponse(
@@ -72,6 +85,16 @@ function prosopoVerifyRouter(env) {
72
85
  ClientApiPaths.VerifyPowCaptchaSolution,
73
86
  async (req, res, next) => {
74
87
  const tasks = new Tasks(env, req.logger);
88
+ if (getMaintenanceMode()) {
89
+ req.logger.info(() => ({
90
+ msg: "Maintenance mode active - returning verified for PoW captcha verification"
91
+ }));
92
+ const verificationResponse = {
93
+ status: "ok",
94
+ verified: true
95
+ };
96
+ return res.json(verificationResponse);
97
+ }
75
98
  let parsed;
76
99
  try {
77
100
  parsed = ServerPowCaptchaVerifyRequestBody.parse(req.body);