@prosopo/provider 2.5.5 → 2.7.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.
Files changed (74) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/dist/api/captcha.d.ts.map +1 -1
  3. package/dist/api/captcha.js +1 -1
  4. package/dist/api/captcha.js.map +1 -1
  5. package/dist/api/ja4Middleware.d.ts +6 -0
  6. package/dist/api/ja4Middleware.d.ts.map +1 -1
  7. package/dist/api/ja4Middleware.js +7 -7
  8. package/dist/api/ja4Middleware.js.map +1 -1
  9. package/dist/cjs/api/admin/apiAdminRoutesProvider.cjs +32 -0
  10. package/dist/cjs/api/admin/apiRegisterSiteKeyEndpoint.cjs +25 -0
  11. package/dist/cjs/api/admin/apiRemoveDetectorKeyEndpoint.cjs +32 -0
  12. package/dist/cjs/api/admin/apiUpdateDetectorKeyEndpoint.cjs +32 -0
  13. package/dist/cjs/api/admin/createApiAdminRoutesProvider.cjs +10 -0
  14. package/dist/cjs/api/authMiddleware.cjs +81 -0
  15. package/dist/cjs/api/blacklistRequestInspector.cjs +73 -0
  16. package/dist/cjs/api/block.cjs +24 -0
  17. package/dist/cjs/api/captcha.cjs +482 -0
  18. package/dist/cjs/api/domainMiddleware.cjs +89 -0
  19. package/dist/cjs/api/headerCheckMiddleware.cjs +29 -0
  20. package/dist/cjs/api/ignoreMiddleware.cjs +14 -0
  21. package/dist/cjs/api/ja4Middleware.cjs +73 -0
  22. package/dist/cjs/api/public.cjs +27 -0
  23. package/dist/cjs/api/requestLoggerMiddleware.cjs +14 -0
  24. package/dist/cjs/api/robotsMiddleware.cjs +12 -0
  25. package/dist/cjs/api/validateAddress.cjs +19 -0
  26. package/dist/cjs/api/verify.cjs +138 -0
  27. package/dist/cjs/index.cjs +41 -0
  28. package/dist/cjs/rules/lang.cjs +16 -0
  29. package/dist/cjs/schedulers/captchaScheduler.cjs +31 -0
  30. package/dist/cjs/schedulers/getClientList.cjs +29 -0
  31. package/dist/cjs/tasks/captchaManager.cjs +90 -0
  32. package/dist/cjs/tasks/client/clientTasks.cjs +281 -0
  33. package/dist/cjs/tasks/dataset/datasetTasks.cjs +30 -0
  34. package/dist/cjs/tasks/dataset/datasetTasksUtils.cjs +34 -0
  35. package/dist/cjs/tasks/detection/decodePayload.cjs +475 -0
  36. package/dist/cjs/tasks/detection/getBotScore.cjs +13 -0
  37. package/dist/cjs/tasks/frictionless/frictionlessTasks.cjs +121 -0
  38. package/dist/cjs/tasks/frictionless/frictionlessTasksUtils.cjs +11 -0
  39. package/dist/cjs/tasks/imgCaptcha/imgCaptchaTasks.cjs +366 -0
  40. package/dist/cjs/tasks/imgCaptcha/imgCaptchaTasksUtils.cjs +25 -0
  41. package/dist/cjs/tasks/index.cjs +4 -0
  42. package/dist/cjs/tasks/powCaptcha/powTasks.cjs +155 -0
  43. package/dist/cjs/tasks/powCaptcha/powTasksUtils.cjs +26 -0
  44. package/dist/cjs/tasks/tasks.cjs +51 -0
  45. package/dist/cjs/util.cjs +58 -0
  46. package/dist/schedulers/captchaScheduler.d.ts +1 -1
  47. package/dist/schedulers/captchaScheduler.d.ts.map +1 -1
  48. package/dist/schedulers/captchaScheduler.js +1 -7
  49. package/dist/schedulers/captchaScheduler.js.map +1 -1
  50. package/dist/schedulers/getClientList.d.ts +1 -1
  51. package/dist/schedulers/getClientList.d.ts.map +1 -1
  52. package/dist/schedulers/getClientList.js +1 -7
  53. package/dist/schedulers/getClientList.js.map +1 -1
  54. package/dist/tasks/client/clientTasks.d.ts +5 -2
  55. package/dist/tasks/client/clientTasks.d.ts.map +1 -1
  56. package/dist/tasks/client/clientTasks.js +55 -9
  57. package/dist/tasks/client/clientTasks.js.map +1 -1
  58. package/dist/tasks/imgCaptcha/imgCaptchaTasks.d.ts +1 -1
  59. package/dist/tasks/imgCaptcha/imgCaptchaTasks.d.ts.map +1 -1
  60. package/dist/tasks/imgCaptcha/imgCaptchaTasks.js +13 -3
  61. package/dist/tasks/imgCaptcha/imgCaptchaTasks.js.map +1 -1
  62. package/dist/tests/unit/api/ja4Middleware.unit.test.d.ts +2 -0
  63. package/dist/tests/unit/api/ja4Middleware.unit.test.d.ts.map +1 -0
  64. package/dist/tests/unit/api/ja4Middleware.unit.test.js +57 -0
  65. package/dist/tests/unit/api/ja4Middleware.unit.test.js.map +1 -0
  66. package/dist/tests/unit/schedulers/captchaScheduler.unit.test.js +2 -2
  67. package/dist/tests/unit/schedulers/captchaScheduler.unit.test.js.map +1 -1
  68. package/dist/tests/unit/tasks/captchaManager.unit.test.js +1 -0
  69. package/dist/tests/unit/tasks/captchaManager.unit.test.js.map +1 -1
  70. package/dist/tests/unit/tasks/client/clientTasks.unit.test.js +11 -0
  71. package/dist/tests/unit/tasks/client/clientTasks.unit.test.js.map +1 -1
  72. package/dist/tests/unit/tasks/imgCaptcha/imgCaptchaTasks.unit.test.js +2 -2
  73. package/dist/tests/unit/tasks/imgCaptcha/imgCaptchaTasks.unit.test.js.map +1 -1
  74. package/package.json +16 -15
