@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 CHANGED
@@ -1 +1 @@
1
- 18.17.1
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
- * @mdebarros @elnyry-sam-k @vijayg10 @kleyow @oderayi
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.15.1",
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
- "@hapi/hapi": "21.3.12",
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.13",
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 documenation from supplied OpenAPI spec (json or yaml).
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 { API_TYPES } = require('../../../constants')
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(
@@ -26,7 +26,7 @@
26
26
 
27
27
  const Test = require('tapes')(require('tape'))
28
28
  const Sinon = require('sinon')
29
- const Joi = require('@hapi/joi')
29
+ const Joi = require('joi')
30
30
 
31
31
  const HealthCheck = require('../../../src/healthCheck').HealthCheck
32
32
 
@@ -24,7 +24,7 @@
24
24
  ******/
25
25
 
26
26
  const Test = require('tape')
27
- const BaseJoi = require('@hapi/joi')
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('@hapi/joi')
30
+ const Joi = require('joi')
31
31
  const StreamingProtocol = require('../../../../../src/util').StreamingProtocol
32
32
 
33
33
  const init = async (options) => {