From 7cde7b4fba1be45f29e92110361d7b4710a3e2b8 Mon Sep 17 00:00:00 2001 From: Alexis Janvier Date: Fri, 8 Nov 2019 18:51:19 +0100 Subject: [PATCH 1/4] Set requiresAuthentication params on api WIP --- src/config.js | 52 ++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 45 insertions(+), 7 deletions(-) diff --git a/src/config.js b/src/config.js index 52ec81f..547e42b 100644 --- a/src/config.js +++ b/src/config.js @@ -1,4 +1,3 @@ -import signale from 'signale'; import convict from 'convict'; import rc from 'rc'; @@ -9,6 +8,39 @@ const isPlayerMode = processEnv['WEB_MYNA_MODE'] === MODE_PLAYER || !processEnv[ const globalConfiguration = rc('webmyna', { apis: [], recordingsPath: null }); +/** + * return the token name as environment variable from api name + * + * @example api name: rick-and-morty => token name RICK_AND_MORTY_TOKEN + * @function + * @param {object} api the api object configuration + * @returns {string} The token name + */ +export const getApiTokenName = api => { + return api.name ? `${api.name.toUpperCase().replace(/-/g, '_')}_TOKEN` : 'fakeWebMynaTokenName'; +}; + +/** + * Returns an array of missing token names from the environment variables + * + * @function + * @param {object[]} apis an array of configured apis + * @param {object} environment the environment from process.env + * @returns {string[]} an array of missing token name from environment variables + */ +export const getMissingEnvironmentTokens = (apis = globalConfiguration.apis, environment = processEnv) => { + let missingTokens = []; + apis.filter(api => api.requiresAuthentication) + .map(api => getApiTokenName(api)) + .map(tokenName => { + if (!environment[tokenName]) { + missingTokens.push(tokenName); + } + }); + + return missingTokens; +}; + convict.addFormat({ name: 'apis', validate: (apis, schema) => { @@ -16,12 +48,13 @@ convict.addFormat({ throw new Error('must be of type Array'); } + // if webmyna mode is recorder and api requires an authentication and token is present in environment variables + // we add token from environment variable in config apis.map((api, index) => { - const tokenName = api.name ? `${api.name.toUpperCase().replace(/-/g, '_')}_TOKEN` : null; - signale.debug(tokenName); - if (tokenName || isPlayerMode) { - const apiToken = processEnv[tokenName]; - apis[index].token = isPlayerMode ? 'webMynaPlayerToken' : apiToken; + const tokenName = getApiTokenName(api); + const apiToken = processEnv[tokenName] || null; + if (apiToken && api.requiresAuthentication && !isPlayerMode) { + apis[index].token = apiToken; } }); @@ -49,10 +82,15 @@ const config = convict({ format: 'url', default: null, }, + requiresAuthentication: { + doc: 'Does the API require authentication or not', + format: Boolean, + default: false, + }, token: { doc: 'Value of the authorization token to add in http headers', format: String, - default: null, + default: 'webMynaDefaultToken', }, tokenKey: { doc: 'Name of the http header for authentification', From d3113b3f9ddf78a1a9a488aa4ccb4d5fd90f91b4 Mon Sep 17 00:00:00 2001 From: Alexis Janvier Date: Sat, 9 Nov 2019 00:11:52 +0100 Subject: [PATCH 2/4] Check missing tokens --- docs/references.md | 35 +++++++++++++++++++++++++++++++++++ src/app.js | 7 +++++-- src/config.js | 4 +++- src/config.spec.js | 32 ++++++++++++++++++++++++++++++++ src/index.js | 16 +++++++++++----- website/i18n/en.json | 2 +- 6 files changed, 87 insertions(+), 9 deletions(-) create mode 100644 src/config.spec.js diff --git a/docs/references.md b/docs/references.md index bfc11e0..a1c2fbd 100644 --- a/docs/references.md +++ b/docs/references.md @@ -1,6 +1,12 @@ ## Functions
+
getApiTokenName(api)string
+

return the token name as environment variable from api name

+
+
getMissingEnvironmentTokens(apis, environment)Array.<string>
+

Returns an array of missing token names from the environment variables

+
transformBinaryToUtf8(value)string

Transform a Binay (Buffer) into string

@@ -15,6 +21,35 @@
+ + +## getApiTokenName(api) ⇒ string +return the token name as environment variable from api name + +**Kind**: global function +**Returns**: string - The token name + +| Param | Type | Description | +| --- | --- | --- | +| api | object | the api object configuration | + +**Example** +```js +api name: rick-and-morty => token name RICK_AND_MORTY_TOKEN +``` + + +## getMissingEnvironmentTokens(apis, environment) ⇒ Array.<string> +Returns an array of missing token names from the environment variables + +**Kind**: global function +**Returns**: Array.<string> - an array of missing token name from environment variables + +| Param | Type | Description | +| --- | --- | --- | +| apis | Array.<object> | an array of configured apis | +| environment | object | the environment from process.env | + ## transformBinaryToUtf8(value) ⇒ string diff --git a/src/app.js b/src/app.js index 789ad2e..690f69d 100644 --- a/src/app.js +++ b/src/app.js @@ -16,8 +16,11 @@ for (const api of config.apis) { target: api.url, ignorePath: false, pathRewrite: path => path.replace(`/${api.name}`, ''), - onProxyReq: proxyReq => - proxyReq.setHeader(`${api.tokenKey || 'authorization'}`, `${api.tokenPrefix || 'Bearer'} ${api.token}`), + onProxyReq: proxyReq => { + if (api.requiresAuthentication) { + proxyReq.setHeader(`${api.tokenKey || 'authorization'}`, `${api.tokenPrefix || 'Bearer'} ${api.token}`); + } + }, }; app.use(`/${api.name}`, myna(api), proxy(proxyOptions)); } diff --git a/src/config.js b/src/config.js index 547e42b..baa1095 100644 --- a/src/config.js +++ b/src/config.js @@ -17,7 +17,9 @@ const globalConfiguration = rc('webmyna', { apis: [], recordingsPath: null }); * @returns {string} The token name */ export const getApiTokenName = api => { - return api.name ? `${api.name.toUpperCase().replace(/-/g, '_')}_TOKEN` : 'fakeWebMynaTokenName'; + return typeof api.name === 'string' && !!api.name + ? `${api.name.toUpperCase().replace(/-/g, '_')}_TOKEN` + : 'fakeWebMynaTokenName'; }; /** diff --git a/src/config.spec.js b/src/config.spec.js new file mode 100644 index 0000000..14a49ae --- /dev/null +++ b/src/config.spec.js @@ -0,0 +1,32 @@ +import { getApiTokenName, getMissingEnvironmentTokens } from './config.js'; + +describe('config', () => { + describe('getApiTokenName', () => { + it('should return "fakeWebMynaTokenName" if api.name is not a string', () => { + const testGetApiTokenName = api => expect(getApiTokenName(api)).toEqual('fakeWebMynaTokenName'); + testGetApiTokenName({ name: null }); + testGetApiTokenName({ name: undefined }); + testGetApiTokenName({ name: 345 }); + testGetApiTokenName({ name: { foo: 'bar' } }); + testGetApiTokenName({ name: '' }); + testGetApiTokenName({}); + }); + + it('should return a well formated token name', () => { + expect(getApiTokenName({ name: 'rick-and-morty' })).toEqual('RICK_AND_MORTY_TOKEN'); + }); + }); + + describe('getMissingEnvironmentTokens', () => { + it('should return an array of missing token in precess.env', () => { + const apis = [ + { name: 'api-1', requiresAuthentication: false }, + { name: 'api-2', requiresAuthentication: true }, + { name: 'api-3', requiresAuthentication: true }, + ]; + const processEnv = {}; + processEnv['API_2_TOKEN'] = 'foo'; + expect(getMissingEnvironmentTokens(apis, processEnv)).toEqual(['API_3_TOKEN']); + }); + }); +}); diff --git a/src/index.js b/src/index.js index 0657295..d6cb80c 100644 --- a/src/index.js +++ b/src/index.js @@ -1,8 +1,14 @@ import signale from 'signale'; -import { app } from './app.js'; -import config from './config.js'; +import * as appJs from './app.js'; +import config, { getMissingEnvironmentTokens } from './config.js'; -app.listen(config.proxyPort, () => { - signale.info(`Web Myna is starded on port ${config.proxyPort} in environment ${config.env}`); -}); +const missingTokens = getMissingEnvironmentTokens(); + +if (missingTokens.length) { + signale.error('IL MANQUE DES TOKENS', missingTokens); +} else { + appJs.app.listen(config.proxyPort, () => { + signale.info(`Web Myna is starded on port ${config.proxyPort} in environment ${config.env}`); + }); +} diff --git a/website/i18n/en.json b/website/i18n/en.json index 3a34e61..fea7e68 100644 --- a/website/i18n/en.json +++ b/website/i18n/en.json @@ -6,7 +6,7 @@ "tagline": "Record then emulate your api.s for testing and development", "docs": { "references": { - "title": "References" + "title": "references" } }, "links": { From b17ecb505a4f4057de111dee731ed45357a947d5 Mon Sep 17 00:00:00 2001 From: Alexis Janvier Date: Sun, 10 Nov 2019 12:39:16 +0100 Subject: [PATCH 3/4] Improve missing tokens display in express env --- src/app.js | 38 +++++++++++++++++++++++--------------- src/index.js | 20 ++++++++++++++++++-- 2 files changed, 41 insertions(+), 17 deletions(-) diff --git a/src/app.js b/src/app.js index 690f69d..9d26493 100644 --- a/src/app.js +++ b/src/app.js @@ -2,25 +2,33 @@ import express from 'express'; import proxy from 'http-proxy-middleware'; import config from './config.js'; +import { MODE_RECORDER } from './modes.js'; import { myna } from './myna.js'; export const app = express(); -app.get('/', (req, res) => res.json({ message: 'Hello Web Myna' })); +app.get('/', (__, res) => res.json({ message: 'Hello Web Myna' })); for (const api of config.apis) { - const proxyOptions = { - changeOrigin: true, - proxyTimeout: 5000, - timeout: 5000, - target: api.url, - ignorePath: false, - pathRewrite: path => path.replace(`/${api.name}`, ''), - onProxyReq: proxyReq => { - if (api.requiresAuthentication) { - proxyReq.setHeader(`${api.tokenKey || 'authorization'}`, `${api.tokenPrefix || 'Bearer'} ${api.token}`); - } - }, - }; - app.use(`/${api.name}`, myna(api), proxy(proxyOptions)); + if (config.mode === MODE_RECORDER) { + const proxyOptions = { + changeOrigin: true, + proxyTimeout: 5000, + timeout: 5000, + target: api.url, + ignorePath: false, + pathRewrite: path => path.replace(`/${api.name}`, ''), + onProxyReq: proxyReq => { + if (api.requiresAuthentication) { + proxyReq.setHeader( + `${api.tokenKey || 'authorization'}`, + `${api.tokenPrefix || 'Bearer'} ${api.token}`, + ); + } + }, + }; + app.use(`/${api.name}`, myna(api), proxy(proxyOptions)); + } else { + app.use(`/${api.name}`, myna(api)); + } } diff --git a/src/index.js b/src/index.js index d6cb80c..516d337 100644 --- a/src/index.js +++ b/src/index.js @@ -1,12 +1,28 @@ import signale from 'signale'; +import boxen from 'boxen'; import * as appJs from './app.js'; -import config, { getMissingEnvironmentTokens } from './config.js'; +import config, { getApiTokenName, getMissingEnvironmentTokens } from './config.js'; const missingTokens = getMissingEnvironmentTokens(); if (missingTokens.length) { - signale.error('IL MANQUE DES TOKENS', missingTokens); + const missingApisWithAuthentication = config.apis + .filter(api => missingTokens.includes(getApiTokenName(api))) + .map(api => api.name); + const isPlural = missingApisWithAuthentication.length > 1; + const message = ` +You have declared ${missingApisWithAuthentication.length} api${ + isPlural ? 's' : '' + } as requiring an authentication token: ${missingApisWithAuthentication.join(', ')}. + +You must therefore declare the following token${isPlural ? 's' : ''} as an environment variable: ${missingTokens + .map( + token => ` + => ${token}`, + ) + .join('')}`; + signale.log(boxen(message, { padding: 1, margin: 1, borderColor: 'red', align: 'center' })); } else { appJs.app.listen(config.proxyPort, () => { signale.info(`Web Myna is starded on port ${config.proxyPort} in environment ${config.env}`); From c50f424a39394c9ca69dda0cd6e6f3377b100c1b Mon Sep 17 00:00:00 2001 From: Alexis Janvier Date: Mon, 11 Nov 2019 22:07:57 +0100 Subject: [PATCH 4/4] Manage requiresAuthentication on cli conf --- cli/index.js | 26 +++++++++++++++----------- cli/questions.js | 6 +++--- package.json | 1 + src/config.js | 26 ++++++++++++++++++++++++++ src/index.js | 18 ++---------------- yarn.lock | 2 +- 6 files changed, 48 insertions(+), 31 deletions(-) diff --git a/cli/index.js b/cli/index.js index dd96588..2220bfe 100644 --- a/cli/index.js +++ b/cli/index.js @@ -6,9 +6,10 @@ import rc from 'rc'; import signale from 'signale'; import boxen from 'boxen'; import fs from 'fs'; +import omit from 'lodash.omit'; import { questions } from './questions.js'; -import config from '../src/config.js'; +import config, { getMissingEnvironmentTokens, getMissingTokensMessage } from '../src/config.js'; import { app } from '../src/app.js'; const clearWn = () => { @@ -47,12 +48,7 @@ if (!globalConfiguration.config) { signale.log(`You already have ${apis.length} api.s configured`); } const apiConfiguration = await questions.askApiConfiguration(); - apis.push({ - name: apiConfiguration.name, - url: apiConfiguration.url, - tokenKey: apiConfiguration.tokenKey || 'authorization', - tokenPrefix: apiConfiguration.tokenPrefix || 'Bearer', - }); + apis.push(omit(apiConfiguration, ['continue'])); configureApi = apiConfiguration.continue; } @@ -80,13 +76,21 @@ if (!globalConfiguration.config) { run(); } else { - app.listen(config.proxyPort, () => { - signale.info(`Web Myna is starded on port ${config.proxyPort} in environment ${config.env}`); - }); + const missingTokens = getMissingEnvironmentTokens(); + + if (missingTokens.length) { + const message = getMissingTokensMessage(config, missingTokens); + signale.log(boxen(message, { padding: 1, margin: 1, borderColor: 'red', align: 'center' })); + process.exit(); + } else { + app.listen(config.proxyPort, () => { + signale.info(`Web Myna is starded on port ${config.proxyPort} in environment ${config.env}`); + }); + } } process.on('SIGINT', function() { clear(); signale.log(chalk.yellow(figlet.textSync('Bye', { horizontalLayout: 'full' }))); - process.exit(1); + process.exit(); }); diff --git a/cli/questions.js b/cli/questions.js index 820852a..d35586d 100644 --- a/cli/questions.js +++ b/cli/questions.js @@ -65,7 +65,7 @@ export const questions = { }, { type: 'confirm', - name: 'needToken', + name: 'requiresAuthentication', message: 'Does the API need an authentication token?', }, { @@ -73,14 +73,14 @@ export const questions = { name: 'tokenKey', message: 'What is the name of the http header with the authentication token?', default: 'authorization', - when: value => value.needToken, + when: value => value.requiresAuthentication, }, { type: 'input', name: 'tokenPrefix', message: 'Should the token be prefixed in the header?', default: 'Bearer', - when: value => value.needToken, + when: value => value.requiresAuthentication, }, { type: 'confirm', diff --git a/package.json b/package.json index 71b27f9..8d795cb 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "figlet": "1.2.4", "http-proxy-middleware": "0.20.0", "inquirer": "7.0.0", + "lodash.omit": "4.5.0", "md5": "2.2.1", "minimist": "1.2.0", "nodemon": "1.19.4", diff --git a/src/config.js b/src/config.js index baa1095..1e90532 100644 --- a/src/config.js +++ b/src/config.js @@ -43,6 +43,32 @@ export const getMissingEnvironmentTokens = (apis = globalConfiguration.apis, env return missingTokens; }; +/** + * Return readable message about missing tokens in process.env + * + * @function + * @param {object} config with apis + * @param {string[]} missingTokens an array of missing token names + * @returns {string} the message + */ +export const getMissingTokensMessage = (config, missingTokens) => { + const missingApisWithAuthentication = config.apis + .filter(api => missingTokens.includes(getApiTokenName(api))) + .map(api => api.name); + const isPlural = missingApisWithAuthentication.length > 1; + return ` +You have declared ${missingApisWithAuthentication.length} api${ + isPlural ? 's' : '' + } as requiring an authentication token: ${missingApisWithAuthentication.join(', ')}. + +You must therefore declare the following token${isPlural ? 's' : ''} as an environment variable: ${missingTokens + .map( + token => ` + => ${token}`, + ) + .join('')}`; +}; + convict.addFormat({ name: 'apis', validate: (apis, schema) => { diff --git a/src/index.js b/src/index.js index 516d337..1523423 100644 --- a/src/index.js +++ b/src/index.js @@ -2,26 +2,12 @@ import signale from 'signale'; import boxen from 'boxen'; import * as appJs from './app.js'; -import config, { getApiTokenName, getMissingEnvironmentTokens } from './config.js'; +import config, { getMissingEnvironmentTokens, getMissingTokensMessage } from './config.js'; const missingTokens = getMissingEnvironmentTokens(); if (missingTokens.length) { - const missingApisWithAuthentication = config.apis - .filter(api => missingTokens.includes(getApiTokenName(api))) - .map(api => api.name); - const isPlural = missingApisWithAuthentication.length > 1; - const message = ` -You have declared ${missingApisWithAuthentication.length} api${ - isPlural ? 's' : '' - } as requiring an authentication token: ${missingApisWithAuthentication.join(', ')}. - -You must therefore declare the following token${isPlural ? 's' : ''} as an environment variable: ${missingTokens - .map( - token => ` - => ${token}`, - ) - .join('')}`; + const message = getMissingTokensMessage(config, missingTokens); signale.log(boxen(message, { padding: 1, margin: 1, borderColor: 'red', align: 'center' })); } else { appJs.app.listen(config.proxyPort, () => { diff --git a/yarn.lock b/yarn.lock index 491d674..c4269f7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5751,7 +5751,7 @@ lodash.memoize@^4.1.2: resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= -lodash.omit@^4.5.0: +lodash.omit@4.5.0, lodash.omit@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.omit/-/lodash.omit-4.5.0.tgz#6eb19ae5a1ee1dd9df0b969e66ce0b7fa30b5e60" integrity sha1-brGa5aHuHdnfC5aeZs4Lf6MLXmA=