@mojaloop/sdk-scheme-adapter 14.0.0 → 16.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 (44) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/audit-resolve.json +59 -473
  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 +12 -61
  8. package/src/OAuthTestServer/index.js +0 -13
  9. package/src/OutboundServer/api.yaml +370 -44
  10. package/src/OutboundServer/index.js +11 -54
  11. package/src/TestServer/index.js +6 -33
  12. package/src/config.js +1 -4
  13. package/src/index.js +163 -146
  14. package/src/lib/cache.js +93 -186
  15. package/src/lib/metrics.js +1 -1
  16. package/src/lib/model/InboundTransfersModel.js +10 -6
  17. package/src/lib/model/OutboundTransfersModel.js +1 -1
  18. package/test/__mocks__/redis.js +51 -26
  19. package/test/config/integration.env +1 -2
  20. package/test/integration/lib/cache.test.js +1 -2
  21. package/test/integration/testEnv.js +1 -4
  22. package/test/unit/ControlClient.test.js +1 -45
  23. package/test/unit/ControlServer/index.js +18 -22
  24. package/test/unit/ControlServer.test.js +0 -60
  25. package/test/unit/InboundServer.test.js +8 -8
  26. package/test/unit/TestServer.test.js +1 -1
  27. package/test/unit/api/accounts/accounts.test.js +2 -2
  28. package/test/unit/api/transfers/transfers.test.js +1 -1
  29. package/test/unit/api/utils.js +12 -4
  30. package/test/unit/config.test.js +1 -2
  31. package/test/unit/data/defaultConfig.json +1 -5
  32. package/test/unit/index.test.js +5 -64
  33. package/test/unit/lib/cache.test.js +5 -6
  34. package/test/unit/lib/model/AccountsModel.test.js +3 -4
  35. package/test/unit/lib/model/InboundTransfersModel.test.js +55 -16
  36. package/test/unit/lib/model/OutboundBulkQuotesModel.test.js +3 -4
  37. package/test/unit/lib/model/OutboundBulkTransfersModel.test.js +1 -2
  38. package/test/unit/lib/model/OutboundRequestToPayModel.test.js +3 -4
  39. package/test/unit/lib/model/OutboundRequestToPayTransferModel.test.js +3 -4
  40. package/test/unit/lib/model/OutboundTransfersModel.test.js +2 -3
  41. package/test/unit/lib/model/common/PersistentStateMachine.test.js +3 -4
  42. package/test/unit/lib/model/data/defaultConfig.json +1 -4
  43. package/test/unit/lib/model/data/notificationAbortedToPayee.json +10 -0
  44. package/test/unit/lib/model/data/notificationReservedToPayee.json +10 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mojaloop/sdk-scheme-adapter",
3
- "version": "14.0.0",
3
+ "version": "16.0.0",
4
4
  "description": "An adapter for connecting to Mojaloop API enabled switches.",
5
5
  "main": "src/index.js",
6
6
  "types": "src/index.d.ts",
@@ -29,7 +29,8 @@
29
29
  "updates:update": "npm run dep:update && npm install",
30
30
  "dep:check": "npx ncu -e 2",
31
31
  "dep:update": "npx ncu -u",
32
- "release": "standard-version --releaseCommitMessageFormat 'chore(release): {{currentTag}} [skip ci]'"
32
+ "release": "standard-version --releaseCommitMessageFormat 'chore(release): {{currentTag}} [skip ci]'",
33
+ "snapshot": "standard-version --no-verify --skip.changelog --prerelease snapshot --releaseCommitMessageFormat 'chore(snapshot): {{currentTag}}'"
33
34
  },
34
35
  "author": "Matt Kingston, James Bush, ModusBox Inc.",
