@mojaloop/sdk-scheme-adapter 15.0.0 → 17.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 (47) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/audit-resolve.json +43 -542
  3. package/docs/dfspInboundApi.yaml +11 -0
  4. package/package.json +27 -25
  5. package/src/ControlAgent/index.js +8 -11
  6. package/src/ControlServer/index.js +13 -13
  7. package/src/InboundServer/index.js +16 -63
  8. package/src/InboundServer/middlewares.js +12 -4
  9. package/src/OAuthTestServer/index.js +0 -13
  10. package/src/OutboundServer/index.js +13 -55
  11. package/src/OutboundServer/middlewares.js +6 -2
  12. package/src/TestServer/index.js +10 -35
  13. package/src/config.js +1 -4
  14. package/src/index.js +163 -146
  15. package/src/lib/cache.js +93 -186
  16. package/src/lib/metrics.js +1 -3
  17. package/src/lib/model/InboundTransfersModel.js +10 -6
  18. package/src/lib/model/OutboundTransfersModel.js +1 -1
  19. package/src/lib/router.js +3 -1
  20. package/src/lib/validate.js +10 -1
  21. package/test/__mocks__/redis.js +51 -26
  22. package/test/config/integration.env +1 -2
  23. package/test/integration/lib/cache.test.js +1 -2
  24. package/test/integration/testEnv.js +1 -4
  25. package/test/unit/ControlClient.test.js +1 -45
  26. package/test/unit/ControlServer/index.js +18 -22
  27. package/test/unit/ControlServer.test.js +0 -60
  28. package/test/unit/InboundServer.test.js +8 -8
  29. package/test/unit/TestServer.test.js +1 -1
  30. package/test/unit/api/accounts/accounts.test.js +2 -2
  31. package/test/unit/api/transfers/transfers.test.js +1 -1
  32. package/test/unit/api/utils.js +12 -4
  33. package/test/unit/config.test.js +1 -2
  34. package/test/unit/data/defaultConfig.json +1 -5
  35. package/test/unit/index.test.js +5 -64
  36. package/test/unit/lib/cache.test.js +5 -6
  37. package/test/unit/lib/model/AccountsModel.test.js +3 -4
  38. package/test/unit/lib/model/InboundTransfersModel.test.js +55 -16
  39. package/test/unit/lib/model/OutboundBulkQuotesModel.test.js +3 -4
  40. package/test/unit/lib/model/OutboundBulkTransfersModel.test.js +1 -2
  41. package/test/unit/lib/model/OutboundRequestToPayModel.test.js +3 -4
  42. package/test/unit/lib/model/OutboundRequestToPayTransferModel.test.js +3 -4
  43. package/test/unit/lib/model/OutboundTransfersModel.test.js +2 -3
  44. package/test/unit/lib/model/common/PersistentStateMachine.test.js +3 -4
  45. package/test/unit/lib/model/data/defaultConfig.json +1 -4
  46. package/test/unit/lib/model/data/notificationAbortedToPayee.json +10 -0
  47. package/test/unit/lib/model/data/notificationReservedToPayee.json +10 -0
package/src/index.js CHANGED
@@ -10,11 +10,10 @@
10
10
 
11
11
  'use strict';
12
12
 
13
- const assert = require('assert/strict');
14
13
  const { hostname } = require('os');
14
+ const _ = require('lodash');
15
15
  const config = require('./config');
16
16
  const EventEmitter = require('events');
17
- const _ = require('lodash');
18
17
 
19
18
  const InboundServer = require('./InboundServer');
20
19
  const OutboundServer = require('./OutboundServer');
@@ -30,8 +29,7 @@ const OutboundServerMiddleware = require('./OutboundServer/middlewares.js');
30
29
  const Router = require('./lib/router');
31
30
  const Validate = require('./lib/validate');
32
31
  const Cache = require('./lib/cache');
33
- const check = require('./lib/check');
34
- const { Logger } = require('@mojaloop/sdk-standard-components');
32
+ const { Logger, WSO2Auth } = require('@mojaloop/sdk-standard-components');
35
33
 
