@bedrock/kms-http 23.2.0 → 23.3.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/README.md CHANGED
@@ -11,7 +11,10 @@
11
11
 
12
12
  ## Install
13
13
 
14
- - Node.js 18+ is required.
14
+ This software requires and supports maintained recent versions of Node.js.
15
+ Updates may remove support for older unmaintained platform versions. Please use
16
+ dependency version lock files and testing to ensure compatibility with this
17
+ software.
15
18
 
16
19
  ### NPM
17
20
 
@@ -36,7 +39,7 @@ npm install
36
39
  In `lib/index.js` (or `main.js`, as appropriate):
37
40
 
38
41
  ```js
39
- import 'bedrock-kms-http';
42
+ import '@bedrock/kms-http';
40
43
  ```
41
44
 
42
45
  ### KMS HTTP API
@@ -27,7 +27,7 @@ export class BedrockKeystoreConfigStorage extends KeystoreConfigStorage {
27
27
  throw new BedrockError(
28
28
  'Permission denied. Source IP is not allowed.', 'NotAllowedError', {
29
29
  httpStatusCode: 403,
30
- public: true,
30
+ public: true
31
31
  });
32
32
  }
33
33
 
@@ -51,7 +51,7 @@ export class BedrockKeystoreConfigStorage extends KeystoreConfigStorage {
51
51
  throw new BedrockError(
52
52
  'Permission denied. Source IP is not allowed.', 'NotAllowedError', {
53
53
  httpStatusCode: 403,
54
- public: true,
54
+ public: true
55
55
  });
56
56
  }
57
57
  }
package/lib/config.js CHANGED
@@ -3,6 +3,7 @@
3
3
  */
4
4
  import {config} from '@bedrock/core';
5
5
  import '@bedrock/app-identity';
6
+ import '@bedrock/express';
6
7
  import '@bedrock/meter-usage-reporter';
7
8
  import '@bedrock/validation';
8
9
 
@@ -64,3 +65,18 @@ cfg.kmsModuleProxy = {
64
65
  }
65
66
  */
66
67
  };
68
+
69
+ // require raw JSON payloads for proxied routes to preserve specific content
70
+ // expression as it is used in signed digests
71
+ const createRawBodyParserOptions = () => ({
72
+ raw: {
73
+ type: ['json', '+json']
74
+ }
75
+ });
76
+ const bodyParserRoutes = config.express.bodyParser.routes;
77
+ // for generating a new key
78
+ bodyParserRoutes['/kms/keystores/:keystoreId/keys'] =
79
+ createRawBodyParserOptions();
80
+ // for running a key operation on an existing key
81
+ bodyParserRoutes['/kms/keystores/:keystoreId/keys/:keyId'] =
82
+ createRawBodyParserOptions();
package/lib/http.js CHANGED
@@ -5,7 +5,7 @@ import * as bedrock from '@bedrock/core';
5
5
  import * as brZCapStorage from '@bedrock/zcap-storage';
6
6
  import * as helpers from './helpers.js';
7
7
  import * as middleware from './middleware.js';