35
36
  "contributors": [
@@ -39,7 +40,8 @@
39
40
  "Shashikant Hirugade <shashikant.hirugade@modusbox.com>",
40
41
  "Paweł Marzec <pawel.marzec@modusbox.com>",
41
42
  "Kevin Leyow <kevin.leyow@modusbox.com",
42
- "Miguel de Barros <miguel.debarros@modusbox.com>"
43
+ "Miguel de Barros <miguel.debarros@modusbox.com>",
44
+ "Yevhen Kyriukha <yevhen.kyriukha@modusbox.com>"
43
45
  ],
44
46
  "license": "Apache-2.0",
45
47
  "licenses": [
@@ -55,11 +57,11 @@
55
57
  "dependencies": {
56
58
  "@koa/cors": "^3.1.0",
57
59
  "@mojaloop/central-services-shared": "17.0.2",
58
- "@mojaloop/sdk-standard-components": "^17.0.1",
60
+ "@mojaloop/sdk-standard-components": "^17.0.4",
59
61
  "ajv": "8.11.0",
60
- "axios": "^0.21.4",
62
+ "axios": "^0.27.2",
61
63
  "co-body": "^6.1.0",
62
- "dotenv": "^10.0.0",
64
+ "dotenv": "^16.0.1",
63
65
  "env-var": "^7.0.1",
64
66
  "express": "^4.17.2",
65
67
  "fast-json-patch": "^3.1.1",
@@ -67,36 +69,36 @@
67
69
  "js-yaml": "^4.1.0",
68
70
  "json-schema-ref-parser": "^9.0.9",
69
71
  "koa": "^2.13.1",
70
- "koa-body": "^4.2.0",
72
+ "koa-body": "^5.0.0",
71
73
  "lodash": "^4.17.21",
72
74
  "module-alias": "^2.2.2",
73
75
  "oauth2-server": "^4.0.0-dev.2",
74
- "openapi-jsonschema-parameters": "^9.3.0",
75
- "prom-client": "^12.0.0",
76
+ "openapi-jsonschema-parameters": "^12.0.0",
77
+ "prom-client": "^14.0.1",
76
78
  "promise-timeout": "^1.3.0",
77
79
  "random-word-slugs": "^0.1.6",
78
- "redis": "^3.1.2",
80
+ "redis": "^4.1.1",
79
81
  "uuidv4": "^6.2.12",
80
- "ws": "^7.5.5"
82
+ "ws": "^8.8.0"
81
83
  },
82
84
  "devDependencies": {
83
- "@babel/core": "^7.15.5",
84
- "@babel/preset-env": "^7.15.6",
85
- "@mojaloop/api-snippets": "^13.0.9",
85
+ "@babel/core": "^7.18.6",
86
+ "@babel/preset-env": "^7.18.6",
87
+ "@mojaloop/api-snippets": "^14.0.0",
86
88
  "@redocly/openapi-cli": "^1.0.0-beta.59",
87
- "@types/jest": "^27.0.1",
88
- "babel-jest": "^27.2.0",
89
- "eslint": "^7.32.0",
90
- "eslint-config-airbnb-base": "^14.2.1",
89
+ "@types/jest": "^28.1.4",
90
+ "babel-jest": "^28.1.2",
91
+ "eslint": "^8.18.0",
92
+ "eslint-config-airbnb-base": "^15.0.0",
91
93
  "eslint-plugin-import": "^2.24.2",
92
- "eslint-plugin-jest": "^24.4.0",
93
- "jest": "^27.2.0",
94
- "jest-junit": "^12.2.0",
95
- "nock": "^13.1.3",
94
+ "eslint-plugin-jest": "^26.5.3",
95
+ "jest": "^28.1.2",
96
+ "jest-junit": "^14.0.0",
97
+ "nock": "^13.2.8",
96
98
  "npm-audit-resolver": "^3.0.0-0",
97
- "npm-check-updates": "^11.8.5",
98
- "openapi-response-validator": "^9.3.0",
99
- "openapi-typescript": "^4.0.2",
99
+ "npm-check-updates": "^15.0.1",
100
+ "openapi-response-validator": "^12.0.0",
101
+ "openapi-typescript": "^5.4.0",
100
102
  "redis-mock": "^0.56.3",
101
103
  "standard-version": "^9.3.1",
102
104
  "supertest": "^6.1.6",
@@ -22,10 +22,10 @@
22
22
  // It expects new configuration to be supplied as an array of JSON patches. It therefore exposes
23
23
  // the current configuration to
24
24
 
25
- const assert = require('assert').strict;
26
25
  const ws = require('ws');
27
26
  const jsonPatch = require('fast-json-patch');
28
27
  const { generateSlug } = require('random-word-slugs');
28
+ const _ = require('lodash');
29
29
 
30
30
  /**************************************************************************
31
31
  * The message protocol messages, verbs, and errors
@@ -163,15 +163,6 @@ class Client extends ws {
163
163
  this.close();
164
164
  }
165
165
 
166
- reconfigure({ logger = this._logger, port = 0, appConfig = this._appConfig }) {
167
- assert(port === this._socket.remotePort, 'Cannot reconfigure running port');
168
- return () => {
169
- this._logger = logger;
170
- this._appConfig = appConfig;
171
- this._logger.log('restarted');
172
- };
173
- }
174
-
175
166
  // Handle incoming message from the server.
176
167
  _handle(data) {
177
168
  // TODO: json-schema validation of received message- should be pretty straight-forward
@@ -187,7 +178,13 @@ class Client extends ws {
187
178
  switch (msg.msg) {
188
179
  case MESSAGE.CONFIGURATION:
189
180
  switch (msg.verb) {
190
- case VERB.NOTIFY:
181
+ case VERB.NOTIFY: {
182
+ const dup = JSON.parse(JSON.stringify(this._appConfig)); // fast-json-patch explicitly mutates
183
+ _.merge(dup, msg.data);
184
+ this._logger.push({ oldConf: this._appConfig, newConf: dup }).log('Emitting new configuration');
185
+ this.emit(EVENT.RECONFIGURE, dup);
186
+ break;
187
+ }
191
188
  case VERB.PATCH: {
192
189
  const dup = JSON.parse(JSON.stringify(this._appConfig)); // fast-json-patch explicitly mutates
193
190
  jsonPatch.applyPatch(dup, msg.data);
@@ -22,11 +22,11 @@
22
22
  // It expects new configuration to be supplied as an array of JSON patches. It therefore exposes
23
23
  // the current configuration to
24
24
 
25
- const assert = require('assert').strict;
26
25
 
27
26
  const ws = require('ws');
28
27
  const jsonPatch = require('fast-json-patch');
29
28
  const { generateSlug } = require('random-word-slugs');
29
+ const _ = require('lodash');
30
30
 
31
31
 
32
32
  /**************************************************************************
@@ -213,21 +213,14 @@ class Server extends ws.Server {
213
213
 
214
214
  // Close the server then wait for all the client sockets to close
215
215
  async stop() {
216
- await new Promise(this.close.bind(this));
216
+ const closing = new Promise(resolve => this.close(resolve));
217
+ for (const client of this.clients) {
218
+ client.terminate();
219
+ }
220
+ await closing;
217
221
  this._logger.log('Control server shutdown complete');
218
222
  }
219
223
 
220
- reconfigure({ logger = this._logger, port = 0, appConfig = this._appConfig }) {
221
- assert(port === this._port, 'Cannot reconfigure running port');
222
- return () => {
223
- const reconfigureClientLogger =
224
- ({ logger: clientLogger }) => clientLogger.configure(logger);
225
- this._clientData.values(reconfigureClientLogger);
226
- this._logger = logger;
227
- this._appConfig = appConfig;
228
- this._logger.log('restarted');
229
- };
230
- }
231
224
 
232
225
  async notifyClientsOfCurrentConfig() {
233
226
  const updateConfMsg = build.CONFIGURATION.NOTIFY(this._appConfig);
@@ -261,6 +254,13 @@ class Server extends ws.Server {
261
254
  case VERB.READ:
262
255
  client.send(build.CONFIGURATION.NOTIFY(this._appConfig, msg.id));
263
256
  break;
257
+ case VERB.NOTIFY: {
258
+ const dup = JSON.parse(JSON.stringify(this._appConfig)); // fast-json-patch explicitly mutates
259
+ _.merge(dup, msg.data);
260
+ this._logger.push({ oldConf: this._appConfig, newConf: dup }).log('Emitting new configuration');
261
+ this.emit(EVENT.RECONFIGURE, dup);
262
+ break;
263
+ }
264
264
  case VERB.PATCH: {
265
265
  // TODO: validate the incoming patch? Or assume clients have used the
266
266
  // client library?
@@ -18,33 +18,22 @@ const fs = require('fs');
18
18
  const path = require('path');
19
19
  const EventEmitter = require('events');
20
20
 
21
- const { WSO2Auth } = require('@mojaloop/sdk-standard-components');
22
-
23
21
  const Validate = require('../lib/validate');
24
22
  const router = require('../lib/router');
25
23
  const handlers = require('./handlers');
26
24
  const middlewares = require('./middlewares');
27
- const check = require('../lib/check');
28
25
 
29
26
  class InboundApi extends EventEmitter {
30
- constructor(conf, logger, cache, validator) {
27
+ constructor(conf, logger, cache, validator, wso2) {
31
28
  super({ captureExceptions: true });
32
29
  this._conf = conf;
33
30
  this._cache = cache;
34
- this._wso2 = {
35
- auth: new WSO2Auth({
36
- ...conf.wso2.auth,
37
- logger,
38
- tlsCreds: conf.inbound.tls.mutualTLS.enabled && conf.inbound.tls.creds,
39
- }),
40
- retryWso2AuthFailureTimes: conf.wso2.requestAuthFailureRetryTimes,
41
- };
42
- this._wso2.auth.on('error', (msg) => {
43
- this.emit('error', 'WSO2 auth error in InboundApi', msg);
44
- });
45
31
 
46
32
  if (conf.validateInboundJws) {
47
- this._jwsVerificationKeys = conf.pm4mlEnabled ? conf.peerJWSKeys : InboundApi._GetJwsKeys(conf.jwsVerificationKeysDirectory);
33
+ // peerJWSKey is a special config option specifically for Payment Manager for Mojaloop
34
+ // that is populated by a management api.
35
+ // This map supersedes local keys that would be loaded in by jwsVerificationKeysDirectory.
36
+ this._jwsVerificationKeys = conf.pm4mlEnabled ? conf.peerJWSKeys : InboundApi._GetJwsKeys(conf.jwsVerificationKeysDirectory);
48
37
  }
49
38
  this._api = InboundApi._SetupApi({
50
39
  conf,
@@ -52,19 +41,15 @@ class InboundApi extends EventEmitter {
52
41
  validator,
53
42
  cache,
54
43
  jwsVerificationKeys: this._jwsVerificationKeys,
55
- wso2: this._wso2,
44
+ wso2,
56
45
  });
57
46
  }
58
47
 
59
48
  async start() {
60
49
  this._startJwsWatcher();
61
- if (!this._conf.testingDisableWSO2AuthStart) {
62
- await this._wso2.auth.start();
63
- }
64
50
  }
65
51
 
66
52
  stop() {
67
- this._wso2.auth.stop();
68
53
  if (this._keyWatcher) {
69
54
  this._keyWatcher.close();
70
55
  this._keyWatcher = null;
@@ -145,7 +130,7 @@ class InboundApi extends EventEmitter {
145
130
  }
146
131
 
147
132
  class InboundServer extends EventEmitter {
148
- constructor(conf, logger, cache) {
133
+ constructor(conf, logger, cache, wso2) {
149
134
  super({ captureExceptions: true });
150
135
  this._conf = conf;
151
136
  this._validator = new Validate();
@@ -154,7 +139,8 @@ class InboundServer extends EventEmitter {
154
139
  conf,
155
140
  this._logger.push({ component: 'api' }),
156
141
  cache,
157
- this._validator
142
+ this._validator,
143
+ wso2,
158
144
  );
159
145
  this._api.on('error', (...args) => {
160
146
  this.emit('error', ...args);
@@ -173,52 +159,17 @@ class InboundServer extends EventEmitter {
173
159
  await this._validator.initialise(apiSpecs);
174
160
  await this._api.start();
175
161
  await new Promise((resolve) => this._server.listen(this._conf.inbound.port, resolve));
176
- this._logger.log(`Serving outbound API on port ${this._conf.inbound.port}`);
162
+ this._logger.log(`Serving inbound API on port ${this._conf.inbound.port}`);
177
163
  }
178
164
 
179
165
  async stop() {
180
- if (this._server) {
166
+ if (this._server.listening) {
181
167
  await new Promise(resolve => this._server.close(resolve));
182
- this._server = null;
183
- }
184
- if (this._api) {
185
- await this._api.stop();
186
- this._api = null;
187
168
  }
169
+ await this._api.stop();
188
170
  this._logger.log('inbound shut down complete');
189
171
  }
190
172
 
191
- async reconfigure(conf, logger, cache) {
192
- // It may be possible to extract the socket from an existing HTTP/HTTPS server and replace
193
- // it in a new server of the other type, as Node's HTTP and HTTPS servers both eventually
194
- // are subclasses of net.Server. This wasn't considered as a requirement at the time of
195
- // writing.
196
- assert(
197
- this._conf.inbound.tls.mutualTLS.enabled === conf.inbound.tls.mutualTLS.enabled,
198
- 'Cannot live-restart an HTTPS server as HTTP or vice versa',
199
- );
200
- const newApi = new InboundApi(conf, logger, cache, this._validator);
201
- await newApi.start();
202
- return () => {
203
- this._logger = logger;
204
- this._cache = cache;
205
- // TODO: .tls might be undefined, causing an.. err.. undefined dereference..
206
- const tlsCredsChanged = check.notDeepEqual(
207
- conf.inbound.tls.creds,
208
- this._conf.inbound.tls.creds
209
- );
210
- if (this._conf.inbound.tls.mutualTLS.enabled && tlsCredsChanged) {
211
- this._server.setSecureContext(conf.inbound.tls.creds);
212
- }
213
- this._server.removeAllListeners('request');
214
- this._server.on('request', newApi.callback());
215
- this._api.stop();
216
- this._api = newApi;
217
- this._conf = conf;
218
- this._logger.log('restarted');
219
- };
220
- }
221
-
222
173
  _createServer(tlsEnabled, tlsCreds, handler) {
223
174
  if (!tlsEnabled) {
224
175
  return http.createServer(handler);
@@ -10,7 +10,6 @@
10
10
 
11
11
  'use strict';
12
12
 
13
- const { assert } = require('assert');
14
13
  const express = require('express');
15
14
  const bodyParser = require('body-parser');
16
15
  const OAuth2Server = require('oauth2-server');
@@ -28,7 +27,6 @@ class OAuthTestServer {
28
27
  * @param {Logger} conf.logger Logger
29
28
  */
30
29
  constructor({ port, clientKey, clientSecret, logger }) {
31
- this._api = null;
32
30
  this._port = port;
33
31
  this._logger = logger;
34
32
  this._clientKey = clientKey;
@@ -65,17 +63,6 @@ class OAuthTestServer {
65
63
  this._logger.log('OAuth2 Test Server shut down complete');
66
64
  }
67
65
 
68
- async reconfigure({ port, clientKey, clientSecret, logger }) {
69
- assert(port === this._port, 'Cannot reconfigure running port');
70
- return () => {
71
- this._port = port;
72
- this._logger = logger;
73
- this.stop().then(() => this.start());
74
- this._api = OAuthTestServer._SetupApi({ clientKey, clientSecret });
75
- this._logger.log('restarted');
76
- };
77
- }
78
-
79
66
  handleResponse(req, res, response) {
80
67
  if (response.status === 302) {
81
68
  const location = response.headers.location;