@mojaloop/sdk-scheme-adapter 12.2.2 → 13.0.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.
Files changed (76) hide show
  1. package/.env.example +3 -0
  2. package/CHANGELOG.md +26 -0
  3. package/audit-resolve.json +71 -1
  4. package/docker/ml-testing-toolkit/spec_files/api_definitions/fspiop_1.1/trigger_templates/transaction_request_followup.json +2 -2
  5. package/docker/ml-testing-toolkit/spec_files/rules_callback/default.json +7 -7
  6. package/docker/ml-testing-toolkit/spec_files/rules_response/default.json +16 -16
  7. package/docker/ml-testing-toolkit/spec_files/rules_response/default_pisp_rules.json +5 -5
  8. package/docker/ml-testing-toolkit/spec_files/rules_validation/default.json +10 -10
  9. package/package.json +4 -1
  10. package/src/ControlAgent/index.js +2 -3
  11. package/src/ControlServer/index.js +2 -2
  12. package/src/InboundServer/handlers.js +114 -52
  13. package/src/InboundServer/index.js +7 -7
  14. package/src/InboundServer/middlewares.js +2 -2
  15. package/src/OutboundServer/api.yaml +54 -3
  16. package/src/OutboundServer/api_interfaces/openapi.d.ts +24 -3
  17. package/src/OutboundServer/api_template/components/schemas/accountsResponse.yaml +9 -0
  18. package/src/OutboundServer/api_template/components/schemas/transferRequest.yaml +3 -0
  19. package/src/OutboundServer/api_template/components/schemas/transferResponse.yaml +28 -2
  20. package/src/OutboundServer/api_template/components/schemas/transferStatusResponse.yaml +8 -1
  21. package/src/OutboundServer/handlers.js +4 -1
  22. package/src/OutboundServer/index.js +10 -11
  23. package/src/config.js +29 -12
  24. package/src/index.js +198 -10
  25. package/src/lib/cache.js +110 -52
  26. package/src/lib/metrics.js +148 -0
  27. package/src/lib/model/AccountsModel.js +17 -12
  28. package/src/lib/model/Async2SyncModel.js +4 -1
  29. package/src/lib/model/InboundTransfersModel.js +170 -25
  30. package/src/lib/model/OutboundBulkQuotesModel.js +4 -1
  31. package/src/lib/model/OutboundBulkTransfersModel.js +4 -1
  32. package/src/lib/model/OutboundRequestToPayModel.js +9 -7
  33. package/src/lib/model/OutboundRequestToPayTransferModel.js +6 -3
  34. package/src/lib/model/OutboundTransfersModel.js +318 -53
  35. package/src/lib/model/PartiesModel.js +1 -1
  36. package/src/lib/model/ProxyModel/index.js +4 -2
  37. package/src/lib/model/common/BackendError.js +28 -4
  38. package/src/lib/model/common/index.js +2 -1
  39. package/src/lib/validate.js +2 -2
  40. package/test/__mocks__/@mojaloop/sdk-standard-components.js +3 -2
  41. package/test/__mocks__/redis.js +4 -0
  42. package/test/config/integration.env +5 -0
  43. package/test/integration/lib/Outbound/parties.test.js +1 -1
  44. package/test/unit/ControlServer/index.js +3 -3
  45. package/test/unit/InboundServer.test.js +10 -10
  46. package/test/unit/TestServer.test.js +11 -13
  47. package/test/unit/api/accounts/data/postAccountsErrorMojaloopResponse.json +11 -3
  48. package/test/unit/api/accounts/data/postAccountsSuccessResponse.json +14 -0
  49. package/test/unit/api/accounts/data/postAccountsSuccessResponseWithError1.json +13 -0
  50. package/test/unit/api/accounts/data/postAccountsSuccessResponseWithError2.json +18 -0
  51. package/test/unit/api/accounts/utils.js +15 -1
  52. package/test/unit/api/transfers/data/getTransfersCommittedResponse.json +18 -15
  53. package/test/unit/api/transfers/data/getTransfersErrorNotFound.json +1 -0
  54. package/test/unit/api/transfers/data/postTransfersErrorMojaloopResponse.json +9 -0
  55. package/test/unit/api/transfers/data/postTransfersErrorTimeoutResponse.json +1 -0
  56. package/test/unit/api/transfers/data/postTransfersSuccessResponse.json +74 -47
  57. package/test/unit/api/transfers/utils.js +85 -4
  58. package/test/unit/api/utils.js +4 -1
  59. package/test/unit/config.test.js +2 -2
  60. package/test/unit/data/commonHttpHeaders.json +1 -0
  61. package/test/unit/data/defaultConfig.json +23 -7
  62. package/test/unit/inboundApi/handlers.test.js +45 -14
  63. package/test/unit/index.test.js +95 -4
  64. package/test/unit/lib/model/AccountsModel.test.js +9 -6
  65. package/test/unit/lib/model/InboundTransfersModel.test.js +210 -30
  66. package/test/unit/lib/model/OutboundRequestToPayModel.test.js +1 -1
  67. package/test/unit/lib/model/OutboundRequestToPayTransferModel.test.js +3 -3
  68. package/test/unit/lib/model/OutboundTransfersModel.test.js +863 -158
  69. package/test/unit/lib/model/data/defaultConfig.json +25 -10
  70. package/test/unit/lib/model/data/mockArguments.json +97 -40
  71. package/test/unit/lib/model/data/payeeParty.json +13 -11
  72. package/test/unit/lib/model/data/quoteResponse.json +36 -25
  73. package/test/unit/lib/model/data/transferFulfil.json +5 -3
  74. package/src/lib/api/index.js +0 -12
  75. package/src/lib/randomphrase/index.js +0 -21
  76. package/src/lib/randomphrase/words.json +0 -3397
