@mojaloop/sdk-scheme-adapter 11.18.8

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 (277) hide show
  1. package/.env.example +140 -0
  2. package/.eslintignore +2 -0
  3. package/.eslintrc.json +30 -0
  4. package/.nvmrc +1 -0
  5. package/.versionrc +15 -0
  6. package/CHANGELOG.md +118 -0
  7. package/InboundServer/api.yaml +3594 -0
  8. package/InboundServer/api_template.yaml +69 -0
  9. package/InboundServer/handlers.js +940 -0
  10. package/InboundServer/index.js +205 -0
  11. package/InboundServer/middlewares.js +426 -0
  12. package/OAuthTestServer/index.js +66 -0
  13. package/OAuthTestServer/model.js +70 -0
  14. package/OutboundServer/api.yaml +2732 -0
  15. package/OutboundServer/api_interfaces/index.d.ts +117 -0
  16. package/OutboundServer/api_interfaces/openapi.d.ts +1475 -0
  17. package/OutboundServer/api_template/components/parameters/bulkQuoteId.yaml +9 -0
  18. package/OutboundServer/api_template/components/parameters/bulkTransferId.yaml +9 -0
  19. package/OutboundServer/api_template/components/parameters/requestToPayTransactionId.yaml +9 -0
  20. package/OutboundServer/api_template/components/parameters/transferId.yaml +9 -0
  21. package/OutboundServer/api_template/components/responses/accountsCreationCompleted.yaml +5 -0
  22. package/OutboundServer/api_template/components/responses/accountsCreationError.yaml +5 -0
  23. package/OutboundServer/api_template/components/responses/accountsCreationTimeout.yaml +5 -0
  24. package/OutboundServer/api_template/components/responses/authorizationPostSuccess.yaml +5 -0
  25. package/OutboundServer/api_template/components/responses/authorizationsServerError.yaml +5 -0
  26. package/OutboundServer/api_template/components/responses/bulkQuoteBadRequest.yaml +5 -0
  27. package/OutboundServer/api_template/components/responses/bulkQuoteServerError.yaml +5 -0
  28. package/OutboundServer/api_template/components/responses/bulkQuoteSuccess.yaml +5 -0
  29. package/OutboundServer/api_template/components/responses/bulkQuoteTimeout.yaml +5 -0
  30. package/OutboundServer/api_template/components/responses/bulkTransferBadRequest.yaml +5 -0
  31. package/OutboundServer/api_template/components/responses/bulkTransferServerError.yaml +5 -0
  32. package/OutboundServer/api_template/components/responses/bulkTransferSuccess.yaml +5 -0
  33. package/OutboundServer/api_template/components/responses/bulkTransferTimeout.yaml +5 -0
  34. package/OutboundServer/api_template/components/responses/partiesByIdError404.yaml +9 -0
  35. package/OutboundServer/api_template/components/responses/partiesByIdSuccess.yaml +5 -0
  36. package/OutboundServer/api_template/components/responses/quotesPostSuccess.yaml +5 -0
  37. package/OutboundServer/api_template/components/responses/quotesServerError.yaml +5 -0
  38. package/OutboundServer/api_template/components/responses/requestToPaySuccess.yaml +5 -0
  39. package/OutboundServer/api_template/components/responses/requestToPayTransferBadRequest.yaml +5 -0
  40. package/OutboundServer/api_template/components/responses/requestToPayTransferSuccess.yaml +5 -0
  41. package/OutboundServer/api_template/components/responses/simpleTransfersPostSuccess.yaml +5 -0
  42. package/OutboundServer/api_template/components/responses/simpleTransfersServerError.yaml +5 -0
  43. package/OutboundServer/api_template/components/responses/transferBadRequest.yaml +5 -0
  44. package/OutboundServer/api_template/components/responses/transferServerError.yaml +5 -0
  45. package/OutboundServer/api_template/components/responses/transferSuccess.yaml +5 -0
  46. package/OutboundServer/api_template/components/responses/transferTimeout.yaml +5 -0
  47. package/OutboundServer/api_template/components/schemas/accountCreationStatus.yaml +18 -0
  48. package/OutboundServer/api_template/components/schemas/accountsCreationState.yaml +4 -0
  49. package/OutboundServer/api_template/components/schemas/accountsRequest.yaml +20 -0
  50. package/OutboundServer/api_template/components/schemas/accountsResponse.yaml +15 -0
  51. package/OutboundServer/api_template/components/schemas/async2SyncCurrentState.yaml +5 -0
  52. package/OutboundServer/api_template/components/schemas/authorizationsPostRequest.yaml +15 -0
  53. package/OutboundServer/api_template/components/schemas/authorizationsPostResponse.yaml +19 -0
  54. package/OutboundServer/api_template/components/schemas/bulkQuoteErrorResponse.yaml +8 -0
  55. package/OutboundServer/api_template/components/schemas/bulkQuoteRequest.yaml +26 -0
  56. package/OutboundServer/api_template/components/schemas/bulkQuoteResponse.yaml +21 -0
  57. package/OutboundServer/api_template/components/schemas/bulkQuoteStatus.yaml +4 -0
  58. package/OutboundServer/api_template/components/schemas/bulkQuoteStatusResponse.yaml +17 -0
  59. package/OutboundServer/api_template/components/schemas/bulkTransferErrorResponse.yaml +8 -0
  60. package/OutboundServer/api_template/components/schemas/bulkTransferRequest.yaml +26 -0
  61. package/OutboundServer/api_template/components/schemas/bulkTransferResponse.yaml +16 -0
  62. package/OutboundServer/api_template/components/schemas/bulkTransferStatus.yaml +4 -0
  63. package/OutboundServer/api_template/components/schemas/bulkTransferStatusResponse.yaml +17 -0
  64. package/OutboundServer/api_template/components/schemas/errorAccountsResponse.yaml +8 -0
  65. package/OutboundServer/api_template/components/schemas/errorAuthorizationsResponse.yaml +3 -0
  66. package/OutboundServer/api_template/components/schemas/errorQuotesResponse.yaml +9 -0
  67. package/OutboundServer/api_template/components/schemas/errorResponse.yaml +8 -0
  68. package/OutboundServer/api_template/components/schemas/errorSimpleTransfersResponse.yaml +3 -0
  69. package/OutboundServer/api_template/components/schemas/errorTransferResponse.yaml +8 -0
  70. package/OutboundServer/api_template/components/schemas/extensionListEmptiable.yaml +6 -0
  71. package/OutboundServer/api_template/components/schemas/individualQuote.yaml +32 -0
  72. package/OutboundServer/api_template/components/schemas/individualQuoteResult.yaml +28 -0
  73. package/OutboundServer/api_template/components/schemas/individualTransfer.yaml +32 -0
  74. package/OutboundServer/api_template/components/schemas/individualTransferFulfilment.yaml +13 -0
  75. package/OutboundServer/api_template/components/schemas/individualTransferResult.yaml +41 -0
  76. package/OutboundServer/api_template/components/schemas/mojaloopError.yaml +5 -0
  77. package/OutboundServer/api_template/components/schemas/mojaloopTransactionRequestState.yaml +2 -0
  78. package/OutboundServer/api_template/components/schemas/partiesByIdResponse.yaml +13 -0
  79. package/OutboundServer/api_template/components/schemas/quote.yaml +3 -0
  80. package/OutboundServer/api_template/components/schemas/quoteError.yaml +16 -0
  81. package/OutboundServer/api_template/components/schemas/quotesPostRequest.yaml +13 -0
  82. package/OutboundServer/api_template/components/schemas/quotesPostResponse.yaml +48 -0
  83. package/OutboundServer/api_template/components/schemas/requestToPayRequest.yaml +39 -0
  84. package/OutboundServer/api_template/components/schemas/requestToPayResponse.yaml +41 -0
  85. package/OutboundServer/api_template/components/schemas/requestToPayTransferRequest.yaml +42 -0
  86. package/OutboundServer/api_template/components/schemas/requestToPayTransferResponse.yaml +58 -0
  87. package/OutboundServer/api_template/components/schemas/simpleTransferServerError.yaml +5 -0
  88. package/OutboundServer/api_template/components/schemas/simpleTransfersPostRequest.yaml +12 -0
  89. package/OutboundServer/api_template/components/schemas/simpleTransfersPostResponse.yaml +11 -0
  90. package/OutboundServer/api_template/components/schemas/transactionType.yaml +4 -0
  91. package/OutboundServer/api_template/components/schemas/transferContinuationAcceptOTP.yaml +9 -0
  92. package/OutboundServer/api_template/components/schemas/transferContinuationAcceptParty.yaml +8 -0
  93. package/OutboundServer/api_template/components/schemas/transferContinuationAcceptQuote.yaml +9 -0
  94. package/OutboundServer/api_template/components/schemas/transferError.yaml +16 -0
  95. package/OutboundServer/api_template/components/schemas/transferFulfilment.yaml +3 -0
  96. package/OutboundServer/api_template/components/schemas/transferParty.yaml +40 -0
  97. package/OutboundServer/api_template/components/schemas/transferRequest.yaml +37 -0
  98. package/OutboundServer/api_template/components/schemas/transferResponse.yaml +58 -0
  99. package/OutboundServer/api_template/components/schemas/transferStatus.yaml +6 -0
  100. package/OutboundServer/api_template/components/schemas/transferStatusResponse.yaml +13 -0
  101. package/OutboundServer/api_template/health.yaml +12 -0
  102. package/OutboundServer/api_template/openapi.yaml +55 -0
  103. package/OutboundServer/api_template/paths/accounts.yaml +26 -0
  104. package/OutboundServer/api_template/paths/authorizations.yaml +19 -0
  105. package/OutboundServer/api_template/paths/bulkQuotes.yaml +23 -0
  106. package/OutboundServer/api_template/paths/bulkQuotes_bulkQuoteId.yaml +24 -0
  107. package/OutboundServer/api_template/paths/bulkTransfers.yaml +23 -0
  108. package/OutboundServer/api_template/paths/bulkTransfers_bulkTransferId.yaml +24 -0
  109. package/OutboundServer/api_template/paths/parties_Type_ID.yaml +20 -0
  110. package/OutboundServer/api_template/paths/parties_Type_ID_SubId.yaml +22 -0
  111. package/OutboundServer/api_template/paths/quotes.yaml +20 -0
  112. package/OutboundServer/api_template/paths/requestToPay.yaml +22 -0
  113. package/OutboundServer/api_template/paths/requestToPayTransfer.yaml +57 -0
  114. package/OutboundServer/api_template/paths/requestToPayTransfer_requestToPayTransactionId.yaml +34 -0
  115. package/OutboundServer/api_template/paths/simpleTransfers.yaml +19 -0
  116. package/OutboundServer/api_template/paths/transfers.yaml +55 -0
  117. package/OutboundServer/api_template/paths/transfers_transferId.yaml +58 -0
  118. package/OutboundServer/handlers.js +622 -0
  119. package/OutboundServer/index.js +137 -0
  120. package/OutboundServer/middlewares.js +67 -0
  121. package/TestServer/api.yaml +62 -0
  122. package/TestServer/handlers.js +63 -0
  123. package/TestServer/index.js +215 -0
  124. package/audit-resolve.json +65 -0
  125. package/babel.config.js +3 -0
  126. package/config.js +158 -0
  127. package/index.d.ts +1 -0
  128. package/index.js +149 -0
  129. package/jest.config.js +15 -0
  130. package/lib/api/index.js +12 -0
  131. package/lib/cache.js +352 -0
  132. package/lib/check.js +25 -0
  133. package/lib/model/AccountsModel.js +396 -0
  134. package/lib/model/Async2SyncModel.js +283 -0
  135. package/lib/model/AuthorizationsModel.js +86 -0
  136. package/lib/model/InboundTransfersModel.js +730 -0
  137. package/lib/model/OutboundBulkQuotesModel.js +485 -0
  138. package/lib/model/OutboundBulkTransfersModel.js +479 -0
  139. package/lib/model/OutboundRequestToPayModel.js +517 -0
  140. package/lib/model/OutboundRequestToPayTransferModel.js +893 -0
  141. package/lib/model/OutboundTransfersModel.js +823 -0
  142. package/lib/model/PartiesModel.js +70 -0
  143. package/lib/model/ProxyModel/MatchRules/Expression.js +48 -0
  144. package/lib/model/ProxyModel/MatchRules/Headers.js +65 -0
  145. package/lib/model/ProxyModel/MatchRules/MatchRule.js +27 -0
  146. package/lib/model/ProxyModel/MatchRules/Path.js +36 -0
  147. package/lib/model/ProxyModel/MatchRules/Query.js +65 -0
  148. package/lib/model/ProxyModel/MatchRules/index.js +19 -0
  149. package/lib/model/ProxyModel/Route.js +82 -0
  150. package/lib/model/ProxyModel/configSchema.json +118 -0
  151. package/lib/model/ProxyModel/index.js +138 -0
  152. package/lib/model/QuotesModel.js +94 -0
  153. package/lib/model/TransfersModel.js +81 -0
  154. package/lib/model/common/BackendError.js +26 -0
  155. package/lib/model/common/PersistentStateMachine.js +93 -0
  156. package/lib/model/common/index.js +18 -0
  157. package/lib/model/index.js +43 -0
  158. package/lib/model/lib/deferredJob.js +113 -0
  159. package/lib/model/lib/index.js +9 -0
  160. package/lib/model/lib/requests/backendRequests.js +227 -0
  161. package/lib/model/lib/requests/common.js +76 -0
  162. package/lib/model/lib/requests/index.js +19 -0
  163. package/lib/model/lib/shared.js +468 -0
  164. package/lib/randomphrase/index.js +21 -0
  165. package/lib/randomphrase/words.json +3397 -0
  166. package/lib/router.js +28 -0
  167. package/lib/validate.js +205 -0
  168. package/package.json +102 -0
  169. package/test/__mocks__/@mojaloop/sdk-standard-components.js +152 -0
  170. package/test/__mocks__/javascript-state-machine.js +21 -0
  171. package/test/__mocks__/redis.js +49 -0
  172. package/test/__mocks__/uuidv4.js +16 -0
  173. package/test/config/integration.env +136 -0
  174. package/test/integration/lib/Outbound/authorizations.test.js +58 -0
  175. package/test/integration/lib/Outbound/data/authorizationsPostRequest.json +43 -0
  176. package/test/integration/lib/Outbound/data/quotesPostRequest.json +52 -0
  177. package/test/integration/lib/Outbound/data/transfersPostRequest.json +24 -0
  178. package/test/integration/lib/Outbound/parties.test.js +28 -0
  179. package/test/integration/lib/Outbound/quotes.test.js +58 -0
  180. package/test/integration/lib/Outbound/simpleTransfers.test.js +67 -0
  181. package/test/integration/lib/cache.test.js +80 -0
  182. package/test/integration/testEnv.js +7 -0
  183. package/test/unit/InboundServer.test.js +443 -0
  184. package/test/unit/TestServer.test.js +394 -0
  185. package/test/unit/api/accounts/accounts.test.js +128 -0
  186. package/test/unit/api/accounts/data/postAccountsBody.json +7 -0
  187. package/test/unit/api/accounts/data/postAccountsErrorMojaloopResponse.json +25 -0
  188. package/test/unit/api/accounts/data/postAccountsErrorTimeoutResponse.json +19 -0
  189. package/test/unit/api/accounts/data/postAccountsSuccessResponse.json +17 -0
  190. package/test/unit/api/accounts/data/postAccountsSuccessResponseWithError1.json +21 -0
  191. package/test/unit/api/accounts/data/postAccountsSuccessResponseWithError2.json +21 -0
  192. package/test/unit/api/accounts/utils.js +65 -0
  193. package/test/unit/api/proxy/data/proxyConfig.yaml +82 -0
  194. package/test/unit/api/proxy/data/requestBody.json +22 -0
  195. package/test/unit/api/proxy/data/requestHeaders.json +5 -0
  196. package/test/unit/api/proxy/data/requestQuery.json +6 -0
  197. package/test/unit/api/proxy/data/responseBody.json +21 -0
  198. package/test/unit/api/proxy/data/responseHeaders.json +5 -0
  199. package/test/unit/api/proxy/proxy.test.js +220 -0
  200. package/test/unit/api/proxy/utils.js +79 -0
  201. package/test/unit/api/transfers/data/getTransfersCommittedResponse.json +21 -0
  202. package/test/unit/api/transfers/data/getTransfersErrorNotFound.json +17 -0
  203. package/test/unit/api/transfers/data/postQuotesBody.json +52 -0
  204. package/test/unit/api/transfers/data/postTransfersBadBody.json +17 -0
  205. package/test/unit/api/transfers/data/postTransfersBody.json +24 -0
  206. package/test/unit/api/transfers/data/postTransfersErrorMojaloopResponse.json +53 -0
  207. package/test/unit/api/transfers/data/postTransfersErrorTimeoutResponse.json +47 -0
  208. package/test/unit/api/transfers/data/postTransfersSimpleBody.json +26 -0
  209. package/test/unit/api/transfers/data/postTransfersSuccessResponse.json +101 -0
  210. package/test/unit/api/transfers/data/putPartiesBody.json +20 -0
  211. package/test/unit/api/transfers/data/putQuotesBody.json +37 -0
  212. package/test/unit/api/transfers/data/putTransfersBody.json +17 -0
  213. package/test/unit/api/transfers/transfers.test.js +191 -0
  214. package/test/unit/api/transfers/utils.js +183 -0
  215. package/test/unit/api/utils.js +75 -0
  216. package/test/unit/config.test.js +119 -0
  217. package/test/unit/data/commonHttpHeaders.json +6 -0
  218. package/test/unit/data/defaultConfig.json +58 -0
  219. package/test/unit/data/postQuotesBody.json +52 -0
  220. package/test/unit/data/putParticipantsBody.json +12 -0
  221. package/test/unit/data/putPartiesBody.json +20 -0
  222. package/test/unit/data/testFile.json +29 -0
  223. package/test/unit/data/testFile.yaml +14 -0
  224. package/test/unit/inboundApi/data/mockArguments.json +117 -0
  225. package/test/unit/inboundApi/data/mockTransactionRequest.json +42 -0
  226. package/test/unit/inboundApi/handlers.test.js +799 -0
  227. package/test/unit/index.test.js +55 -0
  228. package/test/unit/lib/cache.test.js +146 -0
  229. package/test/unit/lib/model/AccountsModel.test.js +121 -0
  230. package/test/unit/lib/model/AuthorizationsModel.test.js +460 -0
  231. package/test/unit/lib/model/InboundTransfersModel.test.js +628 -0
  232. package/test/unit/lib/model/OutboundBulkQuotesModel.test.js +249 -0
  233. package/test/unit/lib/model/OutboundBulkTransfersModel.test.js +244 -0
  234. package/test/unit/lib/model/OutboundRequestToPayModel.test.js +166 -0
  235. package/test/unit/lib/model/OutboundRequestToPayTransferModel.test.js +245 -0
  236. package/test/unit/lib/model/OutboundTransfersModel.test.js +836 -0
  237. package/test/unit/lib/model/PartiesModel.test.js +468 -0
  238. package/test/unit/lib/model/QuotesModel.test.js +470 -0
  239. package/test/unit/lib/model/TransfersModel.test.js +474 -0
  240. package/test/unit/lib/model/common/PersistentStateMachine.test.js +179 -0
  241. package/test/unit/lib/model/data/authorizationsResponse.json +13 -0
  242. package/test/unit/lib/model/data/bulkQuoteRequest.json +27 -0
  243. package/test/unit/lib/model/data/bulkQuoteResponse.json +35 -0
  244. package/test/unit/lib/model/data/bulkTransferFulfil.json +13 -0
  245. package/test/unit/lib/model/data/bulkTransferRequest.json +29 -0
  246. package/test/unit/lib/model/data/defaultConfig.json +47 -0
  247. package/test/unit/lib/model/data/getBulkTransfersBackendResponse.json +42 -0
  248. package/test/unit/lib/model/data/getBulkTransfersMojaloopResponse.json +22 -0
  249. package/test/unit/lib/model/data/getTransfersBackendResponse.json +34 -0
  250. package/test/unit/lib/model/data/getTransfersMojaloopResponse.json +17 -0
  251. package/test/unit/lib/model/data/mockArguments.json +131 -0
  252. package/test/unit/lib/model/data/mockTxnRequestsArguments.json +63 -0
  253. package/test/unit/lib/model/data/notificationToPayee.json +10 -0
  254. package/test/unit/lib/model/data/payeeParty.json +16 -0
  255. package/test/unit/lib/model/data/putAuthorizationsResponse.json +10 -0
  256. package/test/unit/lib/model/data/putQuotesResponse.json +33 -0
  257. package/test/unit/lib/model/data/putTransfersResponse.json +5 -0
  258. package/test/unit/lib/model/data/quoteResponse.json +31 -0
  259. package/test/unit/lib/model/data/requestToPayRequest.json +20 -0
  260. package/test/unit/lib/model/data/requestToPayTransferRequest.json +27 -0
  261. package/test/unit/lib/model/data/transactionRequestResponse.json +18 -0
  262. package/test/unit/lib/model/data/transferFulfil.json +8 -0
  263. package/test/unit/lib/model/data/transferRequest.json +26 -0
  264. package/test/unit/lib/model/mockedLibRequests.js +74 -0
  265. package/test/unit/mockLogger.js +39 -0
  266. package/test/unit/outboundApi/data/bulkQuoteRequest.json +28 -0
  267. package/test/unit/outboundApi/data/bulkTransferRequest.json +28 -0
  268. package/test/unit/outboundApi/data/mockBulkQuoteError.json +45 -0
  269. package/test/unit/outboundApi/data/mockBulkTransferError.json +48 -0
  270. package/test/unit/outboundApi/data/mockError.json +41 -0
  271. package/test/unit/outboundApi/data/mockGetPartiesError.json +4 -0
  272. package/test/unit/outboundApi/data/mockRequestToPayError.json +32 -0
  273. package/test/unit/outboundApi/data/mockRequestToPayTransferError.json +39 -0
  274. package/test/unit/outboundApi/data/requestToPay.json +21 -0
  275. package/test/unit/outboundApi/data/requestToPayTransferRequest.json +20 -0
  276. package/test/unit/outboundApi/data/transferRequest.json +21 -0
  277. package/test/unit/outboundApi/handlers.test.js +986 -0
