@mimik/api-helper 3.0.1 → 3.0.3

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/README.md CHANGED
@@ -39,19 +39,19 @@ import { apiSetup, securityLib, getAPIFile, validateSecuritySchemes, extractProp
39
39
  <a name="module_api-helper..apiSetup"></a>
40
40
 
41
41
  ### api-helper~apiSetup(setup, registeredOperations, securityHandlers, extraFormats, config, correlationId) ⇒ <code>Promise.&lt;object&gt;</code>
42
- Setup the API to be used for a service
42
+ Set up the API to be used for a service
43
43
 
44
44
  **Kind**: inner method of [<code>api-helper</code>](#module_api-helper)
45
- **Returns**: <code>Promise.&lt;object&gt;</code> - The API file itself.
45
+ **Returns**: <code>Promise.&lt;object&gt;</code> - The initialized OpenAPIBackend instance.
46
46
  **Category**: async
47
47
  **Throws**:
48
48
 
49
- - <code>Promise</code> An error is thrown if the initialization failed.
49
+ - <code>Error</code> Rejects with an error if the initialization failed.
50
50
 
51
51
  The following scheme names are reserved: `SystemSecurity`, `AdminSecurity`, `UserSecurity`, `PeerSecurity`, `ApiKeySecurity`.
52
52
  The following security schemes can be defaulted: `SystemSecurity`, `AdminSecurity`, `UserSecurity`, `ApiKeySecurity`.
53
53
  The secOptions in the options property passed when using `init` allows the following operations:
54
- - introduce a customer security scheme, in this case secOptions contains: { newSecurityScheme: {function}newSecurityHandler },
54
+ - introduce a custom security scheme, in this case secOptions contains: { newSecurityScheme: {function}newSecurityHandler },
55
55
  - disable a security scheme that is defined in the swagger API, in this case secOptions contains: { securitySchemeToDisable: { {boolean}notEnabled: true } },
56
56
  - overwrite an existing security scheme, in this case secOptions contains: { securitySchemeToOverwrite: {function}newSecurityHandler }.
57
57
  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.
@@ -64,23 +64,26 @@ The default formats for validation are: `date`, `time`, `date-time`, `byte`, `uu
64
64
  | Param | Type | Description |
65
65
  | --- | --- | --- |
66
66
  | setup | <code>object</code> | Object containing the apiFilename and the existing security schemes in the API definition. |
67
- | registeredOperations | <code>object</code> | List of the operation to register for the API. |
68
- | securityHandlers | <code>object</code> | List of the security handlers to add for the service. |
69
- | extraFormats | <code>object</code> | list of the formats to add for validating properties. |
67
+ | setup.apiFilename | [<code>PATH</code>](#PATH) | Path to the resolved API definition file. |
68
+ | setup.existingSecuritySchemes | <code>Array.&lt;string&gt;</code> | Known security scheme names present in the API definition. |
69
+ | setup.definedSecuritySchemes | <code>Array.&lt;string&gt;</code> | All security scheme names defined in the API definition. |
70
+ | registeredOperations | <code>object</code> | Map of operationId to handler function to register for the API. |
71
+ | securityHandlers | <code>object</code> | Map of security scheme name to handler object to add for the service. |
72
+ | extraFormats | <code>object</code> | Map of format name to format definition for validating properties. Each entry is either an empty object (to use a built-in ajv-formats format) or an object with `type` and `validate` properties (to define a custom format). |
70
73
  | config | <code>object</code> | Configuration of the service. |
71
74
  | correlationId | [<code>UUID</code>](#UUID) | CorrelationId when logging activities. |
72
75
 
73
76
  <a name="module_api-helper..getAPIFile"></a>
74
77
 
75
78
  ### api-helper~getAPIFile(apiFilename, correlationId, options) ⇒ <code>Promise.&lt;object&gt;</code>
76
- Gets the API file from swaggerhub and stores it in the given PATH location.
79
+ Gets and resolves the API definition, loading from local file, Bitbucket, or SwaggerHub, and stores it in the given PATH location.
77
80
 
78
81
  **Kind**: inner method of [<code>api-helper</code>](#module_api-helper)
79
82
  **Returns**: <code>Promise.&lt;object&gt;</code> - The API file itself.
80
83
  **Category**: async
81
84
  **Throws**:
82
85
 
83
- - <code>Promise</code> 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.
86
+ - <code>Error</code> Rejects with an error if the apiFilename resolution generates an error or the request to the API provider fails or the file cannot be saved.
84
87
 
85
88
  `apiInfo` options has the following format:
86
89
  ``` javascript
@@ -105,14 +108,14 @@ Gets the API file from swaggerhub and stores it in the given PATH location.
105
108
  <a name="module_api-helper..setupServerFiles"></a>
106
109
 
107
110
  ### api-helper~setupServerFiles(apiFilename, controllersDirectory, buildDirectory, correlationId, options) ⇒ <code>Promise.&lt;object&gt;</code>
108
- Setup and validates files for the server
111
+ Sets up and validates files for the server
109
112
 
110
113
  **Kind**: inner method of [<code>api-helper</code>](#module_api-helper)
111
114
  **Returns**: <code>Promise.&lt;object&gt;</code> - The API file, the API filename, the existing known security schemes and the defined security schemes.
112
115
  **Category**: async
113
116
  **Throws**:
114
117
 
115
- - <code>Promise</code> An error is thrown for many reasons associated with getAPIFile or validateSecuritySchemes or extractProperties.
118
+ - <code>Error</code> Rejects with an error for many reasons associated with getAPIFile or validateSecuritySchemes or extractProperties.
116
119
 
117
120
  **Requires**: <code>module:@mimik/request-retry</code>, <code>module:@mimik/response-helper</code>, <code>module:@mimik/sumologic-winston-logger</code>, <code>module:fs</code>, <code>module:js-yaml</code>, <code>module:path</code>
118
121
 
@@ -127,16 +130,16 @@ Setup and validates files for the server
127
130
  <a name="module_api-helper..securityLib"></a>
128
131
 
129
132
  ### api-helper~securityLib(config) ⇒ <code>object</code>
130
- Implement the security flows for the API.
133
+ Implements the security flows for the API.
131
134
 
132
135
  **Kind**: inner method of [<code>api-helper</code>](#module_api-helper)
133
136
  **Returns**: <code>object</code> - An object containing `SystemSecurity`, `AdminSecurity`, `UserSecurity`, and `ApiKeySecurity` handlers.
134
137
 
135
- This function is used to setup the following security handlers for the API:
136
- - `SystemSecurity` - used for the system operations, like /system, /onbehalf
137
- - `AdminSecurity` - used for the admin operations, like /admin,
138
- - `UserSecurity` - used for the user operations, like /user,
139
- - `ApiKeySecurity` - used for the API key operations, like /apikey,
138
+ This function is used to set up the following security handlers for the API:
139
+ - `SystemSecurity` - used for system-to-system operations, validates client credentials tokens.
140
+ - `AdminSecurity` - used for admin operations, validates admin/subAdmin client credentials tokens.
141
+ - `UserSecurity` - used for user operations, validates implicit flow tokens.
142
+ - `ApiKeySecurity` - used for API key authenticated operations, validates API keys from headers.
140
143
  The security handlers are used to validate the tokens and scopes for the API operations.
141
144
  **Category**: sync
142
145
  **Requires**: <code>module:@mimik/swagger-helper</code>, <code>module:jsonwebtoken</code>
@@ -155,7 +158,7 @@ Validates the known SecuritySchemes: `SystemSecurity`, `AdminSecurity`, `UserSec
155
158
  **Category**: sync
156
159
  **Throws**:
157
160
 
158
- - An error is thrown if a validation fails.
161
+ - <code>Error</code> An error is thrown if a validation fails.
159
162
 
160
163
  **Requires**: <code>module:@mimik/sumologic-winston-logger</code>, <code>module:@mimik/response-helper</code>
161
164
 
@@ -167,13 +170,13 @@ Validates the known SecuritySchemes: `SystemSecurity`, `AdminSecurity`, `UserSec
167
170
  <a name="module_api-helper..extractProperties"></a>
168
171
 
169
172
  ### api-helper~extractProperties(apiDefinition, controllersDirectory, buildDirectory, correlationId) ⇒ <code>void</code>
170
- Extracts the properties from API definition and creates a file binding the handler with the controller operations.
173
+ Extracts the properties from API definition and creates a `register.js` file in the build directory binding the handler with the controller operations.
171
174
 
172
175
  **Kind**: inner method of [<code>api-helper</code>](#module_api-helper)
173
176
  **Category**: sync
174
177
  **Throws**:
175
178
 
176
- - An error is thrown for many reasons, like operationId does not exist in controllers, controller does not exist...
179
+ - <code>Error</code> An error is thrown for many reasons, like operationId does not exist in controllers, controller does not exist...
177
180
 
178
181
  **Requires**: <code>module:@mimik/response-helper</code>, <code>module:@mimik/sumologic-winston-logger</code>, <code>module:fs</code>
179
182
 
package/index.js CHANGED
@@ -62,7 +62,7 @@ const API_VERSION_INDEX = 2;
62
62
  const POSTFIX_INDEX = 3;
63
63
  /**
64
64
  *
65
- * Implement the security flows for the API.
65
+ * Implements the security flows for the API.
66
66
  *
67
67
  * @function securityLib
68
68
  * @category sync
@@ -71,37 +71,40 @@ const POSTFIX_INDEX = 3;
71
71
  * @param {object} config - Configuration of the service.
72
72
  * @return {object} An object containing `SystemSecurity`, `AdminSecurity`, `UserSecurity`, and `ApiKeySecurity` handlers.
73
73
  *
74
- * This function is used to setup the following security handlers for the API:
75
- * - `SystemSecurity` - used for the system operations, like /system, /onbehalf
76
- * - `AdminSecurity` - used for the admin operations, like /admin,
77
- * - `UserSecurity` - used for the user operations, like /user,
78
- * - `ApiKeySecurity` - used for the API key operations, like /apikey,
74
+ * This function is used to set up the following security handlers for the API:
75
+ * - `SystemSecurity` - used for system-to-system operations, validates client credentials tokens.
76
+ * - `AdminSecurity` - used for admin operations, validates admin/subAdmin client credentials tokens.
77
+ * - `UserSecurity` - used for user operations, validates implicit flow tokens.
78
+ * - `ApiKeySecurity` - used for API key authenticated operations, validates API keys from headers.
79
79
  * The security handlers are used to validate the tokens and scopes for the API operations.
80
80
  */
81
81
  export { securityLib };
82
82
 
83
83
  /**
84
84
  *
85
- * Setup the API to be used for a service
85
+ * Set up the API to be used for a service
86
86
  *
87
87
  * @function apiSetup
88
88
  * @category async
89
89
  * @requires @mimik/response-helper
90
90
  * @requires @mimik/sumologic-winston-logger
91
91
  * @requires openapi-backend
92
- * @param {object} setup - Object containing the apiFilename and the existing security schemes in the API definition.
93
- * @param {object} registeredOperations - List of the operation to register for the API.
94
- * @param {object} securityHandlers - List of the security handlers to add for the service.
95
- * @param {object} extraFormats - list of the formats to add for validating properties.
92
+ * @param {object} setup - Object containing the apiFilename and the existing security schemes in the API definition.
93
+ * @param {PATH} setup.apiFilename - Path to the resolved API definition file.
94
+ * @param {Array.<string>} setup.existingSecuritySchemes - Known security scheme names present in the API definition.
95
+ * @param {Array.<string>} setup.definedSecuritySchemes - All security scheme names defined in the API definition.
96
+ * @param {object} registeredOperations - Map of operationId to handler function to register for the API.
97
+ * @param {object} securityHandlers - Map of security scheme name to handler object to add for the service.
98
+ * @param {object} extraFormats - Map of format name to format definition for validating properties. Each entry is either an empty object (to use a built-in ajv-formats format) or an object with `type` and `validate` properties (to define a custom format).
96
99
  * @param {object} config - Configuration of the service.
97
100
  * @param {UUID} correlationId - CorrelationId when logging activities.
98
- * @return {Promise.<object>} The API file itself.
99
- * @throws {Promise} An error is thrown if the initialization failed.
101
+ * @return {Promise.<object>} The initialized OpenAPIBackend instance.
102
+ * @throws {Error} Rejects with an error if the initialization failed.
100
103
  *
101
104
  * The following scheme names are reserved: `SystemSecurity`, `AdminSecurity`, `UserSecurity`, `PeerSecurity`, `ApiKeySecurity`.
102
105
  * The following security schemes can be defaulted: `SystemSecurity`, `AdminSecurity`, `UserSecurity`, `ApiKeySecurity`.
103
106
  * The secOptions in the options property passed when using `init` allows the following operations:
104
- * - introduce a customer security scheme, in this case secOptions contains: { newSecurityScheme: {function}newSecurityHandler },
107
+ * - introduce a custom security scheme, in this case secOptions contains: { newSecurityScheme: {function}newSecurityHandler },
105
108
  * - disable a security scheme that is defined in the swagger API, in this case secOptions contains: { securitySchemeToDisable: { {boolean}notEnabled: true } },
106
109
  * - overwrite an existing security scheme, in this case secOptions contains: { securitySchemeToOverwrite: {function}newSecurityHandler }.
107
110
  * 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.
@@ -132,7 +135,7 @@ export const apiSetup = (setup, registeredOperations, securityHandlers, extraFor
132
135
  let mode;
133
136
 
134
137
  api.register(registeredOperations);
135
- if (config && (config.nodeEnvironment.toLowerCase() !== LOCAL || config.serverSettings.securitySet === SET_ON)) {
138
+ if (config.nodeEnvironment.toLowerCase() !== LOCAL || config.serverSettings.securitySet === SET_ON) {
136
139
  mode = SECURITY_ON;
137
140
  }
138
141
  else {
@@ -160,7 +163,7 @@ export const apiSetup = (setup, registeredOperations, securityHandlers, extraFor
160
163
  if (unusedSecuritySchemes.length !== EMPTY) throw getRichError('System', 'unused handlers for security schemes', { unusedSecuritySchemes });
161
164
 
162
165
  remainingSecurities.forEach((securityScheme) => {
163
- if (!securityHandlerNames.includes(securityScheme) && !securityHandlers[securityScheme]?.notEnabled) {
166
+ if (!securityHandlerNames.includes(securityScheme)) {
164
167
  throw getRichError('System', 'missing handler for security scheme', { securityScheme });
165
168
  }
166
169
  });
@@ -173,7 +176,7 @@ export const apiSetup = (setup, registeredOperations, securityHandlers, extraFor
173
176
  else if (remainingSecurities.length !== EMPTY) throw getRichError('System', 'missing handlers for security schemes', { missingSecuritySchemes: remainingSecurities });
174
177
  return api.init()
175
178
  .catch((err) => {
176
- throw getRichError('System', 'could not initialize the api', { api }, err);
179
+ throw getRichError('System', 'could not initialize the api', { apiFilename }, err);
177
180
  })
178
181
  .then(() => api);
179
182
  };
@@ -236,7 +239,7 @@ const buildProviderRequest = (params, apiInfo, apiFilename) => {
236
239
 
237
240
  /**
238
241
  *
239
- * Gets the API file from swaggerhub and stores it in the given PATH location.
242
+ * Gets and resolves the API definition, loading from local file, Bitbucket, or SwaggerHub, and stores it in the given PATH location.
240
243
  *
241
244
  * @function getAPIFile
242
245
  * @category async
@@ -246,11 +249,11 @@ const buildProviderRequest = (params, apiInfo, apiFilename) => {
246
249
  * @requires fs
247
250
  * @requires js-yaml
248
251
  * @requires path
249
- * @param {PATH} apiFilename - Name of the file where the API file will be stored.
252
+ * @param {PATH} apiFilename - Name of the file where the API file will be stored.
250
253
  * @param {UUID} correlationId - CorrelationId when logging activities.
251
254
  * @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.
252
255
  * @return {Promise.<object>} The API file itself.
253
- * @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.
256
+ * @throws {Error} Rejects with an error if the apiFilename resolution generates an error or the request to the API provider fails or the file cannot be saved.
254
257
  *
255
258
  * `apiInfo` options has the following format:
256
259
  * ``` javascript
@@ -378,7 +381,7 @@ export const getAPIFile = (apiFilename, correlationId, options) => {
378
381
  * @param {object} apiDefinition - JSON object containing the API definition.
379
382
  * @param {UUID} correlationId - CorrelationId when logging activities.
380
383
  * @return {Array.<string>} An array of the known securitySchemes that are in the API definition.
381
- * @throws An error is thrown if a validation fails.
384
+ * @throws {Error} An error is thrown if a validation fails.
382
385
  */
383
386
  export const validateSecuritySchemes = (apiDefinition, correlationId) => {
384
387
  const existingSecuritySchemes = [];
@@ -398,7 +401,7 @@ export const validateSecuritySchemes = (apiDefinition, correlationId) => {
398
401
 
399
402
  /**
400
403
  *
401
- * Extracts the properties from API definition and creates a file binding the handler with the controller operations.
404
+ * Extracts the properties from API definition and creates a `register.js` file in the build directory binding the handler with the controller operations.
402
405
  *
403
406
  * @function extractProperties
404
407
  * @category sync
@@ -410,7 +413,7 @@ export const validateSecuritySchemes = (apiDefinition, correlationId) => {
410
413
  * @param {PATH} buildDirectory - Directory where the register file will be stored.
411
414
  * @param {UUID} correlationId - CorrelationId when logging activities.
412
415
  * @return {void}
413
- * @throws An error is thrown for many reasons, like operationId does not exist in controllers, controller does not exist...
416
+ * @throws {Error} An error is thrown for many reasons, like operationId does not exist in controllers, controller does not exist...
414
417
  */
415
418
  export const extractProperties = (apiDefinition, controllersDirectory, buildDirectory, correlationId) => {
416
419
  const result = {};
@@ -455,17 +458,19 @@ export const extractProperties = (apiDefinition, controllersDirectory, buildDire
455
458
  catch (err) {
456
459
  throw getRichError('System', 'file system error', { controllerFilename }, err);
457
460
  }
461
+ let file;
462
+
458
463
  try {
459
- const file = fs.readFileSync(controllerFilename, 'utf8');
460
- if (!file.includes(`${operation.operationId},`)
461
- && !file.includes(`${operation.operationId}:`)
462
- && !file.includes(`export ${operation.operationId}`)) { // code must be linted before
463
- throw getRichError('System', 'missing operationId in controller file', { controllerFilename, operationId: operation.operationId });
464
- }
464
+ file = fs.readFileSync(controllerFilename, 'utf8');
465
465
  }
466
466
  catch (err) {
467
467
  throw getRichError('System', 'file system error', { controllerFilename, operationId: operation.operationId }, err);
468
468
  }
469
+ if (!file.includes(`${operation.operationId},`)
470
+ && !file.includes(`${operation.operationId}:`)
471
+ && !file.includes(`export ${operation.operationId}`)) { // code must be linted before
472
+ throw getRichError('System', 'missing operationId in controller file', { controllerFilename, operationId: operation.operationId });
473
+ }
469
474
  if (result[controller]) result[controller].push(operation.operationId);
470
475
  else result[controller] = [operation.operationId];
471
476
  }
@@ -481,7 +486,7 @@ export const extractProperties = (apiDefinition, controllersDirectory, buildDire
481
486
 
482
487
  /**
483
488
  *
484
- * Setup and validates files for the server
489
+ * Sets up and validates files for the server
485
490
  *
486
491
  * @function setupServerFiles
487
492
  * @category async
@@ -491,13 +496,13 @@ export const extractProperties = (apiDefinition, controllersDirectory, buildDire
491
496
  * @requires fs
492
497
  * @requires js-yaml
493
498
  * @requires path
494
- * @param {PATH} apiFilename - Name of the file where the API file will be stored.
499
+ * @param {PATH} apiFilename - Name of the file where the API file will be stored.
495
500
  * @param {PATH} controllersDirectory - Directory to find the controller files.
496
501
  * @param {PATH} buildDirectory - Directory where the register file will be stored.
497
502
  * @param {UUID} correlationId - CorrelationId when logging activities.
498
503
  * @param {object} options - Options associated with the call. Use to pass `metrics` to `rpRetry` and `apiKey` to access private API.
499
504
  * @return {Promise.<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 associated with getAPIFile or validateSecuritySchemes or extractProperties.
505
+ * @throws {Error} Rejects with an error for many reasons associated with getAPIFile or validateSecuritySchemes or extractProperties.
501
506
  */
502
507
  export const setupServerFiles = (apiFilename, controllersDirectory, buildDirectory, correlationId, options) => getAPIFile(apiFilename, correlationId, options)
503
508
  .then((apiDefinition) => {
@@ -1,14 +1,10 @@
1
+ import { ERROR_CODE } from '@mimik/response-helper';
1
2
  import { rejectRequest } from '@mimik/swagger-helper';
2
3
 
3
- const PARAMETER_ERROR = 400;
4
- const NOT_FOUND_ERROR = 404;
5
- const UNAUTHORIZED_ERROR = 401;
6
- const NOT_IMPLEMENTED_ERROR = 501;
7
-
8
4
  const validationFail = (con, req, res) => {
9
5
  const error = new Error('Failed schema validation');
10
6
 
11
- error.statusCode = PARAMETER_ERROR;
7
+ error.statusCode = ERROR_CODE.PARAMETER;
12
8
  error.info = {
13
9
  method: req.method,
14
10
  path: req.url,
@@ -22,7 +18,7 @@ const notFound = (con, req, res) => {
22
18
  const path = req.url;
23
19
  const error = new Error(`path ${path} not defined in Swagger specification`);
24
20
 
25
- error.statusCode = NOT_FOUND_ERROR;
21
+ error.statusCode = ERROR_CODE.NOT_FOUND;
26
22
  error.info = {
27
23
  method: req.method,
28
24
  path,
@@ -33,7 +29,7 @@ const notFound = (con, req, res) => {
33
29
  const unauthorizedHandler = (con, req, res) => {
34
30
  let error = new Error('Unauthorized');
35
31
 
36
- error.statusCode = UNAUTHORIZED_ERROR;
32
+ error.statusCode = ERROR_CODE.UNAUTHORIZED;
37
33
  const schemes = Object.keys(con.security).filter(key => key !== 'authorized');
38
34
 
39
35
  schemes.forEach((scheme) => {
@@ -50,7 +46,7 @@ const notImplemented = (con, req, res) => {
50
46
  const path = req.url;
51
47
  const error = new Error(`${method} ${path} defined in Swagger specification, but not implemented`);
52
48
 
53
- error.statusCode = NOT_IMPLEMENTED_ERROR;
49
+ error.statusCode = ERROR_CODE.NOT_IMPLEMENTED;
54
50
  error.info = {
55
51
  method,
56
52
  path,
@@ -64,7 +60,7 @@ const methodNotAllowed = (con, req, res) => {
64
60
  const path = req.url;
65
61
  const error = new Error(`path ${path} defined in Swagger specification, but the method ${method} is not defined`);
66
62
 
67
- error.statusCode = NOT_IMPLEMENTED_ERROR;
63
+ error.statusCode = ERROR_CODE.NOT_ALLOWED;
68
64
  error.info = {
69
65
  method,
70
66
  path,
@@ -72,24 +68,10 @@ const methodNotAllowed = (con, req, res) => {
72
68
  rejectRequest(error, con, res);
73
69
  };
74
70
 
75
- /*
76
- const postResponseHandler = (con, req, res) => {
77
- const valid = con.api.validateResponse(con.response, con.operation);
78
- console.log('----->', con.response);
79
- console.log('----->', valid);
80
- if (valid.errors) {
81
- // response validation failed
82
- return res.status(502).json({ status: 502, err: valid.errors });
83
- }
84
- return res.status(200).json(con.response);
85
- };
86
- */
87
-
88
71
  export default {
89
72
  validationFail,
90
73
  notFound,
91
74
  unauthorizedHandler,
92
75
  notImplemented,
93
76
  methodNotAllowed,
94
- // postResponseHandler,
95
77
  };
package/lib/common.js CHANGED
@@ -50,7 +50,7 @@ const CLAIMS_SEPARATOR = ',';
50
50
  const BEARERS = ['bearer', 'Bearer'];
51
51
  const USER = 'user';
52
52
  const CLUSTER = 'cluster';
53
- const ESLINT_HEAD = '/* eslint sort-imports:"off" */\n';
53
+ const ESLINT_HEAD = '/* eslint sort-imports: off */\n';
54
54
 
55
55
  export {
56
56
  X_ROUTER_CONTROLLER,
@@ -25,23 +25,18 @@ const saveProperties = (extractResult, buildDirectory, controllersDirectoryName,
25
25
  }
26
26
  const controllers = Object.keys(extractResult);
27
27
  const operationIds = [];
28
- let stringToSave = ESLINT_HEAD;
29
- let itemToSave;
28
+ const imports = [];
30
29
 
31
30
  controllers.forEach((controller) => {
32
- itemToSave = '';
33
- extractResult[controller].forEach((operationId) => {
34
- itemToSave += ` ${operationId},\n`;
31
+ const items = extractResult[controller].map((operationId) => {
35
32
  operationIds.push(operationId);
33
+ return ` ${operationId},`;
36
34
  });
37
- stringToSave += `import {\n${itemToSave}} from '../${controllersDirectoryName}/${controller}${EXTENSION}';\n`;
38
- });
39
- stringToSave += '\nexport {\n';
40
- itemToSave = '';
41
- operationIds.forEach((operationId) => {
42
- itemToSave += ` ${operationId},\n`;
35
+
36
+ imports.push(`import {\n${items.join('\n')}\n} from '../${controllersDirectoryName}/${controller}${EXTENSION}';`);
43
37
  });
44
- stringToSave += `${itemToSave}};\n`;
38
+ const exportLines = operationIds.map(id => ` ${id},\n`).join('');
39
+ const stringToSave = `${ESLINT_HEAD}${imports.join('\n')}\n\nexport {\n${exportLines}};\n`;
45
40
  logger.info(`creating ${filename}`, { filename }, correlationId);
46
41
  try {
47
42
  fs.writeFileSync(filename, stringToSave);
@@ -28,10 +28,10 @@ const validateApiKey = (securitySchemes, securityType) => {
28
28
 
29
29
  if (security) {
30
30
  if (security.in !== API_KEY_IN) {
31
- throw getRichError('System', `apikey security must be in ${API_KEY_IN}`, { securityType, receivedIn: security.in, expectedIn: API_KEY_IN });
31
+ throw getRichError('System', `API key security must be in ${API_KEY_IN}`, { securityType, receivedIn: security.in, expectedIn: API_KEY_IN });
32
32
  }
33
33
  if (security.name !== API_KEY_NAME) {
34
- throw getRichError('System', `apikey security must be named ${API_KEY_NAME}`, { securityType, receivedName: security.name, expectedName: API_KEY_NAME });
34
+ throw getRichError('System', `API key security must be named ${API_KEY_NAME}`, { securityType, receivedName: security.name, expectedName: API_KEY_NAME });
35
35
  }
36
36
  return securityType;
37
37
  }
@@ -36,14 +36,7 @@ const SCOPE_INDEX = 0;
36
36
  const CLAIMS_INDEX = 1;
37
37
  const RESOURCE_INDEX = 0;
38
38
 
39
- const getScopes = (conf, securityType) => {
40
- let scopes = [];
41
-
42
- conf.operation.security.forEach((security) => {
43
- if (security[securityType]) scopes = scopes.concat(security[securityType]);
44
- });
45
- return scopes;
46
- };
39
+ const getScopes = (conf, securityType) => conf.operation.security.flatMap(security => security[securityType] || []);
47
40
 
48
41
  const getError = (message, statusCode) => {
49
42
  const error = new Error(message);
@@ -53,15 +46,8 @@ const getError = (message, statusCode) => {
53
46
  };
54
47
 
55
48
  const checkToken = (authToken) => {
56
- let token;
49
+ const token = jwt.decode(authToken);
57
50
 
58
- try {
59
- token = jwt.decode(authToken);
60
- }
61
- catch (err) {
62
- err.statusCode = UNAUTHORIZED_ERROR;
63
- throw err;
64
- }
65
51
  if (!token) {
66
52
  throw getError('invalid token', UNAUTHORIZED_ERROR);
67
53
  }
@@ -93,17 +79,17 @@ const checkScopes = (tokenScopes, defScopes, definition) => {
93
79
  if (!tokenScopes) {
94
80
  throw getError('no scope in authorization token', UNAUTHORIZED_ERROR);
95
81
  }
96
- let claims = [];
82
+ const claims = [];
97
83
  let onBehalf = false;
98
84
 
99
85
  if (defScopes && defScopes.length !== EMPTY) {
100
86
  const currentScopes = tokenScopes.split(SCOPES_SEPARATOR);
101
87
  const intersects = [];
102
- let resourceIndex = FIRST;
103
88
 
104
89
  currentScopes.forEach((currentScope) => {
105
90
  const analyzedScope = currentScope.split(SCOPE_CLAIMS_SEPARATOR);
106
91
  const analyzedResource = analyzedScope[SCOPE_INDEX].split(RESOURCE_SEPARATOR);
92
+ let resourceIndex = FIRST;
107
93
 
108
94
  if (analyzedResource[RESOURCE_INDEX] === ON_BEHALF) {
109
95
  onBehalf = true;
@@ -129,7 +115,7 @@ const checkScopes = (tokenScopes, defScopes, definition) => {
129
115
  if (claimsIntersects.length !== includedClaims.length) {
130
116
  throw getError(`incorrect claims included: ${includedClaims.filter(cla => !claimsIntersects.includes(cla))}`, FORBIDDEN_ERROR);
131
117
  }
132
- claims = claims.concat(claimsIntersects);
118
+ claims.push(...claimsIntersects);
133
119
  }
134
120
  intersects.push(analyzedScope[SCOPE_INDEX]);
135
121
  }
@@ -141,13 +127,26 @@ const checkScopes = (tokenScopes, defScopes, definition) => {
141
127
  return { onBehalf, claims };
142
128
  };
143
129
 
130
+ const setParam = (req, request, key, value) => {
131
+ req[key] = value;
132
+ request[key] = value;
133
+ };
134
+
135
+ const setParams = (req, request, params) => {
136
+ Object.keys(params).forEach(key => setParam(req, request, key, params[key]));
137
+ };
138
+
139
+ const createMockHandler = params => (con, req) => {
140
+ setParams(req, con.request, params);
141
+ return true;
142
+ };
143
+
144
144
  export const securityLib = (config) => {
145
145
  const verifyTokenClientCredentials = (authToken) => {
146
146
  const { server, generic } = config.security;
147
147
  const options = {
148
148
  audience: (generic.audience === NO_GENERIC) ? server.audience : generic.audience,
149
149
  issuer: server.issuer,
150
- // subject: `${config.serverSettings.id}@clients`,
151
150
  };
152
151
 
153
152
  try {
@@ -186,19 +185,24 @@ export const securityLib = (config) => {
186
185
  }
187
186
  };
188
187
 
188
+ const setClientParams = (req, request, token, scopeResult) => {
189
+ setParam(req, request, TOKEN_PARAMS.claims, scopeResult.claims);
190
+ if (scopeResult.onBehalf) setParam(req, request, TOKEN_PARAMS.onBehalf, true);
191
+ if (token.subType) setParam(req, request, TOKEN_PARAMS.tokenType, token.subType);
192
+ if (token.sub) setParam(req, request, TOKEN_PARAMS.clientId, token.sub);
193
+ if (token.cust) setParam(req, request, TOKEN_PARAMS.customer, token.cust);
194
+ };
195
+
189
196
  const AdminSecurity = {
190
197
  regular: (con, req) => {
191
198
  const authToken = checkHeaders(req.headers);
192
199
  const token = checkToken(authToken);
193
- const { request } = con;
194
200
 
195
201
  if (token.subType !== ADMIN && token.subType !== SUB_ADMIN) {
196
202
  throw getError('invalid token: wrong type', FORBIDDEN_ERROR);
197
203
  }
198
204
  if (token.subType === SUB_ADMIN) {
199
- if (!token.cust) {
200
- throw getError('invalid token: no customer', FORBIDDEN_ERROR);
201
- }
205
+ if (!token.cust) throw getError('invalid token: no customer', FORBIDDEN_ERROR);
202
206
  }
203
207
  else if (token.sub !== `${config.security.admin.externalId}${CLIENT}`) {
204
208
  throw getError(`jwt subject invalid: ${token.sub}`, FORBIDDEN_ERROR);
@@ -206,42 +210,21 @@ export const securityLib = (config) => {
206
210
  verifyTokenClientCredentials(authToken);
207
211
  const scopeResult = checkScopes(token.scope, getScopes(con, ADMIN_SECURITY), con.api.definition);
208
212
 
209
- req[TOKEN_PARAMS.claims] = scopeResult.claims;
210
- request[TOKEN_PARAMS.claims] = scopeResult.claims;
211
- if (token.subType) {
212
- req[TOKEN_PARAMS.tokenType] = token.subType;
213
- request[TOKEN_PARAMS.tokenType] = token.subType;
214
- }
215
- if (token.sub) {
216
- req[TOKEN_PARAMS.clientId] = token.sub;
217
- request[TOKEN_PARAMS.clientId] = token.sub;
218
- }
219
- if (token.cust) {
220
- req[TOKEN_PARAMS.customer] = token.cust;
221
- request[TOKEN_PARAMS.customer] = token.cust;
222
- }
223
- return true;
224
- },
225
- mock: (con, req) => {
226
- const { request } = con;
227
-
228
- req[TOKEN_PARAMS.claims] = ['dummyClaims'];
229
- req[TOKEN_PARAMS.tokenType] = ADMIN;
230
- req[TOKEN_PARAMS.clientId] = 'dummyClientId';
231
- req[TOKEN_PARAMS.customer] = 'dummyCustomer';
232
- request[TOKEN_PARAMS.claims] = ['dummyClaims'];
233
- request[TOKEN_PARAMS.tokenType] = ADMIN;
234
- request[TOKEN_PARAMS.clientId] = 'dummyClientId';
235
- request[TOKEN_PARAMS.customer] = 'dummyCustomer';
213
+ setClientParams(req, con.request, token, scopeResult);
236
214
  return true;
237
215
  },
216
+ mock: createMockHandler({
217
+ [TOKEN_PARAMS.claims]: ['dummyClaims'],
218
+ [TOKEN_PARAMS.tokenType]: ADMIN,
219
+ [TOKEN_PARAMS.clientId]: 'dummyClientId',
220
+ [TOKEN_PARAMS.customer]: 'dummyCustomer',
221
+ }),
238
222
  };
239
223
 
240
224
  const SystemSecurity = {
241
225
  regular: (con, req) => {
242
226
  const authToken = checkHeaders(req.headers);
243
227
  const token = checkToken(authToken);
244
- const { request } = con;
245
228
 
246
229
  if (token.subType === ADMIN || token.subType === SUB_ADMIN) {
247
230
  throw getError('invalid token: wrong type', FORBIDDEN_ERROR);
@@ -249,112 +232,56 @@ export const securityLib = (config) => {
249
232
  verifyTokenClientCredentials(authToken);
250
233
  const scopeResult = checkScopes(token.scope, getScopes(con, SYSTEM_SECURITY), con.api.definition);
251
234
 
252
- if (scopeResult.onBehalf) {
253
- req[TOKEN_PARAMS.onBehalf] = true;
254
- request[TOKEN_PARAMS.onBehalf] = true;
255
- }
256
- req[TOKEN_PARAMS.claims] = scopeResult.claims;
257
- request[TOKEN_PARAMS.claims] = scopeResult.claims;
258
- if (token.subType) {
259
- req[TOKEN_PARAMS.tokenType] = token.subType;
260
- request[TOKEN_PARAMS.tokenType] = token.subType;
261
- }
262
- if (token.sub) {
263
- req[TOKEN_PARAMS.clientId] = token.sub;
264
- request[TOKEN_PARAMS.clientId] = token.sub;
265
- }
266
- if (token.cust) {
267
- req[TOKEN_PARAMS.customer] = token.cust;
268
- request[TOKEN_PARAMS.customer] = token.cust;
269
- }
270
- if (token.type === CLUSTER) {
271
- req[TOKEN_PARAMS.cluster] = true;
272
- request[TOKEN_PARAMS.cluster] = true;
273
- }
274
- return true;
275
- },
276
- mock: (con, req) => {
277
- const { request } = con;
278
-
279
- req[TOKEN_PARAMS.claims] = ['dummyClaims'];
280
- req[TOKEN_PARAMS.tokenType] = 'dummyServiceType';
281
- req[TOKEN_PARAMS.clientId] = 'dummyClientId';
282
- req[TOKEN_PARAMS.customer] = 'dummyCustomer';
283
- request[TOKEN_PARAMS.claims] = ['dummyClaims'];
284
- request[TOKEN_PARAMS.tokenType] = 'dummyServiceType';
285
- request[TOKEN_PARAMS.clientId] = 'dummyClientId';
286
- request[TOKEN_PARAMS.customer] = 'dummyCustomer';
235
+ setClientParams(req, con.request, token, scopeResult);
236
+ if (token.type === CLUSTER) setParam(req, con.request, TOKEN_PARAMS.cluster, true);
287
237
  return true;
288
238
  },
239
+ mock: createMockHandler({
240
+ [TOKEN_PARAMS.claims]: ['dummyClaims'],
241
+ [TOKEN_PARAMS.tokenType]: 'dummyServiceType',
242
+ [TOKEN_PARAMS.clientId]: 'dummyClientId',
243
+ [TOKEN_PARAMS.customer]: 'dummyCustomer',
244
+ }),
289
245
  };
290
246
 
291
247
  const UserSecurity = {
292
248
  regular: (con, req) => {
293
249
  const authToken = checkHeaders(req.headers);
294
250
  const token = checkToken(authToken);
295
- const { request } = con;
296
251
 
297
252
  verifyTokenImplicit(authToken);
298
253
  const scopeResult = checkScopes(token.scope, getScopes(con, USER_SECURITY), con.api.definition);
299
254
 
300
- if (scopeResult.onBehalf) {
301
- req[TOKEN_PARAMS.onBehalf] = true;
302
- request[TOKEN_PARAMS.onBehalf] = true;
303
- }
304
- req[TOKEN_PARAMS.claims] = scopeResult.claims;
305
- request[TOKEN_PARAMS.claims] = scopeResult.claims;
306
- req[TOKEN_PARAMS.tokenType] = USER;
307
- request[TOKEN_PARAMS.tokenType] = USER;
308
- if (token.sub) {
309
- req[TOKEN_PARAMS.userId] = token.sub;
310
- request[TOKEN_PARAMS.userId] = token.sub;
311
- }
312
- if (token.azp) {
313
- req[TOKEN_PARAMS.appId] = token.azp;
314
- request[TOKEN_PARAMS.appId] = token.azp;
315
- }
255
+ if (scopeResult.onBehalf) setParam(req, con.request, TOKEN_PARAMS.onBehalf, true);
256
+ setParam(req, con.request, TOKEN_PARAMS.claims, scopeResult.claims);
257
+ setParam(req, con.request, TOKEN_PARAMS.tokenType, USER);
258
+ if (token.sub) setParam(req, con.request, TOKEN_PARAMS.userId, token.sub);
259
+ if (token.azp) setParam(req, con.request, TOKEN_PARAMS.appId, token.azp);
316
260
  if (token.may_act && token.may_act.sub) {
317
- req[TOKEN_PARAMS.onBehalfId] = token.sub;
318
- request[TOKEN_PARAMS.onBehalfId] = token.sub;
319
- req[TOKEN_PARAMS.userId] = token.may_act.sub;
320
- request[TOKEN_PARAMS.userId] = token.may_act.sub;
261
+ setParam(req, con.request, TOKEN_PARAMS.onBehalfId, token.sub);
262
+ setParam(req, con.request, TOKEN_PARAMS.userId, token.may_act.sub);
321
263
  }
322
264
  return true;
323
265
  },
324
- mock: (con, req) => {
325
- const { request } = con;
326
-
327
- req[TOKEN_PARAMS.claims] = ['dummyClaims'];
328
- req[TOKEN_PARAMS.userId] = 'dummyUserId';
329
- req[TOKEN_PARAMS.appId] = 'dummyAppId';
330
- req[TOKEN_PARAMS.tokenType] = USER;
331
- request[TOKEN_PARAMS.claims] = ['dummyClaims'];
332
- request[TOKEN_PARAMS.userId] = 'dummyUserId';
333
- request[TOKEN_PARAMS.appId] = 'dummyAppId';
334
- request[TOKEN_PARAMS.tokenType] = USER;
335
- return true;
336
- },
266
+ mock: createMockHandler({
267
+ [TOKEN_PARAMS.claims]: ['dummyClaims'],
268
+ [TOKEN_PARAMS.userId]: 'dummyUserId',
269
+ [TOKEN_PARAMS.appId]: 'dummyAppId',
270
+ [TOKEN_PARAMS.tokenType]: USER,
271
+ }),
337
272
  };
338
273
 
339
274
  const ApiKeySecurity = {
340
275
  regular: (con, req) => {
341
276
  const apiKey = req.headers ? req.headers[API_KEY_NAME.toLowerCase()] : null;
342
- const { request } = con;
343
277
 
344
- if (config.security.apiKeys.includes(apiKey)) {
345
- req[API_KEY_NAME] = apiKey;
346
- request[API_KEY_NAME] = apiKey;
278
+ if (config.security.apiKeys && config.security.apiKeys.includes(apiKey)) {
279
+ setParam(req, con.request, API_KEY_NAME, apiKey);
347
280
  return true;
348
281
  }
349
282
  throw getError('invalid API key', UNAUTHORIZED_ERROR);
350
283
  },
351
- mock: (con, req) => {
352
- const { request } = con;
353
-
354
- req[API_KEY_NAME] = 'dummyApiKey';
355
- request[API_KEY_NAME] = 'dummyApiKey';
356
- return true;
357
- },
284
+ mock: createMockHandler({ [API_KEY_NAME]: 'dummyApiKey' }),
358
285
  };
359
286
 
360
287
  return {
package/package.json CHANGED
@@ -1,14 +1,18 @@
1
1
  {
2
2
  "name": "@mimik/api-helper",
3
- "version": "3.0.1",
3
+ "version": "3.0.3",
4
4
  "description": "helper for openAPI backend and mimik service",
5
5
  "main": "./index.js",
6
6
  "type": "module",
7
+ "exports": "./index.js",
8
+ "engines": {
9
+ "node": ">=24.0.0"
10
+ },
7
11
  "scripts": {
8
12
  "lint": "eslint . --no-error-on-unmatched-pattern",
9
13
  "docs": "jsdoc2md index.js > README.md",
10
- "test": "mocha --reporter mochawesome test/ --recursive",
11
- "test-ci": "c8 --reporter=lcov --reporter=text npm test",
14
+ "test": "mocha",
15
+ "test-ci": "c8 npm test",
12
16
  "prepublishOnly": "npm run docs && npm run lint && npm run test-ci",
13
17
  "commit-ready": "npm run docs && npm run lint && npm run test-ci"
14
18
  },
@@ -19,33 +23,31 @@
19
23
  ],
20
24
  "author": "mimik technology inc <support@mimik.com> (https://developer.mimik.com/)",
21
25
  "license": "MIT",
22
- "engines": {
23
- "node": ">=24"
24
- },
25
26
  "repository": {
26
27
  "type": "git",
27
28
  "url": "https://bitbucket.org/mimiktech/api-helper"
28
29
  },
29
30
  "dependencies": {
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",
31
+ "@mimik/request-retry": "^4.0.11",
32
+ "@mimik/response-helper": "^4.0.11",
33
+ "@mimik/sumologic-winston-logger": "^2.2.2",
34
+ "@mimik/swagger-helper": "^5.0.4",
34
35
  "ajv-formats": "3.0.1",
35
36
  "js-base64": "3.7.8",
36
37
  "js-yaml": "4.1.1",
37
38
  "jsonwebtoken": "9.0.3",
38
39
  "openapi-backend": "5.16.1",
39
- "swagger-client": "3.37.0"
40
+ "swagger-client": "3.37.1"
40
41
  },
41
42
  "devDependencies": {
42
- "@eslint/js": "9.32.0",
43
- "@mimik/eslint-plugin-document-env": "^2.0.8",
44
- "@stylistic/eslint-plugin": "5.9.0",
43
+ "@eslint/js": "10.0.1",
44
+ "@mimik/eslint-plugin-document-env": "^2.0.9",
45
+ "@mimik/eslint-plugin-logger": "^1.0.3",
46
+ "@stylistic/eslint-plugin": "5.10.0",
45
47
  "c8": "11.0.0",
46
48
  "chai": "6.2.2",
47
- "eslint": "9.32.0",
48
- "eslint-plugin-import": "2.32.0",
49
+ "eslint": "10.1.0",
50
+ "eslint-plugin-import-x": "4.16.2",
49
51
  "esmock": "2.7.3",
50
52
  "globals": "17.4.0",
51
53
  "husky": "9.1.7",