@prosopo/database 4.1.4 → 4.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build$colon$cjs.log +4 -4
- package/.turbo/turbo-build$colon$tsc.log +15 -15
- package/.turbo/turbo-build.log +4 -4
- package/CHANGELOG.md +43 -0
- package/dist/cjs/databases/captcha.cjs +38 -22
- package/dist/cjs/databases/centralDbStreamer.cjs +26 -8
- package/dist/cjs/databases/provider.cjs +243 -103
- package/dist/databases/captcha.d.ts +8 -3
- package/dist/databases/captcha.d.ts.map +1 -1
- package/dist/databases/captcha.js +39 -23
- package/dist/databases/captcha.js.map +1 -1
- package/dist/databases/centralDbStreamer.d.ts +7 -1
- package/dist/databases/centralDbStreamer.d.ts.map +1 -1
- package/dist/databases/centralDbStreamer.js +26 -8
- package/dist/databases/centralDbStreamer.js.map +1 -1
- package/dist/databases/provider.d.ts +26 -2
- package/dist/databases/provider.d.ts.map +1 -1
- package/dist/databases/provider.js +244 -104
- package/dist/databases/provider.js.map +1 -1
- package/dist/tests/integration/countCommitmentsByNormalisedEmail.integration.test.js +67 -0
- package/dist/tests/integration/countCommitmentsByNormalisedEmail.integration.test.js.map +1 -1
- package/dist/tests/integration/projectionContracts.integration.test.js +54 -0
- package/dist/tests/integration/projectionContracts.integration.test.js.map +1 -1
- package/dist/tests/integration/serverCheckClaim.integration.test.js +52 -0
- package/dist/tests/integration/serverCheckClaim.integration.test.js.map +1 -1
- package/package.json +5 -5
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { MongoDatabase } from "../base/mongo.js";
|
|
2
2
|
import { ProsopoDBError } from "@prosopo/common";
|
|
3
3
|
import { CaptchaStates, CaptchaStatus, CaptchaType, DatasetWithIdsAndTreeSchema, DecisionMachineKind, StoredStatusNames, UserCommitmentSchema, isBlockingCaptchaResult } from "@prosopo/types";
|
|
4
|
-
import { CaptchaRecordSchema, ClientRecordSchema, DatasetRecordSchema, DecisionMachineArtifactRecordSchema, PoWCaptchaRecordSchema, PuzzleCaptchaRecordSchema, SESSION_PROJECTION, ScheduledTaskRecordSchema, ScheduledTaskSchema, SessionRecordSchema, SolutionRecordSchema, SpamEmailDomainRecordSchema, UserCommitmentRecordSchema, UserSolutionRecordSchema } from "@prosopo/types-database";
|
|
4
|
+
import { AudioCaptchaRecordSchema, CaptchaRecordSchema, ClientRecordSchema, DatasetRecordSchema, DecisionMachineArtifactRecordSchema, IconOrderCaptchaRecordSchema, PoWCaptchaRecordSchema, PuzzleCaptchaRecordSchema, SESSION_PROJECTION, ScheduledTaskRecordSchema, ScheduledTaskSchema, SessionRecordSchema, SolutionRecordSchema, SpamEmailDomainRecordSchema, UserCommitmentRecordSchema, UserSolutionRecordSchema } from "@prosopo/types-database";
|
|
5
5
|
import { isHex } from "@polkadot/util/is";
|
|
6
6
|
import { connectToRedis, setupRedisIndex } from "@prosopo/redis-client";
|
|
7
7
|
import { accessRulesRedisIndex, createRedisAccessRulesStorage } from "@prosopo/user-access-policy/redis";
|
|
@@ -23,6 +23,16 @@ var PROVIDER_TABLES = [
|
|
|
23
23
|
modelName: "PuzzleCaptcha",
|
|
24
24
|
schema: PuzzleCaptchaRecordSchema
|
|
25
25
|
},
|
|
26
|
+
{
|
|
27
|
+
collectionName: "audiocaptcha",
|
|
28
|
+
modelName: "AudioCaptcha",
|
|
29
|
+
schema: AudioCaptchaRecordSchema
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
collectionName: "iconordercaptcha",
|
|
33
|
+
modelName: "IconOrderCaptcha",
|
|
34
|
+
schema: IconOrderCaptchaRecordSchema
|
|
35
|
+
},
|
|
26
36
|
{
|
|
27
37
|
collectionName: "dataset",
|
|
28
38
|
modelName: "Dataset",
|
|
@@ -640,46 +650,76 @@ var ProviderDatabase = class ProviderDatabase extends MongoDatabase {
|
|
|
640
650
|
$unset: { pendingStage: 1 }
|
|
641
651
|
}).then(() => {}));
|
|
642
652
|
}
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
653
|
+
interactiveCollection(captchaType) {
|
|
654
|
+
switch (captchaType) {
|
|
655
|
+
case CaptchaType.puzzle: return {
|
|
656
|
+
table: "puzzlecaptcha",
|
|
657
|
+
label: "PuzzleCaptcha",
|
|
658
|
+
captchaType,
|
|
659
|
+
answerProjection: {
|
|
660
|
+
targetX: 1,
|
|
661
|
+
targetY: 1,
|
|
662
|
+
originX: 1,
|
|
663
|
+
originY: 1,
|
|
664
|
+
tolerance: 1,
|
|
665
|
+
puzzleEvents: 1
|
|
666
|
+
},
|
|
667
|
+
streamRecord: (record, markStored) => this.centralStreamer?.streamPuzzleRecord(record, markStored),
|
|
668
|
+
streamUpdate: (challenge, markStored) => this.centralStreamer?.streamPuzzleUpdate(() => this.getPuzzleCaptchaRecordByChallenge(challenge), markStored)
|
|
669
|
+
};
|
|
670
|
+
case CaptchaType.iconOrder: return {
|
|
671
|
+
table: "iconordercaptcha",
|
|
672
|
+
label: "IconOrderCaptcha",
|
|
673
|
+
captchaType,
|
|
674
|
+
answerProjection: {
|
|
675
|
+
targets: 1,
|
|
676
|
+
tolerance: 1,
|
|
677
|
+
clicks: 1,
|
|
678
|
+
iconOrderEvents: 1
|
|
679
|
+
},
|
|
680
|
+
streamRecord: (record, markStored) => this.centralStreamer?.streamIconOrderRecord(record, markStored),
|
|
681
|
+
streamUpdate: (challenge, markStored) => this.centralStreamer?.streamIconOrderUpdate(() => this.getIconOrderCaptchaRecordByChallenge(challenge), markStored)
|
|
682
|
+
};
|
|
683
|
+
case CaptchaType.audio: return {
|
|
684
|
+
table: "audiocaptcha",
|
|
685
|
+
label: "AudioCaptcha",
|
|
686
|
+
captchaType,
|
|
687
|
+
answerProjection: {
|
|
688
|
+
answer: 1,
|
|
689
|
+
submittedAnswer: 1,
|
|
690
|
+
replays: 1,
|
|
691
|
+
audioEvents: 1
|
|
692
|
+
},
|
|
693
|
+
streamRecord: (record, markStored) => this.centralStreamer?.streamAudioRecord(record, markStored),
|
|
694
|
+
streamUpdate: (challenge, markStored) => this.centralStreamer?.streamAudioUpdate(() => this.getAudioCaptchaRecordByChallenge(challenge), markStored)
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
markInteractiveRecordStored(collection, challenge) {
|
|
699
|
+
return (ts) => this.tables[collection.table].updateOne({
|
|
646
700
|
challenge,
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
originY,
|
|
660
|
-
tolerance,
|
|
661
|
-
providerSignature,
|
|
662
|
-
lastUpdatedTimestamp: /* @__PURE__ */ new Date(),
|
|
663
|
-
pendingStage: true,
|
|
664
|
-
sessionId,
|
|
665
|
-
ipInfo
|
|
666
|
-
};
|
|
701
|
+
lastUpdatedTimestamp: { $lte: ts }
|
|
702
|
+
}, {
|
|
703
|
+
$set: { storedAtTimestamp: ts },
|
|
704
|
+
$unset: { pendingStage: 1 }
|
|
705
|
+
}).then(() => {});
|
|
706
|
+
}
|
|
707
|
+
streamInteractiveUpdate(collection, challenge) {
|
|
708
|
+
collection.streamUpdate(challenge, this.markInteractiveRecordStored(collection, challenge));
|
|
709
|
+
}
|
|
710
|
+
async storeInteractiveCaptchaRecord(collection, record) {
|
|
711
|
+
const tables = this.getTables();
|
|
712
|
+
const { challenge, ipInfo } = record;
|
|
667
713
|
try {
|
|
668
|
-
await tables.
|
|
714
|
+
await tables[collection.table].create(record);
|
|
669
715
|
this.logger.info(() => ({
|
|
670
716
|
data: {
|
|
671
717
|
challenge,
|
|
672
718
|
countryCode: ipInfo?.isValid ? ipInfo.countryCode : void 0
|
|
673
719
|
},
|
|
674
|
-
msg:
|
|
720
|
+
msg: `${collection.label} record added successfully`
|
|
675
721
|
}));
|
|
676
|
-
|
|
677
|
-
challenge,
|
|
678
|
-
lastUpdatedTimestamp: { $lte: ts }
|
|
679
|
-
}, {
|
|
680
|
-
$set: { storedAtTimestamp: ts },
|
|
681
|
-
$unset: { pendingStage: 1 }
|
|
682
|
-
}).then(() => {}));
|
|
722
|
+
collection.streamRecord(record, this.markInteractiveRecordStored(collection, challenge));
|
|
683
723
|
} catch (error) {
|
|
684
724
|
const err = new ProsopoDBError("DATABASE.CAPTCHA_UPDATE_FAILED", {
|
|
685
725
|
context: {
|
|
@@ -691,24 +731,60 @@ var ProviderDatabase = class ProviderDatabase extends MongoDatabase {
|
|
|
691
731
|
});
|
|
692
732
|
this.logger.error(() => ({
|
|
693
733
|
err: error,
|
|
694
|
-
msg:
|
|
734
|
+
msg: `Failed to add ${collection.label} record`
|
|
695
735
|
}));
|
|
696
736
|
throw err;
|
|
697
737
|
}
|
|
698
738
|
}
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
739
|
+
pendingInteractiveRecord(challenge, components, providerSignature, ipAddress, headers, ja4, sessionId, ipInfo) {
|
|
740
|
+
return {
|
|
741
|
+
challenge,
|
|
742
|
+
userAccount: components.userAccount,
|
|
743
|
+
dappAccount: components.dappAccount,
|
|
744
|
+
requestedAtTimestamp: new Date(components.requestedAtTimestamp),
|
|
745
|
+
ipAddress,
|
|
746
|
+
headers,
|
|
747
|
+
ja4,
|
|
748
|
+
result: { status: CaptchaStatus.pending },
|
|
749
|
+
userSubmitted: false,
|
|
750
|
+
serverChecked: false,
|
|
751
|
+
providerSignature,
|
|
752
|
+
lastUpdatedTimestamp: /* @__PURE__ */ new Date(),
|
|
753
|
+
pendingStage: true,
|
|
754
|
+
sessionId,
|
|
755
|
+
ipInfo
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
async storePuzzleCaptchaRecord(challenge, components, targetX, targetY, originX, originY, tolerance, providerSignature, ipAddress, headers, ja4, sessionId, ipInfo) {
|
|
759
|
+
await this.storeInteractiveCaptchaRecord(this.interactiveCollection(CaptchaType.puzzle), {
|
|
760
|
+
...this.pendingInteractiveRecord(challenge, components, providerSignature, ipAddress, headers, ja4, sessionId, ipInfo),
|
|
761
|
+
tolerance,
|
|
762
|
+
targetX,
|
|
763
|
+
targetY,
|
|
764
|
+
originX,
|
|
765
|
+
originY
|
|
766
|
+
});
|
|
767
|
+
}
|
|
768
|
+
async storeAudioCaptchaRecord(challenge, components, answer, providerSignature, ipAddress, headers, ja4, sessionId, ipInfo) {
|
|
769
|
+
await this.storeInteractiveCaptchaRecord(this.interactiveCollection(CaptchaType.audio), {
|
|
770
|
+
...this.pendingInteractiveRecord(challenge, components, providerSignature, ipAddress, headers, ja4, sessionId, ipInfo),
|
|
771
|
+
answer
|
|
772
|
+
});
|
|
773
|
+
}
|
|
774
|
+
async storeIconOrderCaptchaRecord(challenge, components, targets, tolerance, providerSignature, ipAddress, headers, ja4, sessionId, ipInfo) {
|
|
775
|
+
await this.storeInteractiveCaptchaRecord(this.interactiveCollection(CaptchaType.iconOrder), {
|
|
776
|
+
...this.pendingInteractiveRecord(challenge, components, providerSignature, ipAddress, headers, ja4, sessionId, ipInfo),
|
|
777
|
+
targets,
|
|
778
|
+
tolerance
|
|
779
|
+
});
|
|
780
|
+
}
|
|
781
|
+
async getInteractiveCaptchaRecordByChallenge(collection, challenge, failedFuncName) {
|
|
705
782
|
if (!this.tables) throw new ProsopoDBError("DATABASE.DATABASE_UNDEFINED", {
|
|
706
|
-
context: { failedFuncName
|
|
783
|
+
context: { failedFuncName },
|
|
707
784
|
logger: this.logger
|
|
708
785
|
});
|
|
709
786
|
try {
|
|
710
|
-
const
|
|
711
|
-
const record = await this.tables.puzzlecaptcha.findOne(filter, {
|
|
787
|
+
const record = await this.tables[collection.table].findOne({ challenge }, {
|
|
712
788
|
challenge: 1,
|
|
713
789
|
userAccount: 1,
|
|
714
790
|
dappAccount: 1,
|
|
@@ -718,12 +794,6 @@ var ProviderDatabase = class ProviderDatabase extends MongoDatabase {
|
|
|
718
794
|
headers: 1,
|
|
719
795
|
ja4: 1,
|
|
720
796
|
result: 1,
|
|
721
|
-
targetX: 1,
|
|
722
|
-
targetY: 1,
|
|
723
|
-
originX: 1,
|
|
724
|
-
originY: 1,
|
|
725
|
-
tolerance: 1,
|
|
726
|
-
puzzleEvents: 1,
|
|
727
797
|
sessionId: 1,
|
|
728
798
|
ipInfo: 1,
|
|
729
799
|
deviceCapability: 1,
|
|
@@ -731,18 +801,19 @@ var ProviderDatabase = class ProviderDatabase extends MongoDatabase {
|
|
|
731
801
|
serverChecked: 1,
|
|
732
802
|
userSubmitted: 1,
|
|
733
803
|
coords: 1,
|
|
734
|
-
clientMetaData: 1
|
|
804
|
+
clientMetaData: 1,
|
|
805
|
+
...collection.answerProjection
|
|
735
806
|
}).lean();
|
|
736
807
|
if (record) {
|
|
737
808
|
this.logger.info(() => ({
|
|
738
809
|
data: { challenge },
|
|
739
|
-
msg:
|
|
810
|
+
msg: `${collection.label} record retrieved successfully`
|
|
740
811
|
}));
|
|
741
812
|
return record;
|
|
742
813
|
}
|
|
743
814
|
this.logger.info(() => ({
|
|
744
815
|
data: { challenge },
|
|
745
|
-
msg:
|
|
816
|
+
msg: `No ${collection.label} record found`
|
|
746
817
|
}));
|
|
747
818
|
return null;
|
|
748
819
|
} catch (error) {
|
|
@@ -755,23 +826,71 @@ var ProviderDatabase = class ProviderDatabase extends MongoDatabase {
|
|
|
755
826
|
});
|
|
756
827
|
this.logger.error(() => ({
|
|
757
828
|
err,
|
|
758
|
-
msg:
|
|
829
|
+
msg: `Failed to retrieve ${collection.label} record`
|
|
759
830
|
}));
|
|
760
831
|
throw err;
|
|
761
832
|
}
|
|
762
833
|
}
|
|
763
834
|
/**
|
|
764
|
-
* @description
|
|
765
|
-
* @param {string} challenge The challenge string
|
|
766
|
-
* @
|
|
767
|
-
* @param serverChecked
|
|
768
|
-
* @param userSubmitted
|
|
769
|
-
* @param userSignature
|
|
770
|
-
* @param coords
|
|
771
|
-
* @param lastUpdatedTimestamp
|
|
772
|
-
* @returns {Promise<void>} A promise that resolves when the record is updated.
|
|
835
|
+
* @description Retrieves a Puzzle Captcha record by its challenge string.
|
|
836
|
+
* @param {string} challenge The challenge string to search for.
|
|
837
|
+
* @returns {Promise<PuzzleCaptchaRecord | null>} A promise that resolves with the found record or null if not found.
|
|
773
838
|
*/
|
|
774
|
-
async
|
|
839
|
+
async getPuzzleCaptchaRecordByChallenge(challenge) {
|
|
840
|
+
return this.getInteractiveCaptchaRecordByChallenge(this.interactiveCollection(CaptchaType.puzzle), challenge, this.getPuzzleCaptchaRecordByChallenge.name);
|
|
841
|
+
}
|
|
842
|
+
async getIconOrderCaptchaRecordByChallenge(challenge) {
|
|
843
|
+
return this.getInteractiveCaptchaRecordByChallenge(this.interactiveCollection(CaptchaType.iconOrder), challenge, this.getIconOrderCaptchaRecordByChallenge.name);
|
|
844
|
+
}
|
|
845
|
+
async getAudioCaptchaRecordByChallenge(challenge) {
|
|
846
|
+
return this.getInteractiveCaptchaRecordByChallenge(this.interactiveCollection(CaptchaType.audio), challenge, this.getAudioCaptchaRecordByChallenge.name);
|
|
847
|
+
}
|
|
848
|
+
/**
|
|
849
|
+
* The filter is the guard: of N concurrent submitters only one matches
|
|
850
|
+
* `userSubmitted: { $ne: true }`, where a read-then-check would let them
|
|
851
|
+
* all through to be graded. `submittedAtTimestamp` is stamped here so the
|
|
852
|
+
* record is consistent even if the caller dies before writing a result.
|
|
853
|
+
*/
|
|
854
|
+
async claimInteractiveCaptchaSubmission(collection, challenge) {
|
|
855
|
+
const tables = this.getTables();
|
|
856
|
+
try {
|
|
857
|
+
const won = (await tables[collection.table].updateOne({
|
|
858
|
+
challenge,
|
|
859
|
+
userSubmitted: { $ne: true }
|
|
860
|
+
}, { $set: {
|
|
861
|
+
userSubmitted: true,
|
|
862
|
+
submittedAtTimestamp: /* @__PURE__ */ new Date()
|
|
863
|
+
} })).modifiedCount === 1;
|
|
864
|
+
this.logger.info(() => ({
|
|
865
|
+
data: {
|
|
866
|
+
challenge,
|
|
867
|
+
won
|
|
868
|
+
},
|
|
869
|
+
msg: won ? `${collection.label} submission claimed` : `${collection.label} submission already claimed`
|
|
870
|
+
}));
|
|
871
|
+
return won;
|
|
872
|
+
} catch (error) {
|
|
873
|
+
const err = new ProsopoDBError("DATABASE.CAPTCHA_UPDATE_FAILED", {
|
|
874
|
+
context: {
|
|
875
|
+
error,
|
|
876
|
+
challenge
|
|
877
|
+
},
|
|
878
|
+
logger: this.logger
|
|
879
|
+
});
|
|
880
|
+
this.logger.error(() => ({
|
|
881
|
+
err,
|
|
882
|
+
msg: `Failed to claim ${collection.label} submission`
|
|
883
|
+
}));
|
|
884
|
+
throw err;
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
async claimIconOrderCaptchaSubmission(challenge) {
|
|
888
|
+
return this.claimInteractiveCaptchaSubmission(this.interactiveCollection(CaptchaType.iconOrder), challenge);
|
|
889
|
+
}
|
|
890
|
+
async claimAudioCaptchaSubmission(challenge) {
|
|
891
|
+
return this.claimInteractiveCaptchaSubmission(this.interactiveCollection(CaptchaType.audio), challenge);
|
|
892
|
+
}
|
|
893
|
+
async updateInteractiveCaptchaRecordResult(collection, challenge, result, serverChecked, userSubmitted, userSignature, coords, lastUpdatedTimestamp) {
|
|
775
894
|
const tables = this.getTables();
|
|
776
895
|
const timestamp = lastUpdatedTimestamp ?? /* @__PURE__ */ new Date();
|
|
777
896
|
const isDisapproved = result.status === CaptchaStatus.disapproved;
|
|
@@ -783,13 +902,13 @@ var ProviderDatabase = class ProviderDatabase extends MongoDatabase {
|
|
|
783
902
|
userSignature,
|
|
784
903
|
lastUpdatedTimestamp: timestamp,
|
|
785
904
|
pendingStage: true,
|
|
786
|
-
blocked: isBlockingCaptchaResult(
|
|
905
|
+
blocked: isBlockingCaptchaResult(collection.captchaType, result),
|
|
787
906
|
...coords && { coords },
|
|
788
907
|
...userSubmitted && { submittedAtTimestamp: timestamp },
|
|
789
908
|
...isDisapproved && { failedAtTimestamp: timestamp }
|
|
790
909
|
};
|
|
791
910
|
try {
|
|
792
|
-
if ((await tables.
|
|
911
|
+
if ((await tables[collection.table].updateOne({ challenge }, { $set: setStage })).matchedCount === 0) {
|
|
793
912
|
const err = new ProsopoDBError("DATABASE.CAPTCHA_GET_FAILED", {
|
|
794
913
|
context: {
|
|
795
914
|
challenge,
|
|
@@ -799,7 +918,7 @@ var ProviderDatabase = class ProviderDatabase extends MongoDatabase {
|
|
|
799
918
|
});
|
|
800
919
|
this.logger.info(() => ({
|
|
801
920
|
err,
|
|
802
|
-
msg:
|
|
921
|
+
msg: `No ${collection.label} record found to update`
|
|
803
922
|
}));
|
|
804
923
|
throw err;
|
|
805
924
|
}
|
|
@@ -808,15 +927,9 @@ var ProviderDatabase = class ProviderDatabase extends MongoDatabase {
|
|
|
808
927
|
challenge,
|
|
809
928
|
...setStage
|
|
810
929
|
},
|
|
811
|
-
msg:
|
|
930
|
+
msg: `${collection.label} record updated successfully`
|
|
812
931
|
}));
|
|
813
|
-
this.
|
|
814
|
-
challenge,
|
|
815
|
-
lastUpdatedTimestamp: { $lte: ts }
|
|
816
|
-
}, {
|
|
817
|
-
$set: { storedAtTimestamp: ts },
|
|
818
|
-
$unset: { pendingStage: 1 }
|
|
819
|
-
}).then(() => {}));
|
|
932
|
+
this.streamInteractiveUpdate(collection, challenge);
|
|
820
933
|
} catch (error) {
|
|
821
934
|
const err = new ProsopoDBError("DATABASE.CAPTCHA_UPDATE_FAILED", {
|
|
822
935
|
context: {
|
|
@@ -828,12 +941,32 @@ var ProviderDatabase = class ProviderDatabase extends MongoDatabase {
|
|
|
828
941
|
});
|
|
829
942
|
this.logger.error(() => ({
|
|
830
943
|
err,
|
|
831
|
-
msg:
|
|
944
|
+
msg: `Failed to update ${collection.label} record`
|
|
832
945
|
}));
|
|
833
946
|
throw err;
|
|
834
947
|
}
|
|
835
948
|
}
|
|
836
|
-
|
|
949
|
+
/**
|
|
950
|
+
* @description Updates a Puzzle Captcha record result in the database.
|
|
951
|
+
* @param {string} challenge The challenge string of the captcha to be updated.
|
|
952
|
+
* @param result
|
|
953
|
+
* @param serverChecked
|
|
954
|
+
* @param userSubmitted
|
|
955
|
+
* @param userSignature
|
|
956
|
+
* @param coords
|
|
957
|
+
* @param lastUpdatedTimestamp
|
|
958
|
+
* @returns {Promise<void>} A promise that resolves when the record is updated.
|
|
959
|
+
*/
|
|
960
|
+
async updatePuzzleCaptchaRecordResult(challenge, result, serverChecked = false, userSubmitted = false, userSignature, coords, lastUpdatedTimestamp) {
|
|
961
|
+
await this.updateInteractiveCaptchaRecordResult(this.interactiveCollection(CaptchaType.puzzle), challenge, result, serverChecked, userSubmitted, userSignature, coords, lastUpdatedTimestamp);
|
|
962
|
+
}
|
|
963
|
+
async updateIconOrderCaptchaRecordResult(challenge, result, serverChecked = false, userSubmitted = false, userSignature, coords, lastUpdatedTimestamp) {
|
|
964
|
+
await this.updateInteractiveCaptchaRecordResult(this.interactiveCollection(CaptchaType.iconOrder), challenge, result, serverChecked, userSubmitted, userSignature, coords, lastUpdatedTimestamp);
|
|
965
|
+
}
|
|
966
|
+
async updateAudioCaptchaRecordResult(challenge, result, serverChecked = false, userSubmitted = false, userSignature, coords, lastUpdatedTimestamp) {
|
|
967
|
+
await this.updateInteractiveCaptchaRecordResult(this.interactiveCollection(CaptchaType.audio), challenge, result, serverChecked, userSubmitted, userSignature, coords, lastUpdatedTimestamp);
|
|
968
|
+
}
|
|
969
|
+
async updateInteractiveCaptchaRecord(collection, challenge, updates) {
|
|
837
970
|
const tables = this.getTables();
|
|
838
971
|
const timestamp = /* @__PURE__ */ new Date();
|
|
839
972
|
const baseSet = {
|
|
@@ -843,19 +976,22 @@ var ProviderDatabase = class ProviderDatabase extends MongoDatabase {
|
|
|
843
976
|
...updates.userSubmitted === true && { submittedAtTimestamp: timestamp },
|
|
844
977
|
...updates.result?.status === CaptchaStatus.disapproved && { failedAtTimestamp: timestamp }
|
|
845
978
|
};
|
|
846
|
-
await tables.
|
|
847
|
-
this.
|
|
848
|
-
challenge,
|
|
849
|
-
lastUpdatedTimestamp: { $lte: ts }
|
|
850
|
-
}, {
|
|
851
|
-
$set: { storedAtTimestamp: ts },
|
|
852
|
-
$unset: { pendingStage: 1 }
|
|
853
|
-
}).then(() => {}));
|
|
979
|
+
await tables[collection.table].updateOne({ challenge }, { $set: baseSet });
|
|
980
|
+
this.streamInteractiveUpdate(collection, challenge);
|
|
854
981
|
}
|
|
855
|
-
async
|
|
982
|
+
async updatePuzzleCaptchaRecord(challenge, updates) {
|
|
983
|
+
await this.updateInteractiveCaptchaRecord(this.interactiveCollection(CaptchaType.puzzle), challenge, updates);
|
|
984
|
+
}
|
|
985
|
+
async updateIconOrderCaptchaRecord(challenge, updates) {
|
|
986
|
+
await this.updateInteractiveCaptchaRecord(this.interactiveCollection(CaptchaType.iconOrder), challenge, updates);
|
|
987
|
+
}
|
|
988
|
+
async updateAudioCaptchaRecord(challenge, updates) {
|
|
989
|
+
await this.updateInteractiveCaptchaRecord(this.interactiveCollection(CaptchaType.audio), challenge, updates);
|
|
990
|
+
}
|
|
991
|
+
async markInteractiveCaptchaRecordChecked(collection, challenge) {
|
|
856
992
|
const tables = this.getTables();
|
|
857
993
|
const timestamp = /* @__PURE__ */ new Date();
|
|
858
|
-
if ((await tables.
|
|
994
|
+
if ((await tables[collection.table].updateOne({
|
|
859
995
|
challenge,
|
|
860
996
|
serverChecked: { $ne: true }
|
|
861
997
|
}, { $set: {
|
|
@@ -864,15 +1000,18 @@ var ProviderDatabase = class ProviderDatabase extends MongoDatabase {
|
|
|
864
1000
|
verifiedAtTimestamp: timestamp,
|
|
865
1001
|
pendingStage: true
|
|
866
1002
|
} })).modifiedCount === 0) return false;
|
|
867
|
-
this.
|
|
868
|
-
challenge,
|
|
869
|
-
lastUpdatedTimestamp: { $lte: ts }
|
|
870
|
-
}, {
|
|
871
|
-
$set: { storedAtTimestamp: ts },
|
|
872
|
-
$unset: { pendingStage: 1 }
|
|
873
|
-
}).then(() => {}));
|
|
1003
|
+
this.streamInteractiveUpdate(collection, challenge);
|
|
874
1004
|
return true;
|
|
875
1005
|
}
|
|
1006
|
+
async markIconOrderCaptchaRecordChecked(challenge) {
|
|
1007
|
+
return this.markInteractiveCaptchaRecordChecked(this.interactiveCollection(CaptchaType.iconOrder), challenge);
|
|
1008
|
+
}
|
|
1009
|
+
async markAudioCaptchaRecordChecked(challenge) {
|
|
1010
|
+
return this.markInteractiveCaptchaRecordChecked(this.interactiveCollection(CaptchaType.audio), challenge);
|
|
1011
|
+
}
|
|
1012
|
+
async markPuzzleCaptchaRecordChecked(challenge) {
|
|
1013
|
+
return this.markInteractiveCaptchaRecordChecked(this.interactiveCollection(CaptchaType.puzzle), challenge);
|
|
1014
|
+
}
|
|
876
1015
|
/** @description Get serverChecked Dapp User image captcha commitments from the commitments table
|
|
877
1016
|
*/
|
|
878
1017
|
async getCheckedDappUserCommitments() {
|
|
@@ -966,13 +1105,13 @@ var ProviderDatabase = class ProviderDatabase extends MongoDatabase {
|
|
|
966
1105
|
} }], { updatePipeline: true });
|
|
967
1106
|
}
|
|
968
1107
|
/**
|
|
969
|
-
* Counts server-checked captcha records across image (`commitment`),
|
|
970
|
-
* PoW and
|
|
971
|
-
* Each collection carries the same partial index
|
|
972
|
-
* (`spamEmailCount_partial`) so
|
|
973
|
-
*
|
|
974
|
-
*
|
|
975
|
-
*
|
|
1108
|
+
* Counts server-checked captcha records across the image (`commitment`),
|
|
1109
|
+
* PoW, puzzle and icon-order collections whose `metadata.emailNormalised`
|
|
1110
|
+
* matches. Each collection carries the same partial index
|
|
1111
|
+
* (`spamEmailCount_partial`) so every count hits an index-only scan. The
|
|
1112
|
+
* counts are summed — one dapp can mix captcha types over time, and
|
|
1113
|
+
* per-email rate limits should apply across the whole verified surface,
|
|
1114
|
+
* not per-type.
|
|
976
1115
|
*/
|
|
977
1116
|
async countCommitmentsByNormalisedEmail(dappAccount, emailNormalised) {
|
|
978
1117
|
if (!emailNormalised) return 0;
|
|
@@ -982,12 +1121,13 @@ var ProviderDatabase = class ProviderDatabase extends MongoDatabase {
|
|
|
982
1121
|
serverChecked: true,
|
|
983
1122
|
"metadata.emailNormalised": emailNormalised
|
|
984
1123
|
};
|
|
985
|
-
|
|
1124
|
+
return (await Promise.all([
|
|
986
1125
|
tables.commitment.countDocuments(filter),
|
|
987
1126
|
tables.powcaptcha.countDocuments(filter),
|
|
988
|
-
tables.puzzlecaptcha.countDocuments(filter)
|
|
989
|
-
|
|
990
|
-
|
|
1127
|
+
tables.puzzlecaptcha.countDocuments(filter),
|
|
1128
|
+
tables.iconordercaptcha.countDocuments(filter),
|
|
1129
|
+
tables.audiocaptcha.countDocuments(filter)
|
|
1130
|
+
])).reduce((total, count) => total + count, 0);
|
|
991
1131
|
}
|
|
992
1132
|
/**
|
|
993
1133
|
* @description Get Dapp User PoW captcha commitments that have not been counted towards the client's total
|