@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.
Files changed (164) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +11 -1
  3. package/index.js +2 -5
  4. package/lib/api-key.js +24 -55
  5. package/lib/api-snapshot.js +4 -1
  6. package/lib/app-shutdown.js +20 -16
  7. package/lib/app.js +6 -5
  8. package/lib/archive.js +58 -50
  9. package/lib/atomic-file.js +11 -5
  10. package/lib/audit-chain.js +12 -4
  11. package/lib/audit-sign.js +38 -13
  12. package/lib/audit-tools.js +4 -4
  13. package/lib/audit.js +8 -4
  14. package/lib/auth/jwt.js +3 -2
  15. package/lib/auth/lockout.js +3 -13
  16. package/lib/auth/oauth.js +27 -17
  17. package/lib/auth/passkey.js +1 -5
  18. package/lib/auth/password.js +23 -13
  19. package/lib/backup/bundle.js +2 -4
  20. package/lib/backup/crypto.js +8 -6
  21. package/lib/backup/index.js +18 -14
  22. package/lib/backup/manifest.js +6 -2
  23. package/lib/break-glass.js +12 -7
  24. package/lib/bundler.js +32 -21
  25. package/lib/cache-redis.js +8 -3
  26. package/lib/cache.js +39 -59
  27. package/lib/canonical-json.js +17 -2
  28. package/lib/chain-writer.js +2 -2
  29. package/lib/cli-helpers.js +9 -4
  30. package/lib/cli.js +27 -5
  31. package/lib/cluster-provider-db.js +2 -1
  32. package/lib/cluster-storage.js +71 -11
  33. package/lib/cluster.js +30 -25
  34. package/lib/config-drift.js +3 -4
  35. package/lib/constants.js +8 -3
  36. package/lib/cookies.js +16 -4
  37. package/lib/credential-hash.js +4 -7
  38. package/lib/crypto.js +9 -9
  39. package/lib/csv.js +11 -8
  40. package/lib/db-declare-row-policy.js +4 -9
  41. package/lib/db-declare-view.js +3 -6
  42. package/lib/db-query.js +9 -3
  43. package/lib/db-schema.js +75 -10
  44. package/lib/db.js +116 -70
  45. package/lib/deprecate.js +3 -8
  46. package/lib/dev.js +30 -19
  47. package/lib/dual-control.js +2 -4
  48. package/lib/error-page.js +32 -39
  49. package/lib/external-db-migrate.js +23 -26
  50. package/lib/external-db.js +11 -3
  51. package/lib/file-type.js +16 -8
  52. package/lib/forms.js +48 -12
  53. package/lib/framework-error.js +1 -2
  54. package/lib/{internal-sha1-hibp.js → framework-sha1-hibp.js} +1 -1
  55. package/lib/handlers.js +1 -1
  56. package/lib/http-client-cookie-jar.js +25 -18
  57. package/lib/http-client.js +46 -23
  58. package/lib/i18n.js +6 -16
  59. package/lib/jobs.js +5 -1
  60. package/lib/log-stream-cloudwatch.js +31 -30
  61. package/lib/log-stream-local.js +12 -3
  62. package/lib/log-stream-otlp-grpc.js +29 -23
  63. package/lib/log-stream-otlp.js +13 -19
  64. package/lib/log-stream-syslog.js +27 -22
  65. package/lib/log-stream-webhook.js +8 -17
  66. package/lib/log-stream.js +13 -2
  67. package/lib/log.js +83 -5
  68. package/lib/mail-bounce.js +22 -15
  69. package/lib/mail-dkim.js +3 -6
  70. package/lib/mail.js +62 -34
  71. package/lib/metrics.js +61 -25
  72. package/lib/middleware/api-encrypt.js +15 -13
  73. package/lib/middleware/body-parser.js +46 -39
  74. package/lib/middleware/bot-guard.js +25 -4
  75. package/lib/middleware/compression.js +26 -11
  76. package/lib/middleware/cors.js +6 -5
  77. package/lib/middleware/csp-nonce.js +4 -3
  78. package/lib/middleware/csrf-protect.js +1 -1
  79. package/lib/middleware/db-role-for.js +7 -30
  80. package/lib/middleware/health.js +42 -8
  81. package/lib/middleware/index.js +55 -36
  82. package/lib/middleware/rate-limit.js +11 -8
  83. package/lib/middleware/request-id.js +10 -2
  84. package/lib/middleware/request-log.js +3 -2
  85. package/lib/middleware/require-auth.js +5 -2
  86. package/lib/middleware/sse.js +5 -4
  87. package/lib/migration-files.js +37 -0
  88. package/lib/migrations.js +26 -15
  89. package/lib/mtls-ca.js +38 -17
  90. package/lib/mtls-engine-default.js +15 -7
  91. package/lib/network-dns.js +49 -20
  92. package/lib/network-heartbeat.js +7 -7
  93. package/lib/network-nts.js +70 -48
  94. package/lib/network-proxy.js +34 -15
  95. package/lib/network-tls.js +7 -5
  96. package/lib/network.js +3 -3
  97. package/lib/notify.js +14 -36
  98. package/lib/ntp-check.js +7 -6
  99. package/lib/numeric-bounds.js +35 -3
  100. package/lib/object-store/azure-blob-bucket-ops.js +73 -15
  101. package/lib/object-store/azure-blob.js +31 -24
  102. package/lib/object-store/gcs-bucket-ops.js +41 -17
  103. package/lib/object-store/gcs.js +41 -34
  104. package/lib/object-store/http-put.js +2 -11
  105. package/lib/object-store/http-request.js +38 -0
  106. package/lib/object-store/index.js +1 -1
  107. package/lib/object-store/sigv4-bucket-ops.js +33 -37
  108. package/lib/object-store/sigv4.js +50 -30
  109. package/lib/observability.js +14 -3
  110. package/lib/otel-export.js +16 -14
  111. package/lib/pagination.js +23 -8
  112. package/lib/parsers/index.js +11 -5
  113. package/lib/parsers/safe-env.js +43 -14
  114. package/lib/parsers/safe-ini.js +29 -10
  115. package/lib/parsers/safe-toml.js +55 -25
  116. package/lib/parsers/safe-xml.js +22 -11
  117. package/lib/parsers/safe-yaml.js +55 -17
  118. package/lib/permissions.js +24 -33
  119. package/lib/pqc-agent.js +4 -2
  120. package/lib/pqc-gate.js +28 -15
  121. package/lib/protobuf-encoder.js +9 -3
  122. package/lib/protocol-dispatcher.js +29 -12
  123. package/lib/pubsub-redis.js +25 -8
  124. package/lib/pubsub.js +84 -12
  125. package/lib/queue-local.js +13 -4
  126. package/lib/queue-redis.js +2 -2
  127. package/lib/queue-sqs.js +21 -16
  128. package/lib/queue.js +8 -4
  129. package/lib/redis-client.js +13 -10
  130. package/lib/request-helpers.js +33 -2
  131. package/lib/restore-bundle.js +2 -4
  132. package/lib/restore-rollback.js +4 -1
  133. package/lib/restore.js +12 -10
  134. package/lib/retry.js +58 -9
  135. package/lib/router.js +96 -27
  136. package/lib/safe-async.js +97 -4
  137. package/lib/safe-buffer.js +47 -0
  138. package/lib/safe-json.js +18 -9
  139. package/lib/safe-schema.js +54 -18
  140. package/lib/safe-url.js +6 -2
  141. package/lib/scheduler.js +25 -14
  142. package/lib/security-assert.js +31 -26
  143. package/lib/seeders.js +71 -60
  144. package/lib/session.js +13 -7
  145. package/lib/slug.js +35 -8
  146. package/lib/ssrf-guard.js +58 -43
  147. package/lib/static.js +11 -7
  148. package/lib/storage.js +2 -1
  149. package/lib/template.js +17 -7
  150. package/lib/testing.js +28 -8
  151. package/lib/time.js +26 -5
  152. package/lib/totp.js +18 -9
  153. package/lib/tracing.js +15 -5
  154. package/lib/uuid.js +47 -20
  155. package/lib/validate-opts.js +213 -0
  156. package/lib/vault/index.js +36 -27
  157. package/lib/vault/rotate.js +41 -22
  158. package/lib/vault/wrap.js +26 -19
  159. package/lib/webhook.js +37 -68
  160. package/lib/websocket.js +98 -52
  161. package/package.json +1 -1
  162. package/sbom.cyclonedx.json +6 -6
  163. package/lib/auth/totp.js +0 -17
  164. 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 httpClient = require("../http-client");
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 * 60;
61
- var SAS_MAX_EXPIRES_SECONDS = 7 * 24 * 60 * 60;
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
- function _httpRequest(method, urlObj, headers, body, opts) {
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 : new 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
- var queryLines = Object.keys(grouped).sort().map(function (name) {
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 : new 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 = new URL(endpoint + "/" + config.container + "/" + key);
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 = new URL(endpoint + "/" + config.container);
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 = expiry.toISOString().replace(/\.\d{3}Z$/, "Z");
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 = new URL(endpoint + "/" + config.container + "/" + opts.key + "?" + token.sas);
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
- if (!BUCKET_NAME_RE.test(name)) {
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", new URL(tokenEndpoint), {
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, [200]);
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
- function _bucketBaseUrl() {
216
- return endpoint + "/storage/v1/b";
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 = new URL(_bucketBaseUrl());
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, [200, 409]);
235
- if (res.statusCode === 409) {
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 = new URL(_bucketBaseUrl() + "/" + encodeURIComponent(name));
274
+ var url = _bucketBaseUrl("/" + encodeURIComponent(name));
251
275
  var headers = authHeader.bearer(token);
252
- var res = await _request("DELETE", url, headers, null, [204, 404]);
253
- return res.statusCode === 204;
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 = new URL(_bucketBaseUrl());
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, [200]);
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 = new URL(_bucketBaseUrl() + "/" + encodeURIComponent(name));
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, [200]);
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 = new URL(_bucketBaseUrl() + "/" + encodeURIComponent(name));
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, [200]);
337
+ await _request("PATCH", url, headers, bodyBuf, [HTTP_OK]);
314
338
  return { rulesApplied: rules.length };
315
339
  }
