@prosopo/database 3.5.6 → 3.13.7
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 +17 -13
- package/.turbo/turbo-build$colon$tsc.log +47 -0
- package/.turbo/turbo-build.log +22 -14
- package/CHANGELOG.md +690 -0
- package/dist/base/index.d.ts +3 -0
- package/dist/base/index.d.ts.map +1 -0
- package/dist/base/index.js.map +1 -0
- package/dist/base/mongo.d.ts +18 -0
- package/dist/base/mongo.d.ts.map +1 -0
- package/dist/base/mongo.js +5 -2
- package/dist/base/mongo.js.map +1 -0
- package/dist/base/mongoMemory.d.ts +10 -0
- package/dist/base/mongoMemory.d.ts.map +1 -0
- package/dist/base/mongoMemory.js.map +1 -0
- package/dist/cjs/base/mongo.cjs +6 -3
- package/dist/cjs/databases/captcha.cjs +2 -1
- package/dist/cjs/databases/centralDbStreamer.cjs +136 -0
- package/dist/cjs/databases/index.cjs +2 -0
- package/dist/cjs/databases/provider.cjs +691 -165
- package/dist/cjs/index.cjs +4 -0
- package/dist/cjs/redisCache.cjs +388 -0
- package/dist/databases/captcha.d.ts +25 -0
- package/dist/databases/captcha.d.ts.map +1 -0
- package/dist/databases/captcha.js +2 -1
- package/dist/databases/captcha.js.map +1 -0
- package/dist/databases/centralDbStreamer.d.ts +19 -0
- package/dist/databases/centralDbStreamer.d.ts.map +1 -0
- package/dist/databases/centralDbStreamer.js +136 -0
- package/dist/databases/centralDbStreamer.js.map +1 -0
- package/dist/databases/client.d.ts +12 -0
- package/dist/databases/client.d.ts.map +1 -0
- package/dist/databases/client.js.map +1 -0
- package/dist/databases/index.d.ts +17 -0
- package/dist/databases/index.d.ts.map +1 -0
- package/dist/databases/index.js +2 -0
- package/dist/databases/index.js.map +1 -0
- package/dist/databases/provider.d.ts +143 -0
- package/dist/databases/provider.d.ts.map +1 -0
- package/dist/databases/provider.js +692 -166
- package/dist/databases/provider.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -0
- package/dist/redisCache.d.ts +31 -0
- package/dist/redisCache.d.ts.map +1 -0
- package/dist/redisCache.js +388 -0
- package/dist/redisCache.js.map +1 -0
- package/dist/tests/integration/ipInfoPersistence.integration.test.d.ts +2 -0
- package/dist/tests/integration/ipInfoPersistence.integration.test.d.ts.map +1 -0
- package/dist/tests/integration/ipInfoPersistence.integration.test.js +243 -0
- package/dist/tests/integration/ipInfoPersistence.integration.test.js.map +1 -0
- package/dist/tests/unit/captchaLabel.unit.test.d.ts +2 -0
- package/dist/tests/unit/captchaLabel.unit.test.d.ts.map +1 -0
- package/dist/tests/unit/captchaLabel.unit.test.js +41 -0
- package/dist/tests/unit/captchaLabel.unit.test.js.map +1 -0
- package/dist/tests/unit/databases/centralDbStreamer.unit.test.d.ts +2 -0
- package/dist/tests/unit/databases/centralDbStreamer.unit.test.d.ts.map +1 -0
- package/dist/tests/unit/databases/centralDbStreamer.unit.test.js +221 -0
- package/dist/tests/unit/databases/centralDbStreamer.unit.test.js.map +1 -0
- package/package.json +14 -10
- package/vite.cjs.config.ts +1 -1
- package/vite.esm.config.ts +1 -1
- package/vite.test.config.ts +18 -0
|
@@ -6,21 +6,25 @@ const redisClient = require("@prosopo/redis-client");
|
|
|
6
6
|
const types = require("@prosopo/types");
|
|
7
7
|
const typesDatabase = require("@prosopo/types-database");
|
|
8
8
|
const redis = require("@prosopo/user-access-policy/redis");
|
|
9
|
+
const util = require("@prosopo/util");
|
|
9
10
|
const mongo = require("../base/mongo.cjs");
|
|
10
11
|
const TWENTY_FOUR_HOURS_IN_MS = 24 * 60 * 60 * 1e3;
|
|
12
|
+
const MAX_DOMAIN_SUFFIX_CANDIDATES = 5;
|
|
11
13
|
var TableNames = /* @__PURE__ */ ((TableNames2) => {
|
|
12
14
|
TableNames2["captcha"] = "captcha";
|
|
13
15
|
TableNames2["dataset"] = "dataset";
|
|
14
16
|
TableNames2["solution"] = "solution";
|
|
15
17
|
TableNames2["commitment"] = "commitment";
|
|
16
18
|
TableNames2["usersolution"] = "usersolution";
|
|
17
|
-
TableNames2["pending"] = "pending";
|
|
18
19
|
TableNames2["scheduler"] = "scheduler";
|
|
19
20
|
TableNames2["powcaptcha"] = "powcaptcha";
|
|
21
|
+
TableNames2["puzzlecaptcha"] = "puzzlecaptcha";
|
|
20
22
|
TableNames2["client"] = "client";
|
|
21
23
|
TableNames2["session"] = "session";
|
|
22
24
|
TableNames2["detector"] = "detector";
|
|
25
|
+
TableNames2["decisionMachine"] = "decisionMachine";
|
|
23
26
|
TableNames2["clientContextEntropy"] = "clientContextEntropy";
|
|
27
|
+
TableNames2["spamEmailDomain"] = "spamEmailDomain";
|
|
24
28
|
return TableNames2;
|
|
25
29
|
})(TableNames || {});
|
|
26
30
|
const PROVIDER_TABLES = [
|
|
@@ -34,6 +38,11 @@ const PROVIDER_TABLES = [
|
|
|
34
38
|
modelName: "PowCaptcha",
|
|
35
39
|
schema: typesDatabase.PoWCaptchaRecordSchema
|
|
36
40
|
},
|
|
41
|
+
{
|
|
42
|
+
collectionName: "puzzlecaptcha",
|
|
43
|
+
modelName: "PuzzleCaptcha",
|
|
44
|
+
schema: typesDatabase.PuzzleCaptchaRecordSchema
|
|
45
|
+
},
|
|
37
46
|
{
|
|
38
47
|
collectionName: "dataset",
|
|
39
48
|
modelName: "Dataset",
|
|
@@ -54,11 +63,6 @@ const PROVIDER_TABLES = [
|
|
|
54
63
|
modelName: "UserSolution",
|
|
55
64
|
schema: typesDatabase.UserSolutionRecordSchema
|
|
56
65
|
},
|
|
57
|
-
{
|
|
58
|
-
collectionName: "pending",
|
|
59
|
-
modelName: "Pending",
|
|
60
|
-
schema: typesDatabase.PendingRecordSchema
|
|
61
|
-
},
|
|
62
66
|
{
|
|
63
67
|
collectionName: "scheduler",
|
|
64
68
|
modelName: "Scheduler",
|
|
@@ -79,10 +83,20 @@ const PROVIDER_TABLES = [
|
|
|
79
83
|
modelName: "Detector",
|
|
80
84
|
schema: typesDatabase.DetectorRecordSchema
|
|
81
85
|
},
|
|
86
|
+
{
|
|
87
|
+
collectionName: "decisionMachine",
|
|
88
|
+
modelName: "DecisionMachine",
|
|
89
|
+
schema: typesDatabase.DecisionMachineArtifactRecordSchema
|
|
90
|
+
},
|
|
82
91
|
{
|
|
83
92
|
collectionName: "clientContextEntropy",
|
|
84
93
|
modelName: "ClientContextEntropy",
|
|
85
94
|
schema: typesDatabase.ClientContextEntropyRecordSchema
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
collectionName: "spamEmailDomain",
|
|
98
|
+
modelName: "SpamEmailDomain",
|
|
99
|
+
schema: typesDatabase.SpamEmailDomainRecordSchema
|
|
86
100
|
}
|
|
87
101
|
];
|
|
88
102
|
class ProviderDatabase extends mongo.MongoDatabase {
|
|
@@ -101,6 +115,12 @@ class ProviderDatabase extends mongo.MongoDatabase {
|
|
|
101
115
|
this.redisConnection = null;
|
|
102
116
|
this.userAccessRulesStorage = null;
|
|
103
117
|
}
|
|
118
|
+
setCentralDbStreamer(streamer) {
|
|
119
|
+
this.centralStreamer = streamer;
|
|
120
|
+
}
|
|
121
|
+
hasCentralDbStreamer() {
|
|
122
|
+
return this.centralStreamer !== void 0;
|
|
123
|
+
}
|
|
104
124
|
async connect() {
|
|
105
125
|
await super.connect();
|
|
106
126
|
this.loadTables();
|
|
@@ -382,7 +402,12 @@ class ProviderDatabase extends mongo.MongoDatabase {
|
|
|
382
402
|
*/
|
|
383
403
|
async getCaptchaById(captchaId) {
|
|
384
404
|
const filter = { captchaId: { $in: captchaId } };
|
|
385
|
-
const cursor = this.tables?.captcha.find(filter
|
|
405
|
+
const cursor = this.tables?.captcha.find(filter, {
|
|
406
|
+
_id: 0,
|
|
407
|
+
captchaId: 1,
|
|
408
|
+
datasetId: 1,
|
|
409
|
+
items: 1
|
|
410
|
+
}).lean();
|
|
386
411
|
const docs = await cursor;
|
|
387
412
|
if (docs?.length) {
|
|
388
413
|
return docs.map(({ _id, ...keepAttrs }) => keepAttrs);
|
|
@@ -447,7 +472,7 @@ class ProviderDatabase extends mongo.MongoDatabase {
|
|
|
447
472
|
* @description Store a Dapp User's captcha solution commitment
|
|
448
473
|
*/
|
|
449
474
|
async storeUserImageCaptchaSolution(captchas, commit) {
|
|
450
|
-
const commitmentRecord =
|
|
475
|
+
const commitmentRecord = types.UserCommitmentSchema.parse({
|
|
451
476
|
...commit,
|
|
452
477
|
lastUpdatedTimestamp: /* @__PURE__ */ new Date()
|
|
453
478
|
});
|
|
@@ -478,6 +503,11 @@ class ProviderDatabase extends mongo.MongoDatabase {
|
|
|
478
503
|
}
|
|
479
504
|
}));
|
|
480
505
|
await this.tables?.usersolution.bulkWrite(ops);
|
|
506
|
+
this.centralStreamer?.streamImageRecord(
|
|
507
|
+
commitmentRecord,
|
|
508
|
+
(ts) => this.tables.commitment.updateOne({ id: commit.id }, { $set: { storedAtTimestamp: ts } }).then(() => {
|
|
509
|
+
})
|
|
510
|
+
);
|
|
481
511
|
}
|
|
482
512
|
}
|
|
483
513
|
/**
|
|
@@ -494,9 +524,10 @@ class ProviderDatabase extends mongo.MongoDatabase {
|
|
|
494
524
|
* @param userSubmitted
|
|
495
525
|
* @param storedStatus
|
|
496
526
|
* @param userSignature
|
|
527
|
+
* @param ipInfo full ipinfo payload from the ipInfoMiddleware
|
|
497
528
|
* @returns {Promise<void>} A promise that resolves when the record is added.
|
|
498
529
|
*/
|
|
499
|
-
async storePowCaptchaRecord(challenge, components, difficulty, providerSignature, ipAddress, headers, ja4, sessionId, serverChecked = false, userSubmitted = false,
|
|
530
|
+
async storePowCaptchaRecord(challenge, components, difficulty, providerSignature, ipAddress, headers, ja4, sessionId, serverChecked = false, userSubmitted = false, userSignature, ipInfo) {
|
|
500
531
|
const tables = this.getTables();
|
|
501
532
|
const powCaptchaRecord = {
|
|
502
533
|
challenge,
|
|
@@ -513,7 +544,9 @@ class ProviderDatabase extends mongo.MongoDatabase {
|
|
|
513
544
|
providerSignature,
|
|
514
545
|
userSignature,
|
|
515
546
|
lastUpdatedTimestamp: /* @__PURE__ */ new Date(),
|
|
516
|
-
|
|
547
|
+
pendingStage: true,
|
|
548
|
+
sessionId,
|
|
549
|
+
ipInfo
|
|
517
550
|
};
|
|
518
551
|
try {
|
|
519
552
|
await tables.powcaptcha.create(powCaptchaRecord);
|
|
@@ -522,10 +555,27 @@ class ProviderDatabase extends mongo.MongoDatabase {
|
|
|
522
555
|
challenge,
|
|
523
556
|
userSubmitted,
|
|
524
557
|
serverChecked,
|
|
525
|
-
|
|
558
|
+
countryCode: ipInfo?.isValid ? ipInfo.countryCode : void 0
|
|
526
559
|
},
|
|
527
560
|
msg: "PowCaptcha record added successfully"
|
|
528
561
|
}));
|
|
562
|
+
this.centralStreamer?.streamPowRecord(
|
|
563
|
+
powCaptchaRecord,
|
|
564
|
+
(ts) => (
|
|
565
|
+
// Guard with `lastUpdatedTimestamp: { $lte: ts }` so a concurrent
|
|
566
|
+
// update (newer lastUpdatedTimestamp) doesn't get its pendingStage
|
|
567
|
+
// flag cleared by this older stage completion. Mismatched docs
|
|
568
|
+
// leave pendingStage set so the next sweep picks them up.
|
|
569
|
+
this.tables.powcaptcha.updateOne(
|
|
570
|
+
{ challenge, lastUpdatedTimestamp: { $lte: ts } },
|
|
571
|
+
{
|
|
572
|
+
$set: { storedAtTimestamp: ts },
|
|
573
|
+
$unset: { pendingStage: 1 }
|
|
574
|
+
}
|
|
575
|
+
).then(() => {
|
|
576
|
+
})
|
|
577
|
+
)
|
|
578
|
+
);
|
|
529
579
|
} catch (error) {
|
|
530
580
|
const err = new common.ProsopoDBError("DATABASE.CAPTCHA_UPDATE_FAILED", {
|
|
531
581
|
context: {
|
|
@@ -533,7 +583,7 @@ class ProviderDatabase extends mongo.MongoDatabase {
|
|
|
533
583
|
challenge,
|
|
534
584
|
userSubmitted,
|
|
535
585
|
serverChecked,
|
|
536
|
-
|
|
586
|
+
ipInfo
|
|
537
587
|
},
|
|
538
588
|
logger: this.logger
|
|
539
589
|
});
|
|
@@ -558,7 +608,24 @@ class ProviderDatabase extends mongo.MongoDatabase {
|
|
|
558
608
|
}
|
|
559
609
|
try {
|
|
560
610
|
const filter = { challenge };
|
|
561
|
-
const record = await this.tables.powcaptcha.findOne(filter
|
|
611
|
+
const record = await this.tables.powcaptcha.findOne(filter, {
|
|
612
|
+
challenge: 1,
|
|
613
|
+
userAccount: 1,
|
|
614
|
+
dappAccount: 1,
|
|
615
|
+
requestedAtTimestamp: 1,
|
|
616
|
+
ipAddress: 1,
|
|
617
|
+
headers: 1,
|
|
618
|
+
ja4: 1,
|
|
619
|
+
result: 1,
|
|
620
|
+
difficulty: 1,
|
|
621
|
+
sessionId: 1,
|
|
622
|
+
ipInfo: 1,
|
|
623
|
+
deviceCapability: 1,
|
|
624
|
+
behavioralDataPacked: 1,
|
|
625
|
+
serverChecked: 1,
|
|
626
|
+
userSubmitted: 1,
|
|
627
|
+
coords: 1
|
|
628
|
+
}).lean();
|
|
562
629
|
if (record) {
|
|
563
630
|
this.logger.info(() => ({
|
|
564
631
|
data: { challenge },
|
|
@@ -592,7 +659,7 @@ class ProviderDatabase extends mongo.MongoDatabase {
|
|
|
592
659
|
* @param userSignature
|
|
593
660
|
* @returns {Promise<void>} A promise that resolves when the record is updated.
|
|
594
661
|
*/
|
|
595
|
-
async updatePowCaptchaRecordResult(challenge, result, serverChecked = false, userSubmitted = false, userSignature) {
|
|
662
|
+
async updatePowCaptchaRecordResult(challenge, result, serverChecked = false, userSubmitted = false, userSignature, coords) {
|
|
596
663
|
const tables = this.getTables();
|
|
597
664
|
const timestamp = /* @__PURE__ */ new Date();
|
|
598
665
|
const update = {
|
|
@@ -600,7 +667,9 @@ class ProviderDatabase extends mongo.MongoDatabase {
|
|
|
600
667
|
serverChecked,
|
|
601
668
|
userSubmitted,
|
|
602
669
|
userSignature,
|
|
603
|
-
lastUpdatedTimestamp: timestamp
|
|
670
|
+
lastUpdatedTimestamp: timestamp,
|
|
671
|
+
pendingStage: true,
|
|
672
|
+
...coords && { coords }
|
|
604
673
|
};
|
|
605
674
|
try {
|
|
606
675
|
const updateResult = await tables.powcaptcha.updateOne(
|
|
@@ -630,6 +699,17 @@ class ProviderDatabase extends mongo.MongoDatabase {
|
|
|
630
699
|
},
|
|
631
700
|
msg: "PowCaptcha record updated successfully"
|
|
632
701
|
}));
|
|
702
|
+
this.centralStreamer?.streamPowUpdate(
|
|
703
|
+
() => this.getPowCaptchaRecordByChallenge(challenge),
|
|
704
|
+
(ts) => this.tables.powcaptcha.updateOne(
|
|
705
|
+
{ challenge, lastUpdatedTimestamp: { $lte: ts } },
|
|
706
|
+
{
|
|
707
|
+
$set: { storedAtTimestamp: ts },
|
|
708
|
+
$unset: { pendingStage: 1 }
|
|
709
|
+
}
|
|
710
|
+
).then(() => {
|
|
711
|
+
})
|
|
712
|
+
);
|
|
633
713
|
} catch (error) {
|
|
634
714
|
const err = new common.ProsopoDBError("DATABASE.CAPTCHA_UPDATE_FAILED", {
|
|
635
715
|
context: {
|
|
@@ -650,7 +730,205 @@ class ProviderDatabase extends mongo.MongoDatabase {
|
|
|
650
730
|
const tables = this.getTables();
|
|
651
731
|
await tables.powcaptcha.updateOne(
|
|
652
732
|
{ challenge },
|
|
653
|
-
{ $set: updates },
|
|
733
|
+
{ $set: { ...updates, pendingStage: true } },
|
|
734
|
+
{ upsert: false }
|
|
735
|
+
);
|
|
736
|
+
this.centralStreamer?.streamPowUpdate(
|
|
737
|
+
() => this.getPowCaptchaRecordByChallenge(challenge),
|
|
738
|
+
(ts) => this.tables.powcaptcha.updateOne(
|
|
739
|
+
{ challenge, lastUpdatedTimestamp: { $lte: ts } },
|
|
740
|
+
{
|
|
741
|
+
$set: { storedAtTimestamp: ts },
|
|
742
|
+
$unset: { pendingStage: 1 }
|
|
743
|
+
}
|
|
744
|
+
).then(() => {
|
|
745
|
+
})
|
|
746
|
+
);
|
|
747
|
+
}
|
|
748
|
+
async storePuzzleCaptchaRecord(challenge, components, targetX, targetY, originX, originY, tolerance, providerSignature, ipAddress, headers, ja4, sessionId, ipInfo) {
|
|
749
|
+
const tables = this.getTables();
|
|
750
|
+
const puzzleCaptchaRecord = {
|
|
751
|
+
challenge,
|
|
752
|
+
userAccount: components.userAccount,
|
|
753
|
+
dappAccount: components.dappAccount,
|
|
754
|
+
requestedAtTimestamp: new Date(components.requestedAtTimestamp),
|
|
755
|
+
ipAddress,
|
|
756
|
+
headers,
|
|
757
|
+
ja4,
|
|
758
|
+
result: { status: types.CaptchaStatus.pending },
|
|
759
|
+
userSubmitted: false,
|
|
760
|
+
serverChecked: false,
|
|
761
|
+
targetX,
|
|
762
|
+
targetY,
|
|
763
|
+
originX,
|
|
764
|
+
originY,
|
|
765
|
+
tolerance,
|
|
766
|
+
providerSignature,
|
|
767
|
+
lastUpdatedTimestamp: /* @__PURE__ */ new Date(),
|
|
768
|
+
pendingStage: true,
|
|
769
|
+
sessionId,
|
|
770
|
+
ipInfo
|
|
771
|
+
};
|
|
772
|
+
try {
|
|
773
|
+
await tables.puzzlecaptcha.create(puzzleCaptchaRecord);
|
|
774
|
+
this.logger.info(() => ({
|
|
775
|
+
data: {
|
|
776
|
+
challenge,
|
|
777
|
+
countryCode: ipInfo?.isValid ? ipInfo.countryCode : void 0
|
|
778
|
+
},
|
|
779
|
+
msg: "PuzzleCaptcha record added successfully"
|
|
780
|
+
}));
|
|
781
|
+
} catch (error) {
|
|
782
|
+
const err = new common.ProsopoDBError("DATABASE.CAPTCHA_UPDATE_FAILED", {
|
|
783
|
+
context: {
|
|
784
|
+
error,
|
|
785
|
+
challenge,
|
|
786
|
+
ipInfo
|
|
787
|
+
},
|
|
788
|
+
logger: this.logger
|
|
789
|
+
});
|
|
790
|
+
this.logger.error(() => ({
|
|
791
|
+
err: error,
|
|
792
|
+
msg: "Failed to add PuzzleCaptcha record"
|
|
793
|
+
}));
|
|
794
|
+
throw err;
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
/**
|
|
798
|
+
* @description Retrieves a Puzzle Captcha record by its challenge string.
|
|
799
|
+
* @param {string} challenge The challenge string to search for.
|
|
800
|
+
* @returns {Promise<PuzzleCaptchaRecord | null>} A promise that resolves with the found record or null if not found.
|
|
801
|
+
*/
|
|
802
|
+
async getPuzzleCaptchaRecordByChallenge(challenge) {
|
|
803
|
+
if (!this.tables) {
|
|
804
|
+
throw new common.ProsopoDBError("DATABASE.DATABASE_UNDEFINED", {
|
|
805
|
+
context: {
|
|
806
|
+
failedFuncName: this.getPuzzleCaptchaRecordByChallenge.name
|
|
807
|
+
},
|
|
808
|
+
logger: this.logger
|
|
809
|
+
});
|
|
810
|
+
}
|
|
811
|
+
try {
|
|
812
|
+
const filter = { challenge };
|
|
813
|
+
const record = await this.tables.puzzlecaptcha.findOne(filter, {
|
|
814
|
+
challenge: 1,
|
|
815
|
+
userAccount: 1,
|
|
816
|
+
dappAccount: 1,
|
|
817
|
+
requestedAtTimestamp: 1,
|
|
818
|
+
ipAddress: 1,
|
|
819
|
+
headers: 1,
|
|
820
|
+
ja4: 1,
|
|
821
|
+
result: 1,
|
|
822
|
+
targetX: 1,
|
|
823
|
+
targetY: 1,
|
|
824
|
+
originX: 1,
|
|
825
|
+
originY: 1,
|
|
826
|
+
tolerance: 1,
|
|
827
|
+
puzzleEvents: 1,
|
|
828
|
+
sessionId: 1,
|
|
829
|
+
ipInfo: 1,
|
|
830
|
+
deviceCapability: 1,
|
|
831
|
+
behavioralDataPacked: 1,
|
|
832
|
+
serverChecked: 1,
|
|
833
|
+
userSubmitted: 1,
|
|
834
|
+
coords: 1
|
|
835
|
+
}).lean();
|
|
836
|
+
if (record) {
|
|
837
|
+
this.logger.info(() => ({
|
|
838
|
+
data: { challenge },
|
|
839
|
+
msg: "PuzzleCaptcha record retrieved successfully"
|
|
840
|
+
}));
|
|
841
|
+
return record;
|
|
842
|
+
}
|
|
843
|
+
this.logger.info(() => ({
|
|
844
|
+
data: { challenge },
|
|
845
|
+
msg: "No PuzzleCaptcha record found"
|
|
846
|
+
}));
|
|
847
|
+
return null;
|
|
848
|
+
} catch (error) {
|
|
849
|
+
const err = new common.ProsopoDBError("DATABASE.CAPTCHA_GET_FAILED", {
|
|
850
|
+
context: { error, challenge },
|
|
851
|
+
logger: this.logger
|
|
852
|
+
});
|
|
853
|
+
this.logger.error(() => ({
|
|
854
|
+
err,
|
|
855
|
+
msg: "Failed to retrieve PuzzleCaptcha record"
|
|
856
|
+
}));
|
|
857
|
+
throw err;
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
/**
|
|
861
|
+
* @description Updates a Puzzle Captcha record result in the database.
|
|
862
|
+
* @param {string} challenge The challenge string of the captcha to be updated.
|
|
863
|
+
* @param result
|
|
864
|
+
* @param serverChecked
|
|
865
|
+
* @param userSubmitted
|
|
866
|
+
* @param userSignature
|
|
867
|
+
* @param coords
|
|
868
|
+
* @param lastUpdatedTimestamp
|
|
869
|
+
* @returns {Promise<void>} A promise that resolves when the record is updated.
|
|
870
|
+
*/
|
|
871
|
+
async updatePuzzleCaptchaRecordResult(challenge, result, serverChecked = false, userSubmitted = false, userSignature, coords, lastUpdatedTimestamp) {
|
|
872
|
+
const tables = this.getTables();
|
|
873
|
+
const timestamp = lastUpdatedTimestamp ?? /* @__PURE__ */ new Date();
|
|
874
|
+
const update = {
|
|
875
|
+
result,
|
|
876
|
+
serverChecked,
|
|
877
|
+
userSubmitted,
|
|
878
|
+
userSignature,
|
|
879
|
+
lastUpdatedTimestamp: timestamp,
|
|
880
|
+
pendingStage: true,
|
|
881
|
+
...coords && { coords }
|
|
882
|
+
};
|
|
883
|
+
try {
|
|
884
|
+
const updateResult = await tables.puzzlecaptcha.updateOne(
|
|
885
|
+
{ challenge },
|
|
886
|
+
{
|
|
887
|
+
$set: update
|
|
888
|
+
}
|
|
889
|
+
);
|
|
890
|
+
if (updateResult.matchedCount === 0) {
|
|
891
|
+
const err = new common.ProsopoDBError("DATABASE.CAPTCHA_GET_FAILED", {
|
|
892
|
+
context: {
|
|
893
|
+
challenge,
|
|
894
|
+
...update
|
|
895
|
+
},
|
|
896
|
+
logger: this.logger
|
|
897
|
+
});
|
|
898
|
+
this.logger.info(() => ({
|
|
899
|
+
err,
|
|
900
|
+
msg: "No PuzzleCaptcha record found to update"
|
|
901
|
+
}));
|
|
902
|
+
throw err;
|
|
903
|
+
}
|
|
904
|
+
this.logger.info(() => ({
|
|
905
|
+
data: {
|
|
906
|
+
challenge,
|
|
907
|
+
...update
|
|
908
|
+
},
|
|
909
|
+
msg: "PuzzleCaptcha record updated successfully"
|
|
910
|
+
}));
|
|
911
|
+
} catch (error) {
|
|
912
|
+
const err = new common.ProsopoDBError("DATABASE.CAPTCHA_UPDATE_FAILED", {
|
|
913
|
+
context: {
|
|
914
|
+
error,
|
|
915
|
+
challenge,
|
|
916
|
+
...update
|
|
917
|
+
},
|
|
918
|
+
logger: this.logger
|
|
919
|
+
});
|
|
920
|
+
this.logger.error(() => ({
|
|
921
|
+
err,
|
|
922
|
+
msg: "Failed to update PuzzleCaptcha record"
|
|
923
|
+
}));
|
|
924
|
+
throw err;
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
async updatePuzzleCaptchaRecord(challenge, updates) {
|
|
928
|
+
const tables = this.getTables();
|
|
929
|
+
await tables.puzzlecaptcha.updateOne(
|
|
930
|
+
{ challenge },
|
|
931
|
+
{ $set: { ...updates, pendingStage: true } },
|
|
654
932
|
{ upsert: false }
|
|
655
933
|
);
|
|
656
934
|
}
|
|
@@ -662,44 +940,38 @@ class ProviderDatabase extends mongo.MongoDatabase {
|
|
|
662
940
|
return docs || [];
|
|
663
941
|
}
|
|
664
942
|
/** @description Get Dapp User captcha commitments from the commitments table that have not been counted towards the
|
|
665
|
-
* client's total
|
|
943
|
+
* client's total.
|
|
944
|
+
*
|
|
945
|
+
* Served by the `pendingStage_partial` index. Records have
|
|
946
|
+
* `pendingStage: true` set on insert and on every mutation (see
|
|
947
|
+
* `updateDappUserCommitment`, `markDappUserCommitmentsChecked`,
|
|
948
|
+
* `approveDappUserCommitment`, `disapproveDappUserCommitment`,
|
|
949
|
+
* `storePendingImageCommitment`). `markDappUserCommitmentsStored` clears
|
|
950
|
+
* the flag after a successful stage, guarded by `lastUpdatedTimestamp`
|
|
951
|
+
* so an in-flight update isn't lost.
|
|
666
952
|
*/
|
|
667
953
|
async getUnstoredDappUserCommitments(limit = 1e3, skip = 0) {
|
|
668
|
-
const
|
|
669
|
-
const docs = await this.tables?.commitment.aggregate([
|
|
670
|
-
{
|
|
671
|
-
$match: {
|
|
672
|
-
$or: [
|
|
673
|
-
filterNoStoredTimestamp,
|
|
674
|
-
{
|
|
675
|
-
$expr: {
|
|
676
|
-
$lt: ["$storedAtTimestamp", "$lastUpdatedTimestamp"]
|
|
677
|
-
}
|
|
678
|
-
}
|
|
679
|
-
]
|
|
680
|
-
}
|
|
681
|
-
},
|
|
682
|
-
{
|
|
683
|
-
$sort: { _id: 1 }
|
|
684
|
-
},
|
|
685
|
-
{
|
|
686
|
-
$skip: skip
|
|
687
|
-
},
|
|
688
|
-
{
|
|
689
|
-
$limit: limit
|
|
690
|
-
}
|
|
691
|
-
]);
|
|
954
|
+
const docs = await this.tables?.commitment.find({ pendingStage: true }).sort({ _id: 1 }).skip(skip).limit(limit).lean();
|
|
692
955
|
return docs || [];
|
|
693
956
|
}
|
|
694
|
-
/** @description Mark a list of captcha commits as stored
|
|
957
|
+
/** @description Mark a list of captcha commits as stored.
|
|
958
|
+
*
|
|
959
|
+
* `asOfTimestamp` defaults to "now" but the sweep should pass the time
|
|
960
|
+
* at which it fetched the batch. The lastUpdatedTimestamp guard prevents
|
|
961
|
+
* us from clearing pendingStage on a record that was mutated between
|
|
962
|
+
* fetch and mark-stored — those records will leave pendingStage set so
|
|
963
|
+
* the next sweep picks them up.
|
|
695
964
|
*/
|
|
696
|
-
async markDappUserCommitmentsStored(commitmentIds) {
|
|
697
|
-
const updateDoc = {
|
|
698
|
-
storedAtTimestamp: /* @__PURE__ */ new Date()
|
|
699
|
-
};
|
|
965
|
+
async markDappUserCommitmentsStored(commitmentIds, asOfTimestamp = /* @__PURE__ */ new Date()) {
|
|
700
966
|
await this.tables?.commitment.updateMany(
|
|
701
|
-
{
|
|
702
|
-
|
|
967
|
+
{
|
|
968
|
+
id: { $in: commitmentIds },
|
|
969
|
+
lastUpdatedTimestamp: { $lte: asOfTimestamp }
|
|
970
|
+
},
|
|
971
|
+
{
|
|
972
|
+
$set: { storedAtTimestamp: /* @__PURE__ */ new Date() },
|
|
973
|
+
$unset: { pendingStage: 1 }
|
|
974
|
+
},
|
|
703
975
|
{ upsert: false }
|
|
704
976
|
);
|
|
705
977
|
}
|
|
@@ -708,7 +980,8 @@ class ProviderDatabase extends mongo.MongoDatabase {
|
|
|
708
980
|
async markDappUserCommitmentsChecked(commitmentIds) {
|
|
709
981
|
const updateDoc = {
|
|
710
982
|
[types.StoredStatusNames.serverChecked]: true,
|
|
711
|
-
lastUpdatedTimestamp: /* @__PURE__ */ new Date()
|
|
983
|
+
lastUpdatedTimestamp: /* @__PURE__ */ new Date(),
|
|
984
|
+
pendingStage: true
|
|
712
985
|
};
|
|
713
986
|
await this.tables?.commitment.updateMany(
|
|
714
987
|
{ id: { $in: commitmentIds } },
|
|
@@ -720,7 +993,11 @@ class ProviderDatabase extends mongo.MongoDatabase {
|
|
|
720
993
|
*/
|
|
721
994
|
async updateDappUserCommitment(commitmentId, updates) {
|
|
722
995
|
const filter = { id: commitmentId };
|
|
723
|
-
await this.tables?.commitment.updateOne(filter,
|
|
996
|
+
await this.tables?.commitment.updateOne(filter, {
|
|
997
|
+
...updates,
|
|
998
|
+
lastUpdatedAtTimestamp: /* @__PURE__ */ new Date(),
|
|
999
|
+
pendingStage: true
|
|
1000
|
+
});
|
|
724
1001
|
}
|
|
725
1002
|
/**
|
|
726
1003
|
* @description Get Dapp User PoW captcha commitments that have not been counted towards the client's total
|
|
@@ -729,54 +1006,25 @@ class ProviderDatabase extends mongo.MongoDatabase {
|
|
|
729
1006
|
* @returns {Promise<PoWCaptchaRecord[]>} Array of PoW captcha records
|
|
730
1007
|
*/
|
|
731
1008
|
async getUnstoredDappUserPoWCommitments(limit = 1e3, skip = 0) {
|
|
732
|
-
const
|
|
733
|
-
const docs = await this.tables?.powcaptcha.aggregate([
|
|
734
|
-
{
|
|
735
|
-
$match: {
|
|
736
|
-
$or: [
|
|
737
|
-
filterNoStoredTimestamp,
|
|
738
|
-
{
|
|
739
|
-
$expr: {
|
|
740
|
-
$lt: [
|
|
741
|
-
{
|
|
742
|
-
$convert: {
|
|
743
|
-
input: "$storedAtTimestamp",
|
|
744
|
-
to: "date"
|
|
745
|
-
}
|
|
746
|
-
},
|
|
747
|
-
{
|
|
748
|
-
$convert: {
|
|
749
|
-
input: "$lastUpdatedTimestamp",
|
|
750
|
-
to: "date"
|
|
751
|
-
}
|
|
752
|
-
}
|
|
753
|
-
]
|
|
754
|
-
}
|
|
755
|
-
}
|
|
756
|
-
]
|
|
757
|
-
}
|
|
758
|
-
},
|
|
759
|
-
{
|
|
760
|
-
$sort: { _id: 1 }
|
|
761
|
-
},
|
|
762
|
-
{
|
|
763
|
-
$skip: skip
|
|
764
|
-
},
|
|
765
|
-
{
|
|
766
|
-
$limit: limit
|
|
767
|
-
}
|
|
768
|
-
]);
|
|
1009
|
+
const docs = await this.tables?.powcaptcha.find({ pendingStage: true }).sort({ _id: 1 }).skip(skip).limit(limit).lean();
|
|
769
1010
|
return docs || [];
|
|
770
1011
|
}
|
|
771
|
-
/** @description Mark a list of PoW captcha commits as stored
|
|
1012
|
+
/** @description Mark a list of PoW captcha commits as stored.
|
|
1013
|
+
*
|
|
1014
|
+
* `asOfTimestamp` defaults to "now" but the sweep should pass the time
|
|
1015
|
+
* at which it fetched the batch. See markDappUserCommitmentsStored for
|
|
1016
|
+
* the guard rationale.
|
|
772
1017
|
*/
|
|
773
|
-
async markDappUserPoWCommitmentsStored(challenges) {
|
|
774
|
-
const updateDoc = {
|
|
775
|
-
storedAtTimestamp: /* @__PURE__ */ new Date()
|
|
776
|
-
};
|
|
1018
|
+
async markDappUserPoWCommitmentsStored(challenges, asOfTimestamp = /* @__PURE__ */ new Date()) {
|
|
777
1019
|
await this.tables?.powcaptcha.updateMany(
|
|
778
|
-
{
|
|
779
|
-
|
|
1020
|
+
{
|
|
1021
|
+
challenge: { $in: challenges },
|
|
1022
|
+
lastUpdatedTimestamp: { $lte: asOfTimestamp }
|
|
1023
|
+
},
|
|
1024
|
+
{
|
|
1025
|
+
$set: { storedAtTimestamp: /* @__PURE__ */ new Date() },
|
|
1026
|
+
$unset: { pendingStage: 1 }
|
|
1027
|
+
},
|
|
780
1028
|
{ upsert: false }
|
|
781
1029
|
);
|
|
782
1030
|
}
|
|
@@ -785,7 +1033,8 @@ class ProviderDatabase extends mongo.MongoDatabase {
|
|
|
785
1033
|
async markDappUserPoWCommitmentsChecked(challenges) {
|
|
786
1034
|
const updateDoc = {
|
|
787
1035
|
[types.StoredStatusNames.serverChecked]: true,
|
|
788
|
-
lastUpdatedTimestamp: /* @__PURE__ */ new Date()
|
|
1036
|
+
lastUpdatedTimestamp: /* @__PURE__ */ new Date(),
|
|
1037
|
+
pendingStage: true
|
|
789
1038
|
};
|
|
790
1039
|
await this.tables?.powcaptcha.updateMany(
|
|
791
1040
|
{ challenge: { $in: challenges } },
|
|
@@ -803,7 +1052,26 @@ class ProviderDatabase extends mongo.MongoDatabase {
|
|
|
803
1052
|
this.logger.debug(() => ({
|
|
804
1053
|
data: { action: "storing", sessionRecord }
|
|
805
1054
|
}));
|
|
806
|
-
|
|
1055
|
+
const recordWithFlag = {
|
|
1056
|
+
...sessionRecord,
|
|
1057
|
+
lastUpdatedTimestamp: sessionRecord.lastUpdatedTimestamp ?? sessionRecord.createdAt,
|
|
1058
|
+
pendingStage: true
|
|
1059
|
+
};
|
|
1060
|
+
await this.tables.session.create(recordWithFlag);
|
|
1061
|
+
this.centralStreamer?.streamSessionRecord(
|
|
1062
|
+
recordWithFlag,
|
|
1063
|
+
(ts) => this.tables.session.updateOne(
|
|
1064
|
+
{
|
|
1065
|
+
sessionId: sessionRecord.sessionId,
|
|
1066
|
+
lastUpdatedTimestamp: { $lte: ts }
|
|
1067
|
+
},
|
|
1068
|
+
{
|
|
1069
|
+
$set: { storedAtTimestamp: ts },
|
|
1070
|
+
$unset: { pendingStage: 1 }
|
|
1071
|
+
}
|
|
1072
|
+
).then(() => {
|
|
1073
|
+
})
|
|
1074
|
+
);
|
|
807
1075
|
} catch (err) {
|
|
808
1076
|
throw new common.ProsopoDBError("DATABASE.SESSION_STORE_FAILED", {
|
|
809
1077
|
context: { error: err, sessionId: sessionRecord.sessionId },
|
|
@@ -816,7 +1084,14 @@ class ProviderDatabase extends mongo.MongoDatabase {
|
|
|
816
1084
|
*/
|
|
817
1085
|
async getSessionRecordBySessionId(sessionId) {
|
|
818
1086
|
const filter = { sessionId };
|
|
819
|
-
const doc = await this.tables.session.findOne(filter
|
|
1087
|
+
const doc = await this.tables.session.findOne(filter, {
|
|
1088
|
+
sessionId: 1,
|
|
1089
|
+
ipInfo: 1,
|
|
1090
|
+
scoreComponents: 1,
|
|
1091
|
+
webView: 1,
|
|
1092
|
+
reason: 1,
|
|
1093
|
+
decryptedHeadHash: 1
|
|
1094
|
+
}).lean();
|
|
820
1095
|
return doc || void 0;
|
|
821
1096
|
}
|
|
822
1097
|
/**
|
|
@@ -843,7 +1118,8 @@ class ProviderDatabase extends mongo.MongoDatabase {
|
|
|
843
1118
|
try {
|
|
844
1119
|
const session = await this.tables.session.findOneAndUpdate(filter, {
|
|
845
1120
|
deleted: true,
|
|
846
|
-
lastUpdatedTimestamp: /* @__PURE__ */ new Date()
|
|
1121
|
+
lastUpdatedTimestamp: /* @__PURE__ */ new Date(),
|
|
1122
|
+
pendingStage: true
|
|
847
1123
|
}).lean();
|
|
848
1124
|
return session || void 0;
|
|
849
1125
|
} catch (err) {
|
|
@@ -853,6 +1129,74 @@ class ProviderDatabase extends mongo.MongoDatabase {
|
|
|
853
1129
|
});
|
|
854
1130
|
}
|
|
855
1131
|
}
|
|
1132
|
+
/**
|
|
1133
|
+
* Update a session record by sessionId. Pure Mongo write — callers in
|
|
1134
|
+
* the provider package are responsible for refreshing the Redis cache
|
|
1135
|
+
* via `RedisWriteQueue.patchCachedSession`, matching the existing
|
|
1136
|
+
* caller-side caching pattern (see `frictionlessTasks.createSession`).
|
|
1137
|
+
*/
|
|
1138
|
+
async updateSessionRecord(sessionId, updates, streamToCentral) {
|
|
1139
|
+
try {
|
|
1140
|
+
await this.tables.session.updateOne(
|
|
1141
|
+
{ sessionId },
|
|
1142
|
+
{
|
|
1143
|
+
$set: {
|
|
1144
|
+
...updates,
|
|
1145
|
+
lastUpdatedTimestamp: /* @__PURE__ */ new Date(),
|
|
1146
|
+
pendingStage: true
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
);
|
|
1150
|
+
if (streamToCentral && this.centralStreamer) {
|
|
1151
|
+
const streamer = this.centralStreamer;
|
|
1152
|
+
const markStored = (ts) => this.tables.session.updateOne(
|
|
1153
|
+
{ sessionId, lastUpdatedTimestamp: { $lte: ts } },
|
|
1154
|
+
{
|
|
1155
|
+
$set: { storedAtTimestamp: ts },
|
|
1156
|
+
$unset: { pendingStage: 1 }
|
|
1157
|
+
}
|
|
1158
|
+
).then(() => {
|
|
1159
|
+
});
|
|
1160
|
+
this.tables.session.findOne({ sessionId }).lean().then((record) => {
|
|
1161
|
+
if (record) {
|
|
1162
|
+
streamer.streamSessionRecord(record, markStored);
|
|
1163
|
+
}
|
|
1164
|
+
}).catch(() => {
|
|
1165
|
+
});
|
|
1166
|
+
}
|
|
1167
|
+
} catch (err) {
|
|
1168
|
+
throw new common.ProsopoDBError("DATABASE.SESSION_GET_FAILED", {
|
|
1169
|
+
context: { error: err, sessionId },
|
|
1170
|
+
logger: this.logger
|
|
1171
|
+
});
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
/**
|
|
1175
|
+
* Atomically record SIMD CPU fingerprint readings on the session — only
|
|
1176
|
+
* if they aren't already present. Uses a Mongo aggregation-pipeline
|
|
1177
|
+
* update so `simdReadings` and `simdReadingsStage` are set together,
|
|
1178
|
+
* first-hop-wins. Pure Mongo write — cache refresh is the caller's
|
|
1179
|
+
* responsibility via `RedisWriteQueue.patchCachedSimdReadingsIfAbsent`.
|
|
1180
|
+
*/
|
|
1181
|
+
async recordSessionSimdReadingsIfAbsent(sessionId, readings, stage) {
|
|
1182
|
+
try {
|
|
1183
|
+
await this.tables.session.updateOne({ sessionId }, [
|
|
1184
|
+
{
|
|
1185
|
+
$set: {
|
|
1186
|
+
simdReadings: { $ifNull: ["$simdReadings", readings] },
|
|
1187
|
+
simdReadingsStage: { $ifNull: ["$simdReadingsStage", stage] },
|
|
1188
|
+
lastUpdatedTimestamp: /* @__PURE__ */ new Date(),
|
|
1189
|
+
pendingStage: true
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
]);
|
|
1193
|
+
} catch (err) {
|
|
1194
|
+
throw new common.ProsopoDBError("DATABASE.SESSION_GET_FAILED", {
|
|
1195
|
+
context: { error: err, sessionId, stage },
|
|
1196
|
+
logger: this.logger
|
|
1197
|
+
});
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
856
1200
|
/**
|
|
857
1201
|
* Get an active session by user IP hash
|
|
858
1202
|
* @param userSitekeyIpHash The hash of user, IP and sitekey combination
|
|
@@ -877,64 +1221,43 @@ class ProviderDatabase extends mongo.MongoDatabase {
|
|
|
877
1221
|
}
|
|
878
1222
|
}
|
|
879
1223
|
/** Get unstored session records
|
|
880
|
-
* @description Get session records that have not been stored yet
|
|
1224
|
+
* @description Get session records that have not been stored yet.
|
|
1225
|
+
*
|
|
1226
|
+
* Served by the `pendingStage_partial` index — see
|
|
1227
|
+
* `getUnstoredDappUserCommitments` for the lifecycle of the flag.
|
|
1228
|
+
* `checkAndRemoveSession` also flips the flag so consumed sessions
|
|
1229
|
+
* propagate to the central DB via the next sweep.
|
|
881
1230
|
* @param limit
|
|
882
1231
|
* @param skip
|
|
883
1232
|
*/
|
|
884
1233
|
getUnstoredSessionRecords(limit = 1e3, skip = 0) {
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
to: "date"
|
|
898
|
-
}
|
|
899
|
-
},
|
|
900
|
-
{
|
|
901
|
-
$convert: {
|
|
902
|
-
input: "$lastUpdatedTimestamp",
|
|
903
|
-
to: "date"
|
|
904
|
-
}
|
|
905
|
-
}
|
|
906
|
-
]
|
|
907
|
-
}
|
|
908
|
-
}
|
|
909
|
-
]
|
|
910
|
-
}
|
|
911
|
-
},
|
|
1234
|
+
return Promise.resolve(this.tables?.session).then(
|
|
1235
|
+
(tbl) => tbl?.find({ pendingStage: true }).sort({ _id: 1 }).skip(skip).limit(limit).lean()
|
|
1236
|
+
).then((docs) => docs || []);
|
|
1237
|
+
}
|
|
1238
|
+
/** Mark a list of session records as stored.
|
|
1239
|
+
*
|
|
1240
|
+
* `asOfTimestamp` defaults to "now" but the sweep should pass the time
|
|
1241
|
+
* at which it fetched the batch. See markDappUserCommitmentsStored for
|
|
1242
|
+
* the guard rationale.
|
|
1243
|
+
*/
|
|
1244
|
+
async markSessionRecordsStored(sessionIds, asOfTimestamp = /* @__PURE__ */ new Date()) {
|
|
1245
|
+
await this.tables?.session.updateMany(
|
|
912
1246
|
{
|
|
913
|
-
|
|
1247
|
+
sessionId: { $in: sessionIds },
|
|
1248
|
+
lastUpdatedTimestamp: { $lte: asOfTimestamp }
|
|
914
1249
|
},
|
|
915
1250
|
{
|
|
916
|
-
$
|
|
1251
|
+
$set: { storedAtTimestamp: /* @__PURE__ */ new Date() },
|
|
1252
|
+
$unset: { pendingStage: 1 }
|
|
917
1253
|
},
|
|
918
|
-
{
|
|
919
|
-
$limit: limit
|
|
920
|
-
}
|
|
921
|
-
]).then((docs) => docs || []);
|
|
922
|
-
}
|
|
923
|
-
/** Mark a list of session records as stored */
|
|
924
|
-
async markSessionRecordsStored(sessionIds) {
|
|
925
|
-
const updateDoc = {
|
|
926
|
-
storedAtTimestamp: /* @__PURE__ */ new Date()
|
|
927
|
-
};
|
|
928
|
-
await this.tables?.session.updateMany(
|
|
929
|
-
{ sessionId: { $in: sessionIds } },
|
|
930
|
-
{ $set: updateDoc },
|
|
931
1254
|
{ upsert: false }
|
|
932
1255
|
);
|
|
933
1256
|
}
|
|
934
1257
|
/**
|
|
935
1258
|
* @description Store a Dapp User's pending record
|
|
936
1259
|
*/
|
|
937
|
-
async storePendingImageCommitment(userAccount, requestHash, salt, deadlineTimestamp, requestedAtTimestamp, ipAddress, threshold, sessionId) {
|
|
1260
|
+
async storePendingImageCommitment(userAccount, requestHash, salt, deadlineTimestamp, requestedAtTimestamp, ipAddress, threshold, sessionId, ipInfo) {
|
|
938
1261
|
if (!is.isHex(requestHash)) {
|
|
939
1262
|
throw new common.ProsopoDBError("DATABASE.INVALID_HASH", {
|
|
940
1263
|
context: {
|
|
@@ -944,24 +1267,47 @@ class ProviderDatabase extends mongo.MongoDatabase {
|
|
|
944
1267
|
});
|
|
945
1268
|
}
|
|
946
1269
|
const pendingRecord = {
|
|
947
|
-
|
|
1270
|
+
userAccount,
|
|
948
1271
|
pending: true,
|
|
949
1272
|
salt,
|
|
950
1273
|
requestHash,
|
|
951
1274
|
deadlineTimestamp,
|
|
952
|
-
requestedAtTimestamp
|
|
1275
|
+
requestedAtTimestamp,
|
|
953
1276
|
ipAddress,
|
|
954
1277
|
sessionId,
|
|
955
|
-
threshold
|
|
1278
|
+
threshold,
|
|
1279
|
+
ipInfo,
|
|
1280
|
+
// Deliberately NOT setting pendingStage here. Placeholder
|
|
1281
|
+
// records have id: "" until the user submits a solution; if we
|
|
1282
|
+
// flag them, the sweep picks them up via the partial index but
|
|
1283
|
+
// then `markDappUserCommitmentsStored` runs
|
|
1284
|
+
// `{ id: { $in: ["", ...] } }` which collapses to a single ""
|
|
1285
|
+
// bound and scans every empty-id row via the `id_-1` index —
|
|
1286
|
+
// turning a cheap sweep into a fresh cache evictor. The real
|
|
1287
|
+
// commitment only gets `pendingStage: true` once `id` is
|
|
1288
|
+
// populated by approve/disapprove, at which point staging it is
|
|
1289
|
+
// meaningful.
|
|
1290
|
+
// Placeholder fields required by schema but not needed for pending state
|
|
1291
|
+
dappAccount: "",
|
|
1292
|
+
providerAccount: "",
|
|
1293
|
+
datasetId: "",
|
|
1294
|
+
id: "",
|
|
1295
|
+
// id is populated by the user's solution record when the user submits a solution, so we can leave it blank here
|
|
1296
|
+
result: { status: types.CaptchaStatus.pending },
|
|
1297
|
+
headers: {},
|
|
1298
|
+
ja4: "",
|
|
1299
|
+
userSignature: "",
|
|
1300
|
+
userSubmitted: false,
|
|
1301
|
+
serverChecked: false
|
|
956
1302
|
};
|
|
957
|
-
await this.tables?.
|
|
1303
|
+
await this.tables?.commitment.updateOne(
|
|
958
1304
|
{ requestHash },
|
|
959
1305
|
{ $set: pendingRecord },
|
|
960
1306
|
{ upsert: true }
|
|
961
1307
|
);
|
|
962
1308
|
}
|
|
963
1309
|
/**
|
|
964
|
-
* @description Get a
|
|
1310
|
+
* @description Get a user's pending record
|
|
965
1311
|
*/
|
|
966
1312
|
async getPendingImageCommitment(requestHash) {
|
|
967
1313
|
if (!is.isHex(requestHash)) {
|
|
@@ -972,12 +1318,22 @@ class ProviderDatabase extends mongo.MongoDatabase {
|
|
|
972
1318
|
}
|
|
973
1319
|
});
|
|
974
1320
|
}
|
|
975
|
-
const
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
1321
|
+
const doc = await this.tables?.commitment.findOne({
|
|
1322
|
+
requestHash,
|
|
1323
|
+
pending: true
|
|
1324
|
+
}).lean();
|
|
979
1325
|
if (doc) {
|
|
980
|
-
return
|
|
1326
|
+
return {
|
|
1327
|
+
dappAccount: doc.dappAccount,
|
|
1328
|
+
pending: doc.pending,
|
|
1329
|
+
salt: doc.salt,
|
|
1330
|
+
requestHash: doc.requestHash,
|
|
1331
|
+
deadlineTimestamp: doc.deadlineTimestamp,
|
|
1332
|
+
requestedAtTimestamp: doc.requestedAtTimestamp,
|
|
1333
|
+
ipAddress: doc.ipAddress,
|
|
1334
|
+
sessionId: doc.sessionId,
|
|
1335
|
+
threshold: doc.threshold
|
|
1336
|
+
};
|
|
981
1337
|
}
|
|
982
1338
|
throw new common.ProsopoDBError("DATABASE.PENDING_RECORD_NOT_FOUND", {
|
|
983
1339
|
context: {
|
|
@@ -998,17 +1354,13 @@ class ProviderDatabase extends mongo.MongoDatabase {
|
|
|
998
1354
|
}
|
|
999
1355
|
});
|
|
1000
1356
|
}
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
};
|
|
1004
|
-
await this.tables?.pending.updateOne(
|
|
1005
|
-
filter,
|
|
1357
|
+
await this.tables?.commitment.updateOne(
|
|
1358
|
+
{ requestHash },
|
|
1006
1359
|
{
|
|
1007
1360
|
$set: {
|
|
1008
|
-
|
|
1361
|
+
pending: false
|
|
1009
1362
|
}
|
|
1010
|
-
}
|
|
1011
|
-
{ upsert: true }
|
|
1363
|
+
}
|
|
1012
1364
|
);
|
|
1013
1365
|
}
|
|
1014
1366
|
/**
|
|
@@ -1109,7 +1461,7 @@ class ProviderDatabase extends mongo.MongoDatabase {
|
|
|
1109
1461
|
const filter = {
|
|
1110
1462
|
commitmentId
|
|
1111
1463
|
};
|
|
1112
|
-
const project = {
|
|
1464
|
+
const project = { _id: 0 };
|
|
1113
1465
|
const cursor = this.tables?.usersolution?.findOne(filter, project).lean();
|
|
1114
1466
|
const doc = await cursor;
|
|
1115
1467
|
if (doc) {
|
|
@@ -1125,7 +1477,18 @@ class ProviderDatabase extends mongo.MongoDatabase {
|
|
|
1125
1477
|
*/
|
|
1126
1478
|
async getDappUserCommitmentById(commitmentId) {
|
|
1127
1479
|
const filter = { id: commitmentId };
|
|
1128
|
-
const commitmentCursor = this.tables?.commitment?.findOne(filter
|
|
1480
|
+
const commitmentCursor = this.tables?.commitment?.findOne(filter, {
|
|
1481
|
+
id: 1,
|
|
1482
|
+
result: 1,
|
|
1483
|
+
serverChecked: 1,
|
|
1484
|
+
requestedAtTimestamp: 1,
|
|
1485
|
+
ipAddress: 1,
|
|
1486
|
+
sessionId: 1,
|
|
1487
|
+
userAccount: 1,
|
|
1488
|
+
dappAccount: 1,
|
|
1489
|
+
headers: 1,
|
|
1490
|
+
ipInfo: 1
|
|
1491
|
+
}).lean();
|
|
1129
1492
|
const doc = await commitmentCursor;
|
|
1130
1493
|
return doc ? doc : void 0;
|
|
1131
1494
|
}
|
|
@@ -1139,7 +1502,10 @@ class ProviderDatabase extends mongo.MongoDatabase {
|
|
|
1139
1502
|
userAccount,
|
|
1140
1503
|
dappAccount
|
|
1141
1504
|
};
|
|
1142
|
-
const project = {
|
|
1505
|
+
const project = {
|
|
1506
|
+
_id: 0,
|
|
1507
|
+
result: 1
|
|
1508
|
+
};
|
|
1143
1509
|
const sort = { sort: { _id: -1 } };
|
|
1144
1510
|
const docs = await this.tables?.commitment?.find(filter, project, sort).lean();
|
|
1145
1511
|
return docs ? docs : [];
|
|
@@ -1155,10 +1521,22 @@ class ProviderDatabase extends mongo.MongoDatabase {
|
|
|
1155
1521
|
const updateDoc = {
|
|
1156
1522
|
result,
|
|
1157
1523
|
lastUpdatedTimestamp: /* @__PURE__ */ new Date(),
|
|
1524
|
+
pendingStage: true,
|
|
1158
1525
|
...coords ? { coords } : {}
|
|
1159
1526
|
};
|
|
1160
1527
|
const filter = { id: commitmentId };
|
|
1161
1528
|
await this.tables?.commitment?.findOneAndUpdate(filter, { $set: updateDoc }, { upsert: false }).lean();
|
|
1529
|
+
this.centralStreamer?.streamImageUpdate(
|
|
1530
|
+
() => this.tables.commitment.findOne({ id: commitmentId }).lean(),
|
|
1531
|
+
(ts) => this.tables.commitment.updateOne(
|
|
1532
|
+
{ id: commitmentId, lastUpdatedTimestamp: { $lte: ts } },
|
|
1533
|
+
{
|
|
1534
|
+
$set: { storedAtTimestamp: ts },
|
|
1535
|
+
$unset: { pendingStage: 1 }
|
|
1536
|
+
}
|
|
1537
|
+
).then(() => {
|
|
1538
|
+
})
|
|
1539
|
+
);
|
|
1162
1540
|
} catch (err) {
|
|
1163
1541
|
throw new common.ProsopoDBError("DATABASE.SOLUTION_APPROVE_FAILED", {
|
|
1164
1542
|
context: { error: err, commitmentId }
|
|
@@ -1176,10 +1554,22 @@ class ProviderDatabase extends mongo.MongoDatabase {
|
|
|
1176
1554
|
const updateDoc = {
|
|
1177
1555
|
result: { status: types.CaptchaStatus.disapproved, reason },
|
|
1178
1556
|
lastUpdatedTimestamp: /* @__PURE__ */ new Date(),
|
|
1557
|
+
pendingStage: true,
|
|
1179
1558
|
...coords ? { coords } : {}
|
|
1180
1559
|
};
|
|
1181
1560
|
const filter = { id: commitmentId };
|
|
1182
1561
|
await this.tables?.commitment?.findOneAndUpdate(filter, { $set: updateDoc }, { upsert: false }).lean();
|
|
1562
|
+
this.centralStreamer?.streamImageUpdate(
|
|
1563
|
+
() => this.tables.commitment.findOne({ id: commitmentId }).lean(),
|
|
1564
|
+
(ts) => this.tables.commitment.updateOne(
|
|
1565
|
+
{ id: commitmentId, lastUpdatedTimestamp: { $lte: ts } },
|
|
1566
|
+
{
|
|
1567
|
+
$set: { storedAtTimestamp: ts },
|
|
1568
|
+
$unset: { pendingStage: 1 }
|
|
1569
|
+
}
|
|
1570
|
+
).then(() => {
|
|
1571
|
+
})
|
|
1572
|
+
);
|
|
1183
1573
|
} catch (err) {
|
|
1184
1574
|
throw new common.ProsopoDBError("DATABASE.SOLUTION_APPROVE_FAILED", {
|
|
1185
1575
|
context: { error: err, commitmentId }
|
|
@@ -1315,6 +1705,17 @@ class ProviderDatabase extends mongo.MongoDatabase {
|
|
|
1315
1705
|
}
|
|
1316
1706
|
await this.tables?.client.bulkWrite(ops);
|
|
1317
1707
|
}
|
|
1708
|
+
/**
|
|
1709
|
+
* @description Remove client records by account
|
|
1710
|
+
*/
|
|
1711
|
+
async removeClientRecords(accounts) {
|
|
1712
|
+
if (!accounts || accounts.length === 0) {
|
|
1713
|
+
return;
|
|
1714
|
+
}
|
|
1715
|
+
await this.tables?.client.deleteMany({
|
|
1716
|
+
account: { $in: accounts }
|
|
1717
|
+
});
|
|
1718
|
+
}
|
|
1318
1719
|
/**
|
|
1319
1720
|
* @description Get all client records
|
|
1320
1721
|
*/
|
|
@@ -1327,7 +1728,11 @@ class ProviderDatabase extends mongo.MongoDatabase {
|
|
|
1327
1728
|
*/
|
|
1328
1729
|
async getClientRecord(account) {
|
|
1329
1730
|
const filter = { account };
|
|
1330
|
-
const doc = await this.tables?.client.findOne(filter
|
|
1731
|
+
const doc = await this.tables?.client.findOne(filter, {
|
|
1732
|
+
account: 1,
|
|
1733
|
+
settings: 1,
|
|
1734
|
+
tier: 1
|
|
1735
|
+
}).lean();
|
|
1331
1736
|
return doc ? doc : void 0;
|
|
1332
1737
|
}
|
|
1333
1738
|
/**
|
|
@@ -1365,6 +1770,97 @@ class ProviderDatabase extends mongo.MongoDatabase {
|
|
|
1365
1770
|
).sort({ createdAt: -1 }).lean();
|
|
1366
1771
|
return (keyRecords || []).map((record) => record.detectorKey);
|
|
1367
1772
|
}
|
|
1773
|
+
/**
|
|
1774
|
+
* Stores a decision machine artifact with a unique scope identifier.
|
|
1775
|
+
*
|
|
1776
|
+
* The combination of scope + dappAccount uniquely identifies a single artifact:
|
|
1777
|
+
* - Global scope: One artifact per provider (dappAccount is null)
|
|
1778
|
+
* - Dapp scope: One artifact per dapp account (dappAccount is specified)
|
|
1779
|
+
*
|
|
1780
|
+
* @param artifact - The decision machine artifact to store
|
|
1781
|
+
*/
|
|
1782
|
+
async upsertDecisionMachineArtifact(artifact) {
|
|
1783
|
+
const now = /* @__PURE__ */ new Date();
|
|
1784
|
+
const dappAccount = artifact.dappAccount ?? null;
|
|
1785
|
+
const filter = {
|
|
1786
|
+
scope: artifact.scope,
|
|
1787
|
+
dappAccount
|
|
1788
|
+
};
|
|
1789
|
+
await this.tables?.decisionMachine.updateOne(
|
|
1790
|
+
filter,
|
|
1791
|
+
{
|
|
1792
|
+
$set: {
|
|
1793
|
+
scope: artifact.scope,
|
|
1794
|
+
dappAccount,
|
|
1795
|
+
runtime: artifact.runtime,
|
|
1796
|
+
language: artifact.language,
|
|
1797
|
+
source: artifact.source,
|
|
1798
|
+
name: artifact.name,
|
|
1799
|
+
version: artifact.version,
|
|
1800
|
+
updatedAt: now
|
|
1801
|
+
},
|
|
1802
|
+
$setOnInsert: {
|
|
1803
|
+
createdAt: now
|
|
1804
|
+
}
|
|
1805
|
+
},
|
|
1806
|
+
{ upsert: true }
|
|
1807
|
+
);
|
|
1808
|
+
}
|
|
1809
|
+
/**
|
|
1810
|
+
* Retrieves a single decision machine artifact by scope and optional dapp account.
|
|
1811
|
+
*
|
|
1812
|
+
* @param scope - The scope level (Global or Dapp)
|
|
1813
|
+
* @param dappAccount - Required for Dapp scope, unused for Global scope
|
|
1814
|
+
* @returns The matching artifact, or undefined if not found
|
|
1815
|
+
*/
|
|
1816
|
+
async getDecisionMachineArtifact(scope, dappAccount) {
|
|
1817
|
+
const filter = {
|
|
1818
|
+
scope,
|
|
1819
|
+
dappAccount: dappAccount ?? null
|
|
1820
|
+
};
|
|
1821
|
+
const doc = await this.tables?.decisionMachine.findOne(filter).lean();
|
|
1822
|
+
return doc ?? void 0;
|
|
1823
|
+
}
|
|
1824
|
+
/**
|
|
1825
|
+
* Retrieves all decision machine artifacts.
|
|
1826
|
+
*
|
|
1827
|
+
* @returns Array of all decision machine artifacts
|
|
1828
|
+
*/
|
|
1829
|
+
async getAllDecisionMachineArtifacts() {
|
|
1830
|
+
const docs = await this.tables?.decisionMachine.find({}).lean();
|
|
1831
|
+
return docs ?? [];
|
|
1832
|
+
}
|
|
1833
|
+
/**
|
|
1834
|
+
* Retrieves a single decision machine artifact by MongoDB ObjectId.
|
|
1835
|
+
*
|
|
1836
|
+
* @param id - The MongoDB ObjectId as a string
|
|
1837
|
+
* @returns The matching artifact, or undefined if not found
|
|
1838
|
+
*/
|
|
1839
|
+
async getDecisionMachineArtifactById(id) {
|
|
1840
|
+
const doc = await this.tables?.decisionMachine.findById(id).lean();
|
|
1841
|
+
return doc ?? void 0;
|
|
1842
|
+
}
|
|
1843
|
+
/**
|
|
1844
|
+
* Removes a decision machine artifact by MongoDB ObjectId.
|
|
1845
|
+
*
|
|
1846
|
+
* @param id - The MongoDB ObjectId as a string
|
|
1847
|
+
* @returns true if deleted, false if not found
|
|
1848
|
+
*/
|
|
1849
|
+
async removeDecisionMachineArtifact(id) {
|
|
1850
|
+
const result = await this.tables?.decisionMachine.deleteOne({
|
|
1851
|
+
_id: id
|
|
1852
|
+
});
|
|
1853
|
+
return (result?.deletedCount ?? 0) > 0;
|
|
1854
|
+
}
|
|
1855
|
+
/**
|
|
1856
|
+
* Removes all decision machine artifacts.
|
|
1857
|
+
*
|
|
1858
|
+
* @returns The number of artifacts deleted
|
|
1859
|
+
*/
|
|
1860
|
+
async removeAllDecisionMachineArtifacts() {
|
|
1861
|
+
const result = await this.tables?.decisionMachine.deleteMany({});
|
|
1862
|
+
return result?.deletedCount ?? 0;
|
|
1863
|
+
}
|
|
1368
1864
|
/**
|
|
1369
1865
|
* @description set client context-specific entropy
|
|
1370
1866
|
*/
|
|
@@ -1407,7 +1903,7 @@ class ProviderDatabase extends mongo.MongoDatabase {
|
|
|
1407
1903
|
},
|
|
1408
1904
|
{
|
|
1409
1905
|
$lookup: {
|
|
1410
|
-
from: "
|
|
1906
|
+
from: "sessions",
|
|
1411
1907
|
localField: "sessionId",
|
|
1412
1908
|
foreignField: "sessionId",
|
|
1413
1909
|
as: "sessionData"
|
|
@@ -1460,5 +1956,35 @@ class ProviderDatabase extends mongo.MongoDatabase {
|
|
|
1460
1956
|
})
|
|
1461
1957
|
)).filter((headHash) => headHash !== void 0);
|
|
1462
1958
|
}
|
|
1959
|
+
async getSpamEmailDomain(domain) {
|
|
1960
|
+
if (!this.tables?.spamEmailDomain) {
|
|
1961
|
+
throw new common.ProsopoDBError("DATABASE.DATABASE_IMPORT_ERROR", {
|
|
1962
|
+
context: { failedFuncName: this.getSpamEmailDomain.name }
|
|
1963
|
+
});
|
|
1964
|
+
}
|
|
1965
|
+
const suffixCandidates = util.buildDomainSuffixCandidates(domain).slice(
|
|
1966
|
+
0,
|
|
1967
|
+
MAX_DOMAIN_SUFFIX_CANDIDATES
|
|
1968
|
+
);
|
|
1969
|
+
const query = suffixCandidates.length > 0 ? { domain: { $in: suffixCandidates } } : { domain };
|
|
1970
|
+
return await this.tables.spamEmailDomain.findOne(query).exec();
|
|
1971
|
+
}
|
|
1972
|
+
async bulkUpdateSpamEmailDomains(domains, upsert) {
|
|
1973
|
+
if (!this.tables?.spamEmailDomain) {
|
|
1974
|
+
throw new common.ProsopoDBError("DATABASE.DATABASE_IMPORT_ERROR", {
|
|
1975
|
+
context: { failedFuncName: this.bulkUpdateSpamEmailDomains.name }
|
|
1976
|
+
});
|
|
1977
|
+
}
|
|
1978
|
+
const bulkOps = domains.map((op) => ({
|
|
1979
|
+
updateOne: {
|
|
1980
|
+
filter: op.filter,
|
|
1981
|
+
update: { $set: op.update },
|
|
1982
|
+
upsert
|
|
1983
|
+
}
|
|
1984
|
+
}));
|
|
1985
|
+
if (bulkOps.length > 0) {
|
|
1986
|
+
await this.tables.spamEmailDomain.bulkWrite(bulkOps);
|
|
1987
|
+
}
|
|
1988
|
+
}
|
|
1463
1989
|
}
|
|
1464
1990
|
exports.ProviderDatabase = ProviderDatabase;
|