@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.
- package/backend/dist/cjs/backend/src/database/collection.d.ts +17 -10
- package/backend/dist/cjs/backend/src/database/collection.js +289 -126
- package/backend/dist/cjs/backend/src/payments/stripe/products.js +21 -6
- package/backend/dist/cjs/backend/src/stream.d.ts +58 -6
- package/backend/dist/cjs/backend/src/stream.js +91 -12
- package/backend/dist/cjs/backend/src/users.d.ts +11 -4
- package/backend/dist/cjs/backend/src/users.js +93 -26
- package/backend/dist/esm/backend/src/database/collection.d.ts +17 -10
- package/backend/dist/esm/backend/src/database/collection.js +311 -152
- package/backend/dist/esm/backend/src/database/filters/strict_update_filter_test.js +10 -0
- package/backend/dist/esm/backend/src/payments/stripe/products.js +24 -3
- package/backend/dist/esm/backend/src/stream.d.ts +58 -6
- package/backend/dist/esm/backend/src/stream.js +96 -12
- package/backend/dist/esm/backend/src/users.d.ts +11 -4
- package/backend/dist/esm/backend/src/users.js +95 -27
- package/frontend/dist/backend/src/database/collection.d.ts +17 -10
- package/frontend/dist/backend/src/database/collection.js +311 -152
- package/frontend/dist/backend/src/payments/stripe/products.js +24 -3
- package/frontend/dist/backend/src/stream.d.ts +58 -6
- package/frontend/dist/backend/src/stream.js +96 -12
- package/frontend/dist/backend/src/users.d.ts +11 -4
- package/frontend/dist/backend/src/users.js +95 -27
- package/frontend/dist/frontend/src/modules/user.js +3 -2
- package/frontend/dist/frontend/src/ui/table.d.ts +2 -2
- package/frontend/dist/frontend/src/ui/table.js +3 -6
- package/package.json +2 -2
|
@@ -1477,31 +1477,115 @@ export class Stream {
|
|
|
1477
1477
|
remove_headers(...names) {
|
|
1478
1478
|
return this.remove_header(...names);
|
|
1479
1479
|
}
|
|
1480
|
-
// Set a cookie.
|
|
1481
1480
|
/**
|
|
1482
|
-
* Set a cookie
|
|
1481
|
+
* Set a cookie to be sent with the response.
|
|
1482
|
+
*
|
|
1483
|
+
* Accepts either:
|
|
1484
|
+
* 1) a pre-built cookie header string (used as-is, no validation), or
|
|
1485
|
+
* 2) a structured object describing the cookie, from which a standards-compliant
|
|
1486
|
+
* cookie string will be generated.
|
|
1487
|
+
*
|
|
1488
|
+
* If a cookie with the same name already exists in the pending response list,
|
|
1489
|
+
* it will be replaced.
|
|
1490
|
+
*
|
|
1491
|
+
* @warning Cookies are only included in the response when using `send()`,
|
|
1492
|
+
* `success()` or `error()`.
|
|
1483
1493
|
*
|
|
1484
|
-
* @warning Will only be added to the response when the user uses `send()`, `success()` or `error()`.
|
|
1485
|
-
* @param cookie The cookie string.
|
|
1486
1494
|
* @example
|
|
1487
1495
|
* ```ts
|
|
1488
|
-
* stream.set_cookie("
|
|
1496
|
+
* stream.set_cookie("sid=abc123; Path=/; SameSite=Lax; Secure; HttpOnly");
|
|
1497
|
+
*
|
|
1498
|
+
* stream.set_cookie({
|
|
1499
|
+
* name: "sid",
|
|
1500
|
+
* value: session_id,
|
|
1501
|
+
* http_only: true,
|
|
1502
|
+
* secure: true,
|
|
1503
|
+
* same_site: "Lax",
|
|
1504
|
+
* path: "/",
|
|
1505
|
+
* max_age: 60 * 60 * 24 * 14,
|
|
1506
|
+
* });
|
|
1489
1507
|
* ```
|
|
1490
|
-
* @docs
|
|
1491
1508
|
*/
|
|
1492
1509
|
set_cookie(cookie) {
|
|
1493
|
-
cookie
|
|
1494
|
-
|
|
1510
|
+
// If the user provided a raw cookie string, trust it and use it as-is.
|
|
1511
|
+
if (typeof cookie === "string") {
|
|
1512
|
+
const cookie_str = cookie.trim();
|
|
1513
|
+
const name_end = cookie_str.indexOf("=");
|
|
1514
|
+
if (name_end !== -1) {
|
|
1515
|
+
const name = cookie_str.substring(0, name_end);
|
|
1516
|
+
for (let i = 0; i < this.res_cookies.length; i++) {
|
|
1517
|
+
if (this.res_cookies[i].startsWith(name)) {
|
|
1518
|
+
this.res_cookies[i] = cookie_str;
|
|
1519
|
+
return this;
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
this.res_cookies.push(cookie_str);
|
|
1524
|
+
return this;
|
|
1525
|
+
}
|
|
1526
|
+
// Structured cookie path (commercial-grade, predictable, minimal validation)
|
|
1527
|
+
const { name, value, path = "/", domain, max_age, expires, secure, http_only, same_site, prefix, extra, } = cookie;
|
|
1528
|
+
if (!name || typeof name !== "string") {
|
|
1529
|
+
throw new Error("set_cookie: cookie.name must be a non-empty string");
|
|
1530
|
+
}
|
|
1531
|
+
const full_name = `${prefix ?? ""}${name}`;
|
|
1532
|
+
// Enforce prefix rules (light but correct)
|
|
1533
|
+
if (prefix === "__Host-") {
|
|
1534
|
+
if (domain) {
|
|
1535
|
+
throw new Error("__Host- cookies must not include a domain attribute");
|
|
1536
|
+
}
|
|
1537
|
+
if (path !== "/") {
|
|
1538
|
+
throw new Error("__Host- cookies must have path='/'");
|
|
1539
|
+
}
|
|
1540
|
+
if (!secure) {
|
|
1541
|
+
throw new Error("__Host- cookies require secure=true");
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
if (prefix === "__Secure-" && !secure) {
|
|
1545
|
+
throw new Error("__Secure- cookies require secure=true");
|
|
1546
|
+
}
|
|
1547
|
+
const encoded_value = value === null || typeof value === "undefined"
|
|
1548
|
+
? ""
|
|
1549
|
+
: encodeURIComponent(String(value));
|
|
1550
|
+
const parts = [];
|
|
1551
|
+
parts.push(`${full_name}=${encoded_value}`);
|
|
1552
|
+
if (path)
|
|
1553
|
+
parts.push(`Path=${path}`);
|
|
1554
|
+
if (domain)
|
|
1555
|
+
parts.push(`Domain=${domain}`);
|
|
1556
|
+
if (typeof max_age === "number" && Number.isFinite(max_age)) {
|
|
1557
|
+
parts.push(`Max-Age=${Math.trunc(max_age)}`);
|
|
1558
|
+
}
|
|
1559
|
+
if (expires) {
|
|
1560
|
+
const exp = expires instanceof Date ? expires.toUTCString() : String(expires).trim();
|
|
1561
|
+
if (exp)
|
|
1562
|
+
parts.push(`Expires=${exp}`);
|
|
1563
|
+
}
|
|
1564
|
+
if (secure)
|
|
1565
|
+
parts.push("Secure");
|
|
1566
|
+
if (http_only)
|
|
1567
|
+
parts.push("HttpOnly");
|
|
1568
|
+
if (same_site)
|
|
1569
|
+
parts.push(`SameSite=${same_site}`);
|
|
1570
|
+
if (extra && Array.isArray(extra)) {
|
|
1571
|
+
for (const attr of extra) {
|
|
1572
|
+
const trimmed = String(attr).trim();
|
|
1573
|
+
if (trimmed)
|
|
1574
|
+
parts.push(trimmed);
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
const cookie_str = parts.join("; ");
|
|
1578
|
+
const name_end = cookie_str.indexOf("=");
|
|
1495
1579
|
if (name_end !== -1) {
|
|
1496
|
-
const
|
|
1580
|
+
const existing_name = cookie_str.substring(0, name_end);
|
|
1497
1581
|
for (let i = 0; i < this.res_cookies.length; i++) {
|
|
1498
|
-
if (this.res_cookies[i].startsWith(
|
|
1499
|
-
this.res_cookies[i] =
|
|
1582
|
+
if (this.res_cookies[i].startsWith(existing_name)) {
|
|
1583
|
+
this.res_cookies[i] = cookie_str;
|
|
1500
1584
|
return this;
|
|
1501
1585
|
}
|
|
1502
1586
|
}
|
|
1503
1587
|
}
|
|
1504
|
-
this.res_cookies.push(
|
|
1588
|
+
this.res_cookies.push(cookie_str);
|
|
1505
1589
|
return this;
|
|
1506
1590
|
}
|
|
1507
1591
|
// 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 (
|
|
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-
|
|
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
|
|
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;
|
|
@@ -431,61 +431,129 @@ export class Users {
|
|
|
431
431
|
// ---------------------------------------------------------
|
|
432
432
|
/**
|
|
433
433
|
* Create the auth token cookie on the response.
|
|
434
|
+
* `T` is treated as a real authentication credential.
|
|
435
|
+
*
|
|
434
436
|
* @param stream The request stream.
|
|
435
437
|
* @param token The token string or Token object.
|
|
436
438
|
*/
|
|
437
439
|
_create_token_cookie(stream, token) {
|
|
438
440
|
stream.set_header("Cache-Control", "max-age=0, no-cache, no-store, must-revalidate, proxy-revalidate");
|
|
439
441
|
stream.set_header("Access-Control-Allow-Credentials", "true");
|
|
440
|
-
|
|
441
|
-
token = token.token;
|
|
442
|
-
}
|
|
442
|
+
const token_value = typeof token === "object" ? token.token : token;
|
|
443
443
|
const max_age = this.token_expiration; // seconds
|
|
444
|
-
|
|
445
|
-
|
|
444
|
+
stream.set_cookie({
|
|
445
|
+
name: "T",
|
|
446
|
+
value: token_value,
|
|
447
|
+
path: "/",
|
|
448
|
+
max_age,
|
|
449
|
+
secure: true,
|
|
450
|
+
http_only: true,
|
|
451
|
+
same_site: "Lax", // REQUIRED for Stripe success/cancel redirects
|
|
452
|
+
});
|
|
446
453
|
}
|
|
447
454
|
/**
|
|
448
|
-
* Create user cookies (
|
|
455
|
+
* Create user cookies (ID and activation flag).
|
|
456
|
+
* These are user-state cookies, NOT auth credentials.
|
|
457
|
+
*
|
|
449
458
|
* @param stream The request stream.
|
|
450
459
|
* @param uid The user ID, or invalid to clear.
|
|
451
460
|
*/
|
|
452
461
|
async _create_user_cookie(stream, uid) {
|
|
453
|
-
if (typeof uid === "string") {
|
|
454
|
-
stream.set_cookie(
|
|
455
|
-
|
|
456
|
-
|
|
462
|
+
if (typeof uid === "string" && uid.length > 0) {
|
|
463
|
+
stream.set_cookie({
|
|
464
|
+
name: "UserID",
|
|
465
|
+
value: uid,
|
|
466
|
+
path: "/",
|
|
467
|
+
secure: true,
|
|
468
|
+
same_site: "Lax",
|
|
469
|
+
});
|
|
470
|
+
const is_activated = this.enable_account_activation
|
|
471
|
+
? await this.is_activated(uid)
|
|
472
|
+
: true;
|
|
473
|
+
stream.set_cookie({
|
|
474
|
+
name: "UserActivated",
|
|
475
|
+
value: is_activated ? "1" : "0",
|
|
476
|
+
path: "/",
|
|
477
|
+
secure: true,
|
|
478
|
+
same_site: "Lax",
|
|
479
|
+
});
|
|
457
480
|
}
|
|
458
481
|
else {
|
|
459
|
-
stream.set_cookie(
|
|
460
|
-
|
|
461
|
-
|
|
482
|
+
stream.set_cookie({
|
|
483
|
+
name: "UserID",
|
|
484
|
+
value: "",
|
|
485
|
+
path: "/",
|
|
486
|
+
max_age: 0,
|
|
487
|
+
secure: true,
|
|
488
|
+
same_site: "Lax",
|
|
489
|
+
});
|
|
490
|
+
stream.set_cookie({
|
|
491
|
+
name: "UserActivated",
|
|
492
|
+
value: "0",
|
|
493
|
+
path: "/",
|
|
494
|
+
max_age: 0,
|
|
495
|
+
secure: true,
|
|
496
|
+
same_site: "Lax",
|
|
497
|
+
});
|
|
462
498
|
}
|
|
463
499
|
}
|
|
464
500
|
/**
|
|
465
|
-
* Create non-
|
|
501
|
+
* Create non-HttpOnly cookies with detailed user info for frontend usage.
|
|
502
|
+
* These are UI convenience cookies only.
|
|
503
|
+
*
|
|
466
504
|
* @param stream The request stream.
|
|
467
505
|
* @param uid The user ID.
|
|
468
506
|
*/
|
|
469
507
|
async _create_detailed_user_cookie(stream, uid) {
|
|
470
508
|
const user = await this.get(uid);
|
|
471
|
-
stream.set_cookie(
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
509
|
+
stream.set_cookie({
|
|
510
|
+
name: "UserName",
|
|
511
|
+
value: user.username ?? "",
|
|
512
|
+
path: "/",
|
|
513
|
+
secure: true,
|
|
514
|
+
same_site: "Lax",
|
|
515
|
+
});
|
|
516
|
+
stream.set_cookie({
|
|
517
|
+
name: "UserFirstName",
|
|
518
|
+
value: user.first_name ?? "",
|
|
519
|
+
path: "/",
|
|
520
|
+
secure: true,
|
|
521
|
+
same_site: "Lax",
|
|
522
|
+
});
|
|
523
|
+
stream.set_cookie({
|
|
524
|
+
name: "UserLastName",
|
|
525
|
+
value: user.last_name ?? "",
|
|
526
|
+
path: "/",
|
|
527
|
+
secure: true,
|
|
528
|
+
same_site: "Lax",
|
|
529
|
+
});
|
|
530
|
+
stream.set_cookie({
|
|
531
|
+
name: "UserEmail",
|
|
532
|
+
value: user.email ?? "",
|
|
533
|
+
path: "/",
|
|
534
|
+
secure: true,
|
|
535
|
+
same_site: "Lax",
|
|
536
|
+
});
|
|
475
537
|
}
|
|
476
538
|
/**
|
|
477
|
-
* Clear all default auth
|
|
539
|
+
* Clear all default auth and user-related cookies.
|
|
540
|
+
*
|
|
478
541
|
* @param stream The request stream.
|
|
479
542
|
*/
|
|
480
543
|
_reset_cookies(stream) {
|
|
481
|
-
const
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
stream.set_cookie(
|
|
488
|
-
stream.set_cookie(
|
|
544
|
+
const clear = {
|
|
545
|
+
path: "/",
|
|
546
|
+
max_age: 0,
|
|
547
|
+
secure: true,
|
|
548
|
+
same_site: "Lax",
|
|
549
|
+
};
|
|
550
|
+
stream.set_cookie({ name: "T", value: "", http_only: true, ...clear });
|
|
551
|
+
stream.set_cookie({ name: "UserID", value: "", ...clear });
|
|
552
|
+
stream.set_cookie({ name: "UserActivated", value: "", ...clear });
|
|
553
|
+
stream.set_cookie({ name: "UserName", value: "", ...clear });
|
|
554
|
+
stream.set_cookie({ name: "UserFirstName", value: "", ...clear });
|
|
555
|
+
stream.set_cookie({ name: "UserLastName", value: "", ...clear });
|
|
556
|
+
stream.set_cookie({ name: "UserEmail", value: "", ...clear });
|
|
489
557
|
}
|
|
490
558
|
// ---------------------------------------------------------
|
|
491
559
|
// 2FA mail.
|
|
@@ -17,7 +17,7 @@ export var User;
|
|
|
17
17
|
*/
|
|
18
18
|
function uid() {
|
|
19
19
|
const uid = Cookies.get("UserID");
|
|
20
|
-
return typeof uid !== "string" || uid == "-1" ? undefined : uid;
|
|
20
|
+
return typeof uid !== "string" || uid == "-1" || uid === "" ? undefined : uid;
|
|
21
21
|
}
|
|
22
22
|
User.uid = uid;
|
|
23
23
|
/**
|
|
@@ -87,7 +87,8 @@ export var User;
|
|
|
87
87
|
* @docs
|
|
88
88
|
*/
|
|
89
89
|
function is_activated() {
|
|
90
|
-
|
|
90
|
+
const activated = Cookies.get("UserActivated");
|
|
91
|
+
return activated === "true" || activated === "1";
|
|
91
92
|
}
|
|
92
93
|
User.is_activated = is_activated;
|
|
93
94
|
/**
|
|
@@ -71,7 +71,7 @@ export declare class TableElement extends VElementTagMap.div {
|
|
|
71
71
|
table_body: TableBodyElement;
|
|
72
72
|
constructor({ rows, columns, show_columns, }: {
|
|
73
73
|
rows?: AppendType[][];
|
|
74
|
-
columns
|
|
74
|
+
columns?: AppendType[];
|
|
75
75
|
show_columns?: boolean;
|
|
76
76
|
});
|
|
77
77
|
iterate(callback: any): this;
|
|
@@ -89,7 +89,7 @@ export declare class TableElement extends VElementTagMap.div {
|
|
|
89
89
|
}
|
|
90
90
|
export declare const Table: <Extensions extends object = {}>(args_0: {
|
|
91
91
|
rows?: AppendType[][];
|
|
92
|
-
columns
|
|
92
|
+
columns?: AppendType[];
|
|
93
93
|
show_columns?: boolean;
|
|
94
94
|
}) => TableElement & Extensions;
|
|
95
95
|
export declare const NullTable: <Extensions extends object = {}>() => TableElement & Extensions;
|
|
@@ -285,19 +285,16 @@ let TableElement = (() => {
|
|
|
285
285
|
table_head = NullTableHead();
|
|
286
286
|
table_body = NullTableBody();
|
|
287
287
|
// Constructor.
|
|
288
|
-
constructor({ rows = [
|
|
289
|
-
["a", "b"],
|
|
290
|
-
["c", "d"],
|
|
291
|
-
], columns = ["Column 1", "Column 2"], show_columns = true, }) {
|
|
288
|
+
constructor({ rows = [], columns = [], show_columns = true, }) {
|
|
292
289
|
// Initialize base class.
|
|
293
290
|
super({
|
|
294
291
|
derived: TableElement,
|
|
295
292
|
});
|
|
296
293
|
// Attributes.
|
|
297
294
|
this.table_rows = [];
|
|
298
|
-
this.show_columns = show_columns === true && columns
|
|
295
|
+
this.show_columns = show_columns === true && columns.length > 0;
|
|
299
296
|
// Append content.
|
|
300
|
-
this.create({ rows, columns
|
|
297
|
+
this.create({ rows, columns });
|
|
301
298
|
this.overflow("hidden"); // without overflow hidden when border radius is set on the element the background of rows can overflow.
|
|
302
299
|
}
|
|
303
300
|
// Iterate the table data items.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"author": "Daan van den Bergh",
|
|
3
3
|
"name": "@vandenberghinc/volt",
|
|
4
|
-
"version": "1.2.
|
|
4
|
+
"version": "1.2.4",
|
|
5
5
|
"description": "",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"types": "./backend/dist/esm/index.d.ts",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@vandenberghinc/vhighlight": "^1.3.17",
|
|
38
|
-
"@vandenberghinc/vlib": "^1.6.
|
|
38
|
+
"@vandenberghinc/vlib": "^1.6.46",
|
|
39
39
|
"blob-stream": "^0.1.3",
|
|
40
40
|
"clean-css": "^5.3.3",
|
|
41
41
|
"esbuild": "^0.25.12",
|