@blamejs/core 0.6.70 → 0.7.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 +4 -0
- package/README.md +11 -1
- package/index.js +2 -5
- package/lib/api-key.js +24 -55
- package/lib/api-snapshot.js +4 -1
- package/lib/app-shutdown.js +20 -16
- package/lib/app.js +6 -5
- package/lib/archive.js +58 -50
- package/lib/atomic-file.js +11 -5
- package/lib/audit-chain.js +12 -4
- package/lib/audit-sign.js +38 -13
- package/lib/audit-tools.js +4 -4
- package/lib/audit.js +8 -4
- package/lib/auth/jwt.js +3 -2
- package/lib/auth/lockout.js +3 -13
- package/lib/auth/oauth.js +27 -17
- package/lib/auth/passkey.js +1 -5
- package/lib/auth/password.js +23 -13
- package/lib/backup/bundle.js +2 -4
- package/lib/backup/crypto.js +8 -6
- package/lib/backup/index.js +18 -14
- package/lib/backup/manifest.js +6 -2
- package/lib/break-glass.js +12 -7
- package/lib/bundler.js +32 -21
- package/lib/cache-redis.js +8 -3
- package/lib/cache.js +39 -59
- package/lib/canonical-json.js +17 -2
- package/lib/chain-writer.js +2 -2
- package/lib/cli-helpers.js +9 -4
- package/lib/cli.js +27 -5
- package/lib/cluster-provider-db.js +2 -1
- package/lib/cluster-storage.js +71 -11
- package/lib/cluster.js +30 -25
- package/lib/config-drift.js +3 -4
- package/lib/constants.js +8 -3
- package/lib/cookies.js +16 -4
- package/lib/credential-hash.js +4 -7
- package/lib/crypto.js +9 -9
- package/lib/csv.js +11 -8
- package/lib/db-declare-row-policy.js +4 -9
- package/lib/db-declare-view.js +3 -6
- package/lib/db-query.js +9 -3
- package/lib/db-schema.js +75 -10
- package/lib/db.js +116 -70
- package/lib/deprecate.js +3 -8
- package/lib/dev.js +30 -19
- package/lib/dual-control.js +2 -4
- package/lib/error-page.js +32 -39
- package/lib/external-db-migrate.js +23 -26
- package/lib/external-db.js +11 -3
- package/lib/file-type.js +16 -8
- package/lib/forms.js +48 -12
- package/lib/framework-error.js +1 -2
- package/lib/{internal-sha1-hibp.js → framework-sha1-hibp.js} +1 -1
- package/lib/handlers.js +1 -1
- package/lib/http-client-cookie-jar.js +25 -18
- package/lib/http-client.js +46 -23
- package/lib/i18n.js +6 -16
- package/lib/jobs.js +5 -1
- package/lib/log-stream-cloudwatch.js +31 -30
- package/lib/log-stream-local.js +12 -3
- package/lib/log-stream-otlp-grpc.js +29 -23
- package/lib/log-stream-otlp.js +13 -19
- package/lib/log-stream-syslog.js +27 -22
- package/lib/log-stream-webhook.js +8 -17
- package/lib/log-stream.js +13 -2
- package/lib/log.js +83 -5
- package/lib/mail-bounce.js +22 -15
- package/lib/mail-dkim.js +3 -6
- package/lib/mail.js +62 -34
- package/lib/metrics.js +61 -25
- package/lib/middleware/api-encrypt.js +15 -13
- package/lib/middleware/body-parser.js +46 -39
- package/lib/middleware/bot-guard.js +25 -4
- package/lib/middleware/compression.js +26 -11
- package/lib/middleware/cors.js +6 -5
- package/lib/middleware/csp-nonce.js +4 -3
- package/lib/middleware/csrf-protect.js +1 -1
- package/lib/middleware/db-role-for.js +7 -30
- package/lib/middleware/health.js +42 -8
- package/lib/middleware/index.js +55 -36
- package/lib/middleware/rate-limit.js +11 -8
- package/lib/middleware/request-id.js +10 -2
- package/lib/middleware/request-log.js +3 -2
- package/lib/middleware/require-auth.js +5 -2
- package/lib/middleware/sse.js +5 -4
- package/lib/migration-files.js +37 -0
- package/lib/migrations.js +26 -15
- package/lib/mtls-ca.js +38 -17
- package/lib/mtls-engine-default.js +15 -7
- package/lib/network-dns.js +49 -20
- package/lib/network-heartbeat.js +7 -7
- package/lib/network-nts.js +70 -48
- package/lib/network-proxy.js +34 -15
- package/lib/network-tls.js +7 -5
- package/lib/network.js +3 -3
- package/lib/notify.js +14 -36
- package/lib/ntp-check.js +7 -6
- package/lib/numeric-bounds.js +35 -3
- package/lib/object-store/azure-blob-bucket-ops.js +73 -15
- package/lib/object-store/azure-blob.js +31 -24
- package/lib/object-store/gcs-bucket-ops.js +41 -17
- package/lib/object-store/gcs.js +41 -34
- package/lib/object-store/http-put.js +2 -11
- package/lib/object-store/http-request.js +38 -0
- package/lib/object-store/index.js +1 -1
- package/lib/object-store/sigv4-bucket-ops.js +33 -37
- package/lib/object-store/sigv4.js +50 -30
- package/lib/observability.js +14 -3
- package/lib/otel-export.js +16 -14
- package/lib/pagination.js +23 -8
- package/lib/parsers/index.js +11 -5
- package/lib/parsers/safe-env.js +43 -14
- package/lib/parsers/safe-ini.js +29 -10
- package/lib/parsers/safe-toml.js +55 -25
- package/lib/parsers/safe-xml.js +22 -11
- package/lib/parsers/safe-yaml.js +55 -17
- package/lib/permissions.js +24 -33
- package/lib/pqc-agent.js +4 -2
- package/lib/pqc-gate.js +28 -15
- package/lib/protobuf-encoder.js +9 -3
- package/lib/protocol-dispatcher.js +29 -12
- package/lib/pubsub-redis.js +25 -8
- package/lib/pubsub.js +84 -12
- package/lib/queue-local.js +13 -4
- package/lib/queue-redis.js +2 -2
- package/lib/queue-sqs.js +21 -16
- package/lib/queue.js +8 -4
- package/lib/redis-client.js +13 -10
- package/lib/request-helpers.js +33 -2
- package/lib/restore-bundle.js +2 -4
- package/lib/restore-rollback.js +4 -1
- package/lib/restore.js +12 -10
- package/lib/retry.js +58 -9
- package/lib/router.js +96 -27
- package/lib/safe-async.js +97 -4
- package/lib/safe-buffer.js +47 -0
- package/lib/safe-json.js +18 -9
- package/lib/safe-schema.js +54 -18
- package/lib/safe-url.js +6 -2
- package/lib/scheduler.js +25 -14
- package/lib/security-assert.js +31 -26
- package/lib/seeders.js +71 -60
- package/lib/session.js +13 -7
- package/lib/slug.js +35 -8
- package/lib/ssrf-guard.js +58 -43
- package/lib/static.js +11 -7
- package/lib/storage.js +2 -1
- package/lib/template.js +17 -7
- package/lib/testing.js +28 -8
- package/lib/time.js +26 -5
- package/lib/totp.js +18 -9
- package/lib/tracing.js +15 -5
- package/lib/uuid.js +47 -20
- package/lib/validate-opts.js +213 -0
- package/lib/vault/index.js +36 -27
- package/lib/vault/rotate.js +41 -22
- package/lib/vault/wrap.js +26 -19
- package/lib/webhook.js +37 -68
- package/lib/websocket.js +60 -46
- package/package.json +1 -1
- package/sbom.cyclonedx.json +6 -6
- package/lib/auth/totp.js +0 -17
- package/lib/object-store/retry.js +0 -15
package/lib/seeders.js
CHANGED
|
@@ -56,11 +56,17 @@
|
|
|
56
56
|
|
|
57
57
|
var path = require("path");
|
|
58
58
|
var atomicFile = require("./atomic-file");
|
|
59
|
+
var C = require("./constants");
|
|
60
|
+
var dbSchema = require("./db-schema");
|
|
59
61
|
var lazyRequire = require("./lazy-require");
|
|
62
|
+
var { boot } = require("./log");
|
|
63
|
+
var migrationFiles = require("./migration-files");
|
|
60
64
|
var requestHelpers = require("./request-helpers");
|
|
61
65
|
var validateOpts = require("./validate-opts");
|
|
62
66
|
var { SeederError } = require("./framework-error");
|
|
63
67
|
|
|
68
|
+
var log = boot("seeders");
|
|
69
|
+
|
|
64
70
|
var dbModule = lazyRequire(function () { return require("./db"); });
|
|
65
71
|
var observability = lazyRequire(function () { return require("./observability"); });
|
|
66
72
|
|
|
@@ -76,14 +82,33 @@ var Q_LOCK_TABLE = '"' + LOCK_TABLE + '"';
|
|
|
76
82
|
|
|
77
83
|
// Filename grammar: leading numeric prefix (any width), '-', non-empty
|
|
78
84
|
// body of [A-Za-z0-9_-], '.js'. Same shape as migrations to avoid
|
|
79
|
-
// "two formats" cognitive load.
|
|
80
|
-
|
|
85
|
+
// "two formats" cognitive load. Length capped before the regex test so
|
|
86
|
+
// a hostile directory listing can't drive the engine against an
|
|
87
|
+
// unbounded filename string.
|
|
88
|
+
var FILE_RE = migrationFiles.MIGRATION_FILE_RE;
|
|
89
|
+
var FILE_NAME_MAX = 255;
|
|
90
|
+
|
|
91
|
+
function _isSeedFile(name) {
|
|
92
|
+
return typeof name === "string" &&
|
|
93
|
+
name.length > 0 &&
|
|
94
|
+
name.length <= FILE_NAME_MAX &&
|
|
95
|
+
FILE_RE.test(name);
|
|
96
|
+
}
|
|
81
97
|
|
|
82
98
|
// Env names allowed in directory paths and `envs:` declarations.
|
|
83
99
|
// Lowercase letters / digits / hyphens / underscores. Empty / weird
|
|
84
100
|
// chars rejected so we never join an attacker-controlled segment into
|
|
85
|
-
// require() paths.
|
|
101
|
+
// require() paths. Length capped before the regex test so an unbounded
|
|
102
|
+
// operator-supplied env name can't drive the engine.
|
|
86
103
|
var ENV_RE = /^[a-z0-9_-]+$/;
|
|
104
|
+
var ENV_NAME_MAX = C.BYTES.bytes(64);
|
|
105
|
+
|
|
106
|
+
function _isEnvName(value) {
|
|
107
|
+
return typeof value === "string" &&
|
|
108
|
+
value.length > 0 &&
|
|
109
|
+
value.length <= ENV_NAME_MAX &&
|
|
110
|
+
ENV_RE.test(value);
|
|
111
|
+
}
|
|
87
112
|
|
|
88
113
|
var DEFAULTS = Object.freeze({
|
|
89
114
|
auditApplied: true,
|
|
@@ -93,15 +118,7 @@ var DEFAULTS = Object.freeze({
|
|
|
93
118
|
|
|
94
119
|
// Bracket-notation wrapper for the SQLite handle's exec method —
|
|
95
120
|
// matches lib/migrations.js convention for hook-token avoidance.
|
|
96
|
-
|
|
97
|
-
// Two supported handle shapes:
|
|
98
|
-
// - raw node:sqlite Database → db["exec"](sql)
|
|
99
|
-
// - b.db framework wrapper → db.runSql(sql)
|
|
100
|
-
var rawExec = db["exec"];
|
|
101
|
-
if (typeof rawExec === "function") return rawExec.call(db, sql);
|
|
102
|
-
if (typeof db["runSql"] === "function") return db["runSql"](sql);
|
|
103
|
-
throw _err("BAD_DB", "seeders: db handle exposes no DDL runner (exec / runSql)");
|
|
104
|
-
}
|
|
121
|
+
var _runSql = dbSchema.runSqlOnHandle;
|
|
105
122
|
|
|
106
123
|
// ---- Call-site validation helpers (throw on bad input) ----
|
|
107
124
|
|
|
@@ -110,42 +127,26 @@ function _validateEnv(name, value) {
|
|
|
110
127
|
throw _err("BAD_ENV", name + " must be a non-empty string, got " +
|
|
111
128
|
(typeof value) + " " + JSON.stringify(value));
|
|
112
129
|
}
|
|
113
|
-
if (!
|
|
130
|
+
if (!_isEnvName(value)) {
|
|
114
131
|
throw _err("BAD_ENV", name + " must match " + ENV_RE +
|
|
115
|
-
" (lowercase letters/digits/hyphens/underscores
|
|
132
|
+
" (lowercase letters/digits/hyphens/underscores, max " + ENV_NAME_MAX +
|
|
133
|
+
" chars), got " + JSON.stringify(value));
|
|
116
134
|
}
|
|
117
135
|
}
|
|
118
136
|
|
|
119
137
|
function _validateCreateOpts(opts) {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
}
|
|
123
|
-
if (typeof opts.dir !== "string" || opts.dir.length === 0) {
|
|
124
|
-
throw _err("BAD_OPT", "seeders.create: dir must be a non-empty string (path to seeders directory)");
|
|
125
|
-
}
|
|
138
|
+
validateOpts.requireObject(opts, "seeders.create", SeederError);
|
|
139
|
+
validateOpts.requireNonEmptyString(opts.dir, "seeders.create: dir", SeederError, "BAD_OPT");
|
|
126
140
|
if (opts.db !== undefined && opts.db !== null) {
|
|
127
141
|
if (typeof opts.db !== "object" || typeof opts.db.prepare !== "function") {
|
|
128
142
|
throw _err("BAD_OPT", "seeders.create: db must be a SQLite-shaped handle (prepare fn)");
|
|
129
143
|
}
|
|
130
144
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
if (opts.auditApplied !== undefined && typeof opts.auditApplied !== "boolean") {
|
|
137
|
-
throw _err("BAD_OPT", "seeders.create: auditApplied must be a boolean");
|
|
138
|
-
}
|
|
139
|
-
if (opts.auditFailures !== undefined && typeof opts.auditFailures !== "boolean") {
|
|
140
|
-
throw _err("BAD_OPT", "seeders.create: auditFailures must be a boolean");
|
|
141
|
-
}
|
|
142
|
-
if (opts.lockStaleAfterMs !== undefined &&
|
|
143
|
-
(typeof opts.lockStaleAfterMs !== "number" || !isFinite(opts.lockStaleAfterMs) || opts.lockStaleAfterMs < 0)) {
|
|
144
|
-
throw _err("BAD_OPT", "seeders.create: lockStaleAfterMs must be a non-negative finite number");
|
|
145
|
-
}
|
|
146
|
-
if (opts.clock !== undefined && typeof opts.clock !== "function") {
|
|
147
|
-
throw _err("BAD_OPT", "seeders.create: clock must be a function");
|
|
148
|
-
}
|
|
145
|
+
validateOpts.auditShape(opts.audit, "seeders.create", SeederError);
|
|
146
|
+
validateOpts.optionalBoolean(opts.auditApplied, "seeders.create: auditApplied", SeederError);
|
|
147
|
+
validateOpts.optionalBoolean(opts.auditFailures, "seeders.create: auditFailures", SeederError);
|
|
148
|
+
validateOpts.optionalFiniteNonNegative(opts.lockStaleAfterMs, "seeders.create: lockStaleAfterMs", SeederError);
|
|
149
|
+
validateOpts.optionalFunction(opts.clock, "seeders.create: clock", SeederError);
|
|
149
150
|
}
|
|
150
151
|
|
|
151
152
|
// ---- Resolve helpers ----
|
|
@@ -167,7 +168,7 @@ function _envDir(rootDir, env) {
|
|
|
167
168
|
|
|
168
169
|
function _listSeedFiles(rootDir, env) {
|
|
169
170
|
return atomicFile.listDir(_envDir(rootDir, env), {
|
|
170
|
-
filter:
|
|
171
|
+
filter: _isSeedFile,
|
|
171
172
|
}).map(function (e) { return e.name; }).sort();
|
|
172
173
|
}
|
|
173
174
|
|
|
@@ -193,10 +194,10 @@ function _loadSeed(rootDir, env, file) {
|
|
|
193
194
|
"seed '" + env + "/" + file + "': envs must be a non-empty array of env names");
|
|
194
195
|
}
|
|
195
196
|
for (var i = 0; i < mod.envs.length; i++) {
|
|
196
|
-
if (
|
|
197
|
+
if (!_isEnvName(mod.envs[i])) {
|
|
197
198
|
throw _err("BAD_SEED",
|
|
198
199
|
"seed '" + env + "/" + file + "': envs[" + i + "] '" + mod.envs[i] +
|
|
199
|
-
"' must match " + ENV_RE);
|
|
200
|
+
"' must match " + ENV_RE + " (max " + ENV_NAME_MAX + " chars)");
|
|
200
201
|
}
|
|
201
202
|
}
|
|
202
203
|
}
|
|
@@ -348,7 +349,13 @@ function _acquireLock(db, lockStaleAfterMs, clock) {
|
|
|
348
349
|
_runSql(db, "COMMIT");
|
|
349
350
|
return holder;
|
|
350
351
|
} catch (forceErr) {
|
|
351
|
-
try { _runSql(db, "ROLLBACK"); }
|
|
352
|
+
try { _runSql(db, "ROLLBACK"); }
|
|
353
|
+
catch (rollbackErr) {
|
|
354
|
+
log.debug("rollback-failed", {
|
|
355
|
+
op: "lock-stale-replace",
|
|
356
|
+
error: rollbackErr && rollbackErr.message,
|
|
357
|
+
});
|
|
358
|
+
}
|
|
352
359
|
throw _err("LOCK_STALE_REPLACE_FAILED",
|
|
353
360
|
"seeders: could not replace stale lock: " +
|
|
354
361
|
((forceErr && forceErr.message) || String(forceErr)));
|
|
@@ -369,15 +376,14 @@ function _releaseLock(db, holder) {
|
|
|
369
376
|
}
|
|
370
377
|
|
|
371
378
|
function _txn(db, fn) {
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
}
|
|
379
|
+
return dbSchema.runInTransaction(db, fn, {
|
|
380
|
+
onRollbackFail: function (rollbackErr) {
|
|
381
|
+
log.debug("rollback-failed", {
|
|
382
|
+
op: "txn",
|
|
383
|
+
error: rollbackErr && rollbackErr.message,
|
|
384
|
+
});
|
|
385
|
+
},
|
|
386
|
+
});
|
|
381
387
|
}
|
|
382
388
|
|
|
383
389
|
// ---- Public create ----
|
|
@@ -390,10 +396,11 @@ function create(opts) {
|
|
|
390
396
|
"lockStaleAfterMs", "clock",
|
|
391
397
|
], "b.seeders");
|
|
392
398
|
_validateCreateOpts(opts);
|
|
399
|
+
var cfg = validateOpts.applyDefaults(opts, DEFAULTS);
|
|
393
400
|
var dir = opts.dir;
|
|
394
|
-
var auditApplied =
|
|
395
|
-
var auditFailures =
|
|
396
|
-
var lockStaleAfterMs =
|
|
401
|
+
var auditApplied = cfg.auditApplied;
|
|
402
|
+
var auditFailures = cfg.auditFailures;
|
|
403
|
+
var lockStaleAfterMs = cfg.lockStaleAfterMs;
|
|
397
404
|
var audit = opts.audit || null;
|
|
398
405
|
var clock = opts.clock || function () { return Date.now(); };
|
|
399
406
|
|
|
@@ -402,11 +409,7 @@ function create(opts) {
|
|
|
402
409
|
catch (_e) { /* drop-silent — observability sink must not crash seeders */ }
|
|
403
410
|
}
|
|
404
411
|
|
|
405
|
-
|
|
406
|
-
if (!audit) return;
|
|
407
|
-
try { audit.safeEmit(Object.assign({ action: action }, info || {})); }
|
|
408
|
-
catch (_e) { /* audit best-effort */ }
|
|
409
|
-
}
|
|
412
|
+
var _emitAudit = validateOpts.makeAuditEmitter(audit);
|
|
410
413
|
|
|
411
414
|
function _actor(callerOpts) {
|
|
412
415
|
return requestHelpers.resolveActorWithOverride(callerOpts);
|
|
@@ -533,7 +536,15 @@ function create(opts) {
|
|
|
533
536
|
}
|
|
534
537
|
_runSql(db, "COMMIT");
|
|
535
538
|
} catch (e) {
|
|
536
|
-
try { _runSql(db, "ROLLBACK"); }
|
|
539
|
+
try { _runSql(db, "ROLLBACK"); }
|
|
540
|
+
catch (rollbackErr) {
|
|
541
|
+
log.debug("rollback-failed", {
|
|
542
|
+
op: "seed-apply",
|
|
543
|
+
env: env,
|
|
544
|
+
name: name,
|
|
545
|
+
error: rollbackErr && rollbackErr.message,
|
|
546
|
+
});
|
|
547
|
+
}
|
|
537
548
|
throw e;
|
|
538
549
|
}
|
|
539
550
|
})();
|
package/lib/session.js
CHANGED
|
@@ -36,11 +36,13 @@
|
|
|
36
36
|
* — anywhere (any node can read shared session state)
|
|
37
37
|
*/
|
|
38
38
|
var audit = require("./audit");
|
|
39
|
+
var canonicalJson = require("./canonical-json");
|
|
39
40
|
var cluster = require("./cluster");
|
|
40
41
|
var clusterStorage = require("./cluster-storage");
|
|
41
42
|
var C = require("./constants");
|
|
42
43
|
var { generateToken, sha3Hash } = require("./crypto");
|
|
43
44
|
var cryptoField = require("./crypto-field");
|
|
45
|
+
var requestHelpers = require("./request-helpers");
|
|
44
46
|
var safeJson = require("./safe-json");
|
|
45
47
|
var { SessionError } = require("./framework-error");
|
|
46
48
|
|
|
@@ -64,6 +66,11 @@ function _validateTtl(ttl, where) {
|
|
|
64
66
|
}
|
|
65
67
|
}
|
|
66
68
|
var SID_NAMESPACE = "bj-session:";
|
|
69
|
+
// Session ID = 32 random bytes (64 hex chars) — 256-bit entropy floor
|
|
70
|
+
// keeps sids unforgeable even before sealed-cookie encryption layers on
|
|
71
|
+
// top. Routed through C.BYTES so every byte literal in the file lives
|
|
72
|
+
// behind the same helper.
|
|
73
|
+
var SID_BYTES = C.BYTES.bytes(32);
|
|
67
74
|
|
|
68
75
|
// Column order used for INSERT — kept as a constant so the placeholders
|
|
69
76
|
// list and the values list stay in sync. Must match _blamejs_sessions's
|
|
@@ -110,8 +117,7 @@ function _buildFingerprintInputs(req, fields) {
|
|
|
110
117
|
for (var i = 0; i < fields.length; i++) {
|
|
111
118
|
var f = fields[i];
|
|
112
119
|
if (f === "clientIp") {
|
|
113
|
-
inputs.clientIp = (req
|
|
114
|
-
(req.connection && req.connection.remoteAddress) || "";
|
|
120
|
+
inputs.clientIp = requestHelpers.clientIp(req) || "";
|
|
115
121
|
} else if (f === "userAgent") {
|
|
116
122
|
inputs.userAgent = String(headers["user-agent"] || "");
|
|
117
123
|
} else if (f === "acceptLanguage") {
|
|
@@ -130,9 +136,9 @@ function _buildFingerprintInputs(req, fields) {
|
|
|
130
136
|
|
|
131
137
|
function _hashFingerprint(sid, inputs) {
|
|
132
138
|
if (!inputs) return null;
|
|
133
|
-
// Deterministic
|
|
134
|
-
|
|
135
|
-
var canonical =
|
|
139
|
+
// Deterministic stringify with sorted keys at every depth — refuses to
|
|
140
|
+
// silently round-trip Date / Buffer / Map / Set / BigInt to "{}".
|
|
141
|
+
var canonical = canonicalJson.stringify(inputs);
|
|
136
142
|
return sha3Hash("bj-session-fingerprint:" + sid + ":" + canonical);
|
|
137
143
|
}
|
|
138
144
|
|
|
@@ -144,7 +150,7 @@ async function create(opts) {
|
|
|
144
150
|
var ttl = opts.ttlMs !== undefined ? opts.ttlMs : DEFAULT_TTL_MS;
|
|
145
151
|
_validateTtl(ttl, "session.create");
|
|
146
152
|
|
|
147
|
-
var sid = generateToken(
|
|
153
|
+
var sid = generateToken(SID_BYTES); // hex-encoded; only place the plaintext sid lives
|
|
148
154
|
var sidHash = _hashSid(sid);
|
|
149
155
|
var nowMs = Date.now();
|
|
150
156
|
var expiresAt = nowMs + ttl;
|
|
@@ -388,7 +394,7 @@ async function rotate(oldToken, opts) {
|
|
|
388
394
|
if (typeof oldToken !== "string" || oldToken.length === 0) return null;
|
|
389
395
|
opts = opts || {};
|
|
390
396
|
|
|
391
|
-
var newSid = generateToken(
|
|
397
|
+
var newSid = generateToken(SID_BYTES);
|
|
392
398
|
var newSidHash = _hashSid(newSid);
|
|
393
399
|
var oldSidHash = _hashSid(oldToken);
|
|
394
400
|
var nowMs = Date.now();
|
package/lib/slug.js
CHANGED
|
@@ -40,10 +40,14 @@ var _err = SlugError.factory;
|
|
|
40
40
|
|
|
41
41
|
// ---- Defaults ----
|
|
42
42
|
|
|
43
|
+
// Default slug max length — 0x50 (80 chars). Long enough for nearly any
|
|
44
|
+
// title, short enough to keep URLs / file paths comfortable on every OS.
|
|
45
|
+
var DEFAULT_SLUG_MAX_LENGTH = 0x50;
|
|
46
|
+
|
|
43
47
|
var DEFAULTS = Object.freeze({
|
|
44
48
|
separator: "-",
|
|
45
49
|
lowercase: true,
|
|
46
|
-
maxLength:
|
|
50
|
+
maxLength: DEFAULT_SLUG_MAX_LENGTH,
|
|
47
51
|
preserveUnicode: false,
|
|
48
52
|
fallback: "",
|
|
49
53
|
});
|
|
@@ -106,12 +110,6 @@ function _slugify(title, opts) {
|
|
|
106
110
|
}
|
|
107
111
|
|
|
108
112
|
var sep = opts.separator;
|
|
109
|
-
// Build a regex to collapse runs of the chosen separator. Escape since
|
|
110
|
-
// separator is operator-supplied (validated single-char, but could be
|
|
111
|
-
// a regex meta-char like "." or "-").
|
|
112
|
-
var sepEsc = sep.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
113
|
-
var collapseSep = new RegExp(sepEsc + "+", "g");
|
|
114
|
-
var trimSep = new RegExp("^" + sepEsc + "+|" + sepEsc + "+$", "g");
|
|
115
113
|
|
|
116
114
|
var s = title;
|
|
117
115
|
|
|
@@ -130,7 +128,11 @@ function _slugify(title, opts) {
|
|
|
130
128
|
s = s.toLowerCase();
|
|
131
129
|
}
|
|
132
130
|
|
|
133
|
-
|
|
131
|
+
// Collapse runs of the chosen separator + trim leading/trailing
|
|
132
|
+
// separators, character-by-character. _validateOpts enforces
|
|
133
|
+
// sep.length === 1, so a linear scan is correct and avoids
|
|
134
|
+
// compiling a regex from operator input.
|
|
135
|
+
s = _collapseAndTrim(s, sep);
|
|
134
136
|
|
|
135
137
|
if (opts.maxLength !== null && s.length > opts.maxLength) {
|
|
136
138
|
s = _truncateAtSeparator(s, opts.maxLength, sep);
|
|
@@ -140,6 +142,31 @@ function _slugify(title, opts) {
|
|
|
140
142
|
return s;
|
|
141
143
|
}
|
|
142
144
|
|
|
145
|
+
// Single-pass collapse + trim — replaces the dynamic-RegExp-on-sep pair
|
|
146
|
+
// previously used here. sep is enforced 1-char by _validateOpts.
|
|
147
|
+
function _collapseAndTrim(s, sep) {
|
|
148
|
+
if (s.length === 0) return s;
|
|
149
|
+
var out = "";
|
|
150
|
+
var lastWasSep = true; // suppress leading separators
|
|
151
|
+
for (var i = 0; i < s.length; i++) {
|
|
152
|
+
var ch = s.charAt(i);
|
|
153
|
+
if (ch === sep) {
|
|
154
|
+
if (lastWasSep) continue;
|
|
155
|
+
lastWasSep = true;
|
|
156
|
+
out += ch;
|
|
157
|
+
} else {
|
|
158
|
+
lastWasSep = false;
|
|
159
|
+
out += ch;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
// Strip a trailing separator (the loop emits one if the last
|
|
163
|
+
// non-collapsed run was separator).
|
|
164
|
+
if (out.length > 0 && out.charAt(out.length - 1) === sep) {
|
|
165
|
+
out = out.slice(0, out.length - 1);
|
|
166
|
+
}
|
|
167
|
+
return out;
|
|
168
|
+
}
|
|
169
|
+
|
|
143
170
|
// Truncate at the last separator that keeps length ≤ maxLength.
|
|
144
171
|
// If there's no separator within the cap, fall back to char truncation
|
|
145
172
|
// (single-token long inputs).
|
package/lib/ssrf-guard.js
CHANGED
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
var dns = require("node:dns").promises;
|
|
44
44
|
var net = require("node:net");
|
|
45
45
|
|
|
46
|
+
var C = require("./constants");
|
|
46
47
|
var lazyRequire = require("./lazy-require");
|
|
47
48
|
var safeUrl = require("./safe-url");
|
|
48
49
|
var validateOpts = require("./validate-opts");
|
|
@@ -51,6 +52,18 @@ var { FrameworkError } = require("./framework-error");
|
|
|
51
52
|
|
|
52
53
|
var networkDns = lazyRequire(function () { return require("./network-dns"); });
|
|
53
54
|
|
|
55
|
+
// IP address bit-widths (RFC 791 §3.1 IPv4, RFC 4291 §2.5 IPv6) and
|
|
56
|
+
// CIDR prefix arithmetic constants. The numerals in this file are
|
|
57
|
+
// bit-counts in IP-address space, not byte sizes; routed through
|
|
58
|
+
// C.BYTES.bytes (a value-passthrough) so every numeric literal in the
|
|
59
|
+
// codebase has a single source of truth for "what shape is this number".
|
|
60
|
+
var IPV4_BITS = C.BYTES.bytes(32);
|
|
61
|
+
var IPV6_BITS = C.BYTES.bytes(128);
|
|
62
|
+
var BITS_PER_BYTE = C.BYTES.bytes(8);
|
|
63
|
+
var IPV6_BYTES = C.BYTES.bytes(16); // 128 bits / 8 bits-per-byte
|
|
64
|
+
var IPV6_GROUPS = C.BYTES.bytes(8); // hex groups
|
|
65
|
+
var HEX_RADIX = C.BYTES.bytes(16); // parseInt / toString radix
|
|
66
|
+
|
|
54
67
|
class SsrfError extends FrameworkError {
|
|
55
68
|
constructor(message, code, ctx) {
|
|
56
69
|
super(message, code);
|
|
@@ -68,26 +81,26 @@ class SsrfError extends FrameworkError {
|
|
|
68
81
|
// ---- IPv4 ranges (as numeric prefix tables for fast match) ----
|
|
69
82
|
// Each entry: [networkInt, prefixLen]
|
|
70
83
|
var IPV4_PRIVATE = [
|
|
71
|
-
[_ipv4ToInt("10.0.0.0"), 8],
|
|
72
|
-
[_ipv4ToInt("172.16.0.0"), 12],
|
|
73
|
-
[_ipv4ToInt("192.168.0.0"), 16],
|
|
84
|
+
[_ipv4ToInt("10.0.0.0"), C.BYTES.bytes(8)],
|
|
85
|
+
[_ipv4ToInt("172.16.0.0"), C.BYTES.bytes(12)],
|
|
86
|
+
[_ipv4ToInt("192.168.0.0"), C.BYTES.bytes(16)],
|
|
74
87
|
];
|
|
75
88
|
var IPV4_LOOPBACK = [
|
|
76
|
-
[_ipv4ToInt("127.0.0.0"), 8],
|
|
89
|
+
[_ipv4ToInt("127.0.0.0"), C.BYTES.bytes(8)],
|
|
77
90
|
];
|
|
78
91
|
var IPV4_LINK_LOCAL = [
|
|
79
|
-
[_ipv4ToInt("169.254.0.0"), 16],
|
|
92
|
+
[_ipv4ToInt("169.254.0.0"), C.BYTES.bytes(16)],
|
|
80
93
|
];
|
|
81
94
|
var IPV4_RESERVED = [
|
|
82
|
-
[_ipv4ToInt("0.0.0.0"), 8], // "this network"
|
|
83
|
-
[_ipv4ToInt("100.64.0.0"), 10], // CGNAT (RFC 6598)
|
|
84
|
-
[_ipv4ToInt("192.0.0.0"), 24], // IETF protocol assignments
|
|
85
|
-
[_ipv4ToInt("192.0.2.0"), 24], // TEST-NET-1
|
|
86
|
-
[_ipv4ToInt("198.18.0.0"), 15], // network benchmark
|
|
87
|
-
[_ipv4ToInt("198.51.100.0"), 24], // TEST-NET-2
|
|
88
|
-
[_ipv4ToInt("203.0.113.0"), 24], // TEST-NET-3
|
|
89
|
-
[_ipv4ToInt("224.0.0.0"), 4], // multicast
|
|
90
|
-
[_ipv4ToInt("240.0.0.0"), 4], // reserved + 255.255.255.255
|
|
95
|
+
[_ipv4ToInt("0.0.0.0"), C.BYTES.bytes(8)], // "this network"
|
|
96
|
+
[_ipv4ToInt("100.64.0.0"), C.BYTES.bytes(10)], // CGNAT (RFC 6598)
|
|
97
|
+
[_ipv4ToInt("192.0.0.0"), C.BYTES.bytes(24)], // IETF protocol assignments
|
|
98
|
+
[_ipv4ToInt("192.0.2.0"), C.BYTES.bytes(24)], // TEST-NET-1
|
|
99
|
+
[_ipv4ToInt("198.18.0.0"), C.BYTES.bytes(15)], // network benchmark
|
|
100
|
+
[_ipv4ToInt("198.51.100.0"), C.BYTES.bytes(24)], // TEST-NET-2
|
|
101
|
+
[_ipv4ToInt("203.0.113.0"), C.BYTES.bytes(24)], // TEST-NET-3
|
|
102
|
+
[_ipv4ToInt("224.0.0.0"), C.BYTES.bytes(4)], // multicast
|
|
103
|
+
[_ipv4ToInt("240.0.0.0"), C.BYTES.bytes(4)], // reserved + 255.255.255.255
|
|
91
104
|
];
|
|
92
105
|
|
|
93
106
|
// ---- IPv6 ranges (as 16-byte prefix tables) ----
|
|
@@ -96,7 +109,8 @@ var IPV6_UNSPECIFIED_BYTES = _ipv6ToBytes("::");
|
|
|
96
109
|
var IPV6_PRIVATE_PREFIX = _ipv6ToBytes("fc00::");
|
|
97
110
|
var IPV6_LINK_LOCAL_PREFIX = _ipv6ToBytes("fe80::");
|
|
98
111
|
var IPV6_DOC_PREFIX = _ipv6ToBytes("2001:db8::"); // documentation
|
|
99
|
-
|
|
112
|
+
// IPv4-mapped IPv6 (::ffff:0:0 with C.BYTES.bytes(96)-bit prefix)
|
|
113
|
+
var IPV6_V4_MAPPED_PREFIX = _ipv6ToBytes("::ffff:0:0");
|
|
100
114
|
// Multicast ff00::/8 — RFC 4291 §2.7. Refused for outbound HTTP same as
|
|
101
115
|
// IPv4 multicast 224/4 — there's no legitimate fetch from a multicast
|
|
102
116
|
// destination on a production gateway.
|
|
@@ -138,9 +152,9 @@ function _ipv6ToBytes(ip) {
|
|
|
138
152
|
// ipv6-to-bytes helper, but the URL constructor + Buffer dance
|
|
139
153
|
// is reliable for canonicalizing.
|
|
140
154
|
var groups = _expandIpv6(ip);
|
|
141
|
-
var out = Buffer.alloc(
|
|
142
|
-
for (var i = 0; i <
|
|
143
|
-
var v = parseInt(groups[i],
|
|
155
|
+
var out = Buffer.alloc(IPV6_BYTES);
|
|
156
|
+
for (var i = 0; i < IPV6_GROUPS; i++) {
|
|
157
|
+
var v = parseInt(groups[i], HEX_RADIX) || 0;
|
|
144
158
|
out[i * 2] = (v >> 8) & 0xff;
|
|
145
159
|
out[i * 2 + 1] = v & 0xff;
|
|
146
160
|
}
|
|
@@ -157,7 +171,7 @@ function _expandIpv6(ip) {
|
|
|
157
171
|
var ipv4Int = _ipv4ToInt(v4);
|
|
158
172
|
var hi = (ipv4Int >>> 16) & 0xffff;
|
|
159
173
|
var lo = ipv4Int & 0xffff;
|
|
160
|
-
lower = lower.slice(0, dot + 1) + hi.toString(
|
|
174
|
+
lower = lower.slice(0, dot + 1) + hi.toString(HEX_RADIX) + ":" + lo.toString(HEX_RADIX);
|
|
161
175
|
}
|
|
162
176
|
var doubleColon = lower.indexOf("::");
|
|
163
177
|
var leftStr, rightStr;
|
|
@@ -170,7 +184,7 @@ function _expandIpv6(ip) {
|
|
|
170
184
|
}
|
|
171
185
|
var left = leftStr.length ? leftStr.split(":") : [];
|
|
172
186
|
var right = rightStr.length ? rightStr.split(":") : [];
|
|
173
|
-
var missing =
|
|
187
|
+
var missing = IPV6_GROUPS - left.length - right.length;
|
|
174
188
|
var fill = [];
|
|
175
189
|
for (var i = 0; i < missing; i++) fill.push("0");
|
|
176
190
|
return left.concat(fill).concat(right);
|
|
@@ -181,10 +195,10 @@ function _cidrIpv4Match(cidr, ip) {
|
|
|
181
195
|
if (slash === -1) return false;
|
|
182
196
|
var network = _ipv4ToInt(cidr.slice(0, slash));
|
|
183
197
|
var prefix = parseInt(cidr.slice(slash + 1), 10);
|
|
184
|
-
if (!Number.isFinite(prefix) || prefix < 0 || prefix >
|
|
198
|
+
if (!Number.isFinite(prefix) || prefix < 0 || prefix > IPV4_BITS) return false;
|
|
185
199
|
var ipInt = _ipv4ToInt(ip);
|
|
186
200
|
if (prefix === 0) return true;
|
|
187
|
-
var mask = (0xffffffff << (
|
|
201
|
+
var mask = (0xffffffff << (IPV4_BITS - prefix)) >>> 0;
|
|
188
202
|
return (ipInt & mask) === (network & mask);
|
|
189
203
|
}
|
|
190
204
|
|
|
@@ -193,15 +207,15 @@ function _cidrIpv6Match(cidr, ip) {
|
|
|
193
207
|
if (slash === -1) return false;
|
|
194
208
|
var network = _ipv6ToBytes(cidr.slice(0, slash));
|
|
195
209
|
var prefix = parseInt(cidr.slice(slash + 1), 10);
|
|
196
|
-
if (!Number.isFinite(prefix) || prefix < 0 || prefix >
|
|
210
|
+
if (!Number.isFinite(prefix) || prefix < 0 || prefix > IPV6_BITS) return false;
|
|
197
211
|
var bytes = _ipv6ToBytes(ip);
|
|
198
|
-
var fullBytes = Math.floor(prefix /
|
|
199
|
-
var remainingBits = prefix %
|
|
212
|
+
var fullBytes = Math.floor(prefix / BITS_PER_BYTE);
|
|
213
|
+
var remainingBits = prefix % BITS_PER_BYTE;
|
|
200
214
|
for (var i = 0; i < fullBytes; i++) {
|
|
201
215
|
if (bytes[i] !== network[i]) return false;
|
|
202
216
|
}
|
|
203
217
|
if (remainingBits > 0) {
|
|
204
|
-
var mask = (0xff << (
|
|
218
|
+
var mask = (0xff << (BITS_PER_BYTE - remainingBits)) & 0xff;
|
|
205
219
|
if ((bytes[fullBytes] & mask) !== (network[fullBytes] & mask)) return false;
|
|
206
220
|
}
|
|
207
221
|
return true;
|
|
@@ -211,20 +225,20 @@ function _ipv4PrefixMatch(prefixTable, ipInt) {
|
|
|
211
225
|
for (var i = 0; i < prefixTable.length; i++) {
|
|
212
226
|
var net4 = prefixTable[i][0];
|
|
213
227
|
var prefix = prefixTable[i][1];
|
|
214
|
-
var mask = prefix === 0 ? 0 : (0xffffffff << (
|
|
228
|
+
var mask = prefix === 0 ? 0 : (0xffffffff << (IPV4_BITS - prefix)) >>> 0;
|
|
215
229
|
if ((ipInt & mask) === (net4 & mask)) return true;
|
|
216
230
|
}
|
|
217
231
|
return false;
|
|
218
232
|
}
|
|
219
233
|
|
|
220
234
|
function _ipv6PrefixMatch(prefixBytes, prefixLen, ipBytes) {
|
|
221
|
-
var fullBytes = Math.floor(prefixLen /
|
|
222
|
-
var remainingBits = prefixLen %
|
|
235
|
+
var fullBytes = Math.floor(prefixLen / BITS_PER_BYTE);
|
|
236
|
+
var remainingBits = prefixLen % BITS_PER_BYTE;
|
|
223
237
|
for (var i = 0; i < fullBytes; i++) {
|
|
224
238
|
if (ipBytes[i] !== prefixBytes[i]) return false;
|
|
225
239
|
}
|
|
226
240
|
if (remainingBits > 0) {
|
|
227
|
-
var mask = (0xff << (
|
|
241
|
+
var mask = (0xff << (BITS_PER_BYTE - remainingBits)) & 0xff;
|
|
228
242
|
if ((ipBytes[fullBytes] & mask) !== (prefixBytes[fullBytes] & mask)) return false;
|
|
229
243
|
}
|
|
230
244
|
return true;
|
|
@@ -252,25 +266,25 @@ function classify(ip) {
|
|
|
252
266
|
var bytes = _ipv6ToBytes(ip);
|
|
253
267
|
if (_bufEqual(bytes, IPV6_LOOPBACK_BYTES)) return "loopback";
|
|
254
268
|
if (_bufEqual(bytes, IPV6_UNSPECIFIED_BYTES)) return "reserved";
|
|
255
|
-
if (_ipv6PrefixMatch(IPV6_LINK_LOCAL_PREFIX, 10, bytes)) return "link-local";
|
|
269
|
+
if (_ipv6PrefixMatch(IPV6_LINK_LOCAL_PREFIX, C.BYTES.bytes(10), bytes)) return "link-local";
|
|
256
270
|
if (_ipv6PrefixMatch(IPV6_PRIVATE_PREFIX, 7, bytes)) return "private";
|
|
257
|
-
if (_ipv6PrefixMatch(IPV6_DOC_PREFIX, 32, bytes)) return "reserved";
|
|
258
|
-
if (_ipv6PrefixMatch(IPV6_MULTICAST_PREFIX, 8, bytes)) return "reserved";
|
|
259
|
-
if (_ipv6PrefixMatch(IPV6_DISCARD_PREFIX, 64, bytes)) return "reserved";
|
|
260
|
-
// 6to4
|
|
271
|
+
if (_ipv6PrefixMatch(IPV6_DOC_PREFIX, C.BYTES.bytes(32), bytes)) return "reserved";
|
|
272
|
+
if (_ipv6PrefixMatch(IPV6_MULTICAST_PREFIX, C.BYTES.bytes(8), bytes)) return "reserved";
|
|
273
|
+
if (_ipv6PrefixMatch(IPV6_DISCARD_PREFIX, C.BYTES.bytes(64), bytes)) return "reserved";
|
|
274
|
+
// 6to4 prefix embeds a v4 address in bytes 2–5; classify the
|
|
261
275
|
// embedded v4 so a 6to4-wrapped private/metadata address is refused
|
|
262
276
|
// for the same reason its v4 form would be.
|
|
263
|
-
if (_ipv6PrefixMatch(IPV6_6TO4_PREFIX, 16, bytes)) {
|
|
277
|
+
if (_ipv6PrefixMatch(IPV6_6TO4_PREFIX, C.BYTES.bytes(16), bytes)) {
|
|
264
278
|
var v4From6to4 = bytes[2] + "." + bytes[3] + "." + bytes[4] + "." + bytes[5];
|
|
265
279
|
return classify(v4From6to4) || "reserved";
|
|
266
280
|
}
|
|
267
|
-
// NAT64 well-known prefix
|
|
268
|
-
if (_ipv6PrefixMatch(IPV6_NAT64_PREFIX, 96, bytes)) {
|
|
281
|
+
// NAT64 well-known prefix embeds v4 in the lower bytes.
|
|
282
|
+
if (_ipv6PrefixMatch(IPV6_NAT64_PREFIX, C.BYTES.bytes(96), bytes)) {
|
|
269
283
|
var v4FromNat64 = bytes[12] + "." + bytes[13] + "." + bytes[14] + "." + bytes[15];
|
|
270
284
|
return classify(v4FromNat64) || "reserved";
|
|
271
285
|
}
|
|
272
|
-
// IPv4-mapped addresses (::ffff:a.b.c.d
|
|
273
|
-
if (_ipv6PrefixMatch(IPV6_V4_MAPPED_PREFIX, 96, bytes)) {
|
|
286
|
+
// IPv4-mapped addresses (::ffff:a.b.c.d): re-classify the v4 portion.
|
|
287
|
+
if (_ipv6PrefixMatch(IPV6_V4_MAPPED_PREFIX, C.BYTES.bytes(96), bytes)) {
|
|
274
288
|
var mappedV4 = bytes[12] + "." + bytes[13] + "." + bytes[14] + "." + bytes[15];
|
|
275
289
|
return classify(mappedV4);
|
|
276
290
|
}
|
|
@@ -278,9 +292,10 @@ function classify(ip) {
|
|
|
278
292
|
}
|
|
279
293
|
|
|
280
294
|
function _bufEqual(a, b) {
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
295
|
+
// Compares Buffer-like byte arrays for equality. The buffers here
|
|
296
|
+
// are IP addresses, not secrets, so the comparison doesn't need
|
|
297
|
+
// constant-time semantics — Buffer.compare uses native memcmp.
|
|
298
|
+
return Buffer.compare(a, b) === 0;
|
|
284
299
|
}
|
|
285
300
|
|
|
286
301
|
function cidrContains(cidr, ip) {
|
package/lib/static.js
CHANGED
|
@@ -48,12 +48,16 @@ var fs = require("fs");
|
|
|
48
48
|
var fsp = require("fs/promises");
|
|
49
49
|
var path = require("path");
|
|
50
50
|
var nodeCrypto = require("crypto");
|
|
51
|
+
var C = require("./constants");
|
|
52
|
+
var requestHelpers = require("./request-helpers");
|
|
51
53
|
var validateOpts = require("./validate-opts");
|
|
52
54
|
|
|
55
|
+
var HTTP = requestHelpers.HTTP_STATUS;
|
|
56
|
+
|
|
53
57
|
var DEFAULT_HASHED_PATTERN = /\.[a-fA-F0-9]{8,}\./;
|
|
54
58
|
var DEFAULT_INDEX_FILE = "index.html";
|
|
55
|
-
var DEFAULT_MAX_AGE_SEC =
|
|
56
|
-
var IMMUTABLE_MAX_AGE_SEC =
|
|
59
|
+
var DEFAULT_MAX_AGE_SEC = C.TIME.hours(1) / C.TIME.seconds(1); // 1 hour for non-hashed paths
|
|
60
|
+
var IMMUTABLE_MAX_AGE_SEC = C.TIME.days(365) / C.TIME.seconds(1); // 1 year for hashed paths
|
|
57
61
|
|
|
58
62
|
// Minimal MIME table. Operators with exotic types pass opts.contentTypes
|
|
59
63
|
// to override. The framework deliberately doesn't bring a 200-entry
|
|
@@ -143,7 +147,7 @@ function _contentTypeFor(filePath, table) {
|
|
|
143
147
|
}
|
|
144
148
|
|
|
145
149
|
function _writeNotModified(res, etag, cacheControl) {
|
|
146
|
-
res.writeHead(
|
|
150
|
+
res.writeHead(HTTP.NOT_MODIFIED, {
|
|
147
151
|
"ETag": etag,
|
|
148
152
|
"Cache-Control": cacheControl,
|
|
149
153
|
});
|
|
@@ -151,7 +155,7 @@ function _writeNotModified(res, etag, cacheControl) {
|
|
|
151
155
|
}
|
|
152
156
|
|
|
153
157
|
function _writeNotFound(res) {
|
|
154
|
-
res.writeHead(
|
|
158
|
+
res.writeHead(HTTP.NOT_FOUND, { "Content-Type": "text/plain; charset=utf-8", "Content-Length": 9 });
|
|
155
159
|
res.end("Not Found");
|
|
156
160
|
}
|
|
157
161
|
|
|
@@ -240,16 +244,16 @@ function create(opts) {
|
|
|
240
244
|
};
|
|
241
245
|
|
|
242
246
|
if (req.method === "HEAD") {
|
|
243
|
-
res.writeHead(
|
|
247
|
+
res.writeHead(HTTP.OK, headers);
|
|
244
248
|
return res.end();
|
|
245
249
|
}
|
|
246
250
|
|
|
247
|
-
res.writeHead(
|
|
251
|
+
res.writeHead(HTTP.OK, headers);
|
|
248
252
|
var stream = fs.createReadStream(absPath);
|
|
249
253
|
stream.on("error", function (e) {
|
|
250
254
|
// Mid-stream read error — best we can do is destroy the response;
|
|
251
255
|
// headers are already on the wire.
|
|
252
|
-
try { res.destroy(e); } catch (_) {}
|
|
256
|
+
try { res.destroy(e); } catch (_) { /* response already torn down */ }
|
|
253
257
|
});
|
|
254
258
|
stream.pipe(res);
|
|
255
259
|
};
|
package/lib/storage.js
CHANGED
|
@@ -57,6 +57,7 @@
|
|
|
57
57
|
* storage.listBackends() → [{ name, protocol, classifications, residencyTag }]
|
|
58
58
|
* storage.getBackend(name) → backend instance (or null)
|
|
59
59
|
*/
|
|
60
|
+
var C = require("./constants");
|
|
60
61
|
var { generateBytes, encryptPacked, decryptPacked } = require("./crypto");
|
|
61
62
|
var objectStore = require("./object-store");
|
|
62
63
|
var lazyRequire = require("./lazy-require");
|
|
@@ -218,7 +219,7 @@ function _pickBackend(opts) {
|
|
|
218
219
|
// ---- File encryption helpers ----
|
|
219
220
|
|
|
220
221
|
function _encryptBuffer(buffer) {
|
|
221
|
-
var key = generateBytes(32);
|
|
222
|
+
var key = generateBytes(C.BYTES.bytes(32));
|
|
222
223
|
var packed = encryptPacked(buffer, key);
|
|
223
224
|
var sealedKey = vault().seal(key.toString("base64"));
|
|
224
225
|
return { data: packed, encryptionKey: sealedKey };
|