@prosopo/database 4.2.10 → 4.4.0
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 +3 -3
- package/.turbo/{turbo-build.log → turbo-build$colon$esm.log} +15 -16
- package/.turbo/turbo-build$colon$tsc.log +15 -15
- package/.turbo/turbo-build$colon$types.log +1 -0
- package/CHANGELOG.md +54 -0
- package/dist/cjs/databases/client.cjs +3 -1
- package/dist/cjs/databases/provider.cjs +6 -4
- package/dist/{databases → esm/databases}/client.js +3 -1
- package/dist/{databases → esm/databases}/provider.js +6 -4
- package/dist/tsc/base/index.d.ts.map +1 -0
- package/dist/tsc/base/index.js +3 -0
- package/dist/tsc/base/index.js.map +1 -0
- package/dist/tsc/base/mongo.d.ts.map +1 -0
- package/dist/tsc/base/mongo.js +142 -0
- package/dist/tsc/base/mongo.js.map +1 -0
- package/dist/tsc/base/mongoMemory.d.ts.map +1 -0
- package/dist/tsc/base/mongoMemory.js +21 -0
- package/dist/tsc/base/mongoMemory.js.map +1 -0
- package/dist/tsc/databases/captcha.d.ts.map +1 -0
- package/dist/tsc/databases/captcha.js +276 -0
- package/dist/tsc/databases/captcha.js.map +1 -0
- package/dist/tsc/databases/centralDbStreamer.d.ts.map +1 -0
- package/dist/tsc/databases/centralDbStreamer.js +163 -0
- package/dist/tsc/databases/centralDbStreamer.js.map +1 -0
- package/dist/{databases → tsc/databases}/client.d.ts +1 -1
- package/dist/tsc/databases/client.d.ts.map +1 -0
- package/dist/tsc/databases/client.js +72 -0
- package/dist/tsc/databases/client.js.map +1 -0
- package/dist/tsc/databases/clientRecordCache.d.ts.map +1 -0
- package/dist/tsc/databases/clientRecordCache.js +31 -0
- package/dist/tsc/databases/clientRecordCache.js.map +1 -0
- package/dist/tsc/databases/index.d.ts.map +1 -0
- package/dist/tsc/databases/index.js +18 -0
- package/dist/tsc/databases/index.js.map +1 -0
- package/dist/{databases → tsc/databases}/provider.d.ts +3 -3
- package/dist/tsc/databases/provider.d.ts.map +1 -0
- package/dist/tsc/databases/provider.js +2054 -0
- package/dist/tsc/databases/provider.js.map +1 -0
- package/dist/tsc/errorSites.d.ts.map +1 -0
- package/dist/{errorSites.js.map → tsc/errorSites.js.map} +1 -1
- package/dist/tsc/index.d.ts.map +1 -0
- package/dist/tsc/index.js +7 -0
- package/dist/tsc/index.js.map +1 -0
- package/dist/tsc/mongooseOptions.d.ts.map +1 -0
- package/dist/tsc/mongooseOptions.js +56 -0
- package/dist/tsc/mongooseOptions.js.map +1 -0
- package/dist/tsc/redisCache.d.ts.map +1 -0
- package/dist/tsc/redisCache.js +340 -0
- package/dist/tsc/redisCache.js.map +1 -0
- package/dist/tsc/tests/integration/centralStreamingEnrichment.integration.test.d.ts.map +1 -0
- package/dist/tsc/tests/integration/centralStreamingEnrichment.integration.test.js.map +1 -0
- package/dist/tsc/tests/integration/clientRecordCache.integration.test.d.ts.map +1 -0
- package/dist/tsc/tests/integration/clientRecordCache.integration.test.js.map +1 -0
- package/dist/tsc/tests/integration/commitmentRecordProjection.integration.test.d.ts.map +1 -0
- package/dist/tsc/tests/integration/commitmentRecordProjection.integration.test.js.map +1 -0
- package/dist/tsc/tests/integration/commitmentUpdateTimestamp.integration.test.d.ts.map +1 -0
- package/dist/tsc/tests/integration/commitmentUpdateTimestamp.integration.test.js.map +1 -0
- package/dist/tsc/tests/integration/commitmentUpsertNoMutation.integration.test.d.ts.map +1 -0
- package/dist/tsc/tests/integration/commitmentUpsertNoMutation.integration.test.js.map +1 -0
- package/dist/tsc/tests/integration/countCommitmentsByNormalisedEmail.integration.test.d.ts.map +1 -0
- package/dist/tsc/tests/integration/countCommitmentsByNormalisedEmail.integration.test.js.map +1 -0
- package/dist/tsc/tests/integration/getUpdatedClients.integration.test.d.ts.map +1 -0
- package/dist/{tests → tsc/tests}/integration/getUpdatedClients.integration.test.js +39 -1
- package/dist/tsc/tests/integration/getUpdatedClients.integration.test.js.map +1 -0
- package/dist/tsc/tests/integration/ipInfoPersistence.integration.test.d.ts.map +1 -0
- package/dist/tsc/tests/integration/ipInfoPersistence.integration.test.js.map +1 -0
- package/dist/tsc/tests/integration/projectionContract.d.ts.map +1 -0
- package/dist/tsc/tests/integration/projectionContract.js.map +1 -0
- package/dist/tsc/tests/integration/projectionContracts.integration.test.d.ts.map +1 -0
- package/dist/tsc/tests/integration/projectionContracts.integration.test.js.map +1 -0
- package/dist/tsc/tests/integration/providedIpPipelineCast.integration.test.d.ts.map +1 -0
- package/dist/tsc/tests/integration/providedIpPipelineCast.integration.test.js.map +1 -0
- package/dist/tsc/tests/integration/puzzleCentralStreaming.integration.test.d.ts.map +1 -0
- package/dist/tsc/tests/integration/puzzleCentralStreaming.integration.test.js.map +1 -0
- package/dist/tsc/tests/integration/redisCacheSessionPatch.integration.test.d.ts.map +1 -0
- package/dist/tsc/tests/integration/redisCacheSessionPatch.integration.test.js.map +1 -0
- package/dist/tsc/tests/integration/saveCaptchasSessionUpsert.integration.test.d.ts.map +1 -0
- package/dist/tsc/tests/integration/saveCaptchasSessionUpsert.integration.test.js.map +1 -0
- package/dist/tsc/tests/integration/serverCheckClaim.integration.test.d.ts.map +1 -0
- package/dist/tsc/tests/integration/serverCheckClaim.integration.test.js.map +1 -0
- package/dist/tsc/tests/integration/sessionRecordProjection.integration.test.d.ts.map +1 -0
- package/dist/tsc/tests/integration/sessionRecordProjection.integration.test.js.map +1 -0
- package/dist/tsc/tests/mongoGlobalSetup.d.ts.map +1 -0
- package/dist/tsc/tests/mongoGlobalSetup.js.map +1 -0
- package/dist/tsc/tests/mongooseOptions.unit.test.d.ts.map +1 -0
- package/dist/tsc/tests/mongooseOptions.unit.test.js.map +1 -0
- package/dist/tsc/tests/unit/base/mongo.unit.test.d.ts.map +1 -0
- package/dist/tsc/tests/unit/base/mongo.unit.test.js.map +1 -0
- package/dist/tsc/tests/unit/captchaLabel.unit.test.d.ts.map +1 -0
- package/dist/tsc/tests/unit/captchaLabel.unit.test.js.map +1 -0
- package/dist/tsc/tests/unit/databases/centralDbStreamer.unit.test.d.ts.map +1 -0
- package/dist/tsc/tests/unit/databases/centralDbStreamer.unit.test.js.map +1 -0
- package/dist/tsc/tests/unit/detectorBundleTtl.unit.test.d.ts.map +1 -0
- package/dist/tsc/tests/unit/detectorBundleTtl.unit.test.js.map +1 -0
- package/dist/tsc/tests/unit/inputMethods.unit.test.d.ts.map +1 -0
- package/dist/tsc/tests/unit/inputMethods.unit.test.js.map +1 -0
- package/dist/tsc/tests/unit/redisCacheEscalation.unit.test.d.ts.map +1 -0
- package/dist/tsc/tests/unit/redisCacheEscalation.unit.test.js.map +1 -0
- package/dist/types/base/index.d.ts +3 -0
- package/dist/types/base/index.d.ts.map +1 -0
- package/dist/types/base/mongo.d.ts +24 -0
- package/dist/types/base/mongo.d.ts.map +1 -0
- package/dist/types/base/mongoMemory.d.ts +10 -0
- package/dist/types/base/mongoMemory.d.ts.map +1 -0
- package/dist/types/databases/captcha.d.ts +34 -0
- package/dist/types/databases/captcha.d.ts.map +1 -0
- package/dist/types/databases/centralDbStreamer.d.ts +29 -0
- package/dist/types/databases/centralDbStreamer.d.ts.map +1 -0
- package/dist/types/databases/client.d.ts +12 -0
- package/dist/types/databases/client.d.ts.map +1 -0
- package/dist/types/databases/clientRecordCache.d.ts +11 -0
- package/dist/types/databases/clientRecordCache.d.ts.map +1 -0
- package/dist/types/databases/index.d.ts +17 -0
- package/dist/types/databases/index.d.ts.map +1 -0
- package/dist/types/databases/provider.d.ts +171 -0
- package/dist/types/databases/provider.d.ts.map +1 -0
- package/dist/types/errorSites.d.ts +57 -0
- package/dist/types/errorSites.d.ts.map +1 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/mongooseOptions.d.ts +15 -0
- package/dist/types/mongooseOptions.d.ts.map +1 -0
- package/dist/types/redisCache.d.ts +26 -0
- package/dist/types/redisCache.d.ts.map +1 -0
- package/dist/types/tests/integration/centralStreamingEnrichment.integration.test.d.ts +2 -0
- package/dist/types/tests/integration/centralStreamingEnrichment.integration.test.d.ts.map +1 -0
- package/dist/types/tests/integration/clientRecordCache.integration.test.d.ts +2 -0
- package/dist/types/tests/integration/clientRecordCache.integration.test.d.ts.map +1 -0
- package/dist/types/tests/integration/commitmentRecordProjection.integration.test.d.ts +2 -0
- package/dist/types/tests/integration/commitmentRecordProjection.integration.test.d.ts.map +1 -0
- package/dist/types/tests/integration/commitmentUpdateTimestamp.integration.test.d.ts +2 -0
- package/dist/types/tests/integration/commitmentUpdateTimestamp.integration.test.d.ts.map +1 -0
- package/dist/types/tests/integration/commitmentUpsertNoMutation.integration.test.d.ts +2 -0
- package/dist/types/tests/integration/commitmentUpsertNoMutation.integration.test.d.ts.map +1 -0
- package/dist/types/tests/integration/countCommitmentsByNormalisedEmail.integration.test.d.ts +2 -0
- package/dist/types/tests/integration/countCommitmentsByNormalisedEmail.integration.test.d.ts.map +1 -0
- package/dist/types/tests/integration/getUpdatedClients.integration.test.d.ts +2 -0
- package/dist/types/tests/integration/getUpdatedClients.integration.test.d.ts.map +1 -0
- package/dist/types/tests/integration/ipInfoPersistence.integration.test.d.ts +2 -0
- package/dist/types/tests/integration/ipInfoPersistence.integration.test.d.ts.map +1 -0
- package/dist/types/tests/integration/projectionContract.d.ts +9 -0
- package/dist/types/tests/integration/projectionContract.d.ts.map +1 -0
- package/dist/types/tests/integration/projectionContracts.integration.test.d.ts +2 -0
- package/dist/types/tests/integration/projectionContracts.integration.test.d.ts.map +1 -0
- package/dist/types/tests/integration/providedIpPipelineCast.integration.test.d.ts +2 -0
- package/dist/types/tests/integration/providedIpPipelineCast.integration.test.d.ts.map +1 -0
- package/dist/types/tests/integration/puzzleCentralStreaming.integration.test.d.ts +2 -0
- package/dist/types/tests/integration/puzzleCentralStreaming.integration.test.d.ts.map +1 -0
- package/dist/types/tests/integration/redisCacheSessionPatch.integration.test.d.ts +2 -0
- package/dist/types/tests/integration/redisCacheSessionPatch.integration.test.d.ts.map +1 -0
- package/dist/types/tests/integration/saveCaptchasSessionUpsert.integration.test.d.ts +2 -0
- package/dist/types/tests/integration/saveCaptchasSessionUpsert.integration.test.d.ts.map +1 -0
- package/dist/types/tests/integration/serverCheckClaim.integration.test.d.ts +2 -0
- package/dist/types/tests/integration/serverCheckClaim.integration.test.d.ts.map +1 -0
- package/dist/types/tests/integration/sessionRecordProjection.integration.test.d.ts +2 -0
- package/dist/types/tests/integration/sessionRecordProjection.integration.test.d.ts.map +1 -0
- package/dist/types/tests/mongoGlobalSetup.d.ts +2 -0
- package/dist/types/tests/mongoGlobalSetup.d.ts.map +1 -0
- package/dist/types/tests/mongooseOptions.unit.test.d.ts +2 -0
- package/dist/types/tests/mongooseOptions.unit.test.d.ts.map +1 -0
- package/dist/types/tests/unit/base/mongo.unit.test.d.ts +2 -0
- package/dist/types/tests/unit/base/mongo.unit.test.d.ts.map +1 -0
- package/dist/types/tests/unit/captchaLabel.unit.test.d.ts +2 -0
- package/dist/types/tests/unit/captchaLabel.unit.test.d.ts.map +1 -0
- package/dist/types/tests/unit/databases/centralDbStreamer.unit.test.d.ts +2 -0
- package/dist/types/tests/unit/databases/centralDbStreamer.unit.test.d.ts.map +1 -0
- package/dist/types/tests/unit/detectorBundleTtl.unit.test.d.ts +2 -0
- package/dist/types/tests/unit/detectorBundleTtl.unit.test.d.ts.map +1 -0
- package/dist/types/tests/unit/inputMethods.unit.test.d.ts +2 -0
- package/dist/types/tests/unit/inputMethods.unit.test.d.ts.map +1 -0
- package/dist/types/tests/unit/redisCacheEscalation.unit.test.d.ts +2 -0
- package/dist/types/tests/unit/redisCacheEscalation.unit.test.d.ts.map +1 -0
- package/package.json +16 -17
- package/dist/base/index.d.ts.map +0 -1
- package/dist/base/index.js.map +0 -1
- package/dist/base/mongo.d.ts.map +0 -1
- package/dist/base/mongo.js.map +0 -1
- package/dist/base/mongoMemory.d.ts.map +0 -1
- package/dist/base/mongoMemory.js.map +0 -1
- package/dist/databases/captcha.d.ts.map +0 -1
- package/dist/databases/captcha.js.map +0 -1
- package/dist/databases/centralDbStreamer.d.ts.map +0 -1
- package/dist/databases/centralDbStreamer.js.map +0 -1
- package/dist/databases/client.d.ts.map +0 -1
- package/dist/databases/client.js.map +0 -1
- package/dist/databases/clientRecordCache.d.ts.map +0 -1
- package/dist/databases/clientRecordCache.js.map +0 -1
- package/dist/databases/index.d.ts.map +0 -1
- package/dist/databases/index.js.map +0 -1
- package/dist/databases/provider.d.ts.map +0 -1
- package/dist/databases/provider.js.map +0 -1
- package/dist/errorSites.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/mongooseOptions.d.ts.map +0 -1
- package/dist/mongooseOptions.js.map +0 -1
- package/dist/redisCache.d.ts.map +0 -1
- package/dist/redisCache.js.map +0 -1
- package/dist/tests/integration/centralStreamingEnrichment.integration.test.d.ts.map +0 -1
- package/dist/tests/integration/centralStreamingEnrichment.integration.test.js.map +0 -1
- package/dist/tests/integration/clientRecordCache.integration.test.d.ts.map +0 -1
- package/dist/tests/integration/clientRecordCache.integration.test.js.map +0 -1
- package/dist/tests/integration/commitmentRecordProjection.integration.test.d.ts.map +0 -1
- package/dist/tests/integration/commitmentRecordProjection.integration.test.js.map +0 -1
- package/dist/tests/integration/commitmentUpdateTimestamp.integration.test.d.ts.map +0 -1
- package/dist/tests/integration/commitmentUpdateTimestamp.integration.test.js.map +0 -1
- package/dist/tests/integration/commitmentUpsertNoMutation.integration.test.d.ts.map +0 -1
- package/dist/tests/integration/commitmentUpsertNoMutation.integration.test.js.map +0 -1
- package/dist/tests/integration/countCommitmentsByNormalisedEmail.integration.test.d.ts.map +0 -1
- package/dist/tests/integration/countCommitmentsByNormalisedEmail.integration.test.js.map +0 -1
- package/dist/tests/integration/getUpdatedClients.integration.test.d.ts.map +0 -1
- package/dist/tests/integration/getUpdatedClients.integration.test.js.map +0 -1
- package/dist/tests/integration/ipInfoPersistence.integration.test.d.ts.map +0 -1
- package/dist/tests/integration/ipInfoPersistence.integration.test.js.map +0 -1
- package/dist/tests/integration/projectionContract.d.ts.map +0 -1
- package/dist/tests/integration/projectionContract.js.map +0 -1
- package/dist/tests/integration/projectionContracts.integration.test.d.ts.map +0 -1
- package/dist/tests/integration/projectionContracts.integration.test.js.map +0 -1
- package/dist/tests/integration/providedIpPipelineCast.integration.test.d.ts.map +0 -1
- package/dist/tests/integration/providedIpPipelineCast.integration.test.js.map +0 -1
- package/dist/tests/integration/puzzleCentralStreaming.integration.test.d.ts.map +0 -1
- package/dist/tests/integration/puzzleCentralStreaming.integration.test.js.map +0 -1
- package/dist/tests/integration/redisCacheSessionPatch.integration.test.d.ts.map +0 -1
- package/dist/tests/integration/redisCacheSessionPatch.integration.test.js.map +0 -1
- package/dist/tests/integration/saveCaptchasSessionUpsert.integration.test.d.ts.map +0 -1
- package/dist/tests/integration/saveCaptchasSessionUpsert.integration.test.js.map +0 -1
- package/dist/tests/integration/serverCheckClaim.integration.test.d.ts.map +0 -1
- package/dist/tests/integration/serverCheckClaim.integration.test.js.map +0 -1
- package/dist/tests/integration/sessionRecordProjection.integration.test.d.ts.map +0 -1
- package/dist/tests/integration/sessionRecordProjection.integration.test.js.map +0 -1
- package/dist/tests/mongoGlobalSetup.d.ts.map +0 -1
- package/dist/tests/mongoGlobalSetup.js.map +0 -1
- package/dist/tests/mongooseOptions.unit.test.d.ts.map +0 -1
- package/dist/tests/mongooseOptions.unit.test.js.map +0 -1
- package/dist/tests/unit/base/mongo.unit.test.d.ts.map +0 -1
- package/dist/tests/unit/base/mongo.unit.test.js.map +0 -1
- package/dist/tests/unit/captchaLabel.unit.test.d.ts.map +0 -1
- package/dist/tests/unit/captchaLabel.unit.test.js.map +0 -1
- package/dist/tests/unit/databases/centralDbStreamer.unit.test.d.ts.map +0 -1
- package/dist/tests/unit/databases/centralDbStreamer.unit.test.js.map +0 -1
- package/dist/tests/unit/detectorBundleTtl.unit.test.d.ts.map +0 -1
- package/dist/tests/unit/detectorBundleTtl.unit.test.js.map +0 -1
- package/dist/tests/unit/inputMethods.unit.test.d.ts.map +0 -1
- package/dist/tests/unit/inputMethods.unit.test.js.map +0 -1
- package/dist/tests/unit/redisCacheEscalation.unit.test.d.ts.map +0 -1
- package/dist/tests/unit/redisCacheEscalation.unit.test.js.map +0 -1
- /package/dist/{_virtual → esm/_virtual}/_rolldown/runtime.js +0 -0
- /package/dist/{base → esm/base}/index.js +0 -0
- /package/dist/{base → esm/base}/mongo.js +0 -0
- /package/dist/{base → esm/base}/mongoMemory.js +0 -0
- /package/dist/{databases → esm/databases}/captcha.js +0 -0
- /package/dist/{databases → esm/databases}/centralDbStreamer.js +0 -0
- /package/dist/{databases → esm/databases}/clientRecordCache.js +0 -0
- /package/dist/{databases → esm/databases}/index.js +0 -0
- /package/dist/{index.js → esm/index.js} +0 -0
- /package/dist/{mongooseOptions.js → esm/mongooseOptions.js} +0 -0
- /package/dist/{redisCache.js → esm/redisCache.js} +0 -0
- /package/dist/{base → tsc/base}/index.d.ts +0 -0
- /package/dist/{base → tsc/base}/mongo.d.ts +0 -0
- /package/dist/{base → tsc/base}/mongoMemory.d.ts +0 -0
- /package/dist/{databases → tsc/databases}/captcha.d.ts +0 -0
- /package/dist/{databases → tsc/databases}/centralDbStreamer.d.ts +0 -0
- /package/dist/{databases → tsc/databases}/clientRecordCache.d.ts +0 -0
- /package/dist/{databases → tsc/databases}/index.d.ts +0 -0
- /package/dist/{errorSites.d.ts → tsc/errorSites.d.ts} +0 -0
- /package/dist/{errorSites.js → tsc/errorSites.js} +0 -0
- /package/dist/{index.d.ts → tsc/index.d.ts} +0 -0
- /package/dist/{mongooseOptions.d.ts → tsc/mongooseOptions.d.ts} +0 -0
- /package/dist/{redisCache.d.ts → tsc/redisCache.d.ts} +0 -0
- /package/dist/{tests → tsc/tests}/integration/centralStreamingEnrichment.integration.test.d.ts +0 -0
- /package/dist/{tests → tsc/tests}/integration/centralStreamingEnrichment.integration.test.js +0 -0
- /package/dist/{tests → tsc/tests}/integration/clientRecordCache.integration.test.d.ts +0 -0
- /package/dist/{tests → tsc/tests}/integration/clientRecordCache.integration.test.js +0 -0
- /package/dist/{tests → tsc/tests}/integration/commitmentRecordProjection.integration.test.d.ts +0 -0
- /package/dist/{tests → tsc/tests}/integration/commitmentRecordProjection.integration.test.js +0 -0
- /package/dist/{tests → tsc/tests}/integration/commitmentUpdateTimestamp.integration.test.d.ts +0 -0
- /package/dist/{tests → tsc/tests}/integration/commitmentUpdateTimestamp.integration.test.js +0 -0
- /package/dist/{tests → tsc/tests}/integration/commitmentUpsertNoMutation.integration.test.d.ts +0 -0
- /package/dist/{tests → tsc/tests}/integration/commitmentUpsertNoMutation.integration.test.js +0 -0
- /package/dist/{tests → tsc/tests}/integration/countCommitmentsByNormalisedEmail.integration.test.d.ts +0 -0
- /package/dist/{tests → tsc/tests}/integration/countCommitmentsByNormalisedEmail.integration.test.js +0 -0
- /package/dist/{tests → tsc/tests}/integration/getUpdatedClients.integration.test.d.ts +0 -0
- /package/dist/{tests → tsc/tests}/integration/ipInfoPersistence.integration.test.d.ts +0 -0
- /package/dist/{tests → tsc/tests}/integration/ipInfoPersistence.integration.test.js +0 -0
- /package/dist/{tests → tsc/tests}/integration/projectionContract.d.ts +0 -0
- /package/dist/{tests → tsc/tests}/integration/projectionContract.js +0 -0
- /package/dist/{tests → tsc/tests}/integration/projectionContracts.integration.test.d.ts +0 -0
- /package/dist/{tests → tsc/tests}/integration/projectionContracts.integration.test.js +0 -0
- /package/dist/{tests → tsc/tests}/integration/providedIpPipelineCast.integration.test.d.ts +0 -0
- /package/dist/{tests → tsc/tests}/integration/providedIpPipelineCast.integration.test.js +0 -0
- /package/dist/{tests → tsc/tests}/integration/puzzleCentralStreaming.integration.test.d.ts +0 -0
- /package/dist/{tests → tsc/tests}/integration/puzzleCentralStreaming.integration.test.js +0 -0
- /package/dist/{tests → tsc/tests}/integration/redisCacheSessionPatch.integration.test.d.ts +0 -0
- /package/dist/{tests → tsc/tests}/integration/redisCacheSessionPatch.integration.test.js +0 -0
- /package/dist/{tests → tsc/tests}/integration/saveCaptchasSessionUpsert.integration.test.d.ts +0 -0
- /package/dist/{tests → tsc/tests}/integration/saveCaptchasSessionUpsert.integration.test.js +0 -0
- /package/dist/{tests → tsc/tests}/integration/serverCheckClaim.integration.test.d.ts +0 -0
- /package/dist/{tests → tsc/tests}/integration/serverCheckClaim.integration.test.js +0 -0
- /package/dist/{tests → tsc/tests}/integration/sessionRecordProjection.integration.test.d.ts +0 -0
- /package/dist/{tests → tsc/tests}/integration/sessionRecordProjection.integration.test.js +0 -0
- /package/dist/{tests → tsc/tests}/mongoGlobalSetup.d.ts +0 -0
- /package/dist/{tests → tsc/tests}/mongoGlobalSetup.js +0 -0
- /package/dist/{tests → tsc/tests}/mongooseOptions.unit.test.d.ts +0 -0
- /package/dist/{tests → tsc/tests}/mongooseOptions.unit.test.js +0 -0
- /package/dist/{tests → tsc/tests}/unit/base/mongo.unit.test.d.ts +0 -0
- /package/dist/{tests → tsc/tests}/unit/base/mongo.unit.test.js +0 -0
- /package/dist/{tests → tsc/tests}/unit/captchaLabel.unit.test.d.ts +0 -0
- /package/dist/{tests → tsc/tests}/unit/captchaLabel.unit.test.js +0 -0
- /package/dist/{tests → tsc/tests}/unit/databases/centralDbStreamer.unit.test.d.ts +0 -0
- /package/dist/{tests → tsc/tests}/unit/databases/centralDbStreamer.unit.test.js +0 -0
- /package/dist/{tests → tsc/tests}/unit/detectorBundleTtl.unit.test.d.ts +0 -0
- /package/dist/{tests → tsc/tests}/unit/detectorBundleTtl.unit.test.js +0 -0
- /package/dist/{tests → tsc/tests}/unit/inputMethods.unit.test.d.ts +0 -0
- /package/dist/{tests → tsc/tests}/unit/inputMethods.unit.test.js +0 -0
- /package/dist/{tests → tsc/tests}/unit/redisCacheEscalation.unit.test.d.ts +0 -0
- /package/dist/{tests → tsc/tests}/unit/redisCacheEscalation.unit.test.js +0 -0
|
@@ -0,0 +1,2054 @@
|
|
|
1
|
+
import { isHex } from "@polkadot/util/is";
|
|
2
|
+
import { ProsopoError } from "@prosopo/common";
|
|
3
|
+
import { connectToRedis, setupRedisIndex, } from "@prosopo/redis-client";
|
|
4
|
+
import { CaptchaStates, CaptchaStatus, CaptchaType, DatasetWithIdsAndTreeSchema, DecisionMachineKind, StoredStatusNames, UserCommitmentSchema, isBlockingCaptchaResult, } from "@prosopo/types";
|
|
5
|
+
import { AudioCaptchaRecordSchema, CaptchaRecordSchema, ClientRecordSchema, DatasetRecordSchema, DecisionMachineArtifactRecordSchema, IconOrderCaptchaRecordSchema, PoWCaptchaRecordSchema, PuzzleCaptchaRecordSchema, SESSION_PROJECTION, ScheduledTaskRecordSchema, ScheduledTaskSchema, SessionRecordSchema, SolutionRecordSchema, SpamEmailDomainRecordSchema, UserCommitmentRecordSchema, UserSolutionRecordSchema, } from "@prosopo/types-database";
|
|
6
|
+
import { accessRulesRedisIndex, createRedisAccessRulesStorage, } from "@prosopo/user-access-policy/redis";
|
|
7
|
+
import { assertCoordsSafe, buildDomainSuffixCandidates } from "@prosopo/util";
|
|
8
|
+
import { MongoDatabase } from "../base/mongo.js";
|
|
9
|
+
import { clientRecordCache } from "./clientRecordCache.js";
|
|
10
|
+
const TWENTY_FOUR_HOURS_IN_MS = 24 * 60 * 60 * 1000;
|
|
11
|
+
const MAX_DOMAIN_SUFFIX_CANDIDATES = 5;
|
|
12
|
+
var TableNames;
|
|
13
|
+
(function (TableNames) {
|
|
14
|
+
TableNames["captcha"] = "captcha";
|
|
15
|
+
TableNames["dataset"] = "dataset";
|
|
16
|
+
TableNames["solution"] = "solution";
|
|
17
|
+
TableNames["commitment"] = "commitment";
|
|
18
|
+
TableNames["usersolution"] = "usersolution";
|
|
19
|
+
TableNames["scheduler"] = "scheduler";
|
|
20
|
+
TableNames["powcaptcha"] = "powcaptcha";
|
|
21
|
+
TableNames["puzzlecaptcha"] = "puzzlecaptcha";
|
|
22
|
+
TableNames["audiocaptcha"] = "audiocaptcha";
|
|
23
|
+
TableNames["iconordercaptcha"] = "iconordercaptcha";
|
|
24
|
+
TableNames["client"] = "client";
|
|
25
|
+
TableNames["session"] = "session";
|
|
26
|
+
TableNames["detector"] = "detector";
|
|
27
|
+
TableNames["decisionMachine"] = "decisionMachine";
|
|
28
|
+
TableNames["spamEmailDomain"] = "spamEmailDomain";
|
|
29
|
+
})(TableNames || (TableNames = {}));
|
|
30
|
+
const PROVIDER_TABLES = [
|
|
31
|
+
{
|
|
32
|
+
collectionName: TableNames.captcha,
|
|
33
|
+
modelName: "Captcha",
|
|
34
|
+
schema: CaptchaRecordSchema,
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
collectionName: TableNames.powcaptcha,
|
|
38
|
+
modelName: "PowCaptcha",
|
|
39
|
+
schema: PoWCaptchaRecordSchema,
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
collectionName: TableNames.puzzlecaptcha,
|
|
43
|
+
modelName: "PuzzleCaptcha",
|
|
44
|
+
schema: PuzzleCaptchaRecordSchema,
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
collectionName: TableNames.audiocaptcha,
|
|
48
|
+
modelName: "AudioCaptcha",
|
|
49
|
+
schema: AudioCaptchaRecordSchema,
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
collectionName: TableNames.iconordercaptcha,
|
|
53
|
+
modelName: "IconOrderCaptcha",
|
|
54
|
+
schema: IconOrderCaptchaRecordSchema,
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
collectionName: TableNames.dataset,
|
|
58
|
+
modelName: "Dataset",
|
|
59
|
+
schema: DatasetRecordSchema,
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
collectionName: TableNames.solution,
|
|
63
|
+
modelName: "Solution",
|
|
64
|
+
schema: SolutionRecordSchema,
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
collectionName: TableNames.commitment,
|
|
68
|
+
modelName: "UserCommitment",
|
|
69
|
+
schema: UserCommitmentRecordSchema,
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
collectionName: TableNames.usersolution,
|
|
73
|
+
modelName: "UserSolution",
|
|
74
|
+
schema: UserSolutionRecordSchema,
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
collectionName: TableNames.scheduler,
|
|
78
|
+
modelName: "Scheduler",
|
|
79
|
+
schema: ScheduledTaskRecordSchema,
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
collectionName: TableNames.client,
|
|
83
|
+
modelName: "Client",
|
|
84
|
+
schema: ClientRecordSchema,
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
collectionName: TableNames.session,
|
|
88
|
+
modelName: "Session",
|
|
89
|
+
schema: SessionRecordSchema,
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
collectionName: TableNames.decisionMachine,
|
|
93
|
+
modelName: "DecisionMachine",
|
|
94
|
+
schema: DecisionMachineArtifactRecordSchema,
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
collectionName: TableNames.spamEmailDomain,
|
|
98
|
+
modelName: "SpamEmailDomain",
|
|
99
|
+
schema: SpamEmailDomainRecordSchema,
|
|
100
|
+
},
|
|
101
|
+
];
|
|
102
|
+
export class ProviderDatabase extends MongoDatabase {
|
|
103
|
+
constructor(options) {
|
|
104
|
+
super(options.mongo.url, options.mongo.dbname, options.mongo.authSource, options.logger);
|
|
105
|
+
this.options = options;
|
|
106
|
+
this.tables = {};
|
|
107
|
+
this.indexesEnsured = false;
|
|
108
|
+
this.tables = {};
|
|
109
|
+
this.redisAccessRulesConnection = null;
|
|
110
|
+
this.redisConnection = null;
|
|
111
|
+
this.userAccessRulesStorage = null;
|
|
112
|
+
}
|
|
113
|
+
setCentralDbStreamer(streamer) {
|
|
114
|
+
this.centralStreamer = streamer;
|
|
115
|
+
}
|
|
116
|
+
hasCentralDbStreamer() {
|
|
117
|
+
return this.centralStreamer !== undefined;
|
|
118
|
+
}
|
|
119
|
+
async connect() {
|
|
120
|
+
await super.connect();
|
|
121
|
+
this.loadTables();
|
|
122
|
+
await this.setupRedis();
|
|
123
|
+
}
|
|
124
|
+
async setupRedis() {
|
|
125
|
+
this.redisConnection = connectToRedis({
|
|
126
|
+
url: this.options.redis?.url,
|
|
127
|
+
password: this.options.redis?.password,
|
|
128
|
+
logger: this.logger,
|
|
129
|
+
});
|
|
130
|
+
this.redisAccessRulesConnection = setupRedisIndex(this.redisConnection, {
|
|
131
|
+
...accessRulesRedisIndex,
|
|
132
|
+
name: this.options.redis?.indexName || accessRulesRedisIndex.name,
|
|
133
|
+
}, this.logger);
|
|
134
|
+
this.userAccessRulesStorage = createRedisAccessRulesStorage(this.redisAccessRulesConnection, this.logger);
|
|
135
|
+
}
|
|
136
|
+
loadTables() {
|
|
137
|
+
const tables = {};
|
|
138
|
+
PROVIDER_TABLES.map(({ collectionName, modelName, schema }) => {
|
|
139
|
+
if (this.connection) {
|
|
140
|
+
tables[collectionName] = this.connection.model(modelName, schema);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
this.tables = tables;
|
|
144
|
+
}
|
|
145
|
+
getTables() {
|
|
146
|
+
if (!this.tables) {
|
|
147
|
+
throw new ProsopoError({
|
|
148
|
+
code: "DB_UNAVAILABLE",
|
|
149
|
+
errorSite: "DBSE-5",
|
|
150
|
+
message: "Tables undefined",
|
|
151
|
+
context: { failedFuncName: this.getTables.name },
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
return this.tables;
|
|
155
|
+
}
|
|
156
|
+
getRedisAccessRulesConnection() {
|
|
157
|
+
if (null === this.redisAccessRulesConnection) {
|
|
158
|
+
throw new ProsopoError({
|
|
159
|
+
code: "DB_UNAVAILABLE",
|
|
160
|
+
errorSite: "DBSE-6",
|
|
161
|
+
message: "Redis access rules connection undefined",
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
return this.redisAccessRulesConnection;
|
|
165
|
+
}
|
|
166
|
+
getRedisConnection() {
|
|
167
|
+
if (null === this.redisConnection) {
|
|
168
|
+
throw new ProsopoError({
|
|
169
|
+
code: "DB_UNAVAILABLE",
|
|
170
|
+
errorSite: "DBSE-7",
|
|
171
|
+
message: "Redis access rules connection undefined",
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
return this.redisConnection;
|
|
175
|
+
}
|
|
176
|
+
async ensureIndexes() {
|
|
177
|
+
const indexPromises = [];
|
|
178
|
+
if (!this.indexesEnsured) {
|
|
179
|
+
PROVIDER_TABLES.map(({ collectionName }) => {
|
|
180
|
+
indexPromises.push(new Promise((resolve) => {
|
|
181
|
+
if (this.connected) {
|
|
182
|
+
this.tables[collectionName].collection.dropIndexes().then(() => {
|
|
183
|
+
this.tables[collectionName]
|
|
184
|
+
.ensureIndexes()
|
|
185
|
+
.then(() => {
|
|
186
|
+
this.logger.info(() => ({
|
|
187
|
+
msg: `Indexes ensured for collection ${collectionName}`,
|
|
188
|
+
}));
|
|
189
|
+
resolve();
|
|
190
|
+
})
|
|
191
|
+
.catch((err) => {
|
|
192
|
+
this.logger.warn(() => ({
|
|
193
|
+
err,
|
|
194
|
+
msg: `Error creating indexes for collection ${collectionName}`,
|
|
195
|
+
}));
|
|
196
|
+
resolve();
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
this.logger.info(() => ({
|
|
202
|
+
msg: `Skipping index creation for collection ${collectionName} as not connected`,
|
|
203
|
+
}));
|
|
204
|
+
resolve();
|
|
205
|
+
}
|
|
206
|
+
}));
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
await Promise.all(indexPromises);
|
|
210
|
+
this.indexesEnsured = true;
|
|
211
|
+
}
|
|
212
|
+
getUserAccessRulesStorage() {
|
|
213
|
+
if (null === this.userAccessRulesStorage) {
|
|
214
|
+
throw new ProsopoError({
|
|
215
|
+
code: "DB_UNAVAILABLE",
|
|
216
|
+
errorSite: "DBSE-8",
|
|
217
|
+
message: "User access rules storage undefined",
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
return this.userAccessRulesStorage;
|
|
221
|
+
}
|
|
222
|
+
async storeDataset(dataset) {
|
|
223
|
+
try {
|
|
224
|
+
this.logger.debug(() => ({
|
|
225
|
+
data: { datasetId: dataset.datasetId },
|
|
226
|
+
msg: "Storing dataset in database",
|
|
227
|
+
}));
|
|
228
|
+
const parsedDataset = DatasetWithIdsAndTreeSchema.parse(dataset);
|
|
229
|
+
const datasetDoc = {
|
|
230
|
+
datasetId: parsedDataset.datasetId,
|
|
231
|
+
datasetContentId: parsedDataset.datasetContentId,
|
|
232
|
+
format: parsedDataset.format,
|
|
233
|
+
contentTree: parsedDataset.contentTree,
|
|
234
|
+
solutionTree: parsedDataset.solutionTree,
|
|
235
|
+
};
|
|
236
|
+
const filter = {
|
|
237
|
+
datasetId: parsedDataset.datasetId,
|
|
238
|
+
};
|
|
239
|
+
await this.tables.dataset?.updateOne(filter, { $set: datasetDoc }, { upsert: true });
|
|
240
|
+
const captchaDocs = parsedDataset.captchas.map(({ solution, ...captcha }, index) => ({
|
|
241
|
+
...captcha,
|
|
242
|
+
datasetId: parsedDataset.datasetId,
|
|
243
|
+
datasetContentId: parsedDataset.datasetContentId,
|
|
244
|
+
index,
|
|
245
|
+
solved: !!solution?.length,
|
|
246
|
+
}));
|
|
247
|
+
this.logger.debug(() => ({
|
|
248
|
+
msg: "Inserting captcha records",
|
|
249
|
+
}));
|
|
250
|
+
if (captchaDocs.length) {
|
|
251
|
+
await this.tables?.captcha.bulkWrite(captchaDocs.map((captchaDoc) => ({
|
|
252
|
+
updateOne: {
|
|
253
|
+
filter: { captchaId: captchaDoc.captchaId },
|
|
254
|
+
update: {
|
|
255
|
+
$set: captchaDoc,
|
|
256
|
+
$setOnInsert: { randomKey: Math.random() },
|
|
257
|
+
},
|
|
258
|
+
upsert: true,
|
|
259
|
+
},
|
|
260
|
+
})));
|
|
261
|
+
}
|
|
262
|
+
const captchaSolutionDocs = parsedDataset.captchas
|
|
263
|
+
.filter(({ solution }) => solution?.length)
|
|
264
|
+
.map((captcha) => ({
|
|
265
|
+
captchaId: captcha.captchaId,
|
|
266
|
+
captchaContentId: captcha.captchaContentId,
|
|
267
|
+
solution: captcha.solution,
|
|
268
|
+
salt: captcha.salt,
|
|
269
|
+
datasetId: parsedDataset.datasetId,
|
|
270
|
+
datasetContentId: parsedDataset.datasetContentId,
|
|
271
|
+
}));
|
|
272
|
+
this.logger.debug(() => ({
|
|
273
|
+
msg: "Inserting solution records",
|
|
274
|
+
}));
|
|
275
|
+
if (captchaSolutionDocs.length) {
|
|
276
|
+
await this.tables?.solution.bulkWrite(captchaSolutionDocs.map((captchaSolutionDoc) => ({
|
|
277
|
+
updateOne: {
|
|
278
|
+
filter: { captchaId: captchaSolutionDoc.captchaId },
|
|
279
|
+
update: { $set: captchaSolutionDoc },
|
|
280
|
+
upsert: true,
|
|
281
|
+
},
|
|
282
|
+
})));
|
|
283
|
+
}
|
|
284
|
+
this.logger.debug(() => ({
|
|
285
|
+
msg: "Dataset stored in database",
|
|
286
|
+
}));
|
|
287
|
+
}
|
|
288
|
+
catch (err) {
|
|
289
|
+
throw new ProsopoError({
|
|
290
|
+
code: "DB_QUERY_FAILED",
|
|
291
|
+
errorSite: "DBSE-9",
|
|
292
|
+
message: "Data set load failed",
|
|
293
|
+
cause: err,
|
|
294
|
+
context: { failedFuncName: this.storeDataset.name },
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
async getSolutions(datasetId) {
|
|
299
|
+
const filter = { datasetId };
|
|
300
|
+
const docs = await this.tables?.solution
|
|
301
|
+
.find(filter)
|
|
302
|
+
.lean();
|
|
303
|
+
return docs ? docs : [];
|
|
304
|
+
}
|
|
305
|
+
async getSolutionByCaptchaId(captchaId) {
|
|
306
|
+
const filter = { captchaId };
|
|
307
|
+
const doc = await this.tables?.solution
|
|
308
|
+
.findOne(filter)
|
|
309
|
+
.lean();
|
|
310
|
+
return doc || null;
|
|
311
|
+
}
|
|
312
|
+
async getDataset(datasetId) {
|
|
313
|
+
const filter = { datasetId };
|
|
314
|
+
const datasetDoc = await this.tables?.dataset.findOne(filter).lean();
|
|
315
|
+
if (datasetDoc) {
|
|
316
|
+
const { datasetContentId, format, contentTree, solutionTree } = datasetDoc;
|
|
317
|
+
const captchas = (await this.tables?.captcha.find(filter).lean()) || [];
|
|
318
|
+
const solutions = (await this.tables?.solution.find(filter).lean()) ||
|
|
319
|
+
[];
|
|
320
|
+
const solutionsKeyed = {};
|
|
321
|
+
for (const solution of solutions) {
|
|
322
|
+
solutionsKeyed[solution.captchaId] = solution;
|
|
323
|
+
}
|
|
324
|
+
return {
|
|
325
|
+
datasetId,
|
|
326
|
+
datasetContentId,
|
|
327
|
+
format,
|
|
328
|
+
contentTree: contentTree || [],
|
|
329
|
+
solutionTree: solutionTree || [],
|
|
330
|
+
captchas: captchas.map((captchaDoc) => {
|
|
331
|
+
const { captchaId, captchaContentId, items, target, salt, solved } = captchaDoc;
|
|
332
|
+
const solution = solutionsKeyed[captchaId];
|
|
333
|
+
return {
|
|
334
|
+
captchaId,
|
|
335
|
+
captchaContentId,
|
|
336
|
+
solved: !!solved,
|
|
337
|
+
salt,
|
|
338
|
+
items,
|
|
339
|
+
target,
|
|
340
|
+
solution: solved && solution ? solution.solution : [],
|
|
341
|
+
};
|
|
342
|
+
}),
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
throw new ProsopoError({
|
|
346
|
+
code: "DB_QUERY_FAILED",
|
|
347
|
+
errorSite: "DBSE-10",
|
|
348
|
+
message: "Failed to get dataset",
|
|
349
|
+
context: { failedFuncName: this.getDataset.name, datasetId },
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
async getRandomCaptcha(solved, datasetId, size) {
|
|
353
|
+
if (!isHex(datasetId)) {
|
|
354
|
+
throw new ProsopoError({
|
|
355
|
+
code: "INVALID_DATA",
|
|
356
|
+
errorSite: "DBSE-11",
|
|
357
|
+
message: "Invalid hash",
|
|
358
|
+
context: { failedFuncName: this.getRandomCaptcha.name, datasetId },
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
const sampleSize = size ? Math.abs(Math.trunc(size)) : 1;
|
|
362
|
+
const projection = {
|
|
363
|
+
_id: 0,
|
|
364
|
+
datasetId: 1,
|
|
365
|
+
datasetContentId: 1,
|
|
366
|
+
captchaId: 1,
|
|
367
|
+
captchaContentId: 1,
|
|
368
|
+
items: 1,
|
|
369
|
+
target: 1,
|
|
370
|
+
};
|
|
371
|
+
const baseFilter = { datasetId: datasetId, solved };
|
|
372
|
+
const pivot = Math.random();
|
|
373
|
+
const head = (await this.tables?.captcha
|
|
374
|
+
.find({ ...baseFilter, randomKey: { $gte: pivot } }, projection)
|
|
375
|
+
.sort({ randomKey: 1 })
|
|
376
|
+
.limit(sampleSize)
|
|
377
|
+
.lean()) ?? [];
|
|
378
|
+
let docs = head;
|
|
379
|
+
if (docs.length < sampleSize) {
|
|
380
|
+
const tail = (await this.tables?.captcha
|
|
381
|
+
.find({ ...baseFilter, randomKey: { $lt: pivot } }, projection)
|
|
382
|
+
.sort({ randomKey: 1 })
|
|
383
|
+
.limit(sampleSize - docs.length)
|
|
384
|
+
.lean()) ?? [];
|
|
385
|
+
docs = docs.concat(tail);
|
|
386
|
+
}
|
|
387
|
+
if (docs.length === 0) {
|
|
388
|
+
const fallback = (await this.tables?.captcha
|
|
389
|
+
.aggregate([
|
|
390
|
+
{ $match: baseFilter },
|
|
391
|
+
{ $sample: { size: sampleSize } },
|
|
392
|
+
{ $project: projection },
|
|
393
|
+
])
|
|
394
|
+
.exec()) ?? [];
|
|
395
|
+
if (fallback.length > 0) {
|
|
396
|
+
return fallback.map(({ _id, ...keep }) => keep);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
if (docs.length > 0) {
|
|
400
|
+
return docs;
|
|
401
|
+
}
|
|
402
|
+
throw new ProsopoError({
|
|
403
|
+
code: "DB_QUERY_FAILED",
|
|
404
|
+
errorSite: "DBSE-12",
|
|
405
|
+
message: "Failed to get captcha",
|
|
406
|
+
context: {
|
|
407
|
+
failedFuncName: this.getRandomCaptcha.name,
|
|
408
|
+
solved,
|
|
409
|
+
datasetId,
|
|
410
|
+
size,
|
|
411
|
+
},
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
async getCaptchaById(captchaId) {
|
|
415
|
+
const filter = { captchaId: { $in: captchaId } };
|
|
416
|
+
const cursor = this.tables?.captcha
|
|
417
|
+
.find(filter, {
|
|
418
|
+
_id: 0,
|
|
419
|
+
captchaId: 1,
|
|
420
|
+
datasetId: 1,
|
|
421
|
+
items: 1,
|
|
422
|
+
})
|
|
423
|
+
.lean();
|
|
424
|
+
const docs = await cursor;
|
|
425
|
+
if (docs?.length) {
|
|
426
|
+
return docs.map(({ _id, ...keepAttrs }) => keepAttrs);
|
|
427
|
+
}
|
|
428
|
+
throw new ProsopoError({
|
|
429
|
+
code: "DB_QUERY_FAILED",
|
|
430
|
+
errorSite: "DBSE-13",
|
|
431
|
+
message: "Failed to get captcha",
|
|
432
|
+
context: { failedFuncName: this.getCaptchaById.name, captchaId },
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
async updateCaptcha(captcha, datasetId) {
|
|
436
|
+
if (!isHex(datasetId)) {
|
|
437
|
+
throw new ProsopoError({
|
|
438
|
+
code: "INVALID_DATA",
|
|
439
|
+
errorSite: "DBSE-14",
|
|
440
|
+
message: "Invalid hash",
|
|
441
|
+
context: { failedFuncName: this.updateCaptcha.name, datasetId },
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
try {
|
|
445
|
+
const filter = { datasetId };
|
|
446
|
+
await this.tables?.captcha.updateOne(filter, { $set: captcha }, { upsert: false });
|
|
447
|
+
}
|
|
448
|
+
catch (err) {
|
|
449
|
+
throw new ProsopoError({
|
|
450
|
+
code: "DB_QUERY_FAILED",
|
|
451
|
+
errorSite: "DBSE-15",
|
|
452
|
+
message: "Failed to update captcha",
|
|
453
|
+
cause: err,
|
|
454
|
+
context: { failedFuncName: this.getDatasetDetails.name },
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
async removeCaptchas(captchaIds) {
|
|
459
|
+
const filter = { captchaId: { $in: captchaIds } };
|
|
460
|
+
await this.tables?.captcha.deleteMany(filter);
|
|
461
|
+
}
|
|
462
|
+
async getMostRecentDatasetId() {
|
|
463
|
+
const dataset = await this.tables?.dataset
|
|
464
|
+
.findOne()
|
|
465
|
+
.sort({ _id: -1 })
|
|
466
|
+
.lean();
|
|
467
|
+
const datasetId = dataset?.datasetId;
|
|
468
|
+
return typeof datasetId === "string" ? datasetId : undefined;
|
|
469
|
+
}
|
|
470
|
+
async getDatasetDetails(datasetId) {
|
|
471
|
+
if (!isHex(datasetId)) {
|
|
472
|
+
throw new ProsopoError({
|
|
473
|
+
code: "INVALID_DATA",
|
|
474
|
+
errorSite: "DBSE-16",
|
|
475
|
+
message: "Invalid hash",
|
|
476
|
+
context: { failedFuncName: this.getDatasetDetails.name, datasetId },
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
const filter = { datasetId };
|
|
480
|
+
const doc = await this.tables?.dataset
|
|
481
|
+
.findOne(filter)
|
|
482
|
+
.lean();
|
|
483
|
+
if (doc) {
|
|
484
|
+
return doc;
|
|
485
|
+
}
|
|
486
|
+
throw new ProsopoError({
|
|
487
|
+
code: "RECORD_NOT_FOUND",
|
|
488
|
+
errorSite: "DBSE-17",
|
|
489
|
+
message: "Failed to get dataset",
|
|
490
|
+
context: { failedFuncName: this.getDatasetDetails.name, datasetId },
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
async storeUserImageCaptchaSolution(captchas, commit) {
|
|
494
|
+
const commitmentRecord = UserCommitmentSchema.parse({
|
|
495
|
+
...commit,
|
|
496
|
+
lastUpdatedTimestamp: new Date(),
|
|
497
|
+
});
|
|
498
|
+
if (captchas.length) {
|
|
499
|
+
const filter = {
|
|
500
|
+
id: commit.id,
|
|
501
|
+
};
|
|
502
|
+
await this.tables?.commitment.updateOne(filter, { $set: { ...commitmentRecord } }, { upsert: true });
|
|
503
|
+
const ops = captchas.map((captcha) => ({
|
|
504
|
+
updateOne: {
|
|
505
|
+
filter: {
|
|
506
|
+
commitmentId: commit.id,
|
|
507
|
+
captchaId: captcha.captchaId,
|
|
508
|
+
},
|
|
509
|
+
update: {
|
|
510
|
+
$set: {
|
|
511
|
+
captchaId: captcha.captchaId,
|
|
512
|
+
captchaContentId: captcha.captchaContentId,
|
|
513
|
+
salt: captcha.salt,
|
|
514
|
+
solution: captcha.solution,
|
|
515
|
+
commitmentId: commit.id,
|
|
516
|
+
processed: false,
|
|
517
|
+
},
|
|
518
|
+
},
|
|
519
|
+
upsert: true,
|
|
520
|
+
},
|
|
521
|
+
}));
|
|
522
|
+
await this.tables?.usersolution.bulkWrite(ops);
|
|
523
|
+
this.centralStreamer?.streamImageRecord(commitmentRecord, (ts) => this.tables.commitment
|
|
524
|
+
.updateOne({ id: commit.id }, { $set: { storedAtTimestamp: ts } })
|
|
525
|
+
.then(() => { }));
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
async storePowCaptchaRecord(challenge, components, difficulty, providerSignature, ipAddress, headers, ja4, sessionId, serverChecked = false, userSubmitted = false, userSignature, ipInfo, fingerprintSummary) {
|
|
529
|
+
const tables = this.getTables();
|
|
530
|
+
const powCaptchaRecord = {
|
|
531
|
+
challenge,
|
|
532
|
+
userAccount: components.userAccount,
|
|
533
|
+
dappAccount: components.dappAccount,
|
|
534
|
+
requestedAtTimestamp: new Date(components.requestedAtTimestamp),
|
|
535
|
+
ipAddress,
|
|
536
|
+
headers,
|
|
537
|
+
ja4,
|
|
538
|
+
result: { status: CaptchaStatus.pending },
|
|
539
|
+
userSubmitted,
|
|
540
|
+
serverChecked,
|
|
541
|
+
difficulty,
|
|
542
|
+
providerSignature,
|
|
543
|
+
userSignature,
|
|
544
|
+
lastUpdatedTimestamp: new Date(),
|
|
545
|
+
pendingStage: true,
|
|
546
|
+
sessionId,
|
|
547
|
+
ipInfo,
|
|
548
|
+
...(fingerprintSummary && { fingerprintSummary }),
|
|
549
|
+
};
|
|
550
|
+
try {
|
|
551
|
+
await tables.powcaptcha.create(powCaptchaRecord);
|
|
552
|
+
this.logger.info(() => ({
|
|
553
|
+
data: {
|
|
554
|
+
challenge,
|
|
555
|
+
userSubmitted,
|
|
556
|
+
serverChecked,
|
|
557
|
+
countryCode: ipInfo?.isValid ? ipInfo.countryCode : undefined,
|
|
558
|
+
},
|
|
559
|
+
msg: "PowCaptcha record added successfully",
|
|
560
|
+
}));
|
|
561
|
+
this.centralStreamer?.streamPowRecord(powCaptchaRecord, (ts) => this.tables.powcaptcha
|
|
562
|
+
.updateOne({ challenge, lastUpdatedTimestamp: { $lte: ts } }, {
|
|
563
|
+
$set: { storedAtTimestamp: ts },
|
|
564
|
+
$unset: { pendingStage: 1 },
|
|
565
|
+
})
|
|
566
|
+
.then(() => { }));
|
|
567
|
+
}
|
|
568
|
+
catch (error) {
|
|
569
|
+
const err = new ProsopoError({
|
|
570
|
+
code: "DB_QUERY_FAILED",
|
|
571
|
+
errorSite: "DBSE-18",
|
|
572
|
+
message: "Failed to update captcha",
|
|
573
|
+
cause: error,
|
|
574
|
+
context: { challenge, userSubmitted, serverChecked, ipInfo },
|
|
575
|
+
});
|
|
576
|
+
this.logger.error(() => ({
|
|
577
|
+
err: error,
|
|
578
|
+
msg: "Failed to add PowCaptcha record",
|
|
579
|
+
}));
|
|
580
|
+
throw err;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
async getPowCaptchaRecordByChallenge(challenge) {
|
|
584
|
+
if (!this.tables) {
|
|
585
|
+
throw new ProsopoError({
|
|
586
|
+
code: "DB_QUERY_FAILED",
|
|
587
|
+
errorSite: "DBSE-19",
|
|
588
|
+
message: "Database client is not connected",
|
|
589
|
+
context: { failedFuncName: this.getPowCaptchaRecordByChallenge.name },
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
try {
|
|
593
|
+
const filter = { challenge };
|
|
594
|
+
const record = await this.tables.powcaptcha
|
|
595
|
+
.findOne(filter, {
|
|
596
|
+
challenge: 1,
|
|
597
|
+
userAccount: 1,
|
|
598
|
+
dappAccount: 1,
|
|
599
|
+
requestedAtTimestamp: 1,
|
|
600
|
+
submittedAtTimestamp: 1,
|
|
601
|
+
verifiedAtTimestamp: 1,
|
|
602
|
+
failedAtTimestamp: 1,
|
|
603
|
+
ipAddress: 1,
|
|
604
|
+
headers: 1,
|
|
605
|
+
ja4: 1,
|
|
606
|
+
result: 1,
|
|
607
|
+
difficulty: 1,
|
|
608
|
+
sessionId: 1,
|
|
609
|
+
ipInfo: 1,
|
|
610
|
+
deviceCapability: 1,
|
|
611
|
+
behavioralDataPacked: 1,
|
|
612
|
+
serverChecked: 1,
|
|
613
|
+
userSubmitted: 1,
|
|
614
|
+
coords: 1,
|
|
615
|
+
clientMetaData: 1,
|
|
616
|
+
})
|
|
617
|
+
.lean();
|
|
618
|
+
if (record) {
|
|
619
|
+
this.logger.info(() => ({
|
|
620
|
+
data: { challenge },
|
|
621
|
+
msg: "PowCaptcha record retrieved successfully",
|
|
622
|
+
}));
|
|
623
|
+
return record;
|
|
624
|
+
}
|
|
625
|
+
this.logger.info(() => ({
|
|
626
|
+
data: { challenge },
|
|
627
|
+
msg: "No PowCaptcha record found",
|
|
628
|
+
}));
|
|
629
|
+
return null;
|
|
630
|
+
}
|
|
631
|
+
catch (error) {
|
|
632
|
+
const err = new ProsopoError({
|
|
633
|
+
code: "DB_QUERY_FAILED",
|
|
634
|
+
errorSite: "DBSE-20",
|
|
635
|
+
message: "Failed to get captcha",
|
|
636
|
+
cause: error,
|
|
637
|
+
context: { challenge },
|
|
638
|
+
});
|
|
639
|
+
this.logger.error(() => ({
|
|
640
|
+
err: err,
|
|
641
|
+
msg: "Failed to retrieve PowCaptcha record",
|
|
642
|
+
}));
|
|
643
|
+
throw err;
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
async updatePowCaptchaRecordResult(challenge, result, serverChecked = false, userSubmitted = false, userSignature, coords) {
|
|
647
|
+
const tables = this.getTables();
|
|
648
|
+
const timestamp = new Date();
|
|
649
|
+
const isDisapproved = result.status === CaptchaStatus.disapproved;
|
|
650
|
+
assertCoordsSafe(coords, "coords");
|
|
651
|
+
const setStage = {
|
|
652
|
+
result,
|
|
653
|
+
serverChecked,
|
|
654
|
+
userSubmitted,
|
|
655
|
+
userSignature,
|
|
656
|
+
lastUpdatedTimestamp: timestamp,
|
|
657
|
+
pendingStage: true,
|
|
658
|
+
blocked: isBlockingCaptchaResult(CaptchaType.pow, result),
|
|
659
|
+
...(coords && { coords }),
|
|
660
|
+
};
|
|
661
|
+
if (userSubmitted) {
|
|
662
|
+
setStage.submittedAtTimestamp = {
|
|
663
|
+
$ifNull: ["$submittedAtTimestamp", timestamp],
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
if (isDisapproved) {
|
|
667
|
+
setStage.failedAtTimestamp = {
|
|
668
|
+
$ifNull: ["$failedAtTimestamp", timestamp],
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
try {
|
|
672
|
+
const updateResult = await tables.powcaptcha.updateOne({ challenge }, [{ $set: setStage }], { updatePipeline: true });
|
|
673
|
+
if (updateResult.matchedCount === 0) {
|
|
674
|
+
const err = new ProsopoError({
|
|
675
|
+
code: "DB_QUERY_FAILED",
|
|
676
|
+
errorSite: "DBSE-21",
|
|
677
|
+
message: "Failed to get captcha",
|
|
678
|
+
context: { challenge, ...setStage },
|
|
679
|
+
});
|
|
680
|
+
this.logger.info(() => ({
|
|
681
|
+
err: err,
|
|
682
|
+
msg: "No PowCaptcha record found to update",
|
|
683
|
+
}));
|
|
684
|
+
throw err;
|
|
685
|
+
}
|
|
686
|
+
this.logger.info(() => ({
|
|
687
|
+
data: {
|
|
688
|
+
challenge,
|
|
689
|
+
...setStage,
|
|
690
|
+
},
|
|
691
|
+
msg: "PowCaptcha record updated successfully",
|
|
692
|
+
}));
|
|
693
|
+
this.centralStreamer?.streamPowUpdate(() => this.getPowCaptchaRecordByChallenge(challenge), (ts) => this.tables.powcaptcha
|
|
694
|
+
.updateOne({ challenge, lastUpdatedTimestamp: { $lte: ts } }, {
|
|
695
|
+
$set: { storedAtTimestamp: ts },
|
|
696
|
+
$unset: { pendingStage: 1 },
|
|
697
|
+
})
|
|
698
|
+
.then(() => { }));
|
|
699
|
+
}
|
|
700
|
+
catch (error) {
|
|
701
|
+
const err = new ProsopoError({
|
|
702
|
+
code: "DB_QUERY_FAILED",
|
|
703
|
+
errorSite: "DBSE-22",
|
|
704
|
+
message: "Failed to update captcha",
|
|
705
|
+
cause: error,
|
|
706
|
+
context: { challenge, ...setStage },
|
|
707
|
+
});
|
|
708
|
+
this.logger.error(() => ({
|
|
709
|
+
err: err,
|
|
710
|
+
msg: "Failed to update PowCaptcha record",
|
|
711
|
+
}));
|
|
712
|
+
throw err;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
async updatePowCaptchaRecord(challenge, updates) {
|
|
716
|
+
const tables = this.getTables();
|
|
717
|
+
await tables.powcaptcha.updateOne({ challenge }, { $set: { ...updates, pendingStage: true } }, { upsert: false });
|
|
718
|
+
this.centralStreamer?.streamPowUpdate(() => this.getPowCaptchaRecordByChallenge(challenge), (ts) => this.tables.powcaptcha
|
|
719
|
+
.updateOne({ challenge, lastUpdatedTimestamp: { $lte: ts } }, {
|
|
720
|
+
$set: { storedAtTimestamp: ts },
|
|
721
|
+
$unset: { pendingStage: 1 },
|
|
722
|
+
})
|
|
723
|
+
.then(() => { }));
|
|
724
|
+
}
|
|
725
|
+
interactiveCollection(captchaType) {
|
|
726
|
+
switch (captchaType) {
|
|
727
|
+
case CaptchaType.puzzle:
|
|
728
|
+
return {
|
|
729
|
+
table: TableNames.puzzlecaptcha,
|
|
730
|
+
label: "PuzzleCaptcha",
|
|
731
|
+
captchaType,
|
|
732
|
+
answerProjection: {
|
|
733
|
+
targetX: 1,
|
|
734
|
+
targetY: 1,
|
|
735
|
+
originX: 1,
|
|
736
|
+
originY: 1,
|
|
737
|
+
tolerance: 1,
|
|
738
|
+
puzzleEvents: 1,
|
|
739
|
+
},
|
|
740
|
+
streamRecord: (record, markStored) => this.centralStreamer?.streamPuzzleRecord(record, markStored),
|
|
741
|
+
streamUpdate: (challenge, markStored) => this.centralStreamer?.streamPuzzleUpdate(() => this.getPuzzleCaptchaRecordByChallenge(challenge), markStored),
|
|
742
|
+
};
|
|
743
|
+
case CaptchaType.iconOrder:
|
|
744
|
+
return {
|
|
745
|
+
table: TableNames.iconordercaptcha,
|
|
746
|
+
label: "IconOrderCaptcha",
|
|
747
|
+
captchaType,
|
|
748
|
+
answerProjection: {
|
|
749
|
+
targets: 1,
|
|
750
|
+
tolerance: 1,
|
|
751
|
+
clicks: 1,
|
|
752
|
+
iconOrderEvents: 1,
|
|
753
|
+
},
|
|
754
|
+
streamRecord: (record, markStored) => this.centralStreamer?.streamIconOrderRecord(record, markStored),
|
|
755
|
+
streamUpdate: (challenge, markStored) => this.centralStreamer?.streamIconOrderUpdate(() => this.getIconOrderCaptchaRecordByChallenge(challenge), markStored),
|
|
756
|
+
};
|
|
757
|
+
case CaptchaType.audio:
|
|
758
|
+
return {
|
|
759
|
+
table: TableNames.audiocaptcha,
|
|
760
|
+
label: "AudioCaptcha",
|
|
761
|
+
captchaType,
|
|
762
|
+
answerProjection: {
|
|
763
|
+
answer: 1,
|
|
764
|
+
submittedAnswer: 1,
|
|
765
|
+
replays: 1,
|
|
766
|
+
audioEvents: 1,
|
|
767
|
+
},
|
|
768
|
+
streamRecord: (record, markStored) => this.centralStreamer?.streamAudioRecord(record, markStored),
|
|
769
|
+
streamUpdate: (challenge, markStored) => this.centralStreamer?.streamAudioUpdate(() => this.getAudioCaptchaRecordByChallenge(challenge), markStored),
|
|
770
|
+
};
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
markInteractiveRecordStored(collection, challenge) {
|
|
774
|
+
return (ts) => this.tables[collection.table]
|
|
775
|
+
.updateOne({ challenge, lastUpdatedTimestamp: { $lte: ts } }, {
|
|
776
|
+
$set: { storedAtTimestamp: ts },
|
|
777
|
+
$unset: { pendingStage: 1 },
|
|
778
|
+
})
|
|
779
|
+
.then(() => { });
|
|
780
|
+
}
|
|
781
|
+
streamInteractiveUpdate(collection, challenge) {
|
|
782
|
+
collection.streamUpdate(challenge, this.markInteractiveRecordStored(collection, challenge));
|
|
783
|
+
}
|
|
784
|
+
async storeInteractiveCaptchaRecord(collection, record) {
|
|
785
|
+
const tables = this.getTables();
|
|
786
|
+
const { challenge, ipInfo } = record;
|
|
787
|
+
try {
|
|
788
|
+
await tables[collection.table].create(record);
|
|
789
|
+
this.logger.info(() => ({
|
|
790
|
+
data: {
|
|
791
|
+
challenge,
|
|
792
|
+
countryCode: ipInfo?.isValid ? ipInfo.countryCode : undefined,
|
|
793
|
+
},
|
|
794
|
+
msg: `${collection.label} record added successfully`,
|
|
795
|
+
}));
|
|
796
|
+
collection.streamRecord(record, this.markInteractiveRecordStored(collection, challenge));
|
|
797
|
+
}
|
|
798
|
+
catch (error) {
|
|
799
|
+
const err = new ProsopoError({
|
|
800
|
+
code: "DB_QUERY_FAILED",
|
|
801
|
+
errorSite: "DBSE-23",
|
|
802
|
+
message: "Failed to update captcha",
|
|
803
|
+
cause: error,
|
|
804
|
+
context: { challenge, ipInfo },
|
|
805
|
+
});
|
|
806
|
+
this.logger.error(() => ({
|
|
807
|
+
err: error,
|
|
808
|
+
msg: `Failed to add ${collection.label} record`,
|
|
809
|
+
}));
|
|
810
|
+
throw err;
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
pendingInteractiveRecord(challenge, components, providerSignature, ipAddress, headers, ja4, sessionId, ipInfo) {
|
|
814
|
+
return {
|
|
815
|
+
challenge,
|
|
816
|
+
userAccount: components.userAccount,
|
|
817
|
+
dappAccount: components.dappAccount,
|
|
818
|
+
requestedAtTimestamp: new Date(components.requestedAtTimestamp),
|
|
819
|
+
ipAddress,
|
|
820
|
+
headers,
|
|
821
|
+
ja4,
|
|
822
|
+
result: { status: CaptchaStatus.pending },
|
|
823
|
+
userSubmitted: false,
|
|
824
|
+
serverChecked: false,
|
|
825
|
+
providerSignature,
|
|
826
|
+
lastUpdatedTimestamp: new Date(),
|
|
827
|
+
pendingStage: true,
|
|
828
|
+
sessionId,
|
|
829
|
+
ipInfo,
|
|
830
|
+
};
|
|
831
|
+
}
|
|
832
|
+
async storePuzzleCaptchaRecord(challenge, components, targetX, targetY, originX, originY, tolerance, providerSignature, ipAddress, headers, ja4, sessionId, ipInfo, fingerprintSummary) {
|
|
833
|
+
await this.storeInteractiveCaptchaRecord(this.interactiveCollection(CaptchaType.puzzle), {
|
|
834
|
+
...this.pendingInteractiveRecord(challenge, components, providerSignature, ipAddress, headers, ja4, sessionId, ipInfo),
|
|
835
|
+
tolerance,
|
|
836
|
+
targetX,
|
|
837
|
+
targetY,
|
|
838
|
+
originX,
|
|
839
|
+
originY,
|
|
840
|
+
...(fingerprintSummary && { fingerprintSummary }),
|
|
841
|
+
});
|
|
842
|
+
}
|
|
843
|
+
async storeAudioCaptchaRecord(challenge, components, answer, providerSignature, ipAddress, headers, ja4, sessionId, ipInfo) {
|
|
844
|
+
await this.storeInteractiveCaptchaRecord(this.interactiveCollection(CaptchaType.audio), {
|
|
845
|
+
...this.pendingInteractiveRecord(challenge, components, providerSignature, ipAddress, headers, ja4, sessionId, ipInfo),
|
|
846
|
+
answer,
|
|
847
|
+
});
|
|
848
|
+
}
|
|
849
|
+
async storeIconOrderCaptchaRecord(challenge, components, targets, tolerance, providerSignature, ipAddress, headers, ja4, sessionId, ipInfo) {
|
|
850
|
+
await this.storeInteractiveCaptchaRecord(this.interactiveCollection(CaptchaType.iconOrder), {
|
|
851
|
+
...this.pendingInteractiveRecord(challenge, components, providerSignature, ipAddress, headers, ja4, sessionId, ipInfo),
|
|
852
|
+
targets,
|
|
853
|
+
tolerance,
|
|
854
|
+
});
|
|
855
|
+
}
|
|
856
|
+
async getInteractiveCaptchaRecordByChallenge(collection, challenge, failedFuncName) {
|
|
857
|
+
if (!this.tables) {
|
|
858
|
+
throw new ProsopoError({
|
|
859
|
+
code: "DB_QUERY_FAILED",
|
|
860
|
+
errorSite: "DBSE-24",
|
|
861
|
+
message: "Database client is not connected",
|
|
862
|
+
context: { failedFuncName },
|
|
863
|
+
});
|
|
864
|
+
}
|
|
865
|
+
try {
|
|
866
|
+
const record = await this.tables[collection.table]
|
|
867
|
+
.findOne({ challenge }, {
|
|
868
|
+
challenge: 1,
|
|
869
|
+
userAccount: 1,
|
|
870
|
+
dappAccount: 1,
|
|
871
|
+
requestedAtTimestamp: 1,
|
|
872
|
+
submittedAtTimestamp: 1,
|
|
873
|
+
ipAddress: 1,
|
|
874
|
+
headers: 1,
|
|
875
|
+
ja4: 1,
|
|
876
|
+
result: 1,
|
|
877
|
+
sessionId: 1,
|
|
878
|
+
ipInfo: 1,
|
|
879
|
+
deviceCapability: 1,
|
|
880
|
+
behavioralDataPacked: 1,
|
|
881
|
+
serverChecked: 1,
|
|
882
|
+
userSubmitted: 1,
|
|
883
|
+
coords: 1,
|
|
884
|
+
clientMetaData: 1,
|
|
885
|
+
...collection.answerProjection,
|
|
886
|
+
})
|
|
887
|
+
.lean();
|
|
888
|
+
if (record) {
|
|
889
|
+
this.logger.info(() => ({
|
|
890
|
+
data: { challenge },
|
|
891
|
+
msg: `${collection.label} record retrieved successfully`,
|
|
892
|
+
}));
|
|
893
|
+
return record;
|
|
894
|
+
}
|
|
895
|
+
this.logger.info(() => ({
|
|
896
|
+
data: { challenge },
|
|
897
|
+
msg: `No ${collection.label} record found`,
|
|
898
|
+
}));
|
|
899
|
+
return null;
|
|
900
|
+
}
|
|
901
|
+
catch (error) {
|
|
902
|
+
const err = new ProsopoError({
|
|
903
|
+
code: "DB_QUERY_FAILED",
|
|
904
|
+
errorSite: "DBSE-25",
|
|
905
|
+
message: "Failed to get captcha",
|
|
906
|
+
cause: error,
|
|
907
|
+
context: { challenge },
|
|
908
|
+
});
|
|
909
|
+
this.logger.error(() => ({
|
|
910
|
+
err: err,
|
|
911
|
+
msg: `Failed to retrieve ${collection.label} record`,
|
|
912
|
+
}));
|
|
913
|
+
throw err;
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
async getPuzzleCaptchaRecordByChallenge(challenge) {
|
|
917
|
+
return this.getInteractiveCaptchaRecordByChallenge(this.interactiveCollection(CaptchaType.puzzle), challenge, this.getPuzzleCaptchaRecordByChallenge.name);
|
|
918
|
+
}
|
|
919
|
+
async getIconOrderCaptchaRecordByChallenge(challenge) {
|
|
920
|
+
return this.getInteractiveCaptchaRecordByChallenge(this.interactiveCollection(CaptchaType.iconOrder), challenge, this.getIconOrderCaptchaRecordByChallenge.name);
|
|
921
|
+
}
|
|
922
|
+
async getAudioCaptchaRecordByChallenge(challenge) {
|
|
923
|
+
return this.getInteractiveCaptchaRecordByChallenge(this.interactiveCollection(CaptchaType.audio), challenge, this.getAudioCaptchaRecordByChallenge.name);
|
|
924
|
+
}
|
|
925
|
+
async claimInteractiveCaptchaSubmission(collection, challenge) {
|
|
926
|
+
const tables = this.getTables();
|
|
927
|
+
try {
|
|
928
|
+
const claim = await tables[collection.table].updateOne({ challenge, userSubmitted: { $ne: true } }, { $set: { userSubmitted: true, submittedAtTimestamp: new Date() } });
|
|
929
|
+
const won = claim.modifiedCount === 1;
|
|
930
|
+
this.logger.info(() => ({
|
|
931
|
+
data: { challenge, won },
|
|
932
|
+
msg: won
|
|
933
|
+
? `${collection.label} submission claimed`
|
|
934
|
+
: `${collection.label} submission already claimed`,
|
|
935
|
+
}));
|
|
936
|
+
return won;
|
|
937
|
+
}
|
|
938
|
+
catch (error) {
|
|
939
|
+
const err = new ProsopoError({
|
|
940
|
+
code: "DB_QUERY_FAILED",
|
|
941
|
+
errorSite: "DBSE-26",
|
|
942
|
+
message: "Failed to update captcha",
|
|
943
|
+
cause: error,
|
|
944
|
+
context: { challenge },
|
|
945
|
+
});
|
|
946
|
+
this.logger.error(() => ({
|
|
947
|
+
err: err,
|
|
948
|
+
msg: `Failed to claim ${collection.label} submission`,
|
|
949
|
+
}));
|
|
950
|
+
throw err;
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
async claimIconOrderCaptchaSubmission(challenge) {
|
|
954
|
+
return this.claimInteractiveCaptchaSubmission(this.interactiveCollection(CaptchaType.iconOrder), challenge);
|
|
955
|
+
}
|
|
956
|
+
async claimAudioCaptchaSubmission(challenge) {
|
|
957
|
+
return this.claimInteractiveCaptchaSubmission(this.interactiveCollection(CaptchaType.audio), challenge);
|
|
958
|
+
}
|
|
959
|
+
async updateInteractiveCaptchaRecordResult(collection, challenge, result, serverChecked, userSubmitted, userSignature, coords, lastUpdatedTimestamp) {
|
|
960
|
+
const tables = this.getTables();
|
|
961
|
+
const timestamp = lastUpdatedTimestamp ?? new Date();
|
|
962
|
+
const isDisapproved = result.status === CaptchaStatus.disapproved;
|
|
963
|
+
assertCoordsSafe(coords, "coords");
|
|
964
|
+
const setStage = {
|
|
965
|
+
result,
|
|
966
|
+
serverChecked,
|
|
967
|
+
userSubmitted,
|
|
968
|
+
userSignature,
|
|
969
|
+
lastUpdatedTimestamp: timestamp,
|
|
970
|
+
pendingStage: true,
|
|
971
|
+
blocked: isBlockingCaptchaResult(collection.captchaType, result),
|
|
972
|
+
...(coords && { coords }),
|
|
973
|
+
...(userSubmitted && { submittedAtTimestamp: timestamp }),
|
|
974
|
+
...(isDisapproved && { failedAtTimestamp: timestamp }),
|
|
975
|
+
};
|
|
976
|
+
try {
|
|
977
|
+
const updateResult = await tables[collection.table].updateOne({ challenge }, { $set: setStage });
|
|
978
|
+
if (updateResult.matchedCount === 0) {
|
|
979
|
+
const err = new ProsopoError({
|
|
980
|
+
code: "DB_QUERY_FAILED",
|
|
981
|
+
errorSite: "DBSE-27",
|
|
982
|
+
message: "Failed to get captcha",
|
|
983
|
+
context: { challenge, ...setStage },
|
|
984
|
+
});
|
|
985
|
+
this.logger.info(() => ({
|
|
986
|
+
err: err,
|
|
987
|
+
msg: `No ${collection.label} record found to update`,
|
|
988
|
+
}));
|
|
989
|
+
throw err;
|
|
990
|
+
}
|
|
991
|
+
this.logger.info(() => ({
|
|
992
|
+
data: {
|
|
993
|
+
challenge,
|
|
994
|
+
...setStage,
|
|
995
|
+
},
|
|
996
|
+
msg: `${collection.label} record updated successfully`,
|
|
997
|
+
}));
|
|
998
|
+
this.streamInteractiveUpdate(collection, challenge);
|
|
999
|
+
}
|
|
1000
|
+
catch (error) {
|
|
1001
|
+
const err = new ProsopoError({
|
|
1002
|
+
code: "DB_QUERY_FAILED",
|
|
1003
|
+
errorSite: "DBSE-28",
|
|
1004
|
+
message: "Failed to update captcha",
|
|
1005
|
+
cause: error,
|
|
1006
|
+
context: { challenge, ...setStage },
|
|
1007
|
+
});
|
|
1008
|
+
this.logger.error(() => ({
|
|
1009
|
+
err: err,
|
|
1010
|
+
msg: `Failed to update ${collection.label} record`,
|
|
1011
|
+
}));
|
|
1012
|
+
throw err;
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
async updatePuzzleCaptchaRecordResult(challenge, result, serverChecked = false, userSubmitted = false, userSignature, coords, lastUpdatedTimestamp) {
|
|
1016
|
+
await this.updateInteractiveCaptchaRecordResult(this.interactiveCollection(CaptchaType.puzzle), challenge, result, serverChecked, userSubmitted, userSignature, coords, lastUpdatedTimestamp);
|
|
1017
|
+
}
|
|
1018
|
+
async updateIconOrderCaptchaRecordResult(challenge, result, serverChecked = false, userSubmitted = false, userSignature, coords, lastUpdatedTimestamp) {
|
|
1019
|
+
await this.updateInteractiveCaptchaRecordResult(this.interactiveCollection(CaptchaType.iconOrder), challenge, result, serverChecked, userSubmitted, userSignature, coords, lastUpdatedTimestamp);
|
|
1020
|
+
}
|
|
1021
|
+
async updateAudioCaptchaRecordResult(challenge, result, serverChecked = false, userSubmitted = false, userSignature, coords, lastUpdatedTimestamp) {
|
|
1022
|
+
await this.updateInteractiveCaptchaRecordResult(this.interactiveCollection(CaptchaType.audio), challenge, result, serverChecked, userSubmitted, userSignature, coords, lastUpdatedTimestamp);
|
|
1023
|
+
}
|
|
1024
|
+
async updateInteractiveCaptchaRecord(collection, challenge, updates) {
|
|
1025
|
+
const tables = this.getTables();
|
|
1026
|
+
const timestamp = new Date();
|
|
1027
|
+
const baseSet = {
|
|
1028
|
+
...updates,
|
|
1029
|
+
pendingStage: true,
|
|
1030
|
+
...(updates.serverChecked === true && {
|
|
1031
|
+
verifiedAtTimestamp: timestamp,
|
|
1032
|
+
}),
|
|
1033
|
+
...(updates.userSubmitted === true && {
|
|
1034
|
+
submittedAtTimestamp: timestamp,
|
|
1035
|
+
}),
|
|
1036
|
+
...(updates.result?.status === CaptchaStatus.disapproved && {
|
|
1037
|
+
failedAtTimestamp: timestamp,
|
|
1038
|
+
}),
|
|
1039
|
+
};
|
|
1040
|
+
await tables[collection.table].updateOne({ challenge }, { $set: baseSet });
|
|
1041
|
+
this.streamInteractiveUpdate(collection, challenge);
|
|
1042
|
+
}
|
|
1043
|
+
async updatePuzzleCaptchaRecord(challenge, updates) {
|
|
1044
|
+
await this.updateInteractiveCaptchaRecord(this.interactiveCollection(CaptchaType.puzzle), challenge, updates);
|
|
1045
|
+
}
|
|
1046
|
+
async updateIconOrderCaptchaRecord(challenge, updates) {
|
|
1047
|
+
await this.updateInteractiveCaptchaRecord(this.interactiveCollection(CaptchaType.iconOrder), challenge, updates);
|
|
1048
|
+
}
|
|
1049
|
+
async updateAudioCaptchaRecord(challenge, updates) {
|
|
1050
|
+
await this.updateInteractiveCaptchaRecord(this.interactiveCollection(CaptchaType.audio), challenge, updates);
|
|
1051
|
+
}
|
|
1052
|
+
async markInteractiveCaptchaRecordChecked(collection, challenge) {
|
|
1053
|
+
const tables = this.getTables();
|
|
1054
|
+
const timestamp = new Date();
|
|
1055
|
+
const result = await tables[collection.table].updateOne({ challenge, serverChecked: { $ne: true } }, {
|
|
1056
|
+
$set: {
|
|
1057
|
+
serverChecked: true,
|
|
1058
|
+
lastUpdatedTimestamp: timestamp,
|
|
1059
|
+
verifiedAtTimestamp: timestamp,
|
|
1060
|
+
pendingStage: true,
|
|
1061
|
+
},
|
|
1062
|
+
});
|
|
1063
|
+
if (result.modifiedCount === 0) {
|
|
1064
|
+
return false;
|
|
1065
|
+
}
|
|
1066
|
+
this.streamInteractiveUpdate(collection, challenge);
|
|
1067
|
+
return true;
|
|
1068
|
+
}
|
|
1069
|
+
async markIconOrderCaptchaRecordChecked(challenge) {
|
|
1070
|
+
return this.markInteractiveCaptchaRecordChecked(this.interactiveCollection(CaptchaType.iconOrder), challenge);
|
|
1071
|
+
}
|
|
1072
|
+
async markAudioCaptchaRecordChecked(challenge) {
|
|
1073
|
+
return this.markInteractiveCaptchaRecordChecked(this.interactiveCollection(CaptchaType.audio), challenge);
|
|
1074
|
+
}
|
|
1075
|
+
async markPuzzleCaptchaRecordChecked(challenge) {
|
|
1076
|
+
return this.markInteractiveCaptchaRecordChecked(this.interactiveCollection(CaptchaType.puzzle), challenge);
|
|
1077
|
+
}
|
|
1078
|
+
async getCheckedDappUserCommitments() {
|
|
1079
|
+
const filter = { [StoredStatusNames.serverChecked]: true };
|
|
1080
|
+
const docs = await this.tables?.commitment
|
|
1081
|
+
.find(filter)
|
|
1082
|
+
.lean();
|
|
1083
|
+
return docs || [];
|
|
1084
|
+
}
|
|
1085
|
+
async getUnstoredDappUserCommitments(limit = 1000, afterId) {
|
|
1086
|
+
const filter = {
|
|
1087
|
+
pendingStage: true,
|
|
1088
|
+
};
|
|
1089
|
+
if (afterId !== undefined) {
|
|
1090
|
+
filter._id = { $gt: afterId };
|
|
1091
|
+
}
|
|
1092
|
+
const docs = await this.tables?.commitment
|
|
1093
|
+
.find(filter)
|
|
1094
|
+
.hint("pendingStage_partial")
|
|
1095
|
+
.sort({ _id: 1 })
|
|
1096
|
+
.limit(limit)
|
|
1097
|
+
.lean();
|
|
1098
|
+
return docs || [];
|
|
1099
|
+
}
|
|
1100
|
+
async markDappUserCommitmentsStored(commitmentIds, asOfTimestamp = new Date()) {
|
|
1101
|
+
await this.tables?.commitment.updateMany({
|
|
1102
|
+
id: { $in: commitmentIds },
|
|
1103
|
+
lastUpdatedTimestamp: { $lte: asOfTimestamp },
|
|
1104
|
+
}, {
|
|
1105
|
+
$set: { storedAtTimestamp: new Date() },
|
|
1106
|
+
$unset: { pendingStage: 1 },
|
|
1107
|
+
}, { upsert: false });
|
|
1108
|
+
}
|
|
1109
|
+
async markDappUserCommitmentsChecked(commitmentIds) {
|
|
1110
|
+
const timestamp = new Date();
|
|
1111
|
+
const result = await this.getTables().commitment.updateMany({ id: { $in: commitmentIds }, serverChecked: { $ne: true } }, [
|
|
1112
|
+
{
|
|
1113
|
+
$set: {
|
|
1114
|
+
serverChecked: true,
|
|
1115
|
+
lastUpdatedTimestamp: timestamp,
|
|
1116
|
+
pendingStage: true,
|
|
1117
|
+
verifiedAtTimestamp: {
|
|
1118
|
+
$ifNull: ["$verifiedAtTimestamp", timestamp],
|
|
1119
|
+
},
|
|
1120
|
+
},
|
|
1121
|
+
},
|
|
1122
|
+
], { updatePipeline: true });
|
|
1123
|
+
return result.modifiedCount;
|
|
1124
|
+
}
|
|
1125
|
+
async updateDappUserCommitment(commitmentId, updates) {
|
|
1126
|
+
const filter = { id: commitmentId };
|
|
1127
|
+
const timestamp = new Date();
|
|
1128
|
+
const baseSet = {
|
|
1129
|
+
...updates,
|
|
1130
|
+
lastUpdatedTimestamp: timestamp,
|
|
1131
|
+
pendingStage: true,
|
|
1132
|
+
};
|
|
1133
|
+
const pipelineExprs = {};
|
|
1134
|
+
if (updates.userSubmitted === true) {
|
|
1135
|
+
pipelineExprs.submittedAtTimestamp = {
|
|
1136
|
+
$ifNull: ["$submittedAtTimestamp", timestamp],
|
|
1137
|
+
};
|
|
1138
|
+
}
|
|
1139
|
+
if (updates.serverChecked === true) {
|
|
1140
|
+
pipelineExprs.verifiedAtTimestamp = {
|
|
1141
|
+
$ifNull: ["$verifiedAtTimestamp", timestamp],
|
|
1142
|
+
};
|
|
1143
|
+
}
|
|
1144
|
+
if (updates.result?.status === CaptchaStatus.disapproved) {
|
|
1145
|
+
pipelineExprs.failedAtTimestamp = {
|
|
1146
|
+
$ifNull: ["$failedAtTimestamp", timestamp],
|
|
1147
|
+
};
|
|
1148
|
+
}
|
|
1149
|
+
if (Object.keys(pipelineExprs).length === 0) {
|
|
1150
|
+
await this.tables?.commitment.updateOne(filter, { $set: baseSet });
|
|
1151
|
+
return;
|
|
1152
|
+
}
|
|
1153
|
+
await this.tables?.commitment.updateOne(filter, [{ $set: { ...baseSet, ...pipelineExprs } }], { updatePipeline: true });
|
|
1154
|
+
}
|
|
1155
|
+
async countCommitmentsByNormalisedEmail(dappAccount, emailNormalised) {
|
|
1156
|
+
if (!emailNormalised)
|
|
1157
|
+
return 0;
|
|
1158
|
+
const tables = this.getTables();
|
|
1159
|
+
const filter = {
|
|
1160
|
+
dappAccount,
|
|
1161
|
+
serverChecked: true,
|
|
1162
|
+
"metadata.emailNormalised": emailNormalised,
|
|
1163
|
+
};
|
|
1164
|
+
const counts = await Promise.all([
|
|
1165
|
+
tables.commitment.countDocuments(filter),
|
|
1166
|
+
tables.powcaptcha.countDocuments(filter),
|
|
1167
|
+
tables.puzzlecaptcha.countDocuments(filter),
|
|
1168
|
+
tables.iconordercaptcha.countDocuments(filter),
|
|
1169
|
+
tables.audiocaptcha.countDocuments(filter),
|
|
1170
|
+
]);
|
|
1171
|
+
return counts.reduce((total, count) => total + count, 0);
|
|
1172
|
+
}
|
|
1173
|
+
async getUnstoredDappUserPoWCommitments(limit = 1000, afterId) {
|
|
1174
|
+
const filter = {
|
|
1175
|
+
pendingStage: true,
|
|
1176
|
+
};
|
|
1177
|
+
if (afterId !== undefined) {
|
|
1178
|
+
filter._id = { $gt: afterId };
|
|
1179
|
+
}
|
|
1180
|
+
const docs = await this.tables?.powcaptcha
|
|
1181
|
+
.find(filter)
|
|
1182
|
+
.hint("pendingStage_partial")
|
|
1183
|
+
.sort({ _id: 1 })
|
|
1184
|
+
.limit(limit)
|
|
1185
|
+
.lean();
|
|
1186
|
+
return docs || [];
|
|
1187
|
+
}
|
|
1188
|
+
async markDappUserPoWCommitmentsStored(challenges, asOfTimestamp = new Date()) {
|
|
1189
|
+
await this.tables?.powcaptcha.updateMany({
|
|
1190
|
+
challenge: { $in: challenges },
|
|
1191
|
+
lastUpdatedTimestamp: { $lte: asOfTimestamp },
|
|
1192
|
+
}, {
|
|
1193
|
+
$set: { storedAtTimestamp: new Date() },
|
|
1194
|
+
$unset: { pendingStage: 1 },
|
|
1195
|
+
}, { upsert: false });
|
|
1196
|
+
}
|
|
1197
|
+
async markDappUserPoWCommitmentsChecked(challenges) {
|
|
1198
|
+
const timestamp = new Date();
|
|
1199
|
+
const result = await this.getTables().powcaptcha.updateMany({ challenge: { $in: challenges }, serverChecked: { $ne: true } }, [
|
|
1200
|
+
{
|
|
1201
|
+
$set: {
|
|
1202
|
+
serverChecked: true,
|
|
1203
|
+
lastUpdatedTimestamp: timestamp,
|
|
1204
|
+
pendingStage: true,
|
|
1205
|
+
verifiedAtTimestamp: {
|
|
1206
|
+
$ifNull: ["$verifiedAtTimestamp", timestamp],
|
|
1207
|
+
},
|
|
1208
|
+
},
|
|
1209
|
+
},
|
|
1210
|
+
], { upsert: false, updatePipeline: true });
|
|
1211
|
+
return result.modifiedCount;
|
|
1212
|
+
}
|
|
1213
|
+
async storeSessionRecord(sessionRecord) {
|
|
1214
|
+
try {
|
|
1215
|
+
this.logger.debug(() => ({
|
|
1216
|
+
data: { action: "storing", sessionRecord },
|
|
1217
|
+
}));
|
|
1218
|
+
const recordWithFlag = {
|
|
1219
|
+
...sessionRecord,
|
|
1220
|
+
lastUpdatedTimestamp: sessionRecord.lastUpdatedTimestamp ?? sessionRecord.createdAt,
|
|
1221
|
+
pendingStage: true,
|
|
1222
|
+
};
|
|
1223
|
+
await this.tables.session.create(recordWithFlag);
|
|
1224
|
+
this.centralStreamer?.streamSessionRecord(recordWithFlag, (ts) => this.tables.session
|
|
1225
|
+
.updateOne({
|
|
1226
|
+
sessionId: sessionRecord.sessionId,
|
|
1227
|
+
lastUpdatedTimestamp: { $lte: ts },
|
|
1228
|
+
}, {
|
|
1229
|
+
$set: { storedAtTimestamp: ts },
|
|
1230
|
+
$unset: { pendingStage: 1 },
|
|
1231
|
+
})
|
|
1232
|
+
.then(() => { }));
|
|
1233
|
+
}
|
|
1234
|
+
catch (err) {
|
|
1235
|
+
throw new ProsopoError({
|
|
1236
|
+
code: "DB_QUERY_FAILED",
|
|
1237
|
+
errorSite: "DBSE-29",
|
|
1238
|
+
message: "Failed to store session",
|
|
1239
|
+
cause: err,
|
|
1240
|
+
context: { sessionId: sessionRecord.sessionId },
|
|
1241
|
+
});
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
async storeBlockedSession(record) {
|
|
1245
|
+
try {
|
|
1246
|
+
const stored = {
|
|
1247
|
+
...record,
|
|
1248
|
+
blocked: true,
|
|
1249
|
+
deleted: true,
|
|
1250
|
+
storedAtTimestamp: record.storedAtTimestamp ?? record.createdAt,
|
|
1251
|
+
lastUpdatedTimestamp: record.lastUpdatedTimestamp ?? record.createdAt,
|
|
1252
|
+
};
|
|
1253
|
+
await this.tables.session.create(stored);
|
|
1254
|
+
this.centralStreamer?.streamSessionRecord(stored, (ts) => this.tables.session
|
|
1255
|
+
.updateOne({
|
|
1256
|
+
sessionId: stored.sessionId,
|
|
1257
|
+
lastUpdatedTimestamp: { $lte: ts },
|
|
1258
|
+
}, { $set: { storedAtTimestamp: ts } })
|
|
1259
|
+
.then(() => { }));
|
|
1260
|
+
}
|
|
1261
|
+
catch (err) {
|
|
1262
|
+
this.logger.warn(() => ({
|
|
1263
|
+
err,
|
|
1264
|
+
msg: "Failed to persist blocked session record",
|
|
1265
|
+
data: { sessionId: record.sessionId, siteKey: record.siteKey },
|
|
1266
|
+
}));
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
async getSessionRecordBySessionId(sessionId) {
|
|
1270
|
+
const filter = { sessionId };
|
|
1271
|
+
const doc = await this.tables.session
|
|
1272
|
+
.findOne(filter, SESSION_PROJECTION)
|
|
1273
|
+
.lean();
|
|
1274
|
+
return doc || undefined;
|
|
1275
|
+
}
|
|
1276
|
+
async getSessionRecordByToken(token) {
|
|
1277
|
+
const filter = { token };
|
|
1278
|
+
const record = await this.tables.session.findOne(filter).lean();
|
|
1279
|
+
return record || undefined;
|
|
1280
|
+
}
|
|
1281
|
+
async checkAndRemoveSession(sessionId) {
|
|
1282
|
+
this.logger.debug(() => ({
|
|
1283
|
+
data: { action: "checking and removing", sessionId },
|
|
1284
|
+
}));
|
|
1285
|
+
const filter = {
|
|
1286
|
+
sessionId,
|
|
1287
|
+
deleted: { $exists: false },
|
|
1288
|
+
};
|
|
1289
|
+
try {
|
|
1290
|
+
const session = await this.tables.session
|
|
1291
|
+
.findOneAndUpdate(filter, {
|
|
1292
|
+
deleted: true,
|
|
1293
|
+
lastUpdatedTimestamp: new Date(),
|
|
1294
|
+
pendingStage: true,
|
|
1295
|
+
})
|
|
1296
|
+
.lean();
|
|
1297
|
+
return session || undefined;
|
|
1298
|
+
}
|
|
1299
|
+
catch (err) {
|
|
1300
|
+
throw new ProsopoError({
|
|
1301
|
+
code: "DB_QUERY_FAILED",
|
|
1302
|
+
errorSite: "DBSE-30",
|
|
1303
|
+
message: "Failed to check and remove session",
|
|
1304
|
+
cause: err,
|
|
1305
|
+
context: { sessionId },
|
|
1306
|
+
});
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
async claimSessionServerCheck(sessionId) {
|
|
1310
|
+
try {
|
|
1311
|
+
const result = await this.tables.session.updateOne({ sessionId, serverChecked: { $ne: true } }, {
|
|
1312
|
+
$set: {
|
|
1313
|
+
serverChecked: true,
|
|
1314
|
+
lastUpdatedTimestamp: new Date(),
|
|
1315
|
+
pendingStage: true,
|
|
1316
|
+
},
|
|
1317
|
+
});
|
|
1318
|
+
return result.modifiedCount > 0;
|
|
1319
|
+
}
|
|
1320
|
+
catch (err) {
|
|
1321
|
+
throw new ProsopoError({
|
|
1322
|
+
code: "DB_QUERY_FAILED",
|
|
1323
|
+
errorSite: "DBSE-31",
|
|
1324
|
+
message: "Failed to get session",
|
|
1325
|
+
cause: err,
|
|
1326
|
+
context: { sessionId },
|
|
1327
|
+
});
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
async updateSessionRecord(sessionId, updates, streamToCentral) {
|
|
1331
|
+
try {
|
|
1332
|
+
await this.tables.session.updateOne({ sessionId }, {
|
|
1333
|
+
$set: {
|
|
1334
|
+
...updates,
|
|
1335
|
+
lastUpdatedTimestamp: new Date(),
|
|
1336
|
+
pendingStage: true,
|
|
1337
|
+
},
|
|
1338
|
+
});
|
|
1339
|
+
if (streamToCentral && this.centralStreamer) {
|
|
1340
|
+
const streamer = this.centralStreamer;
|
|
1341
|
+
const markStored = (ts) => this.tables.session
|
|
1342
|
+
.updateOne({ sessionId, lastUpdatedTimestamp: { $lte: ts } }, {
|
|
1343
|
+
$set: { storedAtTimestamp: ts },
|
|
1344
|
+
$unset: { pendingStage: 1 },
|
|
1345
|
+
})
|
|
1346
|
+
.then(() => { });
|
|
1347
|
+
this.tables.session
|
|
1348
|
+
.findOne({ sessionId })
|
|
1349
|
+
.lean()
|
|
1350
|
+
.then((record) => {
|
|
1351
|
+
if (record) {
|
|
1352
|
+
streamer.streamSessionRecord(record, markStored);
|
|
1353
|
+
}
|
|
1354
|
+
})
|
|
1355
|
+
.catch(() => { });
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
catch (err) {
|
|
1359
|
+
throw new ProsopoError({
|
|
1360
|
+
code: "DB_QUERY_FAILED",
|
|
1361
|
+
errorSite: "DBSE-32",
|
|
1362
|
+
message: "Failed to get session",
|
|
1363
|
+
cause: err,
|
|
1364
|
+
context: { sessionId },
|
|
1365
|
+
});
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
async recordSessionSimdReadingsIfAbsent(sessionId, readings, stage) {
|
|
1369
|
+
try {
|
|
1370
|
+
await this.tables.session.updateOne({ sessionId }, [
|
|
1371
|
+
{
|
|
1372
|
+
$set: {
|
|
1373
|
+
simdReadings: { $ifNull: ["$simdReadings", readings] },
|
|
1374
|
+
simdReadingsStage: { $ifNull: ["$simdReadingsStage", stage] },
|
|
1375
|
+
lastUpdatedTimestamp: new Date(),
|
|
1376
|
+
pendingStage: true,
|
|
1377
|
+
},
|
|
1378
|
+
},
|
|
1379
|
+
], { updatePipeline: true });
|
|
1380
|
+
}
|
|
1381
|
+
catch (err) {
|
|
1382
|
+
throw new ProsopoError({
|
|
1383
|
+
code: "DB_QUERY_FAILED",
|
|
1384
|
+
errorSite: "DBSE-33",
|
|
1385
|
+
message: "Failed to get session",
|
|
1386
|
+
cause: err,
|
|
1387
|
+
context: { sessionId, stage },
|
|
1388
|
+
});
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
async mergeSessionDnsEvent(sessionId, fields, receivedAtIfAbsent) {
|
|
1392
|
+
const setStage = {
|
|
1393
|
+
"dnsEvent.receivedAt": {
|
|
1394
|
+
$ifNull: ["$dnsEvent.receivedAt", receivedAtIfAbsent],
|
|
1395
|
+
},
|
|
1396
|
+
lastUpdatedTimestamp: new Date(),
|
|
1397
|
+
pendingStage: true,
|
|
1398
|
+
};
|
|
1399
|
+
if (fields.resolverIp !== undefined) {
|
|
1400
|
+
setStage["dnsEvent.resolverIp"] = fields.resolverIp;
|
|
1401
|
+
}
|
|
1402
|
+
if (fields.peerIp !== undefined) {
|
|
1403
|
+
setStage["dnsEvent.peerIp"] = fields.peerIp;
|
|
1404
|
+
}
|
|
1405
|
+
if (fields.pathValid !== undefined) {
|
|
1406
|
+
setStage["dnsEvent.pathValid"] = fields.pathValid;
|
|
1407
|
+
}
|
|
1408
|
+
try {
|
|
1409
|
+
const result = await this.tables.session.updateOne({ sessionId }, [{ $set: setStage }], { updatePipeline: true });
|
|
1410
|
+
return result.matchedCount > 0;
|
|
1411
|
+
}
|
|
1412
|
+
catch (err) {
|
|
1413
|
+
throw new ProsopoError({
|
|
1414
|
+
code: "DB_QUERY_FAILED",
|
|
1415
|
+
errorSite: "DBSE-34",
|
|
1416
|
+
message: "Failed to get session",
|
|
1417
|
+
cause: err,
|
|
1418
|
+
context: { sessionId },
|
|
1419
|
+
});
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
async getSessionByuserSitekeyIpHash(userSitekeyIpHash) {
|
|
1423
|
+
this.logger.debug(() => ({
|
|
1424
|
+
data: { action: "getting session by user IP hash", userSitekeyIpHash },
|
|
1425
|
+
}));
|
|
1426
|
+
const filter = {
|
|
1427
|
+
userSitekeyIpHash,
|
|
1428
|
+
deleted: { $exists: false },
|
|
1429
|
+
};
|
|
1430
|
+
try {
|
|
1431
|
+
const session = await this.tables.session
|
|
1432
|
+
.findOne(filter)
|
|
1433
|
+
.lean();
|
|
1434
|
+
return session || undefined;
|
|
1435
|
+
}
|
|
1436
|
+
catch (err) {
|
|
1437
|
+
throw new ProsopoError({
|
|
1438
|
+
code: "DB_QUERY_FAILED",
|
|
1439
|
+
errorSite: "DBSE-35",
|
|
1440
|
+
message: "Failed to get session",
|
|
1441
|
+
cause: err,
|
|
1442
|
+
context: { userSitekeyIpHash },
|
|
1443
|
+
});
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
getUnstoredSessionRecords(limit = 1000, afterId) {
|
|
1447
|
+
const filter = {
|
|
1448
|
+
pendingStage: true,
|
|
1449
|
+
};
|
|
1450
|
+
if (afterId !== undefined) {
|
|
1451
|
+
filter._id = { $gt: afterId };
|
|
1452
|
+
}
|
|
1453
|
+
return Promise.resolve(this.tables?.session)
|
|
1454
|
+
.then((tbl) => tbl
|
|
1455
|
+
?.find(filter)
|
|
1456
|
+
.hint("pendingStage_partial")
|
|
1457
|
+
.sort({ _id: 1 })
|
|
1458
|
+
.limit(limit)
|
|
1459
|
+
.lean())
|
|
1460
|
+
.then((docs) => docs || []);
|
|
1461
|
+
}
|
|
1462
|
+
async markSessionRecordsStored(sessionIds, asOfTimestamp = new Date()) {
|
|
1463
|
+
await this.tables?.session.updateMany({
|
|
1464
|
+
sessionId: { $in: sessionIds },
|
|
1465
|
+
lastUpdatedTimestamp: { $lte: asOfTimestamp },
|
|
1466
|
+
}, {
|
|
1467
|
+
$set: { storedAtTimestamp: new Date() },
|
|
1468
|
+
$unset: { pendingStage: 1 },
|
|
1469
|
+
}, { upsert: false });
|
|
1470
|
+
}
|
|
1471
|
+
async storePendingImageCommitment(userAccount, requestHash, salt, deadlineTimestamp, requestedAtTimestamp, ipAddress, threshold, sessionId, ipInfo) {
|
|
1472
|
+
if (!isHex(requestHash)) {
|
|
1473
|
+
throw new ProsopoError({
|
|
1474
|
+
code: "INVALID_DATA",
|
|
1475
|
+
errorSite: "DBSE-36",
|
|
1476
|
+
message: "Invalid hash",
|
|
1477
|
+
context: {
|
|
1478
|
+
failedFuncName: this.storePendingImageCommitment.name,
|
|
1479
|
+
requestHash,
|
|
1480
|
+
},
|
|
1481
|
+
});
|
|
1482
|
+
}
|
|
1483
|
+
const pendingRecord = {
|
|
1484
|
+
userAccount,
|
|
1485
|
+
pending: true,
|
|
1486
|
+
salt,
|
|
1487
|
+
requestHash,
|
|
1488
|
+
deadlineTimestamp,
|
|
1489
|
+
requestedAtTimestamp,
|
|
1490
|
+
ipAddress,
|
|
1491
|
+
sessionId,
|
|
1492
|
+
threshold,
|
|
1493
|
+
ipInfo,
|
|
1494
|
+
dappAccount: "",
|
|
1495
|
+
providerAccount: "",
|
|
1496
|
+
datasetId: "",
|
|
1497
|
+
id: "",
|
|
1498
|
+
result: { status: CaptchaStatus.pending },
|
|
1499
|
+
headers: {},
|
|
1500
|
+
ja4: "",
|
|
1501
|
+
userSignature: "",
|
|
1502
|
+
userSubmitted: false,
|
|
1503
|
+
serverChecked: false,
|
|
1504
|
+
};
|
|
1505
|
+
await this.tables?.commitment.updateOne({ requestHash: requestHash }, { $set: pendingRecord }, { upsert: true });
|
|
1506
|
+
}
|
|
1507
|
+
async getPendingImageCommitment(requestHash) {
|
|
1508
|
+
if (!isHex(requestHash)) {
|
|
1509
|
+
throw new ProsopoError({
|
|
1510
|
+
code: "INVALID_DATA",
|
|
1511
|
+
errorSite: "DBSE-37",
|
|
1512
|
+
message: "Invalid hash",
|
|
1513
|
+
context: {
|
|
1514
|
+
failedFuncName: this.getPendingImageCommitment.name,
|
|
1515
|
+
requestHash,
|
|
1516
|
+
},
|
|
1517
|
+
});
|
|
1518
|
+
}
|
|
1519
|
+
const doc = await this.tables?.commitment
|
|
1520
|
+
.findOne({
|
|
1521
|
+
requestHash: requestHash,
|
|
1522
|
+
pending: true,
|
|
1523
|
+
})
|
|
1524
|
+
.lean();
|
|
1525
|
+
if (doc) {
|
|
1526
|
+
return {
|
|
1527
|
+
dappAccount: doc.dappAccount,
|
|
1528
|
+
pending: doc.pending,
|
|
1529
|
+
salt: doc.salt,
|
|
1530
|
+
requestHash: doc.requestHash,
|
|
1531
|
+
deadlineTimestamp: doc.deadlineTimestamp,
|
|
1532
|
+
requestedAtTimestamp: doc.requestedAtTimestamp,
|
|
1533
|
+
ipAddress: doc.ipAddress,
|
|
1534
|
+
sessionId: doc.sessionId,
|
|
1535
|
+
threshold: doc.threshold,
|
|
1536
|
+
};
|
|
1537
|
+
}
|
|
1538
|
+
throw new ProsopoError({
|
|
1539
|
+
code: "RECORD_NOT_FOUND",
|
|
1540
|
+
errorSite: "DBSE-38",
|
|
1541
|
+
message: "No pending record found",
|
|
1542
|
+
context: {
|
|
1543
|
+
failedFuncName: this.getPendingImageCommitment.name,
|
|
1544
|
+
requestHash,
|
|
1545
|
+
},
|
|
1546
|
+
});
|
|
1547
|
+
}
|
|
1548
|
+
async updatePendingImageCommitmentStatus(requestHash) {
|
|
1549
|
+
if (!isHex(requestHash)) {
|
|
1550
|
+
throw new ProsopoError({
|
|
1551
|
+
code: "INVALID_DATA",
|
|
1552
|
+
errorSite: "DBSE-39",
|
|
1553
|
+
message: "Invalid hash",
|
|
1554
|
+
context: {
|
|
1555
|
+
failedFuncName: this.updatePendingImageCommitmentStatus.name,
|
|
1556
|
+
requestHash,
|
|
1557
|
+
},
|
|
1558
|
+
});
|
|
1559
|
+
}
|
|
1560
|
+
const result = await this.getTables().commitment.updateOne({ requestHash: requestHash, pending: true }, {
|
|
1561
|
+
$set: {
|
|
1562
|
+
pending: false,
|
|
1563
|
+
},
|
|
1564
|
+
});
|
|
1565
|
+
return result.modifiedCount > 0;
|
|
1566
|
+
}
|
|
1567
|
+
async getAllCaptchasByDatasetId(datasetId, state) {
|
|
1568
|
+
const filter = {
|
|
1569
|
+
datasetId,
|
|
1570
|
+
solved: state === CaptchaStates.Solved,
|
|
1571
|
+
};
|
|
1572
|
+
const cursor = this.tables?.captcha
|
|
1573
|
+
.find(filter)
|
|
1574
|
+
.lean();
|
|
1575
|
+
const docs = await cursor;
|
|
1576
|
+
if (docs) {
|
|
1577
|
+
return docs.map(({ _id, ...keepAttrs }) => keepAttrs);
|
|
1578
|
+
}
|
|
1579
|
+
throw new ProsopoError({
|
|
1580
|
+
code: "DB_QUERY_FAILED",
|
|
1581
|
+
errorSite: "DBSE-40",
|
|
1582
|
+
message: "Failed to get captcha",
|
|
1583
|
+
});
|
|
1584
|
+
}
|
|
1585
|
+
async getAllDappUserSolutions(captchaId) {
|
|
1586
|
+
const filter = {
|
|
1587
|
+
captchaId: { $in: captchaId },
|
|
1588
|
+
};
|
|
1589
|
+
const cursor = this.tables?.usersolution
|
|
1590
|
+
?.find(filter)
|
|
1591
|
+
.lean();
|
|
1592
|
+
const docs = await cursor;
|
|
1593
|
+
if (docs) {
|
|
1594
|
+
return docs.map(({ _id, ...keepAttrs }) => keepAttrs);
|
|
1595
|
+
}
|
|
1596
|
+
throw new ProsopoError({
|
|
1597
|
+
code: "DB_QUERY_FAILED",
|
|
1598
|
+
errorSite: "DBSE-41",
|
|
1599
|
+
message: "Failed to get solution",
|
|
1600
|
+
});
|
|
1601
|
+
}
|
|
1602
|
+
async getDatasetIdWithSolvedCaptchasOfSizeN(solvedCaptchaCount) {
|
|
1603
|
+
const cursor = this.tables?.solution.aggregate([
|
|
1604
|
+
{
|
|
1605
|
+
$match: {},
|
|
1606
|
+
},
|
|
1607
|
+
{
|
|
1608
|
+
$group: {
|
|
1609
|
+
_id: "$datasetId",
|
|
1610
|
+
count: { $sum: 1 },
|
|
1611
|
+
},
|
|
1612
|
+
},
|
|
1613
|
+
{
|
|
1614
|
+
$match: {
|
|
1615
|
+
count: { $gte: solvedCaptchaCount },
|
|
1616
|
+
},
|
|
1617
|
+
},
|
|
1618
|
+
{
|
|
1619
|
+
$sample: { size: 1 },
|
|
1620
|
+
},
|
|
1621
|
+
]);
|
|
1622
|
+
const docs = await cursor;
|
|
1623
|
+
if (docs?.length) {
|
|
1624
|
+
return docs[0]._id;
|
|
1625
|
+
}
|
|
1626
|
+
throw new ProsopoError({
|
|
1627
|
+
code: "DATASET_INVALID",
|
|
1628
|
+
errorSite: "DBSE-42",
|
|
1629
|
+
message: "No datasets found with required number of solutions",
|
|
1630
|
+
});
|
|
1631
|
+
}
|
|
1632
|
+
async getRandomSolvedCaptchasFromSingleDataset(datasetId, size) {
|
|
1633
|
+
if (!isHex(datasetId)) {
|
|
1634
|
+
throw new ProsopoError({
|
|
1635
|
+
code: "INVALID_DATA",
|
|
1636
|
+
errorSite: "DBSE-43",
|
|
1637
|
+
message: "Invalid hash",
|
|
1638
|
+
context: {
|
|
1639
|
+
failedFuncName: this.getRandomSolvedCaptchasFromSingleDataset.name,
|
|
1640
|
+
datasetId,
|
|
1641
|
+
},
|
|
1642
|
+
});
|
|
1643
|
+
}
|
|
1644
|
+
const sampleSize = size ? Math.abs(Math.trunc(size)) : 1;
|
|
1645
|
+
const cursor = this.tables?.solution.aggregate([
|
|
1646
|
+
{ $match: { datasetId } },
|
|
1647
|
+
{ $sample: { size: sampleSize } },
|
|
1648
|
+
{
|
|
1649
|
+
$project: {
|
|
1650
|
+
captchaId: 1,
|
|
1651
|
+
captchaContentId: 1,
|
|
1652
|
+
solution: 1,
|
|
1653
|
+
},
|
|
1654
|
+
},
|
|
1655
|
+
]);
|
|
1656
|
+
const docs = await cursor;
|
|
1657
|
+
if (docs?.length) {
|
|
1658
|
+
return docs;
|
|
1659
|
+
}
|
|
1660
|
+
throw new ProsopoError({
|
|
1661
|
+
code: "DB_QUERY_FAILED",
|
|
1662
|
+
errorSite: "DBSE-44",
|
|
1663
|
+
message: "Failed to get solution",
|
|
1664
|
+
context: {
|
|
1665
|
+
failedFuncName: this.getRandomSolvedCaptchasFromSingleDataset.name,
|
|
1666
|
+
datasetId,
|
|
1667
|
+
size,
|
|
1668
|
+
},
|
|
1669
|
+
});
|
|
1670
|
+
}
|
|
1671
|
+
async getDappUserSolutionById(commitmentId) {
|
|
1672
|
+
const filter = {
|
|
1673
|
+
commitmentId: commitmentId,
|
|
1674
|
+
};
|
|
1675
|
+
const project = { _id: 0 };
|
|
1676
|
+
const cursor = this.tables?.usersolution?.findOne(filter, project).lean();
|
|
1677
|
+
const doc = await cursor;
|
|
1678
|
+
if (doc) {
|
|
1679
|
+
return doc;
|
|
1680
|
+
}
|
|
1681
|
+
throw new ProsopoError({
|
|
1682
|
+
code: "DB_QUERY_FAILED",
|
|
1683
|
+
errorSite: "DBSE-45",
|
|
1684
|
+
message: "Failed to get solution",
|
|
1685
|
+
context: { failedFuncName: this.getCaptchaById.name, commitmentId },
|
|
1686
|
+
});
|
|
1687
|
+
}
|
|
1688
|
+
async getDappUserCommitmentById(commitmentId) {
|
|
1689
|
+
const filter = { id: commitmentId };
|
|
1690
|
+
const commitmentCursor = this.tables?.commitment
|
|
1691
|
+
?.findOne(filter, ProviderDatabase.DAPP_USER_COMMITMENT_PROJECTION)
|
|
1692
|
+
.lean();
|
|
1693
|
+
const doc = await commitmentCursor;
|
|
1694
|
+
return doc ? doc : undefined;
|
|
1695
|
+
}
|
|
1696
|
+
async getDappUserCommitmentByAccount(userAccount, dappAccount) {
|
|
1697
|
+
const filter = {
|
|
1698
|
+
userAccount,
|
|
1699
|
+
dappAccount,
|
|
1700
|
+
};
|
|
1701
|
+
const sort = { sort: { _id: -1 } };
|
|
1702
|
+
const docs = await this.tables?.commitment
|
|
1703
|
+
?.find(filter, ProviderDatabase.DAPP_USER_COMMITMENT_PROJECTION, sort)
|
|
1704
|
+
.lean();
|
|
1705
|
+
return docs ? docs : [];
|
|
1706
|
+
}
|
|
1707
|
+
async approveDappUserCommitment(commitmentId, coords) {
|
|
1708
|
+
try {
|
|
1709
|
+
const result = { status: CaptchaStatus.approved };
|
|
1710
|
+
const updateDoc = {
|
|
1711
|
+
result,
|
|
1712
|
+
lastUpdatedTimestamp: new Date(),
|
|
1713
|
+
pendingStage: true,
|
|
1714
|
+
blocked: isBlockingCaptchaResult(CaptchaType.image, result),
|
|
1715
|
+
...(coords ? { coords } : {}),
|
|
1716
|
+
};
|
|
1717
|
+
const filter = { id: commitmentId };
|
|
1718
|
+
await this.tables?.commitment
|
|
1719
|
+
?.findOneAndUpdate(filter, { $set: updateDoc }, { upsert: false })
|
|
1720
|
+
.lean();
|
|
1721
|
+
this.centralStreamer?.streamImageUpdate(() => this.tables.commitment
|
|
1722
|
+
.findOne({ id: commitmentId })
|
|
1723
|
+
.lean(), (ts) => this.tables.commitment
|
|
1724
|
+
.updateOne({ id: commitmentId, lastUpdatedTimestamp: { $lte: ts } }, {
|
|
1725
|
+
$set: { storedAtTimestamp: ts },
|
|
1726
|
+
$unset: { pendingStage: 1 },
|
|
1727
|
+
})
|
|
1728
|
+
.then(() => { }));
|
|
1729
|
+
}
|
|
1730
|
+
catch (err) {
|
|
1731
|
+
throw new ProsopoError({
|
|
1732
|
+
code: "DB_QUERY_FAILED",
|
|
1733
|
+
errorSite: "DBSE-46",
|
|
1734
|
+
message: "Failed to approve solution",
|
|
1735
|
+
cause: err,
|
|
1736
|
+
context: { commitmentId },
|
|
1737
|
+
});
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
async disapproveDappUserCommitment(commitmentId, reason, coords) {
|
|
1741
|
+
try {
|
|
1742
|
+
const result = {
|
|
1743
|
+
status: CaptchaStatus.disapproved,
|
|
1744
|
+
reason,
|
|
1745
|
+
};
|
|
1746
|
+
const updateDoc = {
|
|
1747
|
+
result,
|
|
1748
|
+
lastUpdatedTimestamp: new Date(),
|
|
1749
|
+
pendingStage: true,
|
|
1750
|
+
blocked: isBlockingCaptchaResult(CaptchaType.image, result),
|
|
1751
|
+
...(coords ? { coords } : {}),
|
|
1752
|
+
};
|
|
1753
|
+
const filter = { id: commitmentId };
|
|
1754
|
+
await this.tables?.commitment
|
|
1755
|
+
?.findOneAndUpdate(filter, { $set: updateDoc }, { upsert: false })
|
|
1756
|
+
.lean();
|
|
1757
|
+
this.centralStreamer?.streamImageUpdate(() => this.tables.commitment
|
|
1758
|
+
.findOne({ id: commitmentId })
|
|
1759
|
+
.lean(), (ts) => this.tables.commitment
|
|
1760
|
+
.updateOne({ id: commitmentId, lastUpdatedTimestamp: { $lte: ts } }, {
|
|
1761
|
+
$set: { storedAtTimestamp: ts },
|
|
1762
|
+
$unset: { pendingStage: 1 },
|
|
1763
|
+
})
|
|
1764
|
+
.then(() => { }));
|
|
1765
|
+
}
|
|
1766
|
+
catch (err) {
|
|
1767
|
+
throw new ProsopoError({
|
|
1768
|
+
code: "DB_QUERY_FAILED",
|
|
1769
|
+
errorSite: "DBSE-47",
|
|
1770
|
+
message: "Failed to approve solution",
|
|
1771
|
+
cause: err,
|
|
1772
|
+
context: { commitmentId },
|
|
1773
|
+
});
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
async flagProcessedDappUserSolutions(captchaIds) {
|
|
1777
|
+
try {
|
|
1778
|
+
await this.tables?.usersolution
|
|
1779
|
+
?.updateMany({ captchaId: { $in: captchaIds } }, { $set: { processed: true } }, { upsert: false })
|
|
1780
|
+
.lean();
|
|
1781
|
+
}
|
|
1782
|
+
catch (err) {
|
|
1783
|
+
throw new ProsopoError({
|
|
1784
|
+
code: "DB_QUERY_FAILED",
|
|
1785
|
+
errorSite: "DBSE-48",
|
|
1786
|
+
message: "Failed to flag solution as processed",
|
|
1787
|
+
cause: err,
|
|
1788
|
+
context: { captchaIds },
|
|
1789
|
+
});
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
async flagProcessedDappUserCommitments(commitmentIds) {
|
|
1793
|
+
try {
|
|
1794
|
+
const distinctCommitmentIds = [...new Set(commitmentIds)];
|
|
1795
|
+
await this.tables?.commitment
|
|
1796
|
+
?.updateMany({ id: { $in: distinctCommitmentIds } }, { $set: { processed: true } }, { upsert: false })
|
|
1797
|
+
.lean();
|
|
1798
|
+
}
|
|
1799
|
+
catch (err) {
|
|
1800
|
+
throw new ProsopoError({
|
|
1801
|
+
code: "DB_QUERY_FAILED",
|
|
1802
|
+
errorSite: "DBSE-49",
|
|
1803
|
+
message: "Failed to flag commitment as processed",
|
|
1804
|
+
cause: err,
|
|
1805
|
+
context: { commitmentIds },
|
|
1806
|
+
});
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
async getScheduledTaskStatus(taskId, status) {
|
|
1810
|
+
const filter = {
|
|
1811
|
+
_id: taskId,
|
|
1812
|
+
status: status,
|
|
1813
|
+
};
|
|
1814
|
+
const cursor = await this.tables?.scheduler
|
|
1815
|
+
?.findOne(filter)
|
|
1816
|
+
.lean();
|
|
1817
|
+
return cursor ? cursor : undefined;
|
|
1818
|
+
}
|
|
1819
|
+
async getLastScheduledTaskStatus(task, status) {
|
|
1820
|
+
const filter = { processName: task };
|
|
1821
|
+
if (status) {
|
|
1822
|
+
filter.status = status;
|
|
1823
|
+
}
|
|
1824
|
+
const sort = {
|
|
1825
|
+
datetime: -1,
|
|
1826
|
+
};
|
|
1827
|
+
const cursor = await this.tables?.scheduler
|
|
1828
|
+
?.findOne(filter)
|
|
1829
|
+
.sort(sort)
|
|
1830
|
+
.limit(1)
|
|
1831
|
+
.lean();
|
|
1832
|
+
return cursor ? cursor : undefined;
|
|
1833
|
+
}
|
|
1834
|
+
async createScheduledTaskStatus(taskName, status) {
|
|
1835
|
+
const now = new Date();
|
|
1836
|
+
const doc = ScheduledTaskSchema.parse({
|
|
1837
|
+
processName: taskName,
|
|
1838
|
+
datetime: now,
|
|
1839
|
+
status,
|
|
1840
|
+
});
|
|
1841
|
+
const taskRecord = await this.tables?.scheduler.create(doc);
|
|
1842
|
+
return taskRecord._id;
|
|
1843
|
+
}
|
|
1844
|
+
async updateScheduledTaskStatus(taskId, status, result) {
|
|
1845
|
+
const update = {
|
|
1846
|
+
status,
|
|
1847
|
+
updated: new Date(),
|
|
1848
|
+
...(result && { result }),
|
|
1849
|
+
};
|
|
1850
|
+
const filter = { _id: taskId };
|
|
1851
|
+
await this.tables?.scheduler.updateOne(filter, { $set: update }, {
|
|
1852
|
+
upsert: false,
|
|
1853
|
+
});
|
|
1854
|
+
}
|
|
1855
|
+
async cleanupScheduledTaskStatus(status) {
|
|
1856
|
+
const filter = {
|
|
1857
|
+
status,
|
|
1858
|
+
};
|
|
1859
|
+
await this.tables?.scheduler.deleteMany(filter);
|
|
1860
|
+
}
|
|
1861
|
+
async updateClientRecords(clientRecords) {
|
|
1862
|
+
const usable = clientRecords.filter((record) => !!record.account);
|
|
1863
|
+
const skipped = clientRecords.length - usable.length;
|
|
1864
|
+
if (skipped > 0) {
|
|
1865
|
+
await this.logger.error(() => ({
|
|
1866
|
+
msg: "Refusing to upsert client records with no account",
|
|
1867
|
+
data: { skipped, received: clientRecords.length },
|
|
1868
|
+
}));
|
|
1869
|
+
}
|
|
1870
|
+
const ops = usable.map((record) => {
|
|
1871
|
+
const clientRecord = {
|
|
1872
|
+
account: record.account,
|
|
1873
|
+
settings: record.settings,
|
|
1874
|
+
tier: record.tier,
|
|
1875
|
+
};
|
|
1876
|
+
const filter = {
|
|
1877
|
+
account: record.account,
|
|
1878
|
+
};
|
|
1879
|
+
return {
|
|
1880
|
+
updateOne: {
|
|
1881
|
+
filter,
|
|
1882
|
+
update: {
|
|
1883
|
+
$set: clientRecord,
|
|
1884
|
+
},
|
|
1885
|
+
upsert: true,
|
|
1886
|
+
},
|
|
1887
|
+
};
|
|
1888
|
+
});
|
|
1889
|
+
if (!ops || ops.length === 0) {
|
|
1890
|
+
await this.logger.debug(() => ({ msg: "No client updates to apply" }));
|
|
1891
|
+
return;
|
|
1892
|
+
}
|
|
1893
|
+
try {
|
|
1894
|
+
await this.tables?.client.bulkWrite(ops);
|
|
1895
|
+
}
|
|
1896
|
+
finally {
|
|
1897
|
+
clientRecordCache.clear();
|
|
1898
|
+
}
|
|
1899
|
+
}
|
|
1900
|
+
async removeClientRecords(accounts) {
|
|
1901
|
+
if (!accounts || accounts.length === 0) {
|
|
1902
|
+
return;
|
|
1903
|
+
}
|
|
1904
|
+
try {
|
|
1905
|
+
await this.tables?.client.deleteMany({
|
|
1906
|
+
account: { $in: accounts },
|
|
1907
|
+
});
|
|
1908
|
+
}
|
|
1909
|
+
finally {
|
|
1910
|
+
clientRecordCache.clear();
|
|
1911
|
+
}
|
|
1912
|
+
}
|
|
1913
|
+
async getAllClientRecords() {
|
|
1914
|
+
const docs = await this.tables?.client.find().lean();
|
|
1915
|
+
return docs || [];
|
|
1916
|
+
}
|
|
1917
|
+
async getClientRecord(account) {
|
|
1918
|
+
const cacheKey = `${this.url}|${account}`;
|
|
1919
|
+
const cached = clientRecordCache.get(cacheKey);
|
|
1920
|
+
if (cached)
|
|
1921
|
+
return cached;
|
|
1922
|
+
const filter = { account };
|
|
1923
|
+
const doc = await this.tables?.client
|
|
1924
|
+
.findOne(filter, {
|
|
1925
|
+
account: 1,
|
|
1926
|
+
settings: 1,
|
|
1927
|
+
tier: 1,
|
|
1928
|
+
})
|
|
1929
|
+
.lean();
|
|
1930
|
+
if (!doc)
|
|
1931
|
+
return undefined;
|
|
1932
|
+
clientRecordCache.set(cacheKey, doc);
|
|
1933
|
+
return doc;
|
|
1934
|
+
}
|
|
1935
|
+
async upsertDecisionMachineArtifact(artifact) {
|
|
1936
|
+
const now = new Date();
|
|
1937
|
+
const dappAccount = artifact.dappAccount ?? null;
|
|
1938
|
+
const kind = artifact.kind ?? DecisionMachineKind.Routing;
|
|
1939
|
+
const filter = {
|
|
1940
|
+
scope: artifact.scope,
|
|
1941
|
+
dappAccount,
|
|
1942
|
+
kind,
|
|
1943
|
+
};
|
|
1944
|
+
await this.tables?.decisionMachine.updateOne(filter, {
|
|
1945
|
+
$set: {
|
|
1946
|
+
scope: artifact.scope,
|
|
1947
|
+
dappAccount,
|
|
1948
|
+
kind,
|
|
1949
|
+
runtime: artifact.runtime,
|
|
1950
|
+
language: artifact.language,
|
|
1951
|
+
source: artifact.source,
|
|
1952
|
+
name: artifact.name,
|
|
1953
|
+
version: artifact.version,
|
|
1954
|
+
captchaType: artifact.captchaType,
|
|
1955
|
+
updatedAt: now,
|
|
1956
|
+
},
|
|
1957
|
+
$setOnInsert: {
|
|
1958
|
+
createdAt: now,
|
|
1959
|
+
},
|
|
1960
|
+
}, { upsert: true });
|
|
1961
|
+
}
|
|
1962
|
+
async getDecisionMachineArtifact(scope, dappAccount, kind) {
|
|
1963
|
+
const filter = {
|
|
1964
|
+
scope,
|
|
1965
|
+
dappAccount: dappAccount ?? null,
|
|
1966
|
+
};
|
|
1967
|
+
if (kind !== undefined) {
|
|
1968
|
+
filter.kind = kind;
|
|
1969
|
+
}
|
|
1970
|
+
const doc = await this.tables?.decisionMachine
|
|
1971
|
+
.findOne(filter)
|
|
1972
|
+
.lean();
|
|
1973
|
+
return doc ?? undefined;
|
|
1974
|
+
}
|
|
1975
|
+
async getAllDecisionMachineArtifacts() {
|
|
1976
|
+
const docs = await this.tables?.decisionMachine
|
|
1977
|
+
.find({})
|
|
1978
|
+
.lean();
|
|
1979
|
+
return docs ?? [];
|
|
1980
|
+
}
|
|
1981
|
+
async getDecisionMachineArtifactById(id) {
|
|
1982
|
+
const doc = await this.tables?.decisionMachine
|
|
1983
|
+
.findById(id)
|
|
1984
|
+
.lean();
|
|
1985
|
+
return doc ?? undefined;
|
|
1986
|
+
}
|
|
1987
|
+
async removeDecisionMachineArtifact(id) {
|
|
1988
|
+
const result = await this.tables?.decisionMachine.deleteOne({
|
|
1989
|
+
_id: id,
|
|
1990
|
+
});
|
|
1991
|
+
return (result?.deletedCount ?? 0) > 0;
|
|
1992
|
+
}
|
|
1993
|
+
async removeAllDecisionMachineArtifacts() {
|
|
1994
|
+
const result = await this.tables?.decisionMachine.deleteMany({});
|
|
1995
|
+
return result?.deletedCount ?? 0;
|
|
1996
|
+
}
|
|
1997
|
+
async getSpamEmailDomain(domain) {
|
|
1998
|
+
if (!this.tables?.spamEmailDomain) {
|
|
1999
|
+
throw new ProsopoError({
|
|
2000
|
+
code: "DB_QUERY_FAILED",
|
|
2001
|
+
errorSite: "DBSE-50",
|
|
2002
|
+
message: "Failed to import database engine",
|
|
2003
|
+
context: { failedFuncName: this.getSpamEmailDomain.name },
|
|
2004
|
+
});
|
|
2005
|
+
}
|
|
2006
|
+
const suffixCandidates = buildDomainSuffixCandidates(domain).slice(0, MAX_DOMAIN_SUFFIX_CANDIDATES);
|
|
2007
|
+
const query = suffixCandidates.length > 0
|
|
2008
|
+
? { domain: { $in: suffixCandidates } }
|
|
2009
|
+
: { domain };
|
|
2010
|
+
return await this.tables.spamEmailDomain
|
|
2011
|
+
.findOne(query)
|
|
2012
|
+
.exec();
|
|
2013
|
+
}
|
|
2014
|
+
async bulkUpdateSpamEmailDomains(domains, upsert) {
|
|
2015
|
+
if (!this.tables?.spamEmailDomain) {
|
|
2016
|
+
throw new ProsopoError({
|
|
2017
|
+
code: "DB_QUERY_FAILED",
|
|
2018
|
+
errorSite: "DBSE-51",
|
|
2019
|
+
message: "Failed to import database engine",
|
|
2020
|
+
context: { failedFuncName: this.bulkUpdateSpamEmailDomains.name },
|
|
2021
|
+
});
|
|
2022
|
+
}
|
|
2023
|
+
const bulkOps = domains.map((op) => ({
|
|
2024
|
+
updateOne: {
|
|
2025
|
+
filter: op.filter,
|
|
2026
|
+
update: { $set: op.update },
|
|
2027
|
+
upsert,
|
|
2028
|
+
},
|
|
2029
|
+
}));
|
|
2030
|
+
if (bulkOps.length > 0) {
|
|
2031
|
+
await this.tables.spamEmailDomain.bulkWrite(bulkOps);
|
|
2032
|
+
}
|
|
2033
|
+
}
|
|
2034
|
+
}
|
|
2035
|
+
ProviderDatabase.DAPP_USER_COMMITMENT_PROJECTION = {
|
|
2036
|
+
id: 1,
|
|
2037
|
+
result: 1,
|
|
2038
|
+
serverChecked: 1,
|
|
2039
|
+
requestedAtTimestamp: 1,
|
|
2040
|
+
submittedAtTimestamp: 1,
|
|
2041
|
+
verifiedAtTimestamp: 1,
|
|
2042
|
+
failedAtTimestamp: 1,
|
|
2043
|
+
ipAddress: 1,
|
|
2044
|
+
sessionId: 1,
|
|
2045
|
+
userAccount: 1,
|
|
2046
|
+
dappAccount: 1,
|
|
2047
|
+
headers: 1,
|
|
2048
|
+
ipInfo: 1,
|
|
2049
|
+
behavioralDataPacked: 1,
|
|
2050
|
+
deviceCapability: 1,
|
|
2051
|
+
coords: 1,
|
|
2052
|
+
clientMetaData: 1,
|
|
2053
|
+
};
|
|
2054
|
+
//# sourceMappingURL=provider.js.map
|