@prosopo/database 3.15.19 → 3.15.21

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.
Files changed (38) hide show
  1. package/.turbo/turbo-build$colon$cjs.log +20 -17
  2. package/.turbo/turbo-build$colon$tsc.log +16 -16
  3. package/.turbo/turbo-build.log +21 -18
  4. package/CHANGELOG.md +24 -0
  5. package/dist/_virtual/_rolldown/runtime.js +3 -0
  6. package/dist/base/index.js +1 -4
  7. package/dist/base/mongo.js +133 -137
  8. package/dist/base/mongoMemory.js +21 -21
  9. package/dist/cjs/_virtual/_rolldown/runtime.cjs +23 -0
  10. package/dist/cjs/base/index.cjs +4 -6
  11. package/dist/cjs/base/mongo.cjs +140 -143
  12. package/dist/cjs/base/mongoMemory.cjs +22 -22
  13. package/dist/cjs/databases/captcha.cjs +220 -256
  14. package/dist/cjs/databases/centralDbStreamer.cjs +148 -170
  15. package/dist/cjs/databases/client.cjs +46 -62
  16. package/dist/cjs/databases/index.cjs +19 -19
  17. package/dist/cjs/databases/provider.cjs +1827 -2251
  18. package/dist/cjs/index.cjs +25 -23
  19. package/dist/cjs/mongooseOptions.cjs +59 -58
  20. package/dist/cjs/redisCache.cjs +432 -474
  21. package/dist/databases/captcha.d.ts.map +1 -1
  22. package/dist/databases/captcha.js +217 -253
  23. package/dist/databases/captcha.js.map +1 -1
  24. package/dist/databases/centralDbStreamer.js +147 -169
  25. package/dist/databases/client.js +44 -60
  26. package/dist/databases/index.js +10 -14
  27. package/dist/databases/provider.d.ts +4 -4
  28. package/dist/databases/provider.d.ts.map +1 -1
  29. package/dist/databases/provider.js +1823 -2247
  30. package/dist/databases/provider.js.map +1 -1
  31. package/dist/index.js +9 -18
  32. package/dist/mongooseOptions.js +59 -59
  33. package/dist/redisCache.js +432 -474
  34. package/dist/tests/integration/saveCaptchasSessionUpsert.integration.test.d.ts +2 -0
  35. package/dist/tests/integration/saveCaptchasSessionUpsert.integration.test.d.ts.map +1 -0
  36. package/dist/tests/integration/saveCaptchasSessionUpsert.integration.test.js +64 -0
  37. package/dist/tests/integration/saveCaptchasSessionUpsert.integration.test.js.map +1 -0
  38. package/package.json +14 -14