package/src/lib/cache.js CHANGED
@@ -21,8 +21,8 @@ const CONN_ST = {
21
21
  };
22
22
 
23
23
  /**
24
- * A shared cache abstraction over a REDIS distributed key/value store
25
- */
24
+ * A shared cache abstraction over a REDIS distributed key/value store
25
+ */
26
26
  class Cache {
27
27
  constructor(config) {
28
28
  this._config = config;
@@ -53,12 +53,12 @@ class Cache {
53
53
  }
54
54
 
55
55
  /**
56
- * Connects to a redis server and waits for ready events
57
- * Note: We create two connections. One for get, set and publish commands
58
- * and another for subscribe commands. We do this as we are not supposed
59
- * to issue any non-pub/sub related commands on a connection used for sub
60
- * See: https://redis.io/topics/pubsub
61
- */
56
+ * Connects to a redis server and waits for ready events
57
+ * Note: We create two connections. One for get, set and publish commands
58
+ * and another for subscribe commands. We do this as we are not supposed
59
+ * to issue any non-pub/sub related commands on a connection used for sub
60
+ * See: https://redis.io/topics/pubsub
61
+ */
62
62
  async connect() {
63
63
  switch(this._connectionState) {
64
64
  case CONN_ST.CONNECTED:
@@ -74,7 +74,6 @@ class Cache {
74
74
  await this._inProgressDisconnection;
75
75
  break;
76
76
  }
77
-
78
77
  this._connectionState = CONN_ST.CONNECTING;
79
78
  this._inProgressConnection = Promise.all([this._getClient(), this._getClient()]);
80
79
  [this._client, this._subscriptionClient] = await this._inProgressConnection;
@@ -82,15 +81,19 @@ class Cache {
82
81
  // hook up our sub message handler
83
82
  this._subscriptionClient.on('message', this._onMessage.bind(this));
84
83
 
84
+ if (this._config.enableTestFeatures) {
85
+ this.setTestMode(true);
86
+ }
87
+
85
88
  this._inProgressConnection = null;
86
89
  this._connectionState = CONN_ST.CONNECTED;
87
90
  }
88
91
 
89
92
  /**
90
- * Configure Redis to emit keyevent events. This corresponds to the application test mode, and
91
- * enables us to listen for changes on callback_* and request_* keys.
92
- * Docs: https://redis.io/topics/notifications
93
- */
93
+ * Configure Redis to emit keyevent events. This corresponds to the application test mode, and
94
+ * enables us to listen for changes on callback_* and request_* keys.
95
+ * Docs: https://redis.io/topics/notifications
96
+ */
94
97
  async setTestMode(enable) {
95
98
  // See for modes: https://redis.io/topics/notifications#configuration
96
99
  // This mode, 'Es$' is:
@@ -100,7 +103,7 @@ class Cache {
100
103
  const mode = enable ? 'Es$' : '';
101
104
  this._logger
102
105
  .push({ 'notify-keyspace-events': mode })
103
- .log('REDIS client Configured to emit keyspace-events');
106
+ .log('Configuring Redis to emit keyevent events');
104
107
  this._client.config('SET', 'notify-keyspace-events', mode);
105
108
  }
106
109
 
@@ -133,12 +136,12 @@ class Cache {
133
136
 
134
137
 
135
138
  /**
136
- * Subscribes to a channel
137
- *
138
- * @param channel {string} - The channel name to subscribe to
139
- * @param callback {function} - Callback function to be executed when messages arrive on the specified channel
140
- * @returns {Promise} - Promise that resolves with an integer callback Id to submit in unsubscribe request
141
- */
139
+ * Subscribes to a channel
140
+ *
141
+ * @param channel {string} - The channel name to subscribe to
142
+ * @param callback {function} - Callback function to be executed when messages arrive on the specified channel
143
+ * @returns {Promise} - Promise that resolves with an integer callback Id to submit in unsubscribe request
144
+ */
142
145
  async subscribe(channel, callback) {
143
146
  return new Promise((resolve, reject) => {
144
147
  this._subscriptionClient.subscribe(channel, (err) => {
@@ -168,11 +171,11 @@ class Cache {
168
171
 
169
172
 
170
173
  /**
171
- * Unsubscribes a callback from a channel
172
- *
173
- * @param channel {string} - name of the channel to unsubscribe from
174
- * @param callbackId {integer} - id of the callback to remove
175
- */
174
+ * Unsubscribes a callback from a channel
175
+ *
176
+ * @param channel {string} - name of the channel to unsubscribe from
177
+ * @param callbackId {integer} - id of the callback to remove
178
+ */
176
179
  async unsubscribe(channel, callbackId) {
177
180
  return new Promise((resolve, reject) => {
178
181
  if(this._callbacks[channel] && this._callbacks[channel][callbackId]) {
@@ -196,8 +199,8 @@ class Cache {
196
199
 
197
200
 
198
201
  /**
199
- * Handler for published messages
200
- */
202
+ * Handler for published messages
203
+ */
201
204
  async _onMessage(channel, msg) {
202
205
  if(this._callbacks[channel]) {
203
206
  // we have some callbacks to make
@@ -219,16 +222,16 @@ class Cache {
219
222
 
220
223
 
221
224
  /**
222
- * Returns a new redis client
223
- *
224
- * @returns {object} - a connected REDIS client
225
- * */
225
+ * Returns a new redis client
226
+ *
227
+ * @returns {object} - a connected REDIS client
228
+ * */
226
229
  async _getClient() {
227
230
  return new Promise((resolve, reject) => {
228
231
  const client = redis.createClient(this._config);
229
232
 
230
233
  client.on('error', (err) => {
231
- this._logger.push({ err }).log('REDIS client Error');
234
+ this._logger.push({ err }).log('Error from REDIS client getting subscriber');
232
235
  return reject(err);
233
236
  });
234
237
 
@@ -247,25 +250,25 @@ class Cache {
247
250
  }
248
251
  });
249
252
 
250
- client.on('ready', () => {
251
- this._logger.log(`REDIS client ready at: ${this._config.host}:${this._config.port}`);
252
- return resolve(client);
253
- });
254
-
255
253
  client.on('connect', () => {
256
254
  this._logger.log(`REDIS client connected at: ${this._config.host}:${this._config.port}`);
257
255
  });
256
+
257
+ client.on('ready', () => {
258
+ this._logger.log(`Connected to REDIS at: ${this._config.host}:${this._config.port}`);
259
+ return resolve(client);
260
+ });
258
261
  });
259
262
  }
260
263
 
261
264
 
262
265
  /**
263
- * Publishes the specified message to the specified channel
264
- *
265
- * @param channelName {string} - channel name to publish to
266
- * @param value - any type that will be converted to a JSON string (unless it is already a string) and published as the message
267
- * @returns {Promise} - Promise that will resolve with redis replies or reject with an error
268
- */
266
+ * Publishes the specified message to the specified channel
267
+ *
268
+ * @param channelName {string} - channel name to publish to
269
+ * @param value - any type that will be converted to a JSON string (unless it is already a string) and published as the message
270
+ * @returns {Promise} - Promise that will resolve with redis replies or reject with an error
271
+ */
269
272
  async publish(channelName, value) {
270
273
  return new Promise((resolve, reject) => {
271
274
  if(typeof(value) !== 'string') {
@@ -288,11 +291,11 @@ class Cache {
288
291
 
289
292
 
290
293
  /**
291
- * Sets a value in the cache
292
- *
293
- * @param key {string} - cache key
294
- * @param value {stirng} - cache value
295
- */
294
+ * Sets a value in the cache
295
+ *
296
+ * @param key {string} - cache key
297
+ * @param value {stirng} - cache value
298
+ */
296
299
  async set(key, value) {
297
300
  return new Promise((resolve, reject) => {
298
301
  //if we are given an object, turn it into a string
@@ -313,10 +316,65 @@ class Cache {
313
316
  }
314
317
 
315
318
  /**
316
- * Gets a value from the cache
317
- *
318
- * @param key {string} - cache key
319
- */
319
+ * Add the specified value to the set stored at key
320
+ *
321
+ * @param key {string} - cache key
322
+ * @param value {string} - cache value
323
+ */
324
+ async add(key, value) {
325
+ return new Promise((resolve, reject) => {
326
+ //if we are given an object, turn it into a string
327
+ if(typeof(value) !== 'string') {
328
+ value = JSON.stringify(value);
329
+ }
330
+
331
+ this._client.sadd(key, value, (err, replies) => {
332
+ if(err) {
333
+ this._logger.push({ key, value, err }).log(`Error setting cache key: ${key}`);
334
+ return reject(err);
335
+ }
336
+
337
+ this._logger.push({ key, value, replies }).log(`Add cache key: ${key}`);
338
+ return resolve(replies);
339
+ });
340
+ });
341
+ }
342
+
343
+ /**
344
+ * Returns all the members of the set value stored at key
345
+ *
346
+ * @param key {string} - cache key
347
+ */
348
+ async members(key) {
349
+ return new Promise((resolve, reject) => {
350
+ this._client.smembers(key, (err, value) => {
351
+ if(err) {
352
+ this._logger.push({ key, err }).log(`Error getting cache key: ${key}`);
353
+ return reject(err);
354
+ }
355
+
356
+ this._logger.push({ key, value }).log(`Got cache key: ${key}`);
357
+
358
+ if(typeof(value) === 'string') {
359
+ try {
360
+ value = JSON.parse(value);
361
+ }
362
+ catch(err) {
363
+ this._logger.push({ err }).log('Error parsing JSON cache value');
364
+ return reject(err);
365
+ }
366
+ }
367
+
368
+ return resolve(value);
369
+ });
370
+ });
371
+ }
372
+
373
+ /**
374
+ * Gets a value from the cache
375
+ *
376
+ * @param key {string} - cache key
377
+ */
320
378
  async get(key) {
321
379
  return new Promise((resolve, reject) => {
322
380
  this._client.get(key, (err, value) => {
@@ -0,0 +1,148 @@
1
+ /**************************************************************************
2
+ * (C) Copyright ModusBox Inc. 2019 - All rights reserved. *
3
+ * *
4
+ * This file is made available under the terms of the license agreement *
5
+ * specified in the corresponding source code repository. *
6
+ * *
7
+ * ORIGINAL AUTHOR: *
8
+ * James Bush - james.bush@modusbox.com *
9
+ **************************************************************************/
10
+
11
+ 'use strict';
12
+
13
+ const http = require('http');
14
+ const Koa = require('koa');
15
+ const koaBody = require('koa-body');
16
+ const PrometheusClient = require('prom-client');
17
+
18
+
19
+ /**
20
+ * A utility class that abstracts the underlying metrics implementation (Prometheus)
21
+ * from the consumer. This may be premature if Prometheus is never swapped out...but
22
+ * who can tell what the Universe will bring us.
23
+ *
24
+ * This object exposes methods for getting different types of measurement construct
25
+ * in order for consuming code to record metrics. The constructs are quite tightly
26
+ * coupled to Prometheus view of metrics, although that is fairly abstract so the
27
+ * risk appears low that this will cause conflicts in future.
28
+ *
29
+ * The metrics client is intended to be used as a singleton in a process and keeps a
30
+ * 'per name' cache of metrics to avoid duplicates. Not sure if this is strictly
31
+ * necessary but I dont have time to dig into the prom-client code to see what
32
+ * happens if you create the same metric twice.
33
+ */
34
+ class MetricsClient {
35
+ constructor() {
36
+ this._prometheusRegister = PrometheusClient.register;
37
+ this._metrics = {};
38
+
39
+ this._counterPrefix = 'cntr_';
40
+ this._histogramPrefix = 'hist_';
41
+ this._gaugePrefix = 'gage_';
42
+ }
43
+
44
+
45
+ getHistogram(name, description, buckets) {
46
+ const metricName = `${this._histogramPrefix}${name}`;
47
+
48
+ let conf = {
49
+ name: name,
50
+ help: description,
51
+ };
52
+
53
+ if(buckets) {
54
+ conf.buckets = buckets;
55
+ }
56
+
57
+ if(!this._metrics[metricName]) {
58
+ this._metrics[metricName] = new PrometheusClient.Histogram(conf);
59
+ }
60
+
61
+ return this._metrics[metricName];
62
+ }
63
+
64
+
65
+ getCounter(name, description) {
66
+ const metricName = `${this._counterPrefix}${name}`;
67
+
68
+ if(!this._metrics[metricName]) {
69
+ this._metrics[metricName] = new PrometheusClient.Counter({
70
+ name: name,
71
+ help: description
72
+ });
73
+ }
74
+
75
+ return this._metrics[metricName];
76
+ }
77
+
78
+
79
+ getGauge(name, description) {
80
+ const metricName = `${this._counterPrefix}${name}`;
81
+
82
+ if(!this._metrics[metricName]) {
83
+ this._metrics[metricName] = new PrometheusClient.Gauge({
84
+ name: name,
85
+ help: description
86
+ });
87
+ }
88
+
89
+ return this._metrics[metricName];
90
+ }
91
+ }
92
+
93
+
94
+ /**
95
+ * Exposes an HTTP endpoint for metrics to be scraped by some external daemon
96
+ */
97
+ class MetricsServer {
98
+ /**
99
+ * @param {number} port metrics server listen port
100
+ * @param {Logger} logger Logger
101
+ * @param {Object} prometheusClient Prometheus client instance
102
+ */
103
+ constructor({ port, logger }) {
104
+ this._port = port;
105
+ this._logger = logger;
106
+ this._prometheusClient = PrometheusClient;
107
+ this._prometheusRegister = PrometheusClient.register;
108
+ this._api = this.setupApi();
109
+ this._server = http.createServer(this._api.callback());
110
+ }
111
+
112
+ async start() {
113
+ if (this._server.listening) {
114
+ return;
115
+ }
116
+ this._prometheusClient.collectDefaultMetrics({
117
+ prefix: 'mojaloop_connector_default_'
118
+ });
119
+
120
+ await new Promise((resolve) => this._server.listen(this._port, resolve));
121
+ this._logger.push({ port: this._port }).log('Serving Metrics');
122
+ }
123
+
124
+ async stop() {
125
+ await new Promise(resolve => this._server.close(resolve));
126
+ this._logger.log('Metrics Server shut down complete');
127
+ }
128
+
129
+ setupApi() {
130
+ const result = new Koa();
131
+
132
+ result.use(koaBody());
133
+ result.use(async ctx => {
134
+ this._logger.log('Metrics request received');
135
+
136
+ ctx.response.set('Content-Type', this._prometheusRegister.contentType);
137
+ ctx.response.body = this._prometheusRegister.metrics();
138
+ });
139
+
140
+ return result;
141
+ }
142
+ }
143
+
144
+
145
+ module.exports = {
146
+ MetricsServer,
147
+ MetricsClient
148
+ };
@@ -36,7 +36,10 @@ class AccountsModel {
36
36
  logger: this._logger,
37
37
  peerEndpoint: config.alsEndpoint,
38
38
  dfspId: config.dfspId,
39
- tls: config.tls,
39
+ tls: {
40
+ enabled: config.outbound.tls.mutualTLS.enabled,
41
+ creds: config.outbound.tls.creds,
42
+ },
40
43
  jwsSign: config.jwsSign,
41
44
  jwsSigningKey: config.jwsSigningKey,
42
45
  wso2: config.wso2,
@@ -130,20 +133,23 @@ class AccountsModel {
130
133
 
131
134
 
132
135
  async _executeCreateAccountsRequest(request) {
136
+ const accountRequest = request;
137
+
133
138
  // eslint-disable-next-line no-async-promise-executor
134
139
  return new Promise(async (resolve, reject) => {
135
- const requestKey = `ac_${request.requestId}`;
140
+ const requestKey = `ac_${accountRequest.requestId}`;
136
141
 
137
142
  const subId = await this._cache.subscribe(requestKey, async (cn, msg, subId) => {
138
143
  try {
139
144
  let error;
140
- let message = JSON.parse(msg);
145
+ const message = JSON.parse(msg);
146
+ this._data.postAccountsResponse = message.data;
141
147
 
142
148
  if (message.type === 'accountsCreationErrorResponse') {
143
- error = new BackendError(`Got an error response creating accounts: ${util.inspect(message.data)}`, 500);
144
- error.mojaloopError = message.data;
149
+ error = new BackendError(`Got an error response creating accounts: ${util.inspect(this._data.postAccountsResponse.body)}`, 500);
150
+ error.mojaloopError = this._data.postAccountsResponse.body;
145
151
  } else if (message.type !== 'accountsCreationSuccessfulResponse') {
146
- this._logger.push({ message }).log(
152
+ this._logger.push(util.inspect(this._data.postAccountsResponse)).log(
147
153
  `Ignoring cache notification for request ${requestKey}. ` +
148
154
  `Unknown message type ${message.type}.`
149
155
  );
@@ -164,7 +170,7 @@ class AccountsModel {
164
170
  return reject(error);
165
171
  }
166
172
 
167
- const response = message.data;
173
+ const response = this._data.postAccountsResponse;
168
174
  this._logger.push({ response }).log('Account creation response received');
169
175
  return resolve(response);
170
176
  }
@@ -175,7 +181,7 @@ class AccountsModel {
175
181
 
176
182
  // set up a timeout for the request
177
183
  const timeout = setTimeout(() => {
178
- const err = new BackendError(`Timeout waiting for response to account creation request ${request.requestId}`, 504);
184
+ const err = new BackendError(`Timeout waiting for response to account creation request ${accountRequest.requestId}`, 504);
179
185
 
180
186
  // we dont really care if the unsubscribe fails but we should log it regardless
181
187
  this._cache.unsubscribe(requestKey, subId).catch(e => {
@@ -188,7 +194,7 @@ class AccountsModel {
188
194
  // now we have a timeout handler and a cache subscriber hooked up we can fire off
189
195
  // a POST /participants request to the switch
190
196
  try {
191
- const res = await this._requests.postParticipants(request);
197
+ const res = await this._requests.postParticipants(accountRequest);
192
198
  this._logger.push({ res }).log('Account creation request sent to peer');
193
199
  }
194
200
  catch(err) {
@@ -215,11 +221,11 @@ class AccountsModel {
215
221
  }
216
222
 
217
223
  _buildClientResponse(response) {
218
- return response.partyList.map(party => ({
224
+ return response.body.partyList.map(party => ({
219
225
  idType: party.partyId.partyIdType,
220
226
  idValue: party.partyId.partyIdentifier,
221
227
  idSubValue: party.partyId.partySubIdOrType,
222
- ...!response.currency && {
228
+ ...!response.body.currency && {
223
229
  error: {
224
230
  statusCode: Errors.MojaloopApiErrorCodes.CLIENT_ERROR.code,
225
231
  message: 'Provided currency not supported',
@@ -292,7 +298,6 @@ class AccountsModel {
292
298
  resp.currentState = stateEnum.ERROR_OCCURRED;
293
299
  break;
294
300
  }
295
-
296
301
  return resp;
297
302
  }
298
303
 
@@ -228,7 +228,10 @@ function generate({
228
228
  transfersEndpoint: config.transfersEndpoint,
229
229
  transactionRequestsEndpoint: config.transactionRequestsEndpoint,
230
230
  dfspId: config.dfspId,
231
- tls: config.tls,
231
+ tls: {
232
+ enabled: config.outbound.tls.mutualTLS.enabled,
233
+ creds: config.outbound.tls.creds,
234
+ },
232
235
  jwsSign: config.jwsSign,
233
236
  jwsSignPutParties: config.jwsSignPutParties,
234
237
  jwsSigningKey: config.jwsSigningKey,