36
34
  const LOG_ID = {
37
35
  INBOUND: { app: 'mojaloop-connector-inbound-api' },
@@ -52,7 +50,7 @@ class Server extends EventEmitter {
52
50
  this.conf = conf;
53
51
  this.logger = logger;
54
52
  this.cache = new Cache({
55
- ...conf.cacheConfig,
53
+ cacheUrl: conf.cacheUrl,
56
54
  logger: this.logger.push(LOG_ID.CACHE),
57
55
  enableTestFeatures: conf.enableTestFeatures,
58
56
  });
@@ -64,11 +62,24 @@ class Server extends EventEmitter {
64
62
  logger: this.logger.push(LOG_ID.METRICS)
65
63
  });
66
64
 
65
+ this.wso2 = {
66
+ auth: new WSO2Auth({
67
+ ...conf.wso2.auth,
68
+ logger,
69
+ tlsCreds: conf.outbound.tls.mutualTLS.enabled && conf.outbound.tls.creds,
70
+ }),
71
+ retryWso2AuthFailureTimes: conf.wso2.requestAuthFailureRetryTimes,
72
+ };
73
+ this.wso2.auth.on('error', (msg) => {
74
+ this.emit('error', 'WSO2 auth error in InboundApi', msg);
75
+ });
76
+
67
77
  this.inboundServer = new InboundServer(
68
78
  this.conf,
69
79
  this.logger.push(LOG_ID.INBOUND),
70
80
  this.cache,
71
- this.metricsClient
81
+ this.metricsClient,
82
+ this.wso2,
72
83
  );
73
84
  this.inboundServer.on('error', (...args) => {
74
85
  this.logger.push({ args }).log('Unhandled error in Inbound Server');
@@ -79,76 +90,63 @@ class Server extends EventEmitter {
79
90
  this.conf,
80
91
  this.logger.push(LOG_ID.OUTBOUND),
81
92
  this.cache,
82
- this.metricsClient
93
+ this.metricsClient,
94
+ this.wso2,
83
95
  );
84
96
  this.outboundServer.on('error', (...args) => {
85
97
  this.logger.push({ args }).log('Unhandled error in Outbound Server');
86
98
  this.emit('error', 'Unhandled error in Outbound Server');
87
99
  });
88
100
 
89
- this.oauthTestServer = new OAuthTestServer({
90
- clientKey: this.conf.oauthTestServer.clientKey,
91
- clientSecret: this.conf.oauthTestServer.clientSecret,
92
- port: this.conf.oauthTestServer.listenPort,
93
- logger: this.logger.push(LOG_ID.OAUTHTEST),
94
- });
101
+ if (this.conf.oauthTestServer.enabled) {
102
+ this.oauthTestServer = new OAuthTestServer({
103
+ clientKey: this.conf.oauthTestServer.clientKey,
104
+ clientSecret: this.conf.oauthTestServer.clientSecret,
105
+ port: this.conf.oauthTestServer.listenPort,
106
+ logger: this.logger.push(LOG_ID.OAUTHTEST),
107
+ });
108
+ }
95
109
 
96
- this.testServer = new TestServer({
97
- port: this.conf.test.port,
98
- logger: this.logger.push(LOG_ID.TEST),
99
- cache: this.cache,
100
- });
110
+ if (this.conf.enableTestFeatures) {
111
+ this.testServer = new TestServer({
112
+ port: this.conf.test.port,
113
+ logger: this.logger.push(LOG_ID.TEST),
114
+ cache: this.cache,
115
+ });
116
+ }
117
+ }
118
+
119
+ async start() {
120
+ await this.cache.connect();
121
+ await this.wso2.auth.start();
122
+
123
+ // We only start the control client if we're running within Mojaloop Payment Manager.
124
+ // The control server is the Payment Manager Management API Service.
125
+ // We only start the client to connect to and listen to the Management API service for
126
+ // management protocol messages e.g configuration changes, certicate updates etc.
127
+ if (this.conf.pm4mlEnabled) {
128
+ const RESTART_INTERVAL_MS = 10000;
129
+ this.controlClient = await ControlAgent.Client.Create({
130
+ address: this.conf.control.mgmtAPIWsUrl,
131
+ port: this.conf.control.mgmtAPIWsPort,
132
+ logger: this.logger.push(LOG_ID.CONTROL),
133
+ appConfig: this.conf,
134
+ });
135
+ this.controlClient.on(ControlAgent.EVENT.RECONFIGURE, this.restart.bind(this));
136
+ this.controlClient.on('close', () => setTimeout(() => this.restart(_.merge({}, this.conf, { control: { stopped: Date.now() } })), RESTART_INTERVAL_MS));
137
+ }
138
+
139
+ await Promise.all([
140
+ this.inboundServer.start(),
141
+ this.outboundServer.start(),
142
+ this.metricsServer.start(),
143
+ this.testServer?.start(),
144
+ this.oauthTestServer?.start(),
145
+ ]);
101
146
  }
102
147
 
103
148
  async restart(newConf) {
104
- // Figuring out what config is necessary in each server and component is a pretty big job
105
- // that we'll have to save for later. For now, when the config changes, we'll restart
106
- // more than we might have to.
107
- // We'll do this by:
108
- // 0. creating a new instance of the logger, if necessary
109
- // 1. creating a new instance of the cache, if necessary
110
- // 2. calling the async reconfigure method of each of the servers as necessary- this will
111
- // return a synchronous function that we can call to swap over the server events and
112
- // object properties to the new ones. It will:
113
- // 1. remove the `request` listener for each of the HTTP servers
114
- // 2. add the new appropriate `request` listener
115
- // This results in a completely synchronous listener changeover to the new config and
116
- // therefore hopefully avoids any concurrency issues arising from restarting different
117
- // servers or components concurrently.
118
- // TODO: in the sense of being able to reason about the code, it would make some sense to
119
- // turn the config items or object passed to each server into an event emitter, or pass an
120
- // additional event emitter to the server constructor for the server to listen to and act
121
- // on changes. Before this, however, it's probably necessary to ensure each server gets
122
- // _only_ the config it needs, not the entire config object.
123
- // Further: it might be possible to use Object.observe for this functionality.
124
- // TODO: what happens if this is run concurrently? I.e. if it is called twice in rapid
125
- // succession. This question probably needs to be asked of the reconfigure message on every
126
- // server.
127
- // Note that it should be possible to reconfigure ports on a running server by reassigning
128
- // servers, e.g.
129
- // this.inboundServer._server = createHttpServer();
130
- // this.inboundServer._server.listen(newPort);
131
- // If there are conflicts, for example if the new configuration specifies the new inbound
132
- // port to be the same value as the old outbound port, this will require either
133
- // 1. some juggling of HTTP servers, e.g.
134
- // const oldInboundServer = this.inboundServer._server;
135
- // this.inboundServer._server = this.outboundServer._server;
136
- // .. etc.
137
- // 2. some juggling of sockets between servers, if possible
138
- // 3. rearchitecting of the servers, perhaps splitting the .start() method on the servers
139
- // to an .init() and .listen() methods, with the latter optionally taking an HTTP server
140
- // as argument
141
- // This _might_ introduce some confusion/complexity for existing websocket clients, but as
142
- // the event handlers _should_ not be modified this shouldn't be a problem. A careful
143
- // analysis of this will be necessary.
144
- assert(newConf.inbound.port === this.conf.inbound.port
145
- && newConf.outbound.port === this.conf.outbound.port
146
- && newConf.test.port === this.conf.test.port
147
- && newConf.oauthTestServer.listenPort === this.conf.oauthTestServer.listenPort
148
- && newConf.control.mgmtAPIWsPort === this.conf.control.mgmtAPIWsPort,
149
- 'Cannot reconfigure ports on running server');
150
- const doNothing = () => {};
151
- const updateLogger = check.notDeepEqual(newConf.logIndent, this.conf.logIndent);
149
+ const updateLogger = !_.isEqual(newConf.logIndent, this.conf.logIndent);
152
150
  if (updateLogger) {
153
151
  this.logger = new Logger.Logger({
154
152
  context: {
@@ -159,99 +157,118 @@ class Server extends EventEmitter {
159
157
  stringify: Logger.buildStringify({ space: this.conf.logIndent }),
160
158
  });
161
159
  }
160
+
162
161
  let oldCache;
163
- const updateCache = (
164
- updateLogger ||
165
- check.notDeepEqual(this.conf.cacheConfig, newConf.cacheConfig) ||
166
- check.notDeepEqual(this.conf.enableTestFeatures, newConf.enableTestFeatures)
167
- );
162
+ const updateCache = !_.isEqual(this.conf.cacheUrl, newConf.cacheUrl)
163
+ || !_.isEqual(this.conf.enableTestFeatures, newConf.enableTestFeatures);
168
164
  if (updateCache) {
169
165
  oldCache = this.cache;
166
+ await this.cache.disconnect();
170
167
  this.cache = new Cache({
171
- ...newConf.cacheConfig,
168
+ cacheUrl: newConf.cacheUrl,
172
169
  logger: this.logger.push(LOG_ID.CACHE),
173
170
  enableTestFeatures: newConf.enableTestFeatures,
174
171
  });
175
172
  await this.cache.connect();
176
173
  }
177
- const confChanged = !check.deepEqual(newConf, this.conf);
178
- // TODO: find better naming than "restart", because that's not really what's happening.
179
- const [restartInboundServer, restartOutboundServer, restartControlClient] = confChanged
180
- ? await Promise.all([
181
- this.inboundServer.reconfigure(newConf, this.logger.push(LOG_ID.INBOUND), this.cache),
182
- this.outboundServer.reconfigure(newConf, this.logger.push(LOG_ID.OUTBOUND), this.cache, this.metricsClient),
183
- this.controlClient.reconfigure({
184
- logger: this.logger.push(LOG_ID.CONTROL),
185
- port: newConf.control.mgmtAPIWsPort,
186
- appConfig: newConf
187
- }),
188
- ])
189
- : [doNothing, doNothing, doNothing];
190
- const updateOAuthTestServer = (
191
- updateLogger || check.notDeepEqual(newConf.oauthTestServer, this.conf.oauthTestServer)
192
- );
193
- const restartOAuthTestServer = updateOAuthTestServer
194
- ? await this.oauthTestServer.reconfigure({
195
- clientKey: this.conf.oauthTestServer.clientKey,
196
- clientSecret: this.conf.oauthTestServer.clientSecret,
197
- port: this.conf.oauthTestServer.listenPort,
198
- logger: this.logger.push(LOG_ID.OAUTHTEST),
199
- })
200
- : doNothing;
201
- const updateTestServer = (
202
- updateLogger || updateCache || check.notDeepEqual(newConf.test.port, this.conf.test.port)
203
- );
204
- const restartTestServer = updateTestServer
205
- ? await this.testServer.reconfigure({
206
- port: newConf.test.port,
207
- logger: this.logger.push(LOG_ID.TEST),
208
- cache: this.cache,
209
- })
210
- : doNothing;
211
- // You may not return an async restart function. Perform any required async activity in the
212
- // reconfigure function and return a sync restart function. See the note at the top of this
213
- // file.
214
- [restartTestServer, restartOAuthTestServer, restartInboundServer, restartOutboundServer, restartControlClient]
215
- .map(f => assert(Promise.resolve(f) !== f, 'Restart functions must be synchronous'));
216
- restartTestServer();
217
- restartOAuthTestServer();
218
- restartInboundServer();
219
- restartOutboundServer();
220
- restartControlClient();
221
- this.conf = newConf;
222
- await Promise.all([
223
- oldCache && oldCache.disconnect(),
224
- ]);
225
- }
226
174
 
227
- async start() {
228
- await this.cache.connect();
175
+ const updateWSO2 = !_.isEqual(this.conf.wso2, newConf.wso2)
176
+ || !_.isEqual(this.conf.outbound.tls, newConf.outbound.tls);
177
+ if (updateWSO2) {
178
+ this.wso2.auth.stop();
179
+ this.wso2.auth = new WSO2Auth({
180
+ ...newConf.wso2.auth,
181
+ logger: this.logger,
182
+ tlsCreds: newConf.outbound.tls.mutualTLS.enabled && newConf.outbound.tls.creds,
183
+ });
184
+ this.wso2.retryWso2AuthFailureTimes = newConf.wso2.requestAuthFailureRetryTimes;
185
+ this.wso2.auth.on('error', (msg) => {
186
+ this.emit('error', 'WSO2 auth error in InboundApi', msg);
187
+ });
188
+ await this.wso2.auth.start();
189
+ }
229
190
 
230
- const startTestServer = this.conf.enableTestFeatures ? this.testServer.start() : null;
231
- const startOauthTestServer = this.conf.oauthTestServer.enabled
232
- ? this.oauthTestServer.start()
233
- : null;
191
+ const updateInboundServer = !_.isEqual(this.conf.inbound, newConf.inbound);
192
+ if (updateInboundServer) {
193
+ await this.inboundServer.stop();
194
+ this.inboundServer = new InboundServer(
195
+ newConf,
196
+ this.logger.push(LOG_ID.INBOUND),
197
+ this.cache,
198
+ this.metricsClient,
199
+ this.wso2,
200
+ );
201
+ this.inboundServer.on('error', (...args) => {
202
+ this.logger.push({ args }).log('Unhandled error in Inbound Server');
203
+ this.emit('error', 'Unhandled error in Inbound Server');
204
+ });
205
+ await this.inboundServer.start();
206
+ }
234
207
 
235
- // We only start the control client if we're running within Mojaloop Payment Manager.
236
- // The control server is the Payment Manager Management API Service.
237
- // We only start the client to connect to and listen to the Management API service for
238
- // management protocol messages e.g configuration changes, certificate updates etc.
239
- if (this.conf.pm4mlEnabled) {
240
- this.controlClient = await ControlAgent.Client.Create({
241
- address: this.conf.control.mgmtAPIWsUrl,
242
- port: this.conf.control.mgmtAPIWsPort,
243
- logger: this.logger.push(LOG_ID.CONTROL),
244
- appConfig: this.conf,
208
+ const updateOutboundServer = !_.isEqual(this.conf.outbound, newConf.outbound);
209
+ if (updateOutboundServer) {
210
+ await this.outboundServer.stop();
211
+ this.outboundServer = new OutboundServer(
212
+ newConf,
213
+ this.logger.push(LOG_ID.OUTBOUND),
214
+ this.cache,
215
+ this.metricsClient,
216
+ this.wso2,
217
+ );
218
+ this.outboundServer.on('error', (...args) => {
219
+ this.logger.push({ args }).log('Unhandled error in Outbound Server');
220
+ this.emit('error', 'Unhandled error in Outbound Server');
245
221
  });
246
- this.controlClient.on(ControlAgent.EVENT.RECONFIGURE, this.restart.bind(this));
222
+ await this.outboundServer.start();
247
223
  }
248
224
 
225
+ const updateControlClient = !_.isEqual(this.conf.control, newConf.control);
226
+ if (updateControlClient) {
227
+ await this.controlClient?.stop();
228
+ if (this.conf.pm4mlEnabled) {
229
+ const RESTART_INTERVAL_MS = 10000;
230
+ this.controlClient = await ControlAgent.Client.Create({
231
+ address: newConf.control.mgmtAPIWsUrl,
232
+ port: newConf.control.mgmtAPIWsPort,
233
+ logger: this.logger.push(LOG_ID.CONTROL),
234
+ appConfig: newConf,
235
+ });
236
+ this.controlClient.on(ControlAgent.EVENT.RECONFIGURE, this.restart.bind(this));
237
+ this.controlClient.on('close', () => setTimeout(() => this.restart(_.merge({}, newConf, { control: { stopped: Date.now() } })), RESTART_INTERVAL_MS));
238
+ }
239
+ }
240
+
241
+ const updateOAuthTestServer = !_.isEqual(newConf.oauthTestServer, this.conf.oauthTestServer);
242
+ if (updateOAuthTestServer) {
243
+ await this.oauthTestServer?.stop();
244
+ if (this.conf.oauthTestServer.enabled) {
245
+ this.oauthTestServer = new OAuthTestServer({
246
+ clientKey: newConf.oauthTestServer.clientKey,
247
+ clientSecret: newConf.oauthTestServer.clientSecret,
248
+ port: newConf.oauthTestServer.listenPort,
249
+ logger: this.logger.push(LOG_ID.OAUTHTEST),
250
+ });
251
+ await this.oauthTestServer.start();
252
+ }
253
+ }
254
+
255
+ const updateTestServer = !_.isEqual(newConf.test.port, this.conf.test.port);
256
+ if (updateTestServer) {
257
+ await this.testServer?.stop();
258
+ if (this.conf.enableTestFeatures) {
259
+ this.testServer = new TestServer({
260
+ port: newConf.test.port,
261
+ logger: this.logger.push(LOG_ID.TEST),
262
+ cache: this.cache,
263
+ });
264
+ await this.testServer.start();
265
+ }
266
+ }
267
+
268
+ this.conf = newConf;
269
+
249
270
  await Promise.all([
250
- this.inboundServer.start(),
251
- this.outboundServer.start(),
252
- this.metricsServer.start(),
253
- startTestServer,
254
- startOauthTestServer,
271
+ oldCache?.disconnect(),
255
272
  ]);
256
273
  }
257
274
 
@@ -259,9 +276,9 @@ class Server extends EventEmitter {
259
276
  return Promise.all([
260
277
  this.inboundServer.stop(),
261
278
  this.outboundServer.stop(),
262
- this.oauthTestServer.stop(),
263
- this.testServer.stop(),
264
- this.controlClient.stop(),
279
+ this.oauthTestServer?.stop(),
280
+ this.testServer?.stop(),
281
+ this.controlClient?.stop(),
265
282
  this.metricsServer.stop(),
266
283
  ]);
267
284
  }