@vandenberghinc/volt 1.2.2 → 1.2.4

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 (26) hide show
  1. package/backend/dist/cjs/backend/src/database/collection.d.ts +17 -10
  2. package/backend/dist/cjs/backend/src/database/collection.js +289 -126
  3. package/backend/dist/cjs/backend/src/payments/stripe/products.js +21 -6
  4. package/backend/dist/cjs/backend/src/stream.d.ts +58 -6
  5. package/backend/dist/cjs/backend/src/stream.js +91 -12
  6. package/backend/dist/cjs/backend/src/users.d.ts +11 -4
  7. package/backend/dist/cjs/backend/src/users.js +93 -26
  8. package/backend/dist/esm/backend/src/database/collection.d.ts +17 -10
  9. package/backend/dist/esm/backend/src/database/collection.js +311 -152
  10. package/backend/dist/esm/backend/src/database/filters/strict_update_filter_test.js +10 -0
  11. package/backend/dist/esm/backend/src/payments/stripe/products.js +24 -3
  12. package/backend/dist/esm/backend/src/stream.d.ts +58 -6
  13. package/backend/dist/esm/backend/src/stream.js +96 -12
  14. package/backend/dist/esm/backend/src/users.d.ts +11 -4
  15. package/backend/dist/esm/backend/src/users.js +95 -27
  16. package/frontend/dist/backend/src/database/collection.d.ts +17 -10
  17. package/frontend/dist/backend/src/database/collection.js +311 -152
  18. package/frontend/dist/backend/src/payments/stripe/products.js +24 -3
  19. package/frontend/dist/backend/src/stream.d.ts +58 -6
  20. package/frontend/dist/backend/src/stream.js +96 -12
  21. package/frontend/dist/backend/src/users.d.ts +11 -4
  22. package/frontend/dist/backend/src/users.js +95 -27
  23. package/frontend/dist/frontend/src/modules/user.js +3 -2
  24. package/frontend/dist/frontend/src/ui/table.d.ts +2 -2
  25. package/frontend/dist/frontend/src/ui/table.js +3 -6
  26. package/package.json +2 -2
