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