diff --git a/src/audit-resolve.json b/src/audit-resolve.json index a193744ac..034920928 100644 --- a/src/audit-resolve.json +++ b/src/audit-resolve.json @@ -140,80 +140,138 @@ "expiresAt": 1637308004771 }, "1004946|@mojaloop/event-sdk>@grpc/proto-loader>yargs>string-width>strip-ansi>ansi-regex": { - "decision": "postpone", - "madeAt": 1637774978679 + "decision": "ignore", + "madeAt": 1644879175753, + "expiresAt": 1647471172307 }, "1004946|@mojaloop/central-services-shared>widdershins>oas-resolver>yargs>string-width>strip-ansi>ansi-regex": { - "decision": "postpone", - "madeAt": 1637774978679 + "decision": "ignore", + "madeAt": 1644879175753, + "expiresAt": 1647471172307 }, "1004946|@mojaloop/central-services-shared>widdershins>oas-resolver>yargs>cliui>string-width>strip-ansi>ansi-regex": { - "decision": "postpone", - "madeAt": 1637774978679 + "decision": "ignore", + "madeAt": 1644879175753, + "expiresAt": 1647471172307 }, "1004946|@mojaloop/central-services-shared>widdershins>swagger2openapi>oas-resolver>yargs>cliui>string-width>strip-ansi>ansi-regex": { - "decision": "postpone", - "madeAt": 1637774978679 + "decision": "ignore", + "madeAt": 1644879175753, + "expiresAt": 1647471172307 }, "1004946|@mojaloop/central-services-shared>widdershins>swagger2openapi>oas-validator>oas-resolver>yargs>cliui>string-width>strip-ansi>ansi-regex": { - "decision": "postpone", - "madeAt": 1637774978679 + "decision": "ignore", + "madeAt": 1644879175754, + "expiresAt": 1647471172307 }, "1004946|@mojaloop/central-services-shared>widdershins>swagger2openapi>oas-validator>oas-resolver>yargs>cliui>wrap-ansi>string-width>strip-ansi>ansi-regex": { - "decision": "postpone", - "madeAt": 1637774978679 + "decision": "ignore", + "madeAt": 1644879175754, + "expiresAt": 1647471172307 }, "1004946|@mojaloop/event-sdk>@grpc/proto-loader>yargs>cliui>wrap-ansi>strip-ansi>ansi-regex": { - "decision": "postpone", - "madeAt": 1637774978679 + "decision": "ignore", + "madeAt": 1644879175754, + "expiresAt": 1647471172307 }, "1004946|@mojaloop/central-services-shared>widdershins>oas-resolver>yargs>cliui>wrap-ansi>strip-ansi>ansi-regex": { - "decision": "postpone", - "madeAt": 1637774978679 + "decision": "ignore", + "madeAt": 1644879175754, + "expiresAt": 1647471172307 }, "1004946|@mojaloop/central-services-shared>widdershins>swagger2openapi>oas-resolver>yargs>cliui>wrap-ansi>strip-ansi>ansi-regex": { - "decision": "postpone", - "madeAt": 1637774978679 + "decision": "ignore", + "madeAt": 1644879175754, + "expiresAt": 1647471172307 }, "1004946|@mojaloop/central-services-shared>widdershins>swagger2openapi>oas-validator>oas-resolver>yargs>cliui>wrap-ansi>strip-ansi>ansi-regex": { - "decision": "postpone", - "madeAt": 1637774978679 + "decision": "ignore", + "madeAt": 1644879175754, + "expiresAt": 1647471172307 }, "1004946|@mojaloop/event-sdk>@grpc/proto-loader>yargs>cliui>strip-ansi>ansi-regex": { - "decision": "postpone", - "madeAt": 1637774978679 + "decision": "ignore", + "madeAt": 1644879175754, + "expiresAt": 1647471172307 }, "1004854|@mojaloop/central-services-shared>widdershins>openapi-sampler>json-pointer": { - "decision": "postpone", - "madeAt": 1637774979253 + "decision": "ignore", + "madeAt": 1644879153322, + "expiresAt": 1647471110577 }, "1004869|@mojaloop/central-services-shared>widdershins>swagger2openapi>better-ajv-errors>jsonpointer": { - "decision": "postpone", - "madeAt": 1637774979672 + "decision": "ignore", + "madeAt": 1644879154472, + "expiresAt": 1647471110577 }, "1004869|@mojaloop/central-services-shared>widdershins>swagger2openapi>oas-validator>better-ajv-errors>jsonpointer": { - "decision": "postpone", - "madeAt": 1637774979672 + "decision": "ignore", + "madeAt": 1644879154472, + "expiresAt": 1647471110577 }, "1004946|@mojaloop/central-services-shared>widdershins>yargs>string-width>strip-ansi>ansi-regex": { - "decision": "postpone", - "madeAt": 1637774980265 + "decision": "ignore", + "madeAt": 1644879155619, + "expiresAt": 1647471110577 }, "1004946|@mojaloop/central-services-shared>widdershins>yargs>cliui>string-width>strip-ansi>ansi-regex": { - "decision": "postpone", - "madeAt": 1637774980265 + "decision": "ignore", + "madeAt": 1644879155619, + "expiresAt": 1647471110577 }, "1005383|@mojaloop/central-services-shared>shins>sanitize-html": { - "decision": "postpone", - "madeAt": 1637774980738 + "decision": "ignore", + "madeAt": 1644879156746, + "expiresAt": 1647471110577 }, "1005384|@mojaloop/central-services-shared>shins>sanitize-html": { - "decision": "postpone", - "madeAt": 1637774980738 + "decision": "ignore", + "madeAt": 1644879156746, + "expiresAt": 1647471110577 }, "1005534|@mojaloop/central-services-shared>widdershins>yargs>yargs-parser": { - "decision": "postpone", - "madeAt": 1637774981266 + "decision": "ignore", + "madeAt": 1644879157837, + "expiresAt": 1647471110577 + }, + "1006865|axios>follow-redirects": { + "decision": "fix", + "madeAt": 1644879138577 + }, + "1006865|@mojaloop/central-services-shared>axios>follow-redirects": { + "decision": "fix", + "madeAt": 1644879138577 + }, + "1007023|axios>follow-redirects": { + "decision": "fix", + "madeAt": 1644879138577 + }, + "1007023|@mojaloop/central-services-shared>axios>follow-redirects": { + "decision": "fix", + "madeAt": 1644879138577 + }, + "1006899|@mojaloop/central-services-shared>widdershins>node-fetch": { + "decision": "fix", + "madeAt": 1644879149946 + }, + "1006899|@mojaloop/event-sdk>grpc>@mapbox/node-pre-gyp>node-fetch": { + "decision": "fix", + "madeAt": 1644879149946 + }, + "1006846|@mojaloop/central-services-shared>shins>sanitize-html>postcss": { + "decision": "ignore", + "madeAt": 1644879158938, + "expiresAt": 1647471110577 + }, + "1006886|@mojaloop/central-services-shared>shins>markdown-it": { + "decision": "ignore", + "madeAt": 1644879160175, + "expiresAt": 1647471110577 + }, + "1007017|@mojaloop/central-services-shared>widdershins>swagger2openapi>oas-validator>ajv": { + "decision": "ignore", + "madeAt": 1644879161354, + "expiresAt": 1647471110577 } }, "rules": {}, diff --git a/src/config.js b/src/config.js index aaab57cd2..a25ece35b 100644 --- a/src/config.js +++ b/src/config.js @@ -154,5 +154,10 @@ module.exports = { proxyConfig: env.get('PROXY_CONFIG_PATH').asYamlConfig(), reserveNotification: env.get('RESERVE_NOTIFICATION').default('false').asBool(), // resourceVersions config should be string in format: "resouceOneName=1.0,resourceTwoName=1.1" - resourceVersions: env.get('RESOURCE_VERSIONS').default('').asResourceVersions() + resourceVersions: env.get('RESOURCE_VERSIONS').default('').asResourceVersions(), + + // in 3PPI DFSP's generate their own `transferId` which is associated with + // a transactionRequestId. this option decodes the ilp packet for + // the `transactionId` to retrieve the quote from cache + allowDifferentTransferTransactionId: env.get('ALLOW_DIFFERENT_TRANSFER_TRANSACTION_ID').default('false').asBool(), }; diff --git a/src/lib/model/InboundTransfersModel.js b/src/lib/model/InboundTransfersModel.js index 7e8edfc7a..56e700923 100644 --- a/src/lib/model/InboundTransfersModel.js +++ b/src/lib/model/InboundTransfersModel.js @@ -34,6 +34,7 @@ class InboundTransfersModel { this._rejectTransfersOnExpiredQuotes = config.rejectTransfersOnExpiredQuotes; this._allowTransferWithoutQuote = config.allowTransferWithoutQuote; this._reserveNotification = config.reserveNotification; + this._allowDifferentTransferTransactionId = config.allowDifferentTransferTransactionId; this._mojaloopRequests = new MojaloopRequests({ logger: this._logger, @@ -175,7 +176,7 @@ class InboundTransfersModel { response.expiration = new Date(expiration).toISOString(); } - // project our internal quote reponse into mojaloop quote response form + // project our internal quote response into mojaloop quote response form const mojaloopResponse = shared.internalQuoteResponseToMojaloop(response); // create our ILP packet and condition and tag them on to our internal quote response @@ -193,7 +194,7 @@ class InboundTransfersModel { fulfilment: fulfilment }); - // now store the quoteRespnse data against the quoteId in our cache to be sent as a response to GET /quotes/{ID} + // now store the quoteResponse data against the quoteId in our cache to be sent as a response to GET /quotes/{ID} await this._cache.set(`quoteResponse_${quoteRequest.quoteId}`, mojaloopResponse); // make a callback to the source fsp with the quote response @@ -209,21 +210,21 @@ class InboundTransfersModel { } /** - * This is executed as when GET /quotes/{ID} request is made to get the response of a previous POST /quotes request. + * This is executed as when GET /quotes/{ID} request is made to get the response of a previous POST /quotes request. * Gets the quoteResponse from the cache and makes a callback to the originator with result */ async getQuoteRequest(quoteId, sourceFspId) { try { // Get the quoteRespnse data for the quoteId from the cache to be sent as a response to GET /quotes/{ID} const quoteResponse = await this._cache.get(`quoteResponse_${quoteId}`); - + // If no quoteResponse is found in the cache, make an error callback to the source fsp if (!quoteResponse) { const err = new Error('Quote Id not found'); const mojaloopError = await this._handleError(err, Errors.MojaloopApiErrorCodes.QUOTE_ID_NOT_FOUND); this._logger.push({ mojaloopError }).log(`Sending error response to ${sourceFspId}`); return await this._mojaloopRequests.putQuotesError(quoteId, - mojaloopError, sourceFspId); + mojaloopError, sourceFspId); } // Make a PUT /quotes/{ID} callback to the source fsp with the quote response return this._mojaloopRequests.putQuotes(quoteId, quoteResponse, sourceFspId); @@ -270,14 +271,20 @@ class InboundTransfersModel { /** - * Validates an incoming transfer prepare request and makes a callback to the originator with + * Validates an incoming transfer prepare request and makes a callback to the originator with * the result */ async prepareTransfer(prepareRequest, sourceFspId) { try { - // retrieve our quote data - const quote = await this._cache.get(`quote_${prepareRequest.transferId}`); + let quote; + + if (this._allowDifferentTransferTransactionId) { + const transactionId = this._ilp.getTransactionObject(prepareRequest.ilpPacket).transactionId; + quote = await this._cache.get(`quote_${transactionId}`); + } else { + quote = await this._cache.get(`quote_${prepareRequest.transferId}`); + } if(!quote) { // Check whether to allow transfers without a previous quote. @@ -703,7 +710,7 @@ class InboundTransfersModel { } /** - * Forwards Switch notification for fulfiled transfer to the DFSP backend, when acting as a payee + * Forwards Switch notification for fulfiled transfer to the DFSP backend, when acting as a payee */ async sendNotificationToPayee(body, transferId) { try { diff --git a/src/package-lock.json b/src/package-lock.json index 239dd3d50..8116c70ba 100644 --- a/src/package-lock.json +++ b/src/package-lock.json @@ -6815,9 +6815,9 @@ "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" }, "follow-redirects": { - "version": "1.14.4", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.4.tgz", - "integrity": "sha512-zwGkiSXC1MUJG/qmeIFH2HBJx9u0V46QGUe3YR1fXG8bXQxq7fLj0RjLZQ5nubr9qNJUZrH+xUcwXEoXNpfS+g==" + "version": "1.14.8", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.8.tgz", + "integrity": "sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA==" }, "foreach": { "version": "2.0.5", @@ -11799,9 +11799,33 @@ } }, "node-fetch": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", - "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "requires": { + "whatwg-url": "^5.0.0" + }, + "dependencies": { + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + } + } }, "node-fetch-h2": { "version": "2.3.0", diff --git a/src/test/unit/lib/model/InboundTransfersModel.test.js b/src/test/unit/lib/model/InboundTransfersModel.test.js index 1fe7a4756..663640a5b 100644 --- a/src/test/unit/lib/model/InboundTransfersModel.test.js +++ b/src/test/unit/lib/model/InboundTransfersModel.test.js @@ -21,6 +21,7 @@ const mockTxnReqquestsArguments = require('./data/mockTxnRequestsArguments'); const { MojaloopRequests, Ilp, Logger } = require('@mojaloop/sdk-standard-components'); const { BackendRequests, HTTPResponseError } = require('../../../../lib/model/lib/requests'); const Cache = require('../../../../lib/cache'); +const shared = require('../../../../lib/model/lib/shared'); const getTransfersBackendResponse = require('./data/getTransfersBackendResponse'); const getTransfersMojaloopResponse = require('./data/getTransfersMojaloopResponse'); @@ -232,9 +233,9 @@ describe('inboundModel', () => { test('calls `mojaloopRequests.putAuthorizations` with the expected arguments.', async () => { await model.getAuthorizations('123456', mockTxnReqArgs.fspId); - + expect(MojaloopRequests.__putAuthorizations).toHaveBeenCalledTimes(1); - + }); @@ -406,6 +407,43 @@ describe('inboundModel', () => { expect(BackendRequests.__postTransfers).toHaveBeenCalledTimes(1); expect(MojaloopRequests.__putTransfers).toHaveBeenCalledTimes(1); }); + + test('allow different transfer and transaction id', async () => { + const transactionId = 'mockTransactionId'; + const TRANSFER_ID = 'transfer-id'; + shared.mojaloopPrepareToInternalTransfer = jest.fn().mockReturnValueOnce({}); + + cache.set(`quote_${transactionId}`, { + fulfilment: '', + mojaloopResponse: { + response: '' + } + }); + + const args = { + transferId: TRANSFER_ID, + amount: { + currency: 'USD', + amount: 20.13 + }, + ilpPacket: 'mockIlpPacket', + condition: 'mockGeneratedCondition' + }; + + const model = new Model({ + ...config, + cache, + logger, + allowDifferentTransferTransactionId: true, + checkIlp: false, + }); + + await model.prepareTransfer(args, mockArgs.fspId); + + expect(MojaloopRequests.__putTransfersError).toHaveBeenCalledTimes(0); + expect(BackendRequests.__postTransfers).toHaveBeenCalledTimes(1); + expect(MojaloopRequests.__putTransfers).toHaveBeenCalledTimes(1); + }); }); describe('prepareBulkTransfer:', () => { @@ -574,7 +612,7 @@ describe('inboundModel', () => { } ] }; - + const model = new Model({ ...config, cache, @@ -595,7 +633,7 @@ describe('inboundModel', () => { const transferId = '1234'; let cache; - beforeEach(async () => { + beforeEach(async () => { cache = new Cache({ host: 'dummycachehost', port: 1234,