@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/network-proxy.js
CHANGED
|
@@ -6,12 +6,19 @@ var net = require("node:net");
|
|
|
6
6
|
var tls = require("node:tls");
|
|
7
7
|
|
|
8
8
|
var C = require("./constants");
|
|
9
|
-
var validateOpts = require("./validate-opts");
|
|
10
9
|
var lazyRequire = require("./lazy-require");
|
|
10
|
+
var safeUrl = require("./safe-url");
|
|
11
|
+
var validateOpts = require("./validate-opts");
|
|
11
12
|
var { defineClass } = require("./framework-error");
|
|
12
13
|
|
|
13
14
|
var ProxyError = defineClass("ProxyError", { alwaysPermanent: false });
|
|
14
15
|
|
|
16
|
+
// Protocol-fixed byte counts — passthrough through C.BYTES so the codebase
|
|
17
|
+
// has a single source of truth for "what shape is this number".
|
|
18
|
+
var IPV4_PREFIX_MAX_BITS = C.BYTES.bytes(32); // RFC 791 §3.1 IPv4 address bit-width
|
|
19
|
+
var DEFAULT_HTTPS_PORT = 443; // RFC 9110 §4.2.2
|
|
20
|
+
var DEFAULT_HTTP_PORT = C.BYTES.bytes(80); // RFC 9110 §4.2.1
|
|
21
|
+
|
|
15
22
|
var observability = lazyRequire(function () { return require("./observability"); });
|
|
16
23
|
|
|
17
24
|
var STATE = {
|
|
@@ -31,14 +38,18 @@ function _parseAuth(spec) {
|
|
|
31
38
|
|
|
32
39
|
function _parseProxyUrl(value) {
|
|
33
40
|
if (!value) return null;
|
|
34
|
-
|
|
35
|
-
|
|
41
|
+
try {
|
|
42
|
+
return safeUrl.parse(value, {
|
|
43
|
+
allowedProtocols: safeUrl.ALLOW_HTTP_ALL,
|
|
44
|
+
allowUserinfo: true, // proxy auth is conventionally placed in the URL
|
|
45
|
+
errorClass: ProxyError,
|
|
46
|
+
});
|
|
47
|
+
} catch (e) {
|
|
48
|
+
if (e && e.isSafeUrlError && e.code === "safe-url/protocol-disallowed") {
|
|
49
|
+
throw new ProxyError("proxy/bad-protocol", "proxy URL must be http:// or https://, got " + value);
|
|
50
|
+
}
|
|
36
51
|
throw new ProxyError("proxy/bad-url", "invalid proxy URL: " + value);
|
|
37
52
|
}
|
|
38
|
-
if (u.protocol !== "http:" && u.protocol !== "https:") {
|
|
39
|
-
throw new ProxyError("proxy/bad-protocol", "proxy URL must be http:// or https://, got " + u.protocol);
|
|
40
|
-
}
|
|
41
|
-
return u;
|
|
42
53
|
}
|
|
43
54
|
|
|
44
55
|
function _parseNoProxy(spec) {
|
|
@@ -97,14 +108,22 @@ function _matchCidr(cidr, host) {
|
|
|
97
108
|
var network = cidr.slice(0, slash);
|
|
98
109
|
var prefix = parseInt(cidr.slice(slash + 1), 10);
|
|
99
110
|
if (net.isIP(host) !== 4 || net.isIP(network) !== 4) return false;
|
|
100
|
-
if (!isFinite(prefix) || prefix < 0 || prefix >
|
|
111
|
+
if (!isFinite(prefix) || prefix < 0 || prefix > IPV4_PREFIX_MAX_BITS) return false;
|
|
101
112
|
if (prefix === 0) return true;
|
|
102
|
-
var mask = (0xffffffff << (
|
|
113
|
+
var mask = (0xffffffff << (IPV4_PREFIX_MAX_BITS - prefix)) >>> 0;
|
|
103
114
|
return (_ipv4ToInt(host) & mask) === (_ipv4ToInt(network) & mask);
|
|
104
115
|
}
|
|
105
116
|
|
|
117
|
+
function _parseTarget(targetUrl) {
|
|
118
|
+
if (targetUrl instanceof URL) return targetUrl;
|
|
119
|
+
return safeUrl.parse(String(targetUrl), {
|
|
120
|
+
allowedProtocols: safeUrl.ALLOW_HTTP_ALL,
|
|
121
|
+
errorClass: ProxyError,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
106
125
|
function shouldProxy(targetUrl) {
|
|
107
|
-
var u =
|
|
126
|
+
var u = _parseTarget(targetUrl);
|
|
108
127
|
var host = u.hostname;
|
|
109
128
|
var rules = STATE.noProxy;
|
|
110
129
|
for (var i = 0; i < rules.length; i++) {
|
|
@@ -121,7 +140,7 @@ function shouldProxy(targetUrl) {
|
|
|
121
140
|
}
|
|
122
141
|
|
|
123
142
|
function _proxyForUrl(targetUrl) {
|
|
124
|
-
var u =
|
|
143
|
+
var u = _parseTarget(targetUrl);
|
|
125
144
|
if (!shouldProxy(u)) return null;
|
|
126
145
|
return u.protocol === "https:" ? STATE.https : STATE.http;
|
|
127
146
|
}
|
|
@@ -134,7 +153,7 @@ function _proxyAuthHeader(proxyUrl) {
|
|
|
134
153
|
}
|
|
135
154
|
|
|
136
155
|
function _connectThroughTunnel(proxyUrl, targetHost, targetPort, callback) {
|
|
137
|
-
var proxyPort = proxyUrl.port || (proxyUrl.protocol === "https:" ?
|
|
156
|
+
var proxyPort = proxyUrl.port || (proxyUrl.protocol === "https:" ? DEFAULT_HTTPS_PORT : DEFAULT_HTTP_PORT);
|
|
138
157
|
var proxySocket = proxyUrl.protocol === "https:"
|
|
139
158
|
? tls.connect({
|
|
140
159
|
host: proxyUrl.hostname,
|
|
@@ -167,7 +186,7 @@ function _connectThroughTunnel(proxyUrl, targetHost, targetPort, callback) {
|
|
|
167
186
|
var match = /HTTP\/1\.[01] (\d{3})/.exec(status);
|
|
168
187
|
if (!match || parseInt(match[1], 10) !== 200) {
|
|
169
188
|
done(new ProxyError("proxy/connect-failed", "proxy CONNECT to " + targetHost + ":" + targetPort + " failed: " + status));
|
|
170
|
-
try { proxySocket.destroy(); } catch (_e) {}
|
|
189
|
+
try { proxySocket.destroy(); } catch (_e) { /* best-effort socket teardown */ }
|
|
171
190
|
return;
|
|
172
191
|
}
|
|
173
192
|
done(null, proxySocket);
|
|
@@ -179,7 +198,7 @@ function _connectThroughTunnel(proxyUrl, targetHost, targetPort, callback) {
|
|
|
179
198
|
function agentFor(targetUrl) {
|
|
180
199
|
var proxy = _proxyForUrl(targetUrl);
|
|
181
200
|
if (!proxy) return null;
|
|
182
|
-
var u =
|
|
201
|
+
var u = _parseTarget(targetUrl);
|
|
183
202
|
var key = proxy.toString() + "|" + u.protocol;
|
|
184
203
|
if (STATE.agentCache.has(key)) return STATE.agentCache.get(key);
|
|
185
204
|
|
|
@@ -227,7 +246,7 @@ function snapshot() {
|
|
|
227
246
|
}
|
|
228
247
|
|
|
229
248
|
function _emitObs(name, fields) {
|
|
230
|
-
try { observability().emit(name, fields || {}); } catch (_e) {}
|
|
249
|
+
try { observability().emit(name, fields || {}); } catch (_e) { /* obs best-effort */ }
|
|
231
250
|
}
|
|
232
251
|
|
|
233
252
|
function _resetForTest() {
|
package/lib/network-tls.js
CHANGED
|
@@ -5,6 +5,8 @@ var fs = require("node:fs");
|
|
|
5
5
|
var path = require("node:path");
|
|
6
6
|
var nodeCrypto = require("node:crypto");
|
|
7
7
|
|
|
8
|
+
var C = require("./constants");
|
|
9
|
+
var safeBuffer = require("./safe-buffer");
|
|
8
10
|
var validateOpts = require("./validate-opts");
|
|
9
11
|
var lazyRequire = require("./lazy-require");
|
|
10
12
|
var { defineClass } = require("./framework-error");
|
|
@@ -66,8 +68,8 @@ function _certMetadata(pem) {
|
|
|
66
68
|
|
|
67
69
|
function _isPathLike(s) {
|
|
68
70
|
if (s.indexOf("-----BEGIN") !== -1) return false;
|
|
69
|
-
if (s.length >
|
|
70
|
-
if (
|
|
71
|
+
if (s.length > C.BYTES.kib(1)) return false;
|
|
72
|
+
if (safeBuffer.hasCrlf(s)) return false;
|
|
71
73
|
return true;
|
|
72
74
|
}
|
|
73
75
|
|
|
@@ -159,7 +161,7 @@ function _emitAuditRemove(metaList, reason) {
|
|
|
159
161
|
reason: reason || "operator",
|
|
160
162
|
},
|
|
161
163
|
});
|
|
162
|
-
} catch (_e) {}
|
|
164
|
+
} catch (_e) { /* audit best-effort — never break the caller */ }
|
|
163
165
|
}
|
|
164
166
|
}
|
|
165
167
|
|
|
@@ -292,12 +294,12 @@ function _emitAuditAdd(metaList, opts) {
|
|
|
292
294
|
label: opts.label || null,
|
|
293
295
|
},
|
|
294
296
|
});
|
|
295
|
-
} catch (_e) {}
|
|
297
|
+
} catch (_e) { /* audit best-effort — never break the caller */ }
|
|
296
298
|
}
|
|
297
299
|
}
|
|
298
300
|
|
|
299
301
|
function _emitObs(name, fields) {
|
|
300
|
-
try { observability().emit(name, fields || {}); } catch (_e) {}
|
|
302
|
+
try { observability().emit(name, fields || {}); } catch (_e) { /* obs best-effort */ }
|
|
301
303
|
}
|
|
302
304
|
|
|
303
305
|
function _resetForTest() {
|
package/lib/network.js
CHANGED
|
@@ -77,7 +77,7 @@ function applyToSocket(socket) {
|
|
|
77
77
|
if (typeof socket.setKeepAlive === "function") {
|
|
78
78
|
socket.setKeepAlive(SOCKET_DEFAULTS.keepAlive, SOCKET_DEFAULTS.keepAliveInitialDelayMs || 0);
|
|
79
79
|
}
|
|
80
|
-
} catch (_e) {}
|
|
80
|
+
} catch (_e) { /* best-effort socket-tuning — apply or skip */ }
|
|
81
81
|
return socket;
|
|
82
82
|
}
|
|
83
83
|
|
|
@@ -172,7 +172,7 @@ function bootFromEnv(opts) {
|
|
|
172
172
|
outcome: "success",
|
|
173
173
|
metadata: applied,
|
|
174
174
|
});
|
|
175
|
-
} catch (_e) {}
|
|
175
|
+
} catch (_e) { /* audit best-effort — never break boot */ }
|
|
176
176
|
}
|
|
177
177
|
}
|
|
178
178
|
_emitObs("network.boot.from_env", { source: "env" });
|
|
@@ -197,7 +197,7 @@ function snapshot() {
|
|
|
197
197
|
}
|
|
198
198
|
|
|
199
199
|
function _emitObs(name, fields) {
|
|
200
|
-
try { observability().emit(name, fields || {}); } catch (_e) {}
|
|
200
|
+
try { observability().emit(name, fields || {}); } catch (_e) { /* obs best-effort */ }
|
|
201
201
|
}
|
|
202
202
|
|
|
203
203
|
function _resetForTest() {
|
package/lib/notify.js
CHANGED
|
@@ -127,9 +127,7 @@ function _validateChannelEntry(name, entry) {
|
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
function _validateCreateOpts(opts) {
|
|
130
|
-
|
|
131
|
-
throw _err("BAD_OPT", "notify.create: opts must be an object");
|
|
132
|
-
}
|
|
130
|
+
validateOpts.requireObject(opts, "notify.create", NotifyError);
|
|
133
131
|
if (!opts.channels || typeof opts.channels !== "object") {
|
|
134
132
|
throw _err("BAD_OPT", "notify.create: channels must be an object mapping channel names to transports");
|
|
135
133
|
}
|
|
@@ -137,17 +135,9 @@ function _validateCreateOpts(opts) {
|
|
|
137
135
|
if (keys.length === 0) {
|
|
138
136
|
throw _err("BAD_OPT", "notify.create: channels object must have at least one channel");
|
|
139
137
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
if (opts.auditSuccess !== undefined && typeof opts.auditSuccess !== "boolean") {
|
|
146
|
-
throw _err("BAD_OPT", "notify.create: auditSuccess must be a boolean");
|
|
147
|
-
}
|
|
148
|
-
if (opts.auditFailures !== undefined && typeof opts.auditFailures !== "boolean") {
|
|
149
|
-
throw _err("BAD_OPT", "notify.create: auditFailures must be a boolean");
|
|
150
|
-
}
|
|
138
|
+
validateOpts.auditShape(opts.audit, "notify.create", NotifyError);
|
|
139
|
+
validateOpts.optionalBoolean(opts.auditSuccess, "notify.create: auditSuccess", NotifyError);
|
|
140
|
+
validateOpts.optionalBoolean(opts.auditFailures, "notify.create: auditFailures", NotifyError);
|
|
151
141
|
if (opts.redact !== undefined && opts.redact !== null && typeof opts.redact !== "function") {
|
|
152
142
|
throw _err("BAD_OPT", "notify.create: redact must be a function returning a redacted message");
|
|
153
143
|
}
|
|
@@ -169,9 +159,7 @@ function _validateCreateOpts(opts) {
|
|
|
169
159
|
throw _err("BAD_OPT", "notify.create: queue must be a b.queue-shaped handle (enqueue fn)");
|
|
170
160
|
}
|
|
171
161
|
}
|
|
172
|
-
|
|
173
|
-
throw _err("BAD_OPT", "notify.create: clock must be a function");
|
|
174
|
-
}
|
|
162
|
+
validateOpts.optionalFunction(opts.clock, "notify.create: clock", NotifyError);
|
|
175
163
|
}
|
|
176
164
|
|
|
177
165
|
// ---- Built-in transports ----
|
|
@@ -183,9 +171,7 @@ function httpJson(opts) {
|
|
|
183
171
|
if (!opts || typeof opts !== "object") {
|
|
184
172
|
throw _err("BAD_OPT", "notify.transports.httpJson: opts must be { url, ... }");
|
|
185
173
|
}
|
|
186
|
-
|
|
187
|
-
throw _err("BAD_OPT", "notify.transports.httpJson: url is required");
|
|
188
|
-
}
|
|
174
|
+
validateOpts.requireNonEmptyString(opts.url, "notify.transports.httpJson: url", NotifyError, "BAD_OPT");
|
|
189
175
|
var allowedProtocols = opts.allowHttp ? safeUrl.ALLOW_HTTP_ALL : safeUrl.ALLOW_HTTP_TLS;
|
|
190
176
|
var allowInternal = opts.allowInternal != null ? opts.allowInternal : null;
|
|
191
177
|
// Validate URL at create time so bad URLs surface at boot, not at first send.
|
|
@@ -270,18 +256,14 @@ function httpJson(opts) {
|
|
|
270
256
|
// log — fire-and-forget developer logger. Never throws; audit + obs still emit.
|
|
271
257
|
function logTransport(opts) {
|
|
272
258
|
opts = opts || {};
|
|
259
|
+
// bootLog.boot() returns a callable with .info / .warn / .error
|
|
260
|
+
// attached; that shape satisfies the operator-supplied opts.logger
|
|
261
|
+
// contract directly. No fallback wrapper needed.
|
|
273
262
|
var logger;
|
|
274
263
|
if (opts.logger && typeof opts.logger.info === "function") {
|
|
275
264
|
logger = opts.logger;
|
|
276
265
|
} else {
|
|
277
266
|
logger = bootLog.boot("notify.log");
|
|
278
|
-
// boot returns a fn-with-fields shape; tolerate both.
|
|
279
|
-
if (typeof logger === "function") {
|
|
280
|
-
logger = { info: logger, error: function (m) { try { console.error(m); } catch (_e) {} } };
|
|
281
|
-
}
|
|
282
|
-
if (typeof logger.error !== "function") {
|
|
283
|
-
logger.error = function (m) { try { console.error(m); } catch (_e) {} };
|
|
284
|
-
}
|
|
285
267
|
}
|
|
286
268
|
return {
|
|
287
269
|
name: opts.name || "log",
|
|
@@ -321,16 +303,16 @@ function create(opts) {
|
|
|
321
303
|
"queue", "clock",
|
|
322
304
|
], "notify");
|
|
323
305
|
_validateCreateOpts(opts);
|
|
306
|
+
var cfg = validateOpts.applyDefaults(opts, DEFAULTS);
|
|
324
307
|
|
|
325
|
-
var auditSuccess =
|
|
326
|
-
var auditFailures =
|
|
308
|
+
var auditSuccess = cfg.auditSuccess;
|
|
309
|
+
var auditFailures = cfg.auditFailures;
|
|
327
310
|
var audit = opts.audit || null;
|
|
328
311
|
var redactFn = (typeof opts.redact === "function")
|
|
329
312
|
? opts.redact
|
|
330
313
|
// Default: b.redact.redact — the framework's PII detector chain.
|
|
331
314
|
: function (m) { return redactModule().redact(m); };
|
|
332
|
-
var defaultTimeoutMs =
|
|
333
|
-
? DEFAULTS.defaultTimeoutMs : opts.defaultTimeoutMs;
|
|
315
|
+
var defaultTimeoutMs = cfg.defaultTimeoutMs;
|
|
334
316
|
var defaultRetry = opts.defaultRetry || null;
|
|
335
317
|
var defaultBreaker = opts.defaultBreaker || null;
|
|
336
318
|
var operatorQueue = opts.queue || null;
|
|
@@ -362,11 +344,7 @@ function create(opts) {
|
|
|
362
344
|
catch (_e) { /* drop-silent — observability sink must not crash send() */ }
|
|
363
345
|
}
|
|
364
346
|
|
|
365
|
-
|
|
366
|
-
if (!audit) return;
|
|
367
|
-
try { audit.safeEmit(Object.assign({ action: action }, info || {})); }
|
|
368
|
-
catch (_e) { /* audit best-effort */ }
|
|
369
|
-
}
|
|
347
|
+
var _emitAudit = validateOpts.makeAuditEmitter(audit);
|
|
370
348
|
|
|
371
349
|
function _actor(callerOpts) {
|
|
372
350
|
return requestHelpers.resolveActorWithOverride(callerOpts);
|
package/lib/ntp-check.js
CHANGED
|
@@ -34,7 +34,8 @@ var NTP_TO_UNIX_OFFSET_SECONDS = 2208988800;
|
|
|
34
34
|
|
|
35
35
|
var DEFAULT_SERVERS = ["pool.ntp.org", "time.cloudflare.com"];
|
|
36
36
|
var DEFAULT_PORT = 123;
|
|
37
|
-
var DEFAULT_TIMEOUT_MS =
|
|
37
|
+
var DEFAULT_TIMEOUT_MS = C.TIME.seconds(3);
|
|
38
|
+
var NTP_PACKET_BYTES = C.BYTES.bytes(48); // RFC 5905 §7.3
|
|
38
39
|
|
|
39
40
|
var DEFAULT_DRIFT_WARN_MS = C.TIME.minutes(5);
|
|
40
41
|
var DEFAULT_DRIFT_FATAL_MS = C.TIME.hours(1);
|
|
@@ -105,9 +106,9 @@ function querySingle(server, opts) {
|
|
|
105
106
|
}, timeoutMs);
|
|
106
107
|
timer.unref();
|
|
107
108
|
|
|
108
|
-
// SNTPv4 client request:
|
|
109
|
+
// SNTPv4 client request: NTP_PACKET_BYTES buffer, byte 0 = 0b00_100_011 = 0x23
|
|
109
110
|
// LI=0 (no warning), VN=4, Mode=3 (client). Other bytes zero.
|
|
110
|
-
var req = Buffer.alloc(
|
|
111
|
+
var req = Buffer.alloc(NTP_PACKET_BYTES);
|
|
111
112
|
req[0] = 0x23;
|
|
112
113
|
var sendTimeMs = Date.now();
|
|
113
114
|
|
|
@@ -119,15 +120,15 @@ function querySingle(server, opts) {
|
|
|
119
120
|
socket.on("message", function (msg) {
|
|
120
121
|
clearTimeout(timer);
|
|
121
122
|
var receiveTimeMs = Date.now();
|
|
122
|
-
if (!Buffer.isBuffer(msg) || msg.length <
|
|
123
|
+
if (!Buffer.isBuffer(msg) || msg.length < NTP_PACKET_BYTES) {
|
|
123
124
|
return done({ code: "ntp/bad-reply", message: "reply too short (" + (msg && msg.length) + " bytes)" });
|
|
124
125
|
}
|
|
125
126
|
// Bytes 40-47 = Transmit Timestamp (NTP epoch seconds.fraction)
|
|
126
127
|
var ntpSeconds = msg.readUInt32BE(40);
|
|
127
128
|
var ntpFraction = msg.readUInt32BE(44);
|
|
128
129
|
var serverUnixSeconds = ntpSeconds - NTP_TO_UNIX_OFFSET_SECONDS;
|
|
129
|
-
var fracMs = Math.round(ntpFraction / 0x100000000
|
|
130
|
-
var serverTimeMs = serverUnixSeconds
|
|
130
|
+
var fracMs = Math.round(C.TIME.seconds(ntpFraction / 0x100000000));
|
|
131
|
+
var serverTimeMs = C.TIME.seconds(serverUnixSeconds) + fracMs;
|
|
131
132
|
|
|
132
133
|
// Round-trip-corrected drift: assume the server's reply transmit
|
|
133
134
|
// timestamp is approximately at the midpoint of our send/receive.
|
package/lib/numeric-bounds.js
CHANGED
|
@@ -52,8 +52,40 @@ function isNonNegativeFiniteInt(value) {
|
|
|
52
52
|
Number.isInteger(value) && value >= 0;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
// requirePositiveFiniteIntIfPresent / requireNonNegativeFiniteIntIfPresent —
|
|
56
|
+
// optional-shape gates that throw via the caller's framework-error class
|
|
57
|
+
// when the value is present but invalid. Replaces the per-file
|
|
58
|
+
// `if (opts.X !== undefined) { if (!nb.isYFiniteInt(opts.X)) throw new
|
|
59
|
+
// XError(...); }` cascade that app-shutdown / bundler / dev /
|
|
60
|
+
// http-client-cookie-jar / external-db-migrate / restore-rollback
|
|
61
|
+
// previously rolled by hand.
|
|
62
|
+
//
|
|
63
|
+
// var nb = require("./numeric-bounds");
|
|
64
|
+
// nb.requirePositiveFiniteIntIfPresent(opts.graceMs,
|
|
65
|
+
// "app-shutdown.create: opts.graceMs", AppShutdownError,
|
|
66
|
+
// "app-shutdown/bad-grace-ms");
|
|
67
|
+
function requirePositiveFiniteIntIfPresent(value, label, errorClass, code) {
|
|
68
|
+
if (value === undefined) return value;
|
|
69
|
+
if (!isPositiveFiniteInt(value)) {
|
|
70
|
+
throw new errorClass(code,
|
|
71
|
+
(label || "value") + " must be a positive finite integer; got " + shape(value));
|
|
72
|
+
}
|
|
73
|
+
return value;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function requireNonNegativeFiniteIntIfPresent(value, label, errorClass, code) {
|
|
77
|
+
if (value === undefined) return value;
|
|
78
|
+
if (!isNonNegativeFiniteInt(value)) {
|
|
79
|
+
throw new errorClass(code,
|
|
80
|
+
(label || "value") + " must be a non-negative finite integer; got " + shape(value));
|
|
81
|
+
}
|
|
82
|
+
return value;
|
|
83
|
+
}
|
|
84
|
+
|
|
55
85
|
module.exports = {
|
|
56
|
-
shape:
|
|
57
|
-
isPositiveFiniteInt:
|
|
58
|
-
isNonNegativeFiniteInt:
|
|
86
|
+
shape: shape,
|
|
87
|
+
isPositiveFiniteInt: isPositiveFiniteInt,
|
|
88
|
+
isNonNegativeFiniteInt: isNonNegativeFiniteInt,
|
|
89
|
+
requirePositiveFiniteIntIfPresent: requirePositiveFiniteIntIfPresent,
|
|
90
|
+
requireNonNegativeFiniteIntIfPresent: requireNonNegativeFiniteIntIfPresent,
|
|
59
91
|
};
|
|
@@ -29,12 +29,42 @@
|
|
|
29
29
|
* gap rather than half-implementing one auth path.
|
|
30
30
|
*/
|
|
31
31
|
var azureBlob = require("./azure-blob");
|
|
32
|
+
var C = require("../constants");
|
|
32
33
|
var httpClient = require("../http-client");
|
|
34
|
+
var requestHelpers = require("../request-helpers");
|
|
33
35
|
var safeUrl = require("../safe-url");
|
|
34
36
|
var { ObjectStoreError } = require("../framework-error");
|
|
35
37
|
|
|
36
38
|
var _err = ObjectStoreError.factory;
|
|
37
39
|
|
|
40
|
+
// HTTP status constants used in the bucket-ops expectStatus arrays.
|
|
41
|
+
// OK / NOT_FOUND / CONFLICT come from the framework's HTTP_STATUS table;
|
|
42
|
+
// CREATED (201) and ACCEPTED (202) are not in the table, so they're
|
|
43
|
+
// named locally with their RFC 7231 values.
|
|
44
|
+
var HTTP_OK = requestHelpers.HTTP_STATUS.OK;
|
|
45
|
+
var HTTP_NOT_FOUND = requestHelpers.HTTP_STATUS.NOT_FOUND;
|
|
46
|
+
var HTTP_CONFLICT = requestHelpers.HTTP_STATUS.CONFLICT;
|
|
47
|
+
var HTTP_CREATED = 201; // RFC 7231 §6.3.2
|
|
48
|
+
var HTTP_ACCEPTED = 202; // RFC 7231 §6.3.3
|
|
49
|
+
|
|
50
|
+
// Internal URL builder — endpoint + path string from validated config.
|
|
51
|
+
// Routes through safeUrl.parse so the protocol allowlist + length cap
|
|
52
|
+
// apply uniformly.
|
|
53
|
+
function _internalUrl(input, allowedProtocols) {
|
|
54
|
+
return safeUrl.parse(input, {
|
|
55
|
+
allowedProtocols: allowedProtocols || safeUrl.ALLOW_HTTP_TLS,
|
|
56
|
+
errorClass: ObjectStoreError,
|
|
57
|
+
maxUrlLength: C.BYTES.kib(32),
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Maximum length for a tag passed into the framework-internal XML
|
|
62
|
+
// extractor. The XML payloads come back from Azure (server-controlled)
|
|
63
|
+
// but the tag name is framework-supplied; cap it explicitly so the
|
|
64
|
+
// open/close substrings used for indexOf walks can't be driven against
|
|
65
|
+
// an oversize pattern.
|
|
66
|
+
var MAX_XML_TAG_LENGTH = 128;
|
|
67
|
+
|
|
38
68
|
// Azure container names: 3-63 chars, lowercase alphanumeric + hyphens,
|
|
39
69
|
// no consecutive hyphens, must start and end with letter or digit.
|
|
40
70
|
var CONTAINER_NAME_RE = /^[a-z0-9](?:[a-z0-9]|-(?!-))*[a-z0-9]$/;
|
|
@@ -49,7 +79,9 @@ function _validateContainerName(name) {
|
|
|
49
79
|
"azure-blob bucketOps: container name must be 3-63 chars (got " +
|
|
50
80
|
name.length + ")", true);
|
|
51
81
|
}
|
|
52
|
-
|
|
82
|
+
// Length is bounded above (3..63) before the regex runs so the
|
|
83
|
+
// pattern can't be driven against an unbounded input.
|
|
84
|
+
if (name.length > 63 || !CONTAINER_NAME_RE.test(name)) {
|
|
53
85
|
throw _err("BUCKET_INVALID_NAME",
|
|
54
86
|
"azure-blob bucketOps: container name '" + name + "' is invalid; " +
|
|
55
87
|
"lowercase letters / digits / hyphens only, no consecutive hyphens, " +
|
|
@@ -119,16 +151,42 @@ function _buildCorsXml(rules) {
|
|
|
119
151
|
|
|
120
152
|
// Tiny XML extractor — pulls every occurrence of <Tag>value</Tag>
|
|
121
153
|
// and returns an array of value strings. Sufficient for the limited
|
|
122
|
-
// shapes we read (Containers list, container metadata).
|
|
154
|
+
// shapes we read (Containers list, container metadata). The tag is
|
|
155
|
+
// framework-supplied (call sites pass hardcoded tag names such as
|
|
156
|
+
// "Container", "Name", "Last-Modified") but cap the length explicitly
|
|
157
|
+
// so the open/close substrings used for indexOf walks can't be driven
|
|
158
|
+
// against an oversize pattern under any future call shape.
|
|
159
|
+
function _validateTag(tag) {
|
|
160
|
+
if (typeof tag !== "string" || tag.length === 0 || tag.length > MAX_XML_TAG_LENGTH) {
|
|
161
|
+
throw _err("BUCKET_INVALID_NAME",
|
|
162
|
+
"azure-blob bucketOps: XML tag must be a non-empty string of length " +
|
|
163
|
+
"<= " + MAX_XML_TAG_LENGTH, true);
|
|
164
|
+
}
|
|
165
|
+
if (!/^[A-Za-z][A-Za-z0-9_.-]*$/.test(tag)) {
|
|
166
|
+
throw _err("BUCKET_INVALID_NAME",
|
|
167
|
+
"azure-blob bucketOps: XML tag '" + tag + "' has invalid characters", true);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
123
171
|
function _extractAll(xml, tag) {
|
|
172
|
+
_validateTag(tag);
|
|
173
|
+
var open = "<" + tag + ">";
|
|
174
|
+
var close = "</" + tag + ">";
|
|
124
175
|
var out = [];
|
|
125
|
-
var
|
|
126
|
-
|
|
127
|
-
|
|
176
|
+
var i = 0;
|
|
177
|
+
while (true) {
|
|
178
|
+
var s = xml.indexOf(open, i);
|
|
179
|
+
if (s === -1) break;
|
|
180
|
+
var e = xml.indexOf(close, s + open.length);
|
|
181
|
+
if (e === -1) break;
|
|
182
|
+
out.push(xml.slice(s + open.length, e));
|
|
183
|
+
i = e + close.length;
|
|
184
|
+
}
|
|
128
185
|
return out;
|
|
129
186
|
}
|
|
130
187
|
|
|
131
188
|
function _extractBlocks(xml, tag) {
|
|
189
|
+
_validateTag(tag);
|
|
132
190
|
var open = "<" + tag + ">";
|
|
133
191
|
var close = "</" + tag + ">";
|
|
134
192
|
var blocks = [];
|
|
@@ -202,7 +260,7 @@ function create(config) {
|
|
|
202
260
|
async function createContainer(name, opts) {
|
|
203
261
|
_validateContainerName(name);
|
|
204
262
|
opts = opts || {};
|
|
205
|
-
var url =
|
|
263
|
+
var url = _internalUrl(endpoint + "/" + name + "?restype=container", allowedProtocols);
|
|
206
264
|
var headers = { "Content-Length": "0" };
|
|
207
265
|
if (opts.publicAccess) {
|
|
208
266
|
if (opts.publicAccess !== "blob" && opts.publicAccess !== "container") {
|
|
@@ -213,8 +271,8 @@ function create(config) {
|
|
|
213
271
|
headers["x-ms-blob-public-access"] = opts.publicAccess;
|
|
214
272
|
}
|
|
215
273
|
var signed = _sign("PUT", url, headers);
|
|
216
|
-
var res = await _request("PUT", url, signed, null, [
|
|
217
|
-
if (res.statusCode ===
|
|
274
|
+
var res = await _request("PUT", url, signed, null, [HTTP_CREATED, HTTP_CONFLICT]);
|
|
275
|
+
if (res.statusCode === HTTP_CONFLICT) {
|
|
218
276
|
throw _err("BUCKET_ALREADY_OWNED",
|
|
219
277
|
"azure-blob bucketOps: container '" + name +
|
|
220
278
|
"' already exists or was recently deleted", true);
|
|
@@ -224,19 +282,19 @@ function create(config) {
|
|
|
224
282
|
|
|
225
283
|
async function deleteContainer(name) {
|
|
226
284
|
_validateContainerName(name);
|
|
227
|
-
var url =
|
|
285
|
+
var url = _internalUrl(endpoint + "/" + name + "?restype=container", allowedProtocols);
|
|
228
286
|
var signed = _sign("DELETE", url, {});
|
|
229
|
-
var res = await _request("DELETE", url, signed, null, [
|
|
230
|
-
return res.statusCode ===
|
|
287
|
+
var res = await _request("DELETE", url, signed, null, [HTTP_ACCEPTED, HTTP_NOT_FOUND]);
|
|
288
|
+
return res.statusCode === HTTP_ACCEPTED;
|
|
231
289
|
}
|
|
232
290
|
|
|
233
291
|
async function listContainers(opts) {
|
|
234
292
|
opts = opts || {};
|
|
235
|
-
var url =
|
|
293
|
+
var url = _internalUrl(endpoint + "/?comp=list", allowedProtocols);
|
|
236
294
|
if (opts.prefix) url.searchParams.set("prefix", opts.prefix);
|
|
237
295
|
if (opts.maxResults != null) url.searchParams.set("maxresults", String(opts.maxResults));
|
|
238
296
|
var signed = _sign("GET", url, {});
|
|
239
|
-
var res = await _request("GET", url, signed, null, [
|
|
297
|
+
var res = await _request("GET", url, signed, null, [HTTP_OK]);
|
|
240
298
|
var xml = Buffer.isBuffer(res.body) ? res.body.toString("utf8") :
|
|
241
299
|
typeof res.body === "string" ? res.body :
|
|
242
300
|
"";
|
|
@@ -261,13 +319,13 @@ function create(config) {
|
|
|
261
319
|
rules.forEach(_validateCorsRule);
|
|
262
320
|
var xml = _buildCorsXml(rules);
|
|
263
321
|
var bodyBuf = Buffer.from(xml, "utf8");
|
|
264
|
-
var url =
|
|
322
|
+
var url = _internalUrl(endpoint + "/?restype=service&comp=properties", allowedProtocols);
|
|
265
323
|
var headers = {
|
|
266
324
|
"Content-Type": "application/xml",
|
|
267
325
|
"Content-Length": String(bodyBuf.length),
|
|
268
326
|
};
|
|
269
327
|
var signed = _sign("PUT", url, headers);
|
|
270
|
-
await _request("PUT", url, signed, bodyBuf, [
|
|
328
|
+
await _request("PUT", url, signed, bodyBuf, [HTTP_ACCEPTED]);
|
|
271
329
|
return { rulesApplied: rules.length };
|
|
272
330
|
}
|
|
273
331
|
|