@webex/internal-plugin-encryption 3.0.0-beta.9 → 3.0.0-beta.91

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/src/index.js CHANGED
@@ -8,126 +8,150 @@
8
8
  // by using Promise.race to resolve replays (as more requests get enqueue for a
9
9
  // specific action, accept whichever one completes first).
10
10
 
11
+ import '@webex/internal-plugin-device';
12
+
13
+ import '@webex/internal-plugin-mercury';
14
+
11
15
  import {registerInternalPlugin} from '@webex/webex-core';
12
16
  import {has, isObject, isString} from 'lodash';
13
17
 
14
18
  import Encryption from './encryption';
15
19
  import config from './config';
16
20
  import {DryError} from './kms-errors';
17
- import '@webex/internal-plugin-device';
18
- import '@webex/internal-plugin-mercury';
21
+
19
22
  import KmsDryErrorInterceptor from './kms-dry-error-interceptor';
20
23
 
21
24
  let interceptors;
22
25
 
23
26
  if (process.env.NODE_ENV === 'test') {
24
27
  interceptors = {
25
- KmsDryErrorInterceptor: KmsDryErrorInterceptor.create
28
+ KmsDryErrorInterceptor: KmsDryErrorInterceptor.create,
26
29
  };
27
30
  }
28
31
 