8
- import {createProxyMiddleware, fixRequestBody} from 'http-proxy-middleware';
8
+ import * as zlib from 'node:zlib';
9
9
  import {
10
10
  getConfigsQuery,
11
11
  postKeystoreBody,
@@ -18,6 +18,7 @@ import {
18
18
  } from '@bedrock/kms';
19
19
  import {reportOperationUsage, SERVICE_TYPE} from './metering.js';
20
20
  import {asyncHandler} from '@bedrock/express';
21
+ import {createProxyMiddleware} from 'http-proxy-middleware';
21
22
  import {generateRandom} from '@digitalbazaar/webkms-switch';
22
23
  import {meters} from '@bedrock/meter-usage-reporter';
23
24
  import {createValidateMiddleware as validate} from '@bedrock/validation';
@@ -52,6 +53,8 @@ bedrock.events.on('bedrock-express.configure.routes', app => {
52
53
 
53
54
  // create KMS module proxy middleware for use with KMS operations
54
55
  const kmsModuleProxyMiddleware = _createKmsModuleProxyMiddleware();
56
+ // create raw => json parser for routes using KMS module proxy middleware
57
+ const rawBodyToJsonMiddleware = asyncHandler(_parseRawBodyAsJson);
55
58
 
56
59
  /* Note: CORS is used on all endpoints. This is safe because authorization
57
60
  uses HTTP signatures + capabilities, not cookies; CSRF is not possible. */
@@ -100,7 +103,7 @@ bedrock.events.on('bedrock-express.configure.routes', app => {
100
103
  // insufficient remaining storage
101
104
  throw new BedrockError('Permission denied.', 'NotAllowedError', {
102
105
  httpStatusCode: 403,
103
- public: true,
106
+ public: true
104
107
  });
105
108
  }
106
109
 
@@ -220,6 +223,7 @@ bedrock.events.on('bedrock-express.configure.routes', app => {
220
223
  cors(),
221
224
  getKeystoreConfig,
222
225
  kmsModuleProxyMiddleware,
226
+ rawBodyToJsonMiddleware,
223
227
  handleOperation);
224
228
 
225
229
  // invoke KMS operation on an existing key
@@ -229,6 +233,7 @@ bedrock.events.on('bedrock-express.configure.routes', app => {
229
233
  cors(),
230
234
  getKeystoreConfig,
231
235
  kmsModuleProxyMiddleware,
236
+ rawBodyToJsonMiddleware,
232
237
  handleOperation);
233
238
 
234
239
  // TODO: consider whether this should be exposed w/o authorization
@@ -240,6 +245,7 @@ bedrock.events.on('bedrock-express.configure.routes', app => {
240
245
  cors(),
241
246
  getKeystoreConfig,
242
247
  kmsModuleProxyMiddleware,
248
+ rawBodyToJsonMiddleware,
243
249
  asyncHandler(async (req, res) => {
244
250
  const {webkms: {keystore}} = req;
245
251
  const keyId = `${keystore.id}/keys/${req.params.keyId}`;
@@ -278,7 +284,7 @@ bedrock.events.on('bedrock-express.configure.routes', app => {
278
284
  // meter is disabled, do not allow storage
279
285
  throw new BedrockError('Permission denied.', 'NotAllowedError', {
280
286
  httpStatusCode: 403,
281
- public: true,
287
+ public: true
282
288
  });
283
289
  }
284
290
 
@@ -319,7 +325,7 @@ function _createKmsModuleProxyMiddleware() {
319
325
  ws: false,
320
326
  on: {
321
327
  // required to rewrite body since it has been parsed
322
- proxyReq: fixRequestBody
328
+ proxyReq: _reserializeRequestBody
323
329
  }
324
330
  }));
325
331
  }
@@ -333,3 +339,61 @@ function _createKmsModuleProxyMiddleware() {
333
339
  mw(req, res, next);
334
340
  };
335
341
  }
342
+
343
+ // parses raw body to json
344
+ function _parseRawBodyAsJson(req, res, next) {
345
+ try {
346
+ // special case empty body
347
+ req.body = req.body?.length > 0 ? JSON.parse(req.body.toString()) : {};
348
+ next();
349
+ } catch(cause) {
350
+ throw new BedrockError('Failed to parse expected JSON body.', {
351
+ name: 'DataError',
352
+ details: {httpStatusCode: 400, public: true},
353
+ cause
354
+ });
355
+ }
356
+ }
357
+
358
+ function _reserializeRequestBody(proxyReq, req) {
359
+ try {
360
+ if(!(req.body?.length > 0)) {
361
+ // nothing to send
362
+ return;
363
+ }
364
+
365
+ const contentType = proxyReq.getHeader('content-type');
366
+ if(!contentType) {
367
+ // nothing to send
368
+ return;
369
+ }
370
+
371
+ // reapply content-encoding
372
+ const contentEncoding = String(
373
+ proxyReq.getHeader('content-encoding')).toLowerCase();
374
+ let data = req.body;
375
+ if(contentEncoding === 'gzip') {
376
+ data = zlib.gzipSync(data);
377
+ } else if(contentEncoding === 'deflate') {
378
+ data = zlib.deflateSync(data);
379
+ } else if(contentEncoding === 'zstd') {
380
+ // note: not supported by body-parser, so unreachable
381
+ data = zlib.zstdCompressSync(data);
382
+ } else if(contentEncoding === 'br') {
383
+ // note: not supported by body-parser, so unreachable
384
+ data = zlib.brotliCompressSync(data);
385
+ } else {
386
+ proxyReq.removeHeader('content-encoding');
387
+ }
388
+
389
+ const transferEncoding = proxyReq.getHeader('transfer-encoding');
390
+ if(!transferEncoding) {
391
+ proxyReq.setHeader('content-length', Buffer.byteLength(data));
392
+ }
393
+ proxyReq.write(data);
394
+ } catch(e) {
395
+ // pass error through `proxyReq` processing channel
396
+ const error = e instanceof Error ? e : new Error(String(e));
397
+ proxyReq.destroy(error);
398
+ }
399
+ }
package/lib/middleware.js CHANGED
@@ -81,7 +81,7 @@ export function authorizeKeystoreZcapInvocation() {
81
81
  rootInvocationTarget: req.webkms.keystore.id
82
82
  };
83
83
  },
84
- getRootController: getKeystoreController,
84
+ getRootController: getKeystoreController
85
85
  });
86
86
  }
87
87
 
@@ -210,13 +210,13 @@ function onError({error}) {
210
210
  error.message,
211
211
  error.name || 'NotAllowedError', {
212
212
  ...details,
213
- public: true,
213
+ public: true
214
214
  }, error);
215
215
  }
216
216
  throw new BedrockError(
217
217
  'Authorization error.', 'NotAllowedError', {
218
218
  httpStatusCode: 403,
219
- public: true,
219
+ public: true
220
220
  }, error);
221
221
  }
222
222
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bedrock/kms-http",
3
- "version": "23.2.0",
3
+ "version": "23.3.0",
4
4
  "type": "module",
5
5
  "description": "Bedrock KMS HTTP API",
6
6
  "main": "./lib/index.js",
@@ -30,13 +30,13 @@
30
30
  "dependencies": {
31
31
  "@digitalbazaar/ed25519-multikey": "^1.3.1",
32
32
  "@digitalbazaar/ed25519-signature-2020": "^5.4.0",
33
- "@digitalbazaar/ezcap-express": "^7.1.0",
33
+ "@digitalbazaar/ezcap-express": "^7.3.0",
34
34
  "@digitalbazaar/webkms-switch": "^12.0.0",
35
- "cidr-regex": "^4.1.3",
36
- "cors": "^2.8.5",
35
+ "cidr-regex": "^7.1.2",
36
+ "cors": "^2.8.6",
37
37
  "forwarded": "^0.2.0",
38
- "http-proxy-middleware": "^3.0.5",
39
- "ipaddr.js": "^2.2.0"
38
+ "http-proxy-middleware": "^4.2.0",
39
+ "ipaddr.js": "^2.5.0"
40
40
  },
41
41
  "peerDependencies": {
42
42
  "@bedrock/app-identity": "^4.0.0",
@@ -56,12 +56,10 @@
56
56
  "lib": "./lib"
57
57
  },
58
58
  "devDependencies": {
59
- "eslint": "^8.57.1",
60
- "eslint-config-digitalbazaar": "^5.2.0",
61
- "eslint-plugin-jsdoc": "^50.6.3",
62
- "eslint-plugin-unicorn": "^56.0.1"
59
+ "@digitalbazaar/eslint-config": "^9.0.0",
60
+ "eslint": "^10.13.0"
63
61
  },
64
62
  "engines": {
65
- "node": ">=18"
63
+ "node": ">=22"
66
64
  }
67
65
  }
@@ -92,7 +92,7 @@ const delegatedZcap = {
92
92
  proofValue: {
93
93
  title: 'proofValue',
94
94
  type: 'string'
95
- },
95
+ }
96
96
  }
97
97
  }
98
98
  }
@@ -109,7 +109,7 @@ const ipAllowList = {
109
109
  items: {
110
110
  type: 'string',
111
111
  // leading and trailing slashes in regex must be removed
112
- pattern: cidrRegex({exact: true}).toString().slice(1, -1),
112
+ pattern: cidrRegex({exact: true}).toString().slice(1, -1)
113
113
  }
114
114
  };
115
115
 
@@ -146,7 +146,7 @@ const updateKeystoreConfigBody = {
146
146
  required: [
147
147
  'controller',
148
148
  'id',
149
- 'sequence',
149
+ 'sequence'
150
150
  ],
151
151
  properties: {
152
152
  controller,