@prosopo/database 3.15.19 → 3.15.21
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/.turbo/turbo-build$colon$cjs.log +20 -17
- package/.turbo/turbo-build$colon$tsc.log +16 -16
- package/.turbo/turbo-build.log +21 -18
- package/CHANGELOG.md +24 -0
- package/dist/_virtual/_rolldown/runtime.js +3 -0
- package/dist/base/index.js +1 -4
- package/dist/base/mongo.js +133 -137
- package/dist/base/mongoMemory.js +21 -21
- package/dist/cjs/_virtual/_rolldown/runtime.cjs +23 -0
- package/dist/cjs/base/index.cjs +4 -6
- package/dist/cjs/base/mongo.cjs +140 -143
- package/dist/cjs/base/mongoMemory.cjs +22 -22
- package/dist/cjs/databases/captcha.cjs +220 -256
- package/dist/cjs/databases/centralDbStreamer.cjs +148 -170
- package/dist/cjs/databases/client.cjs +46 -62
- package/dist/cjs/databases/index.cjs +19 -19
- package/dist/cjs/databases/provider.cjs +1827 -2251
- package/dist/cjs/index.cjs +25 -23
- package/dist/cjs/mongooseOptions.cjs +59 -58
- package/dist/cjs/redisCache.cjs +432 -474
- package/dist/databases/captcha.d.ts.map +1 -1
- package/dist/databases/captcha.js +217 -253
- package/dist/databases/captcha.js.map +1 -1
- package/dist/databases/centralDbStreamer.js +147 -169
- package/dist/databases/client.js +44 -60
- package/dist/databases/index.js +10 -14
- package/dist/databases/provider.d.ts +4 -4
- package/dist/databases/provider.d.ts.map +1 -1
- package/dist/databases/provider.js +1823 -2247
- package/dist/databases/provider.js.map +1 -1
- package/dist/index.js +9 -18
- package/dist/mongooseOptions.js +59 -59
- package/dist/redisCache.js +432 -474
- package/dist/tests/integration/saveCaptchasSessionUpsert.integration.test.d.ts +2 -0
- package/dist/tests/integration/saveCaptchasSessionUpsert.integration.test.d.ts.map +1 -0
- package/dist/tests/integration/saveCaptchasSessionUpsert.integration.test.js +64 -0
- package/dist/tests/integration/saveCaptchasSessionUpsert.integration.test.js.map +1 -0
- package/package.json +14 -14
|
@@ -1,2253 +1,1829 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
modelName: "Detector",
|
|
84
|
-
schema: typesDatabase.DetectorRecordSchema
|
|
85
|
-
},
|
|
86
|
-
{
|
|
87
|
-
collectionName: "decisionMachine",
|
|
88
|
-
modelName: "DecisionMachine",
|
|
89
|
-
schema: typesDatabase.DecisionMachineArtifactRecordSchema
|
|
90
|
-
},
|
|
91
|
-
{
|
|
92
|
-
collectionName: "clientContextEntropy",
|
|
93
|
-
modelName: "ClientContextEntropy",
|
|
94
|
-
schema: typesDatabase.ClientContextEntropyRecordSchema
|
|
95
|
-
},
|
|
96
|
-
{
|
|
97
|
-
collectionName: "spamEmailDomain",
|
|
98
|
-
modelName: "SpamEmailDomain",
|
|
99
|
-
schema: typesDatabase.SpamEmailDomainRecordSchema
|
|
100
|
-
}
|
|
1
|
+
const require_mongo = require("../base/mongo.cjs");
|
|
2
|
+
let _prosopo_common = require("@prosopo/common");
|
|
3
|
+
let _prosopo_types = require("@prosopo/types");
|
|
4
|
+
let _prosopo_types_database = require("@prosopo/types-database");
|
|
5
|
+
let _polkadot_util_is = require("@polkadot/util/is");
|
|
6
|
+
let _prosopo_redis_client = require("@prosopo/redis-client");
|
|
7
|
+
let _prosopo_user_access_policy_redis = require("@prosopo/user-access-policy/redis");
|
|
8
|
+
let _prosopo_util = require("@prosopo/util");
|
|
9
|
+
//#region src/databases/provider.ts
|
|
10
|
+
var TWENTY_FOUR_HOURS_IN_MS = 1440 * 60 * 1e3;
|
|
11
|
+
var MAX_DOMAIN_SUFFIX_CANDIDATES = 5;
|
|
12
|
+
var PROVIDER_TABLES = [
|
|
13
|
+
{
|
|
14
|
+
collectionName: "captcha",
|
|
15
|
+
modelName: "Captcha",
|
|
16
|
+
schema: _prosopo_types_database.CaptchaRecordSchema
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
collectionName: "powcaptcha",
|
|
20
|
+
modelName: "PowCaptcha",
|
|
21
|
+
schema: _prosopo_types_database.PoWCaptchaRecordSchema
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
collectionName: "puzzlecaptcha",
|
|
25
|
+
modelName: "PuzzleCaptcha",
|
|
26
|
+
schema: _prosopo_types_database.PuzzleCaptchaRecordSchema
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
collectionName: "dataset",
|
|
30
|
+
modelName: "Dataset",
|
|
31
|
+
schema: _prosopo_types_database.DatasetRecordSchema
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
collectionName: "solution",
|
|
35
|
+
modelName: "Solution",
|
|
36
|
+
schema: _prosopo_types_database.SolutionRecordSchema
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
collectionName: "commitment",
|
|
40
|
+
modelName: "UserCommitment",
|
|
41
|
+
schema: _prosopo_types_database.UserCommitmentRecordSchema
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
collectionName: "usersolution",
|
|
45
|
+
modelName: "UserSolution",
|
|
46
|
+
schema: _prosopo_types_database.UserSolutionRecordSchema
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
collectionName: "scheduler",
|
|
50
|
+
modelName: "Scheduler",
|
|
51
|
+
schema: _prosopo_types_database.ScheduledTaskRecordSchema
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
collectionName: "client",
|
|
55
|
+
modelName: "Client",
|
|
56
|
+
schema: _prosopo_types_database.ClientRecordSchema
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
collectionName: "session",
|
|
60
|
+
modelName: "Session",
|
|
61
|
+
schema: _prosopo_types_database.SessionRecordSchema
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
collectionName: "detector",
|
|
65
|
+
modelName: "Detector",
|
|
66
|
+
schema: _prosopo_types_database.DetectorRecordSchema
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
collectionName: "decisionMachine",
|
|
70
|
+
modelName: "DecisionMachine",
|
|
71
|
+
schema: _prosopo_types_database.DecisionMachineArtifactRecordSchema
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
collectionName: "clientContextEntropy",
|
|
75
|
+
modelName: "ClientContextEntropy",
|
|
76
|
+
schema: _prosopo_types_database.ClientContextEntropyRecordSchema
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
collectionName: "spamEmailDomain",
|
|
80
|
+
modelName: "SpamEmailDomain",
|
|
81
|
+
schema: _prosopo_types_database.SpamEmailDomainRecordSchema
|
|
82
|
+
}
|
|
101
83
|
];
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
throw new common.ProsopoDBError("DATABASE.SOLUTION_FLAG_FAILED", {
|
|
1848
|
-
context: { error: err, captchaIds }
|
|
1849
|
-
});
|
|
1850
|
-
}
|
|
1851
|
-
}
|
|
1852
|
-
/**
|
|
1853
|
-
* @description Flag dapp users' commitments as used by calculated solution
|
|
1854
|
-
* @param {string[]} commitmentIds
|
|
1855
|
-
*/
|
|
1856
|
-
async flagProcessedDappUserCommitments(commitmentIds) {
|
|
1857
|
-
try {
|
|
1858
|
-
const distinctCommitmentIds = [...new Set(commitmentIds)];
|
|
1859
|
-
await this.tables?.commitment?.updateMany(
|
|
1860
|
-
{ id: { $in: distinctCommitmentIds } },
|
|
1861
|
-
{ $set: { processed: true } },
|
|
1862
|
-
{ upsert: false }
|
|
1863
|
-
).lean();
|
|
1864
|
-
} catch (err) {
|
|
1865
|
-
throw new common.ProsopoDBError("DATABASE.COMMITMENT_FLAG_FAILED", {
|
|
1866
|
-
context: { error: err, commitmentIds }
|
|
1867
|
-
});
|
|
1868
|
-
}
|
|
1869
|
-
}
|
|
1870
|
-
/**
|
|
1871
|
-
* @description Get a scheduled task status record by task ID and status
|
|
1872
|
-
*/
|
|
1873
|
-
async getScheduledTaskStatus(taskId, status) {
|
|
1874
|
-
const filter = {
|
|
1875
|
-
_id: taskId,
|
|
1876
|
-
status
|
|
1877
|
-
};
|
|
1878
|
-
const cursor = await this.tables?.scheduler?.findOne(filter).lean();
|
|
1879
|
-
return cursor ? cursor : void 0;
|
|
1880
|
-
}
|
|
1881
|
-
/**
|
|
1882
|
-
* @description Get the most recent scheduled task status record for a given task
|
|
1883
|
-
*/
|
|
1884
|
-
async getLastScheduledTaskStatus(task, status) {
|
|
1885
|
-
const filter = { processName: task };
|
|
1886
|
-
if (status) {
|
|
1887
|
-
filter.status = status;
|
|
1888
|
-
}
|
|
1889
|
-
const sort = {
|
|
1890
|
-
datetime: -1
|
|
1891
|
-
};
|
|
1892
|
-
const cursor = await this.tables?.scheduler?.findOne(filter).sort(sort).limit(1).lean();
|
|
1893
|
-
return cursor ? cursor : void 0;
|
|
1894
|
-
}
|
|
1895
|
-
/**
|
|
1896
|
-
* @description Create the status of a scheduled task
|
|
1897
|
-
*/
|
|
1898
|
-
async createScheduledTaskStatus(taskName, status) {
|
|
1899
|
-
const now = /* @__PURE__ */ new Date();
|
|
1900
|
-
const doc = typesDatabase.ScheduledTaskSchema.parse({
|
|
1901
|
-
processName: taskName,
|
|
1902
|
-
datetime: now,
|
|
1903
|
-
status
|
|
1904
|
-
});
|
|
1905
|
-
const taskRecord = await this.tables?.scheduler.create(doc);
|
|
1906
|
-
return taskRecord._id;
|
|
1907
|
-
}
|
|
1908
|
-
/**
|
|
1909
|
-
* @description Update the status of a scheduled task and an optional result
|
|
1910
|
-
*/
|
|
1911
|
-
async updateScheduledTaskStatus(taskId, status, result) {
|
|
1912
|
-
const update = {
|
|
1913
|
-
status,
|
|
1914
|
-
updated: /* @__PURE__ */ new Date(),
|
|
1915
|
-
...result && { result }
|
|
1916
|
-
};
|
|
1917
|
-
const filter = { _id: taskId };
|
|
1918
|
-
await this.tables?.scheduler.updateOne(
|
|
1919
|
-
filter,
|
|
1920
|
-
{ $set: update },
|
|
1921
|
-
{
|
|
1922
|
-
upsert: false
|
|
1923
|
-
}
|
|
1924
|
-
);
|
|
1925
|
-
}
|
|
1926
|
-
/**
|
|
1927
|
-
* @description Clean up the scheduled task status records
|
|
1928
|
-
*/
|
|
1929
|
-
async cleanupScheduledTaskStatus(status) {
|
|
1930
|
-
const filter = {
|
|
1931
|
-
status
|
|
1932
|
-
};
|
|
1933
|
-
await this.tables?.scheduler.deleteMany(filter);
|
|
1934
|
-
}
|
|
1935
|
-
/**
|
|
1936
|
-
* @description Update the client records
|
|
1937
|
-
*/
|
|
1938
|
-
async updateClientRecords(clientRecords) {
|
|
1939
|
-
const ops = clientRecords.map((record) => {
|
|
1940
|
-
const clientRecord = {
|
|
1941
|
-
account: record.account,
|
|
1942
|
-
settings: record.settings,
|
|
1943
|
-
tier: record.tier
|
|
1944
|
-
};
|
|
1945
|
-
const filter = {
|
|
1946
|
-
account: record.account
|
|
1947
|
-
};
|
|
1948
|
-
return {
|
|
1949
|
-
updateOne: {
|
|
1950
|
-
filter,
|
|
1951
|
-
update: {
|
|
1952
|
-
$set: clientRecord
|
|
1953
|
-
},
|
|
1954
|
-
upsert: true
|
|
1955
|
-
}
|
|
1956
|
-
};
|
|
1957
|
-
});
|
|
1958
|
-
if (!ops || ops.length === 0) {
|
|
1959
|
-
await this.logger.debug(() => ({ msg: "No client updates to apply" }));
|
|
1960
|
-
return;
|
|
1961
|
-
}
|
|
1962
|
-
await this.tables?.client.bulkWrite(ops);
|
|
1963
|
-
}
|
|
1964
|
-
/**
|
|
1965
|
-
* @description Remove client records by account
|
|
1966
|
-
*/
|
|
1967
|
-
async removeClientRecords(accounts) {
|
|
1968
|
-
if (!accounts || accounts.length === 0) {
|
|
1969
|
-
return;
|
|
1970
|
-
}
|
|
1971
|
-
await this.tables?.client.deleteMany({
|
|
1972
|
-
account: { $in: accounts }
|
|
1973
|
-
});
|
|
1974
|
-
}
|
|
1975
|
-
/**
|
|
1976
|
-
* @description Get all client records
|
|
1977
|
-
*/
|
|
1978
|
-
async getAllClientRecords() {
|
|
1979
|
-
const docs = await this.tables?.client.find().lean();
|
|
1980
|
-
return docs || [];
|
|
1981
|
-
}
|
|
1982
|
-
/**
|
|
1983
|
-
* @description Get a client record
|
|
1984
|
-
*/
|
|
1985
|
-
async getClientRecord(account) {
|
|
1986
|
-
const filter = { account };
|
|
1987
|
-
const doc = await this.tables?.client.findOne(filter, {
|
|
1988
|
-
account: 1,
|
|
1989
|
-
settings: 1,
|
|
1990
|
-
tier: 1
|
|
1991
|
-
}).lean();
|
|
1992
|
-
return doc ? doc : void 0;
|
|
1993
|
-
}
|
|
1994
|
-
/**
|
|
1995
|
-
* @description Store a detector key
|
|
1996
|
-
*/
|
|
1997
|
-
async storeDetectorKey(detectorKey) {
|
|
1998
|
-
return this.tables?.detector.create({
|
|
1999
|
-
detectorKey,
|
|
2000
|
-
createdAt: /* @__PURE__ */ new Date()
|
|
2001
|
-
});
|
|
2002
|
-
}
|
|
2003
|
-
/**
|
|
2004
|
-
* @description Remove a detector key
|
|
2005
|
-
* @param detectorKey The detector key to remove
|
|
2006
|
-
* @param expirationInSeconds Optional expiration time in seconds (default is 10 minutes)
|
|
2007
|
-
* */
|
|
2008
|
-
async removeDetectorKey(detectorKey, expirationInSeconds) {
|
|
2009
|
-
const filter = { detectorKey };
|
|
2010
|
-
const expiresAt = new Date(
|
|
2011
|
-
Date.now() + (expirationInSeconds || 10 * 60) * 1e3
|
|
2012
|
-
);
|
|
2013
|
-
await this.tables?.detector.updateOne(filter, {
|
|
2014
|
-
$set: { expiresAt }
|
|
2015
|
-
});
|
|
2016
|
-
}
|
|
2017
|
-
/**
|
|
2018
|
-
* @description Get valid detector keys
|
|
2019
|
-
*/
|
|
2020
|
-
async getDetectorKeys() {
|
|
2021
|
-
const keyRecords = await this.tables?.detector.find(
|
|
2022
|
-
{
|
|
2023
|
-
$or: [{ expiresAt: { $exists: false } }, { expiresAt: null }]
|
|
2024
|
-
},
|
|
2025
|
-
{ detectorKey: 1 }
|
|
2026
|
-
).sort({ createdAt: -1 }).lean();
|
|
2027
|
-
return (keyRecords || []).map((record) => record.detectorKey);
|
|
2028
|
-
}
|
|
2029
|
-
/**
|
|
2030
|
-
* Stores a decision machine artifact with a unique scope identifier.
|
|
2031
|
-
*
|
|
2032
|
-
* The combination of scope + dappAccount uniquely identifies a single artifact:
|
|
2033
|
-
* - Global scope: One artifact per provider (dappAccount is null)
|
|
2034
|
-
* - Dapp scope: One artifact per dapp account (dappAccount is specified)
|
|
2035
|
-
*
|
|
2036
|
-
* @param artifact - The decision machine artifact to store
|
|
2037
|
-
*/
|
|
2038
|
-
async upsertDecisionMachineArtifact(artifact) {
|
|
2039
|
-
const now = /* @__PURE__ */ new Date();
|
|
2040
|
-
const dappAccount = artifact.dappAccount ?? null;
|
|
2041
|
-
const kind = artifact.kind ?? types.DecisionMachineKind.Routing;
|
|
2042
|
-
const filter = {
|
|
2043
|
-
scope: artifact.scope,
|
|
2044
|
-
dappAccount,
|
|
2045
|
-
kind
|
|
2046
|
-
};
|
|
2047
|
-
await this.tables?.decisionMachine.updateOne(
|
|
2048
|
-
filter,
|
|
2049
|
-
{
|
|
2050
|
-
$set: {
|
|
2051
|
-
scope: artifact.scope,
|
|
2052
|
-
dappAccount,
|
|
2053
|
-
kind,
|
|
2054
|
-
runtime: artifact.runtime,
|
|
2055
|
-
language: artifact.language,
|
|
2056
|
-
source: artifact.source,
|
|
2057
|
-
name: artifact.name,
|
|
2058
|
-
version: artifact.version,
|
|
2059
|
-
captchaType: artifact.captchaType,
|
|
2060
|
-
updatedAt: now
|
|
2061
|
-
},
|
|
2062
|
-
$setOnInsert: {
|
|
2063
|
-
createdAt: now
|
|
2064
|
-
}
|
|
2065
|
-
},
|
|
2066
|
-
{ upsert: true }
|
|
2067
|
-
);
|
|
2068
|
-
}
|
|
2069
|
-
/**
|
|
2070
|
-
* Retrieves a single decision machine artifact by scope and optional dapp account.
|
|
2071
|
-
*
|
|
2072
|
-
* @param scope - The scope level (Global or Dapp)
|
|
2073
|
-
* @param dappAccount - Required for Dapp scope, unused for Global scope
|
|
2074
|
-
* @returns The matching artifact, or undefined if not found
|
|
2075
|
-
*/
|
|
2076
|
-
async getDecisionMachineArtifact(scope, dappAccount, kind) {
|
|
2077
|
-
const filter = {
|
|
2078
|
-
scope,
|
|
2079
|
-
dappAccount: dappAccount ?? null
|
|
2080
|
-
};
|
|
2081
|
-
if (kind !== void 0) {
|
|
2082
|
-
filter.kind = kind;
|
|
2083
|
-
}
|
|
2084
|
-
const doc = await this.tables?.decisionMachine.findOne(filter).lean();
|
|
2085
|
-
return doc ?? void 0;
|
|
2086
|
-
}
|
|
2087
|
-
/**
|
|
2088
|
-
* Retrieves all decision machine artifacts.
|
|
2089
|
-
*
|
|
2090
|
-
* @returns Array of all decision machine artifacts
|
|
2091
|
-
*/
|
|
2092
|
-
async getAllDecisionMachineArtifacts() {
|
|
2093
|
-
const docs = await this.tables?.decisionMachine.find({}).lean();
|
|
2094
|
-
return docs ?? [];
|
|
2095
|
-
}
|
|
2096
|
-
/**
|
|
2097
|
-
* Retrieves a single decision machine artifact by MongoDB ObjectId.
|
|
2098
|
-
*
|
|
2099
|
-
* @param id - The MongoDB ObjectId as a string
|
|
2100
|
-
* @returns The matching artifact, or undefined if not found
|
|
2101
|
-
*/
|
|
2102
|
-
async getDecisionMachineArtifactById(id) {
|
|
2103
|
-
const doc = await this.tables?.decisionMachine.findById(id).lean();
|
|
2104
|
-
return doc ?? void 0;
|
|
2105
|
-
}
|
|
2106
|
-
/**
|
|
2107
|
-
* Removes a decision machine artifact by MongoDB ObjectId.
|
|
2108
|
-
*
|
|
2109
|
-
* @param id - The MongoDB ObjectId as a string
|
|
2110
|
-
* @returns true if deleted, false if not found
|
|
2111
|
-
*/
|
|
2112
|
-
async removeDecisionMachineArtifact(id) {
|
|
2113
|
-
const result = await this.tables?.decisionMachine.deleteOne({
|
|
2114
|
-
_id: id
|
|
2115
|
-
});
|
|
2116
|
-
return (result?.deletedCount ?? 0) > 0;
|
|
2117
|
-
}
|
|
2118
|
-
/**
|
|
2119
|
-
* Removes all decision machine artifacts.
|
|
2120
|
-
*
|
|
2121
|
-
* @returns The number of artifacts deleted
|
|
2122
|
-
*/
|
|
2123
|
-
async removeAllDecisionMachineArtifacts() {
|
|
2124
|
-
const result = await this.tables?.decisionMachine.deleteMany({});
|
|
2125
|
-
return result?.deletedCount ?? 0;
|
|
2126
|
-
}
|
|
2127
|
-
/**
|
|
2128
|
-
* @description set client context-specific entropy
|
|
2129
|
-
*/
|
|
2130
|
-
async setClientContextEntropy(account, contextType, entropy) {
|
|
2131
|
-
const filter = { account, contextType };
|
|
2132
|
-
await this.tables?.clientContextEntropy.updateOne(
|
|
2133
|
-
filter,
|
|
2134
|
-
{ $set: { account, contextType, entropy } },
|
|
2135
|
-
{ upsert: true }
|
|
2136
|
-
);
|
|
2137
|
-
}
|
|
2138
|
-
/**
|
|
2139
|
-
* @description get client context-specific entropy
|
|
2140
|
-
*/
|
|
2141
|
-
async getClientContextEntropy(account, contextType) {
|
|
2142
|
-
const filter = { account, contextType };
|
|
2143
|
-
const doc = await this.tables?.clientContextEntropy.findOne(filter).lean();
|
|
2144
|
-
return doc ? doc.entropy : void 0;
|
|
2145
|
-
}
|
|
2146
|
-
/** Sample captcha records from the database for a specific context */
|
|
2147
|
-
async sampleContextEntropy(sampleSize, siteKey, contextType) {
|
|
2148
|
-
const size = sampleSize ? Math.abs(Math.trunc(sampleSize)) : 1;
|
|
2149
|
-
const max = 1e4;
|
|
2150
|
-
if (size > max) {
|
|
2151
|
-
throw new common.ProsopoDBError("DATABASE.CAPTCHA_SAMPLE_SIZE_EXCEEDED", {
|
|
2152
|
-
context: {
|
|
2153
|
-
failedFuncName: this.sampleContextEntropy.name,
|
|
2154
|
-
sampleSize
|
|
2155
|
-
}
|
|
2156
|
-
});
|
|
2157
|
-
}
|
|
2158
|
-
const pipeline = [
|
|
2159
|
-
{
|
|
2160
|
-
$match: {
|
|
2161
|
-
dappAccount: siteKey,
|
|
2162
|
-
requestedAtTimestamp: {
|
|
2163
|
-
$gt: new Date((/* @__PURE__ */ new Date()).getTime() - TWENTY_FOUR_HOURS_IN_MS)
|
|
2164
|
-
}
|
|
2165
|
-
}
|
|
2166
|
-
},
|
|
2167
|
-
{ $sample: { size: max } },
|
|
2168
|
-
{
|
|
2169
|
-
$lookup: {
|
|
2170
|
-
from: "sessions",
|
|
2171
|
-
localField: "sessionId",
|
|
2172
|
-
foreignField: "sessionId",
|
|
2173
|
-
as: "sessionData"
|
|
2174
|
-
}
|
|
2175
|
-
},
|
|
2176
|
-
{
|
|
2177
|
-
$unwind: {
|
|
2178
|
-
path: "$sessionData",
|
|
2179
|
-
preserveNullAndEmptyArrays: false
|
|
2180
|
-
}
|
|
2181
|
-
}
|
|
2182
|
-
];
|
|
2183
|
-
if (contextType === types.ContextType.Webview) {
|
|
2184
|
-
pipeline.push({
|
|
2185
|
-
$match: {
|
|
2186
|
-
"sessionData.webView": true
|
|
2187
|
-
}
|
|
2188
|
-
});
|
|
2189
|
-
} else if (contextType === types.ContextType.Default) {
|
|
2190
|
-
pipeline.push({
|
|
2191
|
-
$match: {
|
|
2192
|
-
"sessionData.webView": false
|
|
2193
|
-
}
|
|
2194
|
-
});
|
|
2195
|
-
}
|
|
2196
|
-
pipeline.push(
|
|
2197
|
-
{ $sample: { size } },
|
|
2198
|
-
{
|
|
2199
|
-
$project: {
|
|
2200
|
-
_id: 0,
|
|
2201
|
-
sessionId: 1
|
|
2202
|
-
}
|
|
2203
|
-
}
|
|
2204
|
-
);
|
|
2205
|
-
const cursor = this.tables?.powcaptcha.aggregate(pipeline);
|
|
2206
|
-
const docs = await cursor;
|
|
2207
|
-
if (docs?.length === 0) {
|
|
2208
|
-
return [];
|
|
2209
|
-
}
|
|
2210
|
-
return (await Promise.all(
|
|
2211
|
-
docs.map(async (doc) => {
|
|
2212
|
-
if (doc.sessionId) {
|
|
2213
|
-
const tokenRecord = await this.getSessionRecordBySessionId(
|
|
2214
|
-
doc.sessionId
|
|
2215
|
-
);
|
|
2216
|
-
return tokenRecord?.decryptedHeadHash;
|
|
2217
|
-
}
|
|
2218
|
-
return void 0;
|
|
2219
|
-
})
|
|
2220
|
-
)).filter((headHash) => headHash !== void 0);
|
|
2221
|
-
}
|
|
2222
|
-
async getSpamEmailDomain(domain) {
|
|
2223
|
-
if (!this.tables?.spamEmailDomain) {
|
|
2224
|
-
throw new common.ProsopoDBError("DATABASE.DATABASE_IMPORT_ERROR", {
|
|
2225
|
-
context: { failedFuncName: this.getSpamEmailDomain.name }
|
|
2226
|
-
});
|
|
2227
|
-
}
|
|
2228
|
-
const suffixCandidates = util.buildDomainSuffixCandidates(domain).slice(
|
|
2229
|
-
0,
|
|
2230
|
-
MAX_DOMAIN_SUFFIX_CANDIDATES
|
|
2231
|
-
);
|
|
2232
|
-
const query = suffixCandidates.length > 0 ? { domain: { $in: suffixCandidates } } : { domain };
|
|
2233
|
-
return await this.tables.spamEmailDomain.findOne(query).exec();
|
|
2234
|
-
}
|
|
2235
|
-
async bulkUpdateSpamEmailDomains(domains, upsert) {
|
|
2236
|
-
if (!this.tables?.spamEmailDomain) {
|
|
2237
|
-
throw new common.ProsopoDBError("DATABASE.DATABASE_IMPORT_ERROR", {
|
|
2238
|
-
context: { failedFuncName: this.bulkUpdateSpamEmailDomains.name }
|
|
2239
|
-
});
|
|
2240
|
-
}
|
|
2241
|
-
const bulkOps = domains.map((op) => ({
|
|
2242
|
-
updateOne: {
|
|
2243
|
-
filter: op.filter,
|
|
2244
|
-
update: { $set: op.update },
|
|
2245
|
-
upsert
|
|
2246
|
-
}
|
|
2247
|
-
}));
|
|
2248
|
-
if (bulkOps.length > 0) {
|
|
2249
|
-
await this.tables.spamEmailDomain.bulkWrite(bulkOps);
|
|
2250
|
-
}
|
|
2251
|
-
}
|
|
2252
|
-
}
|
|
84
|
+
var ProviderDatabase = class extends require_mongo.MongoDatabase {
|
|
85
|
+
constructor(options) {
|
|
86
|
+
super(options.mongo.url, options.mongo.dbname, options.mongo.authSource, options.logger);
|
|
87
|
+
this.options = options;
|
|
88
|
+
this.tables = {};
|
|
89
|
+
this.indexesEnsured = false;
|
|
90
|
+
this.tables = {};
|
|
91
|
+
this.redisAccessRulesConnection = null;
|
|
92
|
+
this.redisConnection = null;
|
|
93
|
+
this.userAccessRulesStorage = null;
|
|
94
|
+
}
|
|
95
|
+
setCentralDbStreamer(streamer) {
|
|
96
|
+
this.centralStreamer = streamer;
|
|
97
|
+
}
|
|
98
|
+
hasCentralDbStreamer() {
|
|
99
|
+
return this.centralStreamer !== void 0;
|
|
100
|
+
}
|
|
101
|
+
async connect() {
|
|
102
|
+
await super.connect();
|
|
103
|
+
this.loadTables();
|
|
104
|
+
await this.setupRedis();
|
|
105
|
+
}
|
|
106
|
+
async setupRedis() {
|
|
107
|
+
this.redisConnection = (0, _prosopo_redis_client.connectToRedis)({
|
|
108
|
+
url: this.options.redis?.url,
|
|
109
|
+
password: this.options.redis?.password,
|
|
110
|
+
logger: this.logger
|
|
111
|
+
});
|
|
112
|
+
this.redisAccessRulesConnection = (0, _prosopo_redis_client.setupRedisIndex)(this.redisConnection, {
|
|
113
|
+
..._prosopo_user_access_policy_redis.accessRulesRedisIndex,
|
|
114
|
+
name: this.options.redis?.indexName || _prosopo_user_access_policy_redis.accessRulesRedisIndex.name
|
|
115
|
+
}, this.logger);
|
|
116
|
+
this.userAccessRulesStorage = (0, _prosopo_user_access_policy_redis.createRedisAccessRulesStorage)(this.redisAccessRulesConnection, this.logger);
|
|
117
|
+
}
|
|
118
|
+
loadTables() {
|
|
119
|
+
const tables = {};
|
|
120
|
+
PROVIDER_TABLES.map(({ collectionName, modelName, schema }) => {
|
|
121
|
+
if (this.connection) tables[collectionName] = this.connection.model(modelName, schema);
|
|
122
|
+
});
|
|
123
|
+
this.tables = tables;
|
|
124
|
+
}
|
|
125
|
+
getTables() {
|
|
126
|
+
if (!this.tables) throw new _prosopo_common.ProsopoDBError("DATABASE.TABLES_UNDEFINED", {
|
|
127
|
+
context: { failedFuncName: this.getTables.name },
|
|
128
|
+
logger: this.logger
|
|
129
|
+
});
|
|
130
|
+
return this.tables;
|
|
131
|
+
}
|
|
132
|
+
getRedisAccessRulesConnection() {
|
|
133
|
+
if (null === this.redisAccessRulesConnection) throw new _prosopo_common.ProsopoDBError("DATABASE.REDIS_ACCESS_RULES_CONNECTION_UNDEFINED");
|
|
134
|
+
return this.redisAccessRulesConnection;
|
|
135
|
+
}
|
|
136
|
+
getRedisConnection() {
|
|
137
|
+
if (null === this.redisConnection) throw new _prosopo_common.ProsopoDBError("DATABASE.REDIS_ACCESS_RULES_CONNECTION_UNDEFINED");
|
|
138
|
+
return this.redisConnection;
|
|
139
|
+
}
|
|
140
|
+
async ensureIndexes() {
|
|
141
|
+
const indexPromises = [];
|
|
142
|
+
if (!this.indexesEnsured) PROVIDER_TABLES.map(({ collectionName }) => {
|
|
143
|
+
indexPromises.push(new Promise((resolve) => {
|
|
144
|
+
if (this.connected) this.tables[collectionName].collection.dropIndexes().then(() => {
|
|
145
|
+
this.tables[collectionName].ensureIndexes().then(() => {
|
|
146
|
+
this.logger.info(() => ({ msg: `Indexes ensured for collection ${collectionName}` }));
|
|
147
|
+
resolve();
|
|
148
|
+
}).catch((err) => {
|
|
149
|
+
this.logger.warn(() => ({
|
|
150
|
+
err,
|
|
151
|
+
msg: `Error creating indexes for collection ${collectionName}`
|
|
152
|
+
}));
|
|
153
|
+
resolve();
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
else {
|
|
157
|
+
this.logger.info(() => ({ msg: `Skipping index creation for collection ${collectionName} as not connected` }));
|
|
158
|
+
resolve();
|
|
159
|
+
}
|
|
160
|
+
}));
|
|
161
|
+
});
|
|
162
|
+
await Promise.all(indexPromises);
|
|
163
|
+
this.indexesEnsured = true;
|
|
164
|
+
}
|
|
165
|
+
getUserAccessRulesStorage() {
|
|
166
|
+
if (null === this.userAccessRulesStorage) throw new _prosopo_common.ProsopoDBError("DATABASE.USER_ACCESS_RULES_STORAGE_UNDEFINED");
|
|
167
|
+
return this.userAccessRulesStorage;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* @description Load a dataset to the database
|
|
171
|
+
* @param {Dataset} dataset
|
|
172
|
+
*/
|
|
173
|
+
async storeDataset(dataset) {
|
|
174
|
+
try {
|
|
175
|
+
this.logger.debug(() => ({
|
|
176
|
+
data: { datasetId: dataset.datasetId },
|
|
177
|
+
msg: "Storing dataset in database"
|
|
178
|
+
}));
|
|
179
|
+
const parsedDataset = _prosopo_types.DatasetWithIdsAndTreeSchema.parse(dataset);
|
|
180
|
+
const datasetDoc = {
|
|
181
|
+
datasetId: parsedDataset.datasetId,
|
|
182
|
+
datasetContentId: parsedDataset.datasetContentId,
|
|
183
|
+
format: parsedDataset.format,
|
|
184
|
+
contentTree: parsedDataset.contentTree,
|
|
185
|
+
solutionTree: parsedDataset.solutionTree
|
|
186
|
+
};
|
|
187
|
+
const filter = { datasetId: parsedDataset.datasetId };
|
|
188
|
+
await this.tables.dataset?.updateOne(filter, { $set: datasetDoc }, { upsert: true });
|
|
189
|
+
const captchaDocs = parsedDataset.captchas.map(({ solution, ...captcha }, index) => ({
|
|
190
|
+
...captcha,
|
|
191
|
+
datasetId: parsedDataset.datasetId,
|
|
192
|
+
datasetContentId: parsedDataset.datasetContentId,
|
|
193
|
+
index,
|
|
194
|
+
solved: !!solution?.length
|
|
195
|
+
}));
|
|
196
|
+
this.logger.debug(() => ({ msg: "Inserting captcha records" }));
|
|
197
|
+
if (captchaDocs.length) await this.tables?.captcha.bulkWrite(captchaDocs.map((captchaDoc) => ({ updateOne: {
|
|
198
|
+
filter: { captchaId: captchaDoc.captchaId },
|
|
199
|
+
update: {
|
|
200
|
+
$set: captchaDoc,
|
|
201
|
+
$setOnInsert: { randomKey: Math.random() }
|
|
202
|
+
},
|
|
203
|
+
upsert: true
|
|
204
|
+
} })));
|
|
205
|
+
const captchaSolutionDocs = parsedDataset.captchas.filter(({ solution }) => solution?.length).map((captcha) => ({
|
|
206
|
+
captchaId: captcha.captchaId,
|
|
207
|
+
captchaContentId: captcha.captchaContentId,
|
|
208
|
+
solution: captcha.solution,
|
|
209
|
+
salt: captcha.salt,
|
|
210
|
+
datasetId: parsedDataset.datasetId,
|
|
211
|
+
datasetContentId: parsedDataset.datasetContentId
|
|
212
|
+
}));
|
|
213
|
+
this.logger.debug(() => ({ msg: "Inserting solution records" }));
|
|
214
|
+
if (captchaSolutionDocs.length) await this.tables?.solution.bulkWrite(captchaSolutionDocs.map((captchaSolutionDoc) => ({ updateOne: {
|
|
215
|
+
filter: { captchaId: captchaSolutionDoc.captchaId },
|
|
216
|
+
update: { $set: captchaSolutionDoc },
|
|
217
|
+
upsert: true
|
|
218
|
+
} })));
|
|
219
|
+
this.logger.debug(() => ({ msg: "Dataset stored in database" }));
|
|
220
|
+
} catch (err) {
|
|
221
|
+
throw new _prosopo_common.ProsopoDBError("DATABASE.DATASET_LOAD_FAILED", {
|
|
222
|
+
context: {
|
|
223
|
+
failedFuncName: this.storeDataset.name,
|
|
224
|
+
error: err
|
|
225
|
+
},
|
|
226
|
+
logger: this.logger
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
/** @description Get solutions for a dataset
|
|
231
|
+
* @param {string} datasetId
|
|
232
|
+
*/
|
|
233
|
+
async getSolutions(datasetId) {
|
|
234
|
+
const filter = { datasetId };
|
|
235
|
+
const docs = await this.tables?.solution.find(filter).lean();
|
|
236
|
+
return docs ? docs : [];
|
|
237
|
+
}
|
|
238
|
+
/** @description Get a solution by captcha id
|
|
239
|
+
* @param {string} captchaId
|
|
240
|
+
*/
|
|
241
|
+
async getSolutionByCaptchaId(captchaId) {
|
|
242
|
+
const filter = { captchaId };
|
|
243
|
+
return await this.tables?.solution.findOne(filter).lean() || null;
|
|
244
|
+
}
|
|
245
|
+
/** @description Get a dataset from the database
|
|
246
|
+
* @param {string} datasetId
|
|
247
|
+
*/
|
|
248
|
+
async getDataset(datasetId) {
|
|
249
|
+
const filter = { datasetId };
|
|
250
|
+
const datasetDoc = await this.tables?.dataset.findOne(filter).lean();
|
|
251
|
+
if (datasetDoc) {
|
|
252
|
+
const { datasetContentId, format, contentTree, solutionTree } = datasetDoc;
|
|
253
|
+
const captchas = await this.tables?.captcha.find(filter).lean() || [];
|
|
254
|
+
const solutions = await this.tables?.solution.find(filter).lean() || [];
|
|
255
|
+
const solutionsKeyed = {};
|
|
256
|
+
for (const solution of solutions) solutionsKeyed[solution.captchaId] = solution;
|
|
257
|
+
return {
|
|
258
|
+
datasetId,
|
|
259
|
+
datasetContentId,
|
|
260
|
+
format,
|
|
261
|
+
contentTree: contentTree || [],
|
|
262
|
+
solutionTree: solutionTree || [],
|
|
263
|
+
captchas: captchas.map((captchaDoc) => {
|
|
264
|
+
const { captchaId, captchaContentId, items, target, salt, solved } = captchaDoc;
|
|
265
|
+
const solution = solutionsKeyed[captchaId];
|
|
266
|
+
return {
|
|
267
|
+
captchaId,
|
|
268
|
+
captchaContentId,
|
|
269
|
+
solved: !!solved,
|
|
270
|
+
salt,
|
|
271
|
+
items,
|
|
272
|
+
target,
|
|
273
|
+
solution: solved && solution ? solution.solution : []
|
|
274
|
+
};
|
|
275
|
+
})
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
throw new _prosopo_common.ProsopoDBError("DATABASE.DATASET_GET_FAILED", { context: {
|
|
279
|
+
failedFuncName: this.getDataset.name,
|
|
280
|
+
datasetId
|
|
281
|
+
} });
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* @description Get random captchas that are solved or not solved
|
|
285
|
+
* @param {boolean} solved `true` when captcha is solved
|
|
286
|
+
* @param {string} datasetId the id of the data set
|
|
287
|
+
* @param {number} size the number of records to be returned
|
|
288
|
+
*/
|
|
289
|
+
async getRandomCaptcha(solved, datasetId, size) {
|
|
290
|
+
if (!(0, _polkadot_util_is.isHex)(datasetId)) throw new _prosopo_common.ProsopoDBError("DATABASE.INVALID_HASH", { context: {
|
|
291
|
+
failedFuncName: this.getRandomCaptcha.name,
|
|
292
|
+
datasetId
|
|
293
|
+
} });
|
|
294
|
+
const sampleSize = size ? Math.abs(Math.trunc(size)) : 1;
|
|
295
|
+
const projection = {
|
|
296
|
+
_id: 0,
|
|
297
|
+
datasetId: 1,
|
|
298
|
+
datasetContentId: 1,
|
|
299
|
+
captchaId: 1,
|
|
300
|
+
captchaContentId: 1,
|
|
301
|
+
items: 1,
|
|
302
|
+
target: 1
|
|
303
|
+
};
|
|
304
|
+
const baseFilter = {
|
|
305
|
+
datasetId,
|
|
306
|
+
solved
|
|
307
|
+
};
|
|
308
|
+
const pivot = Math.random();
|
|
309
|
+
let docs = await this.tables?.captcha.find({
|
|
310
|
+
...baseFilter,
|
|
311
|
+
randomKey: { $gte: pivot }
|
|
312
|
+
}, projection).sort({ randomKey: 1 }).limit(sampleSize).lean() ?? [];
|
|
313
|
+
if (docs.length < sampleSize) {
|
|
314
|
+
const tail = await this.tables?.captcha.find({
|
|
315
|
+
...baseFilter,
|
|
316
|
+
randomKey: { $lt: pivot }
|
|
317
|
+
}, projection).sort({ randomKey: 1 }).limit(sampleSize - docs.length).lean() ?? [];
|
|
318
|
+
docs = docs.concat(tail);
|
|
319
|
+
}
|
|
320
|
+
if (docs.length === 0) {
|
|
321
|
+
const fallback = await this.tables?.captcha.aggregate([
|
|
322
|
+
{ $match: baseFilter },
|
|
323
|
+
{ $sample: { size: sampleSize } },
|
|
324
|
+
{ $project: projection }
|
|
325
|
+
]).exec() ?? [];
|
|
326
|
+
if (fallback.length > 0) return fallback.map(({ _id, ...keep }) => keep);
|
|
327
|
+
}
|
|
328
|
+
if (docs.length > 0) return docs;
|
|
329
|
+
throw new _prosopo_common.ProsopoDBError("DATABASE.CAPTCHA_GET_FAILED", { context: {
|
|
330
|
+
failedFuncName: this.getRandomCaptcha.name,
|
|
331
|
+
solved,
|
|
332
|
+
datasetId,
|
|
333
|
+
size
|
|
334
|
+
} });
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* @description Get captchas by id
|
|
338
|
+
* @param {string[]} captchaId
|
|
339
|
+
*/
|
|
340
|
+
async getCaptchaById(captchaId) {
|
|
341
|
+
const filter = { captchaId: { $in: captchaId } };
|
|
342
|
+
const docs = await this.tables?.captcha.find(filter, {
|
|
343
|
+
_id: 0,
|
|
344
|
+
captchaId: 1,
|
|
345
|
+
datasetId: 1,
|
|
346
|
+
items: 1
|
|
347
|
+
}).lean();
|
|
348
|
+
if (docs?.length) return docs.map(({ _id, ...keepAttrs }) => keepAttrs);
|
|
349
|
+
throw new _prosopo_common.ProsopoDBError("DATABASE.CAPTCHA_GET_FAILED", { context: {
|
|
350
|
+
failedFuncName: this.getCaptchaById.name,
|
|
351
|
+
captchaId
|
|
352
|
+
} });
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* @description Update a captcha
|
|
356
|
+
* @param {Captcha} captcha
|
|
357
|
+
* @param {string} datasetId the id of the data set
|
|
358
|
+
*/
|
|
359
|
+
async updateCaptcha(captcha, datasetId) {
|
|
360
|
+
if (!(0, _polkadot_util_is.isHex)(datasetId)) throw new _prosopo_common.ProsopoDBError("DATABASE.INVALID_HASH", { context: {
|
|
361
|
+
failedFuncName: this.updateCaptcha.name,
|
|
362
|
+
datasetId
|
|
363
|
+
} });
|
|
364
|
+
try {
|
|
365
|
+
const filter = { datasetId };
|
|
366
|
+
await this.tables?.captcha.updateOne(filter, { $set: captcha }, { upsert: false });
|
|
367
|
+
} catch (err) {
|
|
368
|
+
throw new _prosopo_common.ProsopoDBError("DATABASE.CAPTCHA_UPDATE_FAILED", { context: {
|
|
369
|
+
failedFuncName: this.getDatasetDetails.name,
|
|
370
|
+
error: err
|
|
371
|
+
} });
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* @description Remove captchas
|
|
376
|
+
*/
|
|
377
|
+
async removeCaptchas(captchaIds) {
|
|
378
|
+
const filter = { captchaId: { $in: captchaIds } };
|
|
379
|
+
await this.tables?.captcha.deleteMany(filter);
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* @description Get a dataset by Id
|
|
383
|
+
*/
|
|
384
|
+
/**
|
|
385
|
+
* Returns the most recently uploaded dataset's ID. Used as the per-provider
|
|
386
|
+
* default when a client doesn't pin a specific dataset (which it can't
|
|
387
|
+
* under DNS-based routing — clients don't know which provider they'll hit).
|
|
388
|
+
*/
|
|
389
|
+
async getMostRecentDatasetId() {
|
|
390
|
+
const datasetId = (await this.tables?.dataset.findOne().sort({ _id: -1 }).lean())?.datasetId;
|
|
391
|
+
return typeof datasetId === "string" ? datasetId : void 0;
|
|
392
|
+
}
|
|
393
|
+
async getDatasetDetails(datasetId) {
|
|
394
|
+
if (!(0, _polkadot_util_is.isHex)(datasetId)) throw new _prosopo_common.ProsopoDBError("DATABASE.INVALID_HASH", { context: {
|
|
395
|
+
failedFuncName: this.getDatasetDetails.name,
|
|
396
|
+
datasetId
|
|
397
|
+
} });
|
|
398
|
+
const filter = { datasetId };
|
|
399
|
+
const doc = await this.tables?.dataset.findOne(filter).lean();
|
|
400
|
+
if (doc) return doc;
|
|
401
|
+
throw new _prosopo_common.ProsopoDBError("DATABASE.DATASET_GET_FAILED", { context: {
|
|
402
|
+
failedFuncName: this.getDatasetDetails.name,
|
|
403
|
+
datasetId
|
|
404
|
+
} });
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* @description Store a Dapp User's captcha solution commitment
|
|
408
|
+
*/
|
|
409
|
+
async storeUserImageCaptchaSolution(captchas, commit) {
|
|
410
|
+
const commitmentRecord = _prosopo_types.UserCommitmentSchema.parse({
|
|
411
|
+
...commit,
|
|
412
|
+
lastUpdatedTimestamp: /* @__PURE__ */ new Date()
|
|
413
|
+
});
|
|
414
|
+
if (captchas.length) {
|
|
415
|
+
const filter = { id: commit.id };
|
|
416
|
+
await this.tables?.commitment.updateOne(filter, commitmentRecord, { upsert: true });
|
|
417
|
+
const ops = captchas.map((captcha) => ({ updateOne: {
|
|
418
|
+
filter: {
|
|
419
|
+
commitmentId: commit.id,
|
|
420
|
+
captchaId: captcha.captchaId
|
|
421
|
+
},
|
|
422
|
+
update: { $set: {
|
|
423
|
+
captchaId: captcha.captchaId,
|
|
424
|
+
captchaContentId: captcha.captchaContentId,
|
|
425
|
+
salt: captcha.salt,
|
|
426
|
+
solution: captcha.solution,
|
|
427
|
+
commitmentId: commit.id,
|
|
428
|
+
processed: false
|
|
429
|
+
} },
|
|
430
|
+
upsert: true
|
|
431
|
+
} }));
|
|
432
|
+
await this.tables?.usersolution.bulkWrite(ops);
|
|
433
|
+
this.centralStreamer?.streamImageRecord(commitmentRecord, (ts) => this.tables.commitment.updateOne({ id: commit.id }, { $set: { storedAtTimestamp: ts } }).then(() => {}));
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* @description Adds a new PoW Captcha record to the database.
|
|
438
|
+
* @param {string} challenge The challenge string for the captcha.
|
|
439
|
+
* @param components The components of the PoW challenge.
|
|
440
|
+
* @param difficulty
|
|
441
|
+
* @param providerSignature
|
|
442
|
+
* @param ipAddress
|
|
443
|
+
* @param headers
|
|
444
|
+
* @param ja4
|
|
445
|
+
* @param sessionId
|
|
446
|
+
* @param serverChecked
|
|
447
|
+
* @param userSubmitted
|
|
448
|
+
* @param storedStatus
|
|
449
|
+
* @param userSignature
|
|
450
|
+
* @param ipInfo full ipinfo payload from the ipInfoMiddleware
|
|
451
|
+
* @returns {Promise<void>} A promise that resolves when the record is added.
|
|
452
|
+
*/
|
|
453
|
+
async storePowCaptchaRecord(challenge, components, difficulty, providerSignature, ipAddress, headers, ja4, sessionId, serverChecked = false, userSubmitted = false, userSignature, ipInfo) {
|
|
454
|
+
const tables = this.getTables();
|
|
455
|
+
const powCaptchaRecord = {
|
|
456
|
+
challenge,
|
|
457
|
+
userAccount: components.userAccount,
|
|
458
|
+
dappAccount: components.dappAccount,
|
|
459
|
+
requestedAtTimestamp: new Date(components.requestedAtTimestamp),
|
|
460
|
+
ipAddress,
|
|
461
|
+
headers,
|
|
462
|
+
ja4,
|
|
463
|
+
result: { status: _prosopo_types.CaptchaStatus.pending },
|
|
464
|
+
userSubmitted,
|
|
465
|
+
serverChecked,
|
|
466
|
+
difficulty,
|
|
467
|
+
providerSignature,
|
|
468
|
+
userSignature,
|
|
469
|
+
lastUpdatedTimestamp: /* @__PURE__ */ new Date(),
|
|
470
|
+
pendingStage: true,
|
|
471
|
+
sessionId,
|
|
472
|
+
ipInfo
|
|
473
|
+
};
|
|
474
|
+
try {
|
|
475
|
+
await tables.powcaptcha.create(powCaptchaRecord);
|
|
476
|
+
this.logger.info(() => ({
|
|
477
|
+
data: {
|
|
478
|
+
challenge,
|
|
479
|
+
userSubmitted,
|
|
480
|
+
serverChecked,
|
|
481
|
+
countryCode: ipInfo?.isValid ? ipInfo.countryCode : void 0
|
|
482
|
+
},
|
|
483
|
+
msg: "PowCaptcha record added successfully"
|
|
484
|
+
}));
|
|
485
|
+
this.centralStreamer?.streamPowRecord(powCaptchaRecord, (ts) => this.tables.powcaptcha.updateOne({
|
|
486
|
+
challenge,
|
|
487
|
+
lastUpdatedTimestamp: { $lte: ts }
|
|
488
|
+
}, {
|
|
489
|
+
$set: { storedAtTimestamp: ts },
|
|
490
|
+
$unset: { pendingStage: 1 }
|
|
491
|
+
}).then(() => {}));
|
|
492
|
+
} catch (error) {
|
|
493
|
+
const err = new _prosopo_common.ProsopoDBError("DATABASE.CAPTCHA_UPDATE_FAILED", {
|
|
494
|
+
context: {
|
|
495
|
+
error,
|
|
496
|
+
challenge,
|
|
497
|
+
userSubmitted,
|
|
498
|
+
serverChecked,
|
|
499
|
+
ipInfo
|
|
500
|
+
},
|
|
501
|
+
logger: this.logger
|
|
502
|
+
});
|
|
503
|
+
this.logger.error(() => ({
|
|
504
|
+
err: error,
|
|
505
|
+
msg: "Failed to add PowCaptcha record"
|
|
506
|
+
}));
|
|
507
|
+
throw err;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* @description Retrieves a PoW Captcha record by its challenge string.
|
|
512
|
+
* @param {string} challenge The challenge string to search for.
|
|
513
|
+
* @returns {Promise<PoWCaptchaRecord | null>} A promise that resolves with the found record or null if not found.
|
|
514
|
+
*/
|
|
515
|
+
async getPowCaptchaRecordByChallenge(challenge) {
|
|
516
|
+
if (!this.tables) throw new _prosopo_common.ProsopoDBError("DATABASE.DATABASE_UNDEFINED", {
|
|
517
|
+
context: { failedFuncName: this.getPowCaptchaRecordByChallenge.name },
|
|
518
|
+
logger: this.logger
|
|
519
|
+
});
|
|
520
|
+
try {
|
|
521
|
+
const filter = { challenge };
|
|
522
|
+
const record = await this.tables.powcaptcha.findOne(filter, {
|
|
523
|
+
challenge: 1,
|
|
524
|
+
userAccount: 1,
|
|
525
|
+
dappAccount: 1,
|
|
526
|
+
requestedAtTimestamp: 1,
|
|
527
|
+
submittedAtTimestamp: 1,
|
|
528
|
+
verifiedAtTimestamp: 1,
|
|
529
|
+
failedAtTimestamp: 1,
|
|
530
|
+
ipAddress: 1,
|
|
531
|
+
headers: 1,
|
|
532
|
+
ja4: 1,
|
|
533
|
+
result: 1,
|
|
534
|
+
difficulty: 1,
|
|
535
|
+
sessionId: 1,
|
|
536
|
+
ipInfo: 1,
|
|
537
|
+
deviceCapability: 1,
|
|
538
|
+
behavioralDataPacked: 1,
|
|
539
|
+
serverChecked: 1,
|
|
540
|
+
userSubmitted: 1,
|
|
541
|
+
coords: 1
|
|
542
|
+
}).lean();
|
|
543
|
+
if (record) {
|
|
544
|
+
this.logger.info(() => ({
|
|
545
|
+
data: { challenge },
|
|
546
|
+
msg: "PowCaptcha record retrieved successfully"
|
|
547
|
+
}));
|
|
548
|
+
return record;
|
|
549
|
+
}
|
|
550
|
+
this.logger.info(() => ({
|
|
551
|
+
data: { challenge },
|
|
552
|
+
msg: "No PowCaptcha record found"
|
|
553
|
+
}));
|
|
554
|
+
return null;
|
|
555
|
+
} catch (error) {
|
|
556
|
+
const err = new _prosopo_common.ProsopoDBError("DATABASE.CAPTCHA_GET_FAILED", {
|
|
557
|
+
context: {
|
|
558
|
+
error,
|
|
559
|
+
challenge
|
|
560
|
+
},
|
|
561
|
+
logger: this.logger
|
|
562
|
+
});
|
|
563
|
+
this.logger.error(() => ({
|
|
564
|
+
err,
|
|
565
|
+
msg: "Failed to retrieve PowCaptcha record"
|
|
566
|
+
}));
|
|
567
|
+
throw err;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
/**
|
|
571
|
+
* @description Updates a PoW Captcha record in the database.
|
|
572
|
+
* @param {string} challenge The challenge string of the captcha to be updated.
|
|
573
|
+
* @param result
|
|
574
|
+
* @param serverChecked
|
|
575
|
+
* @param userSubmitted
|
|
576
|
+
* @param userSignature
|
|
577
|
+
* @returns {Promise<void>} A promise that resolves when the record is updated.
|
|
578
|
+
*/
|
|
579
|
+
async updatePowCaptchaRecordResult(challenge, result, serverChecked = false, userSubmitted = false, userSignature, coords) {
|
|
580
|
+
const tables = this.getTables();
|
|
581
|
+
const timestamp = /* @__PURE__ */ new Date();
|
|
582
|
+
const isDisapproved = result.status === _prosopo_types.CaptchaStatus.disapproved;
|
|
583
|
+
(0, _prosopo_util.assertCoordsSafe)(coords, "coords");
|
|
584
|
+
const setStage = {
|
|
585
|
+
result,
|
|
586
|
+
serverChecked,
|
|
587
|
+
userSubmitted,
|
|
588
|
+
userSignature,
|
|
589
|
+
lastUpdatedTimestamp: timestamp,
|
|
590
|
+
pendingStage: true,
|
|
591
|
+
...coords && { coords }
|
|
592
|
+
};
|
|
593
|
+
if (userSubmitted) setStage.submittedAtTimestamp = { $ifNull: ["$submittedAtTimestamp", timestamp] };
|
|
594
|
+
if (isDisapproved) setStage.failedAtTimestamp = { $ifNull: ["$failedAtTimestamp", timestamp] };
|
|
595
|
+
try {
|
|
596
|
+
if ((await tables.powcaptcha.updateOne({ challenge }, [{ $set: setStage }])).matchedCount === 0) {
|
|
597
|
+
const err = new _prosopo_common.ProsopoDBError("DATABASE.CAPTCHA_GET_FAILED", {
|
|
598
|
+
context: {
|
|
599
|
+
challenge,
|
|
600
|
+
...setStage
|
|
601
|
+
},
|
|
602
|
+
logger: this.logger
|
|
603
|
+
});
|
|
604
|
+
this.logger.info(() => ({
|
|
605
|
+
err,
|
|
606
|
+
msg: "No PowCaptcha record found to update"
|
|
607
|
+
}));
|
|
608
|
+
throw err;
|
|
609
|
+
}
|
|
610
|
+
this.logger.info(() => ({
|
|
611
|
+
data: {
|
|
612
|
+
challenge,
|
|
613
|
+
...setStage
|
|
614
|
+
},
|
|
615
|
+
msg: "PowCaptcha record updated successfully"
|
|
616
|
+
}));
|
|
617
|
+
this.centralStreamer?.streamPowUpdate(() => this.getPowCaptchaRecordByChallenge(challenge), (ts) => this.tables.powcaptcha.updateOne({
|
|
618
|
+
challenge,
|
|
619
|
+
lastUpdatedTimestamp: { $lte: ts }
|
|
620
|
+
}, {
|
|
621
|
+
$set: { storedAtTimestamp: ts },
|
|
622
|
+
$unset: { pendingStage: 1 }
|
|
623
|
+
}).then(() => {}));
|
|
624
|
+
} catch (error) {
|
|
625
|
+
const err = new _prosopo_common.ProsopoDBError("DATABASE.CAPTCHA_UPDATE_FAILED", {
|
|
626
|
+
context: {
|
|
627
|
+
error,
|
|
628
|
+
challenge,
|
|
629
|
+
...setStage
|
|
630
|
+
},
|
|
631
|
+
logger: this.logger
|
|
632
|
+
});
|
|
633
|
+
this.logger.error(() => ({
|
|
634
|
+
err,
|
|
635
|
+
msg: "Failed to update PowCaptcha record"
|
|
636
|
+
}));
|
|
637
|
+
throw err;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
async updatePowCaptchaRecord(challenge, updates) {
|
|
641
|
+
await this.getTables().powcaptcha.updateOne({ challenge }, { $set: {
|
|
642
|
+
...updates,
|
|
643
|
+
pendingStage: true
|
|
644
|
+
} }, { upsert: false });
|
|
645
|
+
this.centralStreamer?.streamPowUpdate(() => this.getPowCaptchaRecordByChallenge(challenge), (ts) => this.tables.powcaptcha.updateOne({
|
|
646
|
+
challenge,
|
|
647
|
+
lastUpdatedTimestamp: { $lte: ts }
|
|
648
|
+
}, {
|
|
649
|
+
$set: { storedAtTimestamp: ts },
|
|
650
|
+
$unset: { pendingStage: 1 }
|
|
651
|
+
}).then(() => {}));
|
|
652
|
+
}
|
|
653
|
+
async storePuzzleCaptchaRecord(challenge, components, targetX, targetY, originX, originY, tolerance, providerSignature, ipAddress, headers, ja4, sessionId, ipInfo) {
|
|
654
|
+
const tables = this.getTables();
|
|
655
|
+
const puzzleCaptchaRecord = {
|
|
656
|
+
challenge,
|
|
657
|
+
userAccount: components.userAccount,
|
|
658
|
+
dappAccount: components.dappAccount,
|
|
659
|
+
requestedAtTimestamp: new Date(components.requestedAtTimestamp),
|
|
660
|
+
ipAddress,
|
|
661
|
+
headers,
|
|
662
|
+
ja4,
|
|
663
|
+
result: { status: _prosopo_types.CaptchaStatus.pending },
|
|
664
|
+
userSubmitted: false,
|
|
665
|
+
serverChecked: false,
|
|
666
|
+
targetX,
|
|
667
|
+
targetY,
|
|
668
|
+
originX,
|
|
669
|
+
originY,
|
|
670
|
+
tolerance,
|
|
671
|
+
providerSignature,
|
|
672
|
+
lastUpdatedTimestamp: /* @__PURE__ */ new Date(),
|
|
673
|
+
pendingStage: true,
|
|
674
|
+
sessionId,
|
|
675
|
+
ipInfo
|
|
676
|
+
};
|
|
677
|
+
try {
|
|
678
|
+
await tables.puzzlecaptcha.create(puzzleCaptchaRecord);
|
|
679
|
+
this.logger.info(() => ({
|
|
680
|
+
data: {
|
|
681
|
+
challenge,
|
|
682
|
+
countryCode: ipInfo?.isValid ? ipInfo.countryCode : void 0
|
|
683
|
+
},
|
|
684
|
+
msg: "PuzzleCaptcha record added successfully"
|
|
685
|
+
}));
|
|
686
|
+
this.centralStreamer?.streamPuzzleRecord(puzzleCaptchaRecord, (ts) => this.tables.puzzlecaptcha.updateOne({
|
|
687
|
+
challenge,
|
|
688
|
+
lastUpdatedTimestamp: { $lte: ts }
|
|
689
|
+
}, {
|
|
690
|
+
$set: { storedAtTimestamp: ts },
|
|
691
|
+
$unset: { pendingStage: 1 }
|
|
692
|
+
}).then(() => {}));
|
|
693
|
+
} catch (error) {
|
|
694
|
+
const err = new _prosopo_common.ProsopoDBError("DATABASE.CAPTCHA_UPDATE_FAILED", {
|
|
695
|
+
context: {
|
|
696
|
+
error,
|
|
697
|
+
challenge,
|
|
698
|
+
ipInfo
|
|
699
|
+
},
|
|
700
|
+
logger: this.logger
|
|
701
|
+
});
|
|
702
|
+
this.logger.error(() => ({
|
|
703
|
+
err: error,
|
|
704
|
+
msg: "Failed to add PuzzleCaptcha record"
|
|
705
|
+
}));
|
|
706
|
+
throw err;
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
/**
|
|
710
|
+
* @description Retrieves a Puzzle Captcha record by its challenge string.
|
|
711
|
+
* @param {string} challenge The challenge string to search for.
|
|
712
|
+
* @returns {Promise<PuzzleCaptchaRecord | null>} A promise that resolves with the found record or null if not found.
|
|
713
|
+
*/
|
|
714
|
+
async getPuzzleCaptchaRecordByChallenge(challenge) {
|
|
715
|
+
if (!this.tables) throw new _prosopo_common.ProsopoDBError("DATABASE.DATABASE_UNDEFINED", {
|
|
716
|
+
context: { failedFuncName: this.getPuzzleCaptchaRecordByChallenge.name },
|
|
717
|
+
logger: this.logger
|
|
718
|
+
});
|
|
719
|
+
try {
|
|
720
|
+
const filter = { challenge };
|
|
721
|
+
const record = await this.tables.puzzlecaptcha.findOne(filter, {
|
|
722
|
+
challenge: 1,
|
|
723
|
+
userAccount: 1,
|
|
724
|
+
dappAccount: 1,
|
|
725
|
+
requestedAtTimestamp: 1,
|
|
726
|
+
submittedAtTimestamp: 1,
|
|
727
|
+
ipAddress: 1,
|
|
728
|
+
headers: 1,
|
|
729
|
+
ja4: 1,
|
|
730
|
+
result: 1,
|
|
731
|
+
targetX: 1,
|
|
732
|
+
targetY: 1,
|
|
733
|
+
originX: 1,
|
|
734
|
+
originY: 1,
|
|
735
|
+
tolerance: 1,
|
|
736
|
+
puzzleEvents: 1,
|
|
737
|
+
sessionId: 1,
|
|
738
|
+
ipInfo: 1,
|
|
739
|
+
deviceCapability: 1,
|
|
740
|
+
behavioralDataPacked: 1,
|
|
741
|
+
serverChecked: 1,
|
|
742
|
+
userSubmitted: 1,
|
|
743
|
+
coords: 1
|
|
744
|
+
}).lean();
|
|
745
|
+
if (record) {
|
|
746
|
+
this.logger.info(() => ({
|
|
747
|
+
data: { challenge },
|
|
748
|
+
msg: "PuzzleCaptcha record retrieved successfully"
|
|
749
|
+
}));
|
|
750
|
+
return record;
|
|
751
|
+
}
|
|
752
|
+
this.logger.info(() => ({
|
|
753
|
+
data: { challenge },
|
|
754
|
+
msg: "No PuzzleCaptcha record found"
|
|
755
|
+
}));
|
|
756
|
+
return null;
|
|
757
|
+
} catch (error) {
|
|
758
|
+
const err = new _prosopo_common.ProsopoDBError("DATABASE.CAPTCHA_GET_FAILED", {
|
|
759
|
+
context: {
|
|
760
|
+
error,
|
|
761
|
+
challenge
|
|
762
|
+
},
|
|
763
|
+
logger: this.logger
|
|
764
|
+
});
|
|
765
|
+
this.logger.error(() => ({
|
|
766
|
+
err,
|
|
767
|
+
msg: "Failed to retrieve PuzzleCaptcha record"
|
|
768
|
+
}));
|
|
769
|
+
throw err;
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
/**
|
|
773
|
+
* @description Updates a Puzzle Captcha record result in the database.
|
|
774
|
+
* @param {string} challenge The challenge string of the captcha to be updated.
|
|
775
|
+
* @param result
|
|
776
|
+
* @param serverChecked
|
|
777
|
+
* @param userSubmitted
|
|
778
|
+
* @param userSignature
|
|
779
|
+
* @param coords
|
|
780
|
+
* @param lastUpdatedTimestamp
|
|
781
|
+
* @returns {Promise<void>} A promise that resolves when the record is updated.
|
|
782
|
+
*/
|
|
783
|
+
async updatePuzzleCaptchaRecordResult(challenge, result, serverChecked = false, userSubmitted = false, userSignature, coords, lastUpdatedTimestamp) {
|
|
784
|
+
const tables = this.getTables();
|
|
785
|
+
const timestamp = lastUpdatedTimestamp ?? /* @__PURE__ */ new Date();
|
|
786
|
+
const isDisapproved = result.status === _prosopo_types.CaptchaStatus.disapproved;
|
|
787
|
+
(0, _prosopo_util.assertCoordsSafe)(coords, "coords");
|
|
788
|
+
const setStage = {
|
|
789
|
+
result,
|
|
790
|
+
serverChecked,
|
|
791
|
+
userSubmitted,
|
|
792
|
+
userSignature,
|
|
793
|
+
lastUpdatedTimestamp: timestamp,
|
|
794
|
+
pendingStage: true,
|
|
795
|
+
...coords && { coords },
|
|
796
|
+
...userSubmitted && { submittedAtTimestamp: timestamp },
|
|
797
|
+
...isDisapproved && { failedAtTimestamp: timestamp }
|
|
798
|
+
};
|
|
799
|
+
try {
|
|
800
|
+
if ((await tables.puzzlecaptcha.updateOne({ challenge }, { $set: setStage })).matchedCount === 0) {
|
|
801
|
+
const err = new _prosopo_common.ProsopoDBError("DATABASE.CAPTCHA_GET_FAILED", {
|
|
802
|
+
context: {
|
|
803
|
+
challenge,
|
|
804
|
+
...setStage
|
|
805
|
+
},
|
|
806
|
+
logger: this.logger
|
|
807
|
+
});
|
|
808
|
+
this.logger.info(() => ({
|
|
809
|
+
err,
|
|
810
|
+
msg: "No PuzzleCaptcha record found to update"
|
|
811
|
+
}));
|
|
812
|
+
throw err;
|
|
813
|
+
}
|
|
814
|
+
this.logger.info(() => ({
|
|
815
|
+
data: {
|
|
816
|
+
challenge,
|
|
817
|
+
...setStage
|
|
818
|
+
},
|
|
819
|
+
msg: "PuzzleCaptcha record updated successfully"
|
|
820
|
+
}));
|
|
821
|
+
this.centralStreamer?.streamPuzzleUpdate(() => this.getPuzzleCaptchaRecordByChallenge(challenge), (ts) => this.tables.puzzlecaptcha.updateOne({
|
|
822
|
+
challenge,
|
|
823
|
+
lastUpdatedTimestamp: { $lte: ts }
|
|
824
|
+
}, {
|
|
825
|
+
$set: { storedAtTimestamp: ts },
|
|
826
|
+
$unset: { pendingStage: 1 }
|
|
827
|
+
}).then(() => {}));
|
|
828
|
+
} catch (error) {
|
|
829
|
+
const err = new _prosopo_common.ProsopoDBError("DATABASE.CAPTCHA_UPDATE_FAILED", {
|
|
830
|
+
context: {
|
|
831
|
+
error,
|
|
832
|
+
challenge,
|
|
833
|
+
...setStage
|
|
834
|
+
},
|
|
835
|
+
logger: this.logger
|
|
836
|
+
});
|
|
837
|
+
this.logger.error(() => ({
|
|
838
|
+
err,
|
|
839
|
+
msg: "Failed to update PuzzleCaptcha record"
|
|
840
|
+
}));
|
|
841
|
+
throw err;
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
async updatePuzzleCaptchaRecord(challenge, updates) {
|
|
845
|
+
const tables = this.getTables();
|
|
846
|
+
const timestamp = /* @__PURE__ */ new Date();
|
|
847
|
+
const baseSet = {
|
|
848
|
+
...updates,
|
|
849
|
+
pendingStage: true,
|
|
850
|
+
...updates.serverChecked === true && { verifiedAtTimestamp: timestamp },
|
|
851
|
+
...updates.userSubmitted === true && { submittedAtTimestamp: timestamp },
|
|
852
|
+
...updates.result?.status === _prosopo_types.CaptchaStatus.disapproved && { failedAtTimestamp: timestamp }
|
|
853
|
+
};
|
|
854
|
+
await tables.puzzlecaptcha.updateOne({ challenge }, { $set: baseSet });
|
|
855
|
+
this.centralStreamer?.streamPuzzleUpdate(() => this.getPuzzleCaptchaRecordByChallenge(challenge), (ts) => this.tables.puzzlecaptcha.updateOne({
|
|
856
|
+
challenge,
|
|
857
|
+
lastUpdatedTimestamp: { $lte: ts }
|
|
858
|
+
}, {
|
|
859
|
+
$set: { storedAtTimestamp: ts },
|
|
860
|
+
$unset: { pendingStage: 1 }
|
|
861
|
+
}).then(() => {}));
|
|
862
|
+
}
|
|
863
|
+
/** @description Get serverChecked Dapp User image captcha commitments from the commitments table
|
|
864
|
+
*/
|
|
865
|
+
async getCheckedDappUserCommitments() {
|
|
866
|
+
const filter = { [_prosopo_types.StoredStatusNames.serverChecked]: true };
|
|
867
|
+
return await this.tables?.commitment.find(filter).lean() || [];
|
|
868
|
+
}
|
|
869
|
+
/** @description Get Dapp User captcha commitments from the commitments table that have not been counted towards the
|
|
870
|
+
* client's total.
|
|
871
|
+
*
|
|
872
|
+
* Served by the `pendingStage_partial` index. Records have
|
|
873
|
+
* `pendingStage: true` set on insert and on every mutation (see
|
|
874
|
+
* `updateDappUserCommitment`, `markDappUserCommitmentsChecked`,
|
|
875
|
+
* `approveDappUserCommitment`, `disapproveDappUserCommitment`,
|
|
876
|
+
* `storePendingImageCommitment`). `markDappUserCommitmentsStored` clears
|
|
877
|
+
* the flag after a successful stage, guarded by `lastUpdatedTimestamp`
|
|
878
|
+
* so an in-flight update isn't lost.
|
|
879
|
+
*/
|
|
880
|
+
async getUnstoredDappUserCommitments(limit = 1e3, skip = 0) {
|
|
881
|
+
return await this.tables?.commitment.find({ pendingStage: true }).sort({ _id: 1 }).skip(skip).limit(limit).lean() || [];
|
|
882
|
+
}
|
|
883
|
+
/** @description Mark a list of captcha commits as stored.
|
|
884
|
+
*
|
|
885
|
+
* `asOfTimestamp` defaults to "now" but the sweep should pass the time
|
|
886
|
+
* at which it fetched the batch. The lastUpdatedTimestamp guard prevents
|
|
887
|
+
* us from clearing pendingStage on a record that was mutated between
|
|
888
|
+
* fetch and mark-stored — those records will leave pendingStage set so
|
|
889
|
+
* the next sweep picks them up.
|
|
890
|
+
*/
|
|
891
|
+
async markDappUserCommitmentsStored(commitmentIds, asOfTimestamp = /* @__PURE__ */ new Date()) {
|
|
892
|
+
await this.tables?.commitment.updateMany({
|
|
893
|
+
id: { $in: commitmentIds },
|
|
894
|
+
lastUpdatedTimestamp: { $lte: asOfTimestamp }
|
|
895
|
+
}, {
|
|
896
|
+
$set: { storedAtTimestamp: /* @__PURE__ */ new Date() },
|
|
897
|
+
$unset: { pendingStage: 1 }
|
|
898
|
+
}, { upsert: false });
|
|
899
|
+
}
|
|
900
|
+
/** @description Mark a list of captcha commits as checked
|
|
901
|
+
*/
|
|
902
|
+
async markDappUserCommitmentsChecked(commitmentIds) {
|
|
903
|
+
const timestamp = /* @__PURE__ */ new Date();
|
|
904
|
+
await this.tables?.commitment.updateMany({ id: { $in: commitmentIds } }, [{ $set: {
|
|
905
|
+
serverChecked: true,
|
|
906
|
+
lastUpdatedTimestamp: timestamp,
|
|
907
|
+
pendingStage: true,
|
|
908
|
+
verifiedAtTimestamp: { $ifNull: ["$verifiedAtTimestamp", timestamp] }
|
|
909
|
+
} }]);
|
|
910
|
+
}
|
|
911
|
+
/** @description Update an image captcha commitment
|
|
912
|
+
*/
|
|
913
|
+
async updateDappUserCommitment(commitmentId, updates) {
|
|
914
|
+
const filter = { id: commitmentId };
|
|
915
|
+
const timestamp = /* @__PURE__ */ new Date();
|
|
916
|
+
const baseSet = {
|
|
917
|
+
...updates,
|
|
918
|
+
lastUpdatedAtTimestamp: timestamp,
|
|
919
|
+
pendingStage: true
|
|
920
|
+
};
|
|
921
|
+
const pipelineExprs = {};
|
|
922
|
+
if (updates.userSubmitted === true) pipelineExprs.submittedAtTimestamp = { $ifNull: ["$submittedAtTimestamp", timestamp] };
|
|
923
|
+
if (updates.serverChecked === true) pipelineExprs.verifiedAtTimestamp = { $ifNull: ["$verifiedAtTimestamp", timestamp] };
|
|
924
|
+
if (updates.result?.status === _prosopo_types.CaptchaStatus.disapproved) pipelineExprs.failedAtTimestamp = { $ifNull: ["$failedAtTimestamp", timestamp] };
|
|
925
|
+
if (Object.keys(pipelineExprs).length === 0) {
|
|
926
|
+
await this.tables?.commitment.updateOne(filter, { $set: baseSet });
|
|
927
|
+
return;
|
|
928
|
+
}
|
|
929
|
+
await this.tables?.commitment.updateOne(filter, [{ $set: {
|
|
930
|
+
...baseSet,
|
|
931
|
+
...pipelineExprs
|
|
932
|
+
} }]);
|
|
933
|
+
}
|
|
934
|
+
/**
|
|
935
|
+
* @description Get Dapp User PoW captcha commitments that have not been counted towards the client's total
|
|
936
|
+
* @param {number} limit Maximum number of records to return
|
|
937
|
+
* @param {number} skip Number of records to skip (for pagination)
|
|
938
|
+
* @returns {Promise<PoWCaptchaRecord[]>} Array of PoW captcha records
|
|
939
|
+
*/
|
|
940
|
+
async getUnstoredDappUserPoWCommitments(limit = 1e3, skip = 0) {
|
|
941
|
+
return await this.tables?.powcaptcha.find({ pendingStage: true }).sort({ _id: 1 }).skip(skip).limit(limit).lean() || [];
|
|
942
|
+
}
|
|
943
|
+
/** @description Mark a list of PoW captcha commits as stored.
|
|
944
|
+
*
|
|
945
|
+
* `asOfTimestamp` defaults to "now" but the sweep should pass the time
|
|
946
|
+
* at which it fetched the batch. See markDappUserCommitmentsStored for
|
|
947
|
+
* the guard rationale.
|
|
948
|
+
*/
|
|
949
|
+
async markDappUserPoWCommitmentsStored(challenges, asOfTimestamp = /* @__PURE__ */ new Date()) {
|
|
950
|
+
await this.tables?.powcaptcha.updateMany({
|
|
951
|
+
challenge: { $in: challenges },
|
|
952
|
+
lastUpdatedTimestamp: { $lte: asOfTimestamp }
|
|
953
|
+
}, {
|
|
954
|
+
$set: { storedAtTimestamp: /* @__PURE__ */ new Date() },
|
|
955
|
+
$unset: { pendingStage: 1 }
|
|
956
|
+
}, { upsert: false });
|
|
957
|
+
}
|
|
958
|
+
/** @description Mark a list of PoW captcha commits as checked by the server
|
|
959
|
+
*/
|
|
960
|
+
async markDappUserPoWCommitmentsChecked(challenges) {
|
|
961
|
+
const timestamp = /* @__PURE__ */ new Date();
|
|
962
|
+
await this.tables?.powcaptcha.updateMany({ challenge: { $in: challenges } }, [{ $set: {
|
|
963
|
+
serverChecked: true,
|
|
964
|
+
lastUpdatedTimestamp: timestamp,
|
|
965
|
+
pendingStage: true,
|
|
966
|
+
verifiedAtTimestamp: { $ifNull: ["$verifiedAtTimestamp", timestamp] }
|
|
967
|
+
} }], { upsert: false });
|
|
968
|
+
}
|
|
969
|
+
/**
|
|
970
|
+
* Store a new session record
|
|
971
|
+
*/
|
|
972
|
+
async storeSessionRecord(sessionRecord) {
|
|
973
|
+
try {
|
|
974
|
+
this.logger.debug(() => ({ data: {
|
|
975
|
+
action: "storing",
|
|
976
|
+
sessionRecord
|
|
977
|
+
} }));
|
|
978
|
+
const recordWithFlag = {
|
|
979
|
+
...sessionRecord,
|
|
980
|
+
lastUpdatedTimestamp: sessionRecord.lastUpdatedTimestamp ?? sessionRecord.createdAt,
|
|
981
|
+
pendingStage: true
|
|
982
|
+
};
|
|
983
|
+
await this.tables.session.create(recordWithFlag);
|
|
984
|
+
this.centralStreamer?.streamSessionRecord(recordWithFlag, (ts) => this.tables.session.updateOne({
|
|
985
|
+
sessionId: sessionRecord.sessionId,
|
|
986
|
+
lastUpdatedTimestamp: { $lte: ts }
|
|
987
|
+
}, {
|
|
988
|
+
$set: { storedAtTimestamp: ts },
|
|
989
|
+
$unset: { pendingStage: 1 }
|
|
990
|
+
}).then(() => {}));
|
|
991
|
+
} catch (err) {
|
|
992
|
+
throw new _prosopo_common.ProsopoDBError("DATABASE.SESSION_STORE_FAILED", {
|
|
993
|
+
context: {
|
|
994
|
+
error: err,
|
|
995
|
+
sessionId: sessionRecord.sessionId
|
|
996
|
+
},
|
|
997
|
+
logger: this.logger
|
|
998
|
+
});
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
/**
|
|
1002
|
+
* Persist a synthetic Session record for a request that was 401'd at the
|
|
1003
|
+
* request-time block middleware. The record is written with
|
|
1004
|
+
* `blocked=true` + `deleted=true` so the captcha flow can't pick it up
|
|
1005
|
+
* downstream, and with sentinel values for the schema-required fields
|
|
1006
|
+
* the blocked request never had a chance to populate (score / threshold /
|
|
1007
|
+
* captchaType / etc.).
|
|
1008
|
+
*
|
|
1009
|
+
* Designed for fire-and-forget: callers `void`-cast the promise and any
|
|
1010
|
+
* Mongo error is swallowed-and-logged so the 401 response is never
|
|
1011
|
+
* delayed by a persistence failure. The structured log line at the call
|
|
1012
|
+
* site is the source of truth either way.
|
|
1013
|
+
*
|
|
1014
|
+
* Mirrors `storeSessionRecord` enough that the Traffic-page
|
|
1015
|
+
* aggregations can read these records out of the central-streamer
|
|
1016
|
+
* pipeline once the UI work lands.
|
|
1017
|
+
*/
|
|
1018
|
+
async storeBlockedSession(record) {
|
|
1019
|
+
try {
|
|
1020
|
+
const stored = {
|
|
1021
|
+
...record,
|
|
1022
|
+
blocked: true,
|
|
1023
|
+
deleted: true,
|
|
1024
|
+
storedAtTimestamp: record.storedAtTimestamp ?? record.createdAt,
|
|
1025
|
+
lastUpdatedTimestamp: record.lastUpdatedTimestamp ?? record.createdAt
|
|
1026
|
+
};
|
|
1027
|
+
await this.tables.session.create(stored);
|
|
1028
|
+
this.centralStreamer?.streamSessionRecord(stored, (ts) => this.tables.session.updateOne({
|
|
1029
|
+
sessionId: stored.sessionId,
|
|
1030
|
+
lastUpdatedTimestamp: { $lte: ts }
|
|
1031
|
+
}, { $set: { storedAtTimestamp: ts } }).then(() => {}));
|
|
1032
|
+
} catch (err) {
|
|
1033
|
+
this.logger.warn(() => ({
|
|
1034
|
+
err,
|
|
1035
|
+
msg: "Failed to persist blocked session record",
|
|
1036
|
+
data: {
|
|
1037
|
+
sessionId: record.sessionId,
|
|
1038
|
+
siteKey: record.siteKey
|
|
1039
|
+
}
|
|
1040
|
+
}));
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
/**
|
|
1044
|
+
* Get a session record by sessionId
|
|
1045
|
+
*/
|
|
1046
|
+
async getSessionRecordBySessionId(sessionId) {
|
|
1047
|
+
const filter = { sessionId };
|
|
1048
|
+
return await this.tables.session.findOne(filter, {
|
|
1049
|
+
sessionId: 1,
|
|
1050
|
+
token: 1,
|
|
1051
|
+
score: 1,
|
|
1052
|
+
threshold: 1,
|
|
1053
|
+
scoreComponents: 1,
|
|
1054
|
+
ipAddress: 1,
|
|
1055
|
+
ipInfo: 1,
|
|
1056
|
+
webView: 1,
|
|
1057
|
+
iFrame: 1,
|
|
1058
|
+
isEscalation: 1,
|
|
1059
|
+
decryptedHeadHash: 1,
|
|
1060
|
+
siteKey: 1,
|
|
1061
|
+
reason: 1,
|
|
1062
|
+
mode: 1,
|
|
1063
|
+
solvedImagesCount: 1,
|
|
1064
|
+
userSitekeyIpHash: 1,
|
|
1065
|
+
simdReadings: 1,
|
|
1066
|
+
dnsEvent: 1,
|
|
1067
|
+
currentUrl: 1,
|
|
1068
|
+
iframeUrl: 1,
|
|
1069
|
+
captchaType: 1,
|
|
1070
|
+
"headers.user-agent": 1,
|
|
1071
|
+
"headers.accept": 1,
|
|
1072
|
+
"headers.accept-language": 1,
|
|
1073
|
+
"headers.accept-encoding": 1,
|
|
1074
|
+
"headers.sec-ch-ua": 1,
|
|
1075
|
+
"headers.sec-ch-ua-mobile": 1,
|
|
1076
|
+
"headers.sec-ch-ua-platform": 1,
|
|
1077
|
+
"headers.sec-ch-ua-platform-version": 1,
|
|
1078
|
+
"headers.sec-fetch-dest": 1,
|
|
1079
|
+
"headers.sec-fetch-mode": 1,
|
|
1080
|
+
"headers.sec-fetch-site": 1,
|
|
1081
|
+
"headers.sec-fetch-user": 1,
|
|
1082
|
+
"headers.referer": 1,
|
|
1083
|
+
"headers.origin": 1,
|
|
1084
|
+
"headers.prosopo-user": 1,
|
|
1085
|
+
"headers.prosopo-site-key": 1,
|
|
1086
|
+
"headers.prosopo-type": 1,
|
|
1087
|
+
"headers.x-tls-version": 1
|
|
1088
|
+
}).lean() || void 0;
|
|
1089
|
+
}
|
|
1090
|
+
/**
|
|
1091
|
+
* Get a session record by token
|
|
1092
|
+
* Used to ensure that a token is not used more than once.
|
|
1093
|
+
*/
|
|
1094
|
+
async getSessionRecordByToken(token) {
|
|
1095
|
+
const filter = { token };
|
|
1096
|
+
return await this.tables.session.findOne(filter).lean() || void 0;
|
|
1097
|
+
}
|
|
1098
|
+
/**
|
|
1099
|
+
* Check if a session exists and mark it as removed
|
|
1100
|
+
* @returns The session record if it existed, undefined otherwise
|
|
1101
|
+
*/
|
|
1102
|
+
async checkAndRemoveSession(sessionId) {
|
|
1103
|
+
this.logger.debug(() => ({ data: {
|
|
1104
|
+
action: "checking and removing",
|
|
1105
|
+
sessionId
|
|
1106
|
+
} }));
|
|
1107
|
+
const filter = {
|
|
1108
|
+
sessionId,
|
|
1109
|
+
deleted: { $exists: false }
|
|
1110
|
+
};
|
|
1111
|
+
try {
|
|
1112
|
+
return await this.tables.session.findOneAndUpdate(filter, {
|
|
1113
|
+
deleted: true,
|
|
1114
|
+
lastUpdatedTimestamp: /* @__PURE__ */ new Date(),
|
|
1115
|
+
pendingStage: true
|
|
1116
|
+
}).lean() || void 0;
|
|
1117
|
+
} catch (err) {
|
|
1118
|
+
throw new _prosopo_common.ProsopoDBError("DATABASE.SESSION_CHECK_REMOVE_FAILED", {
|
|
1119
|
+
context: {
|
|
1120
|
+
error: err,
|
|
1121
|
+
sessionId
|
|
1122
|
+
},
|
|
1123
|
+
logger: this.logger
|
|
1124
|
+
});
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
/**
|
|
1128
|
+
* Update a session record by sessionId. Pure Mongo write — callers in
|
|
1129
|
+
* the provider package are responsible for refreshing the Redis cache
|
|
1130
|
+
* via `RedisWriteQueue.patchCachedSession`, matching the existing
|
|
1131
|
+
* caller-side caching pattern (see `frictionlessTasks.createSession`).
|
|
1132
|
+
*/
|
|
1133
|
+
async updateSessionRecord(sessionId, updates, streamToCentral) {
|
|
1134
|
+
try {
|
|
1135
|
+
await this.tables.session.updateOne({ sessionId }, { $set: {
|
|
1136
|
+
...updates,
|
|
1137
|
+
lastUpdatedTimestamp: /* @__PURE__ */ new Date(),
|
|
1138
|
+
pendingStage: true
|
|
1139
|
+
} });
|
|
1140
|
+
if (streamToCentral && this.centralStreamer) {
|
|
1141
|
+
const streamer = this.centralStreamer;
|
|
1142
|
+
const markStored = (ts) => this.tables.session.updateOne({
|
|
1143
|
+
sessionId,
|
|
1144
|
+
lastUpdatedTimestamp: { $lte: ts }
|
|
1145
|
+
}, {
|
|
1146
|
+
$set: { storedAtTimestamp: ts },
|
|
1147
|
+
$unset: { pendingStage: 1 }
|
|
1148
|
+
}).then(() => {});
|
|
1149
|
+
this.tables.session.findOne({ sessionId }).lean().then((record) => {
|
|
1150
|
+
if (record) streamer.streamSessionRecord(record, markStored);
|
|
1151
|
+
}).catch(() => {});
|
|
1152
|
+
}
|
|
1153
|
+
} catch (err) {
|
|
1154
|
+
throw new _prosopo_common.ProsopoDBError("DATABASE.SESSION_GET_FAILED", {
|
|
1155
|
+
context: {
|
|
1156
|
+
error: err,
|
|
1157
|
+
sessionId
|
|
1158
|
+
},
|
|
1159
|
+
logger: this.logger
|
|
1160
|
+
});
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
/**
|
|
1164
|
+
* Atomically record SIMD CPU fingerprint readings on the session — only
|
|
1165
|
+
* if they aren't already present. Uses a Mongo aggregation-pipeline
|
|
1166
|
+
* update so `simdReadings` and `simdReadingsStage` are set together,
|
|
1167
|
+
* first-hop-wins. Pure Mongo write — cache refresh is the caller's
|
|
1168
|
+
* responsibility via `RedisWriteQueue.patchCachedSimdReadingsIfAbsent`.
|
|
1169
|
+
*/
|
|
1170
|
+
async recordSessionSimdReadingsIfAbsent(sessionId, readings, stage) {
|
|
1171
|
+
try {
|
|
1172
|
+
await this.tables.session.updateOne({ sessionId }, [{ $set: {
|
|
1173
|
+
simdReadings: { $ifNull: ["$simdReadings", readings] },
|
|
1174
|
+
simdReadingsStage: { $ifNull: ["$simdReadingsStage", stage] },
|
|
1175
|
+
lastUpdatedTimestamp: /* @__PURE__ */ new Date(),
|
|
1176
|
+
pendingStage: true
|
|
1177
|
+
} }]);
|
|
1178
|
+
} catch (err) {
|
|
1179
|
+
throw new _prosopo_common.ProsopoDBError("DATABASE.SESSION_GET_FAILED", {
|
|
1180
|
+
context: {
|
|
1181
|
+
error: err,
|
|
1182
|
+
sessionId,
|
|
1183
|
+
stage
|
|
1184
|
+
},
|
|
1185
|
+
logger: this.logger
|
|
1186
|
+
});
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
/**
|
|
1190
|
+
* Merge a DNS sidecar observation into the session's `dnsEvent` subdoc
|
|
1191
|
+
* using dotted-path `$set` so DNS-leg and HTTP-leg events on the same
|
|
1192
|
+
* session don't clobber each other. Replaces the prior read-modify-write
|
|
1193
|
+
* pattern that lost half the fields under any race (and 100% of the time
|
|
1194
|
+
* when the read projection didn't include `dnsEvent`).
|
|
1195
|
+
*
|
|
1196
|
+
* `receivedAtIfAbsent` is written only on the first hit for a session
|
|
1197
|
+
* via a `$cond` in an aggregation-pipeline update; later events leave
|
|
1198
|
+
* the original timestamp alone.
|
|
1199
|
+
*/
|
|
1200
|
+
async mergeSessionDnsEvent(sessionId, fields, receivedAtIfAbsent) {
|
|
1201
|
+
const setStage = {
|
|
1202
|
+
"dnsEvent.receivedAt": { $ifNull: ["$dnsEvent.receivedAt", receivedAtIfAbsent] },
|
|
1203
|
+
lastUpdatedTimestamp: /* @__PURE__ */ new Date(),
|
|
1204
|
+
pendingStage: true
|
|
1205
|
+
};
|
|
1206
|
+
if (fields.resolverIp !== void 0) setStage["dnsEvent.resolverIp"] = fields.resolverIp;
|
|
1207
|
+
if (fields.peerIp !== void 0) setStage["dnsEvent.peerIp"] = fields.peerIp;
|
|
1208
|
+
if (fields.pathValid !== void 0) setStage["dnsEvent.pathValid"] = fields.pathValid;
|
|
1209
|
+
try {
|
|
1210
|
+
return (await this.tables.session.updateOne({ sessionId }, [{ $set: setStage }])).matchedCount > 0;
|
|
1211
|
+
} catch (err) {
|
|
1212
|
+
throw new _prosopo_common.ProsopoDBError("DATABASE.SESSION_GET_FAILED", {
|
|
1213
|
+
context: {
|
|
1214
|
+
error: err,
|
|
1215
|
+
sessionId
|
|
1216
|
+
},
|
|
1217
|
+
logger: this.logger
|
|
1218
|
+
});
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
/**
|
|
1222
|
+
* Get an active session by user IP hash
|
|
1223
|
+
* @param userSitekeyIpHash The hash of user, IP and sitekey combination
|
|
1224
|
+
* @returns The session record if it exists and is not deleted, undefined otherwise
|
|
1225
|
+
*/
|
|
1226
|
+
async getSessionByuserSitekeyIpHash(userSitekeyIpHash) {
|
|
1227
|
+
this.logger.debug(() => ({ data: {
|
|
1228
|
+
action: "getting session by user IP hash",
|
|
1229
|
+
userSitekeyIpHash
|
|
1230
|
+
} }));
|
|
1231
|
+
const filter = {
|
|
1232
|
+
userSitekeyIpHash,
|
|
1233
|
+
deleted: { $exists: false }
|
|
1234
|
+
};
|
|
1235
|
+
try {
|
|
1236
|
+
return await this.tables.session.findOne(filter).lean() || void 0;
|
|
1237
|
+
} catch (err) {
|
|
1238
|
+
throw new _prosopo_common.ProsopoDBError("DATABASE.SESSION_GET_FAILED", {
|
|
1239
|
+
context: {
|
|
1240
|
+
error: err,
|
|
1241
|
+
userSitekeyIpHash
|
|
1242
|
+
},
|
|
1243
|
+
logger: this.logger
|
|
1244
|
+
});
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
/** Get unstored session records
|
|
1248
|
+
* @description Get session records that have not been stored yet.
|
|
1249
|
+
*
|
|
1250
|
+
* Served by the `pendingStage_partial` index — see
|
|
1251
|
+
* `getUnstoredDappUserCommitments` for the lifecycle of the flag.
|
|
1252
|
+
* `checkAndRemoveSession` also flips the flag so consumed sessions
|
|
1253
|
+
* propagate to the central DB via the next sweep.
|
|
1254
|
+
* @param limit
|
|
1255
|
+
* @param skip
|
|
1256
|
+
*/
|
|
1257
|
+
getUnstoredSessionRecords(limit = 1e3, skip = 0) {
|
|
1258
|
+
return Promise.resolve(this.tables?.session).then((tbl) => tbl?.find({ pendingStage: true }).sort({ _id: 1 }).skip(skip).limit(limit).lean()).then((docs) => docs || []);
|
|
1259
|
+
}
|
|
1260
|
+
/** Mark a list of session records as stored.
|
|
1261
|
+
*
|
|
1262
|
+
* `asOfTimestamp` defaults to "now" but the sweep should pass the time
|
|
1263
|
+
* at which it fetched the batch. See markDappUserCommitmentsStored for
|
|
1264
|
+
* the guard rationale.
|
|
1265
|
+
*/
|
|
1266
|
+
async markSessionRecordsStored(sessionIds, asOfTimestamp = /* @__PURE__ */ new Date()) {
|
|
1267
|
+
await this.tables?.session.updateMany({
|
|
1268
|
+
sessionId: { $in: sessionIds },
|
|
1269
|
+
lastUpdatedTimestamp: { $lte: asOfTimestamp }
|
|
1270
|
+
}, {
|
|
1271
|
+
$set: { storedAtTimestamp: /* @__PURE__ */ new Date() },
|
|
1272
|
+
$unset: { pendingStage: 1 }
|
|
1273
|
+
}, { upsert: false });
|
|
1274
|
+
}
|
|
1275
|
+
/**
|
|
1276
|
+
* @description Store a Dapp User's pending record
|
|
1277
|
+
*/
|
|
1278
|
+
async storePendingImageCommitment(userAccount, requestHash, salt, deadlineTimestamp, requestedAtTimestamp, ipAddress, threshold, sessionId, ipInfo) {
|
|
1279
|
+
if (!(0, _polkadot_util_is.isHex)(requestHash)) throw new _prosopo_common.ProsopoDBError("DATABASE.INVALID_HASH", { context: {
|
|
1280
|
+
failedFuncName: this.storePendingImageCommitment.name,
|
|
1281
|
+
requestHash
|
|
1282
|
+
} });
|
|
1283
|
+
const pendingRecord = {
|
|
1284
|
+
userAccount,
|
|
1285
|
+
pending: true,
|
|
1286
|
+
salt,
|
|
1287
|
+
requestHash,
|
|
1288
|
+
deadlineTimestamp,
|
|
1289
|
+
requestedAtTimestamp,
|
|
1290
|
+
ipAddress,
|
|
1291
|
+
sessionId,
|
|
1292
|
+
threshold,
|
|
1293
|
+
ipInfo,
|
|
1294
|
+
dappAccount: "",
|
|
1295
|
+
providerAccount: "",
|
|
1296
|
+
datasetId: "",
|
|
1297
|
+
id: "",
|
|
1298
|
+
result: { status: _prosopo_types.CaptchaStatus.pending },
|
|
1299
|
+
headers: {},
|
|
1300
|
+
ja4: "",
|
|
1301
|
+
userSignature: "",
|
|
1302
|
+
userSubmitted: false,
|
|
1303
|
+
serverChecked: false
|
|
1304
|
+
};
|
|
1305
|
+
await this.tables?.commitment.updateOne({ requestHash }, { $set: pendingRecord }, { upsert: true });
|
|
1306
|
+
}
|
|
1307
|
+
/**
|
|
1308
|
+
* @description Get a user's pending record
|
|
1309
|
+
*/
|
|
1310
|
+
async getPendingImageCommitment(requestHash) {
|
|
1311
|
+
if (!(0, _polkadot_util_is.isHex)(requestHash)) throw new _prosopo_common.ProsopoDBError("DATABASE.INVALID_HASH", { context: {
|
|
1312
|
+
failedFuncName: this.getPendingImageCommitment.name,
|
|
1313
|
+
requestHash
|
|
1314
|
+
} });
|
|
1315
|
+
const doc = await this.tables?.commitment.findOne({
|
|
1316
|
+
requestHash,
|
|
1317
|
+
pending: true
|
|
1318
|
+
}).lean();
|
|
1319
|
+
if (doc) return {
|
|
1320
|
+
dappAccount: doc.dappAccount,
|
|
1321
|
+
pending: doc.pending,
|
|
1322
|
+
salt: doc.salt,
|
|
1323
|
+
requestHash: doc.requestHash,
|
|
1324
|
+
deadlineTimestamp: doc.deadlineTimestamp,
|
|
1325
|
+
requestedAtTimestamp: doc.requestedAtTimestamp,
|
|
1326
|
+
ipAddress: doc.ipAddress,
|
|
1327
|
+
sessionId: doc.sessionId,
|
|
1328
|
+
threshold: doc.threshold
|
|
1329
|
+
};
|
|
1330
|
+
throw new _prosopo_common.ProsopoDBError("DATABASE.PENDING_RECORD_NOT_FOUND", { context: {
|
|
1331
|
+
failedFuncName: this.getPendingImageCommitment.name,
|
|
1332
|
+
requestHash
|
|
1333
|
+
} });
|
|
1334
|
+
}
|
|
1335
|
+
/**
|
|
1336
|
+
* @description Mark a pending request as used
|
|
1337
|
+
*/
|
|
1338
|
+
async updatePendingImageCommitmentStatus(requestHash) {
|
|
1339
|
+
if (!(0, _polkadot_util_is.isHex)(requestHash)) throw new _prosopo_common.ProsopoDBError("DATABASE.INVALID_HASH", { context: {
|
|
1340
|
+
failedFuncName: this.updatePendingImageCommitmentStatus.name,
|
|
1341
|
+
requestHash
|
|
1342
|
+
} });
|
|
1343
|
+
await this.tables?.commitment.updateOne({ requestHash }, { $set: { pending: false } });
|
|
1344
|
+
}
|
|
1345
|
+
/**
|
|
1346
|
+
* @description Get all unsolved captchas
|
|
1347
|
+
*/
|
|
1348
|
+
async getAllCaptchasByDatasetId(datasetId, state) {
|
|
1349
|
+
const filter = {
|
|
1350
|
+
datasetId,
|
|
1351
|
+
solved: state === _prosopo_types.CaptchaStates.Solved
|
|
1352
|
+
};
|
|
1353
|
+
const docs = await this.tables?.captcha.find(filter).lean();
|
|
1354
|
+
if (docs) return docs.map(({ _id, ...keepAttrs }) => keepAttrs);
|
|
1355
|
+
throw new _prosopo_common.ProsopoDBError("DATABASE.CAPTCHA_GET_FAILED");
|
|
1356
|
+
}
|
|
1357
|
+
/**
|
|
1358
|
+
* @description Get all dapp user solutions by captchaIds
|
|
1359
|
+
*/
|
|
1360
|
+
async getAllDappUserSolutions(captchaId) {
|
|
1361
|
+
const filter = { captchaId: { $in: captchaId } };
|
|
1362
|
+
const docs = await this.tables?.usersolution?.find(filter).lean();
|
|
1363
|
+
if (docs) return docs.map(({ _id, ...keepAttrs }) => keepAttrs);
|
|
1364
|
+
throw new _prosopo_common.ProsopoDBError("DATABASE.SOLUTION_GET_FAILED");
|
|
1365
|
+
}
|
|
1366
|
+
async getDatasetIdWithSolvedCaptchasOfSizeN(solvedCaptchaCount) {
|
|
1367
|
+
const docs = await this.tables?.solution.aggregate([
|
|
1368
|
+
{ $match: {} },
|
|
1369
|
+
{ $group: {
|
|
1370
|
+
_id: "$datasetId",
|
|
1371
|
+
count: { $sum: 1 }
|
|
1372
|
+
} },
|
|
1373
|
+
{ $match: { count: { $gte: solvedCaptchaCount } } },
|
|
1374
|
+
{ $sample: { size: 1 } }
|
|
1375
|
+
]);
|
|
1376
|
+
if (docs?.length) return docs[0]._id;
|
|
1377
|
+
throw new _prosopo_common.ProsopoDBError("DATABASE.DATASET_WITH_SOLUTIONS_GET_FAILED");
|
|
1378
|
+
}
|
|
1379
|
+
async getRandomSolvedCaptchasFromSingleDataset(datasetId, size) {
|
|
1380
|
+
if (!(0, _polkadot_util_is.isHex)(datasetId)) throw new _prosopo_common.ProsopoDBError("DATABASE.INVALID_HASH", { context: {
|
|
1381
|
+
failedFuncName: this.getRandomSolvedCaptchasFromSingleDataset.name,
|
|
1382
|
+
datasetId
|
|
1383
|
+
} });
|
|
1384
|
+
const sampleSize = size ? Math.abs(Math.trunc(size)) : 1;
|
|
1385
|
+
const docs = await this.tables?.solution.aggregate([
|
|
1386
|
+
{ $match: { datasetId } },
|
|
1387
|
+
{ $sample: { size: sampleSize } },
|
|
1388
|
+
{ $project: {
|
|
1389
|
+
captchaId: 1,
|
|
1390
|
+
captchaContentId: 1,
|
|
1391
|
+
solution: 1
|
|
1392
|
+
} }
|
|
1393
|
+
]);
|
|
1394
|
+
if (docs?.length) return docs;
|
|
1395
|
+
throw new _prosopo_common.ProsopoDBError("DATABASE.SOLUTION_GET_FAILED", { context: {
|
|
1396
|
+
failedFuncName: this.getRandomSolvedCaptchasFromSingleDataset.name,
|
|
1397
|
+
datasetId,
|
|
1398
|
+
size
|
|
1399
|
+
} });
|
|
1400
|
+
}
|
|
1401
|
+
/**
|
|
1402
|
+
* @description Get dapp user solution by ID
|
|
1403
|
+
* @param {string[]} commitmentId
|
|
1404
|
+
*/
|
|
1405
|
+
async getDappUserSolutionById(commitmentId) {
|
|
1406
|
+
const filter = { commitmentId };
|
|
1407
|
+
const doc = await this.tables?.usersolution?.findOne(filter, { _id: 0 }).lean();
|
|
1408
|
+
if (doc) return doc;
|
|
1409
|
+
throw new _prosopo_common.ProsopoDBError("DATABASE.SOLUTION_GET_FAILED", { context: {
|
|
1410
|
+
failedFuncName: this.getCaptchaById.name,
|
|
1411
|
+
commitmentId
|
|
1412
|
+
} });
|
|
1413
|
+
}
|
|
1414
|
+
/**
|
|
1415
|
+
* @description Get dapp user commitment by user account
|
|
1416
|
+
* @param commitmentId
|
|
1417
|
+
*/
|
|
1418
|
+
async getDappUserCommitmentById(commitmentId) {
|
|
1419
|
+
const filter = { id: commitmentId };
|
|
1420
|
+
const doc = await this.tables?.commitment?.findOne(filter, {
|
|
1421
|
+
id: 1,
|
|
1422
|
+
result: 1,
|
|
1423
|
+
serverChecked: 1,
|
|
1424
|
+
requestedAtTimestamp: 1,
|
|
1425
|
+
submittedAtTimestamp: 1,
|
|
1426
|
+
verifiedAtTimestamp: 1,
|
|
1427
|
+
failedAtTimestamp: 1,
|
|
1428
|
+
ipAddress: 1,
|
|
1429
|
+
sessionId: 1,
|
|
1430
|
+
userAccount: 1,
|
|
1431
|
+
dappAccount: 1,
|
|
1432
|
+
headers: 1,
|
|
1433
|
+
ipInfo: 1
|
|
1434
|
+
}).lean();
|
|
1435
|
+
return doc ? doc : void 0;
|
|
1436
|
+
}
|
|
1437
|
+
/**
|
|
1438
|
+
* @description Get dapp user commitment by user account
|
|
1439
|
+
* @param {string} userAccount
|
|
1440
|
+
* @param {string} dappAccount
|
|
1441
|
+
*/
|
|
1442
|
+
async getDappUserCommitmentByAccount(userAccount, dappAccount) {
|
|
1443
|
+
const filter = {
|
|
1444
|
+
userAccount,
|
|
1445
|
+
dappAccount
|
|
1446
|
+
};
|
|
1447
|
+
const docs = await this.tables?.commitment?.find(filter, {
|
|
1448
|
+
_id: 0,
|
|
1449
|
+
result: 1
|
|
1450
|
+
}, { sort: { _id: -1 } }).lean();
|
|
1451
|
+
return docs ? docs : [];
|
|
1452
|
+
}
|
|
1453
|
+
/**
|
|
1454
|
+
* @description Approve a dapp user's solution
|
|
1455
|
+
* @param {string[]} commitmentId
|
|
1456
|
+
* @param coords
|
|
1457
|
+
*/
|
|
1458
|
+
async approveDappUserCommitment(commitmentId, coords) {
|
|
1459
|
+
try {
|
|
1460
|
+
const updateDoc = {
|
|
1461
|
+
result: { status: _prosopo_types.CaptchaStatus.approved },
|
|
1462
|
+
lastUpdatedTimestamp: /* @__PURE__ */ new Date(),
|
|
1463
|
+
pendingStage: true,
|
|
1464
|
+
...coords ? { coords } : {}
|
|
1465
|
+
};
|
|
1466
|
+
const filter = { id: commitmentId };
|
|
1467
|
+
await this.tables?.commitment?.findOneAndUpdate(filter, { $set: updateDoc }, { upsert: false }).lean();
|
|
1468
|
+
this.centralStreamer?.streamImageUpdate(() => this.tables.commitment.findOne({ id: commitmentId }).lean(), (ts) => this.tables.commitment.updateOne({
|
|
1469
|
+
id: commitmentId,
|
|
1470
|
+
lastUpdatedTimestamp: { $lte: ts }
|
|
1471
|
+
}, {
|
|
1472
|
+
$set: { storedAtTimestamp: ts },
|
|
1473
|
+
$unset: { pendingStage: 1 }
|
|
1474
|
+
}).then(() => {}));
|
|
1475
|
+
} catch (err) {
|
|
1476
|
+
throw new _prosopo_common.ProsopoDBError("DATABASE.SOLUTION_APPROVE_FAILED", { context: {
|
|
1477
|
+
error: err,
|
|
1478
|
+
commitmentId
|
|
1479
|
+
} });
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
/**
|
|
1483
|
+
* @description Disapprove a dapp user's solution
|
|
1484
|
+
* @param {string} commitmentId
|
|
1485
|
+
* @param coords
|
|
1486
|
+
* @param reason
|
|
1487
|
+
*/
|
|
1488
|
+
async disapproveDappUserCommitment(commitmentId, reason, coords) {
|
|
1489
|
+
try {
|
|
1490
|
+
const updateDoc = {
|
|
1491
|
+
result: {
|
|
1492
|
+
status: _prosopo_types.CaptchaStatus.disapproved,
|
|
1493
|
+
reason
|
|
1494
|
+
},
|
|
1495
|
+
lastUpdatedTimestamp: /* @__PURE__ */ new Date(),
|
|
1496
|
+
pendingStage: true,
|
|
1497
|
+
...coords ? { coords } : {}
|
|
1498
|
+
};
|
|
1499
|
+
const filter = { id: commitmentId };
|
|
1500
|
+
await this.tables?.commitment?.findOneAndUpdate(filter, { $set: updateDoc }, { upsert: false }).lean();
|
|
1501
|
+
this.centralStreamer?.streamImageUpdate(() => this.tables.commitment.findOne({ id: commitmentId }).lean(), (ts) => this.tables.commitment.updateOne({
|
|
1502
|
+
id: commitmentId,
|
|
1503
|
+
lastUpdatedTimestamp: { $lte: ts }
|
|
1504
|
+
}, {
|
|
1505
|
+
$set: { storedAtTimestamp: ts },
|
|
1506
|
+
$unset: { pendingStage: 1 }
|
|
1507
|
+
}).then(() => {}));
|
|
1508
|
+
} catch (err) {
|
|
1509
|
+
throw new _prosopo_common.ProsopoDBError("DATABASE.SOLUTION_APPROVE_FAILED", { context: {
|
|
1510
|
+
error: err,
|
|
1511
|
+
commitmentId
|
|
1512
|
+
} });
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
/**
|
|
1516
|
+
* @description Flag a dapp user's solutions as used by calculated solution
|
|
1517
|
+
* @param {string[]} captchaIds
|
|
1518
|
+
*/
|
|
1519
|
+
async flagProcessedDappUserSolutions(captchaIds) {
|
|
1520
|
+
try {
|
|
1521
|
+
await this.tables?.usersolution?.updateMany({ captchaId: { $in: captchaIds } }, { $set: { processed: true } }, { upsert: false }).lean();
|
|
1522
|
+
} catch (err) {
|
|
1523
|
+
throw new _prosopo_common.ProsopoDBError("DATABASE.SOLUTION_FLAG_FAILED", { context: {
|
|
1524
|
+
error: err,
|
|
1525
|
+
captchaIds
|
|
1526
|
+
} });
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
/**
|
|
1530
|
+
* @description Flag dapp users' commitments as used by calculated solution
|
|
1531
|
+
* @param {string[]} commitmentIds
|
|
1532
|
+
*/
|
|
1533
|
+
async flagProcessedDappUserCommitments(commitmentIds) {
|
|
1534
|
+
try {
|
|
1535
|
+
const distinctCommitmentIds = [...new Set(commitmentIds)];
|
|
1536
|
+
await this.tables?.commitment?.updateMany({ id: { $in: distinctCommitmentIds } }, { $set: { processed: true } }, { upsert: false }).lean();
|
|
1537
|
+
} catch (err) {
|
|
1538
|
+
throw new _prosopo_common.ProsopoDBError("DATABASE.COMMITMENT_FLAG_FAILED", { context: {
|
|
1539
|
+
error: err,
|
|
1540
|
+
commitmentIds
|
|
1541
|
+
} });
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
/**
|
|
1545
|
+
* @description Get a scheduled task status record by task ID and status
|
|
1546
|
+
*/
|
|
1547
|
+
async getScheduledTaskStatus(taskId, status) {
|
|
1548
|
+
const filter = {
|
|
1549
|
+
_id: taskId,
|
|
1550
|
+
status
|
|
1551
|
+
};
|
|
1552
|
+
const cursor = await this.tables?.scheduler?.findOne(filter).lean();
|
|
1553
|
+
return cursor ? cursor : void 0;
|
|
1554
|
+
}
|
|
1555
|
+
/**
|
|
1556
|
+
* @description Get the most recent scheduled task status record for a given task
|
|
1557
|
+
*/
|
|
1558
|
+
async getLastScheduledTaskStatus(task, status) {
|
|
1559
|
+
const filter = { processName: task };
|
|
1560
|
+
if (status) filter.status = status;
|
|
1561
|
+
const cursor = await this.tables?.scheduler?.findOne(filter).sort({ datetime: -1 }).limit(1).lean();
|
|
1562
|
+
return cursor ? cursor : void 0;
|
|
1563
|
+
}
|
|
1564
|
+
/**
|
|
1565
|
+
* @description Create the status of a scheduled task
|
|
1566
|
+
*/
|
|
1567
|
+
async createScheduledTaskStatus(taskName, status) {
|
|
1568
|
+
const now = /* @__PURE__ */ new Date();
|
|
1569
|
+
const doc = _prosopo_types_database.ScheduledTaskSchema.parse({
|
|
1570
|
+
processName: taskName,
|
|
1571
|
+
datetime: now,
|
|
1572
|
+
status
|
|
1573
|
+
});
|
|
1574
|
+
return (await this.tables?.scheduler.create(doc))._id;
|
|
1575
|
+
}
|
|
1576
|
+
/**
|
|
1577
|
+
* @description Update the status of a scheduled task and an optional result
|
|
1578
|
+
*/
|
|
1579
|
+
async updateScheduledTaskStatus(taskId, status, result) {
|
|
1580
|
+
const update = {
|
|
1581
|
+
status,
|
|
1582
|
+
updated: /* @__PURE__ */ new Date(),
|
|
1583
|
+
...result && { result }
|
|
1584
|
+
};
|
|
1585
|
+
const filter = { _id: taskId };
|
|
1586
|
+
await this.tables?.scheduler.updateOne(filter, { $set: update }, { upsert: false });
|
|
1587
|
+
}
|
|
1588
|
+
/**
|
|
1589
|
+
* @description Clean up the scheduled task status records
|
|
1590
|
+
*/
|
|
1591
|
+
async cleanupScheduledTaskStatus(status) {
|
|
1592
|
+
const filter = { status };
|
|
1593
|
+
await this.tables?.scheduler.deleteMany(filter);
|
|
1594
|
+
}
|
|
1595
|
+
/**
|
|
1596
|
+
* @description Update the client records
|
|
1597
|
+
*/
|
|
1598
|
+
async updateClientRecords(clientRecords) {
|
|
1599
|
+
const ops = clientRecords.map((record) => {
|
|
1600
|
+
const clientRecord = {
|
|
1601
|
+
account: record.account,
|
|
1602
|
+
settings: record.settings,
|
|
1603
|
+
tier: record.tier
|
|
1604
|
+
};
|
|
1605
|
+
return { updateOne: {
|
|
1606
|
+
filter: { account: record.account },
|
|
1607
|
+
update: { $set: clientRecord },
|
|
1608
|
+
upsert: true
|
|
1609
|
+
} };
|
|
1610
|
+
});
|
|
1611
|
+
if (!ops || ops.length === 0) {
|
|
1612
|
+
await this.logger.debug(() => ({ msg: "No client updates to apply" }));
|
|
1613
|
+
return;
|
|
1614
|
+
}
|
|
1615
|
+
await this.tables?.client.bulkWrite(ops);
|
|
1616
|
+
}
|
|
1617
|
+
/**
|
|
1618
|
+
* @description Remove client records by account
|
|
1619
|
+
*/
|
|
1620
|
+
async removeClientRecords(accounts) {
|
|
1621
|
+
if (!accounts || accounts.length === 0) return;
|
|
1622
|
+
await this.tables?.client.deleteMany({ account: { $in: accounts } });
|
|
1623
|
+
}
|
|
1624
|
+
/**
|
|
1625
|
+
* @description Get all client records
|
|
1626
|
+
*/
|
|
1627
|
+
async getAllClientRecords() {
|
|
1628
|
+
return await this.tables?.client.find().lean() || [];
|
|
1629
|
+
}
|
|
1630
|
+
/**
|
|
1631
|
+
* @description Get a client record
|
|
1632
|
+
*/
|
|
1633
|
+
async getClientRecord(account) {
|
|
1634
|
+
const filter = { account };
|
|
1635
|
+
const doc = await this.tables?.client.findOne(filter, {
|
|
1636
|
+
account: 1,
|
|
1637
|
+
settings: 1,
|
|
1638
|
+
tier: 1
|
|
1639
|
+
}).lean();
|
|
1640
|
+
return doc ? doc : void 0;
|
|
1641
|
+
}
|
|
1642
|
+
/**
|
|
1643
|
+
* @description Store a detector key
|
|
1644
|
+
*/
|
|
1645
|
+
async storeDetectorKey(detectorKey) {
|
|
1646
|
+
return this.tables?.detector.create({
|
|
1647
|
+
detectorKey,
|
|
1648
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
1649
|
+
});
|
|
1650
|
+
}
|
|
1651
|
+
/**
|
|
1652
|
+
* @description Remove a detector key
|
|
1653
|
+
* @param detectorKey The detector key to remove
|
|
1654
|
+
* @param expirationInSeconds Optional expiration time in seconds (default is 10 minutes)
|
|
1655
|
+
* */
|
|
1656
|
+
async removeDetectorKey(detectorKey, expirationInSeconds) {
|
|
1657
|
+
const filter = { detectorKey };
|
|
1658
|
+
const expiresAt = new Date(Date.now() + (expirationInSeconds || 600) * 1e3);
|
|
1659
|
+
await this.tables?.detector.updateOne(filter, { $set: { expiresAt } });
|
|
1660
|
+
}
|
|
1661
|
+
/**
|
|
1662
|
+
* @description Get valid detector keys
|
|
1663
|
+
*/
|
|
1664
|
+
async getDetectorKeys() {
|
|
1665
|
+
return (await this.tables?.detector.find({ $or: [{ expiresAt: { $exists: false } }, { expiresAt: null }] }, { detectorKey: 1 }).sort({ createdAt: -1 }).lean() || []).map((record) => record.detectorKey);
|
|
1666
|
+
}
|
|
1667
|
+
/**
|
|
1668
|
+
* Stores a decision machine artifact with a unique scope identifier.
|
|
1669
|
+
*
|
|
1670
|
+
* The combination of scope + dappAccount uniquely identifies a single artifact:
|
|
1671
|
+
* - Global scope: One artifact per provider (dappAccount is null)
|
|
1672
|
+
* - Dapp scope: One artifact per dapp account (dappAccount is specified)
|
|
1673
|
+
*
|
|
1674
|
+
* @param artifact - The decision machine artifact to store
|
|
1675
|
+
*/
|
|
1676
|
+
async upsertDecisionMachineArtifact(artifact) {
|
|
1677
|
+
const now = /* @__PURE__ */ new Date();
|
|
1678
|
+
const dappAccount = artifact.dappAccount ?? null;
|
|
1679
|
+
const kind = artifact.kind ?? _prosopo_types.DecisionMachineKind.Routing;
|
|
1680
|
+
const filter = {
|
|
1681
|
+
scope: artifact.scope,
|
|
1682
|
+
dappAccount,
|
|
1683
|
+
kind
|
|
1684
|
+
};
|
|
1685
|
+
await this.tables?.decisionMachine.updateOne(filter, {
|
|
1686
|
+
$set: {
|
|
1687
|
+
scope: artifact.scope,
|
|
1688
|
+
dappAccount,
|
|
1689
|
+
kind,
|
|
1690
|
+
runtime: artifact.runtime,
|
|
1691
|
+
language: artifact.language,
|
|
1692
|
+
source: artifact.source,
|
|
1693
|
+
name: artifact.name,
|
|
1694
|
+
version: artifact.version,
|
|
1695
|
+
captchaType: artifact.captchaType,
|
|
1696
|
+
updatedAt: now
|
|
1697
|
+
},
|
|
1698
|
+
$setOnInsert: { createdAt: now }
|
|
1699
|
+
}, { upsert: true });
|
|
1700
|
+
}
|
|
1701
|
+
/**
|
|
1702
|
+
* Retrieves a single decision machine artifact by scope and optional dapp account.
|
|
1703
|
+
*
|
|
1704
|
+
* @param scope - The scope level (Global or Dapp)
|
|
1705
|
+
* @param dappAccount - Required for Dapp scope, unused for Global scope
|
|
1706
|
+
* @returns The matching artifact, or undefined if not found
|
|
1707
|
+
*/
|
|
1708
|
+
async getDecisionMachineArtifact(scope, dappAccount, kind) {
|
|
1709
|
+
const filter = {
|
|
1710
|
+
scope,
|
|
1711
|
+
dappAccount: dappAccount ?? null
|
|
1712
|
+
};
|
|
1713
|
+
if (kind !== void 0) filter.kind = kind;
|
|
1714
|
+
return await this.tables?.decisionMachine.findOne(filter).lean() ?? void 0;
|
|
1715
|
+
}
|
|
1716
|
+
/**
|
|
1717
|
+
* Retrieves all decision machine artifacts.
|
|
1718
|
+
*
|
|
1719
|
+
* @returns Array of all decision machine artifacts
|
|
1720
|
+
*/
|
|
1721
|
+
async getAllDecisionMachineArtifacts() {
|
|
1722
|
+
return await this.tables?.decisionMachine.find({}).lean() ?? [];
|
|
1723
|
+
}
|
|
1724
|
+
/**
|
|
1725
|
+
* Retrieves a single decision machine artifact by MongoDB ObjectId.
|
|
1726
|
+
*
|
|
1727
|
+
* @param id - The MongoDB ObjectId as a string
|
|
1728
|
+
* @returns The matching artifact, or undefined if not found
|
|
1729
|
+
*/
|
|
1730
|
+
async getDecisionMachineArtifactById(id) {
|
|
1731
|
+
return await this.tables?.decisionMachine.findById(id).lean() ?? void 0;
|
|
1732
|
+
}
|
|
1733
|
+
/**
|
|
1734
|
+
* Removes a decision machine artifact by MongoDB ObjectId.
|
|
1735
|
+
*
|
|
1736
|
+
* @param id - The MongoDB ObjectId as a string
|
|
1737
|
+
* @returns true if deleted, false if not found
|
|
1738
|
+
*/
|
|
1739
|
+
async removeDecisionMachineArtifact(id) {
|
|
1740
|
+
return ((await this.tables?.decisionMachine.deleteOne({ _id: id }))?.deletedCount ?? 0) > 0;
|
|
1741
|
+
}
|
|
1742
|
+
/**
|
|
1743
|
+
* Removes all decision machine artifacts.
|
|
1744
|
+
*
|
|
1745
|
+
* @returns The number of artifacts deleted
|
|
1746
|
+
*/
|
|
1747
|
+
async removeAllDecisionMachineArtifacts() {
|
|
1748
|
+
return (await this.tables?.decisionMachine.deleteMany({}))?.deletedCount ?? 0;
|
|
1749
|
+
}
|
|
1750
|
+
/**
|
|
1751
|
+
* @description set client context-specific entropy
|
|
1752
|
+
*/
|
|
1753
|
+
async setClientContextEntropy(account, contextType, entropy) {
|
|
1754
|
+
const filter = {
|
|
1755
|
+
account,
|
|
1756
|
+
contextType
|
|
1757
|
+
};
|
|
1758
|
+
await this.tables?.clientContextEntropy.updateOne(filter, { $set: {
|
|
1759
|
+
account,
|
|
1760
|
+
contextType,
|
|
1761
|
+
entropy
|
|
1762
|
+
} }, { upsert: true });
|
|
1763
|
+
}
|
|
1764
|
+
/**
|
|
1765
|
+
* @description get client context-specific entropy
|
|
1766
|
+
*/
|
|
1767
|
+
async getClientContextEntropy(account, contextType) {
|
|
1768
|
+
const filter = {
|
|
1769
|
+
account,
|
|
1770
|
+
contextType
|
|
1771
|
+
};
|
|
1772
|
+
const doc = await this.tables?.clientContextEntropy.findOne(filter).lean();
|
|
1773
|
+
return doc ? doc.entropy : void 0;
|
|
1774
|
+
}
|
|
1775
|
+
/** Sample captcha records from the database for a specific context */
|
|
1776
|
+
async sampleContextEntropy(sampleSize, siteKey, contextType) {
|
|
1777
|
+
const size = sampleSize ? Math.abs(Math.trunc(sampleSize)) : 1;
|
|
1778
|
+
const max = 1e4;
|
|
1779
|
+
if (size > max) throw new _prosopo_common.ProsopoDBError("DATABASE.CAPTCHA_SAMPLE_SIZE_EXCEEDED", { context: {
|
|
1780
|
+
failedFuncName: this.sampleContextEntropy.name,
|
|
1781
|
+
sampleSize
|
|
1782
|
+
} });
|
|
1783
|
+
const pipeline = [
|
|
1784
|
+
{ $match: {
|
|
1785
|
+
dappAccount: siteKey,
|
|
1786
|
+
requestedAtTimestamp: { $gt: new Date((/* @__PURE__ */ new Date()).getTime() - TWENTY_FOUR_HOURS_IN_MS) }
|
|
1787
|
+
} },
|
|
1788
|
+
{ $sample: { size: max } },
|
|
1789
|
+
{ $lookup: {
|
|
1790
|
+
from: "sessions",
|
|
1791
|
+
localField: "sessionId",
|
|
1792
|
+
foreignField: "sessionId",
|
|
1793
|
+
as: "sessionData"
|
|
1794
|
+
} },
|
|
1795
|
+
{ $unwind: {
|
|
1796
|
+
path: "$sessionData",
|
|
1797
|
+
preserveNullAndEmptyArrays: false
|
|
1798
|
+
} }
|
|
1799
|
+
];
|
|
1800
|
+
if (contextType === _prosopo_types.ContextType.Webview) pipeline.push({ $match: { "sessionData.webView": true } });
|
|
1801
|
+
else if (contextType === _prosopo_types.ContextType.Default) pipeline.push({ $match: { "sessionData.webView": false } });
|
|
1802
|
+
pipeline.push({ $sample: { size } }, { $project: {
|
|
1803
|
+
_id: 0,
|
|
1804
|
+
sessionId: 1
|
|
1805
|
+
} });
|
|
1806
|
+
const docs = await this.tables?.powcaptcha.aggregate(pipeline);
|
|
1807
|
+
if (docs?.length === 0) return [];
|
|
1808
|
+
return (await Promise.all(docs.map(async (doc) => {
|
|
1809
|
+
if (doc.sessionId) return (await this.getSessionRecordBySessionId(doc.sessionId))?.decryptedHeadHash;
|
|
1810
|
+
}))).filter((headHash) => headHash !== void 0);
|
|
1811
|
+
}
|
|
1812
|
+
async getSpamEmailDomain(domain) {
|
|
1813
|
+
if (!this.tables?.spamEmailDomain) throw new _prosopo_common.ProsopoDBError("DATABASE.DATABASE_IMPORT_ERROR", { context: { failedFuncName: this.getSpamEmailDomain.name } });
|
|
1814
|
+
const suffixCandidates = (0, _prosopo_util.buildDomainSuffixCandidates)(domain).slice(0, MAX_DOMAIN_SUFFIX_CANDIDATES);
|
|
1815
|
+
const query = suffixCandidates.length > 0 ? { domain: { $in: suffixCandidates } } : { domain };
|
|
1816
|
+
return await this.tables.spamEmailDomain.findOne(query).exec();
|
|
1817
|
+
}
|
|
1818
|
+
async bulkUpdateSpamEmailDomains(domains, upsert) {
|
|
1819
|
+
if (!this.tables?.spamEmailDomain) throw new _prosopo_common.ProsopoDBError("DATABASE.DATABASE_IMPORT_ERROR", { context: { failedFuncName: this.bulkUpdateSpamEmailDomains.name } });
|
|
1820
|
+
const bulkOps = domains.map((op) => ({ updateOne: {
|
|
1821
|
+
filter: op.filter,
|
|
1822
|
+
update: { $set: op.update },
|
|
1823
|
+
upsert
|
|
1824
|
+
} }));
|
|
1825
|
+
if (bulkOps.length > 0) await this.tables.spamEmailDomain.bulkWrite(bulkOps);
|
|
1826
|
+
}
|
|
1827
|
+
};
|
|
1828
|
+
//#endregion
|
|
2253
1829
|
exports.ProviderDatabase = ProviderDatabase;
|