29
32
  registerInternalPlugin('encryption', Encryption, {
30
33
  payloadTransformer: {
31
- predicates: [{
32
- name: 'encryptKmsMessage',
33
- direction: 'outbound',
34
- // I don't see any practical way to reduce complexity here.
35
- // eslint-disable-next-line complexity
36
- test(ctx, options) {
37
- if (!has(options, 'body.kmsMessage')) {
38
- return Promise.resolve(false);
39
- }
40
-
41
- if (!isObject(options.body.kmsMessage)) {
42
- return Promise.resolve(false);
43
- }
44
-
45
- // If this is a template for a kms message, assume another transform
46
- // will fill it in later. This is a bit of a leaky abstraction, but the
47
- // alternative is building a complex rules engine for controlling
48
- // ordering of transforms
49
- if (options.body.kmsMessage.keyUris && options.body.kmsMessage.keyUris.length === 0) {
50
- return Promise.resolve(false);
51
- }
52
- if (options.body.kmsMessage.resourceUri && (options.body.kmsMessage.resourceUri.includes('<KRO>') || options.body.kmsMessage.resourceUri.includes('<KEYURL>'))) {
53
- return Promise.resolve(false);
54
- }
55
- if (options.body.kmsMessage.uri && (options.body.kmsMessage.uri.includes('<KRO>') || options.body.kmsMessage.uri.includes('<KEYURL>'))) {
56
- return Promise.resolve(false);
57
- }
58
-
59
- return Promise.resolve(true);
34
+ predicates: [
35
+ {
36
+ name: 'encryptKmsMessage',
37
+ direction: 'outbound',
38
+ // I don't see any practical way to reduce complexity here.
39
+ // eslint-disable-next-line complexity
40
+ test(ctx, options) {
41
+ if (!has(options, 'body.kmsMessage')) {
42
+ return Promise.resolve(false);
43
+ }
44
+
45
+ if (!isObject(options.body.kmsMessage)) {
46
+ return Promise.resolve(false);
47
+ }
48
+
49
+ // If this is a template for a kms message, assume another transform
50
+ // will fill it in later. This is a bit of a leaky abstraction, but the
51
+ // alternative is building a complex rules engine for controlling
52
+ // ordering of transforms
53
+ if (options.body.kmsMessage.keyUris && options.body.kmsMessage.keyUris.length === 0) {
54
+ return Promise.resolve(false);
55
+ }
56
+ if (
57
+ options.body.kmsMessage.resourceUri &&
58
+ (options.body.kmsMessage.resourceUri.includes('<KRO>') ||
59
+ options.body.kmsMessage.resourceUri.includes('<KEYURL>'))
60
+ ) {
61
+ return Promise.resolve(false);
62
+ }
63
+ if (
64
+ options.body.kmsMessage.uri &&
65
+ (options.body.kmsMessage.uri.includes('<KRO>') ||
66
+ options.body.kmsMessage.uri.includes('<KEYURL>'))
67
+ ) {
68
+ return Promise.resolve(false);
69
+ }
70
+
71
+ return Promise.resolve(true);
72
+ },
73
+ extract(options) {
74
+ return Promise.resolve(options.body);
75
+ },
60
76
  },
61
- extract(options) {
62
- return Promise.resolve(options.body);
63
- }
64
- }, {
65
- name: 'decryptKmsMessage',
66
- direction: 'inbound',
67
- test(ctx, response) {
68
- return Promise.resolve(has(response, 'body.kmsMessage') && isString(response.body.kmsMessage));
77
+ {
78
+ name: 'decryptKmsMessage',
79
+ direction: 'inbound',
80
+ test(ctx, response) {
81
+ return Promise.resolve(
82
+ has(response, 'body.kmsMessage') && isString(response.body.kmsMessage)
83
+ );
84
+ },
85
+ extract(response) {
86
+ return Promise.resolve(response.body);
87
+ },
69
88
  },
70
- extract(response) {
71
- return Promise.resolve(response.body);
72
- }
73
- }, {
74
- name: 'decryptErrorResponse',
75
- direction: 'inbound',
76
- test(ctx, reason) {
77
- return Promise.resolve(Boolean(reason.body && reason.body.errorCode === 1900000));
89
+ {
90
+ name: 'decryptErrorResponse',
91
+ direction: 'inbound',
92
+ test(ctx, reason) {
93
+ return Promise.resolve(Boolean(reason.body && reason.body.errorCode === 1900000));
94
+ },
95
+ extract(reason) {
96
+ return Promise.resolve(reason);
97
+ },
78
98
  },
79
- extract(reason) {
80
- return Promise.resolve(reason);
81
- }
82
- }],
83
- transforms: [{
84
- name: 'encryptKmsMessage',
85
- fn(ctx, object) {
86
- if (!object) {
87
- return Promise.resolve();
88
- }
89
-
90
- if (!object.kmsMessage) {
91
- return Promise.resolve();
92
- }
93
-
94
- if (isString(object.kmsMessage)) {
95
- return Promise.resolve();
96
- }
97
-
98
- return ctx.webex.internal.encryption.kms.prepareRequest(object.kmsMessage)
99
- .then((req) => {
99
+ ],
100
+ transforms: [
101
+ {
102
+ name: 'encryptKmsMessage',
103
+ fn(ctx, object) {
104
+ if (!object) {
105
+ return Promise.resolve();
106
+ }
107
+
108
+ if (!object.kmsMessage) {
109
+ return Promise.resolve();
110
+ }
111
+
112
+ if (isString(object.kmsMessage)) {
113
+ return Promise.resolve();
114
+ }
115
+
116
+ return ctx.webex.internal.encryption.kms.prepareRequest(object.kmsMessage).then((req) => {
100
117
  object.kmsMessage = req.wrapped;
101
118
  });
102
- }
103
- }, {
104
- name: 'decryptKmsMessage',
105
- fn(ctx, object) {
106
- return ctx.webex.internal.encryption.kms.decryptKmsMessage(object.kmsMessage)
107
- .then((kmsMessage) => {
108
- object.kmsMessage = kmsMessage;
109
- });
110
- }
111
- }, {
112
- name: 'decryptErrorResponse',
113
- fn(ctx, reason) {
114
- const promises = reason.body.errors.map((error) => ctx.webex.internal.encryption.kms.decryptKmsMessage(error.description)
115
- .then((desc) => {
116
- error.description = desc;
117
- }));
118
-
119
- promises.push(ctx.webex.internal.encryption.kms.decryptKmsMessage(reason.body.message)
120
- .then((kmsMessage) => {
121
- reason.body.message = kmsMessage;
122
- }));
123
-
124
- return Promise.all(promises)
125
- .then(() => Promise.reject(new DryError(reason)));
126
- }
127
- }]
119
+ },
120
+ },
121
+ {
122
+ name: 'decryptKmsMessage',
123
+ fn(ctx, object) {
124
+ return ctx.webex.internal.encryption.kms
125
+ .decryptKmsMessage(object.kmsMessage)
126
+ .then((kmsMessage) => {
127
+ object.kmsMessage = kmsMessage;
128
+ });
129
+ },
130
+ },
131
+ {
132
+ name: 'decryptErrorResponse',
133
+ fn(ctx, reason) {
134
+ const promises = reason.body.errors.map((error) =>
135
+ ctx.webex.internal.encryption.kms.decryptKmsMessage(error.description).then((desc) => {
136
+ error.description = desc;
137
+ })
138
+ );
139
+
140
+ promises.push(
141
+ ctx.webex.internal.encryption.kms
142
+ .decryptKmsMessage(reason.body.message)
143
+ .then((kmsMessage) => {
144
+ reason.body.message = kmsMessage;
145
+ })
146
+ );
147
+
148
+ return Promise.all(promises).then(() => Promise.reject(new DryError(reason)));
149
+ },
150
+ },
151
+ ],
128
152
  },
129
153
  interceptors,
130
- config
154
+ config,
131
155
  });
132
156
 
133
157
  export {default} from './encryption';
@@ -23,14 +23,19 @@ const KmsBatcher = Batcher.extend({
23
23
  processKmsMessageEvent(event) {
24
24
  this.logger.info('kms-batcher: received kms message');
25
25
 
26
- return Promise.all(event.encryption.kmsMessages.map((kmsMessage) => new Promise((resolve) => {
27
- /* istanbul ignore else */
28
- if (process.env.NODE_ENV !== 'production') {
29
- this.logger.info('kms-batcher:', kmsMessage.body);
30
- }
31
-
32
- resolve(this.acceptItem(kmsMessage));
33
- })));
26
+ return Promise.all(
27
+ event.encryption.kmsMessages.map(
28
+ (kmsMessage) =>
29
+ new Promise((resolve) => {
30
+ /* istanbul ignore else */
31
+ if (process.env.NODE_ENV !== 'production') {
32
+ this.logger.info('kms-batcher:', kmsMessage.body);
33
+ }
34
+
35
+ resolve(this.acceptItem(kmsMessage));
36
+ })
37
+ )
38
+ );
34
39
  },
35
40
 
36
41
  /**
@@ -39,30 +44,34 @@ const KmsBatcher = Batcher.extend({
39
44
  * @returns {Promise<Object>}
40
45
  */
41
46
  prepareItem(item) {
42
- return this.getDeferredForRequest(item)
43
- .then((defer) => {
44
- const timeout = item[TIMEOUT_SYMBOL];
45
-
46
- /* istanbul ignore if */
47
- if (!timeout) {
48
- throw new Error('timeout is required');
49
- }
50
-
51
- const timer = safeSetTimeout(() => {
52
- this.logger.warn(`kms: request timed out; request id: ${item.requestId}; timeout: ${timeout}`);
53
- this.handleItemFailure(item, new KmsTimeoutError({
47
+ return this.getDeferredForRequest(item).then((defer) => {
48
+ const timeout = item[TIMEOUT_SYMBOL];
49
+
50
+ /* istanbul ignore if */
51
+ if (!timeout) {
52
+ throw new Error('timeout is required');
53
+ }
54
+
55
+ const timer = safeSetTimeout(() => {
56
+ this.logger.warn(
57
+ `kms: request timed out; request id: ${item.requestId}; timeout: ${timeout}`
58
+ );
59
+ this.handleItemFailure(
60
+ item,
61
+ new KmsTimeoutError({
54
62
  timeout,
55
- request: item
56
- }));
57
- }, timeout);
63
+ request: item,
64
+ })
65
+ );
66
+ }, timeout);
58
67
 
59
- // Reminder: reassign `promise` is not a viable means of inserting into
60
- // the Promise chain
61
- defer.promise.then(() => clearTimeout(timer));
62
- defer.promise.catch(() => clearTimeout(timer));
68
+ // Reminder: reassign `promise` is not a viable means of inserting into
69
+ // the Promise chain
70
+ defer.promise.then(() => clearTimeout(timer));
71
+ defer.promise.catch(() => clearTimeout(timer));
63
72
 
64
- return item;
65
- });
73
+ return item;
74
+ });
66
75
  },
67
76
 
68
77
  /**
@@ -71,11 +80,10 @@ const KmsBatcher = Batcher.extend({
71
80
  * @returns {Promise<Array>}
72
81
  */
73
82
  prepareRequest(queue) {
74
- return this.webex.internal.encryption.kms._getKMSCluster()
75
- .then((cluster) => ({
76
- destination: cluster,
77
- kmsMessages: queue.map((req) => req.wrapped)
78
- }));
83
+ return this.webex.internal.encryption.kms._getKMSCluster().then((cluster) => ({
84
+ destination: cluster,
85
+ kmsMessages: queue.map((req) => req.wrapped),
86
+ }));
79
87
  },
80
88
 
81
89
  /**
@@ -89,7 +97,7 @@ const KmsBatcher = Batcher.extend({
89
97
  method: 'POST',
90
98
  service: 'encryption',
91
99
  resource: '/kms/messages',
92
- body: payload
100
+ body: payload,
93
101
  });
94
102
  },
95
103
 
@@ -114,10 +122,9 @@ const KmsBatcher = Batcher.extend({
114
122
  * @returns {Promise}
115
123
  */
116
124
  handleItemSuccess(item) {
117
- return this.getDeferredForResponse(item)
118
- .then((defer) => {
119
- defer.resolve(item.body);
120
- });
125
+ return this.getDeferredForResponse(item).then((defer) => {
126
+ defer.resolve(item.body);
127
+ });
121
128
  },
122
129
 
123
130
  /**
@@ -126,10 +133,9 @@ const KmsBatcher = Batcher.extend({
126
133
  * @returns {Promise}
127
134
  */
128
135
  handleItemFailure(item, reason) {
129
- return this.getDeferredForResponse(item)
130
- .then((defer) => {
131
- defer.reject(reason || new KmsError(item.body));
132
- });
136
+ return this.getDeferredForResponse(item).then((defer) => {
137
+ defer.reject(reason || new KmsError(item.body));
138
+ });
133
139
  },
134
140
 
135
141
  /**
@@ -146,7 +152,7 @@ const KmsBatcher = Batcher.extend({
146
152
  */
147
153
  fingerprintResponse(item) {
148
154
  return Promise.resolve(item.requestId);
149
- }
155
+ },
150
156
  });
151
157
 
152
158
  export default KmsBatcher;
@@ -7,7 +7,7 @@ import {
7
7
  RSAPublicKey,
8
8
  CertificateChainValidationEngine,
9
9
  CryptoEngine,
10
- setEngine
10
+ setEngine,
11
11
  } from 'pkijs';
12
12
  import {isArray} from 'lodash';
13
13
  import jose from 'node-jose';
@@ -20,7 +20,7 @@ setEngine(
20
20
  new CryptoEngine({
21
21
  name: '',
22
22
  crypto,
23
- subtle: crypto.subtle
23
+ subtle: crypto.subtle,
24
24
  })
25
25
  );
26
26
 
@@ -83,7 +83,7 @@ const validateKtyHeader = ({kty}) => {
83
83
 
84
84
  const validateKidHeader = ({kid}) => {
85
85
  if (!isUri(kid)) {
86
- throwError('\'kid\' is not a valid URI');
86
+ throwError("'kid' is not a valid URI");
87
87
  }
88
88
 
89
89
  if (parseUrl(kid).protocol !== VALID_KID_PROTOCOL) {
@@ -144,7 +144,7 @@ const validateCommonName = ([certificate], {kid}) => {
144
144
  }
145
145
 
146
146
  if (!validationSuccessful) {
147
- throwError('hostname of the 1st certificate does not match \'kid\'');
147
+ throwError("hostname of the 1st certificate does not match 'kid'");
148
148
  }
149
149
  };
150
150
 
@@ -158,23 +158,22 @@ const validateCommonName = ([certificate], {kid}) => {
158
158
  * @throws {KMSError} if e or n doesn't match the first certificate
159
159
  * @returns {void}
160
160
  */
161
- const validatePublicCertificate =
162
- ([certificate], {e: publicExponent, n: modulus}) => {
163
- const {encode} = jose.util.base64url;
164
-
165
- const publicKey = certificate.subjectPublicKeyInfo.subjectPublicKey;
166
- const asn1PublicCert = fromBER(publicKey.valueBlock.valueHex);
167
- const publicCert = new RSAPublicKey({schema: asn1PublicCert.result});
168
- const publicExponentHex = publicCert.publicExponent.valueBlock.valueHex;
169
- const modulusHex = publicCert.modulus.valueBlock.valueHex;
170
-
171
- if (publicExponent !== encode(publicExponentHex)) {
172
- throwError('Public exponent is invalid');
173
- }
174
- if (modulus !== encode(modulusHex)) {
175
- throwError('Modulus is invalid');
176
- }
177
- };
161
+ const validatePublicCertificate = ([certificate], {e: publicExponent, n: modulus}) => {
162
+ const {encode} = jose.util.base64url;
163
+
164
+ const publicKey = certificate.subjectPublicKeyInfo.subjectPublicKey;
165
+ const asn1PublicCert = fromBER(publicKey.valueBlock.valueHex);
166
+ const publicCert = new RSAPublicKey({schema: asn1PublicCert.result});
167
+ const publicExponentHex = publicCert.publicExponent.valueBlock.valueHex;
168
+ const modulusHex = publicCert.modulus.valueBlock.valueHex;
169
+
170
+ if (publicExponent !== encode(publicExponentHex)) {
171
+ throwError('Public exponent is invalid');
172
+ }
173
+ if (modulus !== encode(modulusHex)) {
174
+ throwError('Modulus is invalid');
175
+ }
176
+ };
178
177
 
179
178
  /**
180
179
  * Validates the list of certificates against the CAs provided
@@ -187,17 +186,14 @@ const validatePublicCertificate =
187
186
  const validateCertificatesSignature = (certificates, caroots = []) => {
188
187
  const certificateEngine = new CertificateChainValidationEngine({
189
188
  trustedCerts: caroots.map(decodeCert),
190
- certs: certificates
189
+ certs: certificates,
191
190
  });
192
191
 
193
- return certificateEngine.verify()
194
- .then(({result, resultCode, resultMessage}) => {
195
- if (!result) {
196
- throwError(
197
- `Certificate Validation failed [${resultCode}]: ${resultMessage}`
198
- );
199
- }
200
- });
192
+ return certificateEngine.verify().then(({result, resultCode, resultMessage}) => {
193
+ if (!result) {
194
+ throwError(`Certificate Validation failed [${resultCode}]: ${resultMessage}`);
195
+ }
196
+ });
201
197
  };
202
198
 
203
199
  /**
@@ -210,25 +206,27 @@ const validateCertificatesSignature = (certificates, caroots = []) => {
210
206
  * validate the KMS
211
207
  * @returns {Promise} when resolved will return the jwt
212
208
  */
213
- const validateKMS = (caroots) => (jwt = {}) => Promise.resolve()
214
- .then(() => {
215
- validateKtyHeader(jwt);
216
- validateKidHeader(jwt);
217
-
218
- if (!(isArray(jwt.x5c) && jwt.x5c.length > 0)) {
219
- throwError('JWK does not contain a list of certificates');
220
- }
221
- const certificates = jwt.x5c.map(decodeCert);
209
+ const validateKMS =
210
+ (caroots) =>
211
+ (jwt = {}) =>
212
+ Promise.resolve().then(() => {
213
+ validateKtyHeader(jwt);
214
+ validateKidHeader(jwt);
215
+
216
+ if (!(isArray(jwt.x5c) && jwt.x5c.length > 0)) {
217
+ throwError('JWK does not contain a list of certificates');
218
+ }
219
+ const certificates = jwt.x5c.map(decodeCert);
222
220
 
223
- validateCommonName(certificates, jwt);
224
- validatePublicCertificate(certificates, jwt);
221
+ validateCommonName(certificates, jwt);
222
+ validatePublicCertificate(certificates, jwt);
225
223
 
226
- // Skip validating signatures if no CA roots were provided
227
- const promise = caroots ?
228
- validateCertificatesSignature(certificates, caroots) : Promise.resolve();
224
+ // Skip validating signatures if no CA roots were provided
225
+ const promise = caroots
226
+ ? validateCertificatesSignature(certificates, caroots)
227
+ : Promise.resolve();
229
228
 
230
- return promise
231
- .then(() => jwt);
232
- });
229
+ return promise.then(() => jwt);
230
+ });
233
231
 
234
232
  export default validateKMS;
@@ -24,7 +24,10 @@ export default class KmsDryErrorInterceptor extends Interceptor {
24
24
  * @returns {Promise}
25
25
  */
26
26
  onResponseError(options, reason) {
27
- if (reason instanceof DryError && reason.message.match(/Failed to resolve authorization token in KmsMessage request for user/)) {
27
+ if (
28
+ reason instanceof DryError &&
29
+ reason.message.match(/Failed to resolve authorization token in KmsMessage request for user/)
30
+ ) {
28
31
  this.webex.logger.error('DRY Request Failed due to kms/test-user flakiness');
29
32
  this.webex.logger.error(reason);
30
33
 
@@ -43,13 +46,14 @@ export default class KmsDryErrorInterceptor extends Interceptor {
43
46
  replay(options, reason) {
44
47
  if (options.replayCount) {
45
48
  options.replayCount += 1;
46
- }
47
- else {
49
+ } else {
48
50
  options.replayCount = 1;
49
51
  }
50
52
 
51
53
  if (options.replayCount > this.webex.config.maxAuthenticationReplays) {
52
- this.webex.logger.error(`kms: failed after ${this.webex.config.maxAuthenticationReplays} replay attempts`);
54
+ this.webex.logger.error(
55
+ `kms: failed after ${this.webex.config.maxAuthenticationReplays} replay attempts`
56
+ );
53
57
 
54
58
  return Promise.reject(reason);
55
59
  }
package/src/kms-errors.js CHANGED
@@ -9,7 +9,8 @@ import {WebexHttpError} from '@webex/webex-core';
9
9
  * Error class for KMS errors
10
10
  */
11
11
  export class KmsError extends Exception {
12
- static defaultMessage = 'An unknown error occurred while communicating with the kms. This implies we received an error response without a body.';
12
+ static defaultMessage =
13
+ 'An unknown error occurred while communicating with the kms. This implies we received an error response without a body.';
13
14
 
14
15
  /**
15
16
  * @param {HttpResponse} body
@@ -21,20 +22,20 @@ export class KmsError extends Exception {
21
22
  Object.defineProperties(this, {
22
23
  body: {
23
24
  enumerable: false,
24
- value: body
25
+ value: body,
25
26
  },
26
27
  reason: {
27
28
  enumerable: false,
28
- value: body.reason
29
+ value: body.reason,
29
30
  },
30
31
  requestId: {
31
32
  enumerable: false,
32
- value: body.requestId
33
+ value: body.requestId,
33
34
  },
34
35
  status: {
35
36
  enumerable: false,
36
- value: body.status
37
- }
37
+ value: body.status,
38
+ },
38
39
  });
39
40
 
40
41
  let message = typeof body === 'string' ? body : body.reason;
@@ -63,7 +64,9 @@ export class KmsTimeoutError extends KmsError {
63
64
  * @returns {string}
64
65
  */
65
66
  parse({request = {}, timeout} = {}) {
66
- let message = `The KMS did not respond within ${timeout ? `${timeout} milliseconds` : 'a timely fashion'}`;
67
+ let message = `The KMS did not respond within ${
68
+ timeout ? `${timeout} milliseconds` : 'a timely fashion'
69
+ }`;
67
70
 
68
71
  if (request) {
69
72
  if (request.method && request.uri) {
@@ -100,12 +103,12 @@ export class DryError extends WebexHttpError {
100
103
  }
101
104
  if (this.options.url) {
102
105
  message += `\n${this.options.method} ${this.options.url}`;
103
- }
104
- else if (this.options.uri) {
106
+ } else if (this.options.uri) {
105
107
  message += `\n${this.options.method} ${this.options.uri}`;
106
- }
107
- else {
108
- message += `\n${this.options.method} ${this.options.service.toUpperCase()}/${this.options.resource}`;
108
+ } else {
109
+ message += `\n${this.options.method} ${this.options.service.toUpperCase()}/${
110
+ this.options.resource
111
+ }`;
109
112
  }
110
113
  message += `\nWEBEX_TRACKING_ID: ${this.options.headers.trackingid}`;
111
114
 
@@ -119,16 +122,16 @@ export class DryError extends WebexHttpError {
119
122
  Object.defineProperties(this, {
120
123
  reason: {
121
124
  enumerable: false,
122
- value: body.reason
125
+ value: body.reason,
123
126
  },
124
127
  requestId: {
125
128
  enumerable: false,
126
- value: body.requestId
129
+ value: body.requestId,
127
130
  },
128
131
  status: {
129
132
  enumerable: false,
130
- value: body.status
131
- }
133
+ value: body.status,
134
+ },
132
135
  });
133
136
 
134
137
  return message;