From f9dd7cc8d4f7fde89a871134ae14948ec1be47e4 Mon Sep 17 00:00:00 2001 From: vipulrawat Date: Mon, 15 Oct 2018 23:24:22 +0530 Subject: [PATCH] Refactoring and handling env vars --- packages/api/__tests__/index.js | 5 ++- packages/api/config/ci.js | 2 +- packages/api/config/dev.js | 2 +- packages/api/config/index.js | 2 +- packages/api/config/prod.js | 2 +- packages/api/constants/ERR_MSGS.js | 8 ++++ packages/api/constants/TOKEN_TYPES.js | 4 ++ packages/api/src/routes/index.js | 23 ++++++------ packages/api/src/services/mail.js | 6 ++- packages/api/src/services/token.js | 8 ++-- packages/api/winston.config.js | 7 ++-- yarn.lock | 54 ++++++++++++++++++++++++++- 12 files changed, 97 insertions(+), 26 deletions(-) create mode 100644 packages/api/constants/ERR_MSGS.js create mode 100644 packages/api/constants/TOKEN_TYPES.js diff --git a/packages/api/__tests__/index.js b/packages/api/__tests__/index.js index 4dfd6fd..0001563 100644 --- a/packages/api/__tests__/index.js +++ b/packages/api/__tests__/index.js @@ -2,7 +2,7 @@ import mongoose from 'mongoose'; import request from 'request-promise-native'; import app from '../src'; -import { PORT, DB_URL } from '../config'; +import { PORT } from '../config'; import tokenService from '../src/services/token'; import { User } from '../src/db'; @@ -35,7 +35,8 @@ let server; const serverUrl = `http://localhost:${PORT}`; beforeAll((done) => { - mongoose.connect(DB_URL, { useNewUrlParser: true }, () => { + jest.setTimeout(10000); + mongoose.connect(process.env.DB_URL, { useNewUrlParser: true }, () => { server = app.listen(PORT, () => { // save current contents of users collection, and insert some dummy users User.find({}) diff --git a/packages/api/config/ci.js b/packages/api/config/ci.js index ec276ac..9e43996 100644 --- a/packages/api/config/ci.js +++ b/packages/api/config/ci.js @@ -1,7 +1,7 @@ module.exports = { PORT: 5000, - DB_URL: 'mongodb://localhost:27017/test-delta', EMAIL_VERIFICATION_TOKEN_EXPIRY: 15 * 60, // 15 minutes LOGIN_TOKEN_EXPIRY: '28d', LOGIN_COOKIE_EXPIRY: 4 * 7 * 24 * 60 * 60 * 1000, // 4 weeks + ...process.env, }; diff --git a/packages/api/config/dev.js b/packages/api/config/dev.js index ec276ac..9e43996 100644 --- a/packages/api/config/dev.js +++ b/packages/api/config/dev.js @@ -1,7 +1,7 @@ module.exports = { PORT: 5000, - DB_URL: 'mongodb://localhost:27017/test-delta', EMAIL_VERIFICATION_TOKEN_EXPIRY: 15 * 60, // 15 minutes LOGIN_TOKEN_EXPIRY: '28d', LOGIN_COOKIE_EXPIRY: 4 * 7 * 24 * 60 * 60 * 1000, // 4 weeks + ...process.env, }; diff --git a/packages/api/config/index.js b/packages/api/config/index.js index bf5d869..5529376 100644 --- a/packages/api/config/index.js +++ b/packages/api/config/index.js @@ -1,5 +1,5 @@ // The env var 'MODE' should be in ['PROD', 'DEV', 'CI'] const mode = process.env.MODE || 'DEV'; - +require('dotenv').load(); // eslint-disable-next-line import/no-dynamic-require module.exports = require(`./${mode.toLowerCase()}`); diff --git a/packages/api/config/prod.js b/packages/api/config/prod.js index ec276ac..9e43996 100644 --- a/packages/api/config/prod.js +++ b/packages/api/config/prod.js @@ -1,7 +1,7 @@ module.exports = { PORT: 5000, - DB_URL: 'mongodb://localhost:27017/test-delta', EMAIL_VERIFICATION_TOKEN_EXPIRY: 15 * 60, // 15 minutes LOGIN_TOKEN_EXPIRY: '28d', LOGIN_COOKIE_EXPIRY: 4 * 7 * 24 * 60 * 60 * 1000, // 4 weeks + ...process.env, }; diff --git a/packages/api/constants/ERR_MSGS.js b/packages/api/constants/ERR_MSGS.js new file mode 100644 index 0000000..cddf1da --- /dev/null +++ b/packages/api/constants/ERR_MSGS.js @@ -0,0 +1,8 @@ +module.exports = { + invalidToken: 'TOKEN_INVALID', + missingToken: 'TOKEN_MISSING', + expiredToken: 'TOKEN_EXPIRED', + missingEmail: 'EMAIL_MISSING', + invalidEmail: 'EMAIL_INVALID', + internalServerError: 'Server error', +}; diff --git a/packages/api/constants/TOKEN_TYPES.js b/packages/api/constants/TOKEN_TYPES.js new file mode 100644 index 0000000..cffd31a --- /dev/null +++ b/packages/api/constants/TOKEN_TYPES.js @@ -0,0 +1,4 @@ +module.exports = { + emailToken: 'EMAIL_VERIFICATION', + loginToken: 'LOGIN', +}; diff --git a/packages/api/src/routes/index.js b/packages/api/src/routes/index.js index 6db44a0..8141b3b 100644 --- a/packages/api/src/routes/index.js +++ b/packages/api/src/routes/index.js @@ -7,7 +7,8 @@ const config = require('../../config'); const tokenService = require('../services/token'); const mailService = require('../services/mail'); const models = require('../db'); - +const ERR_MSGS = require('../../constants/ERR_MSGS'); +const TOKEN_TYPES = require('../../constants/TOKEN_TYPES'); // Checks that the user who sent the request has a valid token // If authenticated, the decoded token is made available to next middleware at req.decoded // else, a 400 response is returned, and the next middleware is not called @@ -15,7 +16,7 @@ function isAuthenticated(req, res, next) { const token = req.headers.Authorization || req.query.token || req.cookies.token || req.body.token; if (!token) { - res.status(400).json({ error: 'TOKEN_MISSING' }); + res.status(400).json({ error: ERR_MSGS.invalidToken }); } else { tokenService.decode(token) .then((decoded) => { @@ -23,21 +24,21 @@ function isAuthenticated(req, res, next) { next(); }) .catch(e => res.status(400).json({ - error: e.name === 'TokenExpiredError' ? 'TOKEN_EXPIRED' : 'TOKEN_INVALID', + error: e.name === 'TokenExpiredError' ? ERR_MSGS.expiredToken : ERR_MSGS.invalidToken, })); } } routes.post('/generateToken', (req, res) => { if (!Reflect.has(req.body, 'email')) { - res.status(400).json({ error: 'EMAIL_MISSING' }); + res.status(400).json({ error: ERR_MSGS.missingEmail }); } else if (typeof req.body.email !== 'string' || !isEmail(req.body.email)) { - res.status(400).json({ error: 'EMAIL_INVALID' }); + res.status(400).json({ error: ERR_MSGS.invalidEmail }); } else { const { email } = req.body; tokenService.generate({ email, - tokenType: 'EMAIL_VERIFICATION', + tokenType: TOKEN_TYPES.emailToken, }, config.EMAIL_VERIFICATION_TOKEN_EXPIRY) .then(token => mailService.sendMail(email, token)) .then(() => res.json({ tokenStatus: 'success', email })) @@ -50,9 +51,9 @@ routes.post('/generateToken', (req, res) => { routes.post('/verifyToken', isAuthenticated, (req, res) => { const { email, tokenType } = req.decoded; - if (tokenType === 'EMAIL_VERIFICATION') { + if (tokenType === TOKEN_TYPES.emailToken) { // give the user a longer-lived token that can be used for future auto-login - tokenService.generate({ email, tokenType: 'LOGIN' }, config.LOGIN_TOKEN_EXPIRY) + tokenService.generate({ email, tokenType: TOKEN_TYPES.loginToken }, config.LOGIN_TOKEN_EXPIRY) .then((generatedToken) => { res.cookie('token', generatedToken, { // not adding a maxAge property to the cookie causes it to be @@ -69,9 +70,9 @@ routes.post('/verifyToken', isAuthenticated, (req, res) => { }) .catch(() => { // TODO: error logging - res.status(500).json({ error: 'Server error' }); + res.status(500).json({ error: ERR_MSGS.internalServerError }); }); - } else if (tokenType === 'LOGIN') { + } else if (tokenType === TOKEN_TYPES.loginToken) { // user already has a login token, so just acknowledge the sign-in res.json({ authentication: 'success', @@ -80,7 +81,7 @@ routes.post('/verifyToken', isAuthenticated, (req, res) => { } else { // We sent a token containing invalid data to the user winston.error(`!IMP: From: ${req.ip} - ${req.method} - ${req.originalUrl} - User Email: ${email}`); - res.status(400).json({ error: 'TOKEN_INVALID' }); + res.status(400).json({ error: ERR_MSGS.invalidToken }); } }); diff --git a/packages/api/src/services/mail.js b/packages/api/src/services/mail.js index 68d9a3c..7d77384 100644 --- a/packages/api/src/services/mail.js +++ b/packages/api/src/services/mail.js @@ -1,6 +1,8 @@ -const sgMail = require('@sendgrid/mail'); +import sgMail from '@sendgrid/mail'; -sgMail.setApiKey('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'); +const { SENDGRID_API_KEY } = require('../../config'); + +sgMail.setApiKey(SENDGRID_API_KEY); const sendMail = (email, token) => { const msg = { diff --git a/packages/api/src/services/token.js b/packages/api/src/services/token.js index 453c8d7..d3a94f5 100644 --- a/packages/api/src/services/token.js +++ b/packages/api/src/services/token.js @@ -1,8 +1,10 @@ -const jwt = require('jsonwebtoken'); +import jwt from 'jsonwebtoken'; + +const { JWT_SECRET } = require('../../config'); function generate(payload, expiresIn = '1d') { return new Promise((resolve, reject) => { - jwt.sign(payload, process.env.JWT_SECRET, { expiresIn }, (err, token) => { + jwt.sign(payload, JWT_SECRET, { expiresIn }, (err, token) => { if (err) reject(err); else resolve(token); }); @@ -11,7 +13,7 @@ function generate(payload, expiresIn = '1d') { function decode(token) { return new Promise((resolve, reject) => { - jwt.verify(token, process.env.JWT_SECRET, (err, decoded) => { + jwt.verify(token, JWT_SECRET, (err, decoded) => { if (err) reject(err); else resolve(decoded); }); diff --git a/packages/api/winston.config.js b/packages/api/winston.config.js index 7c88e9c..a50c5d8 100644 --- a/packages/api/winston.config.js +++ b/packages/api/winston.config.js @@ -1,4 +1,5 @@ -const winston = require('winston'); +import winston from 'winston'; + const path = require('path'); const options = { @@ -7,7 +8,7 @@ const options = { filename: path.join(__dirname, '/logs/app.log'), handleExceptions: true, json: true, - maxsize: 5242880, // 5MB + maxsize: 5 * 1024 * 1024, // 5MB maxFiles: 5, colorize: false, }, @@ -27,7 +28,7 @@ const logger = winston.createLogger({ }); logger.stream = { - write(message, encoding) { // eslint-disable-line + write(message) { logger.info(message); }, }; diff --git a/yarn.lock b/yarn.lock index 89eba68..feada05 100644 --- a/yarn.lock +++ b/yarn.lock @@ -857,6 +857,43 @@ dependencies: any-observable "^0.3.0" +"@sendgrid/client@^6.3.0": + version "6.3.0" + resolved "https://registry.yarnpkg.com/@sendgrid/client/-/client-6.3.0.tgz#25c34b11bec392ab43ca7e52fb35e4105fb00901" + integrity sha512-fTy8vRpA9Whtf8ULQr/0vkSZaQvGQ97rY5N5PrevKRtugJMsJqFMKO0pwzEWeqITSg71aMMTj57QTgw3SjZvnQ== + dependencies: + "@sendgrid/helpers" "^6.3.0" + "@types/request" "^2.0.3" + request "^2.81.0" + +"@sendgrid/helpers@^6.3.0": + version "6.3.0" + resolved "https://registry.yarnpkg.com/@sendgrid/helpers/-/helpers-6.3.0.tgz#1b1798af22aa7a4c98257fab3dd2a6a6afd8b467" + integrity sha512-uTFcmhCDFg/2Uhz+z/cLwyLHH0UsblG49hKwdR7nKbWsGKWv4js7W32FlPdXqy2C/plTJ20vcPLgKM1m3F/MjQ== + dependencies: + chalk "^2.0.1" + deepmerge "^2.1.1" + +"@sendgrid/mail@^6.3.1": + version "6.3.1" + resolved "https://registry.yarnpkg.com/@sendgrid/mail/-/mail-6.3.1.tgz#e5003af167ca4dd358f04075aad4cfc30cef6c34" + integrity sha512-5zIeAV9iU+0hQkrOQ/D4RB2MfpK+lNbOortIfQdCh95aMDF/TRc9WB8FGNhmQrx9YMuJTms5eiBklF0Fi/dbVg== + dependencies: + "@sendgrid/client" "^6.3.0" + "@sendgrid/helpers" "^6.3.0" + +"@types/caseless@*": + version "0.12.1" + resolved "https://registry.yarnpkg.com/@types/caseless/-/caseless-0.12.1.tgz#9794c69c8385d0192acc471a540d1f8e0d16218a" + integrity sha512-FhlMa34NHp9K5MY1Uz8yb+ZvuX0pnvn3jScRSNAb75KHGB8d3rEU6hqMs3Z2vjuytcMfRg6c5CHMc3wtYyD2/A== + +"@types/form-data@*": + version "2.2.1" + resolved "https://registry.yarnpkg.com/@types/form-data/-/form-data-2.2.1.tgz#ee2b3b8eaa11c0938289953606b745b738c54b1e" + integrity sha512-JAMFhOaHIciYVh8fb5/83nmuO/AHwmto+Hq7a9y8FzLDcC1KCU344XDOMEmahnrTFlHjgh4L0WJFczNIX2GxnQ== + dependencies: + "@types/node" "*" + "@types/jss@^9.5.6": version "9.5.6" resolved "https://registry.yarnpkg.com/@types/jss/-/jss-9.5.6.tgz#96e1d246ddfbccc4867494077c714773cf29acde" @@ -890,6 +927,21 @@ "@types/prop-types" "*" csstype "^2.2.0" +"@types/request@^2.0.3": + version "2.47.1" + resolved "https://registry.yarnpkg.com/@types/request/-/request-2.47.1.tgz#25410d3afbdac04c91a94ad9efc9824100735824" + integrity sha512-TV3XLvDjQbIeVxJ1Z3oCTDk/KuYwwcNKVwz2YaT0F5u86Prgc4syDAp6P96rkTQQ4bIdh+VswQIC9zS6NjY7/g== + dependencies: + "@types/caseless" "*" + "@types/form-data" "*" + "@types/node" "*" + "@types/tough-cookie" "*" + +"@types/tough-cookie@*": + version "2.3.3" + resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-2.3.3.tgz#7f226d67d654ec9070e755f46daebf014628e9d9" + integrity sha512-MDQLxNFRLasqS4UlkWMSACMKeSm1x4Q3TxzUC7KQUsh6RK1ZrQ0VEyE3yzXcBu+K8ejVj4wuX32eUG02yNp+YQ== + "@webassemblyjs/ast@1.7.8": version "1.7.8" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.7.8.tgz#f31f480debeef957f01b623f27eabc695fa4fe8f" @@ -2732,7 +2784,7 @@ deep-is@~0.1.3: resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= -deepmerge@^2.0.1: +deepmerge@^2.0.1, deepmerge@^2.1.1: version "2.2.1" resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-2.2.1.tgz#5d3ff22a01c00f645405a2fbc17d0778a1801170" integrity sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==