From 647b96646773e581511304aa226fff4c447a181e Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Tue, 18 Aug 2020 11:47:40 +0100 Subject: [PATCH] #1547: Fail transfer fulfill with "RESERVED" state and v1.0 content-type (#773) * Update dependencies * Bump version * Fix integration tests --- .ncurc.json | 1 + package-lock.json | 111 ++++++++++++------- package.json | 8 +- src/handlers/positions/handler.js | 8 -- src/handlers/transfers/handler.js | 12 ++ test/integration/handlers/handlers.test.js | 6 +- test/unit/handlers/positions/handler.test.js | 26 ----- test/unit/handlers/transfers/handler.test.js | 22 +++- 8 files changed, 111 insertions(+), 83 deletions(-) diff --git a/.ncurc.json b/.ncurc.json index eb3c8d246..4bee8b77e 100644 --- a/.ncurc.json +++ b/.ncurc.json @@ -1,5 +1,6 @@ { "reject": [ + "hapi-swagger", "tape", "ilp-packet" ] diff --git a/package-lock.json b/package-lock.json index 2fb6df382..fc3f9bc91 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@mojaloop/central-ledger", - "version": "11.1.2", + "version": "11.1.3", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -848,6 +848,11 @@ "uuid": "^7.0.3", "v8flags": "^3.2.0" } + }, + "lodash": { + "version": "4.17.19", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", + "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==" } } }, @@ -858,6 +863,13 @@ "requires": { "@mojaloop/sdk-standard-components": "10.3.2", "lodash": "4.17.19" + }, + "dependencies": { + "lodash": { + "version": "4.17.19", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", + "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==" + } } }, "@mojaloop/central-services-health": { @@ -919,6 +931,13 @@ "mustache": "4.0.1", "openapi-backend": "3.5.1", "raw-body": "2.4.1" + }, + "dependencies": { + "lodash": { + "version": "4.17.19", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", + "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==" + } } } } @@ -964,6 +983,13 @@ "openapi-backend": "3.5.1", "raw-body": "2.4.1", "uuid4": "2.0.2" + }, + "dependencies": { + "lodash": { + "version": "4.17.19", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", + "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==" + } } }, "@mojaloop/central-services-stream": { @@ -1001,6 +1027,11 @@ "winston": "3.3.3" }, "dependencies": { + "lodash": { + "version": "4.17.19", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", + "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==" + }, "sinon": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/sinon/-/sinon-9.0.2.tgz", @@ -1747,9 +1778,9 @@ "dev": true }, "aws4": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.10.0.tgz", - "integrity": "sha512-3YDiu347mtVtjpyV3u5kVqQLP242c06zwDOgpeRnybmXlYYsLbtTrUBUm8i8srONt+FWobl5aibnU1030PeeuA==", + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.10.1.tgz", + "integrity": "sha512-zg7Hz2k5lI8kb7U32998pRRFin7zJlkfezGJjUc2heaD4Pw2wObakCDVzkKztTm/Ln7eiVvYsjqak0Ed4LkMDA==", "dev": true }, "axios": { @@ -2081,9 +2112,9 @@ } }, "minizlib": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.0.tgz", - "integrity": "sha512-EzTZN/fjSvifSX0SlqUERCN39o6T40AMarPbv0MrarSFtIITCBh7bi+dU8nxGFHuqs9jdIAeoYoKuQAAASsPPA==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", "dev": true, "requires": { "minipass": "^3.0.0", @@ -2100,15 +2131,15 @@ } }, "tar": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.0.2.tgz", - "integrity": "sha512-Glo3jkRtPcvpDlAs/0+hozav78yoXKFr+c4wgw62NNMO3oo4AaJdCo21Uu7lcwr55h39W2XD1LMERc64wtbItg==", + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.0.5.tgz", + "integrity": "sha512-0b4HOimQHj9nXNEAA7zWwMM91Zhhba3pspja6sQbgTpynOJf+bkjBnfybNYzbpLbnwXnbyB4LOREvlyXLkCHSg==", "dev": true, "requires": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", "minipass": "^3.0.0", - "minizlib": "^2.1.0", + "minizlib": "^2.1.1", "mkdirp": "^1.0.3", "yallist": "^4.0.0" } @@ -5490,9 +5521,9 @@ } }, "lodash": { - "version": "4.17.19", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", - "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==" + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" }, "lodash.camelcase": { "version": "4.3.0", @@ -5865,9 +5896,9 @@ } }, "minizlib": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.0.tgz", - "integrity": "sha512-EzTZN/fjSvifSX0SlqUERCN39o6T40AMarPbv0MrarSFtIITCBh7bi+dU8nxGFHuqs9jdIAeoYoKuQAAASsPPA==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", "dev": true, "requires": { "minipass": "^3.0.0", @@ -6061,13 +6092,13 @@ } }, "mongoose": { - "version": "5.9.28", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.9.28.tgz", - "integrity": "sha512-A8lNRk4eCQDzk+DagSMYdH94LAYrbTK83LgrUlzqdig3YXvizW3DApJqOWQ5DdhuimvsfiD0Z5NTVzXl/rgi2w==", + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.10.0.tgz", + "integrity": "sha512-5itAvBMVDG4+zTDtuLg/IyoTxEMgvpOSHnigQ9Cyh8LR4BEgMAChJj7JSaGkg+tr1AjCSY9DgSdU8bHqCOoxXg==", "requires": { "bson": "^1.1.4", "kareem": "2.3.1", - "mongodb": "3.5.10", + "mongodb": "3.6.0", "mongoose-legacy-pluralize": "1.0.2", "mpath": "0.7.0", "mquery": "3.2.2", @@ -6079,9 +6110,9 @@ }, "dependencies": { "mongodb": { - "version": "3.5.10", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.5.10.tgz", - "integrity": "sha512-p/C48UvTU/dr/PQEDKfb9DsCVDJWXGmdJNFC+u5FPmTQVtog69X6D8vrWHz+sJx1zJnd96sjdh9ueo7bx2ILTw==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.6.0.tgz", + "integrity": "sha512-/XWWub1mHZVoqEsUppE0GV7u9kanLvHxho6EvBxQbShXTKYF9trhZC2NzbulRGeG7xMJHD8IOWRcdKx5LPjAjQ==", "requires": { "bl": "^2.2.0", "bson": "^1.1.4", @@ -6391,9 +6422,9 @@ } }, "npm-check-updates": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/npm-check-updates/-/npm-check-updates-7.1.0.tgz", - "integrity": "sha512-xxEObkGnT361LY57GY/+FQUgCARoAeRfIU5M2GxnESoZFMeDv1W7Ck01MBswyTEQgjD4A3HBolcBTYaJsSl03A==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/npm-check-updates/-/npm-check-updates-7.1.1.tgz", + "integrity": "sha512-mTth44/DK4EmTecdyqEzt6C76sSYdCnDrOo39lNcN1/JWOEkcb+uLQ2CRt0gqkCp6DohALs4RpVBVp+E2i+h8Q==", "dev": true, "requires": { "chalk": "^4.1.0", @@ -6623,9 +6654,9 @@ } }, "minizlib": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.0.tgz", - "integrity": "sha512-EzTZN/fjSvifSX0SlqUERCN39o6T40AMarPbv0MrarSFtIITCBh7bi+dU8nxGFHuqs9jdIAeoYoKuQAAASsPPA==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", "dev": true, "requires": { "minipass": "^3.0.0", @@ -7403,9 +7434,9 @@ } }, "minizlib": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.0.tgz", - "integrity": "sha512-EzTZN/fjSvifSX0SlqUERCN39o6T40AMarPbv0MrarSFtIITCBh7bi+dU8nxGFHuqs9jdIAeoYoKuQAAASsPPA==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", "dev": true, "requires": { "minipass": "^3.0.0", @@ -7434,15 +7465,15 @@ } }, "tar": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.0.2.tgz", - "integrity": "sha512-Glo3jkRtPcvpDlAs/0+hozav78yoXKFr+c4wgw62NNMO3oo4AaJdCo21Uu7lcwr55h39W2XD1LMERc64wtbItg==", + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.0.5.tgz", + "integrity": "sha512-0b4HOimQHj9nXNEAA7zWwMM91Zhhba3pspja6sQbgTpynOJf+bkjBnfybNYzbpLbnwXnbyB4LOREvlyXLkCHSg==", "dev": true, "requires": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", "minipass": "^3.0.0", - "minizlib": "^2.1.0", + "minizlib": "^2.1.1", "mkdirp": "^1.0.3", "yallist": "^4.0.0" } @@ -9503,9 +9534,9 @@ "integrity": "sha1-cAcEaNbSl3ylI3suUZyn0Gouo/0=" }, "swagger-ui-dist": { - "version": "3.31.1", - "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.31.1.tgz", - "integrity": "sha512-+IuIxXX8grZcDVLaC12WCGy62iHJ2v8kTptU4H4EgY/ue6tKeMu/jzIAs+pLFOuYwfG4+VQ+CrC9UeHR9oNKBw==" + "version": "3.32.1", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.32.1.tgz", + "integrity": "sha512-CaIKxDo91McgoesukS5v3nwQ8iur0MQmwNvJ+bPeyd8sOtoiNyXp55ZjO4pXewBdFHD0f9PvGovf2m5x/1typA==" }, "taffydb": { "version": "2.6.2", diff --git a/package.json b/package.json index 09fc3b937..13c982abe 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@mojaloop/central-ledger", - "version": "11.1.2", + "version": "11.1.3", "description": "Central ledger hosted by a scheme to record and settle transfers", "license": "Apache-2.0", "author": "ModusBox", @@ -107,9 +107,9 @@ "hapi-swagger": "13.1.0", "ilp-packet": "2.2.0", "knex": "0.21.4", - "lodash": "4.17.19", + "lodash": "4.17.20", "moment": "2.27.0", - "mongoose": "5.9.28", + "mongoose": "5.10.0", "npm-run-all": "4.1.5", "rc": "1.2.8", "require-glob": "3.2.0", @@ -126,7 +126,7 @@ "jsonpath": "1.0.2", "nodemon": "2.0.4", "npm-audit-resolver": "2.2.1", - "npm-check-updates": "7.1.0", + "npm-check-updates": "7.1.1", "nyc": "15.1.0", "pre-commit": "1.2.2", "proxyquire": "2.1.3", diff --git a/src/handlers/positions/handler.js b/src/handlers/positions/handler.js index 099861fa6..13bb351a6 100644 --- a/src/handlers/positions/handler.js +++ b/src/handlers/positions/handler.js @@ -175,14 +175,6 @@ const positions = async (error, messages) => { } await PositionService.changeParticipantPosition(transferInfo.participantCurrencyId, isReversal, transferInfo.amount, transferStateChange) if (action === Enum.Events.Event.Action.RESERVE) { - if (message.value.content.headers['content-type'].split('=')[1] === '1.0') { - Logger.isInfoEnabled && Logger.info(Utility.breadcrumb(location, `reserve--v1.0--${actionLetter}4.1`)) - const errorMessage = 'action "RESERVE" is not allowed into position handler for v1.0 clients.' - Logger.isErrorEnabled && Logger.error(errorMessage) - !!span && span.error(errorMessage) - histTimerEnd({ success: true, fspId: Config.INSTRUMENTATION_METRICS_LABELS.fspId }) - return true - } const transfer = await TransferService.getById(transferInfo.transferId) message.value.content.payload = TransferObjectTransform.toFulfil(transfer) } diff --git a/src/handlers/transfers/handler.js b/src/handlers/transfers/handler.js index 682073485..c350b2586 100644 --- a/src/handlers/transfers/handler.js +++ b/src/handlers/transfers/handler.js @@ -292,6 +292,18 @@ const fulfil = async (error, messages) => { const params = { message, kafkaTopic, decodedPayload: payload, span, consumer: Consumer, producer: Producer } Logger.isInfoEnabled && Logger.info(Util.breadcrumb(location, { path: 'getById' })) + + // We fail early and silently to allow timeout handler abort transfer + // if 'RESERVED' transfer state is sent in with v1.0 content-type + if (headers['content-type'].split('=')[1] === '1.0' && payload.transferState === TransferState.RESERVED) { + Logger.isInfoEnabled && Logger.info(Util.breadcrumb(location, `failSilentlyforReservedStateWith1.0ContentType--${actionLetter}0`)) + const errorMessage = 'action "RESERVE" is not allowed in fulfil handler for v1.0 clients.' + Logger.isErrorEnabled && Logger.error(errorMessage) + !!span && span.error(errorMessage) + histTimerEnd({ success: true, fspId: Config.INSTRUMENTATION_METRICS_LABELS.fspId }) + return true + } + const transfer = await TransferService.getById(transferId) const transferStateEnum = transfer && transfer.transferStateEnumeration diff --git a/test/integration/handlers/handlers.test.js b/test/integration/handlers/handlers.test.js index dba70e02c..649ceef9d 100644 --- a/test/integration/handlers/handlers.test.js +++ b/test/integration/handlers/handlers.test.js @@ -143,11 +143,13 @@ const prepareTestData = async (dataObj) => { const prepareHeaders = { 'fspiop-source': payer.participant.name, - 'fspiop-destination': payee.participant.name + 'fspiop-destination': payee.participant.name, + 'content-type': 'application/vnd.interoperability.transfers+json;version=1.1' } const fulfilAbortRejectHeaders = { 'fspiop-source': payee.participant.name, - 'fspiop-destination': payer.participant.name + 'fspiop-destination': payer.participant.name, + 'content-type': 'application/vnd.interoperability.transfers+json;version=1.1' } const fulfilPayload = { diff --git a/test/unit/handlers/positions/handler.test.js b/test/unit/handlers/positions/handler.test.js index 4f62b646c..d6ee8c8d0 100644 --- a/test/unit/handlers/positions/handler.test.js +++ b/test/unit/handlers/positions/handler.test.js @@ -304,32 +304,6 @@ Test('Position handler', transferHandlerTest => { } }) - positionsTest.test('log error when RESERVED transfer state is received from v1.0 clients', async (test) => { - const isIncrease = false - const transferStateChange = { - transferId: transferInfo.transferId, - transferStateId: TransferState.RESERVED - } - - await Consumer.createHandler(topicName, config, command) - Kafka.transformGeneralTopicName.returns(topicName) - Kafka.getKafkaConfig.returns(config) - - const m = Object.assign({}, MainUtil.clone(messages[1])) - TransferService.getTransferInfoToChangePosition.withArgs(m.value.content.uriParams.id, Enum.Accounts.TransferParticipantRoleType.PAYEE_DFSP, Enum.Accounts.LedgerEntryType.PRINCIPLE_VALUE).returns(transferInfo) - TransferStateChange.saveTransferStateChange.resolves(true) - PositionService.changeParticipantPosition.withArgs(transferInfo.participantCurrencyId, isIncrease, transferInfo.amount, transferStateChange).resolves(true) - m.value.metadata.event.action = transferEventAction.RESERVE - m.value.content.headers = { 'content-type': 'application/vnd.interoperability.transfers+json;version=1.0' } - Kafka.proceed.returns(true) - - const result = await allTransferHandlers.positions(null, m) - Logger.info(result) - test.ok(SpanStub.error.calledWith('action "RESERVE" is not allowed into position handler for v1.0 clients.')) - test.equal(result, true) - test.end() - }) - positionsTest.end() }) diff --git a/test/unit/handlers/transfers/handler.test.js b/test/unit/handlers/transfers/handler.test.js index 3125c6495..bb2d50623 100644 --- a/test/unit/handlers/transfers/handler.test.js +++ b/test/unit/handlers/transfers/handler.test.js @@ -131,7 +131,7 @@ const messageProtocol = { to: transfer.payeeFsp, type: 'application/json', content: { - headers: { 'fspiop-destination': transfer.payerFsp }, + headers: { 'fspiop-destination': transfer.payerFsp, 'content-type': 'application/vnd.interoperability.transfers+json;version=1.1' }, uriParams: { id: transfer.transferId }, payload: transfer }, @@ -177,7 +177,8 @@ const fulfilMessages = [ uriParams: { id: messageProtocol.content.uriParams.id }, headers: { 'fspiop-source': 'dfsp1', - 'fspiop-destination': 'dfsp2' + 'fspiop-destination': 'dfsp2', + 'content-type': 'application/vnd.interoperability.transfers+json;version=1.1' } }, metadata: { @@ -196,7 +197,8 @@ const fulfilMessages = [ uriParams: { id: messageProtocolBulkCommit.content.uriParams.id }, headers: { 'fspiop-source': 'dfsp1', - 'fspiop-destination': 'dfsp2' + 'fspiop-destination': 'dfsp2', + 'content-type': 'application/vnd.interoperability.transfers+json;version=1.1' } }, metadata: { @@ -957,6 +959,20 @@ Test('Transfer handler', transferHandlerTest => { }) transferHandlerTest.test('fulfil should', fulfilTest => { + fulfilTest.test('fail validation when when RESERVED transfer state is received from v1.0 clients', async (test) => { + const localfulfilMessages = MainUtil.clone(fulfilMessages) + localfulfilMessages[0].value.content.headers['content-type'] = 'application/vnd.interoperability.transfers+json;version=1.0' + localfulfilMessages[0].value.content.payload.transferState = 'RESERVED' + await Consumer.createHandler(topicName, config, command) + Kafka.transformGeneralTopicName.returns(topicName) + TransferService.getById.returns(Promise.resolve(null)) + Kafka.proceed.returns(true) + + const result = await allTransferHandlers.fulfil(null, localfulfilMessages) + test.equal(result, true) + test.end() + }) + fulfilTest.test('fail validation when invalid event action is provided', async (test) => { const localfulfilMessages = MainUtil.clone(fulfilMessages) await Consumer.createHandler(topicName, config, command)