@blamejs/core 0.14.27 → 0.15.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/CHANGELOG.md +6 -0
- package/README.md +2 -2
- package/index.js +4 -0
- package/lib/ai-content-detect.js +9 -10
- package/lib/api-key.js +158 -77
- package/lib/atomic-file.js +29 -1
- package/lib/audit-chain.js +47 -11
- package/lib/audit-sign.js +77 -2
- package/lib/audit-tools.js +79 -51
- package/lib/audit.js +228 -100
- package/lib/backup/index.js +13 -10
- package/lib/break-glass.js +202 -144
- package/lib/cache.js +174 -105
- package/lib/chain-writer.js +38 -16
- package/lib/cli.js +19 -14
- package/lib/cluster-provider-db.js +130 -104
- package/lib/cluster-storage.js +119 -22
- package/lib/cluster.js +119 -71
- package/lib/compliance.js +22 -0
- package/lib/consent.js +82 -29
- package/lib/constants.js +16 -11
- package/lib/crypto-field.js +387 -91
- package/lib/db-declare-row-policy.js +35 -22
- package/lib/db-file-lifecycle.js +3 -2
- package/lib/db-query.js +517 -256
- package/lib/db-schema.js +209 -44
- package/lib/db.js +202 -95
- package/lib/external-db-migrate.js +229 -139
- package/lib/external-db.js +25 -15
- package/lib/framework-error.js +11 -0
- package/lib/framework-files.js +73 -0
- package/lib/framework-schema.js +695 -394
- package/lib/gate-contract.js +596 -1
- package/lib/guard-agent-registry.js +26 -44
- package/lib/guard-all.js +1 -0
- package/lib/guard-auth.js +42 -112
- package/lib/guard-cidr.js +33 -154
- package/lib/guard-csv.js +46 -113
- package/lib/guard-domain.js +34 -157
- package/lib/guard-dsn.js +27 -43
- package/lib/guard-email.js +47 -69
- package/lib/guard-envelope.js +19 -32
- package/lib/guard-event-bus-payload.js +24 -42
- package/lib/guard-event-bus-topic.js +25 -43
- package/lib/guard-filename.js +42 -106
- package/lib/guard-graphql.js +42 -123
- package/lib/guard-html.js +53 -108
- package/lib/guard-idempotency-key.js +24 -42
- package/lib/guard-image.js +46 -103
- package/lib/guard-imap-command.js +18 -32
- package/lib/guard-jmap.js +16 -30
- package/lib/guard-json.js +38 -108
- package/lib/guard-jsonpath.js +38 -171
- package/lib/guard-jwt.js +49 -179
- package/lib/guard-list-id.js +25 -41
- package/lib/guard-list-unsubscribe.js +27 -43
- package/lib/guard-mail-compose.js +24 -42
- package/lib/guard-mail-move.js +26 -44
- package/lib/guard-mail-query.js +28 -46
- package/lib/guard-mail-reply.js +24 -42
- package/lib/guard-mail-sieve.js +24 -42
- package/lib/guard-managesieve-command.js +17 -31
- package/lib/guard-markdown.js +37 -104
- package/lib/guard-message-id.js +26 -45
- package/lib/guard-mime.js +39 -151
- package/lib/guard-oauth.js +54 -135
- package/lib/guard-pdf.js +45 -101
- package/lib/guard-pop3-command.js +21 -31
- package/lib/guard-posture-chain.js +24 -42
- package/lib/guard-regex.js +33 -107
- package/lib/guard-saga-config.js +24 -42
- package/lib/guard-shell.js +42 -172
- package/lib/guard-smtp-command.js +48 -54
- package/lib/guard-snapshot-envelope.js +24 -42
- package/lib/guard-sql.js +1491 -0
- package/lib/guard-stream-args.js +24 -43
- package/lib/guard-svg.js +47 -65
- package/lib/guard-template.js +35 -172
- package/lib/guard-tenant-id.js +26 -45
- package/lib/guard-time.js +32 -154
- package/lib/guard-trace-context.js +25 -44
- package/lib/guard-uuid.js +32 -153
- package/lib/guard-xml.js +38 -113
- package/lib/guard-yaml.js +51 -163
- package/lib/http-client.js +14 -0
- package/lib/inbox.js +120 -107
- package/lib/legal-hold.js +107 -50
- package/lib/log-stream-cloudwatch.js +47 -31
- package/lib/log-stream-otlp.js +32 -18
- package/lib/mail-crypto-smime.js +2 -6
- package/lib/mail-greylist.js +2 -6
- package/lib/mail-helo.js +2 -6
- package/lib/mail-journal.js +85 -64
- package/lib/mail-rbl.js +2 -6
- package/lib/mail-scan.js +2 -6
- package/lib/mail-spam-score.js +2 -6
- package/lib/mail-store.js +293 -154
- package/lib/middleware/fetch-metadata.js +17 -7
- package/lib/middleware/idempotency-key.js +54 -38
- package/lib/middleware/rate-limit.js +102 -32
- package/lib/middleware/security-headers.js +21 -5
- package/lib/migrations.js +108 -66
- package/lib/network-heartbeat.js +7 -0
- package/lib/nonce-store.js +31 -9
- package/lib/object-store/azure-blob-bucket-ops.js +9 -4
- package/lib/object-store/azure-blob.js +31 -3
- package/lib/object-store/sigv4.js +10 -0
- package/lib/outbox.js +136 -82
- package/lib/pqc-agent.js +44 -0
- package/lib/pubsub-cluster.js +42 -20
- package/lib/queue-local.js +202 -139
- package/lib/queue-redis.js +9 -1
- package/lib/queue-sqs.js +6 -0
- package/lib/retention.js +82 -39
- package/lib/safe-dns.js +29 -45
- package/lib/safe-ical.js +18 -33
- package/lib/safe-icap.js +27 -43
- package/lib/safe-sieve.js +21 -40
- package/lib/safe-sql.js +124 -3
- package/lib/safe-vcard.js +18 -33
- package/lib/scheduler.js +35 -12
- package/lib/seeders.js +122 -74
- package/lib/session-stores.js +42 -14
- package/lib/session.js +116 -72
- package/lib/sql.js +3885 -0
- package/lib/static.js +45 -7
- package/lib/subject.js +89 -49
- package/lib/vault/index.js +3 -2
- package/lib/vault/passphrase-ops.js +3 -2
- package/lib/vault/rotate.js +104 -64
- package/lib/vendor-data.js +2 -0
- package/lib/websocket.js +16 -0
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/session-stores.js
CHANGED
|
@@ -37,22 +37,50 @@
|
|
|
37
37
|
* b.session.useStore(sessionStore);
|
|
38
38
|
*/
|
|
39
39
|
|
|
40
|
+
var frameworkSchema = require("./framework-schema");
|
|
40
41
|
var localDbThin = require("./local-db-thin");
|
|
42
|
+
var sql = require("./sql");
|
|
41
43
|
var validateOpts = require("./validate-opts");
|
|
42
44
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
45
|
+
// Logical session-table name — resolved through frameworkSchema.tableName
|
|
46
|
+
// so a configured table prefix (b.frameworkSchema.setTablePrefix) is
|
|
47
|
+
// honored. This isolated localDbThin file owns its own schema; the name
|
|
48
|
+
// must agree with the main-DB / cluster-mode session table b.session
|
|
49
|
+
// reads + the sealedFields registry key (db.js registers under the
|
|
50
|
+
// logical name).
|
|
51
|
+
var SESSION_LOGICAL = "_blamejs_sessions"; // allow:hand-rolled-sql — canonical logical table-name declaration
|
|
52
|
+
|
|
53
|
+
// b.sql opts for this adapter's schema DDL + every statement b.session
|
|
54
|
+
// builds against it. The localDbThin backend is a dedicated node:sqlite
|
|
55
|
+
// file (always sqlite, independent of cluster mode — see local-db-thin.js),
|
|
56
|
+
// so the dialect is the literal "sqlite": this store NEVER dispatches to an
|
|
57
|
+
// external Postgres / MySQL backend. Making the dialect explicit (rather than
|
|
58
|
+
// leaning on b.sql's "sqlite" default) keeps the quoting intent documented +
|
|
59
|
+
// matches the cluster-routed data-layer files threading
|
|
60
|
+
// clusterStorage.dialect() through the same opts seam.
|
|
61
|
+
var SQL_OPTS = { dialect: "sqlite" };
|
|
62
|
+
|
|
63
|
+
// CREATE TABLE + the two session-side indexes (userIdHash for
|
|
64
|
+
// destroyAllForUser, expiresAt for purgeExpired), built through b.sql so
|
|
65
|
+
// every identifier is quoted by construction and the table name resolves
|
|
66
|
+
// through the configurable prefix. DDL binds no values, so each builder
|
|
67
|
+
// returns { sql } only; the statements are joined for the adapter's
|
|
68
|
+
// schemaSql.
|
|
69
|
+
function _sessionSchemaSql() {
|
|
70
|
+
var table = frameworkSchema.tableName(SESSION_LOGICAL);
|
|
71
|
+
var create = sql.createTable(table, [
|
|
72
|
+
{ name: "sidHash", type: "text", primaryKey: true },
|
|
73
|
+
{ name: "userId", type: "text" },
|
|
74
|
+
{ name: "userIdHash", type: "text" },
|
|
75
|
+
{ name: "data", type: "text" },
|
|
76
|
+
{ name: "createdAt", type: "int" },
|
|
77
|
+
{ name: "expiresAt", type: "int" },
|
|
78
|
+
{ name: "lastActivity", type: "int" },
|
|
79
|
+
], SQL_OPTS).sql;
|
|
80
|
+
var idxUser = sql.createIndex(table + "_userIdHash_idx", table, ["userIdHash"], SQL_OPTS).sql;
|
|
81
|
+
var idxExp = sql.createIndex(table + "_expiresAt_idx", table, ["expiresAt"], SQL_OPTS).sql;
|
|
82
|
+
return [create + ";", idxUser + ";", idxExp + ";"].join("\n");
|
|
83
|
+
}
|
|
56
84
|
|
|
57
85
|
/**
|
|
58
86
|
* @primitive b.session.stores.localDbThin
|
|
@@ -99,7 +127,7 @@ function localDbThinStore(opts) {
|
|
|
99
127
|
// logging out every user; operators wanting clear-on-corrupt opt in.
|
|
100
128
|
var handle = localDbThin.thin({
|
|
101
129
|
file: opts.file,
|
|
102
|
-
schemaSql:
|
|
130
|
+
schemaSql: _sessionSchemaSql(),
|
|
103
131
|
recovery: opts.recovery || "refuse",
|
|
104
132
|
pragmas: opts.pragmas,
|
|
105
133
|
audit: opts.audit !== false,
|
package/lib/session.js
CHANGED
|
@@ -53,9 +53,11 @@ var clusterStorage = require("./cluster-storage");
|
|
|
53
53
|
var C = require("./constants");
|
|
54
54
|
var { generateToken, sha3Hash } = require("./crypto");
|
|
55
55
|
var cryptoField = require("./crypto-field");
|
|
56
|
+
var frameworkSchema = require("./framework-schema");
|
|
56
57
|
var lazyRequire = require("./lazy-require");
|
|
57
58
|
var requestHelpers = require("./request-helpers");
|
|
58
59
|
var safeJson = require("./safe-json");
|
|
60
|
+
var sql = require("./sql");
|
|
59
61
|
var { SessionError } = require("./framework-error");
|
|
60
62
|
|
|
61
63
|
// vault is initialized at boot before sessions; lazyRequire keeps the
|
|
@@ -119,8 +121,33 @@ var SID_NAMESPACE = "bj-session:";
|
|
|
119
121
|
// behind the same helper.
|
|
120
122
|
var SID_BYTES = C.BYTES.bytes(32);
|
|
121
123
|
|
|
124
|
+
// Logical session-table name. Two uses, deliberately distinct:
|
|
125
|
+
// - As the cryptoField registry key (sealRow / unsealRow / lookupHash),
|
|
126
|
+
// it stays the LOGICAL name — that is the key db.js registers the
|
|
127
|
+
// sealedFields + derivedHashes under, independent of any table prefix.
|
|
128
|
+
// - As the SQL table name, it is resolved through
|
|
129
|
+
// frameworkSchema.tableName(...) so a configured table prefix
|
|
130
|
+
// (b.frameworkSchema.setTablePrefix) is honored. The name is
|
|
131
|
+
// identity-mapped in LOCAL_TO_EXTERNAL, so clusterStorage's
|
|
132
|
+
// resolveTables leaves it untouched at dispatch.
|
|
133
|
+
var SESSION_TABLE = "_blamejs_sessions"; // allow:hand-rolled-sql — canonical logical table-name + cryptoField registry key
|
|
134
|
+
function _sessionSqlTable() { return frameworkSchema.tableName(SESSION_TABLE); }
|
|
135
|
+
|
|
136
|
+
// b.sql opts for every session statement: thread the ACTIVE backend dialect
|
|
137
|
+
// (clusterStorage.dialect() — "sqlite" single-node, "postgres" | "mysql" in
|
|
138
|
+
// cluster mode) so the emitted identifier quoting and dialect idioms match
|
|
139
|
+
// the backend the SQL dispatches to. b.sql defaults to "sqlite", which works
|
|
140
|
+
// on Postgres only by accident (both double-quote identifiers) and emits the
|
|
141
|
+
// wrong quoting + idioms on MySQL. The default store routes through
|
|
142
|
+
// clusterStorage, and an operator localDbThin store is single-node sqlite —
|
|
143
|
+
// in both single-node cases clusterStorage.dialect() resolves "sqlite", so
|
|
144
|
+
// the opts agree with the store the SQL reaches. clusterStorage.execute (the
|
|
145
|
+
// default store) still rewrites table names + translates `?` placeholders at
|
|
146
|
+
// dispatch; this controls only the builder-side quoting + idiom selection.
|
|
147
|
+
function _sessionSqlOpts() { return { dialect: clusterStorage.dialect() }; }
|
|
148
|
+
|
|
122
149
|
// Column order used for INSERT — kept as a constant so the placeholders
|
|
123
|
-
// list and the values list stay in sync. Must match
|
|
150
|
+
// list and the values list stay in sync. Must match the session table's
|
|
124
151
|
// schema in db.js (single-node) and framework-schema.js (cluster mode).
|
|
125
152
|
var SESSION_COLS = ["sidHash", "userId", "userIdHash", "data", "createdAt", "expiresAt", "lastActivity"];
|
|
126
153
|
|
|
@@ -167,7 +194,7 @@ function _unsealCookieToken(token) {
|
|
|
167
194
|
// where not set). The cryptoField.sealRow call seals userId/data and
|
|
168
195
|
// produces userIdHash from userId.
|
|
169
196
|
function _sealForInsert(row) {
|
|
170
|
-
var sealed = cryptoField.sealRow(
|
|
197
|
+
var sealed = cryptoField.sealRow(SESSION_TABLE, row);
|
|
171
198
|
for (var i = 0; i < SESSION_COLS.length; i++) {
|
|
172
199
|
if (!(SESSION_COLS[i] in sealed)) sealed[SESSION_COLS[i]] = null;
|
|
173
200
|
}
|
|
@@ -433,13 +460,13 @@ async function create(opts) {
|
|
|
433
460
|
expiresAt: expiresAt,
|
|
434
461
|
lastActivity: nowMs,
|
|
435
462
|
});
|
|
436
|
-
var
|
|
437
|
-
var
|
|
438
|
-
var
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
);
|
|
463
|
+
var insertRow = {};
|
|
464
|
+
for (var ci = 0; ci < SESSION_COLS.length; ci++) insertRow[SESSION_COLS[ci]] = sealed[SESSION_COLS[ci]];
|
|
465
|
+
var built = sql.insert(_sessionSqlTable(), _sessionSqlOpts())
|
|
466
|
+
.columns(SESSION_COLS)
|
|
467
|
+
.values(insertRow)
|
|
468
|
+
.toSql();
|
|
469
|
+
await _currentStore().execute(built.sql, built.params);
|
|
443
470
|
|
|
444
471
|
return { token: _sealCookieToken(sid), expiresAt: expiresAt };
|
|
445
472
|
}
|
|
@@ -496,11 +523,11 @@ async function verify(token, verifyOpts) {
|
|
|
496
523
|
if (sid === null) return null;
|
|
497
524
|
var sidHash = _hashSid(sid);
|
|
498
525
|
|
|
499
|
-
var
|
|
500
|
-
"
|
|
501
|
-
"
|
|
502
|
-
|
|
503
|
-
);
|
|
526
|
+
var selBuilt = sql.select(_sessionSqlTable(), _sessionSqlOpts())
|
|
527
|
+
.columns(["sidHash", "userId", "userIdHash", "data", "createdAt", "expiresAt", "lastActivity"])
|
|
528
|
+
.where("sidHash", sidHash)
|
|
529
|
+
.toSql();
|
|
530
|
+
var row = await _currentStore().executeOne(selBuilt.sql, selBuilt.params);
|
|
504
531
|
if (!row) return null;
|
|
505
532
|
var nowMs = Date.now();
|
|
506
533
|
if (Number(row.expiresAt) < nowMs) {
|
|
@@ -554,7 +581,7 @@ async function verify(token, verifyOpts) {
|
|
|
554
581
|
// Unseal sealed columns (userId, data) using the cryptoField pipeline
|
|
555
582
|
// so we return cleartext to the caller — same shape as the previous
|
|
556
583
|
// db().from(...).first() path delivered.
|
|
557
|
-
var unsealed = cryptoField.unsealRow(
|
|
584
|
+
var unsealed = cryptoField.unsealRow(SESSION_TABLE, row);
|
|
558
585
|
var data = null;
|
|
559
586
|
var storedFingerprint = null;
|
|
560
587
|
if (unsealed.data) {
|
|
@@ -679,10 +706,10 @@ async function destroy(token) {
|
|
|
679
706
|
}
|
|
680
707
|
|
|
681
708
|
async function _deleteBySidHash(sidHash) {
|
|
682
|
-
var
|
|
683
|
-
"
|
|
684
|
-
|
|
685
|
-
);
|
|
709
|
+
var built = sql.delete(_sessionSqlTable(), _sessionSqlOpts())
|
|
710
|
+
.where("sidHash", sidHash)
|
|
711
|
+
.toSql();
|
|
712
|
+
var result = await _currentStore().execute(built.sql, built.params);
|
|
686
713
|
return (result.rowCount || 0) > 0;
|
|
687
714
|
}
|
|
688
715
|
|
|
@@ -718,16 +745,23 @@ async function destroyAllForUser(userId) {
|
|
|
718
745
|
true);
|
|
719
746
|
}
|
|
720
747
|
// userId is sealed; look up via derived userIdHash.
|
|
721
|
-
var lookup = cryptoField.lookupHash(
|
|
748
|
+
var lookup = cryptoField.lookupHash(SESSION_TABLE, "userId", userId);
|
|
722
749
|
if (!lookup) {
|
|
723
750
|
throw _err("MISCONFIGURED",
|
|
724
|
-
"
|
|
751
|
+
"the session table schema is missing the userIdHash derived hash — framework misconfigured",
|
|
725
752
|
true);
|
|
726
753
|
}
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
754
|
+
// Dual-read across the keyed-MAC flip: a pre-v0.15.0 session row carries
|
|
755
|
+
// the legacy salted-sha3 userIdHash, so destroy must match both digests
|
|
756
|
+
// or it leaves un-migrated sessions for the user un-revoked.
|
|
757
|
+
var userHashes = [lookup.value];
|
|
758
|
+
if (lookup.legacyValue != null && lookup.legacyValue !== lookup.value) {
|
|
759
|
+
userHashes.push(lookup.legacyValue);
|
|
760
|
+
}
|
|
761
|
+
var built = sql.delete(_sessionSqlTable(), _sessionSqlOpts())
|
|
762
|
+
.whereIn("userIdHash", userHashes)
|
|
763
|
+
.toSql();
|
|
764
|
+
var result = await _currentStore().execute(built.sql, built.params);
|
|
731
765
|
return result.rowCount || 0;
|
|
732
766
|
}
|
|
733
767
|
|
|
@@ -776,18 +810,20 @@ async function touch(token, opts) {
|
|
|
776
810
|
if (opts.extendBy !== undefined && opts.extendBy !== null) {
|
|
777
811
|
_validateTtl(opts.extendBy, "session.touch");
|
|
778
812
|
var newExpires = nowMs + opts.extendBy;
|
|
779
|
-
var
|
|
780
|
-
|
|
781
|
-
"
|
|
782
|
-
|
|
783
|
-
|
|
813
|
+
var built = sql.update(_sessionSqlTable(), _sessionSqlOpts())
|
|
814
|
+
.set({ lastActivity: nowMs, expiresAt: newExpires })
|
|
815
|
+
.where("sidHash", sidHash)
|
|
816
|
+
.where("expiresAt", ">=", nowMs)
|
|
817
|
+
.toSql();
|
|
818
|
+
var result = await _currentStore().execute(built.sql, built.params);
|
|
784
819
|
return (result.rowCount || 0) > 0;
|
|
785
820
|
}
|
|
786
|
-
var
|
|
787
|
-
|
|
788
|
-
"
|
|
789
|
-
|
|
790
|
-
|
|
821
|
+
var built2 = sql.update(_sessionSqlTable(), _sessionSqlOpts())
|
|
822
|
+
.set({ lastActivity: nowMs })
|
|
823
|
+
.where("sidHash", sidHash)
|
|
824
|
+
.where("expiresAt", ">=", nowMs)
|
|
825
|
+
.toSql();
|
|
826
|
+
var result2 = await _currentStore().execute(built2.sql, built2.params);
|
|
791
827
|
return (result2.rowCount || 0) > 0;
|
|
792
828
|
}
|
|
793
829
|
|
|
@@ -844,31 +880,31 @@ async function rotate(oldToken, opts) {
|
|
|
844
880
|
newExpires = nowMs + opts.ttlMs;
|
|
845
881
|
}
|
|
846
882
|
|
|
847
|
-
var
|
|
848
|
-
var setParams = [newSidHash, nowMs];
|
|
883
|
+
var setCols = { sidHash: newSidHash, lastActivity: nowMs };
|
|
849
884
|
|
|
850
885
|
if (opts.data !== undefined) {
|
|
851
886
|
var dataJson = opts.data ? JSON.stringify(opts.data) : null;
|
|
852
|
-
var sealedRow = cryptoField.sealRow(
|
|
853
|
-
|
|
854
|
-
setParams.push(sealedRow.data);
|
|
887
|
+
var sealedRow = cryptoField.sealRow(SESSION_TABLE, { data: dataJson });
|
|
888
|
+
setCols.data = sealedRow.data;
|
|
855
889
|
}
|
|
856
890
|
if (newExpires !== null) {
|
|
857
|
-
|
|
858
|
-
setParams.push(newExpires);
|
|
891
|
+
setCols.expiresAt = newExpires;
|
|
859
892
|
}
|
|
860
893
|
|
|
861
|
-
var
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
894
|
+
var updBuilt = sql.update(_sessionSqlTable(), _sessionSqlOpts())
|
|
895
|
+
.set(setCols)
|
|
896
|
+
.where("sidHash", oldSidHash)
|
|
897
|
+
.where("expiresAt", ">=", nowMs)
|
|
898
|
+
.toSql();
|
|
899
|
+
var result = await _currentStore().execute(updBuilt.sql, updBuilt.params);
|
|
865
900
|
if ((result.rowCount || 0) === 0) return null;
|
|
866
901
|
|
|
867
902
|
// Read the row's effective expiresAt to return — single source of truth.
|
|
868
|
-
var
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
903
|
+
var rowBuilt = sql.select(_sessionSqlTable(), _sessionSqlOpts())
|
|
904
|
+
.columns(["expiresAt"])
|
|
905
|
+
.where("sidHash", newSidHash)
|
|
906
|
+
.toSql();
|
|
907
|
+
var row = await _currentStore().executeOne(rowBuilt.sql, rowBuilt.params);
|
|
872
908
|
var expiresAt = row ? Number(row.expiresAt) : null;
|
|
873
909
|
|
|
874
910
|
// Audit emit — best-effort. The framework's audit chain logs the
|
|
@@ -949,17 +985,18 @@ async function updateData(token, data, opts) {
|
|
|
949
985
|
// wins on the same sid, which is the right shape for cart-style
|
|
950
986
|
// writes; operators needing strict serialization wrap with
|
|
951
987
|
// b.resourceAccessLock.
|
|
952
|
-
var
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
988
|
+
var selBuilt = sql.select(_sessionSqlTable(), _sessionSqlOpts())
|
|
989
|
+
.columns(["userId", "userIdHash", "data", "createdAt", "expiresAt", "lastActivity"])
|
|
990
|
+
.where("sidHash", sidHash)
|
|
991
|
+
.where("expiresAt", ">=", nowMs)
|
|
992
|
+
.toSql();
|
|
993
|
+
var row = await _currentStore().executeOne(selBuilt.sql, selBuilt.params);
|
|
957
994
|
if (!row) return false;
|
|
958
995
|
|
|
959
996
|
// Recover the existing data + reserved fingerprint key (vault-
|
|
960
997
|
// sealed at rest). Operators that want a fresh fingerprint also
|
|
961
998
|
// call b.session.rotate; updateData preserves the binding.
|
|
962
|
-
var unsealed = cryptoField.unsealRow(
|
|
999
|
+
var unsealed = cryptoField.unsealRow(SESSION_TABLE, row);
|
|
963
1000
|
var existing = null;
|
|
964
1001
|
var storedFingerprint = null;
|
|
965
1002
|
if (unsealed.data) {
|
|
@@ -998,19 +1035,20 @@ async function updateData(token, data, opts) {
|
|
|
998
1035
|
|
|
999
1036
|
// Re-seal the data column. cryptoField.sealRow handles the AAD
|
|
1000
1037
|
// binding + sealedFields registration automatically.
|
|
1001
|
-
var sealedRow = cryptoField.sealRow(
|
|
1038
|
+
var sealedRow = cryptoField.sealRow(SESSION_TABLE, {
|
|
1002
1039
|
data: next ? JSON.stringify(next) : null,
|
|
1003
1040
|
});
|
|
1004
1041
|
|
|
1005
|
-
var
|
|
1006
|
-
var setParams = [sealedRow.data];
|
|
1042
|
+
var setCols = { data: sealedRow.data };
|
|
1007
1043
|
if (opts.touchLastActivity !== false) {
|
|
1008
|
-
|
|
1009
|
-
setParams.push(nowMs);
|
|
1044
|
+
setCols.lastActivity = nowMs;
|
|
1010
1045
|
}
|
|
1011
|
-
var
|
|
1012
|
-
|
|
1013
|
-
|
|
1046
|
+
var updBuilt = sql.update(_sessionSqlTable(), _sessionSqlOpts())
|
|
1047
|
+
.set(setCols)
|
|
1048
|
+
.where("sidHash", sidHash)
|
|
1049
|
+
.where("expiresAt", ">=", nowMs)
|
|
1050
|
+
.toSql();
|
|
1051
|
+
var result = await _currentStore().execute(updBuilt.sql, updBuilt.params);
|
|
1014
1052
|
return (result.rowCount || 0) > 0;
|
|
1015
1053
|
}
|
|
1016
1054
|
|
|
@@ -1040,10 +1078,10 @@ async function updateData(token, data, opts) {
|
|
|
1040
1078
|
*/
|
|
1041
1079
|
async function purgeExpired() {
|
|
1042
1080
|
cluster.requireLeader();
|
|
1043
|
-
var
|
|
1044
|
-
"
|
|
1045
|
-
|
|
1046
|
-
);
|
|
1081
|
+
var built = sql.delete(_sessionSqlTable(), _sessionSqlOpts())
|
|
1082
|
+
.where("expiresAt", "<", Date.now())
|
|
1083
|
+
.toSql();
|
|
1084
|
+
var result = await _currentStore().execute(built.sql, built.params);
|
|
1047
1085
|
return result.rowCount || 0;
|
|
1048
1086
|
}
|
|
1049
1087
|
|
|
@@ -1066,10 +1104,16 @@ async function purgeExpired() {
|
|
|
1066
1104
|
* // → 482
|
|
1067
1105
|
*/
|
|
1068
1106
|
async function count() {
|
|
1069
|
-
var
|
|
1070
|
-
"
|
|
1071
|
-
|
|
1072
|
-
|
|
1107
|
+
var built = sql.select(_sessionSqlTable(), _sessionSqlOpts())
|
|
1108
|
+
.count("*", "c")
|
|
1109
|
+
.where("expiresAt", ">=", Date.now())
|
|
1110
|
+
.toSql();
|
|
1111
|
+
var row = await _currentStore().executeOne(built.sql, built.params);
|
|
1112
|
+
// COUNT(*) aliased to `c` is not a framework-schema column, so
|
|
1113
|
+
// clusterStorage.coerceRows does not touch it; node-postgres / mysql2
|
|
1114
|
+
// hand a BIGINT count back as a decimal STRING. Number() at the read
|
|
1115
|
+
// boundary keeps single-node sqlite (native number) and the cluster
|
|
1116
|
+
// backends returning the same JS number.
|
|
1073
1117
|
return row ? Number(row.c) : 0;
|
|
1074
1118
|
}
|
|
1075
1119
|
|