account-lookup-service 17.6.0 → 17.7.0-snapshot.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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "account-lookup-service",
3
3
  "description": "Account Lookup Service is used to validate Party and Participant lookups.",
4
- "version": "17.6.0",
4
+ "version": "17.7.0-snapshot.0",
5
5
  "license": "Apache-2.0",
6
6
  "author": "ModusBox",
7
7
  "contributors": [
@@ -44,10 +44,8 @@
44
44
  "start:api": "node src/index.js server --api",
45
45
  "start:admin": "node src/index.js server --admin",
46
46
  "start:handlers": "node src/handlers/index.js handlers --timeout",
47
- "standard": "standard",
48
- "standard:fix": "standard --fix",
49
- "lint": "npm run standard",
50
- "lint:fix": "npm run standard:fix",
47
+ "lint": "standard",
48
+ "lint:fix": "standard --fix",
51
49
  "dev": "nodemon src/index.js server",
52
50
  "test": "npm run test:unit",
53
51
  "test:unit": "NODE_OPTIONS='--experimental-vm-modules --max-old-space-size=8192' jest --runInBand",
@@ -95,13 +93,13 @@
95
93
  "@mojaloop/central-services-health": "15.0.4",
96
94
  "@mojaloop/central-services-logger": "11.7.0",
97
95
  "@mojaloop/central-services-metrics": "12.5.0",
98
- "@mojaloop/central-services-shared": "18.23.0",
96
+ "@mojaloop/central-services-shared": "18.23.1-snapshot.0",
99
97
  "@mojaloop/central-services-stream": "11.5.1",
100
98
  "@mojaloop/database-lib": "11.1.4",
101
99
  "@mojaloop/event-sdk": "14.3.2",
102
- "@mojaloop/inter-scheme-proxy-cache-lib": "2.3.7",
100
+ "@mojaloop/inter-scheme-proxy-cache-lib": "2.4.0-snapshot.0",
103
101
  "@mojaloop/ml-schema-transformer-lib": "2.5.8",
104
- "@mojaloop/sdk-standard-components": "19.10.3",
102
+ "@mojaloop/sdk-standard-components": "19.11.0",
105
103
  "@now-ims/hapi-now-auth": "2.1.0",
106
104
  "ajv": "8.17.1",
107
105
  "ajv-keywords": "5.1.0",
@@ -163,7 +161,7 @@
163
161
  "devDependencies": {
164
162
  "@types/jest": "29.5.14",
165
163
  "audit-ci": "^7.1.0",
166
- "axios": "1.8.3",
164
+ "axios": "1.8.4",
167
165
  "axios-retry": "^4.5.0",
168
166
  "docdash": "2.0.2",
169
167
  "dotenv": "^16.4.7",
@@ -178,7 +176,7 @@
178
176
  "pre-commit": "1.2.2",
179
177
  "proxyquire": "2.1.3",
180
178
  "replace": "^1.2.2",
181
- "sinon": "19.0.2",
179
+ "sinon": "19.0.4",
182
180
  "standard": "17.1.2",
183
181
  "standard-version": "^9.5.0",
184
182
  "swagmock": "1.0.0"
@@ -190,5 +188,9 @@
190
188
  "scripts": {
191
189
  "postchangelog": "replace '\\[mojaloop/#(\\d+)\\]\\(https://github.com/mojaloop/(.*)/issues/(\\d+)\\)' '[mojaloop/#$1](https://github.com/mojaloop/project/issues/$1)' CHANGELOG.md"
192
190
  }
191
+ },
192
+ "imports": {
193
+ "#src/*": "./src/*.js",
194
+ "#test/*": "./test/*.js"
193
195
  }
194
196
  }
