account-lookup-service 15.5.0-iso.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (209) hide show
  1. package/.circleci/config.yml +11 -0
  2. package/.ncurc.yaml +6 -0
  3. package/.nvmrc +1 -0
  4. package/.nycrc.yml +20 -0
  5. package/.versionrc +15 -0
  6. package/CHANGELOG.md +330 -0
  7. package/CODEOWNERS +38 -0
  8. package/Dockerfile +45 -0
  9. package/LICENSE.md +10 -0
  10. package/README.md +252 -0
  11. package/audit-ci.jsonc +32 -0
  12. package/audit-resolve.json +161 -0
  13. package/config/default.json +109 -0
  14. package/config/knexfile.js +21 -0
  15. package/docker/account-lookup-service/default.json +106 -0
  16. package/docker/account-lookup-service/make-default-json.sh +5 -0
  17. package/docker/account-lookup-service/override.json +15 -0
  18. package/docker/central-ledger/default.json +458 -0
  19. package/docker/config-modifier/account-lookup-service.js +31 -0
  20. package/docker/kafka/consumer.properties +26 -0
  21. package/docker/kafka/producer.properties +45 -0
  22. package/docker/kafka/server.properties +143 -0
  23. package/docker/kafka/tools-log4j.properties +21 -0
  24. package/docker/mock-proxy/Dockerfile +15 -0
  25. package/docker/mock-proxy/package-lock.json +4986 -0
  26. package/docker/mock-proxy/package.json +24 -0
  27. package/docker/mock-proxy/src/config.ts +14 -0
  28. package/docker/mock-proxy/src/server.ts +94 -0
  29. package/docker/mock-proxy/src/utils.ts +29 -0
  30. package/docker/mock-proxy/tsconfig.json +24 -0
  31. package/docker/sql-init/01_permissions.sql +2 -0
  32. package/docker/sql-init-central-ledger/01_permissions.sql +2 -0
  33. package/docker/wait-for/wait-for-account-lookup-service.sh +10 -0
  34. package/docker/wait-for/wait-for-central-ledger.sh +11 -0
  35. package/docker/wait-for/wait-for-kafka.sh +7 -0
  36. package/docker/wait-for/wait-for-ml-api-adapter.sh +9 -0
  37. package/docker/wait-for/wait-for-mockserver.sh +20 -0
  38. package/docker/wait-for/wait-for-mysql-als.sh +14 -0
  39. package/docker/wait-for/wait-for-mysql-central-ledger.sh +11 -0
  40. package/docker/wait-for/wait-for-mysql.sh +11 -0
  41. package/docker/wait-for/wait-for-objstore.sh +12 -0
  42. package/docker/wait-for/wait-for.env +18 -0
  43. package/docker/wait-for/wait-for.sh +81 -0
  44. package/docker-compose.integration.yml +29 -0
  45. package/docker-compose.yml +243 -0
  46. package/jest-int.config.js +8 -0
  47. package/jest.config.js +16 -0
  48. package/jsdoc.json +38 -0
  49. package/migrations/01_currency.js +42 -0
  50. package/migrations/02_endpointType.js +43 -0
  51. package/migrations/03_endpointType-indexes.js +37 -0
  52. package/migrations/04_partyIdType.js +43 -0
  53. package/migrations/05_partyIdType-indexes.js +38 -0
  54. package/migrations/08_oracleEndpoint.js +51 -0
  55. package/migrations/09_oracleEndpoint-indexes.js +41 -0
  56. package/migrations/10_oracleEndpoint-remove-constraints.js +38 -0
  57. package/package.json +180 -0
  58. package/scripts/_wait4_all.js +143 -0
  59. package/scripts/test-functional.sh +76 -0
  60. package/secrets/jwsSigningKey.key +27 -0
  61. package/seeds/currency.js +765 -0
  62. package/seeds/endpointType.js +65 -0
  63. package/seeds/partyIdType.js +79 -0
  64. package/src/api/endpointcache.js +67 -0
  65. package/src/api/health.js +66 -0
  66. package/src/api/index.js +85 -0
  67. package/src/api/oracles/{ID}.js +100 -0
  68. package/src/api/oracles.js +96 -0
  69. package/src/api/participants/{ID}/error.js +44 -0
  70. package/src/api/participants/{ID}.js +44 -0
  71. package/src/api/participants/{Type}/{ID}/error.js +74 -0
  72. package/src/api/participants/{Type}/{ID}/{SubId}/error.js +68 -0
  73. package/src/api/participants/{Type}/{ID}/{SubId}.js +113 -0
  74. package/src/api/participants/{Type}/{ID}.js +133 -0
  75. package/src/api/participants.js +63 -0
  76. package/src/api/parties/{Type}/{ID}/error.js +66 -0
  77. package/src/api/parties/{Type}/{ID}/{SubId}/error.js +56 -0
  78. package/src/api/parties/{Type}/{ID}/{SubId}.js +77 -0
  79. package/src/api/parties/{Type}/{ID}.js +98 -0
  80. package/src/api/routes.js +294 -0
  81. package/src/constants.js +16 -0
  82. package/src/domain/oracle/index.js +33 -0
  83. package/src/domain/oracle/oracle.js +234 -0
  84. package/src/domain/participants/index.js +35 -0
  85. package/src/domain/participants/participants.js +560 -0
  86. package/src/domain/parties/getPartiesByTypeAndID.js +239 -0
  87. package/src/domain/parties/index.js +32 -0
  88. package/src/domain/parties/parties.js +215 -0
  89. package/src/domain/parties/utils.js +84 -0
  90. package/src/domain/timeout/dto.js +48 -0
  91. package/src/domain/timeout/index.js +104 -0
  92. package/src/handlers/TimeoutHandler.js +94 -0
  93. package/src/handlers/index.js +70 -0
  94. package/src/handlers/monitoring/index.js +51 -0
  95. package/src/handlers/monitoring/plugins/health.js +61 -0
  96. package/src/handlers/monitoring/plugins/metrics.js +48 -0
  97. package/src/handlers/register.js +102 -0
  98. package/src/index.js +66 -0
  99. package/src/interface/admin-swagger.yaml +804 -0
  100. package/src/interface/admin_swagger.json +959 -0
  101. package/src/interface/api-swagger-iso20022-parties.yaml +1734 -0
  102. package/src/interface/api-swagger.yaml +1733 -0
  103. package/src/interface/api_swagger.json +3046 -0
  104. package/src/interface/fspiop-rest-v2.0-ISO20022_parties.yaml +2256 -0
  105. package/src/interface/thirdparty/admin-swagger.yaml +808 -0
  106. package/src/interface/thirdparty/admin_swagger.json +961 -0
  107. package/src/interface/thirdparty/api-swagger.yaml +1739 -0
  108. package/src/interface/thirdparty/api_swagger.json +3142 -0
  109. package/src/lib/argv.js +39 -0
  110. package/src/lib/cache.js +126 -0
  111. package/src/lib/config.js +183 -0
  112. package/src/lib/db.js +26 -0
  113. package/src/lib/headers.js +53 -0
  114. package/src/lib/healthCheck/subServiceHealth.js +84 -0
  115. package/src/lib/index.js +11 -0
  116. package/src/lib/migrator.js +17 -0
  117. package/src/lib/requestLogger.js +54 -0
  118. package/src/lib/util.js +66 -0
  119. package/src/metrics/handler.js +33 -0
  120. package/src/metrics/plugin.js +52 -0
  121. package/src/metrics/routes.js +43 -0
  122. package/src/models/currency/currency.js +48 -0
  123. package/src/models/currency/index.js +32 -0
  124. package/src/models/endpointType/endpointType.js +48 -0
  125. package/src/models/endpointType/index.js +32 -0
  126. package/src/models/misc/migrationLock.js +49 -0
  127. package/src/models/oracle/facade.js +341 -0
  128. package/src/models/oracle/index.js +41 -0
  129. package/src/models/oracle/oracleEndpoint.js +192 -0
  130. package/src/models/oracle/oracleEndpointCached.js +108 -0
  131. package/src/models/participantEndpoint/facade.js +238 -0
  132. package/src/models/partyIdType/index.js +32 -0
  133. package/src/models/partyIdType/partyIdType.js +41 -0
  134. package/src/plugins.js +139 -0
  135. package/src/server.js +199 -0
  136. package/test/fixtures/index.js +131 -0
  137. package/test/fixtures/iso.js +110 -0
  138. package/test/integration/.env +8 -0
  139. package/test/integration/api/parties.test.js +137 -0
  140. package/test/integration/constants.js +20 -0
  141. package/test/integration/domain/oracle/index.test.js +324 -0
  142. package/test/integration/domain/timeout/index.test.js +75 -0
  143. package/test/integration/env.sh +15 -0
  144. package/test/integration/example.test.js +12 -0
  145. package/test/integration/models/currency/currency.test.js +68 -0
  146. package/test/integration/plugins.test.js +62 -0
  147. package/test/integration/prepareTestParticipants.js +30 -0
  148. package/test/integration/setup.js +5 -0
  149. package/test/integration-config.json +81 -0
  150. package/test/integration-runner.sh +108 -0
  151. package/test/unit/api/health.test.js +142 -0
  152. package/test/unit/api/oracles/{ID}.test.js +264 -0
  153. package/test/unit/api/oracles.test.js +173 -0
  154. package/test/unit/api/participants/participants.test.js +117 -0
  155. package/test/unit/api/participants/{Type}/{ID}/error.test.js +155 -0
  156. package/test/unit/api/participants/{Type}/{ID}/{SubId}/error.test.js +131 -0
  157. package/test/unit/api/participants/{Type}/{ID}/{SubId}.test.js +377 -0
  158. package/test/unit/api/participants/{Type}/{ID}.test.js +383 -0
  159. package/test/unit/api/participants.test.js +108 -0
  160. package/test/unit/api/parties/endpointcache.test.js +83 -0
  161. package/test/unit/api/parties/parties.test.js +102 -0
  162. package/test/unit/api/parties/{Type}/{ID}/error.test.js +145 -0
  163. package/test/unit/api/parties/{Type}/{ID}/{SubId}/error.test.js +141 -0
  164. package/test/unit/api/parties/{Type}/{ID}/{SubId}.test.js +241 -0
  165. package/test/unit/api/parties/{Type}/{ID}.test.js +240 -0
  166. package/test/unit/domain/oracle/oracle.test.js +505 -0
  167. package/test/unit/domain/participants/participants.test.js +1724 -0
  168. package/test/unit/domain/parties/parties.test.js +940 -0
  169. package/test/unit/domain/timeout/dto.test.js +28 -0
  170. package/test/unit/domain/timeout/index.test.js +81 -0
  171. package/test/unit/handlers/TimeoutHandler.test.js +125 -0
  172. package/test/unit/handlers/index.test.js +56 -0
  173. package/test/unit/handlers/register.test.js +90 -0
  174. package/test/unit/index.test.js +139 -0
  175. package/test/unit/iso20022/partiesValidation.test.js +129 -0
  176. package/test/unit/lib/TransformFacades.test.js +18 -0
  177. package/test/unit/lib/argv.test.js +40 -0
  178. package/test/unit/lib/cache.test.js +172 -0
  179. package/test/unit/lib/config.test.js +108 -0
  180. package/test/unit/lib/healthCheck/subServiceHealth.test.js +89 -0
  181. package/test/unit/lib/migrator.test.js +52 -0
  182. package/test/unit/lib/requestLogger.test.js +115 -0
  183. package/test/unit/lib/util.test.js +68 -0
  184. package/test/unit/mocks.js +66 -0
  185. package/test/unit/models/currency/currency.test.js +91 -0
  186. package/test/unit/models/endpointType/endpointType.test.js +69 -0
  187. package/test/unit/models/misc/migrationLock.test.js +96 -0
  188. package/test/unit/models/oracle/facade.test.js +546 -0
  189. package/test/unit/models/oracle/oracleEndpoint.test.js +409 -0
  190. package/test/unit/models/oracle/oracleEndpointCached.test.js +153 -0
  191. package/test/unit/models/participantEndpoint/facade.test.js +295 -0
  192. package/test/unit/models/partyIdType/partyIdType.test.js +88 -0
  193. package/test/unit/plugins.test.js +89 -0
  194. package/test/unit/setup.js +7 -0
  195. package/test/util/apiClients/AlsApiClient.js +44 -0
  196. package/test/util/apiClients/BasicApiClient.js +34 -0
  197. package/test/util/apiClients/ProxyApiClient.js +25 -0
  198. package/test/util/apiClients/index.js +7 -0
  199. package/test/util/helper.js +332 -0
  200. package/test/util/index.js +11 -0
  201. package/test/util/mockgen.js +43 -0
  202. package/test/util/onboarding.js +132 -0
  203. package/test/util/scripts/addAlsDb.sh +33 -0
  204. package/test/util/scripts/configureMockServer.sh +35 -0
  205. package/test/util/scripts/env.sh +19 -0
  206. package/test/util/scripts/populateTestData.sh +62 -0
  207. package/test/util/scripts/startMockCentralServer.sh +45 -0
  208. package/test/util/scripts/startMockOracleServer.sh +45 -0
  209. package/test/util/testConfig.js +44 -0
