@mimik/api-helper 0.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/.eslintrc ADDED
@@ -0,0 +1,43 @@
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
+ }
package/.nycrc ADDED
@@ -0,0 +1,4 @@
1
+ {
2
+ "exclude": ["gulpfile.js"],
3
+ "reporter": ["lcov", "text"]
4
+ }
package/README.md ADDED
@@ -0,0 +1,130 @@
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>.</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>
24
+ Setup the API to be use for a service
25
+
26
+ **Kind**: global function
27
+ **Returns**: <code>Promise</code> - .
28
+ &fulfil {object} The API file itself.
29
+ **Category**: async
30
+ **Throws**:
31
+
32
+ - <code>Promise</code> An error is thrown if the initiatilization failed.
33
+
34
+ By default System and Admin security are automatically registered
35
+
36
+ **Requires**: <code>module:@mimik/response-helper</code>, <code>module:@mimik/sumologic-winston-logger</code>, <code>module:@mimik/swagger-helper</code>, <code>module:ajv-formats</code>, <code>module:fs</code>, <code>module:jsonwebtoken</code>, <code>module:lodash</code>
37
+
38
+ | Param | Type | Description |
39
+ | --- | --- | --- |
40
+ | setup | <code>object</code> | Object containing the apiFilename and the exisiting securitySchemes in the API definition. |
41
+ | registeredOperations | <code>object</code> | List of the operation to register for the API. |
42
+ | securityHandlers | <code>object</code> | List of the securityHandlers to add for the service. |
43
+ | extraFormats | <code>object</code> | list of the formats to add for validatng properties. |
44
+ | config | <code>object</code> | Configuration of the service. |
45
+ | correlationId | <code>UUID.&lt;string&gt;</code> | CorrelationId when logging activites. |
46
+
47
+ <a name="getAPIFile"></a>
48
+
49
+ ## getAPIFile(apiFilename, correlationId, options) ⇒ <code>Promise</code>
50
+ Gets the API file from swaggerhub and store it in the give PATH location.
51
+
52
+ **Kind**: global function
53
+ **Returns**: <code>Promise</code> - .
54
+ &fulfil {object} The API file itself.
55
+ **Category**: async
56
+ **Throws**:
57
+
58
+ - <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 connot be saved.
59
+
60
+ **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>
61
+
62
+ | Param | Type | Description |
63
+ | --- | --- | --- |
64
+ | apiFilename | <code>PATH.&lt;string&gt;</code> | Name of the file where the API file will be stored. |
65
+ | correlationId | <code>UUID.&lt;string&gt;</code> | CorrelationId when logging activites. |
66
+ | options | <code>object</code> | Options associated with the call. Use to pass `metrics` to `rpRetry` and `apiKey`` to access private API. |
67
+
68
+ <a name="setupServerFiles"></a>
69
+
70
+ ## setupServerFiles(apiFilename, controllersDirectory, buidDirectory, correlationId, options) ⇒ <code>Promise</code>
71
+ Setup and validates files for the server
72
+
73
+ **Kind**: global function
74
+ **Returns**: <code>Promise</code> - .
75
+ &fulfil {object} The API file, the API filename, and the existing know security schemes.
76
+ **Category**: async
77
+ **Throws**:
78
+
79
+ - <code>Promise</code> An error is thrown for many reasons assocated with getAPIFile or validateSecuritySchemes or extractProperties.
80
+
81
+ **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>
82
+
83
+ | Param | Type | Description |
84
+ | --- | --- | --- |
85
+ | apiFilename | <code>PATH.&lt;string&gt;</code> | Name of the file where the API file will be stored. |
86
+ | controllersDirectory | <code>PATH.&lt;string&gt;</code> | Directory to find the controller files. |
87
+ | buidDirectory | <code>PATH.&lt;string&gt;</code> | = Directory where the register file will be stored. |
88
+ | correlationId | <code>UUID.&lt;string&gt;</code> | CorrelationId when logging activites. |
89
+ | options | <code>object</code> | Options associated with the call. Use to pass `metrics` to `rpRetry` and `apiKey`` to access private API. |
90
+
91
+ <a name="validateSecuritySchemes"></a>
92
+
93
+ ## validateSecuritySchemes(apiDefinition, correlationId) ⇒
94
+ Validates the known SecuritySchemes: `SystemSecurity`, `AdminSecurity`, `UserSecurity`, `PeerSecurity`.
95
+
96
+ **Kind**: global function
97
+ **Returns**: An array of the know securitySchemes that are in the API definition.
98
+ **Category**: sync
99
+ **Throws**:
100
+
101
+ - An error is thrown for the first validation fails.
102
+
103
+ **Requires**: <code>module:@mimik/sumologic-winston-logger</code>, <code>module:@mimik/response-helper</code>
104
+
105
+ | Param | Type | Description |
106
+ | --- | --- | --- |
107
+ | apiDefinition | <code>object</code> | JSON object containing the API definition. |
108
+ | correlationId | <code>UUID.&lt;string&gt;</code> | CorrelationId when logging activites. |
109
+
110
+ <a name="extractProperties"></a>
111
+
112
+ ## extractProperties(apiDefinition, controllersDirectory, buidDirectory, correlationId) ⇒
113
+ Extracts the properties from API definiton and creates a file binding the handler with the controller operations.
114
+
115
+ **Kind**: global function
116
+ **Returns**: null
117
+ **Category**: sync
118
+ **Throws**:
119
+
120
+ - An error is thrown for many reasons, like operationId does not exist in controllers, controller dies not exist...
121
+
122
+ **Requires**: <code>module:@mimik/response-helper</code>, <code>module:@mimik/sumologic-winston-logger</code>, <code>module:fs</code>
123
+
124
+ | Param | Type | Description |
125
+ | --- | --- | --- |
126
+ | apiDefinition | <code>object</code> | JSON object containing the API definition. |
127
+ | controllersDirectory | <code>PATH.&lt;string&gt;</code> | Directory to find the controller files. |
128
+ | buidDirectory | <code>PATH.&lt;string&gt;</code> | = Directory where the register file will be stored. |
129
+ | correlationId | <code>UUID.&lt;string&gt;</code> | CorrelationId when logging activites. |
130
+
package/index.js ADDED
@@ -0,0 +1,347 @@
1
+ const { OpenAPIBackend } = require('openapi-backend');
2
+ const fs = require('fs');
3
+ const pathLib = require('path');
4
+ const yaml = require('js-yaml');
5
+ const _ = require('lodash');
6
+
7
+ const logger = require('@mimik/sumologic-winston-logger');
8
+ const { getRichError } = require('@mimik/response-helper');
9
+ const { rpRetry } = require('@mimik/request-retry');
10
+
11
+ const { saveProperties } = require('./lib/extract-helper');
12
+ const { validateOauth2, validateApiKey } = require('./lib/oauthValidation-helper');
13
+ const { ajvFormats } = require('./lib/ajvHelpers');
14
+ const securityLib = require('./lib/securityHandlers');
15
+ const baseHandlers = require('./lib/baseHandlers');
16
+ const {
17
+ LOCAL,
18
+ SET_ON,
19
+ SECURITY_ON,
20
+ SECURITY_OFF,
21
+ X_ROUTER_CONTROLLER,
22
+ EXTENSION,
23
+ ADMIN_SECURITY,
24
+ SYSTEM_SECURITY,
25
+ PEER_SECURITY,
26
+ USER_SECURITY,
27
+ API_KEY_SECURITY,
28
+ CLIENT_CREDENTIALS,
29
+ IMPLICIT,
30
+ POSTFIX,
31
+ API_PROVIDER,
32
+ RESOLVED,
33
+ } = require('./lib/common');
34
+
35
+ /**
36
+ *
37
+ * Setup the API to be use for a service
38
+ *
39
+ * @function apiSetup
40
+ * @category async
41
+ * @requires @mimik/response-helper
42
+ * @requires @mimik/sumologic-winston-logger
43
+ * @requires @mimik/swagger-helper
44
+ * @requires ajv-formats
45
+ * @requires fs
46
+ * @requires jsonwebtoken
47
+ * @requires lodash
48
+ * @param {object} setup - Object containing the apiFilename and the exisiting securitySchemes in the API definition.
49
+ * @param {object} registeredOperations - List of the operation to register for the API.
50
+ * @param {object} securityHandlers - List of the securityHandlers to add for the service.
51
+ * @param {object} extraFormats - list of the formats to add for validatng properties.
52
+ * @param {object} config - Configuration of the service.
53
+ * @param {UUID.<string>} correlationId - CorrelationId when logging activites.
54
+ * @return {Promise}.
55
+ * &fulfil {object} The API file itself.
56
+ * @throws {Promise} An error is thrown if the initiatilization failed.
57
+ *
58
+ * By default System and Admin security are automatically registered
59
+ */
60
+ const apiSetup = (setup, registeredOperations, securityHandlers, extraFormats, config, correlationId) => {
61
+ const { apiFilename, existingSecuritySchemes } = setup;
62
+ const { SystemSecurity, AdminSecurity } = securityLib(config);
63
+ const api = new OpenAPIBackend({
64
+ definition: apiFilename,
65
+ apiRoot: config.serverSettings.basePath,
66
+ strict: true,
67
+ validate: true,
68
+ ajvOpts: {
69
+ allErrors: true,
70
+ verbose: true,
71
+ },
72
+ customizeAjv: ajvFormats(extraFormats),
73
+ handlers: baseHandlers,
74
+ });
75
+ let mode;
76
+
77
+ api.register(registeredOperations);
78
+ if (config && (config.nodeEnvironment.toLowerCase() !== LOCAL || config.serverSettings.securitySet === SET_ON)) {
79
+ mode = SECURITY_ON;
80
+ }
81
+ else {
82
+ logger.warn('security disabled: tokens will not be used and /me and /onbehalf will not work', correlationId);
83
+ mode = SECURITY_OFF;
84
+ }
85
+ api.registerSecurityHandler(SYSTEM_SECURITY, SystemSecurity[mode]);
86
+ api.registerSecurityHandler(ADMIN_SECURITY, AdminSecurity[mode]);
87
+ // api.registerSecurityHandler('ApiKeySecurity', ApiKeySecurity[mode]);
88
+ if (securityHandlers) {
89
+ const securityHandlerNames = Object.keys(securityHandlers);
90
+
91
+ existingSecuritySchemes.forEach((securityScheme) => {
92
+ if (!securityHandlerNames.includes(securityScheme)) throw getRichError('System', 'missing handler for security Scheme', { securityScheme });
93
+ });
94
+ Object.keys(securityHandlers).forEach((securityHandlerName) => {
95
+ api.registerSecurityHandler(securityHandlerName, securityHandlers[securityHandlerName][mode]);
96
+ });
97
+ }
98
+ else if (existingSecuritySchemes.length !== 0) throw getRichError('System', 'missing handlers for security Scheme', { existingSecuritySchemes });
99
+ api.init()
100
+ .catch((err) => {
101
+ throw getRichError('System', 'could not initialize the api', { api }, err);
102
+ });
103
+ return api;
104
+ };
105
+
106
+ /**
107
+ *
108
+ * Gets the API file from swaggerhub and store it in the give PATH location.
109
+ *
110
+ * @function getAPIFile
111
+ * @category async
112
+ * @requires @mimik/request-retry
113
+ * @requires @mimik/response-helper
114
+ * @requires @mimik/sumologic-winston-logger
115
+ * @requires fs
116
+ * @requires js-yaml
117
+ * @requires path
118
+ * @param {PATH.<string>} apiFilename - Name of the file where the API file will be stored.
119
+ * @param {UUID.<string>} correlationId - CorrelationId when logging activites.
120
+ * @param {object} options - Options associated with the call. Use to pass `metrics` to `rpRetry` and `apiKey`` to access private API.
121
+ * @return {Promise}.
122
+ * &fulfil {object} The API file itself.
123
+ * @throws {Promise} An error is thrown if the apiFilename resolution generates an error or the request to the API provider fails or the file connot be saved.
124
+ */
125
+ const getAPIFile = (apiFilename, correlationId, options) => {
126
+ logger.info('Getting API definition', correlationId);
127
+ try {
128
+ if (fs.existsSync(apiFilename)) {
129
+ logger.debug('API file already exists', { apiFilename }, correlationId);
130
+ let apiDefinition = fs.readFileSync(apiFilename, 'utf8');
131
+
132
+ try { apiDefinition = JSON.parse(apiDefinition); }
133
+ catch (errJSON) {
134
+ try { apiDefinition = yaml.load(apiDefinition); }
135
+ catch (errYaml) {
136
+ return Promise.reject(getRichError('System', 'wrong file format', { apiFilename }, errYaml));
137
+ }
138
+ }
139
+ return Promise.resolve(apiDefinition);
140
+ }
141
+ }
142
+ catch (err) {
143
+ return Promise.reject(getRichError('System', 'file system error', { apiFilename }, err));
144
+ }
145
+ let fileName;
146
+ let apiDirectory;
147
+
148
+ try { fileName = pathLib.basename(apiFilename); }
149
+ catch (err) { return Promise.reject(getRichError('System', 'file name error', { apiFilename }, err)); }
150
+ try { apiDirectory = pathLib.dirname(apiFilename); }
151
+ catch (err) { return Promise.reject(getRichError('System', 'directory name error', { apiFilename }, err)); }
152
+ const params = fileName.split('_');
153
+
154
+ if (params.length !== 4 || params[3] !== POSTFIX) {
155
+ return Promise.reject(getRichError('System', 'wrong api name', { apiFilename }));
156
+ }
157
+ try {
158
+ if (!fs.existsSync(apiDirectory)) {
159
+ logger.debug('Creating directory', { apiDirectory }, correlationId);
160
+ fs.mkdirSync(apiDirectory);
161
+ }
162
+ }
163
+ catch (err) {
164
+ return Promise.reject(getRichError('System', 'file system error', { apiDirectory }, err));
165
+ }
166
+ const url = `${API_PROVIDER}/${params[0]}/${params[1]}/${params[2]}?${RESOLVED}`;
167
+
168
+ logger.debug('API file does not exist, retrieving it', { url }, correlationId);
169
+ const opts = {
170
+ method: 'GET',
171
+ url,
172
+ headers: {
173
+ 'x-correlation-id': correlationId,
174
+ },
175
+ };
176
+ if (options) {
177
+ if (options.apiKey) opts.headers = { Authorization: options.apiKey };
178
+ if (options.metrics) {
179
+ opts.metrics = options.metrics;
180
+ opts.metrics.url = url;
181
+ }
182
+ }
183
+ return rpRetry(opts)
184
+ .catch((err) => {
185
+ const error = err;
186
+
187
+ error.message = `${error.message} - { "apiFilename":"${apiFilename}"}`;
188
+ throw error;
189
+ })
190
+ .then((result) => {
191
+ try {
192
+ fs.writeFileSync(apiFilename, JSON.stringify(result, null, 2));
193
+ }
194
+ catch (err) {
195
+ throw getRichError('System', `file system error: ${err.message}`, { apiFilename }, err);
196
+ }
197
+ return result;
198
+ });
199
+ };
200
+
201
+ /**
202
+ *
203
+ * Validates the known SecuritySchemes: `SystemSecurity`, `AdminSecurity`, `UserSecurity`, `PeerSecurity`.
204
+ *
205
+ * @function validateSecuritySchemes
206
+ * @category sync
207
+ * @requires @mimik/sumologic-winston-logger
208
+ * @requires @mimik/response-helper
209
+ * @param {object} apiDefinition - JSON object containing the API definition.
210
+ * @param {UUID.<string>} correlationId - CorrelationId when logging activites.
211
+ * @return An array of the know securitySchemes that are in the API definition.
212
+ * @throws An error is thrown for the first validation fails.
213
+ */
214
+ const validateSecuritySchemes = (apiDefinition, correlationId) => {
215
+ const existingSecuritySchemes = [];
216
+
217
+ if (apiDefinition.components?.securitySchemes) {
218
+ logger.info('validating known security schemes', correlationId);
219
+ const { securitySchemes } = apiDefinition.components;
220
+
221
+ validateOauth2(securitySchemes, ADMIN_SECURITY, CLIENT_CREDENTIALS);
222
+ validateOauth2(securitySchemes, SYSTEM_SECURITY, CLIENT_CREDENTIALS);
223
+ existingSecuritySchemes.push(validateOauth2(securitySchemes, PEER_SECURITY, CLIENT_CREDENTIALS));
224
+ existingSecuritySchemes.push(validateOauth2(securitySchemes, USER_SECURITY, IMPLICIT));
225
+ existingSecuritySchemes.push(validateApiKey(securitySchemes, API_KEY_SECURITY));
226
+ }
227
+ return existingSecuritySchemes;
228
+ };
229
+
230
+ /**
231
+ *
232
+ * Extracts the properties from API definiton and creates a file binding the handler with the controller operations.
233
+ *
234
+ * @function extractProperties
235
+ * @category sync
236
+ * @requires @mimik/response-helper
237
+ * @requires @mimik/sumologic-winston-logger
238
+ * @requires fs
239
+ * @param {object} apiDefinition - JSON object containing the API definition.
240
+ * @param {PATH.<string>} controllersDirectory - Directory to find the controller files.
241
+ * @param {PATH.<string>} buidDirectory = Directory where the register file will be stored.
242
+ * @param {UUID.<string>} correlationId - CorrelationId when logging activites.
243
+ * @return null
244
+ * @throws An error is thrown for many reasons, like operationId does not exist in controllers, controller dies not exist...
245
+ */
246
+ const extractProperties = (apiDefinition, controllersDirectory, buildDirectory, correlationId) => {
247
+ const result = {};
248
+ const { paths } = apiDefinition;
249
+ let controllersDirectoryName;
250
+
251
+ try { controllersDirectoryName = pathLib.basename(controllersDirectory); }
252
+ catch (err) {
253
+ throw getRichError('System', 'directory name error', { controllersDirectory }, err);
254
+ }
255
+ logger.info('creating handler link file', correlationId);
256
+ try {
257
+ if (!fs.existsSync(controllersDirectory)) {
258
+ throw getRichError('System', 'missing directory', { controllersDirectory });
259
+ }
260
+ }
261
+ catch (err) {
262
+ throw getRichError('System', 'file system error', { controllersDirectory }, err);
263
+ }
264
+ if (paths) {
265
+ const routes = Object.keys(paths);
266
+
267
+ routes.forEach((route) => {
268
+ const path = paths[route];
269
+ const verbs = Object.keys(path);
270
+
271
+ verbs.forEach((verb) => {
272
+ const operation = path[verb];
273
+
274
+ if (operation.operationId) {
275
+ if (!operation[X_ROUTER_CONTROLLER]) {
276
+ throw getRichError('System', 'missing property', { property: X_ROUTER_CONTROLLER });
277
+ }
278
+ else {
279
+ const controller = operation[X_ROUTER_CONTROLLER];
280
+ const controllerFilename = `${controllersDirectory}/${controller}${EXTENSION}`;
281
+
282
+ try {
283
+ if (!fs.existsSync(controllerFilename)) {
284
+ throw getRichError('System', 'missing controller file', { controllerFilename });
285
+ }
286
+ }
287
+ catch (err) {
288
+ throw getRichError('System', 'file system error', { controllerFilename }, err);
289
+ }
290
+ try {
291
+ const file = fs.readFileSync(controllerFilename, 'utf8');
292
+ if (!file.includes(`${operation.operationId},`)
293
+ && !file.includes(`${operation.operationId}:`)
294
+ && !file.includes(`module.exports = ${operation.operationId}`)) { // code must be linted before
295
+ throw getRichError('System', 'missing operationId in controller file', { controllerFilename, operationId: operation.operationId });
296
+ }
297
+ }
298
+ catch (err) {
299
+ throw getRichError('System', 'file system error', { controllerFilename, operationId: operation.operationId }, err);
300
+ }
301
+ if (!result[controller]) result[controller] = [operation.operationId];
302
+ else result[controller].push(operation.operationId);
303
+ }
304
+ }
305
+ });
306
+ });
307
+ }
308
+ saveProperties(result, buildDirectory, controllersDirectoryName, correlationId);
309
+ };
310
+
311
+ /**
312
+ *
313
+ * Setup and validates files for the server
314
+ *
315
+ * @function setupServerFiles
316
+ * @category async
317
+ * @requires @mimik/request-retry
318
+ * @requires @mimik/response-helper
319
+ * @requires @mimik/sumologic-winston-logger
320
+ * @requires fs
321
+ * @requires js-yaml
322
+ * @requires path
323
+ * @param {PATH.<string>} apiFilename - Name of the file where the API file will be stored.
324
+ * @param {PATH.<string>} controllersDirectory - Directory to find the controller files.
325
+ * @param {PATH.<string>} buidDirectory = Directory where the register file will be stored.
326
+ * @param {UUID.<string>} correlationId - CorrelationId when logging activites.
327
+ * @param {object} options - Options associated with the call. Use to pass `metrics` to `rpRetry` and `apiKey`` to access private API.
328
+ * @return {Promise}.
329
+ * &fulfil {object} The API file, the API filename, and the existing know security schemes.
330
+ * @throws {Promise} An error is thrown for many reasons assocated with getAPIFile or validateSecuritySchemes or extractProperties.
331
+ */
332
+ const setupServerFiles = (apiFilename, controllersDirectory, buildDirectory, correlationId, options) => getAPIFile(apiFilename, correlationId, options)
333
+ .then((apiDefinition) => {
334
+ const existingSecuritySchemes = _.compact(validateSecuritySchemes(apiDefinition, correlationId));
335
+
336
+ extractProperties(apiDefinition, controllersDirectory, buildDirectory, correlationId);
337
+ return { apiDefinition, apiFilename, existingSecuritySchemes };
338
+ });
339
+
340
+ module.exports = {
341
+ apiSetup,
342
+ securityLib,
343
+ getAPIFile,
344
+ validateSecuritySchemes,
345
+ extractProperties,
346
+ setupServerFiles,
347
+ };
@@ -0,0 +1,22 @@
1
+ const addFormats = require('ajv-formats');
2
+
3
+ const { DEFAULT_FORMATS } = require('./common');
4
+
5
+ const ajvFormats = (origFormats) => (ajv) => {
6
+ const extraFormats = {};
7
+ const libFormats = DEFAULT_FORMATS;
8
+ let formats = origFormats;
9
+
10
+ if (!formats) formats = {};
11
+ Object.keys(formats).forEach((format) => {
12
+ if (!formats[format].type) libFormats.push(format);
13
+ else extraFormats[format] = formats[format];
14
+ });
15
+ addFormats(ajv, libFormats);
16
+ Object.keys(extraFormats).forEach((format) => ajv.addFormat(format, extraFormats[format]));
17
+ return ajv;
18
+ };
19
+
20
+ module.exports = {
21
+ ajvFormats,
22
+ };
@@ -0,0 +1,88 @@
1
+ const { rejectRequest } = require('@mimik/swagger-helper');
2
+
3
+ const validationFail = (c, req, res) => {
4
+ const error = new Error('Failed schema validation');
5
+
6
+ error.statusCode = 400;
7
+ error.info = {
8
+ method: req.method,
9
+ path: req.url,
10
+ errors: c.validation.errors,
11
+ warnings: c.validation.warnings || [],
12
+ };
13
+ rejectRequest(error, c, res);
14
+ };
15
+
16
+ const notFound = (c, req, res) => {
17
+ const path = req.url;
18
+ const error = new Error(`path ${path} not defined in Swagger specification`);
19
+
20
+ error.statusCode = 404;
21
+ error.info = {
22
+ method: req.method,
23
+ path,
24
+ };
25
+ rejectRequest(error, c, res);
26
+ };
27
+
28
+ const unauthorizedHandler = (c, req, res) => {
29
+ let error = new Error('Unauthorized');
30
+
31
+ error.statusCode = 401;
32
+ const schemes = Object.keys(c.security);
33
+
34
+ delete schemes.authorized;
35
+ schemes.forEach((scheme) => {
36
+ if (c.security[scheme]?.error) error = c.security[scheme].error;
37
+ });
38
+ rejectRequest(error, c, res);
39
+ };
40
+
41
+ const notImplemented = (c, req, res) => {
42
+ const { method } = req;
43
+ const path = req.url;
44
+ const error = new Error(`${req.method} ${path} defined in Swagger specification, but no implemented`);
45
+
46
+ error.statusCode = 505;
47
+ error.info = {
48
+ method,
49
+ path,
50
+ operationId: c.operation.operationId,
51
+ };
52
+ rejectRequest(error, c, res);
53
+ };
54
+
55
+ const methodNotAllowed = (c, req, res) => {
56
+ const { method } = req;
57
+ const path = req.url;
58
+ const error = new Error(`path ${path} defined in Swagger specification, but the method ${method} is not defined`);
59
+
60
+ error.statusCode = 405;
61
+ error.info = {
62
+ method,
63
+ path,
64
+ };
65
+ rejectRequest(error, c, res);
66
+ };
67
+
68
+ /*
69
+ const postResponseHandler = (c, req, res) => {
70
+ const valid = c.api.validateResponse(c.response, c.operation);
71
+ console.log('----->', c.response);
72
+ console.log('----->', valid);
73
+ if (valid.errors) {
74
+ // response validation failed
75
+ return res.status(502).json({ status: 502, err: valid.errors });
76
+ }
77
+ return res.status(200).json(c.response);
78
+ };
79
+ */
80
+
81
+ module.exports = {
82
+ validationFail,
83
+ notFound,
84
+ unauthorizedHandler,
85
+ notImplemented,
86
+ methodNotAllowed,
87
+ // postResponseHandler,
88
+ };
package/lib/common.js ADDED
@@ -0,0 +1,80 @@
1
+ const X_ROUTER_CONTROLLER = 'x-swagger-router-controller';
2
+ const EXTENSION = '.js';
3
+ const REGISTER = 'register';
4
+
5
+ const OAUTH2 = 'oauth2';
6
+ const API_KEY_IN = 'header';
7
+ const API_KEY_NAME = 'apiKey';
8
+
9
+ const ADMIN_SECURITY = 'AdminSecurity';
10
+ const SYSTEM_SECURITY = 'SystemSecurity';
11
+ const PEER_SECURITY = 'PeerSecurity';
12
+ const USER_SECURITY = 'UserSecurity';
13
+ const API_KEY_SECURITY = 'ApiKeySecurity';
14
+
15
+ const CLIENT_CREDENTIALS = 'clientCredentials';
16
+ const IMPLICIT = 'implicit';
17
+
18
+ const POSTFIX = 'swagger.json';
19
+ const API_PROVIDER = 'https://api.swaggerhub.com/apis';
20
+ const RESOLVED = 'resolved=true';
21
+
22
+ const LOCAL = 'local';
23
+ const SET_ON = 'on';
24
+ const SECURITY_ON = 'regular';
25
+ const SECURITY_OFF = 'mock';
26
+
27
+ const DEFAULT_FORMATS = ['date', 'time', 'date-time', 'byte', 'uuid', 'uri', 'email', 'ipv4', 'ipv6'];
28
+
29
+ const AUTHORIZATION = 'authorization';
30
+ const ADMIN = 'admin';
31
+ const SUB_ADMIN = 'subAdmin';
32
+ const CLIENT = '@clients';
33
+ const NO_GENERIC = 'noGeneric';
34
+ const ON_BEHALF = 'onBehalf';
35
+ const CLAIMS_DEFINITION = 'Claims';
36
+ const SCOPES_SEPARATOR = ' ';
37
+ const SCOPE_CLAIMS_SEPARATOR = '::';
38
+ const RESOURCE_SEPARATOR = ':';
39
+ const CLAIMS_SEPARATOR = ',';
40
+ const BEARERS = ['bearer', 'Bearer'];
41
+ const USER = 'user';
42
+ const CLUSTER = 'cluster';
43
+
44
+ module.exports = {
45
+ X_ROUTER_CONTROLLER,
46
+ EXTENSION,
47
+ REGISTER,
48
+ OAUTH2,
49
+ API_KEY_IN,
50
+ API_KEY_NAME,
51
+ ADMIN_SECURITY,
52
+ SYSTEM_SECURITY,
53
+ PEER_SECURITY,
54
+ USER_SECURITY,
55
+ API_KEY_SECURITY,
56
+ CLIENT_CREDENTIALS,
57
+ IMPLICIT,
58
+ POSTFIX,
59
+ API_PROVIDER,
60
+ RESOLVED,
61
+ LOCAL,
62
+ SET_ON,
63
+ SECURITY_ON,
64
+ SECURITY_OFF,
65
+ DEFAULT_FORMATS,
66
+ AUTHORIZATION,
67
+ ADMIN,
68
+ SUB_ADMIN,
69
+ CLIENT,
70
+ NO_GENERIC,
71
+ ON_BEHALF,
72
+ CLAIMS_DEFINITION,
73
+ SCOPES_SEPARATOR,
74
+ SCOPE_CLAIMS_SEPARATOR,
75
+ RESOURCE_SEPARATOR,
76
+ CLAIMS_SEPARATOR,
77
+ BEARERS,
78
+ USER,
79
+ CLUSTER,
80
+ };
@@ -0,0 +1,58 @@
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');
7
+
8
+ const saveProperties = (extractResult, buildDirectory, controllersDirectoryName, correlationId) => {
9
+ try {
10
+ if (!fs.existsSync(buildDirectory)) {
11
+ logger.debug('creating directory', { buildDirectory }, correlationId);
12
+ fs.mkdirSync(buildDirectory);
13
+ }
14
+ }
15
+ catch (err) {
16
+ throw getRichError('System', 'file system error', { buildDirectory }, err);
17
+ }
18
+ const filename = `${buildDirectory}/${REGISTER}${EXTENSION}`;
19
+ try {
20
+ if (fs.existsSync(filename)) {
21
+ logger.debug('removing file', { filename }, correlationId);
22
+ fs.unlinkSync(filename);
23
+ }
24
+ }
25
+ catch (err) {
26
+ throw getRichError('System', 'file system error', { filename }, err);
27
+ }
28
+ const controllers = Object.keys(extractResult);
29
+ const operationIds = [];
30
+ let stringToSave = '';
31
+ let itemToSave;
32
+
33
+ controllers.forEach((controller) => {
34
+ itemToSave = '';
35
+ extractResult[controller].forEach((operationId) => {
36
+ itemToSave = `${itemToSave} ${operationId},\n`;
37
+ operationIds.push(operationId);
38
+ });
39
+ stringToSave = `${stringToSave}const {\n${itemToSave}} = require('../${controllersDirectoryName}/${controller}');\n`;
40
+ });
41
+ stringToSave = `${stringToSave}\nmodule.exports = {\n`;
42
+ itemToSave = '';
43
+ operationIds.forEach((operationId) => {
44
+ itemToSave = `${itemToSave} ${operationId},\n`;
45
+ });
46
+ stringToSave = `${stringToSave}${itemToSave}};\n`;
47
+ logger.info(`creating ${filename}`, { filename }, correlationId);
48
+ try {
49
+ fs.writeFileSync(filename, stringToSave);
50
+ }
51
+ catch (err) {
52
+ throw getRichError('System', `file system error: ${err.message}`, { filename }, err);
53
+ }
54
+ };
55
+
56
+ module.exports = {
57
+ saveProperties,
58
+ };
@@ -0,0 +1,40 @@
1
+ const { getRichError } = require('@mimik/response-helper');
2
+
3
+ const { OAUTH2, API_KEY_IN, API_KEY_NAME } = require('./common');
4
+
5
+ const validateOauth2 = (securitySchemes, securityType, flow) => {
6
+ if (securitySchemes) {
7
+ const security = securitySchemes[securityType];
8
+
9
+ if (security) {
10
+ if (security.type !== OAUTH2) {
11
+ throw getRichError('System', `auth type is not ${OAUTH2}`, { securityType, receivedAuth: security.type, expectedAuth: OAUTH2 });
12
+ }
13
+ if (!security.flows[flow]) {
14
+ throw getRichError('System', 'no flow type available', { securityType, flow });
15
+ }
16
+ }
17
+ }
18
+ };
19
+
20
+ const validateApiKey = (securitySchemes, securityType) => {
21
+ if (securitySchemes) {
22
+ const security = securitySchemes[securityType];
23
+
24
+ if (security) {
25
+ if (security.in !== API_KEY_IN) {
26
+ throw getRichError('System', `apikey security must be in ${API_KEY_IN}`, { securityType, receivedIn: security.in, expectedIn: API_KEY_IN });
27
+ }
28
+ if (security.name !== API_KEY_NAME) {
29
+ throw getRichError('System', `apikey security must be named ${API_KEY_NAME}`, { securityType, receivedName: security.name, expectedName: API_KEY_NAME });
30
+ }
31
+ return securityType;
32
+ }
33
+ }
34
+ return null;
35
+ };
36
+
37
+ module.exports = {
38
+ validateOauth2,
39
+ validateApiKey,
40
+ };
@@ -0,0 +1,345 @@
1
+ const jwt = require('jsonwebtoken');
2
+ const _ = require('lodash');
3
+
4
+ const { TOKEN_PARAMS } = require('@mimik/swagger-helper');
5
+ const {
6
+ AUTHORIZATION,
7
+ ADMIN,
8
+ SUB_ADMIN,
9
+ CLIENT,
10
+ NO_GENERIC,
11
+ ON_BEHALF,
12
+ CLAIMS_DEFINITION,
13
+ SCOPES_SEPARATOR,
14
+ SCOPE_CLAIMS_SEPARATOR,
15
+ RESOURCE_SEPARATOR,
16
+ CLAIMS_SEPARATOR,
17
+ BEARERS,
18
+ USER,
19
+ CLUSTER,
20
+ API_KEY_NAME,
21
+ ADMIN_SECURITY,
22
+ SYSTEM_SECURITY,
23
+ USER_SECURITY,
24
+ } = require('./common');
25
+
26
+ const getScopes = (c, securityType) => {
27
+ let scopes = [];
28
+
29
+ c.operation.security.forEach((security) => {
30
+ if (security[securityType]) scopes = scopes.concat(security[securityType]);
31
+ });
32
+ return scopes;
33
+ };
34
+
35
+ const getError = (message, statusCode) => {
36
+ const error = new Error(message);
37
+
38
+ error.statusCode = statusCode;
39
+ return error;
40
+ };
41
+
42
+ const checkToken = (authToken) => {
43
+ let token;
44
+
45
+ try { token = jwt.decode(authToken); }
46
+ catch (err) {
47
+ err.statusCode = 401;
48
+ throw err;
49
+ }
50
+ if (!token) {
51
+ throw getError('invalid token', 401);
52
+ }
53
+ return token;
54
+ };
55
+
56
+ const checkHeaders = (headers) => {
57
+ if (!headers) {
58
+ throw getError('missing header', 401);
59
+ }
60
+ const authNames = Object.keys(headers).filter((key) => key.toLowerCase() === AUTHORIZATION);
61
+ const authNamesLength = authNames.length;
62
+
63
+ if (authNamesLength === 0) {
64
+ throw getError(`missing ${AUTHORIZATION} header`, 401);
65
+ }
66
+ if (authNamesLength !== 1) {
67
+ throw getError(`duplicated ${AUTHORIZATION} header`, 401);
68
+ }
69
+ const auth = headers[authNames[0]].split(' ');
70
+
71
+ if (!BEARERS.includes(auth[0]) || auth.length !== 2) {
72
+ throw getError(`authorization type incorrect: ${auth[0]}`, 401);
73
+ }
74
+ return auth[1];
75
+ };
76
+
77
+ const checkScopes = (tokenScopes, defScopes, definition) => {
78
+ if (!tokenScopes) {
79
+ throw new Error('no scope in authorization token');
80
+ }
81
+ let claims = [];
82
+ let onBehalf = false;
83
+
84
+ if (defScopes && defScopes.length !== 0) {
85
+ const currentScopes = tokenScopes.split(SCOPES_SEPARATOR);
86
+ const intersects = [];
87
+ let resourceIndex = 1;
88
+
89
+ currentScopes.forEach((currentScope) => {
90
+ const analyzedScope = currentScope.split(SCOPE_CLAIMS_SEPARATOR);
91
+ const analyzedResource = analyzedScope[0].split(RESOURCE_SEPARATOR);
92
+
93
+ if (analyzedResource[0] === ON_BEHALF) {
94
+ onBehalf = true;
95
+ resourceIndex = 2; // legacy handling
96
+ }
97
+
98
+ if (defScopes.includes(analyzedScope[0])) {
99
+ if (analyzedScope[1]) {
100
+ const includedDefinitionName = `${analyzedResource[resourceIndex]}${CLAIMS_DEFINITION}`;
101
+
102
+ if (!definition.components || !definition.components.schemas) {
103
+ throw getError(`missing ${includedDefinitionName} definition: no definitions`, 500);
104
+ }
105
+ const includedDefinition = definition.components.schemas[includedDefinitionName];
106
+
107
+ if (!includedDefinition) {
108
+ throw getError(`missing ${includedDefinitionName} definition`, 500);
109
+ }
110
+ const includedClaims = analyzedScope[1].split(CLAIMS_SEPARATOR);
111
+ const definitionClaims = Object.keys(includedDefinition);
112
+ const claimsIntersects = _.intersection(includedClaims, definitionClaims);
113
+
114
+ if (claimsIntersects.length !== includedClaims.length) {
115
+ throw getError(`incorrect claims included: ${_.difference(includedClaims, claimsIntersects)}`, 403);
116
+ }
117
+ claims = claims.concat(claimsIntersects);
118
+ }
119
+ intersects.push(analyzedScope[0]);
120
+ }
121
+ });
122
+ if (intersects.length === 0) {
123
+ throw getError(`incorrect scopes: ${tokenScopes}`, 403);
124
+ }
125
+ }
126
+ return { onBehalf, claims };
127
+ };
128
+
129
+ module.exports = (config) => {
130
+ const verifyTokenClientCredentials = (authToken) => {
131
+ const { server, generic } = config.security;
132
+ const options = {
133
+ audience: (generic.audience === NO_GENERIC) ? server.audience : generic.audience,
134
+ issuer: server.issuer,
135
+ // subject: `${config.serverSettings.id}@clients`,
136
+ };
137
+
138
+ try { jwt.verify(authToken, (generic.key === NO_GENERIC) ? server.accessKey : generic.key, options); }
139
+ catch (err) {
140
+ if (generic.previousKey) { // backward compatibility
141
+ try { jwt.verify(authToken, generic.previousKey, options); }
142
+ catch (secondErr) {
143
+ secondErr.statusCode = 403;
144
+ throw secondErr;
145
+ }
146
+ }
147
+ else {
148
+ err.statusCode = 403;
149
+ throw err;
150
+ }
151
+ }
152
+ };
153
+
154
+ const verifyTokenImplicit = (authToken) => {
155
+ const { implicit, generic, server } = config.security;
156
+ const options = {
157
+ audience: (implicit && implicit.audience) || server.audience,
158
+ issuer: (implicit && implicit.issuer) || server.issuer,
159
+ };
160
+
161
+ try { jwt.verify(authToken, (implicit && implicit.key) || ((generic.key === NO_GENERIC) ? server.accessKey : generic.key), options); }
162
+ catch (err) {
163
+ err.statusCode = 403;
164
+ throw err;
165
+ }
166
+ };
167
+
168
+ const AdminSecurity = {
169
+ regular: (c, req) => {
170
+ const authToken = checkHeaders(req.headers);
171
+ const token = checkToken(authToken);
172
+ const { request } = c;
173
+
174
+ if (token.subType !== ADMIN && token.subType !== SUB_ADMIN) {
175
+ throw getError('invalid token: wrong type', 403);
176
+ }
177
+ if (token.subType === SUB_ADMIN) {
178
+ if (!token.cust) {
179
+ throw getError('invalid token: no customer', 403);
180
+ }
181
+ }
182
+ else if (token.sub !== `${config.security.admin.externalId}${CLIENT}`) {
183
+ throw getError(`jwt subject invalid: ${token.sub}`, 403);
184
+ }
185
+ verifyTokenClientCredentials(authToken);
186
+ const scopeResult = checkScopes(token.scope, getScopes(c, ADMIN_SECURITY), c.api.definition);
187
+
188
+ req[TOKEN_PARAMS.claims] = scopeResult.claims;
189
+ request[TOKEN_PARAMS.claims] = scopeResult.claims;
190
+ if (token.subType) {
191
+ req[TOKEN_PARAMS.tokenType] = token.subType;
192
+ request[TOKEN_PARAMS.tokenType] = token.subType;
193
+ }
194
+ if (token.sub) {
195
+ req[TOKEN_PARAMS.clientId] = token.sub;
196
+ request[TOKEN_PARAMS.clientId] = token.sub;
197
+ }
198
+ if (token.cust) {
199
+ req[TOKEN_PARAMS.customer] = token.cust;
200
+ request[TOKEN_PARAMS.customer] = token.cust;
201
+ }
202
+ return true;
203
+ },
204
+ mock: (c, req) => {
205
+ const { request } = c;
206
+
207
+ req[TOKEN_PARAMS.claims] = ['dummyClaims'];
208
+ req[TOKEN_PARAMS.tokenType] = ADMIN;
209
+ req[TOKEN_PARAMS.clientId] = 'dummyClientId';
210
+ req[TOKEN_PARAMS.customer] = 'dummyCustomer';
211
+ request[TOKEN_PARAMS.claims] = ['dummyClaims'];
212
+ request[TOKEN_PARAMS.tokenType] = ADMIN;
213
+ request[TOKEN_PARAMS.clientId] = 'dummyClientId';
214
+ request[TOKEN_PARAMS.customer] = 'dummyCustomer';
215
+ return true;
216
+ },
217
+ };
218
+
219
+ const SystemSecurity = {
220
+ regular: (c, req) => {
221
+ const authToken = checkHeaders(req.headers);
222
+ const token = checkToken(authToken);
223
+ const { request } = c;
224
+
225
+ if (token.subType === ADMIN || token.subType === SUB_ADMIN) {
226
+ throw getError('invalid token: wrong type', 403);
227
+ }
228
+ verifyTokenClientCredentials(authToken);
229
+ const scopeResult = checkScopes(token.scope, getScopes(c, SYSTEM_SECURITY), c.api.definition);
230
+
231
+ if (scopeResult.onBehalf) {
232
+ req[TOKEN_PARAMS.onBehalf] = true;
233
+ request[TOKEN_PARAMS.onBehalf] = true;
234
+ }
235
+ req[TOKEN_PARAMS.claims] = scopeResult.claims;
236
+ request[TOKEN_PARAMS.claims] = scopeResult.claims;
237
+ if (token.subType) {
238
+ req[TOKEN_PARAMS.tokenType] = token.subType;
239
+ request[TOKEN_PARAMS.tokenType] = token.subType;
240
+ }
241
+ if (token.sub) {
242
+ req[TOKEN_PARAMS.clientId] = token.sub;
243
+ request[TOKEN_PARAMS.clientId] = token.sub;
244
+ }
245
+ if (token.cust) {
246
+ req[TOKEN_PARAMS.customer] = token.cust;
247
+ request[TOKEN_PARAMS.customer] = token.cust;
248
+ }
249
+ if (token.type === CLUSTER) {
250
+ req[TOKEN_PARAMS.cluster] = true;
251
+ request[TOKEN_PARAMS.cluster] = true;
252
+ }
253
+ return true;
254
+ },
255
+ mock: (c, req) => {
256
+ const { request } = c;
257
+
258
+ req[TOKEN_PARAMS.claims] = ['dummyClaims'];
259
+ req[TOKEN_PARAMS.tokenType] = 'dummyServiceType';
260
+ req[TOKEN_PARAMS.clientId] = 'dummyClientId';
261
+ req[TOKEN_PARAMS.customer] = 'dummyCustomer';
262
+ request[TOKEN_PARAMS.claims] = ['dummyClaims'];
263
+ request[TOKEN_PARAMS.tokenType] = 'dummyServiceType';
264
+ request[TOKEN_PARAMS.clientId] = 'dummyClientId';
265
+ request[TOKEN_PARAMS.customer] = 'dummyCustomer';
266
+ return true;
267
+ },
268
+ };
269
+
270
+ const UserSecurity = {
271
+ regular: (c, req) => {
272
+ const authToken = checkHeaders(req.headers);
273
+ const token = checkToken(authToken);
274
+ const { request } = c;
275
+
276
+ verifyTokenImplicit(authToken);
277
+ const scopeResult = checkScopes(token.scope, getScopes(c, USER_SECURITY), c.api.definition);
278
+
279
+ if (scopeResult.onBehalf) {
280
+ req[TOKEN_PARAMS.onBehalf] = true;
281
+ request[TOKEN_PARAMS.onBehalf] = true;
282
+ }
283
+ req[TOKEN_PARAMS.claims] = scopeResult.claims;
284
+ request[TOKEN_PARAMS.claims] = scopeResult.claims;
285
+ req[TOKEN_PARAMS.tokenType] = USER;
286
+ request[TOKEN_PARAMS.tokenType] = USER;
287
+ if (token.sub) {
288
+ req[TOKEN_PARAMS.userId] = token.sub;
289
+ request[TOKEN_PARAMS.userId] = token.sub;
290
+ }
291
+ if (token.azp) {
292
+ req[TOKEN_PARAMS.appId] = token.azp;
293
+ request[TOKEN_PARAMS.appId] = token.azp;
294
+ }
295
+ if (token.may_act && token.may_act.sub) {
296
+ req[TOKEN_PARAMS.onBehalfId] = token.sub;
297
+ request[TOKEN_PARAMS.onBehalfId] = token.sub;
298
+ req[TOKEN_PARAMS.userId] = token.may_act.sub;
299
+ request[TOKEN_PARAMS.userId] = token.may_act.sub;
300
+ }
301
+ return true;
302
+ },
303
+ mock: (c, req) => {
304
+ const { request } = c;
305
+
306
+ req[TOKEN_PARAMS.claims] = ['dummyClaims'];
307
+ req[TOKEN_PARAMS.userId] = 'dummyUserId';
308
+ req[TOKEN_PARAMS.appId] = 'dummyAppId';
309
+ req[TOKEN_PARAMS.tokenType] = USER;
310
+ request[TOKEN_PARAMS.claims] = ['dummyClaims'];
311
+ request[TOKEN_PARAMS.userId] = 'dummyUserId';
312
+ request[TOKEN_PARAMS.appId] = 'dummyAppId';
313
+ request[TOKEN_PARAMS.tokenType] = USER;
314
+ return true;
315
+ },
316
+ };
317
+
318
+ const ApiKeySecurity = {
319
+ regular: (c, req) => {
320
+ const apiKey = req.headers ? req.headers[API_KEY_NAME.toLowerCase()] : null;
321
+ const { request } = c;
322
+
323
+ if (config.security.apiKeys.includes(apiKey)) {
324
+ req[API_KEY_NAME] = apiKey;
325
+ request[API_KEY_NAME] = apiKey;
326
+ return true;
327
+ }
328
+ throw getError('invalid API key', 401);
329
+ },
330
+ mock: (c, req) => {
331
+ const { request } = c;
332
+
333
+ req[API_KEY_NAME] = 'dummyApiKey';
334
+ request[API_KEY_NAME] = 'dummyApiKey';
335
+ return true;
336
+ },
337
+ };
338
+
339
+ return {
340
+ AdminSecurity,
341
+ SystemSecurity,
342
+ UserSecurity,
343
+ ApiKeySecurity,
344
+ };
345
+ };
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@mimik/api-helper",
3
+ "version": "0.0.1",
4
+ "description": "helper for openAPI backend and mimik service",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "lint": "eslint --ignore-path .gitignore .",
8
+ "docs": "jsdoc2md index.js > README.md",
9
+ "test": "echo \"Error: no test specified\" && exit 0",
10
+ "test-ci": "echo \"Error: no test specified\" && exit 0",
11
+ "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"
14
+ },
15
+ "husky": {
16
+ "hooks": {
17
+ "pre-commit": "npm run commit-ready",
18
+ "pre-push": "npm run test"
19
+ }
20
+ },
21
+ "keywords": [
22
+ "mimik",
23
+ "microservice",
24
+ "openAPI"
25
+ ],
26
+ "author": "mimik technology inc <support@mimik.com> (https://developer.mimik.com/)",
27
+ "license": "MIT",
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "https://bitbucket.org/mimiktech/api-helper"
31
+ },
32
+ "dependencies": {
33
+ "@mimik/request-helper":"^1.7.8",
34
+ "@mimik/request-retry": "^2.1.4",
35
+ "@mimik/response-helper": "^2.6.3",
36
+ "@mimik/sumologic-winston-logger": "^1.6.15",
37
+ "@mimik/swagger-helper": "^3.0.5",
38
+ "ajv-formats": "^3.0.0-rc.0",
39
+ "js-yaml":"^4.1.0",
40
+ "jsonwebtoken": "^9.0.0",
41
+ "lodash": "^4.17.21",
42
+ "openapi-backend": "^5.9.1"
43
+ },
44
+ "devDependencies": {
45
+ "@mimik/eslint-plugin-dependencies": "^2.4.5",
46
+ "@mimik/eslint-plugin-document-env": "^1.0.5",
47
+ "eslint": "8.38.0",
48
+ "eslint-config-airbnb": "19.0.4",
49
+ "eslint-plugin-import": "2.27.5",
50
+ "eslint-plugin-jsx-a11y": "6.7.1",
51
+ "eslint-plugin-react": "7.32.2",
52
+ "eslint-plugin-react-hooks": "4.6.0",
53
+ "husky": "8.0.3",
54
+ "jsdoc-to-markdown": "8.0.0"
55
+ }
56
+ }