@prosopo/database 3.6.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 +18 -15
- package/.turbo/turbo-build.log +18 -14
- package/CHANGELOG.md +604 -0
- package/dist/base/mongo.d.ts +1 -1
- package/dist/base/mongo.d.ts.map +1 -1
- package/dist/base/mongo.js +5 -2
- package/dist/base/mongo.js.map +1 -1
- package/dist/base/mongoMemory.d.ts +1 -1
- 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 +687 -162
- package/dist/cjs/index.cjs +4 -0
- package/dist/cjs/redisCache.cjs +388 -0
- package/dist/databases/captcha.d.ts +1 -1
- package/dist/databases/captcha.d.ts.map +1 -1
- package/dist/databases/captcha.js +2 -1
- package/dist/databases/captcha.js.map +1 -1
- 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 +1 -1
- package/dist/databases/client.d.ts.map +1 -1
- package/dist/databases/client.js.map +1 -1
- package/dist/databases/index.d.ts +1 -0
- package/dist/databases/index.d.ts.map +1 -1
- package/dist/databases/index.js +2 -0
- package/dist/databases/index.js.map +1 -1
- package/dist/databases/provider.d.ts +45 -14
- package/dist/databases/provider.d.ts.map +1 -1
- package/dist/databases/provider.js +688 -163
- package/dist/databases/provider.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- 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 +12 -9
- 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 },
|
|
@@ -601,6 +668,7 @@ class ProviderDatabase extends mongo.MongoDatabase {
|
|
|
601
668
|
userSubmitted,
|
|
602
669
|
userSignature,
|
|
603
670
|
lastUpdatedTimestamp: timestamp,
|
|
671
|
+
pendingStage: true,
|
|
604
672
|
...coords && { coords }
|
|
605
673
|
};
|
|
606
674
|
try {
|
|
@@ -631,6 +699,17 @@ class ProviderDatabase extends mongo.MongoDatabase {
|
|
|
631
699
|
},
|
|
632
700
|
msg: "PowCaptcha record updated successfully"
|
|
633
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
|
+
);
|
|
634
713
|
} catch (error) {
|
|
635
714
|
const err = new common.ProsopoDBError("DATABASE.CAPTCHA_UPDATE_FAILED", {
|
|
636
715
|
context: {
|
|
@@ -651,7 +730,205 @@ class ProviderDatabase extends mongo.MongoDatabase {
|
|
|
651
730
|
const tables = this.getTables();
|
|
652
731
|
await tables.powcaptcha.updateOne(
|
|
653
732
|
{ challenge },
|
|
654
|
-
{ $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 } },
|
|
655
932
|
{ upsert: false }
|
|
656
933
|
);
|
|
657
934
|
}
|
|
@@ -663,44 +940,38 @@ class ProviderDatabase extends mongo.MongoDatabase {
|
|
|
663
940
|
return docs || [];
|
|
664
941
|
}
|
|
665
942
|
/** @description Get Dapp User captcha commitments from the commitments table that have not been counted towards the
|
|
666
|
-
* 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.
|
|
667
952
|
*/
|
|
668
953
|
async getUnstoredDappUserCommitments(limit = 1e3, skip = 0) {
|
|
669
|
-
const
|
|
670
|
-
const docs = await this.tables?.commitment.aggregate([
|
|
671
|
-
{
|
|
672
|
-
$match: {
|
|
673
|
-
$or: [
|
|
674
|
-
filterNoStoredTimestamp,
|
|
675
|
-
{
|
|
676
|
-
$expr: {
|
|
677
|
-
$lt: ["$storedAtTimestamp", "$lastUpdatedTimestamp"]
|
|
678
|
-
}
|
|
679
|
-
}
|
|
680
|
-
]
|
|
681
|
-
}
|
|
682
|
-
},
|
|
683
|
-
{
|
|
684
|
-
$sort: { _id: 1 }
|
|
685
|
-
},
|
|
686
|
-
{
|
|
687
|
-
$skip: skip
|
|
688
|
-
},
|
|
689
|
-
{
|
|
690
|
-
$limit: limit
|
|
691
|
-
}
|
|
692
|
-
]);
|
|
954
|
+
const docs = await this.tables?.commitment.find({ pendingStage: true }).sort({ _id: 1 }).skip(skip).limit(limit).lean();
|
|
693
955
|
return docs || [];
|
|
694
956
|
}
|
|
695
|
-
/** @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.
|
|
696
964
|
*/
|
|
697
|
-
async markDappUserCommitmentsStored(commitmentIds) {
|
|
698
|
-
const updateDoc = {
|
|
699
|
-
storedAtTimestamp: /* @__PURE__ */ new Date()
|
|
700
|
-
};
|
|
965
|
+
async markDappUserCommitmentsStored(commitmentIds, asOfTimestamp = /* @__PURE__ */ new Date()) {
|
|
701
966
|
await this.tables?.commitment.updateMany(
|
|
702
|
-
{
|
|
703
|
-
|
|
967
|
+
{
|
|
968
|
+
id: { $in: commitmentIds },
|
|
969
|
+
lastUpdatedTimestamp: { $lte: asOfTimestamp }
|
|
970
|
+
},
|
|
971
|
+
{
|
|
972
|
+
$set: { storedAtTimestamp: /* @__PURE__ */ new Date() },
|
|
973
|
+
$unset: { pendingStage: 1 }
|
|
974
|
+
},
|
|
704
975
|
{ upsert: false }
|
|
705
976
|
);
|
|
706
977
|
}
|
|
@@ -709,7 +980,8 @@ class ProviderDatabase extends mongo.MongoDatabase {
|
|
|
709
980
|
async markDappUserCommitmentsChecked(commitmentIds) {
|
|
710
981
|
const updateDoc = {
|
|
711
982
|
[types.StoredStatusNames.serverChecked]: true,
|
|
712
|
-
lastUpdatedTimestamp: /* @__PURE__ */ new Date()
|
|
983
|
+
lastUpdatedTimestamp: /* @__PURE__ */ new Date(),
|
|
984
|
+
pendingStage: true
|
|
713
985
|
};
|
|
714
986
|
await this.tables?.commitment.updateMany(
|
|
715
987
|
{ id: { $in: commitmentIds } },
|
|
@@ -721,7 +993,11 @@ class ProviderDatabase extends mongo.MongoDatabase {
|
|
|
721
993
|
*/
|
|
722
994
|
async updateDappUserCommitment(commitmentId, updates) {
|
|
723
995
|
const filter = { id: commitmentId };
|
|
724
|
-
await this.tables?.commitment.updateOne(filter,
|
|
996
|
+
await this.tables?.commitment.updateOne(filter, {
|
|
997
|
+
...updates,
|
|
998
|
+
lastUpdatedAtTimestamp: /* @__PURE__ */ new Date(),
|
|
999
|
+
pendingStage: true
|
|
1000
|
+
});
|
|
725
1001
|
}
|
|
726
1002
|
/**
|
|
727
1003
|
* @description Get Dapp User PoW captcha commitments that have not been counted towards the client's total
|
|
@@ -730,54 +1006,25 @@ class ProviderDatabase extends mongo.MongoDatabase {
|
|
|
730
1006
|
* @returns {Promise<PoWCaptchaRecord[]>} Array of PoW captcha records
|
|
731
1007
|
*/
|
|
732
1008
|
async getUnstoredDappUserPoWCommitments(limit = 1e3, skip = 0) {
|
|
733
|
-
const
|
|
734
|
-
const docs = await this.tables?.powcaptcha.aggregate([
|
|
735
|
-
{
|
|
736
|
-
$match: {
|
|
737
|
-
$or: [
|
|
738
|
-
filterNoStoredTimestamp,
|
|
739
|
-
{
|
|
740
|
-
$expr: {
|
|
741
|
-
$lt: [
|
|
742
|
-
{
|
|
743
|
-
$convert: {
|
|
744
|
-
input: "$storedAtTimestamp",
|
|
745
|
-
to: "date"
|
|
746
|
-
}
|
|
747
|
-
},
|
|
748
|
-
{
|
|
749
|
-
$convert: {
|
|
750
|
-
input: "$lastUpdatedTimestamp",
|
|
751
|
-
to: "date"
|
|
752
|
-
}
|
|
753
|
-
}
|
|
754
|
-
]
|
|
755
|
-
}
|
|
756
|
-
}
|
|
757
|
-
]
|
|
758
|
-
}
|
|
759
|
-
},
|
|
760
|
-
{
|
|
761
|
-
$sort: { _id: 1 }
|
|
762
|
-
},
|
|
763
|
-
{
|
|
764
|
-
$skip: skip
|
|
765
|
-
},
|
|
766
|
-
{
|
|
767
|
-
$limit: limit
|
|
768
|
-
}
|
|
769
|
-
]);
|
|
1009
|
+
const docs = await this.tables?.powcaptcha.find({ pendingStage: true }).sort({ _id: 1 }).skip(skip).limit(limit).lean();
|
|
770
1010
|
return docs || [];
|
|
771
1011
|
}
|
|
772
|
-
/** @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.
|
|
773
1017
|
*/
|
|
774
|
-
async markDappUserPoWCommitmentsStored(challenges) {
|
|
775
|
-
const updateDoc = {
|
|
776
|
-
storedAtTimestamp: /* @__PURE__ */ new Date()
|
|
777
|
-
};
|
|
1018
|
+
async markDappUserPoWCommitmentsStored(challenges, asOfTimestamp = /* @__PURE__ */ new Date()) {
|
|
778
1019
|
await this.tables?.powcaptcha.updateMany(
|
|
779
|
-
{
|
|
780
|
-
|
|
1020
|
+
{
|
|
1021
|
+
challenge: { $in: challenges },
|
|
1022
|
+
lastUpdatedTimestamp: { $lte: asOfTimestamp }
|
|
1023
|
+
},
|
|
1024
|
+
{
|
|
1025
|
+
$set: { storedAtTimestamp: /* @__PURE__ */ new Date() },
|
|
1026
|
+
$unset: { pendingStage: 1 }
|
|
1027
|
+
},
|
|
781
1028
|
{ upsert: false }
|
|
782
1029
|
);
|
|
783
1030
|
}
|
|
@@ -786,7 +1033,8 @@ class ProviderDatabase extends mongo.MongoDatabase {
|
|
|
786
1033
|
async markDappUserPoWCommitmentsChecked(challenges) {
|
|
787
1034
|
const updateDoc = {
|
|
788
1035
|
[types.StoredStatusNames.serverChecked]: true,
|
|
789
|
-
lastUpdatedTimestamp: /* @__PURE__ */ new Date()
|
|
1036
|
+
lastUpdatedTimestamp: /* @__PURE__ */ new Date(),
|
|
1037
|
+
pendingStage: true
|
|
790
1038
|
};
|
|
791
1039
|
await this.tables?.powcaptcha.updateMany(
|
|
792
1040
|
{ challenge: { $in: challenges } },
|
|
@@ -804,7 +1052,26 @@ class ProviderDatabase extends mongo.MongoDatabase {
|
|
|
804
1052
|
this.logger.debug(() => ({
|
|
805
1053
|
data: { action: "storing", sessionRecord }
|
|
806
1054
|
}));
|
|
807
|
-
|
|
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
|
+
);
|
|
808
1075
|
} catch (err) {
|
|
809
1076
|
throw new common.ProsopoDBError("DATABASE.SESSION_STORE_FAILED", {
|
|
810
1077
|
context: { error: err, sessionId: sessionRecord.sessionId },
|
|
@@ -817,7 +1084,14 @@ class ProviderDatabase extends mongo.MongoDatabase {
|
|
|
817
1084
|
*/
|
|
818
1085
|
async getSessionRecordBySessionId(sessionId) {
|
|
819
1086
|
const filter = { sessionId };
|
|
820
|
-
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();
|
|
821
1095
|
return doc || void 0;
|
|
822
1096
|
}
|
|
823
1097
|
/**
|
|
@@ -844,7 +1118,8 @@ class ProviderDatabase extends mongo.MongoDatabase {
|
|
|
844
1118
|
try {
|
|
845
1119
|
const session = await this.tables.session.findOneAndUpdate(filter, {
|
|
846
1120
|
deleted: true,
|
|
847
|
-
lastUpdatedTimestamp: /* @__PURE__ */ new Date()
|
|
1121
|
+
lastUpdatedTimestamp: /* @__PURE__ */ new Date(),
|
|
1122
|
+
pendingStage: true
|
|
848
1123
|
}).lean();
|
|
849
1124
|
return session || void 0;
|
|
850
1125
|
} catch (err) {
|
|
@@ -854,6 +1129,74 @@ class ProviderDatabase extends mongo.MongoDatabase {
|
|
|
854
1129
|
});
|
|
855
1130
|
}
|
|
856
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
|
+
}
|
|
857
1200
|
/**
|
|
858
1201
|
* Get an active session by user IP hash
|
|
859
1202
|
* @param userSitekeyIpHash The hash of user, IP and sitekey combination
|
|
@@ -878,64 +1221,43 @@ class ProviderDatabase extends mongo.MongoDatabase {
|
|
|
878
1221
|
}
|
|
879
1222
|
}
|
|
880
1223
|
/** Get unstored session records
|
|
881
|
-
* @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.
|
|
882
1230
|
* @param limit
|
|
883
1231
|
* @param skip
|
|
884
1232
|
*/
|
|
885
1233
|
getUnstoredSessionRecords(limit = 1e3, skip = 0) {
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
to: "date"
|
|
899
|
-
}
|
|
900
|
-
},
|
|
901
|
-
{
|
|
902
|
-
$convert: {
|
|
903
|
-
input: "$lastUpdatedTimestamp",
|
|
904
|
-
to: "date"
|
|
905
|
-
}
|
|
906
|
-
}
|
|
907
|
-
]
|
|
908
|
-
}
|
|
909
|
-
}
|
|
910
|
-
]
|
|
911
|
-
}
|
|
912
|
-
},
|
|
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(
|
|
913
1246
|
{
|
|
914
|
-
|
|
1247
|
+
sessionId: { $in: sessionIds },
|
|
1248
|
+
lastUpdatedTimestamp: { $lte: asOfTimestamp }
|
|
915
1249
|
},
|
|
916
1250
|
{
|
|
917
|
-
$
|
|
1251
|
+
$set: { storedAtTimestamp: /* @__PURE__ */ new Date() },
|
|
1252
|
+
$unset: { pendingStage: 1 }
|
|
918
1253
|
},
|
|
919
|
-
{
|
|
920
|
-
$limit: limit
|
|
921
|
-
}
|
|
922
|
-
]).then((docs) => docs || []);
|
|
923
|
-
}
|
|
924
|
-
/** Mark a list of session records as stored */
|
|
925
|
-
async markSessionRecordsStored(sessionIds) {
|
|
926
|
-
const updateDoc = {
|
|
927
|
-
storedAtTimestamp: /* @__PURE__ */ new Date()
|
|
928
|
-
};
|
|
929
|
-
await this.tables?.session.updateMany(
|
|
930
|
-
{ sessionId: { $in: sessionIds } },
|
|
931
|
-
{ $set: updateDoc },
|
|
932
1254
|
{ upsert: false }
|
|
933
1255
|
);
|
|
934
1256
|
}
|
|
935
1257
|
/**
|
|
936
1258
|
* @description Store a Dapp User's pending record
|
|
937
1259
|
*/
|
|
938
|
-
async storePendingImageCommitment(userAccount, requestHash, salt, deadlineTimestamp, requestedAtTimestamp, ipAddress, threshold, sessionId) {
|
|
1260
|
+
async storePendingImageCommitment(userAccount, requestHash, salt, deadlineTimestamp, requestedAtTimestamp, ipAddress, threshold, sessionId, ipInfo) {
|
|
939
1261
|
if (!is.isHex(requestHash)) {
|
|
940
1262
|
throw new common.ProsopoDBError("DATABASE.INVALID_HASH", {
|
|
941
1263
|
context: {
|
|
@@ -945,24 +1267,47 @@ class ProviderDatabase extends mongo.MongoDatabase {
|
|
|
945
1267
|
});
|
|
946
1268
|
}
|
|
947
1269
|
const pendingRecord = {
|
|
948
|
-
|
|
1270
|
+
userAccount,
|
|
949
1271
|
pending: true,
|
|
950
1272
|
salt,
|
|
951
1273
|
requestHash,
|
|
952
1274
|
deadlineTimestamp,
|
|
953
|
-
requestedAtTimestamp
|
|
1275
|
+
requestedAtTimestamp,
|
|
954
1276
|
ipAddress,
|
|
955
1277
|
sessionId,
|
|
956
|
-
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
|
|
957
1302
|
};
|
|
958
|
-
await this.tables?.
|
|
1303
|
+
await this.tables?.commitment.updateOne(
|
|
959
1304
|
{ requestHash },
|
|
960
1305
|
{ $set: pendingRecord },
|
|
961
1306
|
{ upsert: true }
|
|
962
1307
|
);
|
|
963
1308
|
}
|
|
964
1309
|
/**
|
|
965
|
-
* @description Get a
|
|
1310
|
+
* @description Get a user's pending record
|
|
966
1311
|
*/
|
|
967
1312
|
async getPendingImageCommitment(requestHash) {
|
|
968
1313
|
if (!is.isHex(requestHash)) {
|
|
@@ -973,12 +1318,22 @@ class ProviderDatabase extends mongo.MongoDatabase {
|
|
|
973
1318
|
}
|
|
974
1319
|
});
|
|
975
1320
|
}
|
|
976
|
-
const
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
1321
|
+
const doc = await this.tables?.commitment.findOne({
|
|
1322
|
+
requestHash,
|
|
1323
|
+
pending: true
|
|
1324
|
+
}).lean();
|
|
980
1325
|
if (doc) {
|
|
981
|
-
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
|
+
};
|
|
982
1337
|
}
|
|
983
1338
|
throw new common.ProsopoDBError("DATABASE.PENDING_RECORD_NOT_FOUND", {
|
|
984
1339
|
context: {
|
|
@@ -999,17 +1354,13 @@ class ProviderDatabase extends mongo.MongoDatabase {
|
|
|
999
1354
|
}
|
|
1000
1355
|
});
|
|
1001
1356
|
}
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
};
|
|
1005
|
-
await this.tables?.pending.updateOne(
|
|
1006
|
-
filter,
|
|
1357
|
+
await this.tables?.commitment.updateOne(
|
|
1358
|
+
{ requestHash },
|
|
1007
1359
|
{
|
|
1008
1360
|
$set: {
|
|
1009
|
-
|
|
1361
|
+
pending: false
|
|
1010
1362
|
}
|
|
1011
|
-
}
|
|
1012
|
-
{ upsert: true }
|
|
1363
|
+
}
|
|
1013
1364
|
);
|
|
1014
1365
|
}
|
|
1015
1366
|
/**
|
|
@@ -1110,7 +1461,7 @@ class ProviderDatabase extends mongo.MongoDatabase {
|
|
|
1110
1461
|
const filter = {
|
|
1111
1462
|
commitmentId
|
|
1112
1463
|
};
|
|
1113
|
-
const project = {
|
|
1464
|
+
const project = { _id: 0 };
|
|
1114
1465
|
const cursor = this.tables?.usersolution?.findOne(filter, project).lean();
|
|
1115
1466
|
const doc = await cursor;
|
|
1116
1467
|
if (doc) {
|
|
@@ -1126,7 +1477,18 @@ class ProviderDatabase extends mongo.MongoDatabase {
|
|
|
1126
1477
|
*/
|
|
1127
1478
|
async getDappUserCommitmentById(commitmentId) {
|
|
1128
1479
|
const filter = { id: commitmentId };
|
|
1129
|
-
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();
|
|
1130
1492
|
const doc = await commitmentCursor;
|
|
1131
1493
|
return doc ? doc : void 0;
|
|
1132
1494
|
}
|
|
@@ -1140,7 +1502,10 @@ class ProviderDatabase extends mongo.MongoDatabase {
|
|
|
1140
1502
|
userAccount,
|
|
1141
1503
|
dappAccount
|
|
1142
1504
|
};
|
|
1143
|
-
const project = {
|
|
1505
|
+
const project = {
|
|
1506
|
+
_id: 0,
|
|
1507
|
+
result: 1
|
|
1508
|
+
};
|
|
1144
1509
|
const sort = { sort: { _id: -1 } };
|
|
1145
1510
|
const docs = await this.tables?.commitment?.find(filter, project, sort).lean();
|
|
1146
1511
|
return docs ? docs : [];
|
|
@@ -1156,10 +1521,22 @@ class ProviderDatabase extends mongo.MongoDatabase {
|
|
|
1156
1521
|
const updateDoc = {
|
|
1157
1522
|
result,
|
|
1158
1523
|
lastUpdatedTimestamp: /* @__PURE__ */ new Date(),
|
|
1524
|
+
pendingStage: true,
|
|
1159
1525
|
...coords ? { coords } : {}
|
|
1160
1526
|
};
|
|
1161
1527
|
const filter = { id: commitmentId };
|
|
1162
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
|
+
);
|
|
1163
1540
|
} catch (err) {
|
|
1164
1541
|
throw new common.ProsopoDBError("DATABASE.SOLUTION_APPROVE_FAILED", {
|
|
1165
1542
|
context: { error: err, commitmentId }
|
|
@@ -1177,10 +1554,22 @@ class ProviderDatabase extends mongo.MongoDatabase {
|
|
|
1177
1554
|
const updateDoc = {
|
|
1178
1555
|
result: { status: types.CaptchaStatus.disapproved, reason },
|
|
1179
1556
|
lastUpdatedTimestamp: /* @__PURE__ */ new Date(),
|
|
1557
|
+
pendingStage: true,
|
|
1180
1558
|
...coords ? { coords } : {}
|
|
1181
1559
|
};
|
|
1182
1560
|
const filter = { id: commitmentId };
|
|
1183
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
|
+
);
|
|
1184
1573
|
} catch (err) {
|
|
1185
1574
|
throw new common.ProsopoDBError("DATABASE.SOLUTION_APPROVE_FAILED", {
|
|
1186
1575
|
context: { error: err, commitmentId }
|
|
@@ -1316,6 +1705,17 @@ class ProviderDatabase extends mongo.MongoDatabase {
|
|
|
1316
1705
|
}
|
|
1317
1706
|
await this.tables?.client.bulkWrite(ops);
|
|
1318
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
|
+
}
|
|
1319
1719
|
/**
|
|
1320
1720
|
* @description Get all client records
|
|
1321
1721
|
*/
|
|
@@ -1328,7 +1728,11 @@ class ProviderDatabase extends mongo.MongoDatabase {
|
|
|
1328
1728
|
*/
|
|
1329
1729
|
async getClientRecord(account) {
|
|
1330
1730
|
const filter = { account };
|
|
1331
|
-
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();
|
|
1332
1736
|
return doc ? doc : void 0;
|
|
1333
1737
|
}
|
|
1334
1738
|
/**
|
|
@@ -1366,6 +1770,97 @@ class ProviderDatabase extends mongo.MongoDatabase {
|
|
|
1366
1770
|
).sort({ createdAt: -1 }).lean();
|
|
1367
1771
|
return (keyRecords || []).map((record) => record.detectorKey);
|
|
1368
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
|
+
}
|
|
1369
1864
|
/**
|
|
1370
1865
|
* @description set client context-specific entropy
|
|
1371
1866
|
*/
|
|
@@ -1461,5 +1956,35 @@ class ProviderDatabase extends mongo.MongoDatabase {
|
|
|
1461
1956
|
})
|
|
1462
1957
|
)).filter((headHash) => headHash !== void 0);
|
|
1463
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
|
+
}
|
|
1464
1989
|
}
|
|
1465
1990
|
exports.ProviderDatabase = ProviderDatabase;
|