@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.
- package/CHANGELOG.md +578 -0
- package/dist/api/admin/apiRemoveDetectorKeyEndpoint.js +7 -4
- package/dist/api/blacklistRequestInspector.js +26 -20
- package/dist/api/captcha.js +121 -33
- package/dist/api/domainMiddleware.js +8 -8
- package/dist/api/headerCheckMiddleware.js +4 -0
- package/dist/api/ignoreMiddleware.js +4 -1
- package/dist/api/ja4Middleware.js +5 -23
- package/dist/api/public.js +26 -3
- package/dist/api/verify.js +4 -2
- package/dist/cjs/api/admin/apiRemoveDetectorKeyEndpoint.cjs +6 -3
- package/dist/cjs/api/blacklistRequestInspector.cjs +25 -19
- package/dist/cjs/api/captcha.cjs +121 -33
- package/dist/cjs/api/domainMiddleware.cjs +8 -8
- package/dist/cjs/api/headerCheckMiddleware.cjs +4 -0
- package/dist/cjs/api/ignoreMiddleware.cjs +3 -0
- package/dist/cjs/api/ja4Middleware.cjs +4 -22
- package/dist/cjs/api/public.cjs +26 -3
- package/dist/cjs/api/verify.cjs +4 -2
- package/dist/cjs/compositeIpAddress.cjs +53 -0
- package/dist/cjs/index.cjs +7 -0
- package/dist/cjs/pairs.cjs +27 -0
- package/dist/cjs/services/ipComparison.cjs +123 -0
- package/dist/cjs/services/ipInfo.cjs +87 -0
- package/dist/cjs/tasks/captchaManager.cjs +49 -2
- package/dist/cjs/tasks/client/clientTasks.cjs +33 -12
- package/dist/cjs/tasks/detection/decodePayload.cjs +712 -278
- package/dist/cjs/tasks/detection/getBotScore.cjs +14 -3
- package/dist/cjs/tasks/frictionless/frictionlessTasks.cjs +128 -24
- package/dist/cjs/tasks/frictionless/frictionlessTasksUtils.cjs +17 -0
- package/dist/cjs/tasks/imgCaptcha/imgCaptchaTasks.cjs +62 -20
- package/dist/cjs/tasks/powCaptcha/powTasks.cjs +43 -15
- package/dist/cjs/util.cjs +248 -16
- package/dist/cjs/utils/hashUserAgent.cjs +10 -0
- package/dist/compositeIpAddress.js +53 -0
- package/dist/index.js +8 -1
- package/dist/pairs.js +27 -0
- package/dist/services/ipComparison.js +123 -0
- package/dist/services/ipInfo.js +87 -0
- package/dist/tasks/captchaManager.js +49 -2
- package/dist/tasks/client/clientTasks.js +33 -12
- package/dist/tasks/detection/decodePayload.js +712 -278
- package/dist/tasks/detection/getBotScore.js +15 -4
- package/dist/tasks/frictionless/frictionlessTasks.js +128 -24
- package/dist/tasks/frictionless/frictionlessTasksUtils.js +18 -1
- package/dist/tasks/imgCaptcha/imgCaptchaTasks.js +64 -22
- package/dist/tasks/powCaptcha/powTasks.js +44 -16
- package/dist/util.js +249 -17
- package/dist/utils/hashUserAgent.js +10 -0
- package/package.json +28 -25
- package/vite.test.config.ts +3 -2
- package/vite.threads.test.config.ts +33 -0
package/dist/cjs/api/captcha.cjs
CHANGED
|
@@ -6,8 +6,11 @@ const datasets = require("@prosopo/datasets");
|
|
|
6
6
|
const types = require("@prosopo/types");
|
|
7
7
|
const util = require("@prosopo/util");
|
|
8
8
|
const express = require("express");
|
|
9
|
+
const compositeIpAddress = require("../compositeIpAddress.cjs");
|
|
9
10
|
const frictionlessTasks = require("../tasks/frictionless/frictionlessTasks.cjs");
|
|
11
|
+
const frictionlessTasksUtils = require("../tasks/frictionless/frictionlessTasksUtils.cjs");
|
|
10
12
|
const tasks = require("../tasks/tasks.cjs");
|
|
13
|
+
const hashUserAgent = require("../utils/hashUserAgent.cjs");
|
|
11
14
|
const blacklistRequestInspector = require("./blacklistRequestInspector.cjs");
|
|
12
15
|
const validateAddress = require("./validateAddress.cjs");
|
|
13
16
|
const DEFAULT_FRICTIONLESS_THRESHOLD = 0.5;
|
|
@@ -65,11 +68,13 @@ function prosopoRouter(env) {
|
|
|
65
68
|
dapp,
|
|
66
69
|
userScope
|
|
67
70
|
))[0];
|
|
68
|
-
const { valid, reason, frictionlessTokenId } = await tasks$1.imgCaptchaManager.isValidRequest(
|
|
71
|
+
const { valid, reason, frictionlessTokenId, solvedImagesCount } = await tasks$1.imgCaptchaManager.isValidRequest(
|
|
69
72
|
clientRecord,
|
|
70
73
|
types.CaptchaType.image,
|
|
74
|
+
env,
|
|
71
75
|
sessionId,
|
|
72
|
-
userAccessPolicy
|
|
76
|
+
userAccessPolicy,
|
|
77
|
+
req.ip
|
|
73
78
|
);
|
|
74
79
|
if (!valid) {
|
|
75
80
|
return next(
|
|
@@ -86,7 +91,7 @@ function prosopoRouter(env) {
|
|
|
86
91
|
}
|
|
87
92
|
const captchaConfig = {
|
|
88
93
|
solved: {
|
|
89
|
-
count: userAccessPolicy?.solvedImagesCount || env.config.captchas.solved.count
|
|
94
|
+
count: solvedImagesCount || userAccessPolicy?.solvedImagesCount || env.config.captchas.solved.count
|
|
90
95
|
},
|
|
91
96
|
unsolved: {
|
|
92
97
|
count: userAccessPolicy?.unsolvedImagesCount || env.config.captchas.unsolved.count
|
|
@@ -117,12 +122,23 @@ function prosopoRouter(env) {
|
|
|
117
122
|
}
|
|
118
123
|
}
|
|
119
124
|
};
|
|
125
|
+
req.logger.info(() => ({
|
|
126
|
+
msg: "Image captcha challenge issued",
|
|
127
|
+
data: {
|
|
128
|
+
captchaType: types.CaptchaType.image,
|
|
129
|
+
requestHash: taskData.requestHash,
|
|
130
|
+
solvedImagesCount: captchaConfig.solved.count,
|
|
131
|
+
user,
|
|
132
|
+
dapp,
|
|
133
|
+
sessionId
|
|
134
|
+
}
|
|
135
|
+
}));
|
|
120
136
|
return res.json(captchaResponse);
|
|
121
137
|
} catch (err) {
|
|
122
138
|
req.logger.error(() => ({
|
|
123
139
|
err,
|
|
124
140
|
data: req.params,
|
|
125
|
-
msg: "Error in
|
|
141
|
+
msg: "Error in image captcha challenge request"
|
|
126
142
|
}));
|
|
127
143
|
return next(
|
|
128
144
|
new common.ProsopoApiError("API.BAD_REQUEST", {
|
|
@@ -177,7 +193,7 @@ function prosopoRouter(env) {
|
|
|
177
193
|
parsed[types.ApiParams.signature].user.timestamp,
|
|
178
194
|
Number.parseInt(parsed[types.ApiParams.timestamp]),
|
|
179
195
|
parsed[types.ApiParams.signature].provider.requestHash,
|
|
180
|
-
util.getIPAddress(req.ip || "")
|
|
196
|
+
util.getIPAddress(req.ip || ""),
|
|
181
197
|
util.flatten(req.headers),
|
|
182
198
|
req.ja4
|
|
183
199
|
);
|
|
@@ -192,7 +208,7 @@ function prosopoRouter(env) {
|
|
|
192
208
|
req.logger.error(() => ({
|
|
193
209
|
err,
|
|
194
210
|
body: req.body,
|
|
195
|
-
msg: "Error in
|
|
211
|
+
msg: "Error in image captcha solution submission"
|
|
196
212
|
}));
|
|
197
213
|
return next(
|
|
198
214
|
new common.ProsopoApiError("API.BAD_REQUEST", {
|
|
@@ -248,11 +264,13 @@ function prosopoRouter(env) {
|
|
|
248
264
|
dapp,
|
|
249
265
|
userScope
|
|
250
266
|
))[0];
|
|
251
|
-
const { valid, reason, frictionlessTokenId } = await tasks$1.powCaptchaManager.isValidRequest(
|
|
267
|
+
const { valid, reason, frictionlessTokenId, powDifficulty } = await tasks$1.powCaptchaManager.isValidRequest(
|
|
252
268
|
clientSettings,
|
|
253
269
|
types.CaptchaType.pow,
|
|
270
|
+
env,
|
|
254
271
|
sessionId,
|
|
255
|
-
userAccessPolicy
|
|
272
|
+
userAccessPolicy,
|
|
273
|
+
req.ip
|
|
256
274
|
);
|
|
257
275
|
if (!valid) {
|
|
258
276
|
return next(
|
|
@@ -282,11 +300,12 @@ function prosopoRouter(env) {
|
|
|
282
300
|
})
|
|
283
301
|
);
|
|
284
302
|
}
|
|
303
|
+
const difficulty = powDifficulty || userAccessPolicy?.powDifficulty || clientSettings?.settings?.powDifficulty;
|
|
285
304
|
const challenge = await tasks$1.powCaptchaManager.getPowCaptchaChallenge(
|
|
286
305
|
user,
|
|
287
306
|
dapp,
|
|
288
307
|
origin,
|
|
289
|
-
|
|
308
|
+
difficulty
|
|
290
309
|
);
|
|
291
310
|
await tasks$1.db.storePowCaptchaRecord(
|
|
292
311
|
challenge.challenge,
|
|
@@ -297,7 +316,7 @@ function prosopoRouter(env) {
|
|
|
297
316
|
},
|
|
298
317
|
challenge.difficulty,
|
|
299
318
|
challenge.providerSignature,
|
|
300
|
-
|
|
319
|
+
compositeIpAddress.getCompositeIpAddress(req.ip || ""),
|
|
301
320
|
util.flatten(req.headers),
|
|
302
321
|
req.ja4,
|
|
303
322
|
frictionlessTokenId
|
|
@@ -313,12 +332,23 @@ function prosopoRouter(env) {
|
|
|
313
332
|
}
|
|
314
333
|
}
|
|
315
334
|
};
|
|
335
|
+
req.logger.info(() => ({
|
|
336
|
+
msg: "PoW captcha challenge issued",
|
|
337
|
+
data: {
|
|
338
|
+
captchaType: types.CaptchaType.pow,
|
|
339
|
+
challenge: challenge.challenge,
|
|
340
|
+
difficulty: challenge.difficulty,
|
|
341
|
+
user,
|
|
342
|
+
dapp,
|
|
343
|
+
session: sessionId
|
|
344
|
+
}
|
|
345
|
+
}));
|
|
316
346
|
return res.json(getPowCaptchaResponse);
|
|
317
347
|
} catch (err) {
|
|
318
348
|
req.logger.error(() => ({
|
|
319
349
|
err,
|
|
320
350
|
body: req.body,
|
|
321
|
-
msg: "Error in PoW captcha
|
|
351
|
+
msg: "Error in PoW captcha challenge request"
|
|
322
352
|
}));
|
|
323
353
|
return next(
|
|
324
354
|
new common.ProsopoApiError("API.BAD_REQUEST", {
|
|
@@ -350,15 +380,7 @@ function prosopoRouter(env) {
|
|
|
350
380
|
})
|
|
351
381
|
);
|
|
352
382
|
}
|
|
353
|
-
const {
|
|
354
|
-
challenge,
|
|
355
|
-
difficulty,
|
|
356
|
-
signature,
|
|
357
|
-
nonce,
|
|
358
|
-
verifiedTimeout,
|
|
359
|
-
dapp,
|
|
360
|
-
user
|
|
361
|
-
} = parsed;
|
|
383
|
+
const { challenge, signature, nonce, verifiedTimeout, dapp, user } = parsed;
|
|
362
384
|
validateAddress.validateSiteKey(dapp);
|
|
363
385
|
validateAddress.validateAddr(user);
|
|
364
386
|
try {
|
|
@@ -374,7 +396,6 @@ function prosopoRouter(env) {
|
|
|
374
396
|
}
|
|
375
397
|
const verified = await tasks$1.powCaptchaManager.verifyPowCaptchaSolution(
|
|
376
398
|
challenge,
|
|
377
|
-
difficulty,
|
|
378
399
|
signature.provider.challenge,
|
|
379
400
|
nonce,
|
|
380
401
|
verifiedTimeout,
|
|
@@ -416,17 +437,39 @@ function prosopoRouter(env) {
|
|
|
416
437
|
token: existingToken,
|
|
417
438
|
msg: "Token has already been used"
|
|
418
439
|
}));
|
|
419
|
-
return
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
440
|
+
return next(
|
|
441
|
+
new common.ProsopoApiError("API.BAD_REQUEST", {
|
|
442
|
+
context: {
|
|
443
|
+
code: 400,
|
|
444
|
+
siteKey: dapp,
|
|
445
|
+
user
|
|
446
|
+
},
|
|
447
|
+
i18n: req.i18n,
|
|
448
|
+
logger: req.logger
|
|
449
|
+
})
|
|
423
450
|
);
|
|
424
451
|
}
|
|
425
452
|
const lScore = tasks$1.frictionlessManager.checkLangRules(
|
|
426
453
|
req.headers["accept-language"] || ""
|
|
427
454
|
);
|
|
428
|
-
const {
|
|
429
|
-
|
|
455
|
+
const {
|
|
456
|
+
baseBotScore,
|
|
457
|
+
timestamp,
|
|
458
|
+
providerSelectEntropy,
|
|
459
|
+
userId,
|
|
460
|
+
userAgent
|
|
461
|
+
} = await tasks$1.frictionlessManager.decryptPayload(token);
|
|
462
|
+
req.logger.debug(() => ({
|
|
463
|
+
msg: "Decrypted payload",
|
|
464
|
+
data: {
|
|
465
|
+
baseBotScore,
|
|
466
|
+
timestamp,
|
|
467
|
+
providerSelectEntropy,
|
|
468
|
+
userId,
|
|
469
|
+
userAgent
|
|
470
|
+
}
|
|
471
|
+
}));
|
|
472
|
+
let botScore = baseBotScore + lScore;
|
|
430
473
|
const clientRecord = await tasks$1.db.getClientRecord(dapp);
|
|
431
474
|
if (!clientRecord) {
|
|
432
475
|
return next(
|
|
@@ -439,7 +482,8 @@ function prosopoRouter(env) {
|
|
|
439
482
|
}
|
|
440
483
|
const { valid, reason } = await tasks$1.frictionlessManager.isValidRequest(
|
|
441
484
|
clientRecord,
|
|
442
|
-
types.CaptchaType.frictionless
|
|
485
|
+
types.CaptchaType.frictionless,
|
|
486
|
+
env
|
|
443
487
|
);
|
|
444
488
|
if (!valid) {
|
|
445
489
|
return next(
|
|
@@ -462,7 +506,9 @@ function prosopoRouter(env) {
|
|
|
462
506
|
scoreComponents: {
|
|
463
507
|
baseScore: baseBotScore,
|
|
464
508
|
...lScore && { lScore }
|
|
465
|
-
}
|
|
509
|
+
},
|
|
510
|
+
providerSelectEntropy,
|
|
511
|
+
ipAddress: compositeIpAddress.getCompositeIpAddress(req.ip || "")
|
|
466
512
|
});
|
|
467
513
|
const userScope = blacklistRequestInspector.getRequestUserScope(
|
|
468
514
|
util.flatten(req.headers),
|
|
@@ -475,6 +521,28 @@ function prosopoRouter(env) {
|
|
|
475
521
|
dapp,
|
|
476
522
|
userScope
|
|
477
523
|
))[0];
|
|
524
|
+
const headersUserAgent = req.headers["user-agent"];
|
|
525
|
+
const hashedHeadersUserAgent = headersUserAgent ? hashUserAgent.hashUserAgent(headersUserAgent) : "";
|
|
526
|
+
const headersProsopoUser = req.headers["prosopo-user"];
|
|
527
|
+
if (hashedHeadersUserAgent !== userAgent || headersProsopoUser !== userId) {
|
|
528
|
+
req.logger.info(() => ({
|
|
529
|
+
msg: "User agent or user id does not match",
|
|
530
|
+
data: {
|
|
531
|
+
headersUserAgent,
|
|
532
|
+
hashedHeadersUserAgent,
|
|
533
|
+
userAgent,
|
|
534
|
+
// This is the hashed user agent from the token
|
|
535
|
+
headersProsopoUser,
|
|
536
|
+
userId
|
|
537
|
+
}
|
|
538
|
+
}));
|
|
539
|
+
return res.json(
|
|
540
|
+
await tasks$1.frictionlessManager.sendImageCaptcha(
|
|
541
|
+
tokenId,
|
|
542
|
+
frictionlessTasksUtils.timestampDecayFunction(timestamp)
|
|
543
|
+
)
|
|
544
|
+
);
|
|
545
|
+
}
|
|
478
546
|
if (userAccessPolicy) {
|
|
479
547
|
await tasks$1.frictionlessManager.scoreIncreaseAccessPolicy(
|
|
480
548
|
userAccessPolicy,
|
|
@@ -484,7 +552,10 @@ function prosopoRouter(env) {
|
|
|
484
552
|
);
|
|
485
553
|
if (userAccessPolicy.captchaType === types.CaptchaType.image) {
|
|
486
554
|
return res.json(
|
|
487
|
-
await tasks$1.frictionlessManager.sendImageCaptcha(
|
|
555
|
+
await tasks$1.frictionlessManager.sendImageCaptcha(
|
|
556
|
+
tokenId,
|
|
557
|
+
userAccessPolicy.solvedImagesCount
|
|
558
|
+
)
|
|
488
559
|
);
|
|
489
560
|
}
|
|
490
561
|
if (userAccessPolicy.captchaType === types.CaptchaType.pow) {
|
|
@@ -501,12 +572,26 @@ function prosopoRouter(env) {
|
|
|
501
572
|
tokenId
|
|
502
573
|
);
|
|
503
574
|
return res.json(
|
|
504
|
-
await tasks$1.frictionlessManager.sendImageCaptcha(
|
|
575
|
+
await tasks$1.frictionlessManager.sendImageCaptcha(
|
|
576
|
+
tokenId,
|
|
577
|
+
frictionlessTasksUtils.timestampDecayFunction(timestamp)
|
|
578
|
+
)
|
|
579
|
+
);
|
|
580
|
+
}
|
|
581
|
+
const hostVerified = await tasks$1.frictionlessManager.hostVerified(
|
|
582
|
+
providerSelectEntropy
|
|
583
|
+
);
|
|
584
|
+
if (!hostVerified.verified) {
|
|
585
|
+
botScore = await tasks$1.frictionlessManager.scoreIncreaseUnverifiedHost(
|
|
586
|
+
hostVerified.domain,
|
|
587
|
+
baseBotScore,
|
|
588
|
+
botScore,
|
|
589
|
+
tokenId
|
|
505
590
|
);
|
|
506
591
|
}
|
|
507
592
|
if (Number(botScore) > botThreshold) {
|
|
508
593
|
req.logger.info(() => ({
|
|
509
|
-
|
|
594
|
+
msg: "Bot score is greater than threshold",
|
|
510
595
|
data: {
|
|
511
596
|
botScore,
|
|
512
597
|
botThreshold,
|
|
@@ -514,7 +599,10 @@ function prosopoRouter(env) {
|
|
|
514
599
|
}
|
|
515
600
|
}));
|
|
516
601
|
return res.json(
|
|
517
|
-
await tasks$1.frictionlessManager.sendImageCaptcha(
|
|
602
|
+
await tasks$1.frictionlessManager.sendImageCaptcha(
|
|
603
|
+
tokenId,
|
|
604
|
+
env.config.captchas.solved.count
|
|
605
|
+
)
|
|
518
606
|
);
|
|
519
607
|
}
|
|
520
608
|
return res.json(
|
|
@@ -10,26 +10,26 @@ const domainMiddleware = (env) => {
|
|
|
10
10
|
const tasks$1 = new tasks.Tasks(env);
|
|
11
11
|
return async (req, res, next) => {
|
|
12
12
|
try {
|
|
13
|
-
const
|
|
14
|
-
if (!
|
|
13
|
+
const siteKey = req.headers["prosopo-site-key"];
|
|
14
|
+
if (!siteKey)
|
|
15
15
|
throw siteKeyNotRegisteredError(
|
|
16
16
|
req.i18n,
|
|
17
17
|
"No sitekey provided",
|
|
18
18
|
req.logger
|
|
19
19
|
);
|
|
20
20
|
try {
|
|
21
|
-
utilCrypto.validateAddress(
|
|
21
|
+
utilCrypto.validateAddress(siteKey, false, 42);
|
|
22
22
|
} catch (err) {
|
|
23
|
-
throw invalidSiteKeyError(req.i18n,
|
|
23
|
+
throw invalidSiteKeyError(req.i18n, siteKey, req.logger);
|
|
24
24
|
}
|
|
25
|
-
const clientSettings = await tasks$1.db.getClientRecord(
|
|
25
|
+
const clientSettings = await tasks$1.db.getClientRecord(siteKey);
|
|
26
26
|
if (!clientSettings)
|
|
27
|
-
throw siteKeyNotRegisteredError(req.i18n,
|
|
27
|
+
throw siteKeyNotRegisteredError(req.i18n, siteKey, req.logger);
|
|
28
28
|
const allowedDomains = clientSettings.settings?.domains;
|
|
29
29
|
if (!allowedDomains)
|
|
30
30
|
throw siteKeyInvalidDomainError(
|
|
31
31
|
req.i18n,
|
|
32
|
-
|
|
32
|
+
siteKey,
|
|
33
33
|
req.hostname,
|
|
34
34
|
req.logger
|
|
35
35
|
);
|
|
@@ -37,7 +37,7 @@ const domainMiddleware = (env) => {
|
|
|
37
37
|
if (!origin)
|
|
38
38
|
throw unauthorizedOriginError(req.i18n, void 0, req.logger);
|
|
39
39
|
for (const domain of allowedDomains) {
|
|
40
|
-
if (tasks$1.clientTaskManager.
|
|
40
|
+
if (tasks$1.clientTaskManager.domainPatternMatcher(origin, domain)) {
|
|
41
41
|
next();
|
|
42
42
|
return;
|
|
43
43
|
}
|
|
@@ -19,6 +19,10 @@ const headerCheckMiddleware = (env) => {
|
|
|
19
19
|
validateAddress.validateAddr(user, void 0, req.logger);
|
|
20
20
|
req.user = user;
|
|
21
21
|
req.siteKey = siteKey;
|
|
22
|
+
req.logger = req.logger.with({
|
|
23
|
+
user,
|
|
24
|
+
siteKey
|
|
25
|
+
});
|
|
22
26
|
next();
|
|
23
27
|
} catch (err) {
|
|
24
28
|
return apiExpressRouter.handleErrors(err, req, res, next);
|
|
@@ -3,6 +3,9 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
|
3
3
|
const types = require("@prosopo/types");
|
|
4
4
|
function ignoreMiddleware() {
|
|
5
5
|
return (req, res, next) => {
|
|
6
|
+
if (req.originalUrl.indexOf(types.PublicApiPaths.Healthz) !== -1) {
|
|
7
|
+
return next();
|
|
8
|
+
}
|
|
6
9
|
if (req.originalUrl.indexOf(types.ApiPrefix) === -1) {
|
|
7
10
|
res.statusCode = 404;
|
|
8
11
|
res.send("Not Found");
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
-
const node_crypto = require("node:crypto");
|
|
4
3
|
const node_stream = require("node:stream");
|
|
5
4
|
const apiExpressRouter = require("@prosopo/api-express-router");
|
|
6
5
|
const common = require("@prosopo/common");
|
|
@@ -17,7 +16,6 @@ const getJA4 = async (headers, logger) => {
|
|
|
17
16
|
try {
|
|
18
17
|
const xTlsClientHello = (headers["x-tls-clienthello"] || "").toString();
|
|
19
18
|
const xTlsVersion = (headers["x-tls-version"] || "").toString().toLowerCase();
|
|
20
|
-
const xTlsServerName = (headers["x-tls-server-name"] || "").toString();
|
|
21
19
|
const clientHelloBuffer = Buffer.from(xTlsClientHello, "base64");
|
|
22
20
|
logger.debug(() => ({
|
|
23
21
|
msg: "ClientHello First Bytes:",
|
|
@@ -33,32 +31,13 @@ const getJA4 = async (headers, logger) => {
|
|
|
33
31
|
msg: "Headers TLS Version:",
|
|
34
32
|
data: { xTlsVersion }
|
|
35
33
|
}));
|
|
36
|
-
const tlsVersion = xTlsVersion.replace(/(tls)|\./g, "");
|
|
37
34
|
const readableStream = new node_stream.Readable({
|
|
38
35
|
read() {
|
|
39
36
|
this.push(clientHelloBuffer);
|
|
40
37
|
}
|
|
41
38
|
});
|
|
42
39
|
const clientHello = await readTlsClientHello.readTlsClientHello(readableStream);
|
|
43
|
-
const
|
|
44
|
-
const [_tlsVersion, cipherSuites, extensions] = clientHello.fingerprintData;
|
|
45
|
-
const transport = "t";
|
|
46
|
-
const sniIndicator = xTlsServerName ? "d" : "i";
|
|
47
|
-
const validCipherSuites = cipherSuites.filter(
|
|
48
|
-
(cs) => (cs & 3855) !== 2570
|
|
49
|
-
);
|
|
50
|
-
const cipherCount = validCipherSuites.length;
|
|
51
|
-
const validExtensions = extensions.filter(
|
|
52
|
-
(ext) => (ext & 3855) !== 2570
|
|
53
|
-
);
|
|
54
|
-
const extensionCount = validExtensions.length;
|
|
55
|
-
const alpn = alpnProtocols?.length ? alpnProtocols[0] : "";
|
|
56
|
-
const alpnLabel = alpn ? `${alpn[0]}${alpn[alpn.length - 1]}` : "00";
|
|
57
|
-
const sortedCiphers = validCipherSuites.map((cs) => cs.toString(16).padStart(4, "0")).sort().join(",");
|
|
58
|
-
const cipherHash = node_crypto.createHash("sha256").update(sortedCiphers).digest("hex").slice(0, 12);
|
|
59
|
-
const decimalString = extensions.sort((a, b) => a - b).map((ext) => ext.toString(10)).join("-");
|
|
60
|
-
const extensionHash = node_crypto.createHash("sha256").update(decimalString).digest("hex").slice(0, 12);
|
|
61
|
-
const ja4PlusFingerprint = `${transport}${tlsVersion}${sniIndicator}${cipherCount}${extensionCount}${alpnLabel}_${cipherHash}_${extensionHash}`;
|
|
40
|
+
const ja4PlusFingerprint = readTlsClientHello.calculateJa4FromHelloData(clientHello);
|
|
62
41
|
return { ja4PlusFingerprint };
|
|
63
42
|
} catch (e) {
|
|
64
43
|
logger.error(() => ({
|
|
@@ -74,6 +53,9 @@ const ja4Middleware = (env) => {
|
|
|
74
53
|
req.logger.debug(() => ({ data: { url: req.url } }));
|
|
75
54
|
const ja4 = await getJA4(req.headers, req.logger);
|
|
76
55
|
req.ja4 = ja4.ja4PlusFingerprint || "";
|
|
56
|
+
req.logger = req.logger.with({
|
|
57
|
+
ja4: req.ja4
|
|
58
|
+
});
|
|
77
59
|
next();
|
|
78
60
|
} catch (err) {
|
|
79
61
|
return apiExpressRouter.handleErrors(err, req, res, next);
|
package/dist/cjs/api/public.cjs
CHANGED
|
@@ -5,16 +5,39 @@ const common = require("@prosopo/common");
|
|
|
5
5
|
const types = require("@prosopo/types");
|
|
6
6
|
const util = require("@prosopo/util");
|
|
7
7
|
const express = require("express");
|
|
8
|
-
function publicRouter() {
|
|
8
|
+
function publicRouter(env) {
|
|
9
9
|
const router = express.Router();
|
|
10
10
|
router.get(types.PublicApiPaths.Healthz, (req, res) => {
|
|
11
11
|
res.status(200).send("OK");
|
|
12
12
|
});
|
|
13
13
|
router.get(types.PublicApiPaths.GetProviderDetails, async (req, res, next) => {
|
|
14
14
|
try {
|
|
15
|
-
|
|
15
|
+
const db = env.getDb();
|
|
16
|
+
const redisConnection = db.getRedisConnection();
|
|
17
|
+
const redisAccessRulesConnection = db.getRedisAccessRulesConnection();
|
|
18
|
+
const response = {
|
|
19
|
+
version: util.version,
|
|
20
|
+
message: "Provider online",
|
|
21
|
+
redis: [
|
|
22
|
+
{
|
|
23
|
+
actor: "General",
|
|
24
|
+
isReady: redisConnection.isReady(),
|
|
25
|
+
awaitingTimeSeconds: Math.ceil(
|
|
26
|
+
redisConnection.getAwaitingTimeMs() / 1e3
|
|
27
|
+
)
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
actor: "UAP",
|
|
31
|
+
isReady: redisAccessRulesConnection.isReady(),
|
|
32
|
+
awaitingTimeSeconds: Math.ceil(
|
|
33
|
+
redisAccessRulesConnection.getAwaitingTimeMs() / 1e3
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
]
|
|
37
|
+
};
|
|
38
|
+
return res.json(response);
|
|
16
39
|
} catch (err) {
|
|
17
|
-
|
|
40
|
+
env.logger.error(() => ({
|
|
18
41
|
err,
|
|
19
42
|
data: { reqParams: req.params },
|
|
20
43
|
msg: "Error getting provider details"
|
package/dist/cjs/api/verify.cjs
CHANGED
|
@@ -24,7 +24,7 @@ function prosopoVerifyRouter(env) {
|
|
|
24
24
|
})
|
|
25
25
|
);
|
|
26
26
|
}
|
|
27
|
-
const { dappSignature, token, ip } = parsed;
|
|
27
|
+
const { dappSignature, token, ip, maxVerifiedTime } = parsed;
|
|
28
28
|
try {
|
|
29
29
|
const { user, dapp, timestamp, commitmentId } = types.decodeProcaptchaOutput(token);
|
|
30
30
|
utilCrypto.validateAddress(dapp, false, 42);
|
|
@@ -45,7 +45,8 @@ function prosopoVerifyRouter(env) {
|
|
|
45
45
|
user,
|
|
46
46
|
dapp,
|
|
47
47
|
commitmentId,
|
|
48
|
-
|
|
48
|
+
env,
|
|
49
|
+
maxVerifiedTime,
|
|
49
50
|
ip
|
|
50
51
|
);
|
|
51
52
|
req.logger.debug(() => ({ data: { response } }));
|
|
@@ -113,6 +114,7 @@ function prosopoVerifyRouter(env) {
|
|
|
113
114
|
dapp,
|
|
114
115
|
challenge,
|
|
115
116
|
verifiedTimeout,
|
|
117
|
+
env,
|
|
116
118
|
ip
|
|
117
119
|
);
|
|
118
120
|
const verificationResponse = tasks$1.powCaptchaManager.getVerificationResponse(
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const typesDatabase = require("@prosopo/types-database");
|
|
4
|
+
const util = require("@prosopo/util");
|
|
5
|
+
const ipAddress = require("ip-address");
|
|
6
|
+
const V6_SHIFT = 64n;
|
|
7
|
+
const v6_LOWER_MASK = (1n << V6_SHIFT) - 1n;
|
|
8
|
+
const getCompositeIpAddress = (ip) => {
|
|
9
|
+
let ipAddress2;
|
|
10
|
+
try {
|
|
11
|
+
ipAddress2 = "string" === typeof ip ? util.getIPAddress(ip) : ip;
|
|
12
|
+
} catch (e) {
|
|
13
|
+
return {
|
|
14
|
+
lower: 0n,
|
|
15
|
+
type: typesDatabase.IpAddressType.v4
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
return getCompositeFromIpAddress(ipAddress2);
|
|
19
|
+
};
|
|
20
|
+
const getCompositeFromIpAddress = (ipAddress$1) => {
|
|
21
|
+
const numericIp = ipAddress$1.bigInt();
|
|
22
|
+
if (ipAddress$1 instanceof ipAddress.Address4) {
|
|
23
|
+
return {
|
|
24
|
+
lower: numericIp,
|
|
25
|
+
type: typesDatabase.IpAddressType.v4
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
ipAddress$1;
|
|
29
|
+
return {
|
|
30
|
+
lower: numericIp & v6_LOWER_MASK,
|
|
31
|
+
upper: numericIp >> V6_SHIFT,
|
|
32
|
+
type: typesDatabase.IpAddressType.v6
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
const getIpAddressFromComposite = (compositeIpAddress) => {
|
|
36
|
+
switch (compositeIpAddress.type) {
|
|
37
|
+
case typesDatabase.IpAddressType.v4:
|
|
38
|
+
return ipAddress.Address4.fromBigInt(getBigInt(compositeIpAddress.lower));
|
|
39
|
+
case typesDatabase.IpAddressType.v6:
|
|
40
|
+
return ipAddress.Address6.fromBigInt(
|
|
41
|
+
getBigInt(compositeIpAddress.upper) << V6_SHIFT | getBigInt(compositeIpAddress.lower) & v6_LOWER_MASK
|
|
42
|
+
);
|
|
43
|
+
default:
|
|
44
|
+
never();
|
|
45
|
+
return ipAddress.Address4.fromBigInt(0n);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
const getBigInt = (number) => BigInt(number || 0n);
|
|
49
|
+
const never = () => {
|
|
50
|
+
throw new Error("Unhandled type");
|
|
51
|
+
};
|
|
52
|
+
exports.getCompositeIpAddress = getCompositeIpAddress;
|
|
53
|
+
exports.getIpAddressFromComposite = getIpAddressFromComposite;
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -14,9 +14,13 @@ const headerCheckMiddleware = require("./api/headerCheckMiddleware.cjs");
|
|
|
14
14
|
const createApiAdminRoutesProvider = require("./api/admin/createApiAdminRoutesProvider.cjs");
|
|
15
15
|
const ignoreMiddleware = require("./api/ignoreMiddleware.cjs");
|
|
16
16
|
const robotsMiddleware = require("./api/robotsMiddleware.cjs");
|
|
17
|
+
const compositeIpAddress = require("./compositeIpAddress.cjs");
|
|
18
|
+
const ipComparison = require("./services/ipComparison.cjs");
|
|
17
19
|
const tasks = require("./tasks/tasks.cjs");
|
|
18
20
|
exports.checkIfTaskIsRunning = util.checkIfTaskIsRunning;
|
|
21
|
+
exports.deepValidateIpAddress = util.deepValidateIpAddress;
|
|
19
22
|
exports.encodeStringAddress = util.encodeStringAddress;
|
|
23
|
+
exports.evaluateIpValidationRules = util.evaluateIpValidationRules;
|
|
20
24
|
exports.getIPAddress = util.getIPAddress;
|
|
21
25
|
exports.getIPAddressFromBigInt = util.getIPAddressFromBigInt;
|
|
22
26
|
exports.shuffleArray = util.shuffleArray;
|
|
@@ -35,4 +39,7 @@ exports.headerCheckMiddleware = headerCheckMiddleware.headerCheckMiddleware;
|
|
|
35
39
|
exports.createApiAdminRoutesProvider = createApiAdminRoutesProvider.createApiAdminRoutesProvider;
|
|
36
40
|
exports.ignoreMiddleware = ignoreMiddleware.ignoreMiddleware;
|
|
37
41
|
exports.robotsMiddleware = robotsMiddleware.robotsMiddleware;
|
|
42
|
+
exports.getCompositeIpAddress = compositeIpAddress.getCompositeIpAddress;
|
|
43
|
+
exports.getIpAddressFromComposite = compositeIpAddress.getIpAddressFromComposite;
|
|
44
|
+
exports.compareIPs = ipComparison.compareIPs;
|
|
38
45
|
exports.Tasks = tasks.Tasks;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const util = require("@prosopo/util");
|
|
4
|
+
const constructPairList = (list) => {
|
|
5
|
+
if (list.length % 2 !== 0) {
|
|
6
|
+
throw new Error("Invalid pairs length");
|
|
7
|
+
}
|
|
8
|
+
const pairList = [];
|
|
9
|
+
for (let i = 0; i < list.length; i += 2) {
|
|
10
|
+
pairList.push([util.at(list, i), util.at(list, i + 1)]);
|
|
11
|
+
}
|
|
12
|
+
return pairList;
|
|
13
|
+
};
|
|
14
|
+
const containsIdenticalPairs = (pairsLists) => {
|
|
15
|
+
const set = /* @__PURE__ */ new Set();
|
|
16
|
+
for (const pairList of pairsLists) {
|
|
17
|
+
for (const pair of pairList) {
|
|
18
|
+
const x = util.at(pair, 0);
|
|
19
|
+
const y = util.at(pair, 1);
|
|
20
|
+
const coordString = `${x},${y}`;
|
|
21
|
+
set.add(coordString);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return set.size !== pairsLists.flat().flat().length / 2;
|
|
25
|
+
};
|
|
26
|
+
exports.constructPairList = constructPairList;
|
|
27
|
+
exports.containsIdenticalPairs = containsIdenticalPairs;
|