package/src/constants.js CHANGED
@@ -1,9 +1,38 @@
1
+ /*****
2
+ License
3
+ --------------
4
+ Copyright © 2020-2025 Mojaloop Foundation
5
+ The Mojaloop files are made available by the Mojaloop 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
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ 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.
10
+
11
+ Contributors
12
+ --------------
13
+ This is the official list of the Mojaloop project contributors for this file.
14
+ Names of the original copyright holders (individuals or organizations)
15
+ should be listed with a '*' in the first column. People who have
16
+ contributed from an organization can be listed under the organization
17
+ that actually holds the copyright for their contributions (see the
18
+ Mojaloop Foundation for an example). Those individuals should have
19
+ their names indented and be marked with a '-'. Email address can be added
20
+ optionally within square brackets <email>.
21
+
22
+ * Mojaloop Foundation
23
+ * Eugen Klymniuk <eugen.klymniuk@infitx.com>
24
+
25
+ --------------
26
+ ******/
27
+
1
28
  const ERROR_MESSAGES = Object.freeze({
29
+ emptyFilteredPartyList: 'Empty oracle partyList, filtered based on callbackEndpointType',
30
+ failedToCacheSendToProxiesList: 'Failed to cache sendToProxiesList',
31
+ noDiscoveryRequestsForwarded: 'No discovery requests forwarded to participants',
2
32
  sourceFspNotFound: 'Requester FSP not found',
3
33
  partyDestinationFspNotFound: 'Destination FSP not found',
4
34
  partyProxyNotFound: 'Proxy not found',
5
- proxyConnectionError: 'Proxy connection error',
6
- failedToCacheSendToProxiesList: 'Failed to cache sendToProxiesList'
35
+ proxyConnectionError: 'Proxy connection error - no successful requests sent to proxies'
7
36
  })
8
37
 
