@webdecoy/fcaptcha 1.38.0 → 1.39.1
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/package.json +5 -5
- package/webbotauth.js +32 -27
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webdecoy/fcaptcha",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.39.1",
|
|
4
4
|
"description": "Open source CAPTCHA with PoW, bot detection, and Vision AI protection",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"exports": {
|
|
@@ -22,13 +22,13 @@
|
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"cors": "^2.8.5",
|
|
25
|
-
"express": "^
|
|
25
|
+
"express": "^5.2.1",
|
|
26
26
|
"ipaddr.js": "^2.4.0",
|
|
27
|
-
"redis": "^
|
|
28
|
-
"web-bot-auth": "^0.
|
|
27
|
+
"redis": "^6.2.1",
|
|
28
|
+
"web-bot-auth": "^0.2.0"
|
|
29
29
|
},
|
|
30
30
|
"peerDependencies": {
|
|
31
|
-
"express": "^4.18.0"
|
|
31
|
+
"express": "^4.18.0 || ^5.0.0"
|
|
32
32
|
},
|
|
33
33
|
"peerDependenciesMeta": {
|
|
34
34
|
"express": {
|
package/webbotauth.js
CHANGED
|
@@ -59,7 +59,6 @@ const ipaddr = require('ipaddr.js');
|
|
|
59
59
|
const {
|
|
60
60
|
verify,
|
|
61
61
|
jwkToKeyID,
|
|
62
|
-
helpers,
|
|
63
62
|
HTTP_MESSAGE_SIGNATURES_DIRECTORY,
|
|
64
63
|
} = require('web-bot-auth');
|
|
65
64
|
const { verifierFromJWK } = require('web-bot-auth/crypto');
|
|
@@ -69,11 +68,6 @@ const directoryAgent = new https.Agent({ keepAlive: true, maxTotalSockets: 16, m
|
|
|
69
68
|
const MAX_DIRECTORY_BYTES = 256 * 1024;
|
|
70
69
|
const DIRECTORY_CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour
|
|
71
70
|
|
|
72
|
-
// The exact message web-bot-auth/crypto throws when crypto.subtle.verify()
|
|
73
|
-
// returns false. This — and only this — is affirmative evidence of a forged
|
|
74
|
-
// signature. Everything else (fetch failure, expiry, missing key) fails open.
|
|
75
|
-
const CRYPTO_FAILURE_MESSAGE = 'invalid signature';
|
|
76
|
-
|
|
77
71
|
// Discovery types from protocol-00. `directory` is the default when the header
|
|
78
72
|
// carries no `type` parameter.
|
|
79
73
|
const DISCOVERY_DIRECTORY = 'directory';
|
|
@@ -292,7 +286,9 @@ async function resolveKey(signatureAgentHeader, keyid) {
|
|
|
292
286
|
for (const jwk of keys) {
|
|
293
287
|
let thumbprint;
|
|
294
288
|
try {
|
|
295
|
-
thumbprint = await jwkToKeyID(jwk,
|
|
289
|
+
thumbprint = await jwkToKeyID(jwk,
|
|
290
|
+
value => crypto.subtle.digest('SHA-256', value),
|
|
291
|
+
value => Buffer.from(value).toString('base64url'));
|
|
296
292
|
} catch {
|
|
297
293
|
continue; // unsupported/malformed key in the set — skip it
|
|
298
294
|
}
|
|
@@ -426,38 +422,47 @@ async function checkWebBotAuth(req) {
|
|
|
426
422
|
}
|
|
427
423
|
const path = req.originalUrl || req.url || '/';
|
|
428
424
|
const message = {
|
|
425
|
+
kind: 'request',
|
|
429
426
|
method: (req.method || 'POST').toUpperCase(),
|
|
430
|
-
|
|
431
|
-
headers,
|
|
427
|
+
targetUri: `${forwardedScheme(req)}://${host}${path}`,
|
|
428
|
+
fields: Object.entries(headers).flatMap(([name, value]) =>
|
|
429
|
+
(Array.isArray(value) ? value : [value]).filter(v => v != null)
|
|
430
|
+
.map(v => ({ name, value: String(v) }))),
|
|
432
431
|
};
|
|
433
432
|
|
|
434
433
|
let cryptoFailed = false;
|
|
435
434
|
let resolvedKeyId = '';
|
|
436
435
|
let resolvedAlg = '';
|
|
436
|
+
let verifiedAgent = agent;
|
|
437
437
|
|
|
438
438
|
try {
|
|
439
439
|
await withTimeout(
|
|
440
|
-
verify(message,
|
|
441
|
-
|
|
442
|
-
throw new FetchFailure('no Signature-Agent to resolve key');
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
440
|
+
verify(message, {
|
|
441
|
+
async resolver(candidate) {
|
|
442
|
+
if (!candidate.signatureAgent) throw new FetchFailure('no Signature-Agent to resolve key');
|
|
443
|
+
// Resolve the covered dictionary member, not an unsigned first member.
|
|
444
|
+
const entry = candidate.signatureAgent;
|
|
445
|
+
const sourceHeader = `sig1=${JSON.stringify(entry.uri)};type=${entry.type}`;
|
|
446
|
+
const jwk = await resolveKey(sourceHeader, candidate.keyid);
|
|
447
|
+
const verifier = await verifierFromJWK(jwk);
|
|
448
|
+
resolvedKeyId = candidate.keyid;
|
|
449
|
+
resolvedAlg = verifier.algorithm;
|
|
450
|
+
verifiedAgent = displayAgent(JSON.stringify(entry.uri));
|
|
451
|
+
return {
|
|
452
|
+
...verifier,
|
|
453
|
+
async verify(data, signature) {
|
|
454
|
+
const valid = await verifier.verify(data, signature);
|
|
455
|
+
// Only a completed cryptographic rejection is evidence of forgery.
|
|
456
|
+
if (valid === false) cryptoFailed = true;
|
|
457
|
+
return valid;
|
|
458
|
+
},
|
|
459
|
+
};
|
|
460
|
+
},
|
|
461
|
+
validate: assertWebBotAuthParams,
|
|
457
462
|
}),
|
|
458
463
|
VERIFY_TIMEOUT_MS,
|
|
459
464
|
);
|
|
460
|
-
return [verifiedDetection(
|
|
465
|
+
return [verifiedDetection(verifiedAgent, resolvedKeyId, resolvedAlg)];
|
|
461
466
|
} catch (e) {
|
|
462
467
|
if (cryptoFailed) {
|
|
463
468
|
return [forgedDetection(agent, e)];
|