@@ -1,258 +1,222 @@
1
- "use strict";
2
- Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const common = require("@prosopo/common");
4
- const logger$1 = require("@prosopo/logger");
5
- const typesDatabase = require("@prosopo/types-database");
6
- const bson = require("bson");
7
- require("../base/index.cjs");
8
- const mongo = require("../base/mongo.cjs");
9
- const isBsonLong = (value) => typeof value === "object" && value !== null && "_bsontype" in value && value._bsontype === "Long";
10
- const logger = logger$1.getLogger("info", "database:captcha");
11
- var TableNames = /* @__PURE__ */ ((TableNames2) => {
12
- TableNames2["frictionlessToken"] = "frictionlessToken";
13
- TableNames2["session"] = "session";
14
- TableNames2["commitment"] = "commitment";
15
- TableNames2["powcaptcha"] = "powcaptcha";
16
- TableNames2["puzzlecaptcha"] = "puzzlecaptcha";
17
- return TableNames2;
18
- })(TableNames || {});
19
- const CAPTCHA_TABLES = [
20
- {
21
- collectionName: "session",
22
- modelName: "Session",
23
- schema: typesDatabase.StoredSessionRecordSchema
24
- },
25
- {
26
- collectionName: "powcaptcha",
27
- modelName: "PowCaptcha",
28
- schema: typesDatabase.StoredPoWCaptchaRecordSchema
29
- },
30
- {
31
- collectionName: "commitment",
32
- modelName: "UserCommitment",
33
- schema: typesDatabase.StoredUserCommitmentRecordSchema
34
- },
35
- {
36
- collectionName: "puzzlecaptcha",
37
- modelName: "PuzzleCaptcha",
38
- schema: typesDatabase.StoredPuzzleCaptchaRecordSchema
39
- }
1
+ const require_mongo = require("../base/mongo.cjs");
2
+ let _prosopo_common = require("@prosopo/common");
3
+ let _prosopo_logger = require("@prosopo/logger");
4
+ let _prosopo_types_database = require("@prosopo/types-database");
5
+ let bson = require("bson");
6
+ //#region src/databases/captcha.ts
7
+ var isBsonLong = (value) => typeof value === "object" && value !== null && "_bsontype" in value && value._bsontype === "Long";
8
+ var logger = (0, _prosopo_logger.getLogger)("info", "database:captcha");
9
+ var CAPTCHA_TABLES = [
10
+ {
11
+ collectionName: "session",
12
+ modelName: "Session",
13
+ schema: _prosopo_types_database.StoredSessionRecordSchema
14
+ },
15
+ {
16
+ collectionName: "powcaptcha",
17
+ modelName: "PowCaptcha",
18
+ schema: _prosopo_types_database.StoredPoWCaptchaRecordSchema
19
+ },
20
+ {
21
+ collectionName: "commitment",
22
+ modelName: "UserCommitment",
23
+ schema: _prosopo_types_database.StoredUserCommitmentRecordSchema
24
+ },
25
+ {
26
+ collectionName: "puzzlecaptcha",
27
+ modelName: "PuzzleCaptcha",
28
+ schema: _prosopo_types_database.StoredPuzzleCaptchaRecordSchema
29
+ }
40
30
  ];
41
- class CaptchaDatabase extends mongo.MongoDatabase {
42
- constructor(url, dbname, authSource, logger2) {
43
- super(url, dbname, authSource, logger2);
44
- this.indexesEnsured = false;
45
- this.tables = {};
46
- }
47
- async connect() {
48
- await super.connect();
49
- CAPTCHA_TABLES.map(({ collectionName, modelName, schema }) => {
50
- if (this.connection) {
51
- this.tables[collectionName] = this.connection.model(modelName, schema);
52
- }
53
- });
54
- }
55
- getTables() {
56
- if (!this.tables) {
57
- throw new common.ProsopoDBError("DATABASE.TABLES_UNDEFINED", {
58
- context: { failedFuncName: this.getTables.name },
59
- logger: this.logger
60
- });
61
- }
62
- return this.tables;
63
- }
64
- async ensureIndexes() {
65
- const indexPromises = [];
66
- if (!this.indexesEnsured) {
67
- CAPTCHA_TABLES.map(({ collectionName }) => {
68
- indexPromises.push(
69
- new Promise((resolve) => {
70
- if (this.connected) {
71
- this.tables[collectionName].collection.dropIndexes().then(() => {
72
- this.tables[collectionName].ensureIndexes().then(() => {
73
- resolve();
74
- }).catch((err) => {
75
- this.logger.warn(() => ({
76
- err,
77
- msg: `Error creating indexes for collection ${collectionName}`
78
- }));
79
- resolve();
80
- });
81
- });
82
- } else {
83
- this.logger.info(() => ({
84
- msg: `Skipping index creation for collection ${collectionName} as not connected`
85
- }));
86
- resolve();
87
- }
88
- })
89
- );
90
- });
91
- }
92
- await Promise.all(indexPromises);
93
- this.indexesEnsured = true;
94
- }
95
- /**
96
- * Convert a BSON `Long` (which Mongoose's Decimal128 caster rejects)
97
- * into a Decimal128 with the *unsigned* numeric value — so a Long
98
- * representing an IPv6 lower half with bit 63 set (signed Long shows
99
- * as a negative integer) converts to a positive Decimal128 that
100
- * matches what `bigint→string→Decimal128` would have produced if the
101
- * schema setter had run on the original write.
102
- *
103
- * Production background: pipeline-form updates (`updateOne(filter,
104
- * [{ $set: ... }])`) bypass Mongoose schema casting, so a `bigint`
105
- * passed for an IP half is serialised by the driver as BSON Int64
106
- * (Long) instead of going through the `Decimal128` setter. The
107
- * central-streaming sweep below reads those docs via `.lean()` and
108
- * tries to replay them with `bulkWrite`, but `bulkWrite` also skips
109
- * setters — so the cast fires raw and throws. Normalising the lean
110
- * doc here is the only place that's guaranteed to run before the
111
- * bulkWrite sees it.
112
- */
113
- static normaliseCompositeIp(ip) {
114
- if (!ip) return ip;
115
- const normaliseHalf = (v) => {
116
- if (isBsonLong(v)) {
117
- const lng = v;
118
- return bson.Decimal128.fromString(
119
- bson.Long.fromBits(lng.low, lng.high, true).toString()
120
- );
121
- }
122
- return v;
123
- };
124
- return {
125
- ...ip,
126
- lower: normaliseHalf(ip.lower),
127
- upper: normaliseHalf(ip.upper)
128
- };
129
- }
130
- static normaliseDocCompositeIps(doc) {
131
- return {
132
- ...doc,
133
- ipAddress: CaptchaDatabase.normaliseCompositeIp(doc.ipAddress),
134
- providedIp: CaptchaDatabase.normaliseCompositeIp(doc.providedIp)
135
- };
136
- }
137
- async saveCaptchas(sessionEvents, imageCaptchaEvents, powCaptchaEvents, puzzleCaptchaEvents = []) {
138
- await this.connect();
139
- if (sessionEvents.length) {
140
- const result = await this.tables.session.bulkWrite(
141
- sessionEvents.map((document) => {
142
- const { _id, ...safeDoc } = document;
143
- return {
144
- insertOne: {
145
- document: CaptchaDatabase.normaliseDocCompositeIps(safeDoc)
146
- }
147
- };
148
- })
149
- );
150
- logger.info(() => ({
151
- data: {
152
- insertedCount: result.insertedCount,
153
- totalProcessed: sessionEvents.length
154
- },
155
- msg: "Mongo Saved Session Events"
156
- }));
157
- }
158
- if (imageCaptchaEvents.length) {
159
- const result = await this.tables.commitment.bulkWrite(
160
- imageCaptchaEvents.map((doc) => {
161
- const { _id, ...safeDoc } = doc;
162
- const normalised = CaptchaDatabase.normaliseDocCompositeIps(safeDoc);
163
- return {
164
- updateOne: {
165
- filter: { id: normalised.id },
166
- update: { $set: normalised },
167
- upsert: true
168
- }
169
- };
170
- })
171
- );
172
- logger.info(() => ({
173
- data: {
174
- upsertedCount: result.upsertedCount,
175
- matchedCount: result.matchedCount,
176
- modifiedCount: result.modifiedCount,
177
- totalProcessed: imageCaptchaEvents.length
178
- },
179
- msg: "Mongo Saved Image Events"
180
- }));
181
- }
182
- if (powCaptchaEvents.length) {
183
- const result = await this.tables.powcaptcha.bulkWrite(
184
- powCaptchaEvents.map((doc) => {
185
- const { _id, ...safeDoc } = doc;
186
- const normalised = CaptchaDatabase.normaliseDocCompositeIps(safeDoc);
187
- return {
188
- updateOne: {
189
- filter: { challenge: normalised.challenge },
190
- update: { $set: normalised },
191
- upsert: true
192
- }
193
- };
194
- })
195
- );
196
- logger.info(() => ({
197
- data: {
198
- upsertedCount: result.upsertedCount,
199
- matchedCount: result.matchedCount,
200
- modifiedCount: result.modifiedCount,
201
- totalProcessed: powCaptchaEvents.length
202
- },
203
- msg: "Mongo Saved PoW Events"
204
- }));
205
- }
206
- if (puzzleCaptchaEvents.length) {
207
- const result = await this.tables.puzzlecaptcha.bulkWrite(
208
- puzzleCaptchaEvents.map((doc) => {
209
- const { _id, ...safeDoc } = doc;
210
- const normalised = CaptchaDatabase.normaliseDocCompositeIps(safeDoc);
211
- return {
212
- updateOne: {
213
- filter: { challenge: normalised.challenge },
214
- update: { $set: normalised },
215
- upsert: true
216
- }
217
- };
218
- })
219
- );
220
- logger.info(() => ({
221
- data: {
222
- upsertedCount: result.upsertedCount,
223
- matchedCount: result.matchedCount,
224
- modifiedCount: result.modifiedCount,
225
- totalProcessed: puzzleCaptchaEvents.length
226
- },
227
- msg: "Mongo Saved Puzzle Events"
228
- }));
229
- }
230
- await this.close();
231
- }
232
- async getCaptchas(filter = {}, limit = 100) {
233
- await this.connect();
234
- try {
235
- const commitmentResults = await this.tables.commitment.find(filter).limit(limit).lean();
236
- const powCaptchaResults = await this.tables.powcaptcha.find(filter).limit(limit).lean();
237
- const puzzleCaptchaResults = await this.tables.puzzlecaptcha.find(filter).limit(limit).lean();
238
- return {
239
- userCommitmentRecords: commitmentResults,
240
- powCaptchaRecords: powCaptchaResults,
241
- puzzleCaptchaRecords: puzzleCaptchaResults
242
- };
243
- } catch (error) {
244
- throw new common.ProsopoDBError("DATABASE.QUERY_ERROR", {
245
- context: {
246
- error,
247
- filter,
248
- limit,
249
- failedFuncName: this.getCaptchas.name
250
- },
251
- logger: this.logger
252
- });
253
- } finally {
254
- await this.close();
255
- }
256
- }
257
- }
31
+ var CaptchaDatabase = class CaptchaDatabase extends require_mongo.MongoDatabase {
32
+ constructor(url, dbname, authSource, logger) {
33
+ super(url, dbname, authSource, logger);
34
+ this.indexesEnsured = false;
35
+ this.tables = {};
36
+ }
37
+ async connect() {
38
+ await super.connect();
39
+ CAPTCHA_TABLES.map(({ collectionName, modelName, schema }) => {
40
+ if (this.connection) this.tables[collectionName] = this.connection.model(modelName, schema);
41
+ });
42
+ }
43
+ getTables() {
44
+ if (!this.tables) throw new _prosopo_common.ProsopoDBError("DATABASE.TABLES_UNDEFINED", {
45
+ context: { failedFuncName: this.getTables.name },
46
+ logger: this.logger
47
+ });
48
+ return this.tables;
49
+ }
50
+ async ensureIndexes() {
51
+ const indexPromises = [];
52
+ if (!this.indexesEnsured) CAPTCHA_TABLES.map(({ collectionName }) => {
53
+ indexPromises.push(new Promise((resolve) => {
54
+ if (this.connected) this.tables[collectionName].collection.dropIndexes().then(() => {
55
+ this.tables[collectionName].ensureIndexes().then(() => {
56
+ resolve();
57
+ }).catch((err) => {
58
+ this.logger.warn(() => ({
59
+ err,
60
+ msg: `Error creating indexes for collection ${collectionName}`
61
+ }));
62
+ resolve();
63
+ });
64
+ });
65
+ else {
66
+ this.logger.info(() => ({ msg: `Skipping index creation for collection ${collectionName} as not connected` }));
67
+ resolve();
68
+ }
69
+ }));
70
+ });
71
+ await Promise.all(indexPromises);
72
+ this.indexesEnsured = true;
73
+ }
74
+ /**
75
+ * Convert a BSON `Long` (which Mongoose's Decimal128 caster rejects)
76
+ * into a Decimal128 with the *unsigned* numeric value — so a Long
77
+ * representing an IPv6 lower half with bit 63 set (signed Long shows
78
+ * as a negative integer) converts to a positive Decimal128 that
79
+ * matches what `bigint→string→Decimal128` would have produced if the
80
+ * schema setter had run on the original write.
81
+ *
82
+ * Production background: pipeline-form updates (`updateOne(filter,
83
+ * [{ $set: ... }])`) bypass Mongoose schema casting, so a `bigint`
84
+ * passed for an IP half is serialised by the driver as BSON Int64
85
+ * (Long) instead of going through the `Decimal128` setter. The
86
+ * central-streaming sweep below reads those docs via `.lean()` and
87
+ * tries to replay them with `bulkWrite`, but `bulkWrite` also skips
88
+ * setters — so the cast fires raw and throws. Normalising the lean
89
+ * doc here is the only place that's guaranteed to run before the
90
+ * bulkWrite sees it.
91
+ */
92
+ static normaliseCompositeIp(ip) {
93
+ if (!ip) return ip;
94
+ const normaliseHalf = (v) => {
95
+ if (isBsonLong(v)) {
96
+ const lng = v;
97
+ return bson.Decimal128.fromString(bson.Long.fromBits(lng.low, lng.high, true).toString());
98
+ }
99
+ return v;
100
+ };
101
+ return {
102
+ ...ip,
103
+ lower: normaliseHalf(ip.lower),
104
+ upper: normaliseHalf(ip.upper)
105
+ };
106
+ }
107
+ static normaliseDocCompositeIps(doc) {
108
+ return {
109
+ ...doc,
110
+ ipAddress: CaptchaDatabase.normaliseCompositeIp(doc.ipAddress),
111
+ providedIp: CaptchaDatabase.normaliseCompositeIp(doc.providedIp)
112
+ };
113
+ }
114
+ async saveCaptchas(sessionEvents, imageCaptchaEvents, powCaptchaEvents, puzzleCaptchaEvents = []) {
115
+ await this.connect();
116
+ if (sessionEvents.length) {
117
+ const result = await this.tables.session.bulkWrite(sessionEvents.map((document) => {
118
+ const { _id, ...safeDoc } = document;
119
+ const normalised = CaptchaDatabase.normaliseDocCompositeIps(safeDoc);
120
+ return { updateOne: {
121
+ filter: { sessionId: normalised.sessionId },
122
+ update: { $set: normalised },
123
+ upsert: true
124
+ } };
125
+ }));
126
+ logger.info(() => ({
127
+ data: {
128
+ upsertedCount: result.upsertedCount,
129
+ matchedCount: result.matchedCount,
130
+ modifiedCount: result.modifiedCount,
131
+ totalProcessed: sessionEvents.length
132
+ },
133
+ msg: "Mongo Saved Session Events"
134
+ }));
135
+ }
136
+ if (imageCaptchaEvents.length) {
137
+ const result = await this.tables.commitment.bulkWrite(imageCaptchaEvents.map((doc) => {
138
+ const { _id, ...safeDoc } = doc;
139
+ const normalised = CaptchaDatabase.normaliseDocCompositeIps(safeDoc);
140
+ return { updateOne: {
141
+ filter: { id: normalised.id },
142
+ update: { $set: normalised },
143
+ upsert: true
144
+ } };
145
+ }));
146
+ logger.info(() => ({
147
+ data: {
148
+ upsertedCount: result.upsertedCount,
149
+ matchedCount: result.matchedCount,
150
+ modifiedCount: result.modifiedCount,
151
+ totalProcessed: imageCaptchaEvents.length
152
+ },
153
+ msg: "Mongo Saved Image Events"
154
+ }));
155
+ }
156
+ if (powCaptchaEvents.length) {
157
+ const result = await this.tables.powcaptcha.bulkWrite(powCaptchaEvents.map((doc) => {
158
+ const { _id, ...safeDoc } = doc;
159
+ const normalised = CaptchaDatabase.normaliseDocCompositeIps(safeDoc);
160
+ return { updateOne: {
161
+ filter: { challenge: normalised.challenge },
162
+ update: { $set: normalised },
163
+ upsert: true
164
+ } };
165
+ }));
166
+ logger.info(() => ({
167
+ data: {
168
+ upsertedCount: result.upsertedCount,
169
+ matchedCount: result.matchedCount,
170
+ modifiedCount: result.modifiedCount,
171
+ totalProcessed: powCaptchaEvents.length
172
+ },
173
+ msg: "Mongo Saved PoW Events"
174
+ }));
175
+ }
176
+ if (puzzleCaptchaEvents.length) {
177
+ const result = await this.tables.puzzlecaptcha.bulkWrite(puzzleCaptchaEvents.map((doc) => {
178
+ const { _id, ...safeDoc } = doc;
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
+ }
196
+ await this.close();
197
+ }
198
+ async getCaptchas(filter = {}, limit = 100) {
199
+ await this.connect();
200
+ try {
201
+ return {
202
+ userCommitmentRecords: await this.tables.commitment.find(filter).limit(limit).lean(),
203
+ powCaptchaRecords: await this.tables.powcaptcha.find(filter).limit(limit).lean(),
204
+ puzzleCaptchaRecords: await this.tables.puzzlecaptcha.find(filter).limit(limit).lean()
205
+ };
206
+ } catch (error) {
207
+ throw new _prosopo_common.ProsopoDBError("DATABASE.QUERY_ERROR", {
208
+ context: {
209
+ error,
210
+ filter,
211
+ limit,
212
+ failedFuncName: this.getCaptchas.name
213
+ },
214
+ logger: this.logger
215
+ });
216
+ } finally {
217
+ await this.close();
218
+ }
219
+ }
220
+ };
221
+ //#endregion
258
222
  exports.CaptchaDatabase = CaptchaDatabase;