9
38
  const HANDLER_TYPES = Object.freeze({
@@ -26,17 +26,17 @@
26
26
  ******/
27
27
 
28
28
  const { proxies } = require('@mojaloop/central-services-shared').Util
29
+ const { logger } = require('../../lib')
30
+ const config = require('../../lib/config')
29
31
  const oracle = require('../../models/oracle/facade')
30
32
  const participant = require('../../models/participantEndpoint/facade')
31
- const config = require('../../lib/config')
32
33
  const partiesUtils = require('./partiesUtils')
33
34
 
34
- const createDeps = ({ cache, proxyCache, childSpan, log, stepState }) => Object.freeze({
35
+ const createDeps = ({ cache, proxyCache, childSpan, log = logger }) => Object.freeze({
35
36
  cache,
36
37
  proxyCache,
37
38
  childSpan,
38
39
  log,
39
- stepState,
40
40
  config,
41
41
  oracle,
42
42
  participant,
@@ -50,26 +50,22 @@ const getPartiesByTypeAndID = async (headers, params, method, query, span, cache
50
50
  ['success']
51
51
  ).startTimer()
52
52
  const childSpan = span ? span.getChild(component) : undefined
53
- const log = logger.child({ component, params })
54
- const stepState = libUtil.initStepState()
55
-
56
- const deps = createDeps({ cache, proxyCache, childSpan, log, stepState })
57
- const service = new GetPartiesService(deps)
58
- const results = {}
53
+ const deps = createDeps({ cache, proxyCache, childSpan })
54
+ const service = new GetPartiesService(deps, { headers, params, query })
55
+ let fspiopError
59
56
 
60
57
  try {
61
- await service.handleRequest({ headers, params, query, results })
62
- log.info('getPartiesByTypeAndID is done')
58
+ await service.handleRequest()
59
+ logger.info('getPartiesByTypeAndID is done')
63
60
  histTimerEnd({ success: true })
64
61
  } catch (error) {
65
- const { requester } = results
66
- results.fspiopError = await service.handleError({ error, requester, headers, params })
62
+ fspiopError = await service.handleError(error)
67
63
  histTimerEnd({ success: false })
68
- if (results.fspiopError) {
69
- libUtil.countFspiopError(results.fspiopError, { operation: component, step: stepState.step })
64
+ if (fspiopError) {
65
+ libUtil.countFspiopError(fspiopError, { operation: component, step: service.currenStep })
70
66
  }
71
67
  } finally {
72
- await libUtil.finishSpanWithError(childSpan, results.fspiopError)
68
+ await libUtil.finishSpanWithError(childSpan, fspiopError)
73
69
  }
74
70
  }
75
71
 
@@ -29,7 +29,6 @@
29
29
 
30
30
  'use strict'
31
31
 
32
- const { Headers } = require('@mojaloop/central-services-shared').Enum.Http
33
32
  const Metrics = require('@mojaloop/central-services-metrics')
34
33
  const libUtil = require('../../lib/util')
35
34
  const { logger } = require('../../lib')
@@ -37,9 +36,7 @@ const { createDeps } = require('./deps')
37
36
  const services = require('./services')
38
37
 
39
38
  /**
40
- * @function putPartiesByTypeAndID
41
- *
42
- * @description This sends a callback to inform participant of successful lookup
39
+ * Sends a callback to inform participant of successful lookup
43
40
  *
44
41
  * @param {object} headers - incoming http request headers
45
42
  * @param {object} params - uri parameters of the http request
@@ -58,45 +55,25 @@ const putPartiesByTypeAndID = async (headers, params, method, payload, dataUri,
58
55
  ['success']
59
56
  ).startTimer()
60
57
  // const childSpan = span ? span.getChild(component) : undefined
61
- const log = logger.child({ component, params })
62
- const stepState = libUtil.initStepState()
63
-
64
- const deps = createDeps({ cache, proxyCache, log, stepState })
65
- const service = new services.PutPartiesService(deps)
66
- const results = {}
67
-
68
- const source = headers[Headers.FSPIOP.SOURCE]
69
- const destination = headers[Headers.FSPIOP.DESTINATION]
70
- const proxy = headers[Headers.FSPIOP.PROXY]
71
- log.info('putPartiesByTypeAndID start', { source, destination, proxy })
58
+ const deps = createDeps({ cache, proxyCache })
59
+ const service = new services.PutPartiesService(deps, { headers, params, payload, dataUri })
60
+ let fspiopError
72
61
 
73
62
  try {
74
- await service.validateSourceParticipant({ source, proxy })
75
-
76
- if (proxy) {
77
- await service.checkProxySuccessResponse({ destination, source, headers, params })
78
- }
79
-
80
- const sendTo = await service.identifyDestinationForSuccessCallback(destination)
81
- results.requester = sendTo
82
- await service.sendSuccessCallback({ sendTo, headers, params, dataUri })
83
-
84
- log.info('putPartiesByTypeAndID callback was sent', { sendTo })
63
+ await service.handleRequest()
64
+ logger.info('putPartiesByTypeAndID is done')
85
65
  histTimerEnd({ success: true })
86
66
  } catch (error) {
87
- const { requester } = results
88
- results.fspiopError = await service.handleError({ error, requester, headers, params })
89
- if (results.fspiopError) {
90
- libUtil.countFspiopError(results.fspiopError, { operation: component, step: stepState.step })
67
+ fspiopError = await service.handleError(error)
68
+ if (fspiopError) {
69
+ libUtil.countFspiopError(fspiopError, { operation: component, step: service.currenStep })
91
70
  }
92
71
  histTimerEnd({ success: false })
93
72
  }
94
73
  }
95
74
 
96
75
  /**
97
- * @function putPartiesErrorByTypeAndID
98
- *
99
- * @description This populates the cache of endpoints
76
+ * Sends error callback to inform participant of failed lookup
100
77
  *
101
78
  * @param {object} headers - incoming http request headers
102
79
  * @param {object} params - uri parameters of the http request
@@ -114,53 +91,31 @@ const putPartiesErrorByTypeAndID = async (headers, params, payload, dataUri, spa
114
91
  ['success']
115
92
  ).startTimer()
116
93
  const childSpan = span ? span.getChild(component) : undefined
117
- const log = logger.child({ component, params })
118
- const stepState = libUtil.initStepState()
119
-
120
- const deps = createDeps({ cache, proxyCache, childSpan, log, stepState })
121
- const service = new services.PutPartiesErrorService(deps)
122
- const results = {}
123
-
124
- const destination = headers[Headers.FSPIOP.DESTINATION]
125
- const proxy = headers[Headers.FSPIOP.PROXY]
126
- const proxyEnabled = !!(deps.config.PROXY_CACHE_CONFIG.enabled && proxyCache)
127
- log.info('putPartiesErrorByTypeAndID start', { destination, proxy, proxyEnabled })
94
+ const deps = createDeps({ cache, proxyCache, childSpan })
95
+ const inputs = { headers, params, payload, dataUri }
96
+ const service = new services.PutPartiesErrorService(deps, inputs)
97
+ let fspiopError
128
98
 
129
99
  try {
130
- if (proxyEnabled && proxy) {
131
- const notValid = await service.checkPayee({ headers, params, payload, proxy })
132
- if (notValid) {
133
- const getPartiesService = new services.GetPartiesService(deps)
134
- // todo: think, if we need to remove destination header before starting new discovery
135
- await getPartiesService.handleRequest({ headers, params, results })
136
- log.info('putPartiesErrorByTypeAndID triggered new discovery flow')
137
- histTimerEnd({ success: true })
138
- return
139
- }
140
-
141
- const isLast = await service.checkLastProxyCallback({ destination, proxy, params })
142
- if (!isLast) {
143
- log.info('putPartiesErrorByTypeAndID proxy callback was processed', { proxy })
144
- histTimerEnd({ success: true })
145
- return
146
- }
100
+ const needDiscovery = await service.handleRequest()
101
+ if (needDiscovery) {
102
+ const getPartiesService = new services.GetPartiesService(deps, inputs)
103
+ await getPartiesService.triggerInterSchemeDiscoveryFlow(
104
+ services.GetPartiesService.headersWithoutDestination(headers)
105
+ )
106
+ // think, if we need to start the whole processing with getPartiesService.handleRequest() ?
147
107
  }
148
108
 
149
- const sendTo = await service.identifyDestinationForErrorCallback(destination)
150
- results.requester = sendTo
151
- await service.sendErrorCallbackToParticipant({ sendTo, headers, params, dataUri })
152
-
153
- log.info('putPartiesErrorByTypeAndID callback was sent', { sendTo })
109
+ logger.info('putPartiesErrorByTypeAndID is done')
154
110
  histTimerEnd({ success: true })
155
111
  } catch (error) {
156
- const { requester } = results
157
- results.fspiopError = await service.handleError({ error, requester, headers, params })
158
- if (results.fspiopError) {
159
- libUtil.countFspiopError(results.fspiopError, { operation: component, step: stepState.step })
112
+ fspiopError = await service.handleError(error)
113
+ if (fspiopError) {
114
+ libUtil.countFspiopError(fspiopError, { operation: component, step: service.currenStep })
160
115
  }
161
116
  histTimerEnd({ success: false })
162
117
  } finally {
163
- await libUtil.finishSpanWithError(childSpan, results.fspiopError)
118
+ await libUtil.finishSpanWithError(childSpan, fspiopError)
164
119
  }
165
120
  }
166
121
 
@@ -26,33 +26,61 @@
26
26
  ******/
27
27
 
28
28
  const ErrorHandler = require('@mojaloop/central-services-error-handling')
29
- const { decodePayload } = require('@mojaloop/central-services-shared').Util.StreamingProtocol
30
29
  const { Enum } = require('@mojaloop/central-services-shared')
30
+ const { decodePayload } = require('@mojaloop/central-services-shared').Util.StreamingProtocol
31
+ const { initStepState } = require('../../../lib/util')
32
+ const { createCallbackHeaders } = require('../../../lib/headers')
31
33
 
32
34
  const { FspEndpointTypes, FspEndpointTemplates } = Enum.EndPoints
33
35
  const { Headers, RestMethods } = Enum.Http
34
36
 
37
+ /**
38
+ * @typedef {Object} PartiesInputs
39
+ * @property {Object} headers - incoming http request headers.
40
+ * @property {Object} params - uri parameters of the http request.
41
+ * @property {Object} [payload] - payload of the request being sent out.
42
+ * @property {Object} [query] - uri query parameters of the http request
43
+ * @property {string} [dataUri] - encoded payload of the request being sent out.
44
+ */
45
+
35
46
  class BasePartiesService {
36
- constructor (deps) {
37
- this.deps = deps
38
- this.log = this.deps.log.child({ component: this.constructor.name })
39
- this.proxyEnabled = !!(deps.config.PROXY_CACHE_CONFIG?.enabled && deps.proxyCache)
47
+ #deps
48
+ #inputs // see PartiesInputs
49
+ #state // any calculated values we get during request processing
50
+
51
+ /**
52
+ * @param {Object} deps - The dependencies required by the class instance.
53
+ * @param {PartiesInputs} inputs - The input parameters from incoming http request.
54
+ * @return {void}
55
+ */
56
+ constructor (deps, inputs) {
57
+ this.#deps = Object.freeze(deps)
58
+ this.#inputs = Object.freeze(inputs)
59
+ this.#state = this.#initiateState()
60
+ this.log = this.deps.log.child({
61
+ component: this.constructor.name,
62
+ params: this.inputs.params
63
+ })
40
64
  }
41
65
 
42
- // async handleRequest () {
43
- // throw new Error('handleRequest must be implemented by subclass')
44
- // }
66
+ get deps () { return this.#deps }
67
+ get inputs () { return this.#inputs }
68
+ get state () { return this.#state }
45
69
 
46
- async handleError ({ error, requester, headers, params }) {
70
+ async handleError (error) {
71
+ const { headers, params } = this.inputs
47
72
  const log = this.log.child({ method: 'handleError' })
48
73
  try {
49
74
  log.error('error in processing parties request: ', error)
50
- const sendTo = requester || headers[Headers.FSPIOP.SOURCE]
51
75
  const fspiopError = ErrorHandler.Factory.reformatFSPIOPError(error)
52
76
  const errorInfo = await this.deps.partiesUtils.makePutPartiesErrorPayload(this.deps.config, fspiopError, headers, params)
53
77
 
54
- await this.sendErrorCallback({ sendTo, errorInfo, headers, params })
55
- log.info('handleError in done', { sendTo, errorInfo })
78
+ await this.sendErrorCallback({
79
+ errorInfo,
80
+ headers: BasePartiesService.createErrorCallbackHeaders(headers, params),
81
+ params
82
+ })
83
+ log.info('handleError in done')
56
84
  return fspiopError
57
85
  } catch (exc) {
58
86
  // We can't do anything else here- we _must_ handle all errors _within_ this function because
@@ -62,26 +90,80 @@ class BasePartiesService {
62
90
  }
63
91
 
64
92
  async validateParticipant (participantId) {
93
+ this.stepInProgress('validateParticipant')
65
94
  return this.deps.participant.validateParticipant(participantId)
66
95
  }
67
96
 
68
- async sendErrorCallback ({ sendTo, errorInfo, headers, params, payload = undefined }) {
97
+ async sendErrorCallback ({ errorInfo, headers, params }) {
98
+ this.stepInProgress('sendErrorCallback')
99
+ const sendTo = this.state.requester || this.state.source
69
100
  const endpointType = this.deps.partiesUtils.errorPartyCbType(params.SubId)
101
+
70
102
  await this.deps.participant.sendErrorToParticipant(
71
- sendTo, endpointType, errorInfo, headers, params, payload, this.deps.childSpan
103
+ sendTo, endpointType, errorInfo, headers, params, undefined, this.deps.childSpan
72
104
  )
73
- this.log.verbose('sendErrorCallback is done', { sendTo, errorInfo })
105
+ this.log.info('sendErrorCallback is done', { sendTo, errorInfo })
74
106
  }
75
107
 
76
108
  async sendDeleteOracleRequest (headers, params) {
109
+ this.stepInProgress('sendDeleteOracleRequest')
77
110
  return this.deps.oracle.oracleRequest(headers, RestMethods.DELETE, params, null, null, this.deps.cache)
78
111
  }
79
112
 
113
+ createFspiopIdNotFoundError (errMessage, log = this.log) {
114
+ log.warn(errMessage)
115
+ return ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.ID_NOT_FOUND, errMessage)
116
+ }
117
+
118
+ stepInProgress (stepName) {
119
+ this.log.debug('step is in progress', { stepName })
120
+ this.state.stepState?.inProgress(stepName)
121
+ }
122
+
123
+ get currenStep () {
124
+ return this.state.stepState?.step
125
+ }
126
+
127
+ #initiateState () {
128
+ const { headers } = this.inputs
129
+ return {
130
+ destination: headers[Headers.FSPIOP.DESTINATION],
131
+ source: headers[Headers.FSPIOP.SOURCE],
132
+ proxy: headers[Headers.FSPIOP.PROXY],
133
+ proxyEnabled: !!(this.deps.config.PROXY_CACHE_CONFIG?.enabled && this.deps.proxyCache),
134
+ stepState: initStepState()
135
+ }
136
+ }
137
+
80
138
  static decodeDataUriPayload (dataUri) {
81
139
  const decoded = decodePayload(dataUri, { asParsed: false })
82
140
  return decoded.body.toString()
83
141
  }
84
142
 
143
+ static headersWithoutDestination (headers) {
144
+ const { [Headers.FSPIOP.DESTINATION]: _, ...restHeaders } = headers || {}
145
+ return restHeaders
146
+ }
147
+
148
+ static overrideDestinationHeader (headers, destination) {
149
+ const { [Headers.FSPIOP.DESTINATION]: _, ...restHeaders } = headers || {}
150
+ return {
151
+ ...restHeaders,
152
+ ...(destination && { [Headers.FSPIOP.DESTINATION]: destination })
153
+ }
154
+ }
155
+
156
+ static createErrorCallbackHeaders (headers, params) {
157
+ return createCallbackHeaders({
158
+ requestHeaders: headers,
159
+ partyIdType: params.Type,
160
+ partyIdentifier: params.ID,
161
+ endpointTemplate: params.SubId
162
+ ? FspEndpointTemplates.PARTIES_SUB_ID_PUT_ERROR
163
+ : FspEndpointTemplates.PARTIES_PUT_ERROR
164
+ })
165
+ }
166
+
85
167
  static enums () {
86
168
  return {
87
169
  FspEndpointTypes,