316
340
 
@@ -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 * 60;
51
- var PRESIGN_MAX_EXPIRES_SECONDS = 7 * 24 * 60 * 60;
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
- function _httpRequest(method, urlObj, headers, body, opts) {
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() / 1000);
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 + 3600,
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
- new URL(tokenEndpoint),
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 = new URL(endpoint + "/storage/v1/b/" + encodeURIComponent(bucket) +
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 = new URL(endpoint + "/upload/storage/v1/b/" + encodeURIComponent(bucket) + "/o");
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 = new URL(endpoint + "/storage/v1/b/" + encodeURIComponent(bucket) + "/o");
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 = new URL(endpoint + "/" + encodeURIComponent(bucket) + "/" + encodedKey);
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 iso = date.toISOString().replace(/[-:]/g, "");
311
- var amzDate = iso.slice(0, 8) + "T" + iso.slice(9, 15) + "Z";
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
- var signedHeadersStr = Object.keys(headers).sort().join(";");
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
- var minBytes = (typeof opts.minBytes === "number" && opts.minBytes >= 0) ? opts.minBytes : 0;
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 iso = date.toISOString().replace(/[-:]/g, "");
395
- var amzDate = iso.slice(0, 8) + "T" + iso.slice(9, 15) + "Z";
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 = new URL(endpoint + "/" + encodeURIComponent(bucket) + "/");
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 httpClient.request({
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("./retry");
38
+ var retryHelper = require("../retry");
39
39
  var protocolDispatcher = require("../protocol-dispatcher");
40
40
  var { ObjectStoreError } = require("../framework-error");
41
41