@mojaloop/central-services-shared 14.0.0 → 15.2.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/CHANGELOG.md CHANGED
@@ -2,6 +2,38 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
+ ## [15.2.0](https://github.com/mojaloop/central-services-shared/compare/v15.1.0...v15.2.0) (2021-12-13)
6
+
7
+
8
+ ### Features
9
+
10
+ * **mojaloop/#2608:** injected resource versions config for outbound requests ([#319](https://github.com/mojaloop/central-services-shared/issues/319)) ([13a3d9d](https://github.com/mojaloop/central-services-shared/commit/13a3d9dc8ab8d4815db2aea22563317e3670a19b)), closes [mojaloop/#2608](https://github.com/mojaloop/central-services-shared/issues/2608)
11
+
12
+ ## [15.1.0](https://github.com/mojaloop/central-services-shared/compare/v15.0.1...v15.1.0) (2021-11-17)
13
+
14
+
15
+ ### Features
16
+
17
+ * add a new action enum for `RESERVED_ABORTED` ([#317](https://github.com/mojaloop/central-services-shared/issues/317)) ([0e743b8](https://github.com/mojaloop/central-services-shared/commit/0e743b82a90dd0c97ce3c72454621c191d2a6675))
18
+
19
+ ### [15.0.1](https://github.com/mojaloop/central-services-shared/compare/v15.0.0...v15.0.1) (2021-11-08)
20
+
21
+
22
+ ### Bug Fixes
23
+
24
+ * **#2557:** error notification to payer fsp, header for source having wrong value ([#316](https://github.com/mojaloop/central-services-shared/issues/316)) ([d4b95b6](https://github.com/mojaloop/central-services-shared/commit/d4b95b619ce2c4f810ae6909859ef6dbf5894ad0)), closes [#2557](https://github.com/mojaloop/central-services-shared/issues/2557)
25
+
26
+ ## [15.0.0](https://github.com/mojaloop/central-services-shared/compare/v14.0.0...v15.0.0) (2021-10-18)
27
+
28
+
29
+ ### ⚠ BREAKING CHANGES
30
+
31
+ * **mojaloop/#2536:** split options config for supportedProtocolVersions to supportedProtocolVersions & supportedProtocolAcceptVersions of HeaderValidation Hapi Plugin. It should be backward compatible, but forcing a major version bump to reflect this new functionality.
32
+
33
+ ### Bug Fixes
34
+
35
+ * **mojaloop/#2536:** fspiop api version negotiation not handled by transfers service ([#315](https://github.com/mojaloop/central-services-shared/issues/315)) ([e3a8748](https://github.com/mojaloop/central-services-shared/commit/e3a874829794ed8b85b6487dd58bcb58f31a5dd1)), closes [mojaloop/#2536](https://github.com/mojaloop/central-services-shared/issues/2536) [mojaloop/#2536](https://github.com/mojaloop/central-services-shared/issues/2536)
36
+
5
37
  ## [14.0.0](https://github.com/mojaloop/central-services-shared/compare/v13.4.1...v14.0.0) (2021-09-10)
6
38
 
7
39
 
@@ -1,41 +1,84 @@
1
1
  {
2
2
  "decisions": {
3
- "1500|widdershins>yargs>yargs-parser": {
3
+ "1002401|widdershins>yargs>string-width>strip-ansi>ansi-regex": {
4
4
  "decision": "ignore",
5
- "madeAt": 1629893593728,
6
- "expiresAt": 1632485586783
5
+ "madeAt": 1636372052422,
6
+ "expiresAt": 1638964045850
7
7
  },
8
- "1640|widdershins>urijs": {
9
- "decision": "fix",
10
- "madeAt": 1615187901100
8
+ "1002401|widdershins>yargs>cliui>string-width>strip-ansi>ansi-regex": {
9
+ "decision": "ignore",
10
+ "madeAt": 1636372052422,
11
+ "expiresAt": 1638964045850
12
+ },
13
+ "1002865|shins>sanitize-html": {
14
+ "decision": "ignore",
15
+ "madeAt": 1636372053964,
16
+ "expiresAt": 1638964045850
17
+ },
18
+ "1002866|shins>sanitize-html": {
19
+ "decision": "ignore",
20
+ "madeAt": 1636372053964,
21
+ "expiresAt": 1638964045850
11
22
  },
12
- "1654|@mojaloop/event-sdk>grpc>protobufjs>yargs>y18n": {
13
- "decision": "fix",
14
- "madeAt": 1620391401423
23
+ "1003019|widdershins>yargs>yargs-parser": {
24
+ "decision": "ignore",
25
+ "madeAt": 1636372055094,
26
+ "expiresAt": 1638964045850
27
+ },
28
+ "1004809|widdershins>openapi-sampler>json-pointer": {
29
+ "decision": "ignore",
30
+ "madeAt": 1636956486789,
31
+ "expiresAt": 1639548471465
32
+ },
33
+ "1004812|widdershins>swagger2openapi>better-ajv-errors>jsonpointer": {
34
+ "decision": "ignore",
35
+ "madeAt": 1636956491233,
36
+ "expiresAt": 1639548471465
15
37
  },
16
- "1654|widdershins>yargs>y18n": {
17
- "decision": "fix",
18
- "madeAt": 1620391421241
38
+ "1004812|widdershins>swagger2openapi>oas-validator>better-ajv-errors>jsonpointer": {
39
+ "decision": "ignore",
40
+ "madeAt": 1636956491233,
41
+ "expiresAt": 1639548471465
42
+ },
43
+ "1004854|widdershins>openapi-sampler>json-pointer": {
44
+ "decision": "ignore",
45
+ "madeAt": 1637925238134,
46
+ "expiresAt": 1640517233443
47
+ },
48
+ "1004869|widdershins>swagger2openapi>better-ajv-errors>jsonpointer": {
49
+ "decision": "ignore",
50
+ "madeAt": 1637925239670,
51
+ "expiresAt": 1640517233443
52
+ },
53
+ "1004869|widdershins>swagger2openapi>oas-validator>better-ajv-errors>jsonpointer": {
54
+ "decision": "ignore",
55
+ "madeAt": 1637925239670,
56
+ "expiresAt": 1640517233443
57
+ },
58
+ "1004946|widdershins>yargs>string-width>strip-ansi>ansi-regex": {
59
+ "decision": "ignore",
60
+ "madeAt": 1637925241156,
61
+ "expiresAt": 1640517233443
19
62
  },
20
- "1673|@mojaloop/event-sdk>lodash": {
63
+ "1004946|widdershins>yargs>cliui>string-width>strip-ansi>ansi-regex": {
21
64
  "decision": "ignore",
22
- "madeAt": 1620357960127,
23
- "expiresAt": 1622949946141
65
+ "madeAt": 1637925241156,
66
+ "expiresAt": 1640517233443
24
67
  },
25
- "1675|shins>sanitize-html": {
68
+ "1005383|shins>sanitize-html": {
26
69
  "decision": "ignore",
27
- "madeAt": 1629893597598,
28
- "expiresAt": 1632485586783
70
+ "madeAt": 1637925243300,
71
+ "expiresAt": 1640517233443
29
72
  },
30
- "1676|shins>sanitize-html": {
73
+ "1005384|shins>sanitize-html": {
31
74
  "decision": "ignore",
32
- "madeAt": 1629893597598,
33
- "expiresAt": 1632485586783
75
+ "madeAt": 1637925243300,
76
+ "expiresAt": 1640517233443
34
77
  },
35
- "1693|shins>sanitize-html>postcss": {
78
+ "1005534|widdershins>yargs>yargs-parser": {
36
79
  "decision": "ignore",
37
- "madeAt": 1623756002194,
38
- "expiresAt": 1626347994107
80
+ "madeAt": 1637925244682,
81
+ "expiresAt": 1640517233443
39
82
  }
40
83
  },
41
84
  "rules": {},
package/package.json CHANGED
@@ -1,18 +1,18 @@
1
1
  {
2
2
  "name": "@mojaloop/central-services-shared",
3
- "version": "14.0.0",
3
+ "version": "15.2.0",
4
4
  "description": "Shared code for mojaloop central services",
5
5
  "main": "src/index.js",
6
6
  "types": "src/index.d.ts",
7
7
  "scripts": {
8
- "test": "npm run test:unit | npx faucet",
9
8
  "pretest": "standard",
10
9
  "standard": "standard",
11
10
  "standard:fix": "standard --fix",
12
11
  "lint": "npm run standard",
13
12
  "lint:fix": "npm run standard:fix",
14
- "test:unit": "tapes 'test/unit/**/**.test.js'",
15
- "test:xunit": "npm run test:unit | tap-xunit",
13
+ "test": "npm run test:unit",
14
+ "test:unit": "tape 'test/unit/**/*.test.js' | tap-spec",
15
+ "test:xunit": "tape 'test/unit/**/*.test.js' | tap-xunit",
16
16
  "test:coverage": "nyc --reporter=lcov --reporter=text-summary tapes -- 'test/unit/**/**.test.js'",
17
17
  "test:coverage-check": "npm run test:coverage && nyc check-coverage",
18
18
  "audit:resolve": "SHELL=sh resolve-audit --production",
@@ -48,16 +48,16 @@
48
48
  "dependencies": {
49
49
  "@hapi/catbox": "11.1.1",
50
50
  "@hapi/catbox-memory": "5.0.1",
51
- "axios": "0.21.4",
51
+ "axios": "0.24.0",
52
52
  "clone": "2.1.2",
53
53
  "dotenv": "10.0.0",
54
- "env-var": "7.0.1",
54
+ "env-var": "7.1.1",
55
55
  "event-stream": "4.0.1",
56
- "immutable": "3.8.2",
56
+ "immutable": "4.0.0",
57
57
  "lodash": "4.17.21",
58
58
  "mustache": "4.2.0",
59
- "openapi-backend": "4.2.0",
60
- "raw-body": "2.4.1",
59
+ "openapi-backend": "5.0.1",
60
+ "raw-body": "2.4.2",
61
61
  "rc": "1.2.8",
62
62
  "shins": "2.6.0",
63
63
  "uuid4": "2.0.2",
@@ -65,38 +65,38 @@
65
65
  "yaml": "1.10.2"
66
66
  },
67
67
  "devDependencies": {
68
- "@hapi/hapi": "20.1.5",
68
+ "@hapi/hapi": "20.2.1",
69
69
  "@hapi/joi": "17.1.1",
70
70
  "@mojaloop/central-services-error-handling": "11.3.0",
71
- "@mojaloop/central-services-logger": "10.6.1",
71
+ "@mojaloop/central-services-logger": "10.6.2",
72
72
  "@mojaloop/central-services-metrics": "11.0.0",
73
73
  "@mojaloop/event-sdk": "10.7.1",
74
74
  "@mojaloop/sdk-standard-components": "10.3.2",
75
- "ajv": "8.6.2",
76
- "ajv-keywords": "5.0.0",
75
+ "ajv": "8.8.2",
76
+ "ajv-keywords": "5.1.0",
77
77
  "base64url": "3.0.1",
78
78
  "chance": "1.1.8",
79
- "faucet": "0.0.1",
80
79
  "npm-audit-resolver": "2.3.1",
81
- "npm-check-updates": "11.8.5",
80
+ "npm-check-updates": "12.0.2",
82
81
  "nyc": "15.1.0",
83
82
  "pre-commit": "1.2.2",
84
83
  "proxyquire": "2.1.3",
85
84
  "rewire": "5.0.0",
86
- "sinon": "11.1.2",
87
- "standard": "16.0.3",
88
- "standard-version": "9.3.1",
85
+ "sinon": "12.0.1",
86
+ "standard": "16.0.4",
87
+ "standard-version": "9.3.2",
88
+ "tap-spec": "^5.0.0",
89
89
  "tap-xunit": "2.4.1",
90
- "tape": "5.3.1",
90
+ "tape": "5.3.2",
91
91
  "tapes": "4.1.0"
92
92
  },
93
93
  "peerDependencies": {
94
94
  "@mojaloop/central-services-error-handling": "11.3.0",
95
- "@mojaloop/central-services-logger": "10.6.1",
95
+ "@mojaloop/central-services-logger": "10.6.2",
96
96
  "@mojaloop/central-services-metrics": "11.0.0",
97
97
  "@mojaloop/event-sdk": "10.7.1",
98
- "ajv": "8.6.2",
99
- "ajv-keywords": "5.0.0"
98
+ "ajv": "8.8.2",
99
+ "ajv-keywords": "5.1.0"
100
100
  },
101
101
  "peerDependenciesMeta": {
102
102
  "@mojaloop/central-services-error-handling": {
@@ -60,6 +60,7 @@ const Event = {
60
60
  Action: {
61
61
  ABORT: 'abort',
62
62
  ABORT_DUPLICATE: 'abort-duplicate',
63
+ ABORT_VALIDATION: 'abort-validation',
63
64
  ACCEPT: 'accept',
64
65
  BULK_ABORT: 'bulk-abort',
65
66
  BULK_COMMIT: 'bulk-commit',
@@ -92,6 +93,10 @@ const Event = {
92
93
  RECORD_FUNDS_OUT_PREPARE_RESERVE: 'recordFundsOutPrepareReserve',
93
94
  REJECT: 'reject',
94
95
  RESOLVE: 'resolve',
96
+
97
+ // The Transfer was marked as RESERVED by the payee DFSP
98
+ // and was then aborted by the switch
99
+ RESERVED_ABORTED: 'reserved-aborted',
95
100
  REQUEST: 'request',
96
101
  RESERVE: 'reserve',
97
102
  SETTLEMENT_WINDOW: 'settlement-window',
@@ -90,6 +90,10 @@ const TopicMap = {
90
90
  functionality: transferEventType.NOTIFICATION,
91
91
  action: transferEventAction.EVENT
92
92
  },
93
+ 'abort-validation': {
94
+ functionality: transferEventType.NOTIFICATION,
95
+ action: transferEventAction.EVENT
96
+ },
93
97
  'bulk-abort': {
94
98
  functionality: transferEventType.NOTIFICATION,
95
99
  action: transferEventAction.EVENT
@@ -203,6 +207,10 @@ const TopicMap = {
203
207
  abort: {
204
208
  functionality: transferEventType.TRANSFER,
205
209
  action: transferEventAction.POSITION
210
+ },
211
+ 'abort-validation': {
212
+ functionality: transferEventType.TRANSFER,
213
+ action: transferEventAction.POSITION
206
214
  }
207
215
  },
208
216
  prepare: {
package/src/index.d.ts CHANGED
@@ -260,6 +260,7 @@ declare namespace CentralServicesShared {
260
260
  enum EventActionEnum {
261
261
  ABORT = 'abort',
262
262
  ABORT_DUPLICATE = 'abort-duplicate',
263
+ ABORT_VALIDATION = 'abort-validation',
263
264
  ACCEPT = 'accept',
264
265
  BULK_ABORT = 'bulk-abort',
265
266
  BULK_COMMIT = 'bulk-commit',
@@ -294,6 +295,7 @@ declare namespace CentralServicesShared {
294
295
  RESOLVE = 'resolve',
295
296
  REQUEST = 'request',
296
297
  RESERVE = 'reserve',
298
+ RESERVED_ABORTED = 'reserved-aborted',
297
299
  SETTLEMENT_WINDOW = 'settlement-window',
298
300
  TIMEOUT_RECEIVED = 'timeout-received',
299
301
  TIMEOUT_RESERVED = 'timeout-reserved',
@@ -317,6 +319,7 @@ declare namespace CentralServicesShared {
317
319
  Action: {
318
320
  ABORT: EventActionEnum.ABORT;
319
321
  ABORT_DUPLICATE: EventActionEnum.ABORT_DUPLICATE;
322
+ ABORT_VALIDATION: EventActionEnum.ABORT_VALIDATION;
320
323
  ACCEPT: EventActionEnum.ACCEPT;
321
324
  BULK_ABORT: EventActionEnum.BULK_ABORT;
322
325
  BULK_COMMIT: EventActionEnum.BULK_COMMIT;
@@ -351,6 +354,7 @@ declare namespace CentralServicesShared {
351
354
  RESOLVE: EventActionEnum.RESOLVE;
352
355
  REQUEST: EventActionEnum.REQUEST;
353
356
  RESERVE: EventActionEnum.RESERVE;
357
+ RESERVED_ABORTED: EventActionEnum.RESERVED_ABORTED;
354
358
  SETTLEMENT_WINDOW: EventActionEnum.SETTLEMENT_WINDOW;
355
359
  TIMEOUT_RECEIVED: EventActionEnum.TIMEOUT_RECEIVED;
356
360
  TIMEOUT_RESERVED: EventActionEnum.TIMEOUT_RESERVED;
@@ -399,8 +403,13 @@ declare namespace CentralServicesShared {
399
403
  getEndpointAndRender(switchUrl: string, fsp: string, endpointType: FspEndpointTypesEnum, path: string, options?: any): Promise<string>
400
404
  }
401
405
 
406
+ interface ProtocolVersionsType {
407
+ content: string,
408
+ accept: string
409
+ }
410
+
402
411
  interface Request {
403
- sendRequest(url: string, headers: HapiUtil.Dictionary<string>, source: string, destination: string, method?: RestMethodsEnum, payload?: any, responseType?: string, span?: any, jwsSigner?: any): Promise<any>
412
+ sendRequest(url: string, headers: HapiUtil.Dictionary<string>, source: string, destination: string, method?: RestMethodsEnum, payload?: any, responseType?: string, span?: any, jwsSigner?: any, protocolVersions?: ProtocolVersionsType): Promise<any>
404
413
  }
405
414
 
406
415
  interface Kafka {
@@ -6,7 +6,7 @@
6
6
  // accuracy of this statement has not been thoroughly tested.
7
7
 
8
8
  const { Factory: { createFSPIOPError }, Enums } = require('@mojaloop/central-services-error-handling')
9
- const { parseAcceptHeader, parseContentTypeHeader, protocolVersions, protocolVersionsMap } = require('../../headerValidation')
9
+ const { parseAcceptHeader, parseContentTypeHeader, protocolVersions, convertSupportedVersionToExtensionList } = require('../../headerValidation')
10
10
 
11
11
  // Some defaults
12
12
 
@@ -49,7 +49,8 @@ const plugin = {
49
49
  name: 'fspiop-api-protocol-version-header-validator',
50
50
  register: function (server, /* options: */ {
51
51
  resources = defaultProtocolResources,
52
- supportedProtocolVersions = defaultProtocolVersions
52
+ supportedProtocolContentVersions = defaultProtocolVersions,
53
+ supportedProtocolAcceptVersions = defaultProtocolVersions
53
54
  }) {
54
55
  server.ext('onPostAuth', (request, h) => {
55
56
  // First, extract the resource type from the path
@@ -73,13 +74,14 @@ const plugin = {
73
74
  errorMessages.INVALID_ACCEPT_HEADER
74
75
  )
75
76
  }
76
- if (!supportedProtocolVersions.some(supportedVer => accept.versions.has(supportedVer))) {
77
+ if (!supportedProtocolAcceptVersions.some(supportedVer => accept.versions.has(supportedVer))) {
78
+ const supportedVersionExtensionListMap = convertSupportedVersionToExtensionList(supportedProtocolAcceptVersions)
77
79
  throw createFSPIOPError(
78
80
  Enums.FSPIOPErrorCodes.UNACCEPTABLE_VERSION,
79
81
  errorMessages.REQUESTED_VERSION_NOT_SUPPORTED,
80
82
  null,
81
83
  null,
82
- protocolVersionsMap
84
+ supportedVersionExtensionListMap
83
85
  )
84
86
  }
85
87
  }
@@ -95,13 +97,14 @@ const plugin = {
95
97
  errorMessages.INVALID_CONTENT_TYPE_HEADER
96
98
  )
97
99
  }
98
- if (!supportedProtocolVersions.includes(contentType.version)) {
100
+ if (!supportedProtocolContentVersions.includes(contentType.version)) {
101
+ const supportedVersionExtensionListMap = convertSupportedVersionToExtensionList(supportedProtocolContentVersions)
99
102
  throw createFSPIOPError(
100
103
  Enums.FSPIOPErrorCodes.UNACCEPTABLE_VERSION,
101
104
  errorMessages.SUPPLIED_VERSION_NOT_SUPPORTED,
102
105
  null,
103
106
  null,
104
- protocolVersionsMap
107
+ supportedVersionExtensionListMap
105
108
  )
106
109
  }
107
110
 
@@ -1,9 +1,10 @@
1
1
  'use strict'
2
2
 
3
3
  const assert = require('assert').strict
4
+ const _ = require('lodash')
4
5
 
5
6
  const protocolVersions = {
6
- anyVersion: Symbol('Any version'),
7
+ anyVersion: Symbol('Any'),
7
8
  ONE: ['1', '1.0', '1.1']
8
9
  }
9
10
 
@@ -20,6 +21,9 @@ const generateContentTypeRegex = resource =>
20
21
  const generateAcceptRegex = resource =>
21
22
  new RegExp(`^${generateSingleAcceptRegexStr(resource)}(,${generateSingleAcceptRegexStr(resource)})*$`)
22
23
 
24
+ const generateSingleAcceptRegex = resource =>
25
+ new RegExp(generateSingleAcceptRegexStr(resource))
26
+
23
27
  const generateSingleAcceptRegexStr = resource =>
24
28
  `application/vnd\\.interoperability\\.${resource}\\+json(\\s{0,1};\\s{0,1}version=\\d+(\\.\\d+)?)?`
25
29
 
@@ -55,7 +59,8 @@ const parseAcceptHeader = (resource, header) => {
55
59
  // The header contains a comma-delimited set of versions, extract these
56
60
  const versions = new Set(header
57
61
  .split(',')
58
- .map(verStr => verStr.match(new RegExp(generateSingleAcceptRegexStr(resource)))[1])
62
+ // @ts-ignore
63
+ .map(verStr => verStr.match(generateSingleAcceptRegex(resource))[1])
59
64
  .map(match => match === undefined ? protocolVersions.anyVersion : match.split('=')[1])
60
65
  )
61
66
 
@@ -65,11 +70,31 @@ const parseAcceptHeader = (resource, header) => {
65
70
  }
66
71
  }
67
72
 
73
+ const convertSupportedVersionToExtensionList = (supportedVersions) => {
74
+ const supportedVersionsExtensionListMap = []
75
+ for (const version of supportedVersions) {
76
+ const versionList = version.toString().split('.').filter(num => num !== '')
77
+ if (versionList != null && versionList.length === 2) {
78
+ const versionMap = {}
79
+ versionMap.key = versionList[0]
80
+ versionMap.value = versionList[1]
81
+ supportedVersionsExtensionListMap.push(versionMap)
82
+ } else if (versionList != null && versionList.length === 1 && version !== protocolVersions.anyVersion) {
83
+ const versionMap = {}
84
+ versionMap.key = versionList[0]
85
+ versionMap.value = '0'
86
+ supportedVersionsExtensionListMap.push(versionMap)
87
+ }
88
+ }
89
+ return _.uniqWith(supportedVersionsExtensionListMap, _.isEqual)
90
+ }
91
+
68
92
  module.exports = {
69
93
  protocolVersions,
70
94
  protocolVersionsMap,
71
95
  generateAcceptRegex,
72
96
  generateContentTypeRegex,
73
97
  parseAcceptHeader,
74
- parseContentTypeHeader
98
+ parseContentTypeHeader,
99
+ convertSupportedVersionToExtensionList
75
100
  }
@@ -30,12 +30,36 @@ const ErrorHandler = require('@mojaloop/central-services-error-handling')
30
30
 
31
31
  const resourceVersions = require('../helpers').resourceVersions
32
32
 
33
- const regexForContentAndAcceptHeaders = /(application\/vnd\.interoperability\.)(\w*)+(\+json\s{0,1};\s{0,1}version=)(.*)/
34
-
35
33
  /**
36
34
  * @module src/headers/transformer
37
35
  */
38
36
 
37
+ const regexForContentAndAcceptHeaders = /(application\/vnd\.interoperability\.)(\w*)+(\+json\s{0,1};\s{0,1}version=)(.*)/
38
+
39
+ /**
40
+ * @function getResourceInfoFromHeader
41
+ *
42
+ * @description This will parse either a FSPIOP Content-Type or Accept header and return an object containing the resourceType and applicable version
43
+ *
44
+ * @typedef ResourceInfo
45
+ * @type {object}
46
+ * @property {string} resourceType - resource parsed from the headerValue.
47
+ * @property {string} version - version parsed from the headerValue.
48
+ *
49
+ * @param {string} headerValue - the http header from the request, thus must be either an FSPIOP Content-Type or Accept header.
50
+ *
51
+ * @returns {ResourceInfo} Returns resourceInfo object. If the headerValue was not parsed correctly, an empty object {} will be returned.
52
+ */
53
+ const getResourceInfoFromHeader = (headerValue) => {
54
+ const result = {}
55
+ const regex = regexForContentAndAcceptHeaders.exec(headerValue)
56
+ if (regex) {
57
+ if (regex[2]) result.resourceType = regex[2]
58
+ if (regex[4]) result.version = regex[4]
59
+ }
60
+ return result
61
+ }
62
+
39
63
  /**
40
64
  * @function transformHeaders
41
65
  *
@@ -45,7 +69,31 @@ const regexForContentAndAcceptHeaders = /(application\/vnd\.interoperability\.)(
45
69
  *
46
70
  * see https://nodejs.org/dist/latest-v10.x/docs/api/http.html#http_message_headers
47
71
  *
72
+ * @typedef TransformProtocolVersions
73
+ * @type {object}
74
+ * @property {string} content - protocol version to be used in the ContentType HTTP Header.
75
+ * @property {string} accept - protocol version to be used in the Accept HTTP Header.
76
+ *
77
+ * @typedef TransformHeadersConfig
78
+ * @type {object}
79
+ * @property {string} contentType - HTTP method such as "POST", "PUT", etc.
80
+ * @property {string} accept - Source FSP Identifier.
81
+ * @property {string} destinationFsp - Destination FSP Identifier.
82
+ * @property {TransformProtocolVersions} protocolVersions - Config for Protocol versions to be used.
83
+ *
84
+ * Config supports the following parameters:
85
+ * config: {
86
+ * httpMethod: string,
87
+ * sourceFsp: string,
88
+ * destinationFsp: string,
89
+ * protocolVersions: {
90
+ * content: string,
91
+ * accept: string
92
+ * }
93
+ * }
94
+ *
48
95
  * @param {object} headers - the http header from the request
96
+ * @param {TransformHeadersConfig} headers - the http header from the request
49
97
  *
50
98
  * @returns {object} Returns the normalized headers
51
99
  */
@@ -62,8 +110,15 @@ const transformHeaders = (headers, config) => {
62
110
  const normalizedHeaders = {}
63
111
 
64
112
  // resource type for content-type and accept headers
65
- const getResourceFromHeader = (headerValue) => regexForContentAndAcceptHeaders.exec(headerValue)[2]
66
113
  let resourceType
114
+ let acceptVersion
115
+ let contentVersion
116
+
117
+ // Determine the acceptVersion using the injected config
118
+ if (config && config.protocolVersions && config.protocolVersions.accept) acceptVersion = config.protocolVersions.accept
119
+
120
+ // Determine the contentVersion using the injected config
121
+ if (config && config.protocolVersions && config.protocolVersions.content) contentVersion = config.protocolVersions.content
67
122
 
68
123
  // check to see if FSPIOP-Destination header has been left out of the initial request. If so then add it.
69
124
  if (!normalizedKeys[ENUM.Headers.FSPIOP.DESTINATION]) {
@@ -127,16 +182,20 @@ const transformHeaders = (headers, config) => {
127
182
  normalizedHeaders[headerKey] = headerValue
128
183
  break
129
184
  }
130
- if (!resourceType) resourceType = getResourceFromHeader(headers[headerKey])
131
- normalizedHeaders[headerKey] = `application/vnd.interoperability.${resourceType}+json;version=${resourceVersions[resourceType].acceptVersion}`
185
+ if (!resourceType) resourceType = getResourceInfoFromHeader(headers[headerKey]).resourceType
186
+ // Fall back to using the legacy approach to determine the resourceVersion
187
+ if (resourceType && !acceptVersion) acceptVersion = resourceVersions[resourceType].acceptVersion
188
+ normalizedHeaders[headerKey] = `application/vnd.interoperability.${resourceType}+json;version=${acceptVersion}`
132
189
  break
133
190
  case (ENUM.Headers.GENERAL.CONTENT_TYPE.value):
134
191
  if (!ENUM.Headers.FSPIOP.SWITCH.regex.test(config.sourceFsp)) {
135
192
  normalizedHeaders[headerKey] = headerValue
136
193
  break
137
194
  }
138
- if (!resourceType) resourceType = getResourceFromHeader(headers[headerKey])
139
- normalizedHeaders[headerKey] = `application/vnd.interoperability.${resourceType}+json;version=${resourceVersions[resourceType].contentVersion}`
195
+ if (!resourceType) resourceType = getResourceInfoFromHeader(headers[headerKey]).resourceType
196
+ // Fall back to using the legacy approach to determine the resourceVersion
197
+ if (resourceType && !contentVersion) contentVersion = resourceVersions[resourceType].contentVersion
198
+ normalizedHeaders[headerKey] = `application/vnd.interoperability.${resourceType}+json;version=${contentVersion}`
140
199
  break
141
200
  default:
142
201
  normalizedHeaders[headerKey] = headerValue
@@ -157,5 +216,6 @@ const transformHeaders = (headers, config) => {
157
216
  }
158
217
 
159
218
  module.exports = {
219
+ getResourceInfoFromHeader,
160
220
  transformHeaders
161
221
  }
@@ -43,20 +43,26 @@ delete request.defaults.headers.common.Accept
43
43
  *
44
44
  * @description sends a request to url
45
45
  *
46
+ * @typedef SendRequestProtocolVersions
47
+ * @type {object}
48
+ * @property {string} content - protocol version to be used in the ContentType HTTP Header.
49
+ * @property {string} accept - protocol version to be used in the Accept HTTP Header.
50
+ *
46
51
  * @param {string} url the endpoint for the service you require
47
52
  * @param {object} headers the http headers
48
53
  * @param {string} method http method being requested i.e. GET, POST, PUT
49
54
  * @param {string} source id for which callback is being sent from
50
55
  * @param {string} destination id for which callback is being sent
51
- * @param {object} payload the body of the request being sent
56
+ * @param {object | undefined} payload the body of the request being sent
52
57
  * @param {string} responseType the type of the response object
53
- * @param {object} span a span for event logging if this request is within a span
54
- * @param {object} jwsSigner the jws signer for signing the requests
58
+ * @param {object | undefined} span a span for event logging if this request is within a span
59
+ * @param {object | undefined} jwsSigner the jws signer for signing the requests
60
+ * @param {SendRequestProtocolVersions | undefined} protocolVersions the config for Protocol versions to be used
55
61
  *
56
- *@return {object} The response for the request being sent or error object with response included
62
+ *@return {Promise<any>} The response for the request being sent or error object with response included
57
63
  */
58
64
 
59
- const sendRequest = async (url, headers, source, destination, method = enums.Http.RestMethods.GET, payload = undefined, responseType = enums.Http.ResponseTypes.JSON, span = undefined, jwsSigner = undefined) => {
65
+ const sendRequest = async (url, headers, source, destination, method = enums.Http.RestMethods.GET, payload = undefined, responseType = enums.Http.ResponseTypes.JSON, span = undefined, jwsSigner = undefined, protocolVersions = undefined) => {
60
66
  const histTimerEnd = !!Metrics.isInitiated() && Metrics.getHistogram(
61
67
  'sendRequest',
62
68
  `sending ${method} request to: ${url} from: ${source} to: ${destination}`,
@@ -75,7 +81,8 @@ const sendRequest = async (url, headers, source, destination, method = enums.Htt
75
81
  const transformedHeaders = Headers.transformHeaders(headers, {
76
82
  httpMethod: method,
77
83
  sourceFsp: source,
78
- destinationFsp: destination
84
+ destinationFsp: destination,
85
+ protocolVersions
79
86
  })
80
87
  requestOptions = {
81
88
  url,
@@ -31,6 +31,7 @@ const Sinon = require('sinon')
31
31
  const Transformer = require('../../../src/util').Headers
32
32
  const Enum = require('../../../src/enums')
33
33
  const Util = require('../../../src/util')
34
+ const Helper = require('../../util/helper')
34
35
 
35
36
  const headerConfigExample = {
36
37
  httpMethod: 'PUT',
@@ -67,6 +68,30 @@ Test('Transfer Transformer tests', TransformerTest => {
67
68
  t.end()
68
69
  })
69
70
 
71
+ TransformerTest.test('Transformer.getResourceInfoFromHeaderTest() should', getResourceInfoFromHeaderTest => {
72
+ getResourceInfoFromHeaderTest.test('parse FSPIOP Content-Type example correctly', async test => {
73
+ const result = Transformer.getResourceInfoFromHeader(headerDataInputExample['Content-Type'])
74
+ test.equal(headerDataInputExample['Content-Type'], Helper.generateProtocolHeader(result.resourceType, result.version))
75
+ test.end()
76
+ })
77
+
78
+ getResourceInfoFromHeaderTest.test('return an empty result with standard application/json', async test => {
79
+ const contentType = 'application/json'
80
+ const result = Transformer.getResourceInfoFromHeader(contentType)
81
+ test.same(result, {})
82
+ test.end()
83
+ })
84
+
85
+ getResourceInfoFromHeaderTest.test('return an empty result with incorrect FSPIOP Content-Type input', async test => {
86
+ const contentType = 'application/vnd.interoperability.transfers+json'
87
+ const result = Transformer.getResourceInfoFromHeader(contentType)
88
+ test.same(result, {})
89
+ test.end()
90
+ })
91
+
92
+ getResourceInfoFromHeaderTest.end()
93
+ })
94
+
70
95
  TransformerTest.test('Transformer.transformHeaders() should', transformHeadersTest => {
71
96
  transformHeadersTest.test('Remove all unnecessary fields from Header', async test => {
72
97
  const headerData = Util.clone(headerDataInputExample)
@@ -78,6 +103,58 @@ Test('Transfer Transformer tests', TransformerTest => {
78
103
  test.end()
79
104
  })
80
105
 
106
+ transformHeadersTest.test('Set ContentType && Accept versions via RESOURCE_VERSIONS env variable', async test => {
107
+ const RESOURCE_VERSIONS_BACKUP = process.env.RESOURCE_VERSIONS
108
+ process.env.RESOURCE_VERSIONS = 'transfers=1.1,quotes=1.0'
109
+
110
+ const headerConfig = {
111
+ httpMethod: 'PUT',
112
+ sourceFsp: 'switch',
113
+ destinationFsp: 'FSPDest'
114
+ }
115
+
116
+ const headerData = Util.clone(headerDataInputExample)
117
+ headerData.Accept = Helper.generateProtocolHeader('transfers', '1')
118
+
119
+ const transformedHeaderData = Transformer.transformHeaders(headerData, headerConfig)
120
+
121
+ test.deepEqual(headerData['Content-Type'], transformedHeaderData['Content-Type'])
122
+ test.deepEqual(headerData.Accept, transformedHeaderData.Accept)
123
+ test.end()
124
+ process.env.RESOURCE_VERSIONS = RESOURCE_VERSIONS_BACKUP
125
+ })
126
+
127
+ transformHeadersTest.test('Set ContentType && Accept versions via config', async test => {
128
+ const RESOURCE_VERSIONS_BACKUP = process.env.RESOURCE_VERSIONS
129
+ // we keep this here to make sure it does not override the injected protocolVersions config
130
+ process.env.RESOURCE_VERSIONS = 'transfers=1.0,quotes=1.0'
131
+
132
+ const headerConfig = {
133
+ httpMethod: 'PUT',
134
+ sourceFsp: 'switch',
135
+ destinationFsp: 'FSPDest',
136
+ protocolVersions: {
137
+ content: '1.1',
138
+ accept: '1'
139
+ }
140
+ }
141
+
142
+ const headerData = Util.clone(headerDataInputExample)
143
+ headerData.Accept = Helper.generateProtocolHeader('transfers', '1.1')
144
+
145
+ const resourceInfoFromHeader = Transformer.getResourceInfoFromHeader(headerData['Content-Type'])
146
+
147
+ const transformedHeaderData = Transformer.transformHeaders(headerData, headerConfig)
148
+ const resourceInfoFromTransformedHeader = Transformer.getResourceInfoFromHeader(transformedHeaderData['Content-Type'])
149
+
150
+ test.equal(resourceInfoFromHeader.resourceType, resourceInfoFromTransformedHeader.resourceType)
151
+ test.equal(resourceInfoFromHeader.version, '1.0')
152
+ test.equal(resourceInfoFromTransformedHeader.version, '1.1')
153
+ test.equal(transformedHeaderData.Accept, Helper.generateProtocolHeader('transfers', headerConfig.protocolVersions.accept))
154
+ test.end()
155
+ process.env.RESOURCE_VERSIONS = RESOURCE_VERSIONS_BACKUP
156
+ })
157
+
81
158
  transformHeadersTest.test('Translate Date field into correct format for String value', async test => {
82
159
  const key = 'Date'
83
160
  const val = '2018-09-13T13:52:15.221Z'
@@ -9,7 +9,10 @@ const fs = require('fs')
9
9
  const path = require('path')
10
10
  const {
11
11
  generateAcceptRegex,
12
- generateContentTypeRegex
12
+ generateContentTypeRegex,
13
+ convertSupportedVersionToExtensionList,
14
+ parseAcceptHeader,
15
+ protocolVersions
13
16
  } = require('../../../../src/util/headerValidation/index')
14
17
  const {
15
18
  generateAcceptHeader,
@@ -124,3 +127,91 @@ test('Run positive content-type header fuzz', t => {
124
127
  }
125
128
  t.end()
126
129
  })
130
+
131
+ test('Run test-case 1 for convertSupportedVersionToExtensionList', t => {
132
+ const supportedVersionList = [
133
+ '1',
134
+ '1.0',
135
+ '1.1'
136
+ ]
137
+ const expectedResult = [
138
+ { key: '1', value: '0' },
139
+ { key: '1', value: '1' }
140
+ ]
141
+ const result = convertSupportedVersionToExtensionList(supportedVersionList)
142
+ t.deepEqual(result, expectedResult)
143
+ t.end()
144
+ })
145
+
146
+ test('Run test-case 2 for convertSupportedVersionToExtensionList', t => {
147
+ const supportedVersionList = [
148
+ '1.',
149
+ '1.0',
150
+ '1.1'
151
+ ]
152
+ const expectedResult = [
153
+ { key: '1', value: '0' },
154
+ { key: '1', value: '1' }
155
+ ]
156
+ const result = convertSupportedVersionToExtensionList(supportedVersionList)
157
+ t.deepEqual(result, expectedResult)
158
+ t.end()
159
+ })
160
+
161
+ test('Run test-case 3 for convertSupportedVersionToExtensionList', t => {
162
+ const supportedVersionList = []
163
+ const expectedResult = []
164
+ const result = convertSupportedVersionToExtensionList(supportedVersionList)
165
+ t.deepEqual(result, expectedResult)
166
+ t.end()
167
+ })
168
+
169
+ test('Run test-case 4 for convertSupportedVersionToExtensionList', t => {
170
+ const supportedVersionList = [
171
+ 2,
172
+ 2.0,
173
+ 3.1,
174
+ '1.0'
175
+ ]
176
+ const expectedResult = [
177
+ { key: '2', value: '0' },
178
+ { key: '3', value: '1' },
179
+ { key: '1', value: '0' }
180
+ ]
181
+ const result = convertSupportedVersionToExtensionList(supportedVersionList)
182
+ t.deepEqual(result, expectedResult)
183
+ t.end()
184
+ })
185
+
186
+ test('Run test-case 4 for convertSupportedVersionToExtensionList', t => {
187
+ const supportedVersionList = [
188
+ 2,
189
+ 2.0,
190
+ 3.1,
191
+ '1.0',
192
+ protocolVersions.anyVersion
193
+ ]
194
+ const expectedResult = [
195
+ { key: '2', value: '0' },
196
+ { key: '3', value: '1' },
197
+ { key: '1', value: '0' }
198
+ ]
199
+ const result = convertSupportedVersionToExtensionList(supportedVersionList)
200
+ t.deepEqual(result, expectedResult)
201
+ t.end()
202
+ })
203
+
204
+ test('Run test-case for parseAcceptHeader', t => {
205
+ const resource = 'participants'
206
+ const acceptHeader = `application/vnd.interoperability.${resource}+json;version=1,application/vnd.interoperability.${resource}+json;version=1.1`
207
+ const expectedResult = {
208
+ valid: true,
209
+ versions: new Set([
210
+ '1',
211
+ '1.1'
212
+ ])
213
+ }
214
+ const result = parseAcceptHeader(resource, acceptHeader)
215
+ t.deepEqual(result, expectedResult)
216
+ t.end()
217
+ })
@@ -112,6 +112,59 @@ Test('ParticipantEndpoint Model Test', modelTest => {
112
112
  }
113
113
  })
114
114
 
115
+ getEndpointTest.test('handle protocolVersions for config injection', async (test) => {
116
+ const protocolVersions = {
117
+ content: '2.1',
118
+ accept: '2'
119
+ }
120
+ const fsp = 'fsp'
121
+ const requestOptions = {
122
+ url: Mustache.render(Config.ENDPOINT_SOURCE_URL + Enum.EndPoints.FspEndpointTemplates.PARTICIPANT_ENDPOINTS_GET, { fsp }),
123
+ method: 'get'
124
+ }
125
+ const requestFunction = (request) => {
126
+ test.equal(request.headers['content-type'], Helper.generateProtocolHeader('participants', protocolVersions.content))
127
+ test.equal(request.headers.accept, Helper.generateProtocolHeader('participants', protocolVersions.accept))
128
+ return Helper.getEndPointsResponse
129
+ }
130
+ const span = EventSdk.Tracer.createSpan('test-span')
131
+ Model = proxyquire('../../../src/util/request', { axios: requestFunction })
132
+
133
+ try {
134
+ const result = await Model.sendRequest(requestOptions.url, Helper.defaultHeaders(Enum.Http.HeaderResources.SWITCH, Enum.Http.HeaderResources.PARTICIPANTS, Enum.Http.HeaderResources.SWITCH), Enum.Http.HeaderResources.SWITCH, Enum.Http.HeaderResources.SWITCH, Enum.Http.RestMethods.GET, undefined, Enum.Http.ResponseTypes.JSON, span, null, protocolVersions)
135
+ test.deepEqual(result, Helper.getEndPointsResponse, 'The results match')
136
+ test.end()
137
+ } catch (err) {
138
+ test.fail('Error thrown', err)
139
+ test.end()
140
+ }
141
+ })
142
+
143
+ getEndpointTest.test('handle protocolVersions without config injection', async (test) => {
144
+ const protocolVersions = null
145
+ const fsp = 'fsp'
146
+ const requestOptions = {
147
+ url: Mustache.render(Config.ENDPOINT_SOURCE_URL + Enum.EndPoints.FspEndpointTemplates.PARTICIPANT_ENDPOINTS_GET, { fsp }),
148
+ method: 'get'
149
+ }
150
+ const requestFunction = (request) => {
151
+ test.equal(request.headers['content-type'], Helper.generateProtocolHeader('participants', '1.0'))
152
+ test.equal(request.headers.accept, Helper.generateProtocolHeader('participants', '1'))
153
+ return Helper.getEndPointsResponse
154
+ }
155
+ const span = EventSdk.Tracer.createSpan('test-span')
156
+ Model = proxyquire('../../../src/util/request', { axios: requestFunction })
157
+
158
+ try {
159
+ const result = await Model.sendRequest(requestOptions.url, Helper.defaultHeaders(Enum.Http.HeaderResources.SWITCH, Enum.Http.HeaderResources.PARTICIPANTS, Enum.Http.HeaderResources.SWITCH), Enum.Http.HeaderResources.SWITCH, Enum.Http.HeaderResources.SWITCH, Enum.Http.RestMethods.GET, undefined, Enum.Http.ResponseTypes.JSON, span, null, protocolVersions)
160
+ test.deepEqual(result, Helper.getEndPointsResponse, 'The results match')
161
+ test.end()
162
+ } catch (err) {
163
+ test.fail('Error thrown', err)
164
+ test.end()
165
+ }
166
+ })
167
+
115
168
  getEndpointTest.test('throw error', async (test) => {
116
169
  const fsp = 'fsp1'
117
170
 
@@ -26,13 +26,15 @@
26
26
 
27
27
  const Enums = require('../../src/enums')
28
28
 
29
- function defaultHeaders (destination, resource, source, version = '1.0') {
29
+ const generateProtocolHeader = (resource, version) => `application/vnd.interoperability.${resource}+json;version=${version}`
30
+
31
+ const defaultHeaders = (destination, resource, source, version = '1.0') => {
30
32
  // TODO: See API section 3.2.1; what should we do about X-Forwarded-For? Also, should we
31
33
  // add/append to this field in all 'queueResponse' calls?
32
34
  return {
33
- accept: `application/vnd.interoperability.${resource}+json;version=${version}`,
35
+ accept: generateProtocolHeader(resource, version),
34
36
  'fspiop-destination': destination || '',
35
- 'content-type': `application/vnd.interoperability.${resource}+json;version=${version}`,
37
+ 'content-type': generateProtocolHeader(resource, version),
36
38
  date: '2019-05-24 08:52:19',
37
39
  'fspiop-source': source
38
40
  }
@@ -74,6 +76,7 @@ const getEndpointAndRenderResponse = {
74
76
 
75
77
  module.exports = {
76
78
  defaultHeaders,
79
+ generateProtocolHeader,
77
80
  getEndPointsResponse,
78
81
  getEndpointAndRenderResponse
79
82
  }