@@ -1237,31 +1237,110 @@ class Stream {
1237
1237
  remove_headers(...names) {
1238
1238
  return this.remove_header(...names);
1239
1239
  }
1240
- // Set a cookie.
1241
1240
  /**
1242
- * Set a cookie that will be sent with the response.
1241
+ * Set a cookie to be sent with the response.
1242
+ *
1243
+ * Accepts either:
1244
+ * 1) a pre-built cookie header string (used as-is, no validation), or
1245
+ * 2) a structured object describing the cookie, from which a standards-compliant
1246
+ * cookie string will be generated.
1247
+ *
1248
+ * If a cookie with the same name already exists in the pending response list,
1249
+ * it will be replaced.
1250
+ *
1251
+ * @warning Cookies are only included in the response when using `send()`,
1252
+ * `success()` or `error()`.
1243
1253
  *
1244
- * @warning Will only be added to the response when the user uses `send()`, `success()` or `error()`.
1245
- * @param cookie The cookie string.
1246
1254
  * @example
1247
1255
  * ```ts
1248
- * stream.set_cookie("MyCookie=Hello World;");
1256
+ * stream.set_cookie("sid=abc123; Path=/; SameSite=Lax; Secure; HttpOnly");
1257
+ *
1258
+ * stream.set_cookie({
1259
+ * name: "sid",
1260
+ * value: session_id,
1261
+ * http_only: true,
1262
+ * secure: true,
1263
+ * same_site: "Lax",
1264
+ * path: "/",
1265
+ * max_age: 60 * 60 * 24 * 14,
1266
+ * });
1249
1267
  * ```
1250
- * @docs
1251
1268
  */
1252
1269
  set_cookie(cookie) {
1253
- cookie = cookie.trim();
1254
- const name_end = cookie.indexOf("=");
1270
+ if (typeof cookie === "string") {
1271
+ const cookie_str2 = cookie.trim();
1272
+ const name_end2 = cookie_str2.indexOf("=");
1273
+ if (name_end2 !== -1) {
1274
+ const name2 = cookie_str2.substring(0, name_end2);
1275
+ for (let i = 0; i < this.res_cookies.length; i++) {
1276
+ if (this.res_cookies[i].startsWith(name2)) {
1277
+ this.res_cookies[i] = cookie_str2;
1278
+ return this;
1279
+ }
1280
+ }
1281
+ }
1282
+ this.res_cookies.push(cookie_str2);
1283
+ return this;
1284
+ }
1285
+ const { name, value, path = "/", domain, max_age, expires, secure, http_only, same_site, prefix, extra } = cookie;
1286
+ if (!name || typeof name !== "string") {
1287
+ throw new Error("set_cookie: cookie.name must be a non-empty string");
1288
+ }
1289
+ const full_name = `${prefix ?? ""}${name}`;
1290
+ if (prefix === "__Host-") {
1291
+ if (domain) {
1292
+ throw new Error("__Host- cookies must not include a domain attribute");
1293
+ }
1294
+ if (path !== "/") {
1295
+ throw new Error("__Host- cookies must have path='/'");
1296
+ }
1297
+ if (!secure) {
1298
+ throw new Error("__Host- cookies require secure=true");
1299
+ }
1300
+ }
1301
+ if (prefix === "__Secure-" && !secure) {
1302
+ throw new Error("__Secure- cookies require secure=true");
1303
+ }
1304
+ const encoded_value = value === null || typeof value === "undefined" ? "" : encodeURIComponent(String(value));
1305
+ const parts = [];
1306
+ parts.push(`${full_name}=${encoded_value}`);
1307
+ if (path)
1308
+ parts.push(`Path=${path}`);
1309
+ if (domain)
1310
+ parts.push(`Domain=${domain}`);
1311
+ if (typeof max_age === "number" && Number.isFinite(max_age)) {
1312
+ parts.push(`Max-Age=${Math.trunc(max_age)}`);
1313
+ }
1314
+ if (expires) {
1315
+ const exp = expires instanceof Date ? expires.toUTCString() : String(expires).trim();
1316
+ if (exp)
1317
+ parts.push(`Expires=${exp}`);
1318
+ }
1319
+ if (secure)
1320
+ parts.push("Secure");
1321
+ if (http_only)
1322
+ parts.push("HttpOnly");
1323
+ if (same_site)
1324
+ parts.push(`SameSite=${same_site}`);
1325
+ if (extra && Array.isArray(extra)) {
1326
+ for (const attr of extra) {
1327
+ const trimmed = String(attr).trim();
1328
+ if (trimmed)
1329
+ parts.push(trimmed);
1330
+ }
1331
+ }
1332
+ const cookie_str = parts.join("; ");
1333
+ const name_end = cookie_str.indexOf("=");
1255
1334
  if (name_end !== -1) {
1256
- const name = cookie.substr(0, name_end);
1335
+ const existing_name = cookie_str.substring(0, name_end);
1257
1336
  for (let i = 0; i < this.res_cookies.length; i++) {
1258
- if (this.res_cookies[i].startsWith(name)) {
1259
- this.res_cookies[i] = cookie;
1337
+ if (this.res_cookies[i].startsWith(existing_name)) {
1338
+ this.res_cookies[i] = cookie_str;
1260
1339
  return this;
1261
1340
  }
1262
1341
  }
1263
1342
  }
1264
- this.res_cookies.push(cookie);
1343
+ this.res_cookies.push(cookie_str);
1265
1344
  return this;
1266
1345
  }
1267
1346
  // Set cookies.
@@ -259,24 +259,31 @@ export declare class Users {
259
259
  }): Promise<void>;
260
260
  /**
261
261
  * Create the auth token cookie on the response.
262
+ * `T` is treated as a real authentication credential.
263
+ *
262
264
  * @param stream The request stream.
263
265
  * @param token The token string or Token object.
264
266
  */
265
267
  _create_token_cookie(stream: Stream, token: string | User.Token): void;
266
268
  /**
267
- * Create user cookies (id and activation flag).
269
+ * Create user cookies (ID and activation flag).
270
+ * These are user-state cookies, NOT auth credentials.
271
+ *
268
272
  * @param stream The request stream.
269
273
  * @param uid The user ID, or invalid to clear.
270
274
  */
271
- _create_user_cookie(stream: Stream, uid: string): Promise<void>;
275
+ _create_user_cookie(stream: Stream, uid: string | null): Promise<void>;
272
276
  /**
273
- * Create non-HTTP-only cookies with detailed user info for the frontend.
277
+ * Create non-HttpOnly cookies with detailed user info for frontend usage.
278
+ * These are UI convenience cookies only.
279
+ *
274
280
  * @param stream The request stream.
275
281
  * @param uid The user ID.
276
282
  */
277
283
  _create_detailed_user_cookie(stream: Stream, uid: string): Promise<void>;
278
284
  /**
279
- * Clear all default auth/user cookies.
285
+ * Clear all default auth and user-related cookies.
286
+ *
280
287
  * @param stream The request stream.
281
288
  */
282
289
  _reset_cookies(stream: Stream): void;
@@ -428,60 +428,127 @@ class Users {
428
428
  // ---------------------------------------------------------
429
429
  /**
430
430
  * Create the auth token cookie on the response.
431
+ * `T` is treated as a real authentication credential.
432
+ *
431
433
  * @param stream The request stream.
432
434
  * @param token The token string or Token object.
433
435
  */
434
436
  _create_token_cookie(stream, token) {
435
437
  stream.set_header("Cache-Control", "max-age=0, no-cache, no-store, must-revalidate, proxy-revalidate");
436
438
  stream.set_header("Access-Control-Allow-Credentials", "true");
437
- if (typeof token === "object") {
438
- token = token.token;
439
- }
439
+ const token_value = typeof token === "object" ? token.token : token;
440
440
  const max_age = this.token_expiration;
441
- const expires = new Date(Date.now() + max_age * 1e3).toUTCString();
442
- stream.set_cookie(`T=${encodeURIComponent(token ?? "")}; Max-Age=${max_age}; Path=/; Expires=${expires}; SameSite=Strict; Secure; HttpOnly;`);
441
+ stream.set_cookie({
442
+ name: "T",
443
+ value: token_value,
444
+ path: "/",
445
+ max_age,
446
+ secure: true,
447
+ http_only: true,
448
+ same_site: "Lax"
449
+ // REQUIRED for Stripe success/cancel redirects
450
+ });
443
451
  }
444
452
  /**
445
- * Create user cookies (id and activation flag).
453
+ * Create user cookies (ID and activation flag).
454
+ * These are user-state cookies, NOT auth credentials.
455
+ *
446
456
  * @param stream The request stream.
447
457
  * @param uid The user ID, or invalid to clear.
448
458
  */
449
459
  async _create_user_cookie(stream, uid) {
450
- if (typeof uid === "string") {
451
- stream.set_cookie(`UserID=${encodeURIComponent(uid ?? "")}; Path=/; SameSite=Strict; Secure;`);
460
+ if (typeof uid === "string" && uid.length > 0) {
461
+ stream.set_cookie({
462
+ name: "UserID",
463
+ value: uid,
464
+ path: "/",
465
+ secure: true,
466
+ same_site: "Lax"
467
+ });
452
468
  const is_activated = this.enable_account_activation ? await this.is_activated(uid) : true;
453
- stream.set_cookie(`UserActivated=${is_activated}; Path=/; SameSite=Strict; Secure;`);
469
+ stream.set_cookie({
470
+ name: "UserActivated",
471
+ value: is_activated ? "1" : "0",
472
+ path: "/",
473
+ secure: true,
474
+ same_site: "Lax"
475
+ });
454
476
  } else {
455
- stream.set_cookie(`UserID=-1; Path=/; SameSite=Strict; Secure;`);
456
- const is_activated = this.enable_account_activation ? false : true;
457
- stream.set_cookie(`UserActivated=${is_activated}; Path=/; SameSite=Strict; Secure;`);
477
+ stream.set_cookie({
478
+ name: "UserID",
479
+ value: "",
480
+ path: "/",
481
+ max_age: 0,
482
+ secure: true,
483
+ same_site: "Lax"
484
+ });
485
+ stream.set_cookie({
486
+ name: "UserActivated",
487
+ value: "0",
488
+ path: "/",
489
+ max_age: 0,
490
+ secure: true,
491
+ same_site: "Lax"
492
+ });
458
493
  }
459
494
  }
460
495
  /**
461
- * Create non-HTTP-only cookies with detailed user info for the frontend.
496
+ * Create non-HttpOnly cookies with detailed user info for frontend usage.
497
+ * These are UI convenience cookies only.
498
+ *
462
499
  * @param stream The request stream.
463
500
  * @param uid The user ID.
464
501
  */
465
502
  async _create_detailed_user_cookie(stream, uid) {
466
503
  const user = await this.get(uid);
467
- stream.set_cookie(`UserName=${encodeURIComponent(user.username ?? "")}; Path=/; SameSite=Strict; Secure;`);
468
- stream.set_cookie(`UserFirstName=${encodeURIComponent(user.first_name ?? "")}; Path=/; SameSite=Strict; Secure;`);
469
- stream.set_cookie(`UserLastName=${encodeURIComponent(user.last_name ?? "")}; Path=/; SameSite=Strict; Secure;`);
470
- stream.set_cookie(`UserEmail=${encodeURIComponent(user.email ?? "")}; Path=/; SameSite=Strict; Secure;`);
504
+ stream.set_cookie({
505
+ name: "UserName",
506
+ value: user.username ?? "",
507
+ path: "/",
508
+ secure: true,
509
+ same_site: "Lax"
510
+ });
511
+ stream.set_cookie({
512
+ name: "UserFirstName",
513
+ value: user.first_name ?? "",
514
+ path: "/",
515
+ secure: true,
516
+ same_site: "Lax"
517
+ });
518
+ stream.set_cookie({
519
+ name: "UserLastName",
520
+ value: user.last_name ?? "",
521
+ path: "/",
522
+ secure: true,
523
+ same_site: "Lax"
524
+ });
525
+ stream.set_cookie({
526
+ name: "UserEmail",
527
+ value: user.email ?? "",
528
+ path: "/",
529
+ secure: true,
530
+ same_site: "Lax"
531
+ });
471
532
  }
472
533
  /**
473
- * Clear all default auth/user cookies.
534
+ * Clear all default auth and user-related cookies.
535
+ *
474
536
  * @param stream The request stream.
475
537
  */
476
538
  _reset_cookies(stream) {
477
- const past = "Thu, 01 Jan 1970 00:00:00 GMT";
478
- stream.set_cookie(`T=; Max-Age=0; Path=/; Expires=${past}; SameSite=Strict; Secure; HttpOnly;`);
479
- stream.set_cookie(`UserID=; Max-Age=0; Path=/; Expires=${past}; SameSite=Strict; Secure;`);
480
- stream.set_cookie(`UserActivated=; Max-Age=0; Path=/; Expires=${past}; SameSite=Strict; Secure;`);
481
- stream.set_cookie(`UserName=; Max-Age=0; Path=/; Expires=${past}; SameSite=Strict; Secure;`);
482
- stream.set_cookie(`UserFirstName=; Max-Age=0; Path=/; Expires=${past}; SameSite=Strict; Secure;`);
483
- stream.set_cookie(`UserLastName=; Max-Age=0; Path=/; Expires=${past}; SameSite=Strict; Secure;`);
484
- stream.set_cookie(`UserEmail=; Max-Age=0; Path=/; Expires=${past}; SameSite=Strict; Secure;`);
539
+ const clear = {
540
+ path: "/",
541
+ max_age: 0,
542
+ secure: true,
543
+ same_site: "Lax"
544
+ };
545
+ stream.set_cookie({ name: "T", value: "", http_only: true, ...clear });
546
+ stream.set_cookie({ name: "UserID", value: "", ...clear });
547
+ stream.set_cookie({ name: "UserActivated", value: "", ...clear });
548
+ stream.set_cookie({ name: "UserName", value: "", ...clear });
549
+ stream.set_cookie({ name: "UserFirstName", value: "", ...clear });
550
+ stream.set_cookie({ name: "UserLastName", value: "", ...clear });
551
+ stream.set_cookie({ name: "UserEmail", value: "", ...clear });
485
552
  }
486
553
  // ---------------------------------------------------------
487
554
  // 2FA mail.
@@ -159,6 +159,23 @@ export declare class Collection<Data extends mongodb.Document = mongodb.Document
159
159
  * - Plain object without '$' keys → NOT valid for updateOne/findOneAndUpdate.
160
160
  */
161
161
  private _is_operator_update_or_pipeline;
162
+ private _index_key_signature;
163
+ private _keys_equal;
164
+ private _normalize_index_opts;
165
+ /**
166
+ * Drop all indexes that are NOT part of this._init_indexes, excluding _id_ (and TTL index if enabled).
167
+ *
168
+ * @note We match by key pattern rather than name because names can differ.
169
+ */
170
+ private _drop_non_init_indexes;
171
+ /**
172
+ * Creates indexes on collections.
173
+ *
174
+ * @note When transaction mode is enabled, the session option will not be used.
175
+ *
176
+ * @param opts The index create options.
177
+ */
178
+ private _create_index;
162
179
  /**
163
180
  * Initialize the collection, creating indexes and setting up TTL if needed.
164
181
  * @returns The initialized collection instance.
@@ -215,16 +232,6 @@ export declare class Collection<Data extends mongodb.Document = mongodb.Document
215
232
  * @docs
216
233
  */
217
234
  has_index(index: string): Promise<boolean>;
218
- /**
219
- * Creates indexes on collections.
220
- *
221
- * @note When transaction mode is enabled, the session option will not be used.
222
- *
223
- * @param opts The index create options.
224
- *
225
- * @docs
226
- */
227
- create_index(opts: string | Collection.IndexOpts): Promise<string>;
228
235
  /**
229
236
  * Standalone helper: merge `source` into `target` for missing keys only.
230
237
  * Clones assigned nested objects/arrays/dates once (when `clone` is true).