@prosopo/database 4.1.4 → 4.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build$colon$cjs.log +4 -4
- package/.turbo/turbo-build$colon$tsc.log +15 -15
- package/.turbo/turbo-build.log +4 -4
- package/CHANGELOG.md +43 -0
- package/dist/cjs/databases/captcha.cjs +38 -22
- package/dist/cjs/databases/centralDbStreamer.cjs +26 -8
- package/dist/cjs/databases/provider.cjs +243 -103
- package/dist/databases/captcha.d.ts +8 -3
- package/dist/databases/captcha.d.ts.map +1 -1
- package/dist/databases/captcha.js +39 -23
- package/dist/databases/captcha.js.map +1 -1
- package/dist/databases/centralDbStreamer.d.ts +7 -1
- package/dist/databases/centralDbStreamer.d.ts.map +1 -1
- package/dist/databases/centralDbStreamer.js +26 -8
- package/dist/databases/centralDbStreamer.js.map +1 -1
- package/dist/databases/provider.d.ts +26 -2
- package/dist/databases/provider.d.ts.map +1 -1
- package/dist/databases/provider.js +244 -104
- package/dist/databases/provider.js.map +1 -1
- package/dist/tests/integration/countCommitmentsByNormalisedEmail.integration.test.js +67 -0
- package/dist/tests/integration/countCommitmentsByNormalisedEmail.integration.test.js.map +1 -1
- package/dist/tests/integration/projectionContracts.integration.test.js +54 -0
- package/dist/tests/integration/projectionContracts.integration.test.js.map +1 -1
- package/dist/tests/integration/serverCheckClaim.integration.test.js +52 -0
- package/dist/tests/integration/serverCheckClaim.integration.test.js.map +1 -1
- package/package.json +5 -5
|
@@ -45,9 +45,9 @@ dist/cjs/index.cjs 1.66 kB │ gzip: 0.49 kB
|
|
|
45
45
|
dist/cjs/databases/client.cjs 1.79 kB │ gzip: 0.77 kB
|
|
46
46
|
dist/cjs/mongooseOptions.cjs 3.74 kB │ gzip: 1.56 kB
|
|
47
47
|
dist/cjs/base/mongo.cjs 4.27 kB │ gzip: 1.41 kB
|
|
48
|
-
dist/cjs/databases/centralDbStreamer.cjs
|
|
49
|
-
dist/cjs/databases/captcha.cjs
|
|
48
|
+
dist/cjs/databases/centralDbStreamer.cjs 6.40 kB │ gzip: 1.52 kB
|
|
49
|
+
dist/cjs/databases/captcha.cjs 8.20 kB │ gzip: 2.36 kB
|
|
50
50
|
dist/cjs/redisCache.cjs 14.31 kB │ gzip: 4.46 kB
|
|
51
|
-
dist/cjs/databases/provider.cjs
|
|
51
|
+
dist/cjs/databases/provider.cjs 67.79 kB │ gzip: 13.13 kB
|
|
52
52
|
|
|
53
|
-
[32m✓ built in
|
|
53
|
+
[32m✓ built in 109ms[39m
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
$ tsc --build --verbose
|
|
2
|
-
|
|
2
|
+
11:33:58 AM - Projects in this build:
|
|
3
3
|
* ../../dev/config/tsconfig.json
|
|
4
4
|
* ../util/tsconfig.json
|
|
5
5
|
* ../locale/tsconfig.json
|
|
@@ -14,31 +14,31 @@ $ tsc --build --verbose
|
|
|
14
14
|
* ../types-database/tsconfig.json
|
|
15
15
|
* tsconfig.json
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
11:33:58 AM - Project '../../dev/config/tsconfig.json' is up to date because newest input '../../dev/config/src/webpack/webpack.config.ts' is older than output '../../dev/config/tsconfig.tsbuildinfo'
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
11:33:58 AM - Project '../util/tsconfig.json' is up to date because newest input '../util/src/url.ts' is older than output '../util/tsconfig.tsbuildinfo'
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
11:33:58 AM - Project '../locale/tsconfig.json' is up to date because newest input '../locale/src/translationKey.ts' is older than output '../locale/tsconfig.tsbuildinfo'
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
11:33:58 AM - Project '../logger/tsconfig.json' is up to date because newest input '../logger/src/index.ts' is older than output '../logger/tsconfig.tsbuildinfo'
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
11:33:58 AM - Project '../util-crypto/tsconfig.json' is up to date because newest input '../util-crypto/src/types.ts' is older than output '../util-crypto/tsconfig.tsbuildinfo'
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
11:33:58 AM - Project '../types/tsconfig.json' is up to date because newest input '../types/src/types.test-d.ts' is older than output '../types/tsconfig.tsbuildinfo'
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
11:33:58 AM - Project '../common/tsconfig.json' is up to date because newest input '../common/src/error.ts' is older than output '../common/tsconfig.tsbuildinfo'
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
11:33:58 AM - Project '../api-route/tsconfig.json' is up to date because newest input '../api-route/src/tests/apiRoute.unit.test.ts' is older than output '../api-route/tsconfig.tsbuildinfo'
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
11:33:58 AM - Project '../redis-client/tsconfig.json' is up to date because newest input '../redis-client/src/index.ts' is older than output '../redis-client/tsconfig.tsbuildinfo'
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
11:33:58 AM - Project '../api/tsconfig.json' is up to date because newest input '../api/src/tests/apiHarness.ts' is older than output '../api/tsconfig.tsbuildinfo'
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
11:33:58 AM - Project '../user-access-policy/tsconfig.json' is up to date because newest input '../user-access-policy/src/transformRule.ts' is older than output '../user-access-policy/tsconfig.tsbuildinfo'
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
11:33:58 AM - Project '../types-database/tsconfig.json' is up to date because newest input '../types-database/src/index.ts' is older than output '../types-database/tsconfig.tsbuildinfo'
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
11:33:58 AM - Project 'tsconfig.json' is out of date because output file 'tsconfig.tsbuildinfo' does not exist
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
11:33:58 AM - Building project '/home/runner/work/captcha/captcha/packages/database/tsconfig.json'...
|
|
44
44
|
|
package/.turbo/turbo-build.log
CHANGED
|
@@ -46,9 +46,9 @@ dist/index.js 1.07 kB │ gzip: 0.38 kB
|
|
|
46
46
|
dist/databases/client.js 1.70 kB │ gzip: 0.75 kB
|
|
47
47
|
dist/mongooseOptions.js 3.58 kB │ gzip: 1.53 kB
|
|
48
48
|
dist/base/mongo.js 4.01 kB │ gzip: 1.32 kB
|
|
49
|
-
dist/databases/centralDbStreamer.js
|
|
50
|
-
dist/databases/captcha.js
|
|
49
|
+
dist/databases/centralDbStreamer.js 6.33 kB │ gzip: 1.51 kB
|
|
50
|
+
dist/databases/captcha.js 8.13 kB │ gzip: 2.36 kB
|
|
51
51
|
dist/redisCache.js 14.26 kB │ gzip: 4.44 kB
|
|
52
|
-
dist/databases/provider.js
|
|
52
|
+
dist/databases/provider.js 66.18 kB │ gzip: 13.13 kB
|
|
53
53
|
|
|
54
|
-
[32m✓ built in
|
|
54
|
+
[32m✓ built in 82ms[39m
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,48 @@
|
|
|
1
1
|
# @prosopo/database
|
|
2
2
|
|
|
3
|
+
## 4.2.1
|
|
4
|
+
### Patch Changes
|
|
5
|
+
|
|
6
|
+
- Updated dependencies [4570692]
|
|
7
|
+
- @prosopo/types@5.15.0
|
|
8
|
+
- @prosopo/types-database@5.10.0
|
|
9
|
+
- @prosopo/user-access-policy@3.14.17
|
|
10
|
+
- @prosopo/common@3.1.64
|
|
11
|
+
|
|
12
|
+
## 4.2.0
|
|
13
|
+
### Minor Changes
|
|
14
|
+
|
|
15
|
+
- b299a91: An audio challenge, offered only as an accessibility alternative, like reCAPTCHA's audio option. The user hears a short sequence of spoken digits and types them in.
|
|
16
|
+
|
|
17
|
+
It is off by default and needs two switches. Prosopo has to turn on the `captchaTypeFeatureFlags.audio` feature flag for the site, which the site owner cannot do, and the site owner has to set `audioAccessibilityEnabled`. When both are on, image, puzzle and icon-order challenges show a "Use audio instead" button. Pressing it swaps the visual challenge for the audio one. After a wrong answer the user stays on audio and gets a fresh clip.
|
|
18
|
+
|
|
19
|
+
Audio is never a captcha type that can be selected or routed to. A site's `captchaType`, traffic-filter categories, Restrict rules and the site-key CLI all reject it, and routing, PoW escalation and the severity tiers leave it out. The provider only serves audio against the visual session the user was already given, and only on a site with both switches on; any other audio request is refused before the session is used up, and a request without a session is always refused.
|
|
20
|
+
|
|
21
|
+
The spoken digits are synthesised by `@prosopo/audio-assets`, so there is no recorded set of clips to collect. The answer never leaves the provider, and a challenge can be submitted and verified only once, even under concurrent requests. `@prosopo/procaptcha-audio` is the widget. It and the "Use audio instead" button are built on the shared widget code in `@prosopo/procaptcha-common`, and the provider side is built on the shared interactive-captcha code that puzzle and icon-order use.
|
|
22
|
+
|
|
23
|
+
The demo playground has audio pages, and there is an end-to-end test for it.
|
|
24
|
+
- e13d7a8: New captcha type: `iconOrder`. The user is shown a frame of icons and a legend, and clicks the legend's icons in the order given.
|
|
25
|
+
|
|
26
|
+
Icon-order is off by default. Only Prosopo can switch it on for a site, with the `captchaTypeFeatureFlags.iconOrder` feature flag; the site owner cannot. A site without the flag is never served icon-order by any route, and the challenge endpoint refuses it. Once the flag is on, the owner can still keep icon-order out of the frictionless flow with `frictionlessTypes.iconOrder`, the same way as image and puzzle.
|
|
27
|
+
|
|
28
|
+
The answer never leaves the provider. Icon positions are stored on the challenge record, and the widget receives only the rendered frame and legend. Grading checks order as well as position, and each icon's hit radius scales with its size. Verifying a token is single-use under concurrent requests.
|
|
29
|
+
|
|
30
|
+
`@prosopo/icon-order-assets` draws the imagery, and `@prosopo/procaptcha-icon-order` is the widget. Its text is translated into every supported language.
|
|
31
|
+
|
|
32
|
+
Puzzle and icon-order now share their server code: challenge and solution handlers, the verify route, the submit and verify pipeline, and the database record methods. The widget code they have in common moves into `@prosopo/procaptcha-common`: the lazy mount wrapper, manager expiry and dispose, spent-session handling, behavioural data encryption and trusted click coordinates. Puzzle's behaviour is unchanged.
|
|
33
|
+
|
|
34
|
+
The demo playground has icon-order pages, and there is an end-to-end test for it.
|
|
35
|
+
|
|
36
|
+
### Patch Changes
|
|
37
|
+
|
|
38
|
+
- Updated dependencies [b299a91]
|
|
39
|
+
- Updated dependencies [e13d7a8]
|
|
40
|
+
- Updated dependencies [270395d]
|
|
41
|
+
- @prosopo/types@5.14.0
|
|
42
|
+
- @prosopo/types-database@5.9.0
|
|
43
|
+
- @prosopo/user-access-policy@3.14.16
|
|
44
|
+
- @prosopo/common@3.1.63
|
|
45
|
+
|
|
3
46
|
## 4.1.4
|
|
4
47
|
### Patch Changes
|
|
5
48
|
|
|
@@ -26,6 +26,16 @@ var CAPTCHA_TABLES = [
|
|
|
26
26
|
collectionName: "puzzlecaptcha",
|
|
27
27
|
modelName: "PuzzleCaptcha",
|
|
28
28
|
schema: _prosopo_types_database.StoredPuzzleCaptchaRecordSchema
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
collectionName: "audiocaptcha",
|
|
32
|
+
modelName: "AudioCaptcha",
|
|
33
|
+
schema: _prosopo_types_database.StoredAudioCaptchaRecordSchema
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
collectionName: "iconordercaptcha",
|
|
37
|
+
modelName: "IconOrderCaptcha",
|
|
38
|
+
schema: _prosopo_types_database.StoredIconOrderCaptchaRecordSchema
|
|
29
39
|
}
|
|
30
40
|
];
|
|
31
41
|
var CaptchaDatabase = class CaptchaDatabase extends require_mongo.MongoDatabase {
|
|
@@ -111,7 +121,7 @@ var CaptchaDatabase = class CaptchaDatabase extends require_mongo.MongoDatabase
|
|
|
111
121
|
providedIp: CaptchaDatabase.normaliseCompositeIp(doc.providedIp)
|
|
112
122
|
};
|
|
113
123
|
}
|
|
114
|
-
async saveCaptchas(sessionEvents, imageCaptchaEvents, powCaptchaEvents, puzzleCaptchaEvents = []) {
|
|
124
|
+
async saveCaptchas(sessionEvents, imageCaptchaEvents, powCaptchaEvents, puzzleCaptchaEvents = [], iconOrderCaptchaEvents = [], audioCaptchaEvents = []) {
|
|
115
125
|
await this.connect();
|
|
116
126
|
if (sessionEvents.length) {
|
|
117
127
|
const result = await this.tables.session.bulkWrite(sessionEvents.map((document) => {
|
|
@@ -173,35 +183,41 @@ var CaptchaDatabase = class CaptchaDatabase extends require_mongo.MongoDatabase
|
|
|
173
183
|
msg: "Mongo Saved PoW Events"
|
|
174
184
|
}));
|
|
175
185
|
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
const normalised = CaptchaDatabase.normaliseDocCompositeIps(safeDoc);
|
|
180
|
-
return { updateOne: {
|
|
181
|
-
filter: { challenge: normalised.challenge },
|
|
182
|
-
update: { $set: normalised },
|
|
183
|
-
upsert: true
|
|
184
|
-
} };
|
|
185
|
-
}));
|
|
186
|
-
logger.info(() => ({
|
|
187
|
-
data: {
|
|
188
|
-
upsertedCount: result.upsertedCount,
|
|
189
|
-
matchedCount: result.matchedCount,
|
|
190
|
-
modifiedCount: result.modifiedCount,
|
|
191
|
-
totalProcessed: puzzleCaptchaEvents.length
|
|
192
|
-
},
|
|
193
|
-
msg: "Mongo Saved Puzzle Events"
|
|
194
|
-
}));
|
|
195
|
-
}
|
|
186
|
+
await this.upsertChallengeRecords("puzzlecaptcha", puzzleCaptchaEvents, "Mongo Saved Puzzle Events");
|
|
187
|
+
await this.upsertChallengeRecords("audiocaptcha", audioCaptchaEvents, "Mongo Saved Audio Events");
|
|
188
|
+
await this.upsertChallengeRecords("iconordercaptcha", iconOrderCaptchaEvents, "Mongo Saved Icon Order Events");
|
|
196
189
|
await this.close();
|
|
197
190
|
}
|
|
191
|
+
async upsertChallengeRecords(table, records, msg) {
|
|
192
|
+
if (!records.length) return;
|
|
193
|
+
const result = await this.tables[table].bulkWrite(records.map((doc) => {
|
|
194
|
+
const { _id, ...safeDoc } = doc;
|
|
195
|
+
const normalised = CaptchaDatabase.normaliseDocCompositeIps(safeDoc);
|
|
196
|
+
return { updateOne: {
|
|
197
|
+
filter: { challenge: normalised.challenge },
|
|
198
|
+
update: { $set: normalised },
|
|
199
|
+
upsert: true
|
|
200
|
+
} };
|
|
201
|
+
}));
|
|
202
|
+
logger.info(() => ({
|
|
203
|
+
data: {
|
|
204
|
+
upsertedCount: result.upsertedCount,
|
|
205
|
+
matchedCount: result.matchedCount,
|
|
206
|
+
modifiedCount: result.modifiedCount,
|
|
207
|
+
totalProcessed: records.length
|
|
208
|
+
},
|
|
209
|
+
msg
|
|
210
|
+
}));
|
|
211
|
+
}
|
|
198
212
|
async getCaptchas(filter = {}, limit = 100) {
|
|
199
213
|
await this.connect();
|
|
200
214
|
try {
|
|
201
215
|
return {
|
|
202
216
|
userCommitmentRecords: await this.tables.commitment.find(filter).limit(limit).lean(),
|
|
203
217
|
powCaptchaRecords: await this.tables.powcaptcha.find(filter).limit(limit).lean(),
|
|
204
|
-
puzzleCaptchaRecords: await this.tables.puzzlecaptcha.find(filter).limit(limit).lean()
|
|
218
|
+
puzzleCaptchaRecords: await this.tables.puzzlecaptcha.find(filter).limit(limit).lean(),
|
|
219
|
+
audioCaptchaRecords: await this.tables.audiocaptcha.find(filter).limit(limit).lean(),
|
|
220
|
+
iconOrderCaptchaRecords: await this.tables.iconordercaptcha.find(filter).limit(limit).lean()
|
|
205
221
|
};
|
|
206
222
|
} catch (error) {
|
|
207
223
|
throw new _prosopo_common.ProsopoDBError("DATABASE.QUERY_ERROR", {
|
|
@@ -113,27 +113,45 @@ var CentralDbStreamer = class CentralDbStreamer {
|
|
|
113
113
|
* Fire-and-forget: errors are logged, never thrown.
|
|
114
114
|
*/
|
|
115
115
|
streamPuzzleRecord(record, markStored) {
|
|
116
|
+
this.streamChallengeRecord("puzzlecaptcha", "puzzle", record, markStored);
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Stream a partial puzzle update by fetching the full record first, then upserting.
|
|
120
|
+
*/
|
|
121
|
+
streamPuzzleUpdate(getFullRecord, markStored) {
|
|
122
|
+
this.streamChallengeUpdate(getFullRecord, (record) => this.streamPuzzleRecord(record, markStored), "puzzle");
|
|
123
|
+
}
|
|
124
|
+
streamAudioRecord(record, markStored) {
|
|
125
|
+
this.streamChallengeRecord("audiocaptcha", "audio", record, markStored);
|
|
126
|
+
}
|
|
127
|
+
streamAudioUpdate(getFullRecord, markStored) {
|
|
128
|
+
this.streamChallengeUpdate(getFullRecord, (record) => this.streamAudioRecord(record, markStored), "audio");
|
|
129
|
+
}
|
|
130
|
+
streamIconOrderRecord(record, markStored) {
|
|
131
|
+
this.streamChallengeRecord("iconordercaptcha", "icon-order", record, markStored);
|
|
132
|
+
}
|
|
133
|
+
streamIconOrderUpdate(getFullRecord, markStored) {
|
|
134
|
+
this.streamChallengeUpdate(getFullRecord, (record) => this.streamIconOrderRecord(record, markStored), "icon-order");
|
|
135
|
+
}
|
|
136
|
+
streamChallengeRecord(table, label, record, markStored) {
|
|
116
137
|
const timestamp = this.getRecordTimestamp(record);
|
|
117
138
|
this.ensureConnected().then(() => {
|
|
118
139
|
const { _id, ...safeDoc } = record;
|
|
119
|
-
return this.db.tables.
|
|
140
|
+
return this.db.tables[table].updateOne({ challenge: safeDoc.challenge }, { $set: safeDoc }, { upsert: true });
|
|
120
141
|
}).then(() => markStored?.(timestamp)).catch((err) => {
|
|
121
142
|
this.logger.error(() => ({
|
|
122
143
|
err,
|
|
123
|
-
msg:
|
|
144
|
+
msg: `Failed to stream ${label} record to central DB`
|
|
124
145
|
}));
|
|
125
146
|
});
|
|
126
147
|
}
|
|
127
|
-
|
|
128
|
-
* Stream a partial puzzle update by fetching the full record first, then upserting.
|
|
129
|
-
*/
|
|
130
|
-
streamPuzzleUpdate(getFullRecord, markStored) {
|
|
148
|
+
streamChallengeUpdate(getFullRecord, stream, label) {
|
|
131
149
|
getFullRecord().then((record) => {
|
|
132
|
-
if (record)
|
|
150
|
+
if (record) stream(record);
|
|
133
151
|
}).catch((err) => {
|
|
134
152
|
this.logger.error(() => ({
|
|
135
153
|
err,
|
|
136
|
-
msg:
|
|
154
|
+
msg: `Failed to fetch ${label} record for central DB streaming`
|
|
137
155
|
}));
|
|
138
156
|
});
|
|
139
157
|
}
|