@@ -0,0 +1,205 @@
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
+ const Koa = require('koa');
12
+
13
+ const assert = require('assert').strict;
14
+ const https = require('https');
15
+ const http = require('http');
16
+ const yaml = require('js-yaml');
17
+ const fs = require('fs');
18
+ const path = require('path');
19
+ const EventEmitter = require('events');
20
+
21
+ const { WSO2Auth } = require('@mojaloop/sdk-standard-components');
22
+
23
+ const Validate = require('../lib/validate');
24
+ const router = require('../lib/router');
25
+ const handlers = require('./handlers');
26
+ const middlewares = require('./middlewares');
27
+
28
+ class InboundApi extends EventEmitter {
29
+ constructor(conf, logger, cache, validator) {
30
+ super({ captureExceptions: true });
31
+ this._conf = conf;
32
+ this._cache = cache;
33
+ this._wso2 = {
34
+ auth: new WSO2Auth({
35
+ ...conf.wso2.auth,
36
+ logger,
37
+ tlsCreds: conf.mutualTLS.outboundRequests.enabled && conf.mutualTLS.outboundRequests.creds,
38
+ }),
39
+ retryWso2AuthFailureTimes: conf.wso2.requestAuthFailureRetryTimes,
40
+ };
41
+ this._wso2.auth.on('error', (msg) => {
42
+ this.emit('error', 'WSO2 auth error in InboundApi', msg);
43
+ });
44
+
45
+ if (conf.validateInboundJws) {
46
+ this._jwsVerificationKeys = InboundApi._GetJwsKeys(conf.jwsVerificationKeysDirectory);
47
+ }
48
+ this._api = InboundApi._SetupApi({
49
+ conf,
50
+ logger,
51
+ validator,
52
+ cache,
53
+ jwsVerificationKeys: this._jwsVerificationKeys,
54
+ wso2: this._wso2,
55
+ });
56
+ }
57
+
58
+ async start() {
59
+ this._startJwsWatcher();
60
+ if (!this._conf.testingDisableWSO2AuthStart) {
61
+ await this._wso2.auth.start();
62
+ }
63
+ }
64
+
65
+ stop() {
66
+ this._wso2.auth.stop();
67
+ if (this._keyWatcher) {
68
+ this._keyWatcher.close();
69
+ this._keyWatcher = null;
70
+ }
71
+ }
72
+
73
+ callback() {
74
+ return this._api.callback();
75
+ }
76
+
77
+ _startJwsWatcher() {
78
+ const FS_EVENT_TYPES = {
79
+ CHANGE: 'change',
80
+ RENAME: 'rename'
81
+ };
82
+ const watchHandler = async (eventType, filename) => {
83
+ // On most platforms, 'rename' is emitted whenever a filename appears or disappears in the directory.
84
+ // From: https://nodejs.org/docs/latest/api/fs.html#fs_fs_watch_filename_options_listener
85
+ if (path.extname(filename) !== '.pem') {
86
+ return;
87
+ }
88
+ const keyName = path.basename(filename, '.pem');
89
+ const keyPath = path.join(this._conf.jwsVerificationKeysDirectory, filename);
90
+ if (eventType === FS_EVENT_TYPES.RENAME) {
91
+ if (fs.existsSync(keyPath)) {
92
+ this._jwsVerificationKeys[keyName] = await fs.promises.readFile(keyPath);
93
+ } else {
94
+ delete this._jwsVerificationKeys[keyName];
95
+ }
96
+ } else if (eventType === FS_EVENT_TYPES.CHANGE) {
97
+ this._jwsVerificationKeys[keyName] = await fs.promises.readFile(keyPath);
98
+ }
99
+ };
100
+ if (this._conf.jwsVerificationKeysDirectory) {
101
+ this._keyWatcher = fs.watch(this._conf.jwsVerificationKeysDirectory, watchHandler);
102
+ }
103
+ }
104
+
105
+ static _SetupApi({ conf, logger, validator, cache, jwsVerificationKeys, wso2 }) {
106
+ const api = new Koa();
107
+
108
+ api.use(middlewares.createErrorHandler(logger));
109
+ api.use(middlewares.createRequestIdGenerator());
110
+ api.use(middlewares.createHeaderValidator(logger));
111
+ if (conf.validateInboundJws) {
112
+ const jwsExclusions = conf.validateInboundPutPartiesJws ? [] : ['putParties'];
113
+ api.use(middlewares.createJwsValidator(logger, jwsVerificationKeys, jwsExclusions));
114
+ }
115
+
116
+ api.use(middlewares.applyState({ cache, wso2, conf }));
117
+ api.use(middlewares.createLogger(logger));
118
+ api.use(middlewares.createRequestValidator(validator));
119
+ api.use(middlewares.assignFspiopIdentifier());
120
+ if (conf.enableTestFeatures) {
121
+ api.use(middlewares.cacheRequest(cache));
122
+ }
123
+ api.use(router(handlers));
124
+ api.use(middlewares.createResponseBodyHandler());
125
+
126
+ api.context.resourceVersions = conf.resourceVersions;
127
+
128
+ return api;
129
+ }
130
+
131
+ static _GetJwsKeys(fromDir) {
132
+ const keys = {};
133
+ if (fromDir) {
134
+ fs.readdirSync(fromDir)
135
+ .filter(f => f.endsWith('.pem'))
136
+ .forEach(f => {
137
+ const keyName = path.basename(f, '.pem');
138
+ const keyPath = path.join(fromDir, f);
139
+ keys[keyName] = fs.readFileSync(keyPath);
140
+ });
141
+ }
142
+ return keys;
143
+ }
144
+ }
145
+
146
+ class InboundServer extends EventEmitter {
147
+ constructor(conf, logger, cache) {
148
+ super({ captureExceptions: true });
149
+ this._conf = conf;
150
+ this._validator = new Validate();
151
+ this._logger = logger;
152
+ this._api = new InboundApi(
153
+ conf,
154
+ this._logger.push({ component: 'api' }),
155
+ cache,
156
+ this._validator
157
+ );
158
+ this._api.on('error', (...args) => {
159
+ this.emit('error', ...args);
160
+ });
161
+ this._server = this._createServer(
162
+ conf.mutualTLS.inboundRequests.enabled,
163
+ conf.mutualTLS.inboundRequests.creds,
164
+ this._api.callback()
165
+ );
166
+ }
167
+
168
+ async start() {
169
+ assert(!this._server.listening, 'Server already listening');
170
+ const specPath = path.join(__dirname, 'api.yaml');
171
+ const apiSpecs = yaml.load(fs.readFileSync(specPath));
172
+ await this._validator.initialise(apiSpecs);
173
+ await this._api.start();
174
+ await new Promise((resolve) => this._server.listen(this._conf.inboundServerPort, resolve));
175
+ this._logger.log(`Serving inbound API on port ${this._conf.inboundServerPort}`);
176
+ }
177
+
178
+ async stop() {
179
+ if (this._server) {
180
+ await new Promise(resolve => this._server.close(resolve));
181
+ this._server = null;
182
+ }
183
+ if (this._api) {
184
+ await this._api.stop();
185
+ this._api = null;
186
+ }
187
+ this._logger.log('inbound shut down complete');
188
+ }
189
+
190
+ _createServer(tlsEnabled, tlsCreds, handler) {
191
+ if (!tlsEnabled) {
192
+ return http.createServer(handler);
193
+ }
194
+
195
+ const inboundHttpsOpts = {
196
+ ...tlsCreds,
197
+ requestCert: true,
198
+ rejectUnauthorized: true // no effect if requestCert is not true
199
+ };
200
+ return https.createServer(inboundHttpsOpts, handler);
201
+ }
202
+
203
+ }
204
+
205
+ module.exports = InboundServer;
@@ -0,0 +1,426 @@
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
+ const coBody = require('co-body');
12
+
13
+ const randomPhrase = require('../lib/randomphrase');
14
+ const { Jws, Errors } = require('@mojaloop/sdk-standard-components');
15
+ const {
16
+ parseAcceptHeader,
17
+ parseContentTypeHeader,
18
+ protocolVersionsMap
19
+ } = require('@mojaloop/central-services-shared').Util.HeaderValidation;
20
+ const {
21
+ defaultProtocolResources,
22
+ defaultProtocolVersions,
23
+ errorMessages
24
+ } = require('@mojaloop/central-services-shared').Util.Hapi.FSPIOPHeaderValidation;
25
+
26
+ /**
27
+ * Log raw to console as a last resort
28
+ * @return {Function}
29
+ */
30
+ const createErrorHandler = (logger) => async (ctx, next) => {
31
+ try {
32
+ await next();
33
+ } catch (err) {
34
+ // TODO: return a 500 here if the response has not already been sent?
35
+ logger.push({ err }).log('Error caught in catchall');
36
+ }
37
+ };
38
+
39
+
40
+ /**
41
+ * tag each incoming request with the FSPIOP identifier from it's path or body
42
+ * @return {Function}
43
+ */
44
+ const assignFspiopIdentifier = () => async (ctx, next) => {
45
+ const getters = {
46
+ '/authorizations/{ID}': {
47
+ get: () => ctx.state.path.params.ID,
48
+ put: () => ctx.state.path.params.ID,
49
+ },
50
+ '/bulkQuotes': {
51
+ post: () => ctx.request.body.bulkQuoteId,
52
+ },
53
+ '/bulkQuotes/{ID}': {
54
+ get: () => ctx.state.path.params.ID,
55
+ put: () => ctx.state.path.params.ID,
56
+ },
57
+ '/bulkQuotes/{ID}/error': {
58
+ put: () => ctx.state.path.params.ID,
59
+ },
60
+ '/bulkTransfers': {
61
+ post: () => ctx.request.body.bulkTransferId,
62
+ },
63
+ '/bulkTransfers/{ID}': {
64
+ get: () => ctx.state.path.params.ID,
65
+ put: () => ctx.state.path.params.ID,
66
+ },
67
+ '/bulkTransfers/{ID}/error': {
68
+ put: () => ctx.state.path.params.ID,
69
+ },
70
+ '/participants/{ID}': {
71
+ put: () => ctx.state.path.params.ID,
72
+ },
73
+ '/participants/{Type}/{ID}': {
74
+ get: () => ctx.state.path.params.ID,
75
+ },
76
+ '/participants/{Type}/{ID}/{SubId}': {
77
+ get: () => ctx.state.path.params.ID,
78
+ put: () => ctx.state.path.params.ID,
79
+ },
80
+ '/participants/{Type}/{ID}/{SubId}/error': {
81
+ put: () => ctx.state.path.params.ID,
82
+ },
83
+ '/participants/{ID}/error': {
84
+ put: () => ctx.state.path.params.ID,
85
+ },
86
+ '/parties/{Type}/{ID}': {
87
+ get: () => ctx.state.path.params.ID,
88
+ put: () => ctx.state.path.params.ID,
89
+ },
90
+ '/parties/{Type}/{ID}/{SubId}': {
91
+ get: () => ctx.state.path.params.ID,
92
+ put: () => ctx.state.path.params.ID,
93
+ },
94
+ '/parties/{Type}/{ID}/error': {
95
+ put: () => ctx.state.path.params.ID,
96
+ },
97
+ '/parties/{Type}/{ID}/{SubId}/error': {
98
+ put: () => ctx.state.path.params.ID,
99
+ },
100
+ '/quotes': {
101
+ post: () => ctx.request.body.quoteId,
102
+ },
103
+ '/quotes/{ID}': {
104
+ put: () => ctx.state.path.params.ID,
105
+ },
106
+ '/quotes/{ID}/error': {
107
+ put: () => ctx.state.path.params.ID,
108
+ },
109
+ '/transfers': {
110
+ post: () => ctx.request.body.transferId,
111
+ },
112
+ '/transfers/{ID}': {
113
+ get: () => ctx.state.path.params.ID,
114
+ put: () => ctx.state.path.params.ID,
115
+ patch: () => ctx.state.path.params.ID,
116
+ },
117
+ '/transfers/{ID}/error': {
118
+ put: () => ctx.state.path.params.ID,
119
+ },
120
+ '/transactionRequests': {
121
+ post: () => ctx.request.body.transactionRequestId,
122
+ },
123
+ '/transactionRequests/{ID}': {
124
+ put: () => ctx.state.path.params.ID,
125
+ }
126
+ }[ctx.state.path.pattern];
127
+ if (getters) {
128
+ const getter = getters[ctx.method.toLowerCase()];
129
+ if (getter) {
130
+ ctx.state.fspiopId = getter(ctx.request);
131
+ }
132
+ }
133
+ await next();
134
+ };
135
+
136
+
137
+ /**
138
+ * cache incoming requests and callbacks
139
+ * @return {Function}
140
+ */
141
+ const cacheRequest = (cache) => async (ctx, next) => {
142
+ if (ctx.state.fspiopId) {
143
+ const req = {
144
+ headers: ctx.request.headers,
145
+ data: ctx.request.body,
146
+ };
147
+ const prefix = ctx.method.toLowerCase() === 'put' ? cache.CALLBACK_PREFIX : cache.REQUEST_PREFIX;
148
+ const res = await cache.set(`${prefix}${ctx.state.fspiopId}`, req);
149
+ ctx.state.logger.push({ res }).log('Caching request');
150
+ }
151
+ await next();
152
+ };
153
+
154
+
155
+ /**
156
+ * tag each incoming request with a unique identifier
157
+ * @return {Function}
158
+ */
159
+ const createRequestIdGenerator = () => async (ctx, next) => {
160
+ ctx.request.id = randomPhrase();
161
+ await next();
162
+ };
163
+
164
+
165
+ /**
166
+ * Deal with mojaloop API content type headers, treat as JSON
167
+ * This is based on the Hapi header validation plugin found in `central-services-shared`
168
+ * Since `sdk-scheme-adapter` uses Koa instead of Hapi we convert the plugin
169
+ * into middleware
170
+ * @param logger
171
+ * @return {Function}
172
+ */
173
+ //
174
+ const createHeaderValidator = (logger) => async (
175
+ ctx,
176
+ next,
177
+ resources = defaultProtocolResources,
178
+ supportedProtocolVersions = defaultProtocolVersions
179
+ ) => {
180
+ const request = ctx.request;
181
+
182
+ // First, extract the resource type from the path
183
+ const resource = request.path.replace(/^\//, '').split('/')[0];
184
+
185
+ // Only validate requests for the requested resources
186
+ if (!resources.includes(resource)) {
187
+ return await next();
188
+ }
189
+
190
+ // Always validate the accept header for a get request, or optionally if it has been
191
+ // supplied
192
+ if (request.method.toLowerCase() === 'get' || request.headers.accept) {
193
+ if (request.headers.accept === undefined) {
194
+ ctx.response.status = Errors.MojaloopApiErrorCodes.MISSING_ELEMENT.httpStatusCode;
195
+ ctx.response.body = new Errors.MojaloopFSPIOPError(
196
+ Errors.MojaloopApiErrorObjectFromCode(Errors.MojaloopApiErrorCodes.MISSING_ELEMENT.httpStatusCode),
197
+ errorMessages.REQUIRE_ACCEPT_HEADER,
198
+ null,
199
+ Errors.MojaloopApiErrorCodes.MISSING_ELEMENT
200
+ ).toApiErrorObject();
201
+ return;
202
+ }
203
+ const accept = parseAcceptHeader(resource, request.headers.accept);
204
+ if (!accept.valid) {
205
+ ctx.response.status = Errors.MojaloopApiErrorCodes.MALFORMED_SYNTAX.httpStatusCode;
206
+ ctx.response.body = new Errors.MojaloopFSPIOPError(
207
+ Errors.MojaloopApiErrorObjectFromCode(Errors.MojaloopApiErrorCodes.MALFORMED_SYNTAX.httpStatusCode),
208
+ errorMessages.INVALID_ACCEPT_HEADER,
209
+ null,
210
+ Errors.MojaloopApiErrorCodes.MALFORMED_SYNTAX
211
+ ).toApiErrorObject();
212
+ return;
213
+ }
214
+ if (!supportedProtocolVersions.some(supportedVer => accept.versions.has(supportedVer))) {
215
+ ctx.response.status = Errors.MojaloopApiErrorCodes.UNACCEPTABLE_VERSION.httpStatusCode;
216
+ ctx.response.body = new Errors.MojaloopFSPIOPError(
217
+ Errors.MojaloopApiErrorObjectFromCode(Errors.MojaloopApiErrorCodes.UNACCEPTABLE_VERSION.httpStatusCode),
218
+ errorMessages.REQUESTED_VERSION_NOT_SUPPORTED,
219
+ null,
220
+ Errors.MojaloopApiErrorCodes.UNACCEPTABLE_VERSION,
221
+ protocolVersionsMap
222
+ ).toApiErrorObject();
223
+ return;
224
+ }
225
+ }
226
+
227
+ // Always validate the content-type header
228
+ if (request.headers['content-type'] === undefined) {
229
+ ctx.response.status = Errors.MojaloopApiErrorCodes.MISSING_ELEMENT.httpStatusCode;
230
+ ctx.response.body = new Errors.MojaloopFSPIOPError(
231
+ Errors.MojaloopApiErrorObjectFromCode(Errors.MojaloopApiErrorCodes.MISSING_ELEMENT.httpStatusCode),
232
+ errorMessages.REQUIRE_CONTENT_TYPE_HEADER,
233
+ null,
234
+ Errors.MojaloopApiErrorCodes.MISSING_ELEMENT,
235
+ protocolVersionsMap
236
+ ).toApiErrorObject();
237
+ return;
238
+ }
239
+
240
+ const contentType = parseContentTypeHeader(resource, request.headers['content-type']);
241
+ if (!contentType.valid) {
242
+ ctx.response.status = Errors.MojaloopApiErrorCodes.MALFORMED_SYNTAX.httpStatusCode;
243
+ ctx.response.body = new Errors.MojaloopFSPIOPError(
244
+ Errors.MojaloopApiErrorObjectFromCode(Errors.MojaloopApiErrorCodes.MALFORMED_SYNTAX.httpStatusCode),
245
+ errorMessages.INVALID_CONTENT_TYPE_HEADER,
246
+ null,
247
+ Errors.MojaloopApiErrorCodes.MALFORMED_SYNTAX
248
+ ).toApiErrorObject();
249
+ return;
250
+ }
251
+ if (!supportedProtocolVersions.includes(contentType.version)) {
252
+ ctx.response.status = Errors.MojaloopApiErrorCodes.UNACCEPTABLE_VERSION.httpStatusCode;
253
+ ctx.response.body = new Errors.MojaloopFSPIOPError(
254
+ Errors.MojaloopApiErrorObjectFromCode(Errors.MojaloopApiErrorCodes.UNACCEPTABLE_VERSION.httpStatusCode),
255
+ errorMessages.SUPPLIED_VERSION_NOT_SUPPORTED,
256
+ null,
257
+ Errors.MojaloopApiErrorCodes.UNACCEPTABLE_VERSION,
258
+ protocolVersionsMap
259
+ ).toApiErrorObject();
260
+ return;
261
+ }
262
+
263
+ try {
264
+ ctx.request.body = await coBody.json(ctx.req);
265
+ }
266
+ catch(err) {
267
+ // error parsing body
268
+ logger.push({ err }).log('Error parsing body');
269
+ ctx.response.status = Errors.MojaloopApiErrorCodes.MALFORMED_SYNTAX.httpStatusCode;
270
+ ctx.response.body = new Errors.MojaloopFSPIOPError(err, err.message, null,
271
+ Errors.MojaloopApiErrorCodes.MALFORMED_SYNTAX).toApiErrorObject();
272
+ return;
273
+ }
274
+ await next();
275
+ };
276
+
277
+
278
+ /**
279
+ *
280
+ * @param logger
281
+ * @param keys {Object} JWS verification keys
282
+ * @param exclusions {string[]} Requests to exclude from validation. Possible values: "putParties"
283
+ * @return {Function}
284
+ */
285
+ const createJwsValidator = (logger, keys, exclusions) => {
286
+ const jwsValidator = new Jws.validator({
287
+ logger: logger,
288
+ validationKeys: keys,
289
+ });
290
+ // JWS validation for incoming requests
291
+ return async (ctx, next) => {
292
+ try {
293
+ // we skip inbound JWS validation on PUT /parties requests if our config flag
294
+ // is set to do so.
295
+ if (exclusions.includes('putParties')
296
+ && ctx.request.method === 'PUT'
297
+ && ctx.request.path.startsWith('/parties/')) {
298
+ logger.log('Skipping jws validation on put parties. config flag is set');
299
+ return await next();
300
+ }
301
+
302
+ // we dont check signatures on GET requests
303
+ // todo: validate this requirement. No state is mutated by GETs but
304
+ // there are potential security issues if message origin is used to
305
+ // determine permission sets i.e. what is "readable"
306
+ if(ctx.request.method !== 'GET') {
307
+ logger.push({ request: ctx.request, body: ctx.request.body }).log('Validating JWS');
308
+ jwsValidator.validate(ctx.request, logger);
309
+ }
310
+
311
+ }
312
+ catch(err) {
313
+ logger.push({ err }).log('Inbound request failed JWS validation');
314
+
315
+ ctx.response.status = 400;
316
+ ctx.response.body = new Errors.MojaloopFSPIOPError(
317
+ err, err.message, null, Errors.MojaloopApiErrorCodes.INVALID_SIGNATURE
318
+ ).toApiErrorObject();
319
+ return;
320
+ }
321
+ await next();
322
+ };
323
+ };
324
+
325
+
326
+ /**
327
+ * Add request state.
328
+ * TODO: this should probably be app context:
329
+ * https://github.com/koajs/koa/blob/master/docs/api/index.md#appcontext
330
+ * @param sharedState
331
+ * @return {Function}
332
+ */
333
+ const applyState = (sharedState) => async (ctx, next) => {
334
+ Object.assign(ctx.state, {
335
+ ...sharedState,
336
+ conf: {
337
+ ...sharedState.conf,
338
+ tls: sharedState?.conf?.mutualTLS?.outboundRequests,
339
+ }
340
+ });
341
+ await next();
342
+ };
343
+
344
+
345
+ /**
346
+ * Add a log context for each request, log the receipt and handling thereof
347
+ * @param logger
348
+ * @return {Function}
349
+ */
350
+ const createLogger = (logger) => async (ctx, next) => {
351
+ ctx.state.logger = logger.push({ request: {
352
+ id: ctx.request.id,
353
+ path: ctx.path,
354
+ method: ctx.method
355
+ }});
356
+ ctx.state.logger.push({ body: ctx.request.body }).log('Request received');
357
+ try {
358
+ await next();
359
+ } catch (err) {
360
+ ctx.state.logger.push(err).log('Error');
361
+ }
362
+ await ctx.state.logger.log('Request processed');
363
+ };
364
+
365
+
366
+ /**
367
+ * Add validation for each inbound request
368
+ * @param validator
369
+ * @return {Function}
370
+ */
371
+ const createRequestValidator = (validator) => async (ctx, next) => {
372
+ ctx.state.logger.log('Validating request');
373
+ try {
374
+ ctx.state.path = validator.validateRequest(ctx, ctx.state.logger);
375
+ ctx.state.logger.log('Request passed validation');
376
+ await next();
377
+ } catch (err) {
378
+ ctx.state.logger.push({ err }).log('Request failed validation.');
379
+ // send a mojaloop spec error response
380
+ ctx.response.status = err.httpStatusCode || 400;
381
+
382
+ if(err instanceof Errors.MojaloopFSPIOPError) {
383
+ // this is a specific mojaloop spec error
384
+ ctx.response.body = err.toApiErrorObject();
385
+ return;
386
+ }
387
+
388
+ // generic mojaloop spec validation error
389
+ ctx.response.body = {
390
+ errorInformation: {
391
+ errorCode: '3100',
392
+ errorDescription: `${err.dataPath ? err.dataPath + ' ' : ''}${err.message}`
393
+ }
394
+ };
395
+ }
396
+ };
397
+
398
+
399
+ /**
400
+ * Override Koa's default behaviour of returning the status code as text in the body. If we
401
+ * haven't defined the body, we want it empty. Note that if setting this to null, Koa appears
402
+ * to override the status code with a 204. This is correct behaviour in the sense that the
403
+ * status code correctly corresponds to the content (none) but unfortunately the Mojaloop API
404
+ * does not respect this convention and requires a 200.
405
+ * @return {Function}
406
+ */
407
+ const createResponseBodyHandler = () => async (ctx, next) => {
408
+ if (ctx.response.body === undefined) {
409
+ ctx.response.body = '';
410
+ }
411
+ return await next();
412
+ };
413
+
414
+
415
+ module.exports = {
416
+ applyState,
417
+ assignFspiopIdentifier,
418
+ cacheRequest,
419
+ createErrorHandler,
420
+ createRequestIdGenerator,
421
+ createHeaderValidator,
422
+ createJwsValidator,
423
+ createLogger,
424
+ createRequestValidator,
425
+ createResponseBodyHandler,
426
+ };