@@ -0,0 +1,281 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const node_crypto = require("node:crypto");
4
+ const common = require("@prosopo/common");
5
+ const database = require("@prosopo/database");
6
+ const types = require("@prosopo/types");
7
+ const util = require("@prosopo/util");
8
+ const validateAddress = require("../../api/validateAddress.cjs");
9
+ const isValidPrivateKey = (privateKeyString) => {
10
+ const privateKey = Buffer.from(privateKeyString, "base64").toString("ascii");
11
+ try {
12
+ node_crypto.createPrivateKey({
13
+ key: privateKey,
14
+ format: "pem",
15
+ type: "pkcs8"
16
+ });
17
+ return true;
18
+ } catch (error) {
19
+ return false;
20
+ }
21
+ };
22
+ class ClientTaskManager {
23
+ constructor(config, logger, db) {
24
+ this.config = config;
25
+ this.logger = logger;
26
+ this.providerDB = db;
27
+ }
28
+ /**
29
+ * @description Get the captcha database connection or create a new one
30
+ * @returns CaptchaDatabase
31
+ */
32
+ getCaptchaDB(mongoCaptchaUri) {
33
+ if (this.captchaDB) {
34
+ return this.captchaDB;
35
+ }
36
+ if (!this.captchaDB) {
37
+ this.captchaDB = new database.CaptchaDatabase(
38
+ mongoCaptchaUri,
39
+ void 0,
40
+ void 0,
41
+ this.logger
42
+ );
43
+ }
44
+ return this.captchaDB;
45
+ }
46
+ /**
47
+ * @description Store commitments externally in the database (Sends image captcha data to the big Mongo Cloud DB)
48
+ * @returns Promise<void>
49
+ */
50
+ async storeCommitmentsExternal() {
51
+ if (!this.config.mongoCaptchaUri) {
52
+ this.logger.info("Mongo env not set");
53
+ return;
54
+ }
55
+ const lastTask = await this.providerDB.getLastScheduledTaskStatus(
56
+ types.ScheduledTaskNames.StoreCommitmentsExternal,
57
+ types.ScheduledTaskStatus.Completed
58
+ );
59
+ const taskID = await this.providerDB.createScheduledTaskStatus(
60
+ types.ScheduledTaskNames.StoreCommitmentsExternal,
61
+ types.ScheduledTaskStatus.Running
62
+ );
63
+ try {
64
+ const BATCH_SIZE = 1e3;
65
+ const captchaDB = this.getCaptchaDB(this.config.mongoCaptchaUri);
66
+ let processedCommitments = 0;
67
+ await this.processBatchesWithCursor(
68
+ async (skip) => await this.providerDB.getUnstoredDappUserCommitments(
69
+ BATCH_SIZE,
70
+ skip
71
+ ),
72
+ async (batch) => {
73
+ const filteredBatch = lastTask?.updated ? batch.filter((commitment) => this.isRecordUpdated(commitment)) : batch;
74
+ if (filteredBatch.length > 0) {
75
+ await captchaDB.saveCaptchas([], filteredBatch, []);
76
+ await this.providerDB.markDappUserCommitmentsStored(
77
+ filteredBatch.map((commitment) => commitment.id)
78
+ );
79
+ }
80
+ processedCommitments += filteredBatch.length;
81
+ }
82
+ );
83
+ let processedPowRecords = 0;
84
+ await this.processBatchesWithCursor(
85
+ async (skip) => await this.providerDB.getUnstoredDappUserPoWCommitments(
86
+ BATCH_SIZE,
87
+ skip
88
+ ),
89
+ async (batch) => {
90
+ const filteredBatch = lastTask?.updated ? batch.filter((record) => this.isRecordUpdated(record)) : batch;
91
+ if (filteredBatch.length > 0) {
92
+ await captchaDB.saveCaptchas([], [], filteredBatch);
93
+ await this.providerDB.markDappUserPoWCommitmentsStored(
94
+ filteredBatch.map((record) => record.challenge)
95
+ );
96
+ }
97
+ processedPowRecords += filteredBatch.length;
98
+ }
99
+ );
100
+ let processedSessionRecords = 0;
101
+ await this.processBatchesWithCursor(
102
+ async (skip) => await this.providerDB.getUnstoredSessionRecords(BATCH_SIZE, skip),
103
+ async (batch) => {
104
+ const filteredBatch = lastTask?.updated ? batch.filter((record) => this.isRecordUpdated(record)) : batch;
105
+ const frictionlessTokenRecords = await this.providerDB.getFrictionlessTokenRecordsByTokenIds(
106
+ filteredBatch.map((record) => record.tokenId)
107
+ );
108
+ this.logger.info(
109
+ `Frictionless token records: ${frictionlessTokenRecords.length}`
110
+ );
111
+ const filteredBatchWithScores = filteredBatch.map((record) => {
112
+ const tokenRecord = frictionlessTokenRecords.find(
113
+ (tokenRecord2) => tokenRecord2._id?.toString() === record.tokenId.toString()
114
+ );
115
+ if (!tokenRecord) {
116
+ this.logger.error({
117
+ message: "No token record found",
118
+ context: { tokenId: record.tokenId }
119
+ });
120
+ return {
121
+ ...record,
122
+ score: 0,
123
+ scoreComponents: {
124
+ baseScore: 0
125
+ },
126
+ threshold: 0
127
+ };
128
+ }
129
+ return {
130
+ ...record,
131
+ score: tokenRecord?.score || 0,
132
+ scoreComponents: tokenRecord?.scoreComponents,
133
+ threshold: tokenRecord?.threshold || 0
134
+ };
135
+ });
136
+ if (filteredBatch.length > 0) {
137
+ await captchaDB.saveCaptchas(filteredBatchWithScores, [], []);
138
+ await this.providerDB.markSessionRecordsStored(
139
+ filteredBatch.map((record) => record.sessionId)
140
+ );
141
+ }
142
+ processedSessionRecords += filteredBatch.length;
143
+ }
144
+ );
145
+ await this.providerDB.updateScheduledTaskStatus(
146
+ taskID,
147
+ types.ScheduledTaskStatus.Completed,
148
+ {
149
+ data: {
150
+ processedSessionRecords,
151
+ processedCommitments,
152
+ processedPowRecords
153
+ }
154
+ }
155
+ );
156
+ this.captchaDB?.close();
157
+ } catch (e) {
158
+ this.logger.error(e);
159
+ this.captchaDB?.close();
160
+ await this.providerDB.updateScheduledTaskStatus(
161
+ taskID,
162
+ types.ScheduledTaskStatus.Failed,
163
+ { error: String(e) }
164
+ );
165
+ }
166
+ }
167
+ /**
168
+ * @description Get a list of client accounts and their settings from the client database
169
+ * @returns Promise<void>
170
+ */
171
+ async getClientList() {
172
+ if (!this.config.mongoClientUri) {
173
+ this.logger.info("Mongo env not set");
174
+ return;
175
+ }
176
+ const lastTask = await this.providerDB.getLastScheduledTaskStatus(
177
+ types.ScheduledTaskNames.GetClientList,
178
+ types.ScheduledTaskStatus.Completed
179
+ );
180
+ const taskID = await this.providerDB.createScheduledTaskStatus(
181
+ types.ScheduledTaskNames.GetClientList,
182
+ types.ScheduledTaskStatus.Running
183
+ );
184
+ try {
185
+ const clientDB = new database.ClientDatabase(
186
+ this.config.mongoClientUri,
187
+ void 0,
188
+ // expected to come from URI
189
+ void 0,
190
+ // expected to come from URI
191
+ this.logger
192
+ );
193
+ const tenMinuteWindow = 10 * 60 * 1e3;
194
+ const updatedAtTimestamp = lastTask?.updated ? lastTask.updated - tenMinuteWindow || 0 : 0;
195
+ this.logger.info({
196
+ message: `Getting updated client records since ${new Date(updatedAtTimestamp).toDateString()}`
197
+ });
198
+ const newClientRecords = await clientDB.getUpdatedClients(updatedAtTimestamp);
199
+ if (newClientRecords) {
200
+ await this.providerDB.updateClientRecords(newClientRecords);
201
+ }
202
+ await this.providerDB.updateScheduledTaskStatus(
203
+ taskID,
204
+ types.ScheduledTaskStatus.Completed,
205
+ {
206
+ data: {
207
+ clientRecords: newClientRecords.length
208
+ }
209
+ }
210
+ );
211
+ } catch (e) {
212
+ const getClientListError = new common.ProsopoApiError("DATABASE.UNKNOWN", {
213
+ context: { error: e },
214
+ logger: this.logger
215
+ });
216
+ this.logger.error(getClientListError, { context: { error: e } });
217
+ await this.providerDB.updateScheduledTaskStatus(
218
+ taskID,
219
+ types.ScheduledTaskStatus.Failed,
220
+ { error: String(e) }
221
+ );
222
+ }
223
+ }
224
+ async registerSiteKey(siteKey, tier, settings) {
225
+ validateAddress.validiateSiteKey(siteKey);
226
+ await this.providerDB.updateClientRecords([
227
+ {
228
+ account: siteKey,
229
+ tier,
230
+ settings
231
+ }
232
+ ]);
233
+ }
234
+ async updateDetectorKey(detectorKey) {
235
+ if (!isValidPrivateKey(detectorKey)) {
236
+ throw new common.ProsopoApiError("INVALID_DETECTOR_KEY", {
237
+ context: { detectorKey },
238
+ logger: this.logger
239
+ });
240
+ }
241
+ await this.providerDB.storeDetectorKey(detectorKey);
242
+ }
243
+ async removeDetectorKey(detectorKey) {
244
+ if (!isValidPrivateKey(detectorKey)) {
245
+ throw new common.ProsopoApiError("INVALID_DETECTOR_KEY", {
246
+ context: { detectorKey },
247
+ logger: this.logger
248
+ });
249
+ }
250
+ await this.providerDB.removeDetectorKey(detectorKey);
251
+ }
252
+ isSubdomainOrExactMatch(referrer, clientDomain) {
253
+ if (!referrer || !clientDomain) return false;
254
+ if (clientDomain === "*") return true;
255
+ try {
256
+ const referrerDomain = util.parseUrl(referrer).hostname.replace(/\.$/, "");
257
+ const allowedDomain = util.parseUrl(clientDomain).hostname.replace(/\.$/, "");
258
+ return referrerDomain === allowedDomain || referrerDomain.endsWith(`.${allowedDomain}`);
259
+ } catch {
260
+ this.logger.error({
261
+ message: "Error in isSubdomainOrExactMatch",
262
+ context: { referrer, clientDomain }
263
+ });
264
+ return false;
265
+ }
266
+ }
267
+ isRecordUpdated(record) {
268
+ const { lastUpdatedTimestamp, storedAtTimestamp } = record;
269
+ return !lastUpdatedTimestamp || !storedAtTimestamp || lastUpdatedTimestamp > storedAtTimestamp;
270
+ }
271
+ async processBatchesWithCursor(fetchBatch, processBatch) {
272
+ let skip = 0;
273
+ while (true) {
274
+ const batch = await fetchBatch(skip);
275
+ if (!batch.length) break;
276
+ await processBatch(batch);
277
+ skip += batch.length;
278
+ }
279
+ }
280
+ }
281
+ exports.ClientTaskManager = ClientTaskManager;
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const datasets = require("@prosopo/datasets");
4
+ const datasetTasksUtils = require("./datasetTasksUtils.cjs");
5
+ class DatasetManager {
6
+ constructor(config, logger, captchaConfig, db) {
7
+ this.config = config;
8
+ this.logger = logger;
9
+ this.captchaConfig = captchaConfig;
10
+ this.db = db;
11
+ }
12
+ /**
13
+ * @description Set the provider dataset from a file
14
+ *
15
+ * @param file JSON of the captcha dataset
16
+ */
17
+ async providerSetDatasetFromFile(file) {
18
+ const datasetRaw = datasets.parseCaptchaDataset(file);
19
+ return await this.providerSetDataset(datasetRaw);
20
+ }
21
+ async providerSetDataset(datasetRaw) {
22
+ const dataset = await datasetTasksUtils.providerValidateDataset(
23
+ datasetRaw,
24
+ this.captchaConfig.solved.count,
25
+ this.captchaConfig.unsolved.count
26
+ );
27
+ await this.db?.storeDataset(dataset);
28
+ }
29
+ }
30
+ exports.DatasetManager = DatasetManager;
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const common = require("@prosopo/common");
4
+ const datasets = require("@prosopo/datasets");
5
+ const providerValidateDataset = async (datasetRaw, minSolvedCaptchas, minUnsolvedCaptchas) => {
6
+ if (datasetRaw.captchas.length < minSolvedCaptchas + minUnsolvedCaptchas) {
7
+ throw new common.ProsopoEnvError("DATASET.CAPTCHAS_COUNT_LESS_THAN_CONFIGURED", {
8
+ context: { failedFuncName: providerValidateDataset.name }
9
+ });
10
+ }
11
+ const solutions = datasetRaw.captchas.map((captcha) => captcha.solution ? 1 : 0).reduce((partialSum, b) => partialSum + b, 0);
12
+ if (solutions < minSolvedCaptchas) {
13
+ throw new common.ProsopoEnvError("DATASET.SOLUTIONS_COUNT_LESS_THAN_CONFIGURED", {
14
+ context: { failedFuncName: providerValidateDataset.name }
15
+ });
16
+ }
17
+ if (solutions < minUnsolvedCaptchas) {
18
+ throw new common.ProsopoEnvError("DATASET.SOLUTIONS_COUNT_LESS_THAN_CONFIGURED", {
19
+ context: { failedFuncName: providerValidateDataset.name }
20
+ });
21
+ }
22
+ const dataset = await datasets.buildDataset(datasetRaw);
23
+ if (!dataset.datasetId || !dataset.datasetContentId) {
24
+ throw new common.ProsopoEnvError("DATASET.DATASET_ID_UNDEFINED", {
25
+ context: {
26
+ failedFuncName: providerValidateDataset.name,
27
+ datasetId: dataset.datasetId,
28
+ datasetContentId: dataset.datasetContentId
29
+ }
30
+ });
31
+ }
32
+ return dataset;
33
+ };
34
+ exports.providerValidateDataset = providerValidateDataset;