@blamejs/core 0.6.70 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/README.md +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 +98 -52
- 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
|
@@ -34,18 +34,31 @@ var nodeCrypto = require("crypto");
|
|
|
34
34
|
var { URL } = require("url");
|
|
35
35
|
var { Readable } = require("stream");
|
|
36
36
|
var safeXml = require("../parsers/safe-xml");
|
|
37
|
+
var sharedRequest = require("./http-request");
|
|
37
38
|
var C = require("../constants");
|
|
38
39
|
var { ObjectStoreError } = require("../framework-error");
|
|
39
|
-
var
|
|
40
|
+
var time = require("../time");
|
|
40
41
|
var safeUrl = require("../safe-url");
|
|
41
42
|
|
|
42
43
|
// Azure Blob list responses are commonly multi-thousand-key paginated
|
|
43
44
|
// payloads — well above the parser's default 1 MiB / 10K element ceilings.
|
|
44
45
|
var LIST_PARSE_OPTS = {
|
|
45
46
|
maxBytes: C.BYTES.mib(8),
|
|
46
|
-
maxElements: 50000,
|
|
47
|
+
maxElements: C.BYTES.bytes(50000),
|
|
47
48
|
};
|
|
48
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. Cap is generous since SAS-presigned URLs carry many
|
|
53
|
+
// query params.
|
|
54
|
+
function _internalUrl(input, allowedProtocols) {
|
|
55
|
+
return safeUrl.parse(input, {
|
|
56
|
+
allowedProtocols: allowedProtocols || safeUrl.ALLOW_HTTP_TLS,
|
|
57
|
+
errorClass: ObjectStoreError,
|
|
58
|
+
maxUrlLength: C.BYTES.kib(32),
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
49
62
|
function _arrayify(value) {
|
|
50
63
|
if (value == null) return [];
|
|
51
64
|
return Array.isArray(value) ? value : [value];
|
|
@@ -57,25 +70,13 @@ var DEFAULT_API_VERSION = "2024-08-04";
|
|
|
57
70
|
// matching the SigV4 / V4 7-day cap keeps semantics uniform across
|
|
58
71
|
// backends and reflects best practice (long-lived SAS tokens are
|
|
59
72
|
// effectively credentials).
|
|
60
|
-
var SAS_DEFAULT_EXPIRES_SECONDS = 15
|
|
61
|
-
var SAS_MAX_EXPIRES_SECONDS = 7
|
|
73
|
+
var SAS_DEFAULT_EXPIRES_SECONDS = C.TIME.minutes(15) / C.TIME.seconds(1);
|
|
74
|
+
var SAS_MAX_EXPIRES_SECONDS = C.TIME.days(7) / C.TIME.seconds(1);
|
|
62
75
|
var SAS_MIN_EXPIRES_SECONDS = 1;
|
|
63
76
|
|
|
64
77
|
var _err = ObjectStoreError.factory;
|
|
65
78
|
|
|
66
|
-
|
|
67
|
-
return httpClient.request({
|
|
68
|
-
method: method,
|
|
69
|
-
url: urlObj,
|
|
70
|
-
headers: headers,
|
|
71
|
-
body: body,
|
|
72
|
-
idleTimeoutMs: opts && opts.timeoutMs,
|
|
73
|
-
maxResponseBytes: opts && opts.maxResponseBytes,
|
|
74
|
-
errorClass: ObjectStoreError,
|
|
75
|
-
allowedProtocols: opts && opts.allowedProtocols,
|
|
76
|
-
...((opts && opts.allowInternal !== undefined) ? { allowInternal: opts.allowInternal } : {}),
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
+
var _httpRequest = sharedRequest;
|
|
79
80
|
|
|
80
81
|
// ---- Shared Key signing ----
|
|
81
82
|
//
|
|
@@ -101,7 +102,7 @@ function _httpRequest(method, urlObj, headers, body, opts) {
|
|
|
101
102
|
|
|
102
103
|
function buildStringToSign(opts) {
|
|
103
104
|
var headers = opts.headers || {};
|
|
104
|
-
var url = opts.url instanceof URL ? opts.url :
|
|
105
|
+
var url = opts.url instanceof URL ? opts.url : _internalUrl(opts.url);
|
|
105
106
|
|
|
106
107
|
var canonicalHeaders = (function () {
|
|
107
108
|
var pairs = [];
|
|
@@ -133,7 +134,13 @@ function buildStringToSign(opts) {
|
|
|
133
134
|
if (!grouped[p[0]]) grouped[p[0]] = [];
|
|
134
135
|
grouped[p[0]].push(p[1]);
|
|
135
136
|
});
|
|
136
|
-
|
|
137
|
+
// Azure Shared Key canonicalized resource: sorted query param names,
|
|
138
|
+
// each on its own line as `name:value` — Azure-spec, not JSON
|
|
139
|
+
// canonicalization.
|
|
140
|
+
var groupedKeys = [];
|
|
141
|
+
for (var gk in grouped) groupedKeys.push(gk);
|
|
142
|
+
groupedKeys.sort();
|
|
143
|
+
var queryLines = groupedKeys.map(function (name) {
|
|
137
144
|
return "\n" + name + ":" + grouped[name].join(",");
|
|
138
145
|
}).join("");
|
|
139
146
|
return resourcePath + queryLines;
|
|
@@ -163,7 +170,7 @@ function signRequest(opts) {
|
|
|
163
170
|
if (!headers["x-ms-version"]) headers["x-ms-version"] = opts.apiVersion || DEFAULT_API_VERSION;
|
|
164
171
|
if (!headers["x-ms-date"]) headers["x-ms-date"] = new Date().toUTCString();
|
|
165
172
|
|
|
166
|
-
var url = opts.url instanceof URL ? opts.url :
|
|
173
|
+
var url = opts.url instanceof URL ? opts.url : _internalUrl(opts.url);
|
|
167
174
|
if (!headers["host"]) headers["host"] = url.host;
|
|
168
175
|
|
|
169
176
|
var sts = buildStringToSign({
|
|
@@ -200,7 +207,7 @@ function create(config) {
|
|
|
200
207
|
if (allowInternal !== null) reqOpts.allowInternal = allowInternal;
|
|
201
208
|
|
|
202
209
|
function _blobUrl(key, params) {
|
|
203
|
-
var u =
|
|
210
|
+
var u = _internalUrl(endpoint + "/" + config.container + "/" + key, allowedProtocols);
|
|
204
211
|
if (params) {
|
|
205
212
|
Object.keys(params).forEach(function (k) { u.searchParams.set(k, params[k]); });
|
|
206
213
|
}
|
|
@@ -208,7 +215,7 @@ function create(config) {
|
|
|
208
215
|
}
|
|
209
216
|
|
|
210
217
|
function _containerUrl(params) {
|
|
211
|
-
var u =
|
|
218
|
+
var u = _internalUrl(endpoint + "/" + config.container, allowedProtocols);
|
|
212
219
|
if (params) {
|
|
213
220
|
Object.keys(params).forEach(function (k) { u.searchParams.set(k, params[k]); });
|
|
214
221
|
}
|
|
@@ -323,7 +330,7 @@ function create(config) {
|
|
|
323
330
|
var nowDate = opts.date || new Date();
|
|
324
331
|
var expiry = new Date(nowDate.getTime() + C.TIME.seconds(expiresIn));
|
|
325
332
|
// Azure accepts ISO 8601 with second precision; strip ms.
|
|
326
|
-
var signedExpiry =
|
|
333
|
+
var signedExpiry = time.toIso8601NoMs(expiry);
|
|
327
334
|
var signedStart = ""; // omitted = SAS valid immediately
|
|
328
335
|
var signedVersion = apiVersion;
|
|
329
336
|
var signedResource = "b"; // blob
|
|
@@ -383,7 +390,7 @@ function create(config) {
|
|
|
383
390
|
}
|
|
384
391
|
|
|
385
392
|
var token = _buildSasToken(permissions, opts);
|
|
386
|
-
var url =
|
|
393
|
+
var url = _internalUrl(endpoint + "/" + config.container + "/" + opts.key + "?" + token.sas, allowedProtocols);
|
|
387
394
|
|
|
388
395
|
var clientHeaders = {};
|
|
389
396
|
if (opts.contentType) clientHeaders["Content-Type"] = opts.contentType;
|
|
@@ -30,10 +30,28 @@ var httpClient = require("../http-client");
|
|
|
30
30
|
var safeJson = require("../safe-json");
|
|
31
31
|
var safeUrl = require("../safe-url");
|
|
32
32
|
var C = require("../constants");
|
|
33
|
+
var requestHelpers = require("../request-helpers");
|
|
33
34
|
var { ObjectStoreError } = require("../framework-error");
|
|
34
35
|
|
|
35
36
|
var _err = ObjectStoreError.factory;
|
|
36
37
|
|
|
38
|
+
// HTTP status constants used in the bucket-ops expectStatus arrays.
|
|
39
|
+
var HTTP_OK = requestHelpers.HTTP_STATUS.OK;
|
|
40
|
+
var HTTP_NO_CONTENT = requestHelpers.HTTP_STATUS.NO_CONTENT;
|
|
41
|
+
var HTTP_NOT_FOUND = requestHelpers.HTTP_STATUS.NOT_FOUND;
|
|
42
|
+
var HTTP_CONFLICT = requestHelpers.HTTP_STATUS.CONFLICT;
|
|
43
|
+
|
|
44
|
+
// Internal URL builder — endpoint + path string from validated config.
|
|
45
|
+
// Routes through safeUrl.parse so the protocol allowlist + length cap
|
|
46
|
+
// apply uniformly.
|
|
47
|
+
function _internalUrl(input, allowedProtocols) {
|
|
48
|
+
return safeUrl.parse(input, {
|
|
49
|
+
allowedProtocols: allowedProtocols || safeUrl.ALLOW_HTTP_TLS,
|
|
50
|
+
errorClass: ObjectStoreError,
|
|
51
|
+
maxUrlLength: C.BYTES.kib(32),
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
37
55
|
// GCS bucket names: 3-63 chars, lowercase letters / digits / hyphens /
|
|
38
56
|
// underscores / dots; can't start or end with hyphen; can't contain '..'
|
|
39
57
|
// or 'goog' prefix; can't be IP address. Most violations the API will
|
|
@@ -49,7 +67,9 @@ function _validateBucketName(name) {
|
|
|
49
67
|
throw _err("BUCKET_INVALID_NAME",
|
|
50
68
|
"gcs bucketOps: bucket name must be 3-63 chars (got " + name.length + ")", true);
|
|
51
69
|
}
|
|
52
|
-
|
|
70
|
+
// Length is bounded above (3..63) before the regex runs so the
|
|
71
|
+
// pattern can't be driven against an unbounded input.
|
|
72
|
+
if (name.length > 63 || !BUCKET_NAME_RE.test(name)) {
|
|
53
73
|
throw _err("BUCKET_INVALID_NAME",
|
|
54
74
|
"gcs bucketOps: bucket name '" + name + "' is invalid; lowercase " +
|
|
55
75
|
"letters / digits / hyphens / underscores / dots only, must start " +
|
|
@@ -194,10 +214,10 @@ function create(config) {
|
|
|
194
214
|
var bodyStr = "grant_type=" + encodeURIComponent("urn:ietf:params:oauth:grant-type:jwt-bearer") +
|
|
195
215
|
"&assertion=" + encodeURIComponent(assertion);
|
|
196
216
|
var bodyBuf = Buffer.from(bodyStr, "utf8");
|
|
197
|
-
var res = await _request("POST",
|
|
217
|
+
var res = await _request("POST", _internalUrl(tokenEndpoint, allowedProtocols), {
|
|
198
218
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
199
219
|
"Content-Length": String(bodyBuf.length),
|
|
200
|
-
}, bodyBuf, [
|
|
220
|
+
}, bodyBuf, [HTTP_OK]);
|
|
201
221
|
var tokenResp = safeJson.parse(res.body);
|
|
202
222
|
if (!tokenResp.access_token) {
|
|
203
223
|
throw _err("AUTH_FAILED",
|
|
@@ -212,15 +232,19 @@ function create(config) {
|
|
|
212
232
|
return cachedToken.accessToken;
|
|
213
233
|
}
|
|
214
234
|
|
|
215
|
-
|
|
216
|
-
|
|
235
|
+
// Returns a parsed URL ready for `.searchParams` mutation. The bucket
|
|
236
|
+
// base path is "/storage/v1/b"; suffix (e.g., "/" + bucket name) is
|
|
237
|
+
// appended before parsing so safeUrl.parse owns every URL we hand to
|
|
238
|
+
// httpClient.
|
|
239
|
+
function _bucketBaseUrl(suffix) {
|
|
240
|
+
return _internalUrl(endpoint + "/storage/v1/b" + (suffix || ""), allowedProtocols);
|
|
217
241
|
}
|
|
218
242
|
|
|
219
243
|
async function createBucket(name, opts) {
|
|
220
244
|
_validateBucketName(name);
|
|
221
245
|
opts = opts || {};
|
|
222
246
|
var token = await _ensureToken();
|
|
223
|
-
var url =
|
|
247
|
+
var url = _bucketBaseUrl();
|
|
224
248
|
url.searchParams.set("project", projectId);
|
|
225
249
|
var bodyObj = { name: name };
|
|
226
250
|
if (opts.location) bodyObj.location = opts.location;
|
|
@@ -231,8 +255,8 @@ function create(config) {
|
|
|
231
255
|
"Content-Type": "application/json",
|
|
232
256
|
"Content-Length": String(bodyBuf.length),
|
|
233
257
|
});
|
|
234
|
-
var res = await _request("POST", url, headers, bodyBuf, [
|
|
235
|
-
if (res.statusCode ===
|
|
258
|
+
var res = await _request("POST", url, headers, bodyBuf, [HTTP_OK, HTTP_CONFLICT]);
|
|
259
|
+
if (res.statusCode === HTTP_CONFLICT) {
|
|
236
260
|
throw _err("BUCKET_ALREADY_OWNED",
|
|
237
261
|
"gcs bucketOps: bucket '" + name + "' already exists", true);
|
|
238
262
|
}
|
|
@@ -247,22 +271,22 @@ function create(config) {
|
|
|
247
271
|
async function deleteBucket(name) {
|
|
248
272
|
_validateBucketName(name);
|
|
249
273
|
var token = await _ensureToken();
|
|
250
|
-
var url =
|
|
274
|
+
var url = _bucketBaseUrl("/" + encodeURIComponent(name));
|
|
251
275
|
var headers = authHeader.bearer(token);
|
|
252
|
-
var res = await _request("DELETE", url, headers, null, [
|
|
253
|
-
return res.statusCode ===
|
|
276
|
+
var res = await _request("DELETE", url, headers, null, [HTTP_NO_CONTENT, HTTP_NOT_FOUND]);
|
|
277
|
+
return res.statusCode === HTTP_NO_CONTENT;
|
|
254
278
|
}
|
|
255
279
|
|
|
256
280
|
async function listBuckets(opts) {
|
|
257
281
|
opts = opts || {};
|
|
258
282
|
var token = await _ensureToken();
|
|
259
|
-
var url =
|
|
283
|
+
var url = _bucketBaseUrl();
|
|
260
284
|
url.searchParams.set("project", projectId);
|
|
261
285
|
if (opts.prefix) url.searchParams.set("prefix", opts.prefix);
|
|
262
286
|
if (opts.maxResults) url.searchParams.set("maxResults", String(opts.maxResults));
|
|
263
287
|
if (opts.pageToken) url.searchParams.set("pageToken", opts.pageToken);
|
|
264
288
|
var headers = authHeader.bearer(token);
|
|
265
|
-
var res = await _request("GET", url, headers, null, [
|
|
289
|
+
var res = await _request("GET", url, headers, null, [HTTP_OK]);
|
|
266
290
|
var parsed = safeJson.parse(res.body);
|
|
267
291
|
var items = Array.isArray(parsed.items) ? parsed.items : [];
|
|
268
292
|
return items.map(function (item) {
|
|
@@ -284,14 +308,14 @@ function create(config) {
|
|
|
284
308
|
}
|
|
285
309
|
rules.forEach(_validateLifecycleRule);
|
|
286
310
|
var token = await _ensureToken();
|
|
287
|
-
var url =
|
|
311
|
+
var url = _bucketBaseUrl("/" + encodeURIComponent(name));
|
|
288
312
|
var bodyObj = { lifecycle: { rule: rules } };
|
|
289
313
|
var bodyBuf = Buffer.from(JSON.stringify(bodyObj), "utf8");
|
|
290
314
|
var headers = Object.assign(authHeader.bearer(token), {
|
|
291
315
|
"Content-Type": "application/json",
|
|
292
316
|
"Content-Length": String(bodyBuf.length),
|
|
293
317
|
});
|
|
294
|
-
await _request("PATCH", url, headers, bodyBuf, [
|
|
318
|
+
await _request("PATCH", url, headers, bodyBuf, [HTTP_OK]);
|
|
295
319
|
return { rulesApplied: rules.length };
|
|
296
320
|
}
|
|
297
321
|
|
|
@@ -303,14 +327,14 @@ function create(config) {
|
|
|
303
327
|
}
|
|
304
328
|
rules.forEach(_validateCorsRule);
|
|
305
329
|
var token = await _ensureToken();
|
|
306
|
-
var url =
|
|
330
|
+
var url = _bucketBaseUrl("/" + encodeURIComponent(name));
|
|
307
331
|
var bodyObj = { cors: rules };
|
|
308
332
|
var bodyBuf = Buffer.from(JSON.stringify(bodyObj), "utf8");
|
|
309
333
|
var headers = Object.assign(authHeader.bearer(token), {
|
|
310
334
|
"Content-Type": "application/json",
|
|
311
335
|
"Content-Length": String(bodyBuf.length),
|
|
312
336
|
});
|
|
313
|
-
await _request("PATCH", url, headers, bodyBuf, [
|
|
337
|
+
await _request("PATCH", url, headers, bodyBuf, [HTTP_OK]);
|
|
314
338
|
return { rulesApplied: rules.length };
|
|
315
339
|
}
|
|
316
340
|
|
package/lib/object-store/gcs.js
CHANGED
|
@@ -24,16 +24,28 @@
|
|
|
24
24
|
*/
|
|
25
25
|
var fs = require("fs");
|
|
26
26
|
var nodeCrypto = require("crypto");
|
|
27
|
-
var { URL } = require("url");
|
|
28
27
|
var { Readable } = require("stream");
|
|
29
28
|
var safeJson = require("../safe-json");
|
|
30
29
|
var C = require("../constants");
|
|
30
|
+
var nb = require("../numeric-bounds");
|
|
31
31
|
var { ObjectStoreError } = require("../framework-error");
|
|
32
|
-
var httpClient = require("../http-client");
|
|
33
32
|
var safeUrl = require("../safe-url");
|
|
33
|
+
var sharedRequest = require("./http-request");
|
|
34
34
|
var authHeader = require("../auth-header");
|
|
35
35
|
var sigv4 = require("./sigv4");
|
|
36
36
|
|
|
37
|
+
// Internal URL builder — endpoint + path string from validated config.
|
|
38
|
+
// Routes through safeUrl.parse so the protocol allowlist + length cap
|
|
39
|
+
// apply uniformly. Cap is generous since presigned URLs can carry many
|
|
40
|
+
// query params.
|
|
41
|
+
function _internalUrl(input, allowedProtocols) {
|
|
42
|
+
return safeUrl.parse(input, {
|
|
43
|
+
allowedProtocols: allowedProtocols || safeUrl.ALLOW_HTTP_TLS,
|
|
44
|
+
errorClass: ObjectStoreError,
|
|
45
|
+
maxUrlLength: C.BYTES.kib(32),
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
37
49
|
// V4 query-string presigning. GCS uses the same canonical-request
|
|
38
50
|
// shape as AWS SigV4, differing only in:
|
|
39
51
|
// - algorithm: GOOG4-RSA-SHA256 (RSA service-account signing)
|
|
@@ -47,8 +59,8 @@ var GCS_V4_SERVICE = "storage";
|
|
|
47
59
|
var GCS_V4_REGION = "auto";
|
|
48
60
|
|
|
49
61
|
// Same expiry bounds as SigV4 — V4 presigning shares the spec's 7-day cap.
|
|
50
|
-
var PRESIGN_DEFAULT_EXPIRES_SECONDS = 15
|
|
51
|
-
var PRESIGN_MAX_EXPIRES_SECONDS = 7
|
|
62
|
+
var PRESIGN_DEFAULT_EXPIRES_SECONDS = C.TIME.minutes(15) / C.TIME.seconds(1);
|
|
63
|
+
var PRESIGN_MAX_EXPIRES_SECONDS = C.TIME.days(7) / C.TIME.seconds(1);
|
|
52
64
|
var PRESIGN_MIN_EXPIRES_SECONDS = 1;
|
|
53
65
|
|
|
54
66
|
var SERVICE_ACCOUNT_SCHEMA = {
|
|
@@ -71,19 +83,7 @@ var _err = ObjectStoreError.factory;
|
|
|
71
83
|
|
|
72
84
|
// ---- Generic HTTP helper (separate from sigv4's; no signing here) ----
|
|
73
85
|
|
|
74
|
-
|
|
75
|
-
return httpClient.request({
|
|
76
|
-
method: method,
|
|
77
|
-
url: urlObj,
|
|
78
|
-
headers: headers,
|
|
79
|
-
body: body,
|
|
80
|
-
idleTimeoutMs: opts && opts.timeoutMs,
|
|
81
|
-
maxResponseBytes: opts && opts.maxResponseBytes,
|
|
82
|
-
errorClass: ObjectStoreError,
|
|
83
|
-
allowedProtocols: opts && opts.allowedProtocols,
|
|
84
|
-
...((opts && opts.allowInternal !== undefined) ? { allowInternal: opts.allowInternal } : {}),
|
|
85
|
-
});
|
|
86
|
-
}
|
|
86
|
+
var _httpRequest = sharedRequest;
|
|
87
87
|
|
|
88
88
|
// ---- JWT signing for service-account auth ----
|
|
89
89
|
|
|
@@ -95,14 +95,14 @@ function _base64UrlEncode(buf) {
|
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
function _signJwt(serviceAccount, scope, audience) {
|
|
98
|
-
var nowSec = Math.floor(Date.now() /
|
|
98
|
+
var nowSec = Math.floor(Date.now() / C.TIME.seconds(1));
|
|
99
99
|
var header = { alg: "RS256", typ: "JWT" };
|
|
100
100
|
var claim = {
|
|
101
101
|
iss: serviceAccount.client_email,
|
|
102
102
|
scope: scope,
|
|
103
103
|
aud: audience || TOKEN_ENDPOINT,
|
|
104
104
|
iat: nowSec,
|
|
105
|
-
exp: nowSec +
|
|
105
|
+
exp: nowSec + (C.TIME.hours(1) / C.TIME.seconds(1)),
|
|
106
106
|
};
|
|
107
107
|
var headerB64 = _base64UrlEncode(JSON.stringify(header));
|
|
108
108
|
var claimB64 = _base64UrlEncode(JSON.stringify(claim));
|
|
@@ -163,7 +163,7 @@ function create(config) {
|
|
|
163
163
|
var bodyBuf = Buffer.from(bodyStr, "utf8");
|
|
164
164
|
var res = await _httpRequest(
|
|
165
165
|
"POST",
|
|
166
|
-
|
|
166
|
+
_internalUrl(tokenEndpoint, allowedProtocols),
|
|
167
167
|
{
|
|
168
168
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
169
169
|
"Content-Length": String(bodyBuf.length),
|
|
@@ -184,8 +184,8 @@ function create(config) {
|
|
|
184
184
|
}
|
|
185
185
|
|
|
186
186
|
function _objectUrl(key, params) {
|
|
187
|
-
var u =
|
|
188
|
-
"/o/" + encodeURIComponent(key));
|
|
187
|
+
var u = _internalUrl(endpoint + "/storage/v1/b/" + encodeURIComponent(bucket) +
|
|
188
|
+
"/o/" + encodeURIComponent(key), allowedProtocols);
|
|
189
189
|
if (params) {
|
|
190
190
|
Object.keys(params).forEach(function (k) { u.searchParams.set(k, params[k]); });
|
|
191
191
|
}
|
|
@@ -193,14 +193,14 @@ function create(config) {
|
|
|
193
193
|
}
|
|
194
194
|
|
|
195
195
|
function _uploadUrl(key) {
|
|
196
|
-
var u =
|
|
196
|
+
var u = _internalUrl(endpoint + "/upload/storage/v1/b/" + encodeURIComponent(bucket) + "/o", allowedProtocols);
|
|
197
197
|
u.searchParams.set("uploadType", "media");
|
|
198
198
|
u.searchParams.set("name", key);
|
|
199
199
|
return u;
|
|
200
200
|
}
|
|
201
201
|
|
|
202
202
|
function _listUrl(prefix, opts) {
|
|
203
|
-
var u =
|
|
203
|
+
var u = _internalUrl(endpoint + "/storage/v1/b/" + encodeURIComponent(bucket) + "/o", allowedProtocols);
|
|
204
204
|
if (prefix) u.searchParams.set("prefix", prefix);
|
|
205
205
|
if (opts && opts.maxResults) u.searchParams.set("maxResults", String(opts.maxResults));
|
|
206
206
|
if (opts && opts.pageToken) u.searchParams.set("pageToken", opts.pageToken);
|
|
@@ -303,19 +303,22 @@ function create(config) {
|
|
|
303
303
|
var encodedKey = opts.key.split("/")
|
|
304
304
|
.map(function (s) { return sigv4.awsUriEncode(s, true); })
|
|
305
305
|
.join("/");
|
|
306
|
-
var url =
|
|
306
|
+
var url = _internalUrl(endpoint + "/" + encodeURIComponent(bucket) + "/" + encodedKey, allowedProtocols);
|
|
307
307
|
|
|
308
308
|
var date = opts.date || new Date();
|
|
309
309
|
// Reuse SigV4's date formatters — the wire format is identical.
|
|
310
|
-
var
|
|
311
|
-
var
|
|
312
|
-
var dateStamp = date.toISOString().slice(0, 10).replace(/-/g, "");
|
|
310
|
+
var amzDate = sigv4.formatAmzDate(date);
|
|
311
|
+
var dateStamp = sigv4.formatDateStamp(date);
|
|
313
312
|
var credentialScope = dateStamp + "/" + GCS_V4_REGION + "/" +
|
|
314
313
|
GCS_V4_SERVICE + "/goog4_request";
|
|
315
314
|
|
|
316
315
|
var headers = { host: url.host };
|
|
317
316
|
if (opts.contentType) headers["content-type"] = opts.contentType;
|
|
318
|
-
|
|
317
|
+
// GCS V4 SignedHeaders: lowercase, semicolon-joined, AWS-spec order.
|
|
318
|
+
var signedHeaderKeys = [];
|
|
319
|
+
for (var hk in headers) signedHeaderKeys.push(hk);
|
|
320
|
+
signedHeaderKeys.sort();
|
|
321
|
+
var signedHeadersStr = signedHeaderKeys.join(";");
|
|
319
322
|
|
|
320
323
|
url.searchParams.set("X-Goog-Algorithm", GCS_V4_ALGORITHM);
|
|
321
324
|
url.searchParams.set("X-Goog-Credential", serviceAccount.client_email + "/" + credentialScope);
|
|
@@ -379,7 +382,12 @@ function create(config) {
|
|
|
379
382
|
"POST-form policy enforces body size via the content-length-range condition; " +
|
|
380
383
|
"use presignedUploadUrl if size enforcement is not needed", true);
|
|
381
384
|
}
|
|
382
|
-
|
|
385
|
+
if (opts.minBytes !== undefined && !nb.isNonNegativeFiniteInt(opts.minBytes)) {
|
|
386
|
+
throw _err("INVALID_MIN_BYTES",
|
|
387
|
+
"presignedUploadPolicy: minBytes must be a non-negative finite integer; got " +
|
|
388
|
+
nb.shape(opts.minBytes), true);
|
|
389
|
+
}
|
|
390
|
+
var minBytes = opts.minBytes !== undefined ? opts.minBytes : 0;
|
|
383
391
|
var expiresIn = opts.expiresIn != null ? opts.expiresIn : PRESIGN_DEFAULT_EXPIRES_SECONDS;
|
|
384
392
|
if (typeof expiresIn !== "number" ||
|
|
385
393
|
expiresIn < PRESIGN_MIN_EXPIRES_SECONDS ||
|
|
@@ -391,9 +399,8 @@ function create(config) {
|
|
|
391
399
|
}
|
|
392
400
|
|
|
393
401
|
var date = opts.date || new Date();
|
|
394
|
-
var
|
|
395
|
-
var
|
|
396
|
-
var dateStamp = date.toISOString().slice(0, 10).replace(/-/g, "");
|
|
402
|
+
var amzDate = sigv4.formatAmzDate(date);
|
|
403
|
+
var dateStamp = sigv4.formatDateStamp(date);
|
|
397
404
|
var credentialScope = dateStamp + "/" + GCS_V4_REGION + "/" +
|
|
398
405
|
GCS_V4_SERVICE + "/goog4_request";
|
|
399
406
|
var credential = serviceAccount.client_email + "/" + credentialScope;
|
|
@@ -431,7 +438,7 @@ function create(config) {
|
|
|
431
438
|
};
|
|
432
439
|
if (opts.contentType) fields["content-type"] = opts.contentType;
|
|
433
440
|
|
|
434
|
-
var url =
|
|
441
|
+
var url = _internalUrl(endpoint + "/" + encodeURIComponent(bucket) + "/", allowedProtocols);
|
|
435
442
|
|
|
436
443
|
return {
|
|
437
444
|
url: url.toString(),
|
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
*/
|
|
19
19
|
var { Readable } = require("stream");
|
|
20
20
|
var { ObjectStoreError } = require("../framework-error");
|
|
21
|
-
var httpClient = require("../http-client");
|
|
22
21
|
var safeUrl = require("../safe-url");
|
|
22
|
+
var sharedRequest = require("./http-request");
|
|
23
23
|
var authHeader = require("../auth-header");
|
|
24
24
|
|
|
25
25
|
var _err = ObjectStoreError.factory;
|
|
@@ -36,16 +36,7 @@ function _authHeaders(authConfig) {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
function _request(method, url, body, headers, opts) {
|
|
39
|
-
return
|
|
40
|
-
method: method,
|
|
41
|
-
url: url,
|
|
42
|
-
headers: headers,
|
|
43
|
-
body: body,
|
|
44
|
-
idleTimeoutMs: opts && opts.timeoutMs,
|
|
45
|
-
errorClass: ObjectStoreError,
|
|
46
|
-
allowedProtocols: opts && opts.allowedProtocols,
|
|
47
|
-
...((opts && opts.allowInternal !== undefined) ? { allowInternal: opts.allowInternal } : {}),
|
|
48
|
-
});
|
|
39
|
+
return sharedRequest(method, url, headers, body, opts);
|
|
49
40
|
}
|
|
50
41
|
|
|
51
42
|
function _keyToUrl(baseUrl, key) {
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Shared HTTP-request helper for object-store backends.
|
|
4
|
+
*
|
|
5
|
+
* Every protocol backend (azure-blob / gcs / sigv4 / http-put)
|
|
6
|
+
* previously rolled an identical wrapper around httpClient.request that
|
|
7
|
+
* threaded the same five opts: idleTimeoutMs / maxResponseBytes /
|
|
8
|
+
* errorClass / allowedProtocols / allowInternal. Centralized here so a
|
|
9
|
+
* future change to the call shape (new opt, different error class
|
|
10
|
+
* default, etc.) is a one-file change.
|
|
11
|
+
*
|
|
12
|
+
* var sharedRequest = require("./http-request");
|
|
13
|
+
* sharedRequest("PUT", url, headers, body, { timeoutMs: 5000 });
|
|
14
|
+
*
|
|
15
|
+
* The errorClass defaults to ObjectStoreError; backends that need a
|
|
16
|
+
* different class pass `errorClass: SomeError` in opts.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
var httpClient = require("../http-client");
|
|
20
|
+
var { ObjectStoreError } = require("../framework-error");
|
|
21
|
+
|
|
22
|
+
function request(method, url, headers, body, opts) {
|
|
23
|
+
opts = opts || {};
|
|
24
|
+
var req = {
|
|
25
|
+
method: method,
|
|
26
|
+
url: url,
|
|
27
|
+
headers: headers,
|
|
28
|
+
body: body,
|
|
29
|
+
idleTimeoutMs: opts.timeoutMs,
|
|
30
|
+
errorClass: opts.errorClass || ObjectStoreError,
|
|
31
|
+
allowedProtocols: opts.allowedProtocols,
|
|
32
|
+
};
|
|
33
|
+
if (opts.maxResponseBytes !== undefined) req.maxResponseBytes = opts.maxResponseBytes;
|
|
34
|
+
if (opts.allowInternal !== undefined) req.allowInternal = opts.allowInternal;
|
|
35
|
+
return httpClient.request(req);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
module.exports = request;
|
|
@@ -35,7 +35,7 @@ var gcsProto = require("./gcs");
|
|
|
35
35
|
var gcsBucketOps = require("./gcs-bucket-ops");
|
|
36
36
|
var azureBlobProto = require("./azure-blob");
|
|
37
37
|
var azureBlobBucketOps = require("./azure-blob-bucket-ops");
|
|
38
|
-
var retryHelper = require("
|
|
38
|
+
var retryHelper = require("../retry");
|
|
39
39
|
var protocolDispatcher = require("../protocol-dispatcher");
|
|
40
40
|
var { ObjectStoreError } = require("../framework-error");
|
|
41
41
|
|