@mimik/api-helper 2.0.9 → 2.0.10
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/.nycrc +4 -0
- package/README.md +63 -57
- package/eslint.config.js +11 -30
- package/index.js +123 -115
- package/lib/ajvHelpers.js +1 -1
- package/lib/baseHandlers.js +3 -2
- package/lib/oauthValidation-helper.js +1 -1
- package/lib/securityHandlers.js +5 -3
- package/package.json +25 -23
- package/.claude/settings.local.json +0 -9
- package/.husky/pre-commit +0 -2
- package/.husky/pre-push +0 -2
- package/test/ajvHelpers.test.js +0 -159
- package/test/baseHandlers.test.js +0 -150
- package/test/extract-helper.test.js +0 -100
- package/test/index-async.test.js +0 -599
- package/test/index-sync.test.js +0 -282
- package/test/oauthValidation-helper.test.js +0 -136
- package/test/securityHandlers.test.js +0 -557
package/index.js
CHANGED
|
@@ -30,6 +30,8 @@ import { OpenAPIBackend } from 'openapi-backend';
|
|
|
30
30
|
import SwaggerClient from 'swagger-client';
|
|
31
31
|
import { ajvFormats } from './lib/ajvHelpers.js';
|
|
32
32
|
import baseHandlers from './lib/baseHandlers.js';
|
|
33
|
+
import compact from 'lodash.compact';
|
|
34
|
+
import difference from 'lodash.difference';
|
|
33
35
|
import fs from 'fs';
|
|
34
36
|
import { getRichError } from '@mimik/response-helper';
|
|
35
37
|
import { load } from 'js-yaml';
|
|
@@ -42,7 +44,7 @@ import { securityLib } from './lib/securityHandlers.js';
|
|
|
42
44
|
* @module api-helper
|
|
43
45
|
* @example
|
|
44
46
|
* import apiHelper from '@mimik/api-helper';
|
|
45
|
-
*
|
|
47
|
+
* or
|
|
46
48
|
* import { apiSetup, securityLib, getAPIFile, validateSecuritySchemes, extractProperties, setupServerFiles } from '@mimik/api-helper';
|
|
47
49
|
*/
|
|
48
50
|
const EMPTY = 0;
|
|
@@ -57,11 +59,13 @@ const POSTFIX_INDEX = 3;
|
|
|
57
59
|
* Implement the security flows for the API.
|
|
58
60
|
*
|
|
59
61
|
* @function securityLib
|
|
60
|
-
* @category
|
|
62
|
+
* @category async
|
|
61
63
|
* @requires @mimik/swagger-helper
|
|
62
64
|
* @requires jsonwebtoken
|
|
65
|
+
* @requires lodash
|
|
63
66
|
* @param {object} config - Configuration of the service.
|
|
64
|
-
*
|
|
67
|
+
* &fulfil {object} The API file itself.
|
|
68
|
+
* @throws {Promise} An error is thrown if the initiatilization failed.
|
|
65
69
|
*
|
|
66
70
|
* This function is used to setup the following security handlers for the API:
|
|
67
71
|
* - `SystemSecurity` - used for the system operations, like /system, /onbehalf
|
|
@@ -74,28 +78,33 @@ export { securityLib };
|
|
|
74
78
|
|
|
75
79
|
/**
|
|
76
80
|
*
|
|
77
|
-
* Setup the API to be
|
|
81
|
+
* Setup the API to be use for a service
|
|
78
82
|
*
|
|
79
83
|
* @function apiSetup
|
|
80
84
|
* @category async
|
|
81
85
|
* @requires @mimik/response-helper
|
|
82
86
|
* @requires @mimik/sumologic-winston-logger
|
|
83
|
-
* @requires
|
|
84
|
-
* @
|
|
87
|
+
* @requires @mimik/swagger-helper
|
|
88
|
+
* @requires ajv-formats
|
|
89
|
+
* @requires fs
|
|
90
|
+
* @requires jsonwebtoken
|
|
91
|
+
* @requires lodash
|
|
92
|
+
* @param {object} setup - Object containing the apiFilename and the exisiting security schemes in the API definition.
|
|
85
93
|
* @param {object} registeredOperations - List of the operation to register for the API.
|
|
86
94
|
* @param {object} securityHandlers - List of the security handlers to add for the service.
|
|
87
|
-
* @param {object} extraFormats - list of the formats to add for
|
|
95
|
+
* @param {object} extraFormats - list of the formats to add for validatng properties.
|
|
88
96
|
* @param {object} config - Configuration of the service.
|
|
89
|
-
* @param {UUID.<string>} correlationId - CorrelationId when logging
|
|
90
|
-
* @return {Promise
|
|
91
|
-
*
|
|
97
|
+
* @param {UUID.<string>} correlationId - CorrelationId when logging activites.
|
|
98
|
+
* @return {Promise}.
|
|
99
|
+
* &fulfil {object} The API file itself.
|
|
100
|
+
* @throws {Promise} An error is thrown if the initiatilization failed.
|
|
92
101
|
*
|
|
93
102
|
* The following scheme names are reserved: `SystemSecurity`, `AdminSecurity`, `UserSecurity`, `PeerSecurity`, `ApiKeySecurity`.
|
|
94
103
|
* The following security schemes can be defaulted: `SystemSecurity`, `AdminSecurity`, `UserSecurity`, `ApiKeySecurity`.
|
|
95
104
|
* The secOptions in the options property passed when using `init` allows the following operations:
|
|
96
105
|
* - introduce a customer security scheme, in this case secOptions contains: { newSecurityScheme: {function}newSecurityHandler },
|
|
97
106
|
* - disable a security scheme that is defined in the swagger API, in this case secOptions contains: { securitySchemeToDisable: { {boolean}notEnabled: true } },
|
|
98
|
-
* -
|
|
107
|
+
* - overwite an existing security scheme, in this case secOptions contains: { securitySchemeToOverwrite: {function}newSecurityHandler }.
|
|
99
108
|
* If the secOptions is not present either to introduce, disable or overwrite a security scheme that is present in the swagger API file an error is generated.
|
|
100
109
|
* If the secOptions contains unused security schemes, an error is generated.
|
|
101
110
|
*
|
|
@@ -133,7 +142,7 @@ export const apiSetup = (setup, registeredOperations, securityHandlers, extraFor
|
|
|
133
142
|
}
|
|
134
143
|
const appliedSecurities = [];
|
|
135
144
|
const registerDefault = (securitySchemeName, securityHandler) => {
|
|
136
|
-
if (existingSecuritySchemes.includes(securitySchemeName) && (!securityHandlers || !securityHandlers[securitySchemeName])) {
|
|
145
|
+
if (existingSecuritySchemes.includes(securitySchemeName) && (!securityHandlers || (securityHandlers && !securityHandlers[securitySchemeName]))) {
|
|
137
146
|
api.registerSecurityHandler(securitySchemeName, securityHandler);
|
|
138
147
|
appliedSecurities.push(securitySchemeName);
|
|
139
148
|
}
|
|
@@ -143,16 +152,16 @@ export const apiSetup = (setup, registeredOperations, securityHandlers, extraFor
|
|
|
143
152
|
registerDefault(ADMIN_SECURITY, AdminSecurity[mode]);
|
|
144
153
|
registerDefault(USER_SECURITY, UserSecurity[mode]);
|
|
145
154
|
registerDefault(API_KEY_SECURITY, ApiKeySecurity[mode]);
|
|
146
|
-
const remainingSecurities = definedSecuritySchemes
|
|
155
|
+
const remainingSecurities = difference(definedSecuritySchemes, appliedSecurities);
|
|
147
156
|
|
|
148
157
|
if (securityHandlers) {
|
|
149
158
|
const securityHandlerNames = Object.keys(securityHandlers);
|
|
150
|
-
const unusedSecuritySchemes = securityHandlerNames
|
|
159
|
+
const unusedSecuritySchemes = difference(securityHandlerNames, definedSecuritySchemes);
|
|
151
160
|
|
|
152
161
|
if (unusedSecuritySchemes.length !== EMPTY) throw getRichError('System', 'unused handlers for security schemes', { unusedSecuritySchemes });
|
|
153
162
|
|
|
154
163
|
remainingSecurities.forEach((securityScheme) => {
|
|
155
|
-
if (!securityHandlerNames.includes(securityScheme) && !securityHandlers[securityScheme]
|
|
164
|
+
if (!securityHandlerNames.includes(securityScheme) && !securityHandlers[securityScheme].notEnabled) {
|
|
156
165
|
throw getRichError('System', 'missing handler for security scheme', { securityScheme });
|
|
157
166
|
}
|
|
158
167
|
});
|
|
@@ -163,72 +172,16 @@ export const apiSetup = (setup, registeredOperations, securityHandlers, extraFor
|
|
|
163
172
|
});
|
|
164
173
|
}
|
|
165
174
|
else if (remainingSecurities.length !== EMPTY) throw getRichError('System', 'missing handlers for security schemes', { missingSecuritySchemes: remainingSecurities });
|
|
166
|
-
|
|
175
|
+
api.init()
|
|
167
176
|
.catch((err) => {
|
|
168
177
|
throw getRichError('System', 'could not initialize the api', { api }, err);
|
|
169
|
-
})
|
|
170
|
-
|
|
171
|
-
};
|
|
172
|
-
|
|
173
|
-
const swaggerOptions = spec => ({
|
|
174
|
-
spec,
|
|
175
|
-
allowMetaPatches: false,
|
|
176
|
-
skipNormalization: true,
|
|
177
|
-
mode: 'strict',
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
const saveResolvedSpec = (apiDefinitionResult, apiFilename, correlationId) => {
|
|
181
|
-
if (apiDefinitionResult.errors.length !== EMPTY) {
|
|
182
|
-
logger.error('errors while resolving definition', { errors: apiDefinitionResult.errors }, correlationId);
|
|
183
|
-
throw getRichError('Parameter', 'errors while resolving definition', { apiFilename, errors: apiDefinitionResult.errors });
|
|
184
|
-
}
|
|
185
|
-
try {
|
|
186
|
-
fs.writeFileSync(apiFilename, JSON.stringify(apiDefinitionResult.spec, null, TAB));
|
|
187
|
-
}
|
|
188
|
-
catch (err) {
|
|
189
|
-
throw getRichError('System', 'file system error', { apiFilename }, err);
|
|
190
|
-
}
|
|
191
|
-
return apiDefinitionResult.spec;
|
|
192
|
-
};
|
|
193
|
-
|
|
194
|
-
const buildProviderRequest = (params, apiInfo, apiFilename) => {
|
|
195
|
-
const provider = apiInfo.provider || BITBUCKET;
|
|
196
|
-
|
|
197
|
-
switch (provider) {
|
|
198
|
-
case SWAGGERHUB: {
|
|
199
|
-
const result = {
|
|
200
|
-
url: `${API_PROVIDER_SWAGGERHUB}/${params[CUSTOMER_INDEX]}/${params[API_NAME_INDEX]}/${params[API_VERSION_INDEX]}?${RESOLVED}`,
|
|
201
|
-
};
|
|
202
|
-
|
|
203
|
-
if (apiInfo.apiApiKey) result.authorization = apiInfo.apiApiKey;
|
|
204
|
-
return result;
|
|
205
|
-
}
|
|
206
|
-
case BITBUCKET: {
|
|
207
|
-
if (!apiInfo.apiBasicAuth || !apiInfo.apiBasicAuth.username || !apiInfo.apiBasicAuth.password) {
|
|
208
|
-
throw getRichError('Parameter', 'missing username/password for accessing Bitbucket', { apiFilename });
|
|
209
|
-
}
|
|
210
|
-
if (apiInfo.apiBasicAuth.username === DEFAULT_BITBUCKET_USERNAME || apiInfo.apiBasicAuth.password === DEFAULT_BITBUCKET_PASSWORD) {
|
|
211
|
-
throw getRichError('Parameter', 'missing username/password for accessing Bitbucket', { apiFilename });
|
|
212
|
-
}
|
|
213
|
-
try {
|
|
214
|
-
return {
|
|
215
|
-
url: `${API_PROVIDER_BITBUCKET}/${params[CUSTOMER_INDEX]}/${params[API_NAME_INDEX]}${API_SOURCE}/${params[API_VERSION_INDEX]}/${SWAGGER}${EXTENSION_YML}`,
|
|
216
|
-
authorization: `Basic ${Base64.encode(`${apiInfo.apiBasicAuth.username}:${apiInfo.apiBasicAuth.password}`)}`,
|
|
217
|
-
};
|
|
218
|
-
}
|
|
219
|
-
catch (err) {
|
|
220
|
-
throw getRichError('System', 'could not create basicAuth', { apiFilename }, err);
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
default: {
|
|
224
|
-
throw getRichError('Parameter', 'invalid API provider', { provider, apiFilename });
|
|
225
|
-
}
|
|
226
|
-
}
|
|
178
|
+
});
|
|
179
|
+
return api;
|
|
227
180
|
};
|
|
228
181
|
|
|
229
182
|
/**
|
|
230
183
|
*
|
|
231
|
-
* Gets the API file from swaggerhub and store it in the
|
|
184
|
+
* Gets the API file from swaggerhub and store it in the give PATH location.
|
|
232
185
|
*
|
|
233
186
|
* @function getAPIFile
|
|
234
187
|
* @category async
|
|
@@ -239,10 +192,11 @@ const buildProviderRequest = (params, apiInfo, apiFilename) => {
|
|
|
239
192
|
* @requires js-yaml
|
|
240
193
|
* @requires path
|
|
241
194
|
* @param {PATH.<string>} apiFilename - Name of the file where the API file will be stored.
|
|
242
|
-
* @param {UUID.<string>} correlationId - CorrelationId when logging
|
|
195
|
+
* @param {UUID.<string>} correlationId - CorrelationId when logging activites.
|
|
243
196
|
* @param {object} options - Options associated with the call. Use to pass `metrics` to `rpRetry` and `apiInfo` to access the api file in the api provider.
|
|
244
|
-
* @return {Promise
|
|
245
|
-
*
|
|
197
|
+
* @return {Promise}.
|
|
198
|
+
* &fulfil {object} The API file itself.
|
|
199
|
+
* @throws {Promise} An error is thrown if the apiFilename resolution generates an error or the request to the API provider fails or the file connot be saved.
|
|
246
200
|
*
|
|
247
201
|
* `apiInfo` options has the following format:
|
|
248
202
|
* ``` javascript
|
|
@@ -252,11 +206,17 @@ const buildProviderRequest = (params, apiInfo, apiFilename) => {
|
|
|
252
206
|
* "username": "username for bitbucket",
|
|
253
207
|
* "password": "password for bitbucket"
|
|
254
208
|
* },
|
|
255
|
-
* "apiApiKey": "apiKey for access private API on swaggerhub, can be optional if the API is accessible
|
|
209
|
+
* "apiApiKey": "apiKey for access private API on swaggerhub, can be optional if the API is accessible publically"
|
|
256
210
|
* }
|
|
257
|
-
* ```
|
|
258
211
|
*/
|
|
259
212
|
export const getAPIFile = (apiFilename, correlationId, options) => {
|
|
213
|
+
const swaggerOptions = spec => ({
|
|
214
|
+
spec,
|
|
215
|
+
allowMetaPatches: false,
|
|
216
|
+
skipNormalization: true,
|
|
217
|
+
mode: 'strict',
|
|
218
|
+
});
|
|
219
|
+
|
|
260
220
|
logger.info('getting API definition', correlationId);
|
|
261
221
|
let apiDefinition;
|
|
262
222
|
|
|
@@ -278,12 +238,24 @@ export const getAPIFile = (apiFilename, correlationId, options) => {
|
|
|
278
238
|
}
|
|
279
239
|
return SwaggerClient.resolve(swaggerOptions(apiDefinition))
|
|
280
240
|
.catch((err) => {
|
|
281
|
-
throw getRichError('System', 'could not resolve
|
|
241
|
+
throw getRichError('System', 'could not resolve apiDefiniton', { apiFilename }, err);
|
|
282
242
|
})
|
|
283
|
-
.then(
|
|
243
|
+
.then((apiDefinitionResult) => {
|
|
244
|
+
if (apiDefinitionResult.errors.length !== EMPTY) {
|
|
245
|
+
logger.error('errors while resolving definition', { errors: apiDefinitionResult.errors }, correlationId);
|
|
246
|
+
throw getRichError('Parameter', 'errors while resolving definition', { apiFilename, errors: apiDefinitionResult.errors });
|
|
247
|
+
}
|
|
248
|
+
try {
|
|
249
|
+
fs.writeFileSync(apiFilename, JSON.stringify(apiDefinitionResult.spec, null, TAB));
|
|
250
|
+
}
|
|
251
|
+
catch (err) {
|
|
252
|
+
throw getRichError('System', 'file system error', { apiFilename }, err);
|
|
253
|
+
}
|
|
254
|
+
return apiDefinitionResult.spec;
|
|
255
|
+
});
|
|
284
256
|
}
|
|
285
257
|
if (!options) {
|
|
286
|
-
return Promise.reject(getRichError('
|
|
258
|
+
return Promise.reject(getRichError('Paremater', 'no options', { apiFilename }));
|
|
287
259
|
}
|
|
288
260
|
const { apiInfo } = options;
|
|
289
261
|
|
|
@@ -315,33 +287,56 @@ export const getAPIFile = (apiFilename, correlationId, options) => {
|
|
|
315
287
|
catch (err) {
|
|
316
288
|
return Promise.reject(getRichError('System', 'file system error', { apiDirectory }, err));
|
|
317
289
|
}
|
|
318
|
-
let providerResult;
|
|
319
|
-
|
|
320
|
-
try {
|
|
321
|
-
providerResult = buildProviderRequest(params, apiInfo, apiFilename);
|
|
322
|
-
}
|
|
323
|
-
catch (err) {
|
|
324
|
-
return Promise.reject(err);
|
|
325
|
-
}
|
|
326
290
|
const opts = {
|
|
327
291
|
method: 'GET',
|
|
328
|
-
url: providerResult.url,
|
|
329
292
|
headers: {
|
|
330
293
|
'x-correlation-id': correlationId,
|
|
331
294
|
},
|
|
332
|
-
retry: {
|
|
333
|
-
logLevel: {
|
|
334
|
-
response: 'debug',
|
|
335
|
-
responseDetails: 'type',
|
|
336
|
-
request: 'debug',
|
|
337
|
-
},
|
|
338
|
-
},
|
|
339
295
|
};
|
|
296
|
+
const provider = apiInfo.provider || BITBUCKET;
|
|
340
297
|
|
|
341
|
-
|
|
298
|
+
try {
|
|
299
|
+
switch (provider) {
|
|
300
|
+
case SWAGGERHUB: {
|
|
301
|
+
opts.url = `${API_PROVIDER_SWAGGERHUB}/${params[CUSTOMER_INDEX]}/${params[API_NAME_INDEX]}/${params[API_VERSION_INDEX]}?${RESOLVED}`;
|
|
302
|
+
if (apiInfo.apiApiKey) opts.headers.Authorization = apiInfo.apiApiKey;
|
|
303
|
+
break;
|
|
304
|
+
}
|
|
305
|
+
case BITBUCKET: {
|
|
306
|
+
if (!apiInfo.apiBasicAuth || !apiInfo.apiBasicAuth.username || !apiInfo.apiBasicAuth.password) {
|
|
307
|
+
throw getRichError('Parameter', 'missing username/password for accessing Bitbucket', { apiFilename });
|
|
308
|
+
}
|
|
309
|
+
if (apiInfo.apiBasicAuth.username === DEFAULT_BITBUCKET_USERNAME || apiInfo.apiBasicAuth.password === DEFAULT_BITBUCKET_PASSWORD) {
|
|
310
|
+
throw getRichError('Parameter', 'missing username/password for accessing Bitbucket', { apiFilename });
|
|
311
|
+
}
|
|
312
|
+
try {
|
|
313
|
+
opts.headers.Authorization = `Basic ${Base64.encode(`${apiInfo.apiBasicAuth.username}:${apiInfo.apiBasicAuth.password}`)}`;
|
|
314
|
+
}
|
|
315
|
+
catch (err) {
|
|
316
|
+
throw getRichError('System', 'could not create basicAuth', { apiFilename }, err);
|
|
317
|
+
}
|
|
318
|
+
opts.url = `${API_PROVIDER_BITBUCKET}/${params[CUSTOMER_INDEX]}/${params[API_NAME_INDEX]}${API_SOURCE}/${params[API_VERSION_INDEX]}/${SWAGGER}${EXTENSION_YML}`;
|
|
319
|
+
break;
|
|
320
|
+
}
|
|
321
|
+
default: {
|
|
322
|
+
throw getRichError('Parameter', 'invalid API provider', { provider, apiFilename });
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
catch (err) {
|
|
327
|
+
return Promise.reject(err);
|
|
328
|
+
}
|
|
342
329
|
if (options.metrics) {
|
|
343
|
-
opts.metrics =
|
|
330
|
+
opts.metrics = options.metrics;
|
|
331
|
+
opts.metrics.url = opts.url;
|
|
344
332
|
}
|
|
333
|
+
opts.retry = {
|
|
334
|
+
logLevel: {
|
|
335
|
+
response: 'debug',
|
|
336
|
+
responseDetails: 'type',
|
|
337
|
+
request: 'debug',
|
|
338
|
+
},
|
|
339
|
+
};
|
|
345
340
|
logger.debug('API file does not exist, retrieving it', { url: opts.url }, correlationId);
|
|
346
341
|
return rpRetry(opts)
|
|
347
342
|
.then((result) => {
|
|
@@ -354,9 +349,21 @@ export const getAPIFile = (apiFilename, correlationId, options) => {
|
|
|
354
349
|
if (err.statusCode) {
|
|
355
350
|
throw err;
|
|
356
351
|
}
|
|
357
|
-
throw getRichError('System', 'could not resolve
|
|
352
|
+
throw getRichError('System', 'could not resolve apiDefiniton', { apiFilename }, err);
|
|
358
353
|
})
|
|
359
|
-
.then(
|
|
354
|
+
.then((apiDefinitionResult) => {
|
|
355
|
+
if (apiDefinitionResult.errors.length !== EMPTY) {
|
|
356
|
+
logger.error('errors while resolving definition', { errors: apiDefinitionResult.errors }, correlationId);
|
|
357
|
+
throw getRichError('Parameter', 'errors while resolving definition', { apiFilename, errors: apiDefinitionResult.errors });
|
|
358
|
+
}
|
|
359
|
+
try {
|
|
360
|
+
fs.writeFileSync(apiFilename, JSON.stringify(apiDefinitionResult.spec, null, TAB));
|
|
361
|
+
}
|
|
362
|
+
catch (err) {
|
|
363
|
+
throw getRichError('System', 'file system error', { apiFilename }, err);
|
|
364
|
+
}
|
|
365
|
+
return apiDefinitionResult.spec;
|
|
366
|
+
});
|
|
360
367
|
};
|
|
361
368
|
|
|
362
369
|
/**
|
|
@@ -368,9 +375,9 @@ export const getAPIFile = (apiFilename, correlationId, options) => {
|
|
|
368
375
|
* @requires @mimik/sumologic-winston-logger
|
|
369
376
|
* @requires @mimik/response-helper
|
|
370
377
|
* @param {object} apiDefinition - JSON object containing the API definition.
|
|
371
|
-
* @param {UUID.<string>} correlationId - CorrelationId when logging
|
|
372
|
-
* @return
|
|
373
|
-
* @throws An error is thrown
|
|
378
|
+
* @param {UUID.<string>} correlationId - CorrelationId when logging activites.
|
|
379
|
+
* @return An array of the known securitySchemes that are in the API definition.
|
|
380
|
+
* @throws An error is thrown for the first validation fails.
|
|
374
381
|
*/
|
|
375
382
|
export const validateSecuritySchemes = (apiDefinition, correlationId) => {
|
|
376
383
|
const existingSecuritySchemes = [];
|
|
@@ -390,7 +397,7 @@ export const validateSecuritySchemes = (apiDefinition, correlationId) => {
|
|
|
390
397
|
|
|
391
398
|
/**
|
|
392
399
|
*
|
|
393
|
-
* Extracts the properties from API
|
|
400
|
+
* Extracts the properties from API definiton and creates a file binding the handler with the controller operations.
|
|
394
401
|
*
|
|
395
402
|
* @function extractProperties
|
|
396
403
|
* @category sync
|
|
@@ -400,9 +407,9 @@ export const validateSecuritySchemes = (apiDefinition, correlationId) => {
|
|
|
400
407
|
* @param {object} apiDefinition - JSON object containing the API definition.
|
|
401
408
|
* @param {PATH.<string>} controllersDirectory - Directory to find the controller files.
|
|
402
409
|
* @param {PATH.<string>} buildDirectory - Directory where the register file will be stored.
|
|
403
|
-
* @param {UUID.<string>} correlationId - CorrelationId when logging
|
|
404
|
-
* @return
|
|
405
|
-
* @throws An error is thrown for many reasons, like operationId does not exist in controllers, controller
|
|
410
|
+
* @param {UUID.<string>} correlationId - CorrelationId when logging activites.
|
|
411
|
+
* @return null
|
|
412
|
+
* @throws An error is thrown for many reasons, like operationId does not exist in controllers, controller dies not exist...
|
|
406
413
|
*/
|
|
407
414
|
export const extractProperties = (apiDefinition, controllersDirectory, buildDirectory, correlationId) => {
|
|
408
415
|
const result = {};
|
|
@@ -486,14 +493,15 @@ export const extractProperties = (apiDefinition, controllersDirectory, buildDire
|
|
|
486
493
|
* @param {PATH.<string>} apiFilename - Name of the file where the API file will be stored.
|
|
487
494
|
* @param {PATH.<string>} controllersDirectory - Directory to find the controller files.
|
|
488
495
|
* @param {PATH.<string>} buildDirectory - Directory where the register file will be stored.
|
|
489
|
-
* @param {UUID.<string>} correlationId - CorrelationId when logging
|
|
490
|
-
* @param {object} options - Options associated with the call. Use to pass `metrics` to `rpRetry` and `apiKey
|
|
491
|
-
* @return {Promise
|
|
492
|
-
*
|
|
496
|
+
* @param {UUID.<string>} correlationId - CorrelationId when logging activites.
|
|
497
|
+
* @param {object} options - Options associated with the call. Use to pass `metrics` to `rpRetry` and `apiKey`` to access private API.
|
|
498
|
+
* @return {Promise}.
|
|
499
|
+
* &fulfil {object} The API file, the API filename, the existing known security schemes and the defined security schemes.
|
|
500
|
+
* @throws {Promise} An error is thrown for many reasons assocated with getAPIFile or validateSecuritySchemes or extractProperties.
|
|
493
501
|
*/
|
|
494
502
|
export const setupServerFiles = (apiFilename, controllersDirectory, buildDirectory, correlationId, options) => getAPIFile(apiFilename, correlationId, options)
|
|
495
503
|
.then((apiDefinition) => {
|
|
496
|
-
const existingSecuritySchemes = validateSecuritySchemes(apiDefinition, correlationId)
|
|
504
|
+
const existingSecuritySchemes = compact(validateSecuritySchemes(apiDefinition, correlationId));
|
|
497
505
|
|
|
498
506
|
extractProperties(apiDefinition, controllersDirectory, buildDirectory, correlationId);
|
|
499
507
|
const schemes = apiDefinition.components?.securitySchemes;
|
package/lib/ajvHelpers.js
CHANGED
|
@@ -14,7 +14,7 @@ const ajvFormats = origFormats => (ajv) => {
|
|
|
14
14
|
validate: /((^\s*((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))\s*$)|(^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$))/u,
|
|
15
15
|
},
|
|
16
16
|
};
|
|
17
|
-
const libFormats =
|
|
17
|
+
const libFormats = DEFAULT_FORMATS;
|
|
18
18
|
let formats = origFormats;
|
|
19
19
|
|
|
20
20
|
if (!formats) formats = {};
|
package/lib/baseHandlers.js
CHANGED
|
@@ -34,8 +34,9 @@ const unauthorizedHandler = (con, req, res) => {
|
|
|
34
34
|
let error = new Error('Unauthorized');
|
|
35
35
|
|
|
36
36
|
error.statusCode = UNAUTHORIZED_ERROR;
|
|
37
|
-
const schemes = Object.keys(con.security)
|
|
37
|
+
const schemes = Object.keys(con.security);
|
|
38
38
|
|
|
39
|
+
delete schemes.authorized;
|
|
39
40
|
schemes.forEach((scheme) => {
|
|
40
41
|
const { error: schemeError } = con.security[scheme] || {};
|
|
41
42
|
if (schemeError) {
|
|
@@ -48,7 +49,7 @@ const unauthorizedHandler = (con, req, res) => {
|
|
|
48
49
|
const notImplemented = (con, req, res) => {
|
|
49
50
|
const { method } = req;
|
|
50
51
|
const path = req.url;
|
|
51
|
-
const error = new Error(`${method} ${path} defined in Swagger specification, but
|
|
52
|
+
const error = new Error(`${req.method} ${path} defined in Swagger specification, but no implemented`);
|
|
52
53
|
|
|
53
54
|
error.statusCode = NOT_IMPLEMENTED_ERROR;
|
|
54
55
|
error.info = {
|
|
@@ -13,7 +13,7 @@ const validateOauth2 = (securitySchemes, securityType, flow) => {
|
|
|
13
13
|
if (security.type !== OAUTH2) {
|
|
14
14
|
throw getRichError('System', `auth type is not ${OAUTH2}`, { securityType, receivedAuth: security.type, expectedAuth: OAUTH2 });
|
|
15
15
|
}
|
|
16
|
-
if (!security.flows
|
|
16
|
+
if (!security.flows[flow]) {
|
|
17
17
|
throw getRichError('System', 'no flow type available', { securityType, flow });
|
|
18
18
|
}
|
|
19
19
|
return securityType;
|
package/lib/securityHandlers.js
CHANGED
|
@@ -19,6 +19,8 @@ import {
|
|
|
19
19
|
USER_SECURITY,
|
|
20
20
|
} from './common.js';
|
|
21
21
|
import { TOKEN_PARAMS } from '@mimik/swagger-helper';
|
|
22
|
+
import difference from 'lodash.difference';
|
|
23
|
+
import intersection from 'lodash.intersection';
|
|
22
24
|
import jwt from 'jsonwebtoken';
|
|
23
25
|
|
|
24
26
|
const UNAUTHORIZED_ERROR = 401;
|
|
@@ -91,7 +93,7 @@ const checkHeaders = (headers) => {
|
|
|
91
93
|
|
|
92
94
|
const checkScopes = (tokenScopes, defScopes, definition) => {
|
|
93
95
|
if (!tokenScopes) {
|
|
94
|
-
throw
|
|
96
|
+
throw new Error('no scope in authorization token');
|
|
95
97
|
}
|
|
96
98
|
let claims = [];
|
|
97
99
|
let onBehalf = false;
|
|
@@ -124,10 +126,10 @@ const checkScopes = (tokenScopes, defScopes, definition) => {
|
|
|
124
126
|
}
|
|
125
127
|
const includedClaims = analyzedScope[CLAIMS_INDEX].split(CLAIMS_SEPARATOR);
|
|
126
128
|
const definitionClaims = Object.keys(includedDefinition);
|
|
127
|
-
const claimsIntersects = includedClaims
|
|
129
|
+
const claimsIntersects = intersection(includedClaims, definitionClaims);
|
|
128
130
|
|
|
129
131
|
if (claimsIntersects.length !== includedClaims.length) {
|
|
130
|
-
throw getError(`incorrect claims included: ${includedClaims
|
|
132
|
+
throw getError(`incorrect claims included: ${difference(includedClaims, claimsIntersects)}`, FORBIDDEN_ERROR);
|
|
131
133
|
}
|
|
132
134
|
claims = claims.concat(claimsIntersects);
|
|
133
135
|
}
|
package/package.json
CHANGED
|
@@ -1,17 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mimik/api-helper",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.10",
|
|
4
4
|
"description": "helper for openAPI backend and mimik service",
|
|
5
5
|
"main": "./index.js",
|
|
6
|
-
"type": "module",
|
|
6
|
+
"type": "module",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"lint": "eslint . --no-error-on-unmatched-pattern",
|
|
9
9
|
"docs": "jsdoc2md index.js > README.md",
|
|
10
|
-
"test": "
|
|
11
|
-
"test-ci": "
|
|
10
|
+
"test": "echo \"Error: no test specified\" && exit 0",
|
|
11
|
+
"test-ci": "echo \"Error: no test specified\" && exit 0",
|
|
12
12
|
"prepublishOnly": "npm run docs && npm run lint && npm run test-ci",
|
|
13
13
|
"commit-ready": "npm run docs && npm run lint && npm run test-ci"
|
|
14
14
|
},
|
|
15
|
+
"husky": {
|
|
16
|
+
"hooks": {
|
|
17
|
+
"pre-commit": "npm run commit-ready",
|
|
18
|
+
"pre-push": "npm run test"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
15
21
|
"keywords": [
|
|
16
22
|
"mimik",
|
|
17
23
|
"microservice",
|
|
@@ -19,37 +25,33 @@
|
|
|
19
25
|
],
|
|
20
26
|
"author": "mimik technology inc <support@mimik.com> (https://developer.mimik.com/)",
|
|
21
27
|
"license": "MIT",
|
|
22
|
-
"engines": {
|
|
23
|
-
"node": ">=24"
|
|
24
|
-
},
|
|
25
28
|
"repository": {
|
|
26
29
|
"type": "git",
|
|
27
30
|
"url": "https://bitbucket.org/mimiktech/api-helper"
|
|
28
31
|
},
|
|
29
32
|
"dependencies": {
|
|
30
|
-
"@mimik/request-
|
|
31
|
-
"@mimik/
|
|
32
|
-
"@mimik/
|
|
33
|
-
"@mimik/
|
|
33
|
+
"@mimik/request-helper":"^2.0.2",
|
|
34
|
+
"@mimik/request-retry": "^4.0.3",
|
|
35
|
+
"@mimik/response-helper": "^4.0.4",
|
|
36
|
+
"@mimik/sumologic-winston-logger": "^2.0.3",
|
|
37
|
+
"@mimik/swagger-helper": "^5.0.2",
|
|
34
38
|
"ajv-formats": "3.0.1",
|
|
35
|
-
"js-base64": "3.7.
|
|
36
|
-
"js-yaml":
|
|
37
|
-
"jsonwebtoken": "9.0.
|
|
38
|
-
"
|
|
39
|
-
"
|
|
39
|
+
"js-base64": "3.7.7",
|
|
40
|
+
"js-yaml":"4.1.0",
|
|
41
|
+
"jsonwebtoken": "9.0.2",
|
|
42
|
+
"lodash.compact": "3.0.1",
|
|
43
|
+
"lodash.difference": "4.5.0",
|
|
44
|
+
"lodash.intersection": "4.4.0",
|
|
45
|
+
"openapi-backend": "5.13.0",
|
|
46
|
+
"swagger-client": "3.35.6"
|
|
40
47
|
},
|
|
41
48
|
"devDependencies": {
|
|
42
49
|
"@eslint/js": "9.32.0",
|
|
43
50
|
"@mimik/eslint-plugin-document-env": "^2.0.8",
|
|
44
|
-
"@stylistic/eslint-plugin": "5.
|
|
45
|
-
"c8": "10.1.3",
|
|
46
|
-
"chai": "6.2.2",
|
|
51
|
+
"@stylistic/eslint-plugin": "5.2.2",
|
|
47
52
|
"eslint": "9.32.0",
|
|
48
53
|
"eslint-plugin-import": "2.32.0",
|
|
49
|
-
"esmock": "2.7.3",
|
|
50
|
-
"globals": "17.3.0",
|
|
51
54
|
"husky": "9.1.7",
|
|
52
|
-
"jsdoc-to-markdown": "9.1.
|
|
53
|
-
"mocha": "11.7.5"
|
|
55
|
+
"jsdoc-to-markdown": "9.1.2"
|
|
54
56
|
}
|
|
55
57
|
}
|
package/.husky/pre-commit
DELETED
package/.husky/pre-push
DELETED