@mimik/api-helper 1.1.4 → 2.0.1

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
@@ -1,29 +1,53 @@
1
- ## Functions
2
-
3
- <dl>
4
- <dt><a href="#apiSetup">apiSetup(setup, registeredOperations, securityHandlers, extraFormats, config, correlationId)</a> ⇒ <code>Promise</code></dt>
5
- <dd><p>Setup the API to be use for a service</p>
6
- </dd>
7
- <dt><a href="#getAPIFile">getAPIFile(apiFilename, correlationId, options)</a> ⇒ <code>Promise</code></dt>
8
- <dd><p>Gets the API file from swaggerhub and store it in the give PATH location.</p>
9
- </dd>
10
- <dt><a href="#setupServerFiles">setupServerFiles(apiFilename, controllersDirectory, buidDirectory, correlationId, options)</a> ⇒ <code>Promise</code></dt>
11
- <dd><p>Setup and validates files for the server</p>
12
- </dd>
13
- <dt><a href="#validateSecuritySchemes">validateSecuritySchemes(apiDefinition, correlationId)</a> ⇒</dt>
14
- <dd><p>Validates the known SecuritySchemes: <code>SystemSecurity</code>, <code>AdminSecurity</code>, <code>UserSecurity</code>, <code>PeerSecurity</code>, <code>ApiKeySecurity</code>.</p>
15
- </dd>
16
- <dt><a href="#extractProperties">extractProperties(apiDefinition, controllersDirectory, buidDirectory, correlationId)</a> ⇒</dt>
17
- <dd><p>Extracts the properties from API definiton and creates a file binding the handler with the controller operations.</p>
18
- </dd>
19
- </dl>
20
-
21
- <a name="apiSetup"></a>
22
-
23
- ## apiSetup(setup, registeredOperations, securityHandlers, extraFormats, config, correlationId) ⇒ <code>Promise</code>
1
+ <a name="module_api-helper"></a>
2
+
3
+ ## api-helper
4
+ **Example**
5
+ ```js
6
+ import apiHelper from '@mimik/api-helper';
7
+ or
8
+ import { apiSetup, securityLib, getAPIFile, validateSecuritySchemes, extractProperties, setupServerFiles } from '@mimik/api-helper';
9
+ ```
10
+
11
+ * [api-helper](#module_api-helper)
12
+ * _async_
13
+ * [~securityLib(config)](#module_api-helper..securityLib)
14
+ * [~apiSetup(setup, registeredOperations, securityHandlers, extraFormats, config, correlationId)](#module_api-helper..apiSetup) <code>Promise</code>
15
+ * [~getAPIFile(apiFilename, correlationId, options)](#module_api-helper..getAPIFile) ⇒ <code>Promise</code>
16
+ * [~setupServerFiles(apiFilename, controllersDirectory, buidDirectory, correlationId, options)](#module_api-helper..setupServerFiles) ⇒ <code>Promise</code>
17
+ * _sync_
18
+ * [~validateSecuritySchemes(apiDefinition, correlationId)](#module_api-helper..validateSecuritySchemes) ⇒
19
+ * [~extractProperties(apiDefinition, controllersDirectory, buidDirectory, correlationId)](#module_api-helper..extractProperties) ⇒
20
+
21
+ <a name="module_api-helper..securityLib"></a>
22
+
23
+ ### api-helper~securityLib(config)
24
+ Implement the security flows for the API.
25
+
26
+ **Kind**: inner method of [<code>api-helper</code>](#module_api-helper)
27
+ **Category**: async
28
+ **Throws**:
29
+
30
+ - <code>Promise</code> An error is thrown if the initiatilization failed.
31
+
32
+ This function is used to setup the following security handlers for the API:
33
+ - `SystemSecurity` - used for the system operations, like /system, /onbehalf
34
+ - `AdminSecurity` - used for the admin operations, like /admin,
35
+ - `UserSecurity` - used for the user operations, like /user,
36
+ - `ApiKeySecurity` - used for the API key operations, like /apikey,
37
+ The security handlers are used to validate the tokens and scopes for the API operations.
38
+
39
+ **Requires**: <code>module:@mimik/swagger-helper</code>, <code>module:jsonwebtoken</code>, <code>module:lodash</code>
40
+
41
+ | Param | Type | Description |
42
+ | --- | --- | --- |
43
+ | config | <code>object</code> | Configuration of the service. &fulfil {object} The API file itself. |
44
+
45
+ <a name="module_api-helper..apiSetup"></a>
46
+
47
+ ### api-helper~apiSetup(setup, registeredOperations, securityHandlers, extraFormats, config, correlationId) ⇒ <code>Promise</code>
24
48
  Setup the API to be use for a service
25
49
 
26
- **Kind**: global function
50
+ **Kind**: inner method of [<code>api-helper</code>](#module_api-helper)
27
51
  **Returns**: <code>Promise</code> - .
28
52
  &fulfil {object} The API file itself.
29
53
  **Category**: async
@@ -53,12 +77,12 @@ The default formats for validation are: `date`, `time`, `date-time`, `byte`, `uu
53
77
  | config | <code>object</code> | Configuration of the service. |
54
78
  | correlationId | <code>UUID.&lt;string&gt;</code> | CorrelationId when logging activites. |
55
79
 
56
- <a name="getAPIFile"></a>
80
+ <a name="module_api-helper..getAPIFile"></a>
57
81
 
58
- ## getAPIFile(apiFilename, correlationId, options) ⇒ <code>Promise</code>
82
+ ### api-helper~getAPIFile(apiFilename, correlationId, options) ⇒ <code>Promise</code>
59
83
  Gets the API file from swaggerhub and store it in the give PATH location.
60
84
 
61
- **Kind**: global function
85
+ **Kind**: inner method of [<code>api-helper</code>](#module_api-helper)
62
86
  **Returns**: <code>Promise</code> - .
63
87
  &fulfil {object} The API file itself.
64
88
  **Category**: async
@@ -85,12 +109,12 @@ Gets the API file from swaggerhub and store it in the give PATH location.
85
109
  | correlationId | <code>UUID.&lt;string&gt;</code> | CorrelationId when logging activites. |
86
110
  | options | <code>object</code> | Options associated with the call. Use to pass `metrics` to `rpRetry` and `apiInfo` to access the api file in the api provider. |
87
111
 
88
- <a name="setupServerFiles"></a>
112
+ <a name="module_api-helper..setupServerFiles"></a>
89
113
 
90
- ## setupServerFiles(apiFilename, controllersDirectory, buidDirectory, correlationId, options) ⇒ <code>Promise</code>
114
+ ### api-helper~setupServerFiles(apiFilename, controllersDirectory, buidDirectory, correlationId, options) ⇒ <code>Promise</code>
91
115
  Setup and validates files for the server
92
116
 
93
- **Kind**: global function
117
+ **Kind**: inner method of [<code>api-helper</code>](#module_api-helper)
94
118
  **Returns**: <code>Promise</code> - .
95
119
  &fulfil {object} The API file, the API filename, the existing known security schemes and the defined security schemes.
96
120
  **Category**: async
@@ -104,16 +128,16 @@ Setup and validates files for the server
104
128
  | --- | --- | --- |
105
129
  | apiFilename | <code>PATH.&lt;string&gt;</code> | Name of the file where the API file will be stored. |
106
130
  | controllersDirectory | <code>PATH.&lt;string&gt;</code> | Directory to find the controller files. |
107
- | buidDirectory | <code>PATH.&lt;string&gt;</code> | = Directory where the register file will be stored. |
131
+ | buidDirectory | <code>PATH.&lt;string&gt;</code> | Directory where the register file will be stored. |
108
132
  | correlationId | <code>UUID.&lt;string&gt;</code> | CorrelationId when logging activites. |
109
133
  | options | <code>object</code> | Options associated with the call. Use to pass `metrics` to `rpRetry` and `apiKey`` to access private API. |
110
134
 
111
- <a name="validateSecuritySchemes"></a>
135
+ <a name="module_api-helper..validateSecuritySchemes"></a>
112
136
 
113
- ## validateSecuritySchemes(apiDefinition, correlationId) ⇒
137
+ ### api-helper~validateSecuritySchemes(apiDefinition, correlationId) ⇒
114
138
  Validates the known SecuritySchemes: `SystemSecurity`, `AdminSecurity`, `UserSecurity`, `PeerSecurity`, `ApiKeySecurity`.
115
139
 
116
- **Kind**: global function
140
+ **Kind**: inner method of [<code>api-helper</code>](#module_api-helper)
117
141
  **Returns**: An array of the known securitySchemes that are in the API definition.
118
142
  **Category**: sync
119
143
  **Throws**:
@@ -127,12 +151,12 @@ Validates the known SecuritySchemes: `SystemSecurity`, `AdminSecurity`, `UserSec
127
151
  | apiDefinition | <code>object</code> | JSON object containing the API definition. |
128
152
  | correlationId | <code>UUID.&lt;string&gt;</code> | CorrelationId when logging activites. |
129
153
 
130
- <a name="extractProperties"></a>
154
+ <a name="module_api-helper..extractProperties"></a>
131
155
 
132
- ## extractProperties(apiDefinition, controllersDirectory, buidDirectory, correlationId) ⇒
156
+ ### api-helper~extractProperties(apiDefinition, controllersDirectory, buidDirectory, correlationId) ⇒
133
157
  Extracts the properties from API definiton and creates a file binding the handler with the controller operations.
134
158
 
135
- **Kind**: global function
159
+ **Kind**: inner method of [<code>api-helper</code>](#module_api-helper)
136
160
  **Returns**: null
137
161
  **Category**: sync
138
162
  **Throws**:
@@ -145,6 +169,6 @@ Extracts the properties from API definiton and creates a file binding the handle
145
169
  | --- | --- | --- |
146
170
  | apiDefinition | <code>object</code> | JSON object containing the API definition. |
147
171
  | controllersDirectory | <code>PATH.&lt;string&gt;</code> | Directory to find the controller files. |
148
- | buidDirectory | <code>PATH.&lt;string&gt;</code> | = Directory where the register file will be stored. |
172
+ | buidDirectory | <code>PATH.&lt;string&gt;</code> | Directory where the register file will be stored. |
149
173
  | correlationId | <code>UUID.&lt;string&gt;</code> | CorrelationId when logging activites. |
150
174
 
@@ -0,0 +1,63 @@
1
+ import importPlugin from 'eslint-plugin-import';
2
+ import js from '@eslint/js';
3
+ import processDoc from '@mimik/eslint-plugin-document-env';
4
+ import stylistic from '@stylistic/eslint-plugin';
5
+
6
+ const MAX_LENGTH_LINE = 180;
7
+ const MAX_FUNCTION_PARAMETERS = 6;
8
+ const MAX_LINES_IN_FILES = 600;
9
+ const MAX_LINES_IN_FUNCTION = 150;
10
+ const MAX_STATEMENTS_IN_FUNCTION = 45;
11
+ const MIN_KEYS_IN_OBJECT = 10;
12
+ const MAX_COMPLEXITY = 30;
13
+
14
+ export default [
15
+ {
16
+ ignores: ['mochawesome-report/**', 'node_modules/**', 'dist/**'],
17
+ },
18
+ importPlugin.flatConfigs.recommended,
19
+ stylistic.configs.recommended,
20
+ js.configs.all,
21
+ {
22
+ plugins: {
23
+ processDoc,
24
+ },
25
+ languageOptions: {
26
+ ecmaVersion: 2022,
27
+ globals: {
28
+ console: 'readonly',
29
+ describe: 'readonly',
30
+ it: 'readonly',
31
+ require: 'readonly',
32
+ },
33
+ sourceType: 'module',
34
+ },
35
+ rules: {
36
+ '@stylistic/brace-style': ['warn', 'stroustrup', { allowSingleLine: true }],
37
+ '@stylistic/line-comment-position': ['off'],
38
+ '@stylistic/semi': ['error', 'always'],
39
+ 'capitalized-comments': ['off'],
40
+ 'complexity': ['error', MAX_COMPLEXITY],
41
+ 'curly': ['off'],
42
+ 'id-length': ['error', { exceptions: ['x', 'y', 'z', 'i', 'j', 'k'] }],
43
+ 'import/no-extraneous-dependencies': ['error', { devDependencies: true }],
44
+ 'import/no-unresolved': ['error', { amd: true, caseSensitiveStrict: true, commonjs: true }],
45
+ 'init-declarations': ['off'],
46
+ 'linebreak-style': ['off'],
47
+ 'max-len': ['warn', MAX_LENGTH_LINE, { ignoreComments: true }],
48
+ 'max-lines': ['warn', { max: MAX_LINES_IN_FILES, skipComments: true }],
49
+ 'max-lines-per-function': ['warn', { max: MAX_LINES_IN_FUNCTION, skipComments: true }],
50
+ 'max-params': ['error', MAX_FUNCTION_PARAMETERS],
51
+ 'max-statements': ['warn', MAX_STATEMENTS_IN_FUNCTION],
52
+ 'no-confusing-arrow': ['off'], // arrow isnt confusing
53
+ 'no-inline-comments': ['off'],
54
+ 'no-process-env': ['error'],
55
+ 'no-ternary': ['off'],
56
+ 'no-undefined': ['off'],
57
+ 'one-var': ['error', 'never'],
58
+ 'processDoc/validate-document-env': ['error'],
59
+ 'quotes': ['warn', 'single'],
60
+ 'sort-keys': ['error', 'asc', { caseSensitive: true, minKeys: MIN_KEYS_IN_OBJECT, natural: false }],
61
+ },
62
+ },
63
+ ];
package/index.js CHANGED
@@ -1,47 +1,80 @@
1
- const { OpenAPIBackend } = require('openapi-backend');
2
- const fs = require('fs');
3
- const pathLib = require('path');
4
- const yaml = require('js-yaml');
5
- const compact = require('lodash.compact');
6
- const difference = require('lodash.difference');
7
- const SwaggerClient = require('swagger-client');
8
- const { Base64 } = require('js-base64');
9
-
10
- const logger = require('@mimik/sumologic-winston-logger');
11
- const { getRichError } = require('@mimik/response-helper');
12
- const { rpRetry } = require('@mimik/request-retry');
13
-
14
- const { saveProperties } = require('./lib/extract-helper');
15
- const { validateOauth2, validateApiKey } = require('./lib/oauthValidation-helper');
16
- const { ajvFormats } = require('./lib/ajvHelpers');
17
- const securityLib = require('./lib/securityHandlers');
18
- const baseHandlers = require('./lib/baseHandlers');
19
- const {
20
- LOCAL,
21
- SET_ON,
22
- SECURITY_ON,
23
- SECURITY_OFF,
24
- X_ROUTER_CONTROLLER,
25
- EXTENSION,
1
+ import {
26
2
  ADMIN_SECURITY,
27
- SYSTEM_SECURITY,
28
- PEER_SECURITY,
29
- USER_SECURITY,
30
3
  API_KEY_SECURITY,
4
+ API_PROVIDER_BITBUCKET,
5
+ API_PROVIDER_SWAGGERHUB,
6
+ API_SOURCE,
7
+ BITBUCKET,
31
8
  CLIENT_CREDENTIALS,
9
+ DEFAULT_BITBUCKET_PASSWORD,
10
+ DEFAULT_BITBUCKET_USERNAME,
11
+ EXTENSION,
12
+ EXTENSION_YML,
32
13
  IMPLICIT,
14
+ LOCAL,
15
+ PEER_SECURITY,
33
16
  POSTFIX,
34
- API_PROVIDER_SWAGGERHUB,
35
- API_PROVIDER_BITBUCKET,
36
17
  RESOLVED,
37
- API_SOURCE,
18
+ SECURITY_OFF,
19
+ SECURITY_ON,
20
+ SET_ON,
38
21
  SWAGGER,
39
- BITBUCKET,
40
22
  SWAGGERHUB,
41
- EXTENSION_YML,
42
- DEFAULT_BITBUCKET_USERNAME,
43
- DEFAULT_BITBUCKET_PASSWORD,
44
- } = require('./lib/common');
23
+ SYSTEM_SECURITY,
24
+ USER_SECURITY,
25
+ X_ROUTER_CONTROLLER,
26
+ } from './lib/common.js';
27
+ import { validateApiKey, validateOauth2 } from './lib/oauthValidation-helper.js';
28
+ import { Base64 } from 'js-base64';
29
+ import OpenAPIBackend from 'openapi-backend';
30
+ import SwaggerClient from 'swagger-client';
31
+ import { ajvFormats } from './lib/ajvHelpers.js';
32
+ import baseHandlers from './lib/baseHandlers.js';
33
+ import compact from 'lodash.compact';
34
+ import difference from 'lodash.difference';
35
+ import fs from 'fs';
36
+ import { getRichError } from '@mimik/response-helper';
37
+ import { load } from 'js-yaml';
38
+ import logger from '@mimik/sumologic-winston-logger';
39
+ import pathLib from 'path';
40
+ import { rpRetry } from '@mimik/request-retry';
41
+ import { saveProperties } from './lib/extract-helper.js';
42
+ import { securityLib } from './lib/securityHandlers.js';
43
+ /**
44
+ * @module api-helper
45
+ * @example
46
+ * import apiHelper from '@mimik/api-helper';
47
+ * or
48
+ * import { apiSetup, securityLib, getAPIFile, validateSecuritySchemes, extractProperties, setupServerFiles } from '@mimik/api-helper';
49
+ */
50
+ const EMPTY = 0;
51
+ const TAB = 2;
52
+ const FULL_NAME_LENGTH = 4;
53
+ const CUSTOMER_INDEX = 0;
54
+ const API_NAME_INDEX = 1;
55
+ const API_VERSION_INDEX = 2;
56
+ const POSTFIX_INDEX = 3;
57
+ /**
58
+ *
59
+ * Implement the security flows for the API.
60
+ *
61
+ * @function securityLib
62
+ * @category async
63
+ * @requires @mimik/swagger-helper
64
+ * @requires jsonwebtoken
65
+ * @requires lodash
66
+ * @param {object} config - Configuration of the service.
67
+ * &fulfil {object} The API file itself.
68
+ * @throws {Promise} An error is thrown if the initiatilization failed.
69
+ *
70
+ * This function is used to setup the following security handlers for the API:
71
+ * - `SystemSecurity` - used for the system operations, like /system, /onbehalf
72
+ * - `AdminSecurity` - used for the admin operations, like /admin,
73
+ * - `UserSecurity` - used for the user operations, like /user,
74
+ * - `ApiKeySecurity` - used for the API key operations, like /apikey,
75
+ * The security handlers are used to validate the tokens and scopes for the API operations.
76
+ */
77
+ export { securityLib };
45
78
 
46
79
  /**
47
80
  *
@@ -77,7 +110,7 @@ const {
77
110
  *
78
111
  * The default formats for validation are: `date`, `time`, `date-time`, `byte`, `uuid`, `uri`, `email`, `ipv4`, `ipv6`, `semver`, `ip`.
79
112
  */
80
- const apiSetup = (setup, registeredOperations, securityHandlers, extraFormats, config, correlationId) => {
113
+ export const apiSetup = (setup, registeredOperations, securityHandlers, extraFormats, config, correlationId) => {
81
114
  const { apiFilename, existingSecuritySchemes, definedSecuritySchemes } = setup;
82
115
  const {
83
116
  SystemSecurity,
@@ -125,7 +158,7 @@ const apiSetup = (setup, registeredOperations, securityHandlers, extraFormats, c
125
158
  const securityHandlerNames = Object.keys(securityHandlers);
126
159
  const unusedSecuritySchemes = difference(securityHandlerNames, definedSecuritySchemes);
127
160
 
128
- if (unusedSecuritySchemes.length !== 0) throw getRichError('System', 'unused handlers for security schemes', { unusedSecuritySchemes });
161
+ if (unusedSecuritySchemes.length !== EMPTY) throw getRichError('System', 'unused handlers for security schemes', { unusedSecuritySchemes });
129
162
 
130
163
  remainingSecurities.forEach((securityScheme) => {
131
164
  if (!securityHandlerNames.includes(securityScheme) && !securityHandlers[securityScheme].notEnabled) {
@@ -138,7 +171,7 @@ const apiSetup = (setup, registeredOperations, securityHandlers, extraFormats, c
138
171
  }
139
172
  });
140
173
  }
141
- else if (remainingSecurities.length !== 0) throw getRichError('System', 'missing handlers for security schemes', { missingSecuritySchemes: remainingSecurities });
174
+ else if (remainingSecurities.length !== EMPTY) throw getRichError('System', 'missing handlers for security schemes', { missingSecuritySchemes: remainingSecurities });
142
175
  api.init()
143
176
  .catch((err) => {
144
177
  throw getRichError('System', 'could not initialize the api', { api }, err);
@@ -176,8 +209,8 @@ const apiSetup = (setup, registeredOperations, securityHandlers, extraFormats, c
176
209
  * "apiApiKey": "apiKey for access private API on swaggerhub, can be optional if the API is accessible publically"
177
210
  * }
178
211
  */
179
- const getAPIFile = (apiFilename, correlationId, options) => {
180
- const swaggerOptions = (spec) => ({
212
+ export const getAPIFile = (apiFilename, correlationId, options) => {
213
+ const swaggerOptions = spec => ({
181
214
  spec,
182
215
  allowMetaPatches: false,
183
216
  skipNormalization: true,
@@ -197,7 +230,9 @@ const getAPIFile = (apiFilename, correlationId, options) => {
197
230
  return Promise.reject(getRichError('System', 'file system error', { apiFilename }, err));
198
231
  }
199
232
  if (apiDefinition) {
200
- try { apiDefinition = JSON.parse(apiDefinition); }
233
+ try {
234
+ apiDefinition = JSON.parse(apiDefinition);
235
+ }
201
236
  catch (err) {
202
237
  return Promise.reject(getRichError('System', 'wrong file format', { apiFilename }, err));
203
238
  }
@@ -206,11 +241,13 @@ const getAPIFile = (apiFilename, correlationId, options) => {
206
241
  throw getRichError('System', 'could not resolve apiDefiniton', { apiFilename }, err);
207
242
  })
208
243
  .then((apiDefinitionResult) => {
209
- if (apiDefinitionResult.errors.length !== 0) {
244
+ if (apiDefinitionResult.errors.length !== EMPTY) {
210
245
  logger.error('errors while resolving definition', { errors: apiDefinitionResult.errors }, correlationId);
211
246
  throw getRichError('Parameter', 'errors while resolving definition', { apiFilename, errors: apiDefinitionResult.errors });
212
247
  }
213
- try { fs.writeFileSync(apiFilename, JSON.stringify(apiDefinitionResult.spec, null, 2)); }
248
+ try {
249
+ fs.writeFileSync(apiFilename, JSON.stringify(apiDefinitionResult.spec, null, TAB));
250
+ }
214
251
  catch (err) {
215
252
  throw getRichError('System', 'file system error', { apiFilename }, err);
216
253
  }
@@ -228,13 +265,17 @@ const getAPIFile = (apiFilename, correlationId, options) => {
228
265
  let fileName;
229
266
  let apiDirectory;
230
267
 
231
- try { fileName = pathLib.basename(apiFilename); }
268
+ try {
269
+ fileName = pathLib.basename(apiFilename);
270
+ }
232
271
  catch (err) { return Promise.reject(getRichError('System', 'file name error', { apiFilename }, err)); }
233
- try { apiDirectory = pathLib.dirname(apiFilename); }
272
+ try {
273
+ apiDirectory = pathLib.dirname(apiFilename);
274
+ }
234
275
  catch (err) { return Promise.reject(getRichError('System', 'directory name error', { apiFilename }, err)); }
235
276
  const params = fileName.split('_');
236
277
 
237
- if (params.length !== 4 || params[3] !== POSTFIX) {
278
+ if (params.length !== FULL_NAME_LENGTH || params[POSTFIX_INDEX] !== POSTFIX) {
238
279
  return Promise.reject(getRichError('System', 'wrong api name', { apiFilename }));
239
280
  }
240
281
  try {
@@ -257,7 +298,7 @@ const getAPIFile = (apiFilename, correlationId, options) => {
257
298
  try {
258
299
  switch (provider) {
259
300
  case SWAGGERHUB: {
260
- opts.url = `${API_PROVIDER_SWAGGERHUB}/${params[0]}/${params[1]}/${params[2]}?${RESOLVED}`;
301
+ opts.url = `${API_PROVIDER_SWAGGERHUB}/${params[CUSTOMER_INDEX]}/${params[API_NAME_INDEX]}/${params[API_VERSION_INDEX]}?${RESOLVED}`;
261
302
  if (apiInfo.apiApiKey) opts.headers.Authorization = apiInfo.apiApiKey;
262
303
  break;
263
304
  }
@@ -268,11 +309,13 @@ const getAPIFile = (apiFilename, correlationId, options) => {
268
309
  if (apiInfo.apiBasicAuth.username === DEFAULT_BITBUCKET_USERNAME || apiInfo.apiBasicAuth.password === DEFAULT_BITBUCKET_PASSWORD) {
269
310
  throw getRichError('Parameter', 'missing username/password for accessing Bitbucket', { apiFilename });
270
311
  }
271
- try { opts.headers.Authorization = `Basic ${Base64.encode(`${apiInfo.apiBasicAuth.username}:${apiInfo.apiBasicAuth.password}`)}`; }
312
+ try {
313
+ opts.headers.Authorization = `Basic ${Base64.encode(`${apiInfo.apiBasicAuth.username}:${apiInfo.apiBasicAuth.password}`)}`;
314
+ }
272
315
  catch (err) {
273
316
  throw getRichError('System', 'could not create basicAuth', { apiFilename }, err);
274
317
  }
275
- opts.url = `${API_PROVIDER_BITBUCKET}/${params[0]}/${params[1]}${API_SOURCE}/${params[2]}/${SWAGGER}${EXTENSION_YML}`;
318
+ opts.url = `${API_PROVIDER_BITBUCKET}/${params[CUSTOMER_INDEX]}/${params[API_NAME_INDEX]}${API_SOURCE}/${params[API_VERSION_INDEX]}/${SWAGGER}${EXTENSION_YML}`;
276
319
  break;
277
320
  }
278
321
  default: {
@@ -299,7 +342,7 @@ const getAPIFile = (apiFilename, correlationId, options) => {
299
342
  .then((result) => {
300
343
  let resultJSON = result;
301
344
 
302
- if (typeof result !== 'object') resultJSON = yaml.load(result);
345
+ if (typeof result !== 'object') resultJSON = load(result);
303
346
  return SwaggerClient.resolve(swaggerOptions(resultJSON));
304
347
  })
305
348
  .catch((err) => {
@@ -309,11 +352,13 @@ const getAPIFile = (apiFilename, correlationId, options) => {
309
352
  throw getRichError('System', 'could not resolve apiDefiniton', { apiFilename }, err);
310
353
  })
311
354
  .then((apiDefinitionResult) => {
312
- if (apiDefinitionResult.errors.length !== 0) {
355
+ if (apiDefinitionResult.errors.length !== EMPTY) {
313
356
  logger.error('errors while resolving definition', { errors: apiDefinitionResult.errors }, correlationId);
314
357
  throw getRichError('Parameter', 'errors while resolving definition', { apiFilename, errors: apiDefinitionResult.errors });
315
358
  }
316
- try { fs.writeFileSync(apiFilename, JSON.stringify(apiDefinitionResult.spec, null, 2)); }
359
+ try {
360
+ fs.writeFileSync(apiFilename, JSON.stringify(apiDefinitionResult.spec, null, TAB));
361
+ }
317
362
  catch (err) {
318
363
  throw getRichError('System', 'file system error', { apiFilename }, err);
319
364
  }
@@ -334,7 +379,7 @@ const getAPIFile = (apiFilename, correlationId, options) => {
334
379
  * @return An array of the known securitySchemes that are in the API definition.
335
380
  * @throws An error is thrown for the first validation fails.
336
381
  */
337
- const validateSecuritySchemes = (apiDefinition, correlationId) => {
382
+ export const validateSecuritySchemes = (apiDefinition, correlationId) => {
338
383
  const existingSecuritySchemes = [];
339
384
 
340
385
  if (apiDefinition.components?.securitySchemes) {
@@ -361,17 +406,19 @@ const validateSecuritySchemes = (apiDefinition, correlationId) => {
361
406
  * @requires fs
362
407
  * @param {object} apiDefinition - JSON object containing the API definition.
363
408
  * @param {PATH.<string>} controllersDirectory - Directory to find the controller files.
364
- * @param {PATH.<string>} buidDirectory = Directory where the register file will be stored.
409
+ * @param {PATH.<string>} buidDirectory - Directory where the register file will be stored.
365
410
  * @param {UUID.<string>} correlationId - CorrelationId when logging activites.
366
411
  * @return null
367
412
  * @throws An error is thrown for many reasons, like operationId does not exist in controllers, controller dies not exist...
368
413
  */
369
- const extractProperties = (apiDefinition, controllersDirectory, buildDirectory, correlationId) => {
414
+ export const extractProperties = (apiDefinition, controllersDirectory, buildDirectory, correlationId) => {
370
415
  const result = {};
371
416
  const { paths } = apiDefinition;
372
417
  let controllersDirectoryName;
373
418
 
374
- try { controllersDirectoryName = pathLib.basename(controllersDirectory); }
419
+ try {
420
+ controllersDirectoryName = pathLib.basename(controllersDirectory);
421
+ }
375
422
  catch (err) {
376
423
  throw getRichError('System', 'directory name error', { controllersDirectory }, err);
377
424
  }
@@ -395,10 +442,7 @@ const extractProperties = (apiDefinition, controllersDirectory, buildDirectory,
395
442
  const operation = path[verb];
396
443
 
397
444
  if (operation.operationId) {
398
- if (!operation[X_ROUTER_CONTROLLER]) {
399
- throw getRichError('System', 'missing property', { property: X_ROUTER_CONTROLLER });
400
- }
401
- else {
445
+ if (operation[X_ROUTER_CONTROLLER]) {
402
446
  const controller = operation[X_ROUTER_CONTROLLER];
403
447
  const controllerFilename = `${controllersDirectory}/${controller}${EXTENSION}`;
404
448
 
@@ -413,16 +457,19 @@ const extractProperties = (apiDefinition, controllersDirectory, buildDirectory,
413
457
  try {
414
458
  const file = fs.readFileSync(controllerFilename, 'utf8');
415
459
  if (!file.includes(`${operation.operationId},`)
416
- && !file.includes(`${operation.operationId}:`)
417
- && !file.includes(`module.exports = ${operation.operationId}`)) { // code must be linted before
460
+ && !file.includes(`${operation.operationId}:`)
461
+ && !file.includes(`export ${operation.operationId}`)) { // code must be linted before
418
462
  throw getRichError('System', 'missing operationId in controller file', { controllerFilename, operationId: operation.operationId });
419
463
  }
420
464
  }
421
465
  catch (err) {
422
466
  throw getRichError('System', 'file system error', { controllerFilename, operationId: operation.operationId }, err);
423
467
  }
424
- if (!result[controller]) result[controller] = [operation.operationId];
425
- else result[controller].push(operation.operationId);
468
+ if (result[controller]) result[controller].push(operation.operationId);
469
+ else result[controller] = [operation.operationId];
470
+ }
471
+ else {
472
+ throw getRichError('System', 'missing property', { property: X_ROUTER_CONTROLLER });
426
473
  }
427
474
  }
428
475
  });
@@ -445,14 +492,14 @@ const extractProperties = (apiDefinition, controllersDirectory, buildDirectory,
445
492
  * @requires path
446
493
  * @param {PATH.<string>} apiFilename - Name of the file where the API file will be stored.
447
494
  * @param {PATH.<string>} controllersDirectory - Directory to find the controller files.
448
- * @param {PATH.<string>} buidDirectory = Directory where the register file will be stored.
495
+ * @param {PATH.<string>} buidDirectory - Directory where the register file will be stored.
449
496
  * @param {UUID.<string>} correlationId - CorrelationId when logging activites.
450
497
  * @param {object} options - Options associated with the call. Use to pass `metrics` to `rpRetry` and `apiKey`` to access private API.
451
498
  * @return {Promise}.
452
499
  * &fulfil {object} The API file, the API filename, the existing known security schemes and the defined security schemes.
453
500
  * @throws {Promise} An error is thrown for many reasons assocated with getAPIFile or validateSecuritySchemes or extractProperties.
454
501
  */
455
- const setupServerFiles = (apiFilename, controllersDirectory, buildDirectory, correlationId, options) => getAPIFile(apiFilename, correlationId, options)
502
+ export const setupServerFiles = (apiFilename, controllersDirectory, buildDirectory, correlationId, options) => getAPIFile(apiFilename, correlationId, options)
456
503
  .then((apiDefinition) => {
457
504
  const existingSecuritySchemes = compact(validateSecuritySchemes(apiDefinition, correlationId));
458
505
 
@@ -469,7 +516,7 @@ const setupServerFiles = (apiFilename, controllersDirectory, buildDirectory, cor
469
516
  };
470
517
  });
471
518
 
472
- module.exports = {
519
+ export default {
473
520
  apiSetup,
474
521
  securityLib,
475
522
  getAPIFile,
package/lib/ajvHelpers.js CHANGED
@@ -1,16 +1,17 @@
1
- const addFormats = require('ajv-formats');
1
+ import { DEFAULT_FORMATS } from './common.js';
2
+ import addFormats from 'ajv-formats';
2
3
 
3
- const { DEFAULT_FORMATS } = require('./common');
4
-
5
- const ajvFormats = (origFormats) => (ajv) => {
4
+ const ajvFormats = origFormats => (ajv) => {
6
5
  const extraFormats = {
7
6
  semver: {
8
7
  type: 'string',
9
- validate: /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/,
8
+ // eslint-disable-next-line prefer-named-capture-group
9
+ validate: /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/u,
10
10
  },
11
11
  ip: {
12
12
  type: 'string',
13
- 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*$))/,
13
+ // eslint-disable-next-line prefer-named-capture-group
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,
14
15
  },
15
16
  };
16
17
  const libFormats = DEFAULT_FORMATS;
@@ -18,14 +19,14 @@ const ajvFormats = (origFormats) => (ajv) => {
18
19
 
19
20
  if (!formats) formats = {};
20
21
  Object.keys(formats).forEach((format) => {
21
- if (!formats[format].type) libFormats.push(format);
22
- else extraFormats[format] = formats[format];
22
+ if (formats[format].type) extraFormats[format] = formats[format];
23
+ else libFormats.push(format);
23
24
  });
24
25
  addFormats(ajv, libFormats);
25
- Object.keys(extraFormats).forEach((format) => ajv.addFormat(format, extraFormats[format]));
26
+ Object.keys(extraFormats).forEach(format => ajv.addFormat(format, extraFormats[format]));
26
27
  return ajv;
27
28
  };
28
29
 
29
- module.exports = {
30
+ export {
30
31
  ajvFormats,
31
32
  };
@@ -1,84 +1,92 @@
1
- const { rejectRequest } = require('@mimik/swagger-helper');
1
+ import { rejectRequest } from '@mimik/swagger-helper';
2
2
 
3
- const validationFail = (c, req, res) => {
3
+ const PARAMETER_ERROR = 400;
4
+ const NOT_FOUND_ERROR = 404;
5
+ const UNAUTHORIZED_ERROR = 401;
6
+ const NOT_IMPLEMENTED_ERROR = 501;
7
+
8
+ const validationFail = (con, req, res) => {
4
9
  const error = new Error('Failed schema validation');
5
10
 
6
- error.statusCode = 400;
11
+ error.statusCode = PARAMETER_ERROR;
7
12
  error.info = {
8
13
  method: req.method,
9
14
  path: req.url,
10
- errors: c.validation.errors,
11
- warnings: c.validation.warnings || [],
15
+ errors: con.validation.errors,
16
+ warnings: con.validation.warnings || [],
12
17
  };
13
- rejectRequest(error, c, res);
18
+ rejectRequest(error, con, res);
14
19
  };
15
20
 
16
- const notFound = (c, req, res) => {
21
+ const notFound = (con, req, res) => {
17
22
  const path = req.url;
18
23
  const error = new Error(`path ${path} not defined in Swagger specification`);
19
24
 
20
- error.statusCode = 404;
25
+ error.statusCode = NOT_FOUND_ERROR;
21
26
  error.info = {
22
27
  method: req.method,
23
28
  path,
24
29
  };
25
- rejectRequest(error, c, res);
30
+ rejectRequest(error, con, res);
26
31
  };
27
32
 
28
- const unauthorizedHandler = (c, req, res) => {
33
+ const unauthorizedHandler = (con, req, res) => {
29
34
  let error = new Error('Unauthorized');
30
35
 
31
- error.statusCode = 401;
32
- const schemes = Object.keys(c.security);
36
+ error.statusCode = UNAUTHORIZED_ERROR;
37
+ const schemes = Object.keys(con.security);
33
38
 
34
39
  delete schemes.authorized;
35
40
  schemes.forEach((scheme) => {
36
- if (c.security[scheme]?.error) error = c.security[scheme].error;
41
+ const { error: schemeError } = con.security[scheme] || {};
42
+ if (schemeError) {
43
+ error = schemeError;
44
+ }
37
45
  });
38
- rejectRequest(error, c, res);
46
+ rejectRequest(error, con, res);
39
47
  };
40
48
 
41
- const notImplemented = (c, req, res) => {
49
+ const notImplemented = (con, req, res) => {
42
50
  const { method } = req;
43
51
  const path = req.url;
44
52
  const error = new Error(`${req.method} ${path} defined in Swagger specification, but no implemented`);
45
53
 
46
- error.statusCode = 505;
54
+ error.statusCode = NOT_IMPLEMENTED_ERROR;
47
55
  error.info = {
48
56
  method,
49
57
  path,
50
- operationId: c.operation.operationId,
58
+ operationId: con.operation.operationId,
51
59
  };
52
- rejectRequest(error, c, res);
60
+ rejectRequest(error, con, res);
53
61
  };
54
62
 
55
- const methodNotAllowed = (c, req, res) => {
63
+ const methodNotAllowed = (con, req, res) => {
56
64
  const { method } = req;
57
65
  const path = req.url;
58
66
  const error = new Error(`path ${path} defined in Swagger specification, but the method ${method} is not defined`);
59
67
 
60
- error.statusCode = 405;
68
+ error.statusCode = NOT_IMPLEMENTED_ERROR;
61
69
  error.info = {
62
70
  method,
63
71
  path,
64
72
  };
65
- rejectRequest(error, c, res);
73
+ rejectRequest(error, con, res);
66
74
  };
67
75
 
68
76
  /*
69
- const postResponseHandler = (c, req, res) => {
70
- const valid = c.api.validateResponse(c.response, c.operation);
71
- console.log('----->', c.response);
77
+ const postResponseHandler = (con, req, res) => {
78
+ const valid = con.api.validateResponse(con.response, con.operation);
79
+ console.log('----->', con.response);
72
80
  console.log('----->', valid);
73
81
  if (valid.errors) {
74
82
  // response validation failed
75
83
  return res.status(502).json({ status: 502, err: valid.errors });
76
84
  }
77
- return res.status(200).json(c.response);
85
+ return res.status(200).json(con.response);
78
86
  };
79
87
  */
80
88
 
81
- module.exports = {
89
+ export default {
82
90
  validationFail,
83
91
  notFound,
84
92
  unauthorizedHandler,
package/lib/common.js CHANGED
@@ -51,7 +51,7 @@ const BEARERS = ['bearer', 'Bearer'];
51
51
  const USER = 'user';
52
52
  const CLUSTER = 'cluster';
53
53
 
54
- module.exports = {
54
+ export {
55
55
  X_ROUTER_CONTROLLER,
56
56
  EXTENSION,
57
57
  REGISTER,
@@ -1,9 +1,7 @@
1
- const fs = require('fs');
2
-
3
- const { getRichError } = require('@mimik/response-helper');
4
- const logger = require('@mimik/sumologic-winston-logger');
5
-
6
- const { EXTENSION, REGISTER } = require('./common');
1
+ import { EXTENSION, REGISTER } from './common.js';
2
+ import fs from 'fs';
3
+ import { getRichError } from '@mimik/response-helper';
4
+ import logger from '@mimik/sumologic-winston-logger';
7
5
 
8
6
  const saveProperties = (extractResult, buildDirectory, controllersDirectoryName, correlationId) => {
9
7
  try {
@@ -36,9 +34,9 @@ const saveProperties = (extractResult, buildDirectory, controllersDirectoryName,
36
34
  itemToSave += ` ${operationId},\n`;
37
35
  operationIds.push(operationId);
38
36
  });
39
- stringToSave += `const {\n${itemToSave}} = require('../${controllersDirectoryName}/${controller}');\n`;
37
+ stringToSave += `import {\n${itemToSave}} from '../${controllersDirectoryName}/${controller}';\n`;
40
38
  });
41
- stringToSave += '\nmodule.exports = {\n';
39
+ stringToSave += '\nexport {\n';
42
40
  itemToSave = '';
43
41
  operationIds.forEach((operationId) => {
44
42
  itemToSave += ` ${operationId},\n`;
@@ -53,6 +51,6 @@ const saveProperties = (extractResult, buildDirectory, controllersDirectoryName,
53
51
  }
54
52
  };
55
53
 
56
- module.exports = {
54
+ export {
57
55
  saveProperties,
58
56
  };
@@ -1,6 +1,9 @@
1
- const { getRichError } = require('@mimik/response-helper');
2
-
3
- const { OAUTH2, API_KEY_IN, API_KEY_NAME } = require('./common');
1
+ import {
2
+ API_KEY_IN,
3
+ API_KEY_NAME,
4
+ OAUTH2,
5
+ } from './common.js';
6
+ import { getRichError } from '@mimik/response-helper';
4
7
 
5
8
  const validateOauth2 = (securitySchemes, securityType, flow) => {
6
9
  if (securitySchemes) {
@@ -36,7 +39,7 @@ const validateApiKey = (securitySchemes, securityType) => {
36
39
  return null;
37
40
  };
38
41
 
39
- module.exports = {
42
+ export {
40
43
  validateOauth2,
41
44
  validateApiKey,
42
45
  };
@@ -1,33 +1,47 @@
1
- const jwt = require('jsonwebtoken');
2
- const intersection = require('lodash.intersection');
3
- const difference = require('lodash.difference');
4
-
5
- const { TOKEN_PARAMS } = require('@mimik/swagger-helper');
6
- const {
7
- AUTHORIZATION,
1
+ import {
8
2
  ADMIN,
9
- SUB_ADMIN,
3
+ ADMIN_SECURITY,
4
+ API_KEY_NAME,
5
+ AUTHORIZATION,
6
+ BEARERS,
7
+ CLAIMS_DEFINITION,
8
+ CLAIMS_SEPARATOR,
10
9
  CLIENT,
10
+ CLUSTER,
11
11
  NO_GENERIC,
12
12
  ON_BEHALF,
13
- CLAIMS_DEFINITION,
13
+ RESOURCE_SEPARATOR,
14
14
  SCOPES_SEPARATOR,
15
15
  SCOPE_CLAIMS_SEPARATOR,
16
- RESOURCE_SEPARATOR,
17
- CLAIMS_SEPARATOR,
18
- BEARERS,
19
- USER,
20
- CLUSTER,
21
- API_KEY_NAME,
22
- ADMIN_SECURITY,
16
+ SUB_ADMIN,
23
17
  SYSTEM_SECURITY,
18
+ USER,
24
19
  USER_SECURITY,
25
- } = require('./common');
26
-
27
- const getScopes = (c, securityType) => {
20
+ } from './common';
21
+ import { TOKEN_PARAMS } from '@mimik/swagger-helper';
22
+ import difference from 'lodash.difference';
23
+ import intersection from 'lodash.intersection';
24
+ import jwt from 'jsonwebtoken';
25
+
26
+ const UNAUTHORIZED_ERROR = 401;
27
+ const FORBIDDEN_ERROR = 403;
28
+ const SYSTEM_ERROR = 500;
29
+ const EMPTY = 0;
30
+ const SINGLE = 1;
31
+ const FIRST_AUTHORIZATION = 0;
32
+ const BEARER_INDEX = 0;
33
+ const TOKEN_INDEX = 1;
34
+ const BEARER_LENGTH = 2;
35
+ const FIRST = 1;
36
+ const SECOND = 2;
37
+ const SCOPE_INDEX = 0;
38
+ const CLAIMS_INDEX = 1;
39
+ const RESOURCE_INDEX = 0;
40
+
41
+ const getScopes = (conf, securityType) => {
28
42
  let scopes = [];
29
43
 
30
- c.operation.security.forEach((security) => {
44
+ conf.operation.security.forEach((security) => {
31
45
  if (security[securityType]) scopes = scopes.concat(security[securityType]);
32
46
  });
33
47
  return scopes;
@@ -43,36 +57,38 @@ const getError = (message, statusCode) => {
43
57
  const checkToken = (authToken) => {
44
58
  let token;
45
59
 
46
- try { token = jwt.decode(authToken); }
60
+ try {
61
+ token = jwt.decode(authToken);
62
+ }
47
63
  catch (err) {
48
- err.statusCode = 401;
64
+ err.statusCode = UNAUTHORIZED_ERROR;
49
65
  throw err;
50
66
  }
51
67
  if (!token) {
52
- throw getError('invalid token', 401);
68
+ throw getError('invalid token', UNAUTHORIZED_ERROR);
53
69
  }
54
70
  return token;
55
71
  };
56
72
 
57
73
  const checkHeaders = (headers) => {
58
74
  if (!headers) {
59
- throw getError('missing header', 401);
75
+ throw getError('missing header', UNAUTHORIZED_ERROR);
60
76
  }
61
- const authNames = Object.keys(headers).filter((key) => key.toLowerCase() === AUTHORIZATION);
77
+ const authNames = Object.keys(headers).filter(key => key.toLowerCase() === AUTHORIZATION);
62
78
  const authNamesLength = authNames.length;
63
79
 
64
- if (authNamesLength === 0) {
65
- throw getError(`missing ${AUTHORIZATION} header`, 401);
80
+ if (authNamesLength === EMPTY) {
81
+ throw getError(`missing ${AUTHORIZATION} header`, UNAUTHORIZED_ERROR);
66
82
  }
67
- if (authNamesLength !== 1) {
68
- throw getError(`duplicated ${AUTHORIZATION} header`, 401);
83
+ if (authNamesLength !== SINGLE) {
84
+ throw getError(`duplicated ${AUTHORIZATION} header`, UNAUTHORIZED_ERROR);
69
85
  }
70
- const auth = headers[authNames[0]].split(' ');
86
+ const auth = headers[authNames[FIRST_AUTHORIZATION]].split(' ');
71
87
 
72
- if (!BEARERS.includes(auth[0]) || auth.length !== 2) {
73
- throw getError(`authorization type incorrect: ${auth[0]}`, 401);
88
+ if (!BEARERS.includes(auth[BEARER_INDEX]) || auth.length !== BEARER_LENGTH) {
89
+ throw getError(`authorization type incorrect: ${auth[BEARER_INDEX]}`, UNAUTHORIZED_ERROR);
74
90
  }
75
- return auth[1];
91
+ return auth[TOKEN_INDEX];
76
92
  };
77
93
 
78
94
  const checkScopes = (tokenScopes, defScopes, definition) => {
@@ -82,52 +98,52 @@ const checkScopes = (tokenScopes, defScopes, definition) => {
82
98
  let claims = [];
83
99
  let onBehalf = false;
84
100
 
85
- if (defScopes && defScopes.length !== 0) {
101
+ if (defScopes && defScopes.length !== EMPTY) {
86
102
  const currentScopes = tokenScopes.split(SCOPES_SEPARATOR);
87
103
  const intersects = [];
88
- let resourceIndex = 1;
104
+ let resourceIndex = FIRST;
89
105
 
90
106
  currentScopes.forEach((currentScope) => {
91
107
  const analyzedScope = currentScope.split(SCOPE_CLAIMS_SEPARATOR);
92
- const analyzedResource = analyzedScope[0].split(RESOURCE_SEPARATOR);
108
+ const analyzedResource = analyzedScope[SCOPE_INDEX].split(RESOURCE_SEPARATOR);
93
109
 
94
- if (analyzedResource[0] === ON_BEHALF) {
110
+ if (analyzedResource[RESOURCE_INDEX] === ON_BEHALF) {
95
111
  onBehalf = true;
96
- resourceIndex = 2; // legacy handling
112
+ resourceIndex = SECOND; // legacy handling
97
113
  }
98
114
 
99
- if (defScopes.includes(analyzedScope[0])) {
100
- if (analyzedScope[1]) {
115
+ if (defScopes.includes(analyzedScope[SCOPE_INDEX])) {
116
+ if (analyzedScope[CLAIMS_INDEX]) {
101
117
  const includedDefinitionName = `${analyzedResource[resourceIndex]}${CLAIMS_DEFINITION}`;
102
118
 
103
119
  if (!definition.components || !definition.components.schemas) {
104
- throw getError(`missing ${includedDefinitionName} definition: no definitions`, 500);
120
+ throw getError(`missing ${includedDefinitionName} definition: no definition`, SYSTEM_ERROR);
105
121
  }
106
122
  const includedDefinition = definition.components.schemas[includedDefinitionName];
107
123
 
108
124
  if (!includedDefinition) {
109
- throw getError(`missing ${includedDefinitionName} definition`, 500);
125
+ throw getError(`missing ${includedDefinitionName} definition`, SYSTEM_ERROR);
110
126
  }
111
- const includedClaims = analyzedScope[1].split(CLAIMS_SEPARATOR);
127
+ const includedClaims = analyzedScope[CLAIMS_INDEX].split(CLAIMS_SEPARATOR);
112
128
  const definitionClaims = Object.keys(includedDefinition);
113
129
  const claimsIntersects = intersection(includedClaims, definitionClaims);
114
130
 
115
131
  if (claimsIntersects.length !== includedClaims.length) {
116
- throw getError(`incorrect claims included: ${difference(includedClaims, claimsIntersects)}`, 403);
132
+ throw getError(`incorrect claims included: ${difference(includedClaims, claimsIntersects)}`, FORBIDDEN_ERROR);
117
133
  }
118
134
  claims = claims.concat(claimsIntersects);
119
135
  }
120
- intersects.push(analyzedScope[0]);
136
+ intersects.push(analyzedScope[SCOPE_INDEX]);
121
137
  }
122
138
  });
123
- if (intersects.length === 0) {
124
- throw getError(`incorrect scopes: ${tokenScopes}`, 403);
139
+ if (intersects.length === EMPTY) {
140
+ throw getError(`incorrect scopes: ${tokenScopes}`, FORBIDDEN_ERROR);
125
141
  }
126
142
  }
127
143
  return { onBehalf, claims };
128
144
  };
129
145
 
130
- module.exports = (config) => {
146
+ export const securityLib = (config) => {
131
147
  const verifyTokenClientCredentials = (authToken) => {
132
148
  const { server, generic } = config.security;
133
149
  const options = {
@@ -136,17 +152,21 @@ module.exports = (config) => {
136
152
  // subject: `${config.serverSettings.id}@clients`,
137
153
  };
138
154
 
139
- try { jwt.verify(authToken, (generic.key === NO_GENERIC) ? server.accessKey : generic.key, options); }
155
+ try {
156
+ jwt.verify(authToken, (generic.key === NO_GENERIC) ? server.accessKey : generic.key, options);
157
+ }
140
158
  catch (err) {
141
159
  if (generic.previousKey) { // backward compatibility
142
- try { jwt.verify(authToken, generic.previousKey, options); }
160
+ try {
161
+ jwt.verify(authToken, generic.previousKey, options);
162
+ }
143
163
  catch (secondErr) {
144
- secondErr.statusCode = 403;
164
+ secondErr.statusCode = FORBIDDEN_ERROR;
145
165
  throw secondErr;
146
166
  }
147
167
  }
148
168
  else {
149
- err.statusCode = 403;
169
+ err.statusCode = FORBIDDEN_ERROR;
150
170
  throw err;
151
171
  }
152
172
  }
@@ -159,32 +179,34 @@ module.exports = (config) => {
159
179
  issuer: (implicit && implicit.issuer) || server.issuer,
160
180
  };
161
181
 
162
- try { jwt.verify(authToken, (implicit && implicit.key) || ((generic.key === NO_GENERIC) ? server.accessKey : generic.key), options); }
182
+ try {
183
+ jwt.verify(authToken, (implicit && implicit.key) || ((generic.key === NO_GENERIC) ? server.accessKey : generic.key), options);
184
+ }
163
185
  catch (err) {
164
- err.statusCode = 403;
186
+ err.statusCode = FORBIDDEN_ERROR;
165
187
  throw err;
166
188
  }
167
189
  };
168
190
 
169
191
  const AdminSecurity = {
170
- regular: (c, req) => {
192
+ regular: (con, req) => {
171
193
  const authToken = checkHeaders(req.headers);
172
194
  const token = checkToken(authToken);
173
- const { request } = c;
195
+ const { request } = con;
174
196
 
175
197
  if (token.subType !== ADMIN && token.subType !== SUB_ADMIN) {
176
- throw getError('invalid token: wrong type', 403);
198
+ throw getError('invalid token: wrong type', FORBIDDEN_ERROR);
177
199
  }
178
200
  if (token.subType === SUB_ADMIN) {
179
201
  if (!token.cust) {
180
- throw getError('invalid token: no customer', 403);
202
+ throw getError('invalid token: no customer', FORBIDDEN_ERROR);
181
203
  }
182
204
  }
183
205
  else if (token.sub !== `${config.security.admin.externalId}${CLIENT}`) {
184
- throw getError(`jwt subject invalid: ${token.sub}`, 403);
206
+ throw getError(`jwt subject invalid: ${token.sub}`, FORBIDDEN_ERROR);
185
207
  }
186
208
  verifyTokenClientCredentials(authToken);
187
- const scopeResult = checkScopes(token.scope, getScopes(c, ADMIN_SECURITY), c.api.definition);
209
+ const scopeResult = checkScopes(token.scope, getScopes(con, ADMIN_SECURITY), con.api.definition);
188
210
 
189
211
  req[TOKEN_PARAMS.claims] = scopeResult.claims;
190
212
  request[TOKEN_PARAMS.claims] = scopeResult.claims;
@@ -202,8 +224,8 @@ module.exports = (config) => {
202
224
  }
203
225
  return true;
204
226
  },
205
- mock: (c, req) => {
206
- const { request } = c;
227
+ mock: (con, req) => {
228
+ const { request } = con;
207
229
 
208
230
  req[TOKEN_PARAMS.claims] = ['dummyClaims'];
209
231
  req[TOKEN_PARAMS.tokenType] = ADMIN;
@@ -218,16 +240,16 @@ module.exports = (config) => {
218
240
  };
219
241
 
220
242
  const SystemSecurity = {
221
- regular: (c, req) => {
243
+ regular: (con, req) => {
222
244
  const authToken = checkHeaders(req.headers);
223
245
  const token = checkToken(authToken);
224
- const { request } = c;
246
+ const { request } = con;
225
247
 
226
248
  if (token.subType === ADMIN || token.subType === SUB_ADMIN) {
227
- throw getError('invalid token: wrong type', 403);
249
+ throw getError('invalid token: wrong type', FORBIDDEN_ERROR);
228
250
  }
229
251
  verifyTokenClientCredentials(authToken);
230
- const scopeResult = checkScopes(token.scope, getScopes(c, SYSTEM_SECURITY), c.api.definition);
252
+ const scopeResult = checkScopes(token.scope, getScopes(con, SYSTEM_SECURITY), con.api.definition);
231
253
 
232
254
  if (scopeResult.onBehalf) {
233
255
  req[TOKEN_PARAMS.onBehalf] = true;
@@ -253,8 +275,8 @@ module.exports = (config) => {
253
275
  }
254
276
  return true;
255
277
  },
256
- mock: (c, req) => {
257
- const { request } = c;
278
+ mock: (con, req) => {
279
+ const { request } = con;
258
280
 
259
281
  req[TOKEN_PARAMS.claims] = ['dummyClaims'];
260
282
  req[TOKEN_PARAMS.tokenType] = 'dummyServiceType';
@@ -269,13 +291,13 @@ module.exports = (config) => {
269
291
  };
270
292
 
271
293
  const UserSecurity = {
272
- regular: (c, req) => {
294
+ regular: (con, req) => {
273
295
  const authToken = checkHeaders(req.headers);
274
296
  const token = checkToken(authToken);
275
- const { request } = c;
297
+ const { request } = con;
276
298
 
277
299
  verifyTokenImplicit(authToken);
278
- const scopeResult = checkScopes(token.scope, getScopes(c, USER_SECURITY), c.api.definition);
300
+ const scopeResult = checkScopes(token.scope, getScopes(con, USER_SECURITY), con.api.definition);
279
301
 
280
302
  if (scopeResult.onBehalf) {
281
303
  req[TOKEN_PARAMS.onBehalf] = true;
@@ -301,8 +323,8 @@ module.exports = (config) => {
301
323
  }
302
324
  return true;
303
325
  },
304
- mock: (c, req) => {
305
- const { request } = c;
326
+ mock: (con, req) => {
327
+ const { request } = con;
306
328
 
307
329
  req[TOKEN_PARAMS.claims] = ['dummyClaims'];
308
330
  req[TOKEN_PARAMS.userId] = 'dummyUserId';
@@ -317,19 +339,19 @@ module.exports = (config) => {
317
339
  };
318
340
 
319
341
  const ApiKeySecurity = {
320
- regular: (c, req) => {
342
+ regular: (con, req) => {
321
343
  const apiKey = req.headers ? req.headers[API_KEY_NAME.toLowerCase()] : null;
322
- const { request } = c;
344
+ const { request } = con;
323
345
 
324
346
  if (config.security.apiKeys.includes(apiKey)) {
325
347
  req[API_KEY_NAME] = apiKey;
326
348
  request[API_KEY_NAME] = apiKey;
327
349
  return true;
328
350
  }
329
- throw getError('invalid API key', 401);
351
+ throw getError('invalid API key', UNAUTHORIZED_ERROR);
330
352
  },
331
- mock: (c, req) => {
332
- const { request } = c;
353
+ mock: (con, req) => {
354
+ const { request } = con;
333
355
 
334
356
  req[API_KEY_NAME] = 'dummyApiKey';
335
357
  request[API_KEY_NAME] = 'dummyApiKey';
package/package.json CHANGED
@@ -1,16 +1,16 @@
1
1
  {
2
2
  "name": "@mimik/api-helper",
3
- "version": "1.1.4",
3
+ "version": "2.0.1",
4
4
  "description": "helper for openAPI backend and mimik service",
5
- "main": "index.js",
5
+ "main": "./index.js",
6
+ "type": "module",
6
7
  "scripts": {
7
- "lint": "eslint --ignore-path .gitignore .",
8
+ "lint": "eslint . --no-error-on-unmatched-pattern",
8
9
  "docs": "jsdoc2md index.js > README.md",
9
10
  "test": "echo \"Error: no test specified\" && exit 0",
10
11
  "test-ci": "echo \"Error: no test specified\" && exit 0",
11
12
  "prepublishOnly": "npm run docs && npm run lint && npm run test-ci",
12
- "commit-ready": "npm run docs && npm run lint && npm run test-ci",
13
- "prepare": "husky install"
13
+ "commit-ready": "npm run docs && npm run lint && npm run test-ci"
14
14
  },
15
15
  "husky": {
16
16
  "hooks": {
@@ -30,11 +30,11 @@
30
30
  "url": "https://bitbucket.org/mimiktech/api-helper"
31
31
  },
32
32
  "dependencies": {
33
- "@mimik/request-helper":"^1.7.11",
34
- "@mimik/request-retry": "^3.0.1",
35
- "@mimik/response-helper": "^3.1.0",
36
- "@mimik/sumologic-winston-logger": "^1.6.21",
37
- "@mimik/swagger-helper": "^4.0.10",
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",
38
38
  "ajv-formats": "3.0.1",
39
39
  "js-base64": "3.7.7",
40
40
  "js-yaml":"4.1.0",
@@ -42,19 +42,16 @@
42
42
  "lodash.compact": "3.0.1",
43
43
  "lodash.difference": "4.5.0",
44
44
  "lodash.intersection": "4.4.0",
45
- "openapi-backend": "5.11.0",
46
- "swagger-client": "3.29.3"
45
+ "openapi-backend": "5.13.0",
46
+ "swagger-client": "3.35.6"
47
47
  },
48
48
  "devDependencies": {
49
- "@mimik/eslint-plugin-dependencies": "^2.4.6",
50
- "@mimik/eslint-plugin-document-env": "^1.0.6",
51
- "eslint": "8.57.0",
52
- "eslint-config-airbnb": "19.0.4",
53
- "eslint-plugin-import": "2.31.0",
54
- "eslint-plugin-jsx-a11y": "6.10.0",
55
- "eslint-plugin-react": "7.37.1",
56
- "eslint-plugin-react-hooks": "4.6.2",
57
- "husky": "9.1.6",
58
- "jsdoc-to-markdown": "9.0.2"
49
+ "@eslint/js": "9.31.0",
50
+ "@mimik/eslint-plugin-document-env": "^2.0.8",
51
+ "@stylistic/eslint-plugin": "5.2.1",
52
+ "eslint": "9.31.0",
53
+ "eslint-plugin-import": "2.32.0",
54
+ "husky": "9.1.7",
55
+ "jsdoc-to-markdown": "9.1.2"
59
56
  }
60
57
  }
package/.eslintrc DELETED
@@ -1,43 +0,0 @@
1
- {
2
- "plugins": [
3
- "@mimik/document-env",
4
- "@mimik/dependencies"
5
- ],
6
- "env": {
7
- "node": true
8
- },
9
- "parserOptions": {
10
- "ecmaVersion": 2020
11
- },
12
- "extends": "airbnb",
13
- "rules": {
14
- "import/no-extraneous-dependencies": ["error", { "devDependencies": true }],
15
- "import/no-unresolved": ["error", { "amd": true, "commonjs": true, "caseSensitiveStrict": true }],
16
- "brace-style": [1, "stroustrup", { "allowSingleLine": true }],
17
- "no-confusing-arrow": [0], // arrow isnt confusing
18
- "max-len": [1, 180, { "ignoreComments": true }],
19
- "linebreak-style": 0,
20
- "quotes": [1, "single"],
21
- "semi": [1, "always"],
22
- "no-process-env": ["error"],
23
- "@mimik/document-env/validate-document-env": 2,
24
- "@mimik/dependencies/case-sensitive": 2,
25
- "@mimik/dependencies/no-cycles": 2,
26
- "@mimik/dependencies/require-json-ext": 2
27
- },
28
- "settings":{
29
- "react": {
30
- "version": "detect"
31
- }
32
- },
33
- "globals": {
34
- "module": true,
35
- "require": true,
36
- "const": false,
37
- "it": false,
38
- "describe": false,
39
- "before": true,
40
- "after": true,
41
- "JSON": true
42
- }
43
- }