@prosopo/provider 3.3.0 → 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 +545 -0
- package/dist/api/admin/apiRemoveDetectorKeyEndpoint.js +7 -4
- package/dist/api/blacklistRequestInspector.js +11 -9
- 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 +11 -9
- 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 +26 -23
- package/vite.test.config.ts +3 -2
- package/vite.threads.test.config.ts +33 -0
|
@@ -13,7 +13,20 @@ class CaptchaManager {
|
|
|
13
13
|
);
|
|
14
14
|
return tokenRecord ? tokenRecord._id : void 0;
|
|
15
15
|
}
|
|
16
|
-
async
|
|
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
|
+
}
|
|
27
|
+
return { valid: true };
|
|
28
|
+
}
|
|
29
|
+
async isValidRequest(clientSettings, requestedCaptchaType, env, sessionId, userAccessPolicy, currentIP) {
|
|
17
30
|
this.logger.debug(() => ({
|
|
18
31
|
msg: "Validating request",
|
|
19
32
|
data: {
|
|
@@ -52,11 +65,45 @@ class CaptchaManager {
|
|
|
52
65
|
type: requestedCaptchaType
|
|
53
66
|
};
|
|
54
67
|
}
|
|
68
|
+
if (currentIP) {
|
|
69
|
+
const ipValidation = await this.validateFrictionlessTokenIP(
|
|
70
|
+
sessionRecord,
|
|
71
|
+
currentIP,
|
|
72
|
+
env
|
|
73
|
+
);
|
|
74
|
+
if (!ipValidation.valid) {
|
|
75
|
+
return {
|
|
76
|
+
valid: false,
|
|
77
|
+
reason: ipValidation.reason,
|
|
78
|
+
type: requestedCaptchaType
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
}
|
|
55
82
|
const frictionlessTokenId = await this.getFrictionlessTokenIdFromSession(sessionRecord);
|
|
83
|
+
if (sessionRecord.captchaType !== requestedCaptchaType) {
|
|
84
|
+
this.logger.warn(() => ({
|
|
85
|
+
msg: "Invalid frictionless request",
|
|
86
|
+
data: {
|
|
87
|
+
account: clientSettings.account,
|
|
88
|
+
sessionId
|
|
89
|
+
}
|
|
90
|
+
}));
|
|
91
|
+
return {
|
|
92
|
+
valid: false,
|
|
93
|
+
reason: "CAPTCHA.NO_SESSION_FOUND",
|
|
94
|
+
type: requestedCaptchaType
|
|
95
|
+
};
|
|
96
|
+
}
|
|
56
97
|
return {
|
|
57
98
|
valid: true,
|
|
58
99
|
frictionlessTokenId,
|
|
59
|
-
type: requestedCaptchaType
|
|
100
|
+
type: requestedCaptchaType,
|
|
101
|
+
...sessionRecord.powDifficulty && {
|
|
102
|
+
powDifficulty: sessionRecord.powDifficulty
|
|
103
|
+
},
|
|
104
|
+
...sessionRecord.solvedImagesCount && {
|
|
105
|
+
solvedImagesCount: sessionRecord.solvedImagesCount
|
|
106
|
+
}
|
|
60
107
|
};
|
|
61
108
|
}
|
|
62
109
|
this.logger.warn(() => ({
|
|
@@ -124,11 +124,10 @@ class ClientTaskManager {
|
|
|
124
124
|
threshold: 0
|
|
125
125
|
};
|
|
126
126
|
}
|
|
127
|
+
const { _id, token, ...tokenRecordWithoutId } = tokenRecord;
|
|
127
128
|
return {
|
|
128
129
|
...record,
|
|
129
|
-
|
|
130
|
-
scoreComponents: tokenRecord?.scoreComponents,
|
|
131
|
-
threshold: tokenRecord?.threshold || 0
|
|
130
|
+
...tokenRecordWithoutId
|
|
132
131
|
};
|
|
133
132
|
});
|
|
134
133
|
if (filteredBatch.length > 0) {
|
|
@@ -136,6 +135,9 @@ class ClientTaskManager {
|
|
|
136
135
|
await this.providerDB.markSessionRecordsStored(
|
|
137
136
|
filteredBatch.map((record) => record.sessionId)
|
|
138
137
|
);
|
|
138
|
+
await this.providerDB.markFrictionlessTokenRecordsStored(
|
|
139
|
+
filteredBatch.map((record) => record.tokenId).filter((id) => !!id)
|
|
140
|
+
);
|
|
139
141
|
}
|
|
140
142
|
processedSessionRecords += filteredBatch.length;
|
|
141
143
|
}
|
|
@@ -246,25 +248,44 @@ class ClientTaskManager {
|
|
|
246
248
|
const activeDetectorKeys = await this.providerDB.getDetectorKeys();
|
|
247
249
|
return activeDetectorKeys;
|
|
248
250
|
}
|
|
249
|
-
async removeDetectorKey(detectorKey) {
|
|
251
|
+
async removeDetectorKey(detectorKey, expirationInSeconds) {
|
|
250
252
|
if (!isValidPrivateKey(detectorKey)) {
|
|
251
253
|
throw new ProsopoApiError("INVALID_DETECTOR_KEY", {
|
|
252
254
|
context: { detectorKey },
|
|
253
255
|
logger: this.logger
|
|
254
256
|
});
|
|
255
257
|
}
|
|
256
|
-
await this.providerDB.removeDetectorKey(detectorKey);
|
|
258
|
+
await this.providerDB.removeDetectorKey(detectorKey, expirationInSeconds);
|
|
257
259
|
}
|
|
258
|
-
|
|
260
|
+
/**
|
|
261
|
+
* Matches a request referrer against an allowed domain pattern.
|
|
262
|
+
* Supports global '*', subdomain '*.example.com', glob '*example*',
|
|
263
|
+
* plain domains (exact or subdomain), and 'localhost'.
|
|
264
|
+
*/
|
|
265
|
+
domainPatternMatcher(referrer, clientDomain) {
|
|
259
266
|
if (!referrer || !clientDomain) return false;
|
|
260
|
-
if (clientDomain === "*") return true;
|
|
261
267
|
try {
|
|
262
|
-
const
|
|
263
|
-
const
|
|
264
|
-
|
|
265
|
-
|
|
268
|
+
const referrerHost = parseUrl(referrer).hostname.replace(/\.$/, "");
|
|
269
|
+
const pattern = clientDomain.trim().toLowerCase();
|
|
270
|
+
if (pattern === "*") return true;
|
|
271
|
+
if (pattern === "localhost") {
|
|
272
|
+
return referrerHost === "localhost" || referrerHost.startsWith("localhost:");
|
|
273
|
+
}
|
|
274
|
+
if (pattern.startsWith("*.")) {
|
|
275
|
+
const suffix = pattern.slice(2);
|
|
276
|
+
const allowed = parseUrl(suffix).hostname.replace(/\.$/, "");
|
|
277
|
+
return referrerHost.endsWith(`.${allowed}`) || referrerHost === allowed;
|
|
278
|
+
}
|
|
279
|
+
if (pattern.includes("*")) {
|
|
280
|
+
const escaped = pattern.replace(/[.+?^${}()|\[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
281
|
+
const regex = new RegExp(`^${escaped}$`, "i");
|
|
282
|
+
return regex.test(referrerHost);
|
|
283
|
+
}
|
|
284
|
+
const allowedHost = parseUrl(pattern).hostname.replace(/\.$/, "");
|
|
285
|
+
return referrerHost === allowedHost || referrerHost.endsWith(`.${allowedHost}`);
|
|
286
|
+
} catch (e) {
|
|
266
287
|
this.logger.error(() => ({
|
|
267
|
-
msg: "Error in
|
|
288
|
+
msg: "Error in domainPatternMatcher",
|
|
268
289
|
data: { referrer, clientDomain }
|
|
269
290
|
}));
|
|
270
291
|
return false;
|