@blamejs/core 0.14.26 → 0.15.0
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/agent-envelope-mac.js +104 -0
- package/lib/agent-event-bus.js +105 -4
- package/lib/agent-posture-chain.js +8 -42
- package/lib/ai-content-detect.js +9 -10
- package/lib/api-key.js +107 -74
- package/lib/atomic-file.js +62 -4
- 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 +249 -123
- package/lib/auth/openid-federation.js +108 -47
- 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 +169 -4
- package/lib/consent.js +73 -24
- package/lib/constants.js +16 -11
- package/lib/crypto-field.js +474 -92
- package/lib/db-declare-row-policy.js +35 -22
- package/lib/db-file-lifecycle.js +3 -2
- package/lib/db-query.js +497 -255
- package/lib/db-schema.js +209 -44
- package/lib/db.js +176 -95
- package/lib/error-page.js +14 -1
- package/lib/external-db-migrate.js +229 -139
- package/lib/external-db.js +25 -15
- package/lib/file-upload.js +52 -7
- package/lib/framework-error.js +14 -1
- package/lib/framework-files.js +73 -0
- package/lib/framework-schema.js +695 -394
- package/lib/gate-contract.js +649 -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 +37 -9
- 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-server-jmap.js +117 -12
- package/lib/mail-spam-score.js +2 -6
- package/lib/mail-store.js +287 -154
- package/lib/middleware/body-parser.js +71 -25
- package/lib/middleware/csrf-protect.js +19 -8
- 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 +57 -3
- package/lib/object-store/sigv4.js +10 -0
- package/lib/observability.js +87 -0
- package/lib/otel-export.js +25 -1
- package/lib/outbox.js +136 -82
- package/lib/parsers/safe-xml.js +47 -7
- 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/redact.js +68 -11
- package/lib/redis-client.js +160 -31
- package/lib/retention.js +82 -39
- package/lib/router.js +212 -5
- 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 +109 -72
- package/lib/sql.js +3885 -0
- package/lib/ssrf-guard.js +51 -4
- package/lib/static.js +177 -34
- package/lib/subject.js +55 -17
- 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 +35 -5
- 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,16 @@ 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
|
-
var
|
|
728
|
-
"
|
|
729
|
-
|
|
730
|
-
);
|
|
754
|
+
var built = sql.delete(_sessionSqlTable(), _sessionSqlOpts())
|
|
755
|
+
.where("userIdHash", lookup.value)
|
|
756
|
+
.toSql();
|
|
757
|
+
var result = await _currentStore().execute(built.sql, built.params);
|
|
731
758
|
return result.rowCount || 0;
|
|
732
759
|
}
|
|
733
760
|
|
|
@@ -776,18 +803,20 @@ async function touch(token, opts) {
|
|
|
776
803
|
if (opts.extendBy !== undefined && opts.extendBy !== null) {
|
|
777
804
|
_validateTtl(opts.extendBy, "session.touch");
|
|
778
805
|
var newExpires = nowMs + opts.extendBy;
|
|
779
|
-
var
|
|
780
|
-
|
|
781
|
-
"
|
|
782
|
-
|
|
783
|
-
|
|
806
|
+
var built = sql.update(_sessionSqlTable(), _sessionSqlOpts())
|
|
807
|
+
.set({ lastActivity: nowMs, expiresAt: newExpires })
|
|
808
|
+
.where("sidHash", sidHash)
|
|
809
|
+
.where("expiresAt", ">=", nowMs)
|
|
810
|
+
.toSql();
|
|
811
|
+
var result = await _currentStore().execute(built.sql, built.params);
|
|
784
812
|
return (result.rowCount || 0) > 0;
|
|
785
813
|
}
|
|
786
|
-
var
|
|
787
|
-
|
|
788
|
-
"
|
|
789
|
-
|
|
790
|
-
|
|
814
|
+
var built2 = sql.update(_sessionSqlTable(), _sessionSqlOpts())
|
|
815
|
+
.set({ lastActivity: nowMs })
|
|
816
|
+
.where("sidHash", sidHash)
|
|
817
|
+
.where("expiresAt", ">=", nowMs)
|
|
818
|
+
.toSql();
|
|
819
|
+
var result2 = await _currentStore().execute(built2.sql, built2.params);
|
|
791
820
|
return (result2.rowCount || 0) > 0;
|
|
792
821
|
}
|
|
793
822
|
|
|
@@ -844,31 +873,31 @@ async function rotate(oldToken, opts) {
|
|
|
844
873
|
newExpires = nowMs + opts.ttlMs;
|
|
845
874
|
}
|
|
846
875
|
|
|
847
|
-
var
|
|
848
|
-
var setParams = [newSidHash, nowMs];
|
|
876
|
+
var setCols = { sidHash: newSidHash, lastActivity: nowMs };
|
|
849
877
|
|
|
850
878
|
if (opts.data !== undefined) {
|
|
851
879
|
var dataJson = opts.data ? JSON.stringify(opts.data) : null;
|
|
852
|
-
var sealedRow = cryptoField.sealRow(
|
|
853
|
-
|
|
854
|
-
setParams.push(sealedRow.data);
|
|
880
|
+
var sealedRow = cryptoField.sealRow(SESSION_TABLE, { data: dataJson });
|
|
881
|
+
setCols.data = sealedRow.data;
|
|
855
882
|
}
|
|
856
883
|
if (newExpires !== null) {
|
|
857
|
-
|
|
858
|
-
setParams.push(newExpires);
|
|
884
|
+
setCols.expiresAt = newExpires;
|
|
859
885
|
}
|
|
860
886
|
|
|
861
|
-
var
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
887
|
+
var updBuilt = sql.update(_sessionSqlTable(), _sessionSqlOpts())
|
|
888
|
+
.set(setCols)
|
|
889
|
+
.where("sidHash", oldSidHash)
|
|
890
|
+
.where("expiresAt", ">=", nowMs)
|
|
891
|
+
.toSql();
|
|
892
|
+
var result = await _currentStore().execute(updBuilt.sql, updBuilt.params);
|
|
865
893
|
if ((result.rowCount || 0) === 0) return null;
|
|
866
894
|
|
|
867
895
|
// Read the row's effective expiresAt to return — single source of truth.
|
|
868
|
-
var
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
896
|
+
var rowBuilt = sql.select(_sessionSqlTable(), _sessionSqlOpts())
|
|
897
|
+
.columns(["expiresAt"])
|
|
898
|
+
.where("sidHash", newSidHash)
|
|
899
|
+
.toSql();
|
|
900
|
+
var row = await _currentStore().executeOne(rowBuilt.sql, rowBuilt.params);
|
|
872
901
|
var expiresAt = row ? Number(row.expiresAt) : null;
|
|
873
902
|
|
|
874
903
|
// Audit emit — best-effort. The framework's audit chain logs the
|
|
@@ -949,17 +978,18 @@ async function updateData(token, data, opts) {
|
|
|
949
978
|
// wins on the same sid, which is the right shape for cart-style
|
|
950
979
|
// writes; operators needing strict serialization wrap with
|
|
951
980
|
// b.resourceAccessLock.
|
|
952
|
-
var
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
981
|
+
var selBuilt = sql.select(_sessionSqlTable(), _sessionSqlOpts())
|
|
982
|
+
.columns(["userId", "userIdHash", "data", "createdAt", "expiresAt", "lastActivity"])
|
|
983
|
+
.where("sidHash", sidHash)
|
|
984
|
+
.where("expiresAt", ">=", nowMs)
|
|
985
|
+
.toSql();
|
|
986
|
+
var row = await _currentStore().executeOne(selBuilt.sql, selBuilt.params);
|
|
957
987
|
if (!row) return false;
|
|
958
988
|
|
|
959
989
|
// Recover the existing data + reserved fingerprint key (vault-
|
|
960
990
|
// sealed at rest). Operators that want a fresh fingerprint also
|
|
961
991
|
// call b.session.rotate; updateData preserves the binding.
|
|
962
|
-
var unsealed = cryptoField.unsealRow(
|
|
992
|
+
var unsealed = cryptoField.unsealRow(SESSION_TABLE, row);
|
|
963
993
|
var existing = null;
|
|
964
994
|
var storedFingerprint = null;
|
|
965
995
|
if (unsealed.data) {
|
|
@@ -998,19 +1028,20 @@ async function updateData(token, data, opts) {
|
|
|
998
1028
|
|
|
999
1029
|
// Re-seal the data column. cryptoField.sealRow handles the AAD
|
|
1000
1030
|
// binding + sealedFields registration automatically.
|
|
1001
|
-
var sealedRow = cryptoField.sealRow(
|
|
1031
|
+
var sealedRow = cryptoField.sealRow(SESSION_TABLE, {
|
|
1002
1032
|
data: next ? JSON.stringify(next) : null,
|
|
1003
1033
|
});
|
|
1004
1034
|
|
|
1005
|
-
var
|
|
1006
|
-
var setParams = [sealedRow.data];
|
|
1035
|
+
var setCols = { data: sealedRow.data };
|
|
1007
1036
|
if (opts.touchLastActivity !== false) {
|
|
1008
|
-
|
|
1009
|
-
setParams.push(nowMs);
|
|
1037
|
+
setCols.lastActivity = nowMs;
|
|
1010
1038
|
}
|
|
1011
|
-
var
|
|
1012
|
-
|
|
1013
|
-
|
|
1039
|
+
var updBuilt = sql.update(_sessionSqlTable(), _sessionSqlOpts())
|
|
1040
|
+
.set(setCols)
|
|
1041
|
+
.where("sidHash", sidHash)
|
|
1042
|
+
.where("expiresAt", ">=", nowMs)
|
|
1043
|
+
.toSql();
|
|
1044
|
+
var result = await _currentStore().execute(updBuilt.sql, updBuilt.params);
|
|
1014
1045
|
return (result.rowCount || 0) > 0;
|
|
1015
1046
|
}
|
|
1016
1047
|
|
|
@@ -1040,10 +1071,10 @@ async function updateData(token, data, opts) {
|
|
|
1040
1071
|
*/
|
|
1041
1072
|
async function purgeExpired() {
|
|
1042
1073
|
cluster.requireLeader();
|
|
1043
|
-
var
|
|
1044
|
-
"
|
|
1045
|
-
|
|
1046
|
-
);
|
|
1074
|
+
var built = sql.delete(_sessionSqlTable(), _sessionSqlOpts())
|
|
1075
|
+
.where("expiresAt", "<", Date.now())
|
|
1076
|
+
.toSql();
|
|
1077
|
+
var result = await _currentStore().execute(built.sql, built.params);
|
|
1047
1078
|
return result.rowCount || 0;
|
|
1048
1079
|
}
|
|
1049
1080
|
|
|
@@ -1066,10 +1097,16 @@ async function purgeExpired() {
|
|
|
1066
1097
|
* // → 482
|
|
1067
1098
|
*/
|
|
1068
1099
|
async function count() {
|
|
1069
|
-
var
|
|
1070
|
-
"
|
|
1071
|
-
|
|
1072
|
-
|
|
1100
|
+
var built = sql.select(_sessionSqlTable(), _sessionSqlOpts())
|
|
1101
|
+
.count("*", "c")
|
|
1102
|
+
.where("expiresAt", ">=", Date.now())
|
|
1103
|
+
.toSql();
|
|
1104
|
+
var row = await _currentStore().executeOne(built.sql, built.params);
|
|
1105
|
+
// COUNT(*) aliased to `c` is not a framework-schema column, so
|
|
1106
|
+
// clusterStorage.coerceRows does not touch it; node-postgres / mysql2
|
|
1107
|
+
// hand a BIGINT count back as a decimal STRING. Number() at the read
|
|
1108
|
+
// boundary keeps single-node sqlite (native number) and the cluster
|
|
1109
|
+
// backends returning the same JS number.
|
|
1073
1110
|
return row ? Number(row.c) : 0;
|
|
1074
1111
|
}
|
|
1075
1112
|
|