@@ -0,0 +1,239 @@
1
+ /*****
2
+ License
3
+ --------------
4
+ Copyright © 2017 Bill & Melinda Gates Foundation
5
+ The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at
6
+ http://www.apache.org/licenses/LICENSE-2.0
7
+ Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
8
+
9
+ Contributors
10
+ --------------
11
+ This is the official list of the Mojaloop project contributors for this file.
12
+ Names of the original copyright holders (individuals or organizations)
13
+ should be listed with a '*' in the first column. People who have
14
+ contributed from an organization can be listed under the organization
15
+ that actually holds the copyright for their contributions (see the
16
+ Gates Foundation organization for an example). Those individuals should have
17
+ their names indented and be marked with a '-'. Email address can be added
18
+ optionally within square brackets <email>.
19
+ * Gates Foundation
20
+ - Name Surname <name.surname@gatesfoundation.com>
21
+
22
+ * Eugen Klymniuk <eugen.klymniuk@infitx.com>
23
+ --------------
24
+ **********/
25
+
26
+ const { Enum, Util } = require('@mojaloop/central-services-shared')
27
+ const ErrorHandler = require('@mojaloop/central-services-error-handling')
28
+ const Metrics = require('@mojaloop/central-services-metrics')
29
+
30
+ const config = require('../../lib/config')
31
+ const oracle = require('../../models/oracle/facade')
32
+ const participant = require('../../models/participantEndpoint/facade')
33
+ const { createCallbackHeaders } = require('../../lib/headers')
34
+ const { ERROR_MESSAGES } = require('../../constants')
35
+ const { loggerFactory } = require('../../lib')
36
+ const Config = require('../../lib/config')
37
+ const utils = require('./utils')
38
+
39
+ const { FspEndpointTypes, FspEndpointTemplates } = Enum.EndPoints
40
+ const { Headers, RestMethods } = Enum.Http
41
+
42
+ const logger = loggerFactory('domain:get-parties')
43
+
44
+ const proxyCacheTtlSec = 40 // todo: make configurable
45
+
46
+ const validateRequester = async ({ source, proxy, proxyCache }) => {
47
+ const sourceParticipant = await participant.validateParticipant(source)
48
+ if (sourceParticipant) {
49
+ logger.debug('source is in scheme', { source })
50
+ return source
51
+ }
52
+
53
+ if (!proxy) {
54
+ const errMessage = ERROR_MESSAGES.partySourceFspNotFound
55
+ throw ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.ID_NOT_FOUND, errMessage)
56
+ }
57
+
58
+ const proxyParticipant = await participant.validateParticipant(proxy)
59
+ if (!proxyParticipant) {
60
+ const errMessage = ERROR_MESSAGES.partyProxyNotFound
61
+ throw ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.ID_NOT_FOUND, errMessage)
62
+ }
63
+
64
+ const isCached = await proxyCache.addDfspIdToProxyMapping(source, proxy)
65
+ // think, what if isCached !== true?
66
+ logger.info('source is added to proxyMapping cache:', { source, proxy, isCached })
67
+ return proxy
68
+ }
69
+
70
+ /**
71
+ * @function getPartiesByTypeAndID
72
+ *
73
+ * @description sends request to applicable oracle based on type and sends results to a callback url
74
+ *
75
+ * @param {object} headers - incoming http request headers
76
+ * @param {object} params - uri parameters of the http request
77
+ * @param {string} method - http request method
78
+ * @param {object} query - uri query parameters of the http request
79
+ * @param {object} span
80
+ * @param {object} cache
81
+ * @param {IProxyCache} [proxyCache] - IProxyCache instance
82
+ */
83
+ const getPartiesByTypeAndID = async (headers, params, method, query, span, cache, proxyCache = undefined) => {
84
+ const histTimerEnd = Metrics.getHistogram(
85
+ 'getPartiesByTypeAndID',
86
+ 'Get party by Type and Id',
87
+ ['success']
88
+ ).startTimer()
89
+ const proxyEnabled = !!(Config.PROXY_CACHE_CONFIG.enabled && proxyCache)
90
+ const type = params.Type
91
+ const partySubId = params.SubId
92
+ const source = headers[Headers.FSPIOP.SOURCE]
93
+ const proxy = proxyEnabled && headers[Headers.FSPIOP.PROXY]
94
+ const callbackEndpointType = utils.getPartyCbType(partySubId)
95
+
96
+ const childSpan = span ? span.getChild('getPartiesByTypeAndID') : undefined
97
+ logger.info('parties::getPartiesByTypeAndID::begin', { source, proxy, params })
98
+
99
+ let requester
100
+ let fspiopError
101
+
102
+ try {
103
+ requester = await validateRequester({ source, proxy, proxyCache })
104
+
105
+ const options = {
106
+ partyIdType: type,
107
+ partyIdentifier: params.ID,
108
+ ...(partySubId && { partySubIdOrType: partySubId })
109
+ }
110
+
111
+ let destination = headers[Headers.FSPIOP.DESTINATION]
112
+ // see https://github.com/mojaloop/design-authority/issues/79
113
+ // the requester has specified a destination routing header. We should respect that and forward the request directly to the destination
114
+ // without consulting any oracles.
115
+ if (destination) {
116
+ const destParticipantModel = await participant.validateParticipant(destination)
117
+ if (!destParticipantModel) {
118
+ const proxyId = proxyEnabled && await proxyCache.lookupProxyByDfspId(destination)
119
+
120
+ if (!proxyId) {
121
+ const errMessage = ERROR_MESSAGES.partyDestinationFspNotFound
122
+ throw ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.ID_NOT_FOUND, errMessage)
123
+ }
124
+ destination = proxyId
125
+ }
126
+ // all ok, go ahead and forward the request
127
+ await participant.sendRequest(headers, destination, callbackEndpointType, RestMethods.GET, undefined, options, childSpan)
128
+
129
+ histTimerEnd({ success: true })
130
+ logger.info('discovery getPartiesByTypeAndID request was sent to destination', { destination })
131
+ return
132
+ }
133
+
134
+ const response = await oracle.oracleRequest(headers, method, params, query, undefined, cache)
135
+ if (Array.isArray(response?.data?.partyList) && response.data.partyList.length > 0) {
136
+ // Oracle's API is a standard rest-style end-point Thus a GET /party on the oracle will return all participant-party records. We must filter the results based on the callbackEndpointType to make sure we remove records containing partySubIdOrType when we are in FSPIOP_CALLBACK_URL_PARTIES_GET mode:
137
+ let filteredResponsePartyList
138
+ switch (callbackEndpointType) {
139
+ case FspEndpointTypes.FSPIOP_CALLBACK_URL_PARTIES_GET:
140
+ filteredResponsePartyList = response.data.partyList.filter(party => party.partySubIdOrType == null) // Filter records that DON'T contain a partySubIdOrType
141
+ break
142
+ case FspEndpointTypes.FSPIOP_CALLBACK_URL_PARTIES_SUB_ID_GET:
143
+ filteredResponsePartyList = response.data.partyList.filter(party => party.partySubIdOrType === partySubId) // Filter records that match partySubIdOrType
144
+ break
145
+ default:
146
+ filteredResponsePartyList = response // Fallback to providing the standard list
147
+ }
148
+
149
+ if (!Array.isArray(filteredResponsePartyList) || !filteredResponsePartyList.length) {
150
+ const errMessage = 'Requested FSP/Party not found'
151
+ throw ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.ID_NOT_FOUND, errMessage)
152
+ }
153
+
154
+ let sentCount = 0 // if sentCount === 0 after sending, should we restart the whole process?
155
+ const sending = filteredResponsePartyList.map(async party => {
156
+ const { fspId } = party
157
+ const clonedHeaders = { ...headers }
158
+ if (!destination) {
159
+ clonedHeaders[Headers.FSPIOP.DESTINATION] = fspId
160
+ }
161
+ const schemeParticipant = await participant.validateParticipant(fspId)
162
+ if (schemeParticipant) {
163
+ sentCount++
164
+ logger.debug('participant is in scheme', { fspId })
165
+ return participant.sendRequest(clonedHeaders, party.fspId, callbackEndpointType, RestMethods.GET, undefined, options, childSpan)
166
+ }
167
+
168
+ // If the participant is not in the scheme and proxy routing is enabled,
169
+ // we should check if there is a proxy for it and send the request to the proxy
170
+ if (proxyEnabled) {
171
+ const proxyName = await proxyCache.lookupProxyByDfspId(fspId)
172
+ if (!proxyName) {
173
+ logger.warn('no proxyMapping for participant! TODO: Delete reference in oracle...', { fspId })
174
+ // todo: delete reference in oracle
175
+ } else {
176
+ sentCount++
177
+ logger.debug('participant NOT is in scheme, use proxy name', { fspId, proxyName })
178
+ return participant.sendRequest(clonedHeaders, proxyName, callbackEndpointType, RestMethods.GET, undefined, options, childSpan)
179
+ }
180
+ }
181
+ })
182
+ await Promise.all(sending)
183
+ logger.info('participant.sendRequests to filtered oracle partyList are done', { sentCount })
184
+ // todo: think what if sentCount === 0 here
185
+ } else {
186
+ logger.info('empty partyList form oracle, getting proxies list...', { proxyEnabled, params })
187
+ let filteredProxyNames = []
188
+
189
+ if (proxyEnabled) {
190
+ const proxyNames = await Util.proxies.getAllProxiesNames(Config.SWITCH_ENDPOINT)
191
+ filteredProxyNames = proxyNames.filter(name => name !== proxy)
192
+ }
193
+
194
+ if (!filteredProxyNames.length) {
195
+ const callbackHeaders = createCallbackHeaders({
196
+ requestHeaders: headers,
197
+ partyIdType: type,
198
+ partyIdentifier: params.ID,
199
+ endpointTemplate: partySubId
200
+ ? FspEndpointTemplates.PARTIES_SUB_ID_PUT_ERROR
201
+ : FspEndpointTemplates.PARTIES_PUT_ERROR
202
+ })
203
+ fspiopError = ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.PARTY_NOT_FOUND)
204
+ const errorCallbackEndpointType = utils.errorPartyCbType(partySubId)
205
+ await participant.sendErrorToParticipant(requester, errorCallbackEndpointType,
206
+ fspiopError.toApiErrorObject(config.ERROR_HANDLING), callbackHeaders, params, childSpan)
207
+ } else {
208
+ const alsReq = utils.alsRequestDto(source, params)
209
+ logger.info('starting setSendToProxiesList flow: ', { filteredProxyNames, alsReq, proxyCacheTtlSec })
210
+ const isCached = await proxyCache.setSendToProxiesList(alsReq, filteredProxyNames, proxyCacheTtlSec)
211
+ if (!isCached) {
212
+ throw ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.ID_NOT_FOUND, ERROR_MESSAGES.failedToCacheSendToProxiesList)
213
+ }
214
+
215
+ const sending = filteredProxyNames.map(
216
+ proxyName => participant.sendRequest(headers, proxyName, callbackEndpointType, RestMethods.GET, undefined, options, childSpan)
217
+ .then(({ status, data } = {}) => ({ status, data }))
218
+ )
219
+ const results = await Promise.allSettled(sending)
220
+ const isOk = results.some(result => result.status === 'fulfilled')
221
+ // If, at least, one request is sent to proxy, we treat the whole flow as successful.
222
+ // Failed requests should be handled by TTL expired/timeout handler
223
+ // todo: - think, if we should handle failed requests here (e.g., by calling receivedErrorResponse)
224
+ logger.info('setSendToProxiesList flow is done:', { isOk, results, filteredProxyNames, alsReq })
225
+ if (!isOk) {
226
+ throw ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.ID_NOT_FOUND, ERROR_MESSAGES.proxyConnectionError)
227
+ }
228
+ }
229
+ }
230
+ histTimerEnd({ success: true })
231
+ } catch (err) {
232
+ fspiopError = await utils.handleErrorOnSendingCallback(err, headers, params, requester)
233
+ histTimerEnd({ success: false })
234
+ } finally {
235
+ await utils.finishSpanWithError(childSpan, fspiopError)
236
+ }
237
+ }
238
+
239
+ module.exports = getPartiesByTypeAndID
@@ -0,0 +1,32 @@
1
+ /*****
2
+ License
3
+ --------------
4
+ Copyright © 2017 Bill & Melinda Gates Foundation
5
+ The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at
6
+ http://www.apache.org/licenses/LICENSE-2.0
7
+ Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
8
+ Contributors
9
+ --------------
10
+ This is the official list of the Mojaloop project contributors for this file.
11
+ Names of the original copyright holders (individuals or organizations)
12
+ should be listed with a '*' in the first column. People who have
13
+ contributed from an organization can be listed under the organization
14
+ that actually holds the copyright for their contributions (see the
15
+ Gates Foundation organization for an example). Those individuals should have
16
+ their names indented and be marked with a '-'. Email address can be added
17
+ optionally within square brackets <email>.
18
+ * Gates Foundation
19
+ - Name Surname <name.surname@gatesfoundation.com>
20
+
21
+ - Rajiv Mothilal <rajiv.mothilal@modusbox.com>
22
+
23
+ --------------
24
+ ******/
25
+
26
+ 'use strict'
27
+
28
+ const parties = require('./parties')
29
+
30
+ exports.getPartiesByTypeAndID = parties.getPartiesByTypeAndID
31
+ exports.putPartiesByTypeAndID = parties.putPartiesByTypeAndID
32
+ exports.putPartiesErrorByTypeAndID = parties.putPartiesErrorByTypeAndID
@@ -0,0 +1,215 @@
1
+ /*****
2
+ License
3
+ --------------
4
+ Copyright © 2017 Bill & Melinda Gates Foundation
5
+ The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at
6
+ http://www.apache.org/licenses/LICENSE-2.0
7
+ Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
8
+ Contributors
9
+ --------------
10
+ This is the official list of the Mojaloop project contributors for this file.
11
+ Names of the original copyright holders (individuals or organizations)
12
+ should be listed with a '*' in the first column. People who have
13
+ contributed from an organization can be listed under the organization
14
+ that actually holds the copyright for their contributions (see the
15
+ Gates Foundation organization for an example). Those individuals should have
16
+ their names indented and be marked with a '-'. Email address can be added
17
+ optionally within square brackets <email>.
18
+ * Gates Foundation
19
+ - Name Surname <name.surname@gatesfoundation.com>
20
+
21
+ - Rajiv Mothilal <rajiv.mothilal@modusbox.com>
22
+ - Henk Kodde <henk.kodde@modusbox.com>
23
+ - Steven Oderayi <steven.oderayi@modusbox.com>
24
+ - Juan Correa <juan.correa@modusbox.com>
25
+ - James Bush <james.bush@modusbox.com>
26
+
27
+ --------------
28
+ ******/
29
+
30
+ 'use strict'
31
+
32
+ const { Headers, RestMethods } = require('@mojaloop/central-services-shared').Enum.Http
33
+ const { decodePayload } = require('@mojaloop/central-services-shared').Util.StreamingProtocol
34
+ const { MojaloopApiErrorCodes } = require('@mojaloop/sdk-standard-components').Errors
35
+ const ErrorHandler = require('@mojaloop/central-services-error-handling')
36
+ const Metrics = require('@mojaloop/central-services-metrics')
37
+
38
+ const oracle = require('../../models/oracle/facade')
39
+ const participant = require('../../models/participantEndpoint/facade')
40
+ const { ERROR_MESSAGES } = require('../../constants')
41
+ const { loggerFactory } = require('../../lib')
42
+ const Config = require('../../lib/config')
43
+ const utils = require('./utils')
44
+ const getPartiesByTypeAndID = require('./getPartiesByTypeAndID')
45
+
46
+ const logger = loggerFactory('domain:put-parties')
47
+
48
+ /**
49
+ * @function putPartiesByTypeAndID
50
+ *
51
+ * @description This sends a callback to inform participant of successful lookup
52
+ *
53
+ * @param {object} headers - incoming http request headers
54
+ * @param {object} params - uri parameters of the http request
55
+ * @param {string} method - http request method
56
+ * @param {object} payload - payload of the request being sent out
57
+ * @param {string} dataUri - encoded payload of the request being sent out
58
+ * @param {CacheClient} cache - in-memory cache with CatboxMemory engine
59
+ * @param {IProxyCache} [proxyCache] - IProxyCache instance
60
+ */
61
+ const putPartiesByTypeAndID = async (headers, params, method, payload, dataUri, cache, proxyCache = undefined) => {
62
+ const histTimerEnd = Metrics.getHistogram(
63
+ 'putPartiesByTypeAndID',
64
+ 'Put parties by type and id',
65
+ ['success']
66
+ ).startTimer()
67
+ const type = params.Type
68
+ const partySubId = params.SubId
69
+ const source = headers[Headers.FSPIOP.SOURCE]
70
+ const destination = headers[Headers.FSPIOP.DESTINATION]
71
+ const proxy = headers[Headers.FSPIOP.PROXY]
72
+ const proxyEnabled = !!(Config.PROXY_CACHE_CONFIG.enabled && proxyCache)
73
+ logger.info('parties::putPartiesByTypeAndID::begin', { source, destination, proxy, params })
74
+
75
+ let sendTo
76
+ try {
77
+ const requesterParticipant = await participant.validateParticipant(source)
78
+ if (!requesterParticipant) {
79
+ if (!proxyEnabled || !proxy) {
80
+ const errMessage = ERROR_MESSAGES.partySourceFspNotFound
81
+ throw ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.ID_NOT_FOUND, errMessage)
82
+ }
83
+ const isCached = await proxyCache.addDfspIdToProxyMapping(source, proxy)
84
+ // think,if we should throw error if isCached === false?
85
+ logger.info('addDfspIdToProxyMapping is done', { source, proxy, isCached })
86
+ }
87
+
88
+ if (proxyEnabled && proxy) {
89
+ const alsReq = utils.alsRequestDto(destination, params) // or source?
90
+ const isExists = await proxyCache.receivedSuccessResponse(alsReq)
91
+ if (!isExists) {
92
+ logger.warn('destination is NOT in scheme, and no cached sendToProxiesList', { destination, alsReq })
93
+ // think, if we need to throw an error here
94
+ } else {
95
+ const mappingPayload = {
96
+ fspId: source
97
+ }
98
+ await oracle.oracleRequest(headers, RestMethods.POST, params, null, mappingPayload, cache)
99
+ logger.info('oracle was updated with mappingPayload', { mappingPayload, params })
100
+ }
101
+ }
102
+
103
+ const destinationParticipant = await participant.validateParticipant(destination)
104
+ if (!destinationParticipant) {
105
+ const proxyName = proxyEnabled && await proxyCache.lookupProxyByDfspId(destination)
106
+ if (!proxyName) {
107
+ const errMessage = ERROR_MESSAGES.partyDestinationFspNotFound
108
+ throw ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.DESTINATION_FSP_ERROR, errMessage)
109
+ }
110
+ sendTo = proxyName
111
+ } else {
112
+ sendTo = destinationParticipant.name
113
+ }
114
+
115
+ const decodedPayload = decodePayload(dataUri, { asParsed: false })
116
+ const callbackEndpointType = utils.putPartyCbType(partySubId)
117
+ const options = {
118
+ partyIdType: type,
119
+ partyIdentifier: params.ID,
120
+ ...(partySubId && { partySubIdOrType: partySubId })
121
+ }
122
+ await participant.sendRequest(headers, sendTo, callbackEndpointType, RestMethods.PUT, decodedPayload.body.toString(), options)
123
+
124
+ logger.info('parties::putPartiesByTypeAndID::callback was sent', { sendTo, options })
125
+ histTimerEnd({ success: true })
126
+ } catch (err) {
127
+ await utils.handleErrorOnSendingCallback(err, headers, params, sendTo)
128
+ histTimerEnd({ success: false })
129
+ }
130
+ }
131
+
132
+ /**
133
+ * @function putPartiesErrorByTypeAndID
134
+ *
135
+ * @description This populates the cache of endpoints
136
+ *
137
+ * @param {object} headers - incoming http request headers
138
+ * @param {object} params - uri parameters of the http request
139
+ * @param {object} payload - payload of the request being sent out
140
+ * @param {string} dataUri - encoded payload of the request being sent out
141
+ * @param {object} span
142
+ * @param {CacheClient} cache - in-memory cache with CatboxMemory engine
143
+ * @param {IProxyCache} [proxyCache] - IProxyCache instance
144
+ */
145
+ const putPartiesErrorByTypeAndID = async (headers, params, payload, dataUri, span, cache, proxyCache = undefined) => {
146
+ const histTimerEnd = Metrics.getHistogram(
147
+ 'putPartiesErrorByTypeAndID',
148
+ 'Put parties error by type and id',
149
+ ['success']
150
+ ).startTimer()
151
+ const partySubId = params.SubId
152
+ const destination = headers[Headers.FSPIOP.DESTINATION]
153
+ const callbackEndpointType = utils.errorPartyCbType(partySubId)
154
+ const proxyEnabled = !!(Config.PROXY_CACHE_CONFIG.enabled && proxyCache)
155
+
156
+ const childSpan = span ? span.getChild('putPartiesErrorByTypeAndID') : undefined
157
+
158
+ let sendTo
159
+ let fspiopError
160
+
161
+ try {
162
+ const proxy = proxyEnabled && headers[Headers.FSPIOP.PROXY]
163
+ if (proxy) {
164
+ if (isNotValidPayeeCase(payload)) {
165
+ const swappedHeaders = utils.swapSourceDestinationHeaders(headers)
166
+ await oracle.oracleRequest(swappedHeaders, RestMethods.DELETE, params, null, null, cache)
167
+ getPartiesByTypeAndID(swappedHeaders, params, RestMethods.GET, {}, span, cache, proxyCache)
168
+ // todo: - think if we need to send errorCallback?
169
+ // - or sentCallback after getPartiesByTypeAndID is done
170
+ logger.info('notValidPayee case - deleted Participants and run getPartiesByTypeAndID:', { proxy, params, payload })
171
+ return
172
+ }
173
+
174
+ const alsReq = utils.alsRequestDto(destination, params) // or source?
175
+ const isLast = await proxyCache.receivedErrorResponse(alsReq, proxy)
176
+ if (!isLast) {
177
+ logger.info('got NOT last error callback from proxy:', { proxy, alsReq })
178
+ return
179
+ }
180
+ }
181
+
182
+ const destinationParticipant = await participant.validateParticipant(destination)
183
+
184
+ if (destinationParticipant) {
185
+ sendTo = destination
186
+ } else {
187
+ const proxyName = proxyEnabled && await proxyCache.lookupProxyByDfspId(destination)
188
+ if (!proxyName) {
189
+ const errMessage = ERROR_MESSAGES.partyDestinationFspNotFound
190
+ throw ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.DESTINATION_FSP_ERROR, errMessage)
191
+ }
192
+ sendTo = proxyName
193
+ }
194
+ const decodedPayload = decodePayload(dataUri, { asParsed: false })
195
+ await participant.sendErrorToParticipant(sendTo, callbackEndpointType, decodedPayload.body.toString(), headers, params, childSpan)
196
+
197
+ logger.info('putPartiesErrorByTypeAndID callback was sent', { sendTo })
198
+ histTimerEnd({ success: true })
199
+ } catch (err) {
200
+ fspiopError = await utils.handleErrorOnSendingCallback(err, headers, params, sendTo)
201
+ histTimerEnd({ success: false })
202
+ } finally {
203
+ await utils.finishSpanWithError(childSpan, fspiopError)
204
+ }
205
+ }
206
+
207
+ function isNotValidPayeeCase (payload) {
208
+ return payload?.errorInformation?.errorCode === MojaloopApiErrorCodes.PAYEE_IDENTIFIER_NOT_VALID.code
209
+ }
210
+
211
+ module.exports = {
212
+ getPartiesByTypeAndID,
213
+ putPartiesByTypeAndID,
214
+ putPartiesErrorByTypeAndID
215
+ }
@@ -0,0 +1,84 @@
1
+ const { Enum } = require('@mojaloop/central-services-shared')
2
+ const EventSdk = require('@mojaloop/event-sdk')
3
+ const ErrorHandler = require('@mojaloop/central-services-error-handling')
4
+
5
+ const participant = require('../../models/participantEndpoint/facade')
6
+ const Config = require('../../lib/config')
7
+ const { logger } = require('../../lib')
8
+
9
+ const { FspEndpointTypes } = Enum.EndPoints
10
+ const { Headers } = Enum.Http
11
+
12
+ const getPartyCbType = (partySubId) => partySubId
13
+ ? FspEndpointTypes.FSPIOP_CALLBACK_URL_PARTIES_SUB_ID_GET
14
+ : FspEndpointTypes.FSPIOP_CALLBACK_URL_PARTIES_GET
15
+
16
+ const putPartyCbType = (partySubId) => partySubId
17
+ ? FspEndpointTypes.FSPIOP_CALLBACK_URL_PARTIES_SUB_ID_PUT
18
+ : FspEndpointTypes.FSPIOP_CALLBACK_URL_PARTIES_PUT
19
+
20
+ const errorPartyCbType = (partySubId) => partySubId
21
+ ? FspEndpointTypes.FSPIOP_CALLBACK_URL_PARTIES_SUB_ID_PUT_ERROR
22
+ : FspEndpointTypes.FSPIOP_CALLBACK_URL_PARTIES_PUT_ERROR
23
+
24
+ const finishSpanWithError = async (childSpan, fspiopError) => {
25
+ if (childSpan && !childSpan.isFinished) {
26
+ if (fspiopError) {
27
+ const state = new EventSdk.EventStateMetadata(EventSdk.EventStatusType.failed, fspiopError.apiErrorCode.code, fspiopError.apiErrorCode.message)
28
+ await childSpan.error(fspiopError, state)
29
+ await childSpan.finish(fspiopError.message, state)
30
+ } else {
31
+ await childSpan.finish()
32
+ }
33
+ }
34
+ }
35
+
36
+ const alsRequestDto = (sourceId, params) => ({
37
+ sourceId,
38
+ type: params.Type,
39
+ partyId: params.ID
40
+ })
41
+
42
+ const swapSourceDestinationHeaders = (headers) => {
43
+ const {
44
+ [Headers.FSPIOP.SOURCE]: source,
45
+ [Headers.FSPIOP.DESTINATION]: destination,
46
+ [Headers.FSPIOP.PROXY]: proxy,
47
+ ...restHeaders
48
+ } = headers
49
+ return {
50
+ ...restHeaders,
51
+ [Headers.FSPIOP.SOURCE]: destination,
52
+ [Headers.FSPIOP.DESTINATION]: source
53
+ }
54
+ }
55
+
56
+ // change signature to accept object
57
+ const handleErrorOnSendingCallback = async (err, headers, params, requester) => {
58
+ try {
59
+ logger.error('error in sending parties callback', err)
60
+ const sendTo = requester || headers[Headers.FSPIOP.SOURCE]
61
+ const errorCallbackEndpointType = errorPartyCbType(params.SubId)
62
+ const fspiopError = ErrorHandler.Factory.reformatFSPIOPError(err)
63
+ const errInfo = fspiopError.toApiErrorObject(Config.ERROR_HANDLING)
64
+
65
+ await participant.sendErrorToParticipant(sendTo, errorCallbackEndpointType, errInfo, headers, params)
66
+
67
+ logger.info('handleErrorOnSendingCallback in done', { sendTo, params, errInfo })
68
+ return fspiopError
69
+ } catch (exc) {
70
+ // We can't do anything else here- we _must_ handle all errors _within_ this function because
71
+ // we've already sent a sync response- we cannot throw.
72
+ logger.error('error in handleErrorOnSendingCallback', exc)
73
+ }
74
+ }
75
+
76
+ module.exports = {
77
+ getPartyCbType,
78
+ putPartyCbType,
79
+ errorPartyCbType,
80
+ finishSpanWithError,
81
+ handleErrorOnSendingCallback,
82
+ alsRequestDto,
83
+ swapSourceDestinationHeaders
84
+ }
@@ -0,0 +1,48 @@
1
+ const {
2
+ Factory: { createFSPIOPError },
3
+ Enums: { FSPIOPErrorCodes }
4
+ } = require('@mojaloop/central-services-error-handling')
5
+ const {
6
+ Http: { Headers: { FSPIOP: FSPIOPHeaders } },
7
+ Events: { Event: { Type: EventType, Action: EventAction } },
8
+ EndPoints: { FspEndpointTypes }
9
+ } = require('@mojaloop/central-services-shared').Enum
10
+ const { Tracer } = require('@mojaloop/event-sdk')
11
+ const { API_TYPES } = require('@mojaloop/central-services-shared').Util.Hapi
12
+
13
+ const { TransformFacades } = require('../../lib')
14
+ const LibUtil = require('../../lib/util')
15
+ const Config = require('../../lib/config')
16
+
17
+ const makeErrorPayload = async (headers, params) => {
18
+ const body = createFSPIOPError(FSPIOPErrorCodes.EXPIRED_ERROR).toApiErrorObject(Config.ERROR_HANDLING)
19
+ return Config.API_TYPE === API_TYPES.iso20022
20
+ ? (await TransformFacades.FSPIOP.parties.putError({ body, headers, params })).body
21
+ : body
22
+ }
23
+
24
+ const timeoutCallbackDto = async ({ destination, partyId, partyType }) => {
25
+ const headers = {
26
+ [FSPIOPHeaders.SOURCE]: Config.HUB_NAME,
27
+ [FSPIOPHeaders.DESTINATION]: destination
28
+ }
29
+ const params = {
30
+ ID: partyId,
31
+ Type: partyType
32
+ }
33
+ const dto = {
34
+ errorInformation: await makeErrorPayload(headers, params),
35
+ headers,
36
+ params,
37
+ endpointType: FspEndpointTypes.FSPIOP_CALLBACK_URL_PARTIES_PUT_ERROR
38
+ }
39
+ const span = Tracer.createSpan('timeoutInterschemePartiesLookups', { headers: dto.headers })
40
+ const spanTags = LibUtil.getSpanTags({ headers: dto.headers }, EventType.PARTY, EventAction.PUT)
41
+ span.setTags(spanTags)
42
+
43
+ return { ...dto, span }
44
+ }
45
+
46
+ module.exports = {
47
+ timeoutCallbackDto
48
+ }