@mojaloop/central-services-shared 18.15.1 → 18.16.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/.nvmrc +1 -1
- package/CHANGELOG.md +14 -0
- package/CODEOWNERS +1 -1
- package/package.json +7 -4
- package/src/index.d.ts +35 -1
- package/src/util/hapi/plugins/apiDocumentation.js +1 -1
- package/src/util/hapi/plugins/headerValidation.js +19 -1
- package/test/unit/healthCheck/HealthCheck.test.js +1 -1
- package/test/unit/util/hapi/plugins/customCurrencyCodeExtension.test.js +1 -1
- package/test/unit/util/hapi/plugins/headerValidation.test.js +55 -11
- package/test/unit/util/hapi/plugins/rawPayloadToDataUri.test.js +1 -1
package/.nvmrc
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
18.
|
|
1
|
+
18.20.4
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
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
|
+
## [18.16.0](https://github.com/mojaloop/central-services-shared/compare/v18.15.2...v18.16.0) (2025-01-24)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* added missing types and maintenance fixes ([#428](https://github.com/mojaloop/central-services-shared/issues/428)) ([f8e84e5](https://github.com/mojaloop/central-services-shared/commit/f8e84e52c3052196de23b51994297b35fd6ac757))
|
|
11
|
+
|
|
12
|
+
### [18.15.2](https://github.com/mojaloop/central-services-shared/compare/v18.15.1...v18.15.2) (2025-01-20)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Chore
|
|
16
|
+
|
|
17
|
+
* add date validation ([#427](https://github.com/mojaloop/central-services-shared/issues/427)) ([96b3e34](https://github.com/mojaloop/central-services-shared/commit/96b3e34cf9732178b197daa488079f1bb7ea6bf9))
|
|
18
|
+
|
|
5
19
|
### [18.15.1](https://github.com/mojaloop/central-services-shared/compare/v18.15.0...v18.15.1) (2025-01-07)
|
|
6
20
|
|
|
7
21
|
|
package/CODEOWNERS
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
## @global-owner1 and @global-owner2 will be requested for
|
|
7
7
|
## review when someone opens a pull request.
|
|
8
8
|
#* @global-owner1 @global-owner2
|
|
9
|
-
* @
|
|
9
|
+
* @bushjames @elnyry-sam-k @vijayg10 @kleyow @oderayi @shashi165 @gibaros
|
|
10
10
|
|
|
11
11
|
## Order is important; the last matching pattern takes the most
|
|
12
12
|
## precedence. When someone opens a pull request that only
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mojaloop/central-services-shared",
|
|
3
|
-
"version": "18.
|
|
3
|
+
"version": "18.16.0",
|
|
4
4
|
"description": "Shared code for mojaloop central services",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "ModusBox",
|
|
@@ -60,7 +60,10 @@
|
|
|
60
60
|
"dependencies": {
|
|
61
61
|
"@hapi/catbox": "12.1.1",
|
|
62
62
|
"@hapi/catbox-memory": "5.0.1",
|
|
63
|
+
"@hapi/hapi": "21.3.12",
|
|
64
|
+
"@hapi/joi-date": "2.0.1",
|
|
63
65
|
"@mojaloop/inter-scheme-proxy-cache-lib": "2.3.1",
|
|
66
|
+
"@mojaloop/sdk-standard-components": "19.6.3",
|
|
64
67
|
"axios": "1.7.9",
|
|
65
68
|
"clone": "2.1.2",
|
|
66
69
|
"dotenv": "16.4.7",
|
|
@@ -69,6 +72,7 @@
|
|
|
69
72
|
"fast-safe-stringify": "^2.1.1",
|
|
70
73
|
"immutable": "5.0.3",
|
|
71
74
|
"ioredis": "^5.4.2",
|
|
75
|
+
"joi": "17.13.3",
|
|
72
76
|
"lodash": "4.17.21",
|
|
73
77
|
"mustache": "4.2.0",
|
|
74
78
|
"openapi-backend": "5.11.1",
|
|
@@ -81,12 +85,11 @@
|
|
|
81
85
|
"yaml": "2.7.0"
|
|
82
86
|
},
|
|
83
87
|
"devDependencies": {
|
|
84
|
-
"@
|
|
85
|
-
"@hapi/joi": "17.1.1",
|
|
88
|
+
"@types/hapi__joi": "^17.1.15",
|
|
86
89
|
"audit-ci": "^7.1.0",
|
|
87
90
|
"base64url": "3.0.1",
|
|
88
91
|
"chance": "1.1.12",
|
|
89
|
-
"npm-check-updates": "17.1.
|
|
92
|
+
"npm-check-updates": "17.1.14",
|
|
90
93
|
"nyc": "17.1.0",
|
|
91
94
|
"portfinder": "1.0.32",
|
|
92
95
|
"pre-commit": "1.2.2",
|
package/src/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Utils as HapiUtil, Server } from '@hapi/hapi'
|
|
2
|
+
import { Joi } from 'joi'
|
|
2
3
|
import { ILogger } from '@mojaloop/central-services-logger/src/contextLogger'
|
|
3
4
|
|
|
4
5
|
declare namespace CentralServicesShared {
|
|
@@ -663,6 +664,9 @@ declare namespace CentralServicesShared {
|
|
|
663
664
|
type ProtocolResources = string[]
|
|
664
665
|
type ProtocolVersions = (string | symbol)[]
|
|
665
666
|
type ApiTypeValues = 'fspiop' | 'iso20022'
|
|
667
|
+
type APIDocumentationPluginOptions =
|
|
668
|
+
| { documentPath: string; document?: never }
|
|
669
|
+
| { document?: string; documentPath?: never }
|
|
666
670
|
|
|
667
671
|
type LoggingPluginOptions = {
|
|
668
672
|
log?: ILogger,
|
|
@@ -694,10 +698,40 @@ declare namespace CentralServicesShared {
|
|
|
694
698
|
defaultProtocolResources: ProtocolResources
|
|
695
699
|
defaultProtocolVersions: ProtocolVersions
|
|
696
700
|
};
|
|
701
|
+
OpenapiBackendValidator: {
|
|
702
|
+
plugin: {
|
|
703
|
+
name: string,
|
|
704
|
+
register: (server: Server) => void
|
|
705
|
+
}
|
|
706
|
+
};
|
|
707
|
+
HapiEventPlugin: {
|
|
708
|
+
plugin: {
|
|
709
|
+
name: string,
|
|
710
|
+
register: (server: Server) => void
|
|
711
|
+
}
|
|
712
|
+
};
|
|
713
|
+
customCurrencyCodeValidation: (joi: Joi) => {
|
|
714
|
+
base: Joi.StringSchema;
|
|
715
|
+
type: string;
|
|
716
|
+
messages: {
|
|
717
|
+
'currency.base': string;
|
|
718
|
+
};
|
|
719
|
+
rules: {
|
|
720
|
+
currency: {
|
|
721
|
+
validate: (value: string, helpers: any) => string | any;
|
|
722
|
+
};
|
|
723
|
+
};
|
|
724
|
+
};
|
|
725
|
+
APIDocumentation: {
|
|
726
|
+
plugin: {
|
|
727
|
+
name: string,
|
|
728
|
+
register: (server: Server, options: APIDocumentationPluginOptions) => void
|
|
729
|
+
}
|
|
730
|
+
};
|
|
697
731
|
loggingPlugin: {
|
|
698
732
|
name: string,
|
|
699
733
|
register: (server: Server, options?: LoggingPluginOptions) => Promise<void>
|
|
700
|
-
}
|
|
734
|
+
};
|
|
701
735
|
API_TYPES: Record<ApiTypeValues, ApiTypeValues>;
|
|
702
736
|
}
|
|
703
737
|
// todo: define the rest of the types
|
|
@@ -27,7 +27,7 @@ const APIDocBuilder = require('../../documentation').APIDocBuilder
|
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
29
|
* Hapi plugin to add '/swagger.json' and '/documentation' endpoints.
|
|
30
|
-
* It generates API
|
|
30
|
+
* It generates API documentation from supplied OpenAPI spec (json or yaml).
|
|
31
31
|
*
|
|
32
32
|
* options.documentPath - Full path to the OpenAPI (fka Swagger) document (JSON or YAML). Mutually exclusive to `document` option.
|
|
33
33
|
* options.document - OpenAPI document as string. Mutually exclusive to `documentPath` option.
|
|
@@ -6,7 +6,10 @@
|
|
|
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
|
|
9
|
+
const RootJoi = require('joi')
|
|
10
|
+
const DateExtension = require('@hapi/joi-date')
|
|
11
|
+
const Joi = RootJoi.extend(DateExtension)
|
|
12
|
+
const { API_TYPES, MAX_CONTENT_LENGTH } = require('../../../constants')
|
|
10
13
|
const {
|
|
11
14
|
checkApiType,
|
|
12
15
|
parseAcceptHeader,
|
|
@@ -100,10 +103,25 @@ const plugin = {
|
|
|
100
103
|
}
|
|
101
104
|
}
|
|
102
105
|
|
|
106
|
+
const dateSchema = Joi.date().format('ddd, DD MMM YYYY HH:mm:ss [GMT]').required()
|
|
107
|
+
const dateHeader = request.headers.date
|
|
108
|
+
const { error } = dateSchema.validate(dateHeader)
|
|
109
|
+
|
|
110
|
+
if (error) {
|
|
111
|
+
throw createFSPIOPError(Enums.FSPIOPErrorCodes.MALFORMED_SYNTAX, 'Invalid date header')
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (request.headers['content-length'] > MAX_CONTENT_LENGTH) {
|
|
115
|
+
throw createFSPIOPError(
|
|
116
|
+
Enums.FSPIOPErrorCodes.TOO_LARGE_PAYLOAD, 'Payload size is too large.'
|
|
117
|
+
)
|
|
118
|
+
}
|
|
119
|
+
|
|
103
120
|
// Always validate the content-type header
|
|
104
121
|
if (request.headers['content-type'] === undefined) {
|
|
105
122
|
throw createFSPIOPError(Enums.FSPIOPErrorCodes.MISSING_ELEMENT, errorMessages.REQUIRE_CONTENT_TYPE_HEADER)
|
|
106
123
|
}
|
|
124
|
+
|
|
107
125
|
const contentType = parseContentTypeHeader(resource, request.headers['content-type'], apiType)
|
|
108
126
|
if (!contentType.valid) {
|
|
109
127
|
throw createFSPIOPError(
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
******/
|
|
25
25
|
|
|
26
26
|
const Test = require('tape')
|
|
27
|
-
const BaseJoi = require('
|
|
27
|
+
const BaseJoi = require('joi')
|
|
28
28
|
const currencyExtension = require('../../../../../src/util/hapi/plugins/customCurrencyCodeExtension')
|
|
29
29
|
const Joi = BaseJoi.extend(currencyExtension)
|
|
30
30
|
|
|
@@ -106,7 +106,8 @@ Test('headerValidation plugin test', async (pluginTest) => {
|
|
|
106
106
|
url: '/unconfigured',
|
|
107
107
|
headers: {
|
|
108
108
|
accept: generateAcceptHeader(resource, [1]),
|
|
109
|
-
'content-type': generateContentTypeHeader(resource, 1)
|
|
109
|
+
'content-type': generateContentTypeHeader(resource, 1),
|
|
110
|
+
date: new Date().toUTCString()
|
|
110
111
|
}
|
|
111
112
|
})
|
|
112
113
|
t.is(res.statusCode, 202)
|
|
@@ -119,7 +120,8 @@ Test('headerValidation plugin test', async (pluginTest) => {
|
|
|
119
120
|
method: 'get',
|
|
120
121
|
url: `/${resource}`,
|
|
121
122
|
headers: {
|
|
122
|
-
'content-type': generateContentTypeHeader(resource, 1)
|
|
123
|
+
'content-type': generateContentTypeHeader(resource, 1),
|
|
124
|
+
date: new Date().toUTCString()
|
|
123
125
|
}
|
|
124
126
|
})
|
|
125
127
|
t.is(res.statusCode, fspiopCode.httpStatusCode)
|
|
@@ -132,7 +134,8 @@ Test('headerValidation plugin test', async (pluginTest) => {
|
|
|
132
134
|
const opts = {
|
|
133
135
|
url: `/${resource}`,
|
|
134
136
|
headers: {
|
|
135
|
-
'content-type': generateContentTypeHeader(resource, 1)
|
|
137
|
+
'content-type': generateContentTypeHeader(resource, 1),
|
|
138
|
+
date: new Date().toUTCString()
|
|
136
139
|
}
|
|
137
140
|
}
|
|
138
141
|
await Promise.all(['post', 'put'].map(async method => {
|
|
@@ -150,7 +153,8 @@ Test('headerValidation plugin test', async (pluginTest) => {
|
|
|
150
153
|
url: `/${resource}`,
|
|
151
154
|
headers: {
|
|
152
155
|
'content-type': generateContentTypeHeader(resource, 1),
|
|
153
|
-
accept: 'hello'
|
|
156
|
+
accept: 'hello',
|
|
157
|
+
date: new Date().toUTCString()
|
|
154
158
|
}
|
|
155
159
|
})
|
|
156
160
|
t.is(res.statusCode, fspiopCode.httpStatusCode)
|
|
@@ -167,7 +171,8 @@ Test('headerValidation plugin test', async (pluginTest) => {
|
|
|
167
171
|
url: `/${resource}`,
|
|
168
172
|
headers: {
|
|
169
173
|
'content-type': generateContentTypeHeader(resource, 1),
|
|
170
|
-
accept: generateAcceptHeader(resource, [5])
|
|
174
|
+
accept: generateAcceptHeader(resource, [5]),
|
|
175
|
+
date: new Date().toUTCString()
|
|
171
176
|
}
|
|
172
177
|
})
|
|
173
178
|
t.is(res.statusCode, fspiopCode.httpStatusCode)
|
|
@@ -185,7 +190,8 @@ Test('headerValidation plugin test', async (pluginTest) => {
|
|
|
185
190
|
url: `/${resource}`,
|
|
186
191
|
headers: {
|
|
187
192
|
'content-type': 'application/json',
|
|
188
|
-
accept: generateAcceptHeader(resource, [1])
|
|
193
|
+
accept: generateAcceptHeader(resource, [1]),
|
|
194
|
+
date: new Date().toUTCString()
|
|
189
195
|
}
|
|
190
196
|
})
|
|
191
197
|
t.is(res.statusCode, fspiopCode.httpStatusCode)
|
|
@@ -202,7 +208,8 @@ Test('headerValidation plugin test', async (pluginTest) => {
|
|
|
202
208
|
url: `/${resource}`,
|
|
203
209
|
headers: {
|
|
204
210
|
'content-type': generateContentTypeHeader(resource, 5),
|
|
205
|
-
accept: generateAcceptHeader(resource, [1])
|
|
211
|
+
accept: generateAcceptHeader(resource, [1]),
|
|
212
|
+
date: new Date().toUTCString()
|
|
206
213
|
}
|
|
207
214
|
})
|
|
208
215
|
t.is(res.statusCode, fspiopCode.httpStatusCode)
|
|
@@ -219,7 +226,8 @@ Test('headerValidation plugin test', async (pluginTest) => {
|
|
|
219
226
|
url: `/${resource}/MSISDN/12346`,
|
|
220
227
|
headers: {
|
|
221
228
|
'content-type': generateContentTypeHeader(resource, 1),
|
|
222
|
-
accept: generateAcceptHeader(resource, [1])
|
|
229
|
+
accept: generateAcceptHeader(resource, [1]),
|
|
230
|
+
date: new Date().toUTCString()
|
|
223
231
|
}
|
|
224
232
|
})
|
|
225
233
|
t.is(res.statusCode, 202)
|
|
@@ -233,7 +241,8 @@ Test('headerValidation plugin test', async (pluginTest) => {
|
|
|
233
241
|
url: `/${resource}/MSISDN/12346`,
|
|
234
242
|
headers: {
|
|
235
243
|
'content-type': generateContentTypeHeader(resource, 5),
|
|
236
|
-
accept: generateAcceptHeader(resource, [1])
|
|
244
|
+
accept: generateAcceptHeader(resource, [1]),
|
|
245
|
+
date: new Date().toUTCString()
|
|
237
246
|
}
|
|
238
247
|
})
|
|
239
248
|
t.is(res.statusCode, fspiopCode.httpStatusCode)
|
|
@@ -249,7 +258,8 @@ Test('headerValidation plugin test', async (pluginTest) => {
|
|
|
249
258
|
url: `/${resource}`,
|
|
250
259
|
headers: {
|
|
251
260
|
'content-type': generateContentTypeHeader(resource, 1),
|
|
252
|
-
accept: generateAcceptHeader(resource, [1])
|
|
261
|
+
accept: generateAcceptHeader(resource, [1]),
|
|
262
|
+
date: new Date().toUTCString()
|
|
253
263
|
}
|
|
254
264
|
})
|
|
255
265
|
t.is(res.payload, '')
|
|
@@ -263,7 +273,41 @@ Test('headerValidation plugin test', async (pluginTest) => {
|
|
|
263
273
|
url: `/${resource}`,
|
|
264
274
|
headers: {
|
|
265
275
|
'content-type': generateContentTypeHeader(resource, 1),
|
|
266
|
-
accept: `application/vnd.interoperability.${resource}+json
|
|
276
|
+
accept: `application/vnd.interoperability.${resource}+json`,
|
|
277
|
+
date: new Date().toUTCString()
|
|
278
|
+
}
|
|
279
|
+
})
|
|
280
|
+
t.is(res.payload, '')
|
|
281
|
+
t.is(res.statusCode, 202)
|
|
282
|
+
t.end()
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
pluginTest.test('MALFORMED_SYNTAX/INVALID_DATE_HEADER', async t => {
|
|
286
|
+
const fspiopCode = ErrorHandling.Enums.FSPIOPErrorCodes.MALFORMED_SYNTAX
|
|
287
|
+
const res = await server.inject({
|
|
288
|
+
method: 'put',
|
|
289
|
+
url: `/${resource}`,
|
|
290
|
+
headers: {
|
|
291
|
+
'content-type': generateContentTypeHeader(resource, 1),
|
|
292
|
+
accept: generateAcceptHeader(resource, [1]),
|
|
293
|
+
date: 'invalid-date'
|
|
294
|
+
}
|
|
295
|
+
})
|
|
296
|
+
t.is(res.statusCode, fspiopCode.httpStatusCode)
|
|
297
|
+
const payload = JSON.parse(res.payload)
|
|
298
|
+
t.is(payload.apiErrorCode.code, fspiopCode.code)
|
|
299
|
+
t.is(payload.message, 'Invalid date header')
|
|
300
|
+
t.end()
|
|
301
|
+
})
|
|
302
|
+
|
|
303
|
+
pluginTest.test('accepts valid date header', async t => {
|
|
304
|
+
const res = await server.inject({
|
|
305
|
+
method: 'put',
|
|
306
|
+
url: `/${resource}`,
|
|
307
|
+
headers: {
|
|
308
|
+
'content-type': generateContentTypeHeader(resource, 1),
|
|
309
|
+
accept: generateAcceptHeader(resource, [1]),
|
|
310
|
+
date: new Date().toUTCString()
|
|
267
311
|
}
|
|
268
312
|
})
|
|
269
313
|
t.is(res.payload, '')
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
const Hapi = require('@hapi/hapi')
|
|
28
28
|
const Test = require('tapes')(require('tape'))
|
|
29
29
|
const Sinon = require('sinon')
|
|
30
|
-
const Joi = require('
|
|
30
|
+
const Joi = require('joi')
|
|
31
31
|
const StreamingProtocol = require('../../../../../src/util').StreamingProtocol
|
|
32
32
|
|
|
33
33
|
const init = async (options) => {
|