From 618123100eb2387d4da3d83c39eb22137715ce21 Mon Sep 17 00:00:00 2001 From: timakasucces Date: Mon, 11 Feb 2019 18:20:20 +0300 Subject: [PATCH] added middlewares, user model and controller --- dbinsert.js | 30 ++++++++++++++++--- package.json | 2 +- src/controllers/pages.js | 2 +- src/controllers/users.js | 33 +++++++++++++++++++++ src/models/page.js | 2 +- src/models/user.js | 41 ++++++++++++++++++++++++++ src/routes/auth.js | 45 +++++++++++++++++----------- src/routes/home.js | 3 +- src/routes/middlewares/locals.js | 7 +++++ src/routes/middlewares/token.js | 13 ++------- src/routes/pages.js | 47 +++++++++++++----------------- yarn.lock | 50 +++++++++++++++++++------------- 12 files changed, 192 insertions(+), 83 deletions(-) create mode 100644 src/controllers/users.js create mode 100644 src/models/user.js create mode 100644 src/routes/middlewares/locals.js diff --git a/dbinsert.js b/dbinsert.js index be4f3d9..ae75712 100644 --- a/dbinsert.js +++ b/dbinsert.js @@ -1,16 +1,38 @@ let { password: db } = require('./src/utils/database'); -const md5 = require('md5'); const program = require('commander'); +const bcrypt = require('bcrypt'); +const saltRounds = 10; + program .description('Application for inserting new passwords to database.') .usage('insert [password]') .command('insert ') .action(async function (password) { - let doc = { password: md5(password) }; - let newDoc = await db.insert(doc); + let userDoc = null; + let saltDoc = null; - console.log('Password was inserted as', newDoc); + bcrypt.genSalt(saltRounds, function (err1, salt) { + if (err1) { + return ('Salt generation error'); + } + + saltDoc = { type: 'salt', saltValue: salt }; + + bcrypt.hash(password, salt, async (err2, hash) => { + if (err2) { + return ('Hash generation error'); + } + + userDoc = { passHash: hash }; + + await db.insert(saltDoc); + await db.insert(userDoc); + + console.log('Generated salt:', saltDoc.saltValue); + console.log('Password hash was inserted as:', userDoc.passHash); + }); + }); }); program.on('--help', () => { diff --git a/package.json b/package.json index 9776e7b..bff0c70 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@babel/polyfill": "^7.0.0", + "bcrypt": "^3.0.3", "body-parser": "latest", "codex.editor": "^2.1.3", "codex.editor.header": "^2.0.5", @@ -23,7 +24,6 @@ "express": "~4.16.0", "http-errors": "~1.7.1", "jsonwebtoken": "^8.4.0", - "md5": "^2.2.1", "module-dispatcher": "^1.0.2", "morgan": "~1.9.0", "multer": "^1.3.1", diff --git a/src/controllers/pages.js b/src/controllers/pages.js index 696f682..35d9b12 100644 --- a/src/controllers/pages.js +++ b/src/controllers/pages.js @@ -48,7 +48,7 @@ class Pages { * @param {string} parent - id of current page * @returns {Promise} */ - static async getAllExceptChildrens(parent) { + static async getAllExceptChildren(parent) { let pagesAvailable = this.removeChildren(await Pages.getAll(), parent); return pagesAvailable.filter((item) => item !== null); diff --git a/src/controllers/users.js b/src/controllers/users.js new file mode 100644 index 0000000..bc99b7a --- /dev/null +++ b/src/controllers/users.js @@ -0,0 +1,33 @@ +const Model = require('../models/user'); + +/** + * @class Users + * @classdesc Users controller + */ +class Users { + /** + * @static + * Find and return user model with given password hash + * + * @param {string} passHash - hashed password + * @returns {Promise} + */ + static async get(passHash) { + const userDoc = await Model.get(passHash); + + return userDoc; + } + + /** + * Find and return salt + * + * @returns {Promise} + */ + static async getSalt() { + const salt = await Model.getSalt(); + + return salt; + } +} + +module.exports = Users; diff --git a/src/models/page.js b/src/models/page.js index a78cf87..02e2bc8 100644 --- a/src/models/page.js +++ b/src/models/page.js @@ -75,7 +75,7 @@ class Page { /** * Return PageData object * - * @returns {PageData} + * @ {PageData} */ get data() { return { diff --git a/src/models/user.js b/src/models/user.js new file mode 100644 index 0000000..3c59b08 --- /dev/null +++ b/src/models/user.js @@ -0,0 +1,41 @@ +const {password: db} = require('../utils/database/index'); + +/** + * @class User + * @class User model + * + * @property {string} passHash - hashed password + */ +class User { + /** + * Find and return model of user with given password hash + * + * @param {string} passHash - hashed password + * @returns {Promise} + */ + static async get(passHash) { + const data = await db.findOne({ passHash: passHash }); + + return new User(data); + } + + /** + * + */ + static async getSalt() { + const saltDoc = await db.findOne({type: 'salt'}); + + return saltDoc.saltValue; + } + + /** + * @constructor + * + * @param {Object} userData + */ + constructor(userData) { + this.passHash = userData.passHash; + } +} + +module.exports = User; diff --git a/src/routes/auth.js b/src/routes/auth.js index e17073f..70d3253 100644 --- a/src/routes/auth.js +++ b/src/routes/auth.js @@ -1,36 +1,47 @@ const express = require('express'); const router = express.Router(); -const csrf = require('csurf'); const bodyParser = require('body-parser'); -const { password: db } = require('../utils/database/index'); const jwt = require('jsonwebtoken'); -const config = require('../../config/index'); -const md5 = require('md5'); +const Users = require('../controllers/users'); +const config = require('../../config/index'); + +const bcrypt = require('bcrypt'); +// const saltRounds = 10; + +const csrf = require('csurf'); const csrfProtection = csrf({ cookie: true }); const parseForm = bodyParser.urlencoded({ extended: false }); /* GET authorization page. */ -router.get('/auth', csrfProtection, function (req, res, next) { +router.get('/auth', csrfProtection, function (req, res) { res.render('auth', { title: 'Login page ', header: 'Enter password', csrfToken: req.csrfToken() }); }); router.post('/auth', parseForm, csrfProtection, async (req, res) => { - const passwordDoc = await db.findOne({ 'password': md5(req.body.password) }); + let salt = await Users.getSalt(); - if (passwordDoc !== null) { - const token = jwt.sign({ - 'iss': 'Codex Team', - 'sub': 'auth', - 'iat': Date.now() - }, passwordDoc.password + config.secret); + bcrypt.hash(req.body.password, salt, async function (err, hash) { + if (err) { + res.status(500); + } - res.cookie('authToken', token); + const userDoc = await Users.get(hash); - res.redirect('/'); - } else { - res.render('auth', { title: 'Login page', header: 'Wrong password' }); - } + if (userDoc) { + const token = jwt.sign({ + 'iss': 'Codex Team', + 'sub': 'auth', + 'iat': Date.now() + }, userDoc.passHash + config.secret); + + res.cookie('authToken', token); + + res.redirect('/'); + } else { + res.render('auth', { title: 'Login page', header: 'Wrong password', csrfToken: req.csrfToken() }); + } + }); }); module.exports = router; diff --git a/src/routes/home.js b/src/routes/home.js index a80210a..6bd68ea 100644 --- a/src/routes/home.js +++ b/src/routes/home.js @@ -4,8 +4,7 @@ const router = express.Router(); /* GET home page. */ router.get('/', verifyToken, async (req, res) => { - - res.render('index', { title: 'Express', isAuthorized: req.isAuthorized }); + res.render('index', { title: 'Express', isAuthorized: res.locals.isAuthorized }); }); module.exports = router; diff --git a/src/routes/middlewares/locals.js b/src/routes/middlewares/locals.js new file mode 100644 index 0000000..55e71f4 --- /dev/null +++ b/src/routes/middlewares/locals.js @@ -0,0 +1,7 @@ +module.exports = function allowEdit(req, res, next) { + if (res.locals.isAuthorized) { + next(); + } else { + res.redirect('/auth'); + } +}; diff --git a/src/routes/middlewares/token.js b/src/routes/middlewares/token.js index 1afbdef..c5c0671 100644 --- a/src/routes/middlewares/token.js +++ b/src/routes/middlewares/token.js @@ -4,16 +4,9 @@ const jwt = require('jsonwebtoken'); module.exports = async function verifyToken(req, res, next) { let token = req.cookies.authToken; - let isAuthorized = false; - jwt.verify(token, process.env.PASSWORD + config.secret, (err, decodedToken) => { - if (err || !decodedToken) { - return (err); - } else { - isAuthorized = true; - } + jwt.verify(token, process.env.PASSHASH + config.secret, (err, decodedToken) => { + res.locals.isAuthorized = !(err || !decodedToken); + next(); }); - - req.isAuthorized = isAuthorized; - next(); }; diff --git a/src/routes/pages.js b/src/routes/pages.js index d32a79e..8ee10f3 100644 --- a/src/routes/pages.js +++ b/src/routes/pages.js @@ -2,44 +2,37 @@ const express = require('express'); const router = express.Router(); const Pages = require('../controllers/pages'); const verifyToken = require('./middlewares/token'); +const allowEdit = require('./middlewares/locals'); /** * Create new page form */ -router.get('/page/new', verifyToken, async (req, res, next) => { - if (!req.isAuthorized) { - res.render('auth', { title: 'Login page', header: 'Enter password to do this!' }); - } else { - let pagesAvailable = await Pages.getAll(); +router.get('/page/new', verifyToken, allowEdit, async (req, res, next) => { + let pagesAvailable = await Pages.getAll(); - res.render('pages/form', { - pagesAvailable, - page: null - }); - } + res.render('pages/form', { + pagesAvailable, + page: null + }); }); /** * Edit page form */ -router.get('/page/edit/:id', verifyToken, async (req, res, next) => { - if (!req.isAuthorized) { - res.render('auth', { title: 'Login page', header: 'Enter password to do this!' }); - } else { - const pageId = req.params.id; +router.get('/page/edit/:id', verifyToken, allowEdit, async (req, res, next) => { + const pageId = req.params.id; - try { - let page = await Pages.get(pageId); - let pagesAvailable = await Pages.getAllExceptChildrens(pageId); + try { + let page = await Pages.get(pageId); + let pagesAvailable = await Pages.getAllExceptChildren(pageId); - res.render('pages/form', { - pagesAvailable, - page - }); - } catch (error) { - res.status(404); - next(error); - } + res.render('pages/form', { + pagesAvailable, + page + }); + } catch (error) { + res.status(404); + next(error); } }); @@ -55,7 +48,7 @@ router.get('/page/:id', verifyToken, async (req, res, next) => { let pageParent = await page.parent; res.render('pages/page', { - page, pageParent, isAuthorized: req.isAuthorized + page, pageParent, isAuthorized: res.locals.isAuthorized }); } catch (error) { res.status(404); diff --git a/yarn.lock b/yarn.lock index eebdee8..8e5df5a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1140,6 +1140,14 @@ basic-auth@~2.0.0: dependencies: safe-buffer "5.1.2" +bcrypt@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/bcrypt/-/bcrypt-3.0.3.tgz#7d1e9e5d533c5ea060e6ac8834942c004dbffe9c" + integrity sha512-4EuzUo6K790QC3uq/ogzy9w2Hc7XDIBoEndU5y7l7YaEAwQF8vyFqv6tC30+gOBZvyxk3F632xzKBQoLNz2pjg== + dependencies: + nan "2.12.1" + node-pre-gyp "0.12.0" + big.js@^3.1.3: version "3.2.0" resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.2.0.tgz#a5fc298b81b9e0dca2e458824784b65c52ba588e" @@ -1506,11 +1514,6 @@ chardet@^0.7.0: resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== -charenc@~0.0.1: - version "0.0.2" - resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" - integrity sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc= - check-error@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" @@ -1932,11 +1935,6 @@ cross-spawn@^6.0.0, cross-spawn@^6.0.5: shebang-command "^1.2.0" which "^1.2.9" -crypt@~0.0.1: - version "0.0.2" - resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" - integrity sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs= - crypto-browserify@^3.11.0: version "3.12.0" resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" @@ -3583,7 +3581,7 @@ is-binary-path@^1.0.0: dependencies: binary-extensions "^1.0.0" -is-buffer@^1.1.5, is-buffer@~1.1.1: +is-buffer@^1.1.5: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== @@ -4241,15 +4239,6 @@ md5.js@^1.3.4: inherits "^2.0.1" safe-buffer "^5.1.2" -md5@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/md5/-/md5-2.2.1.tgz#53ab38d5fe3c8891ba465329ea23fac0540126f9" - integrity sha1-U6s41f48iJG6RlMp6iP6wFQBJvk= - dependencies: - charenc "~0.0.1" - crypt "~0.0.1" - is-buffer "~1.1.1" - mdn-data@~1.1.0: version "1.1.4" resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-1.1.4.tgz#50b5d4ffc4575276573c4eedb8780812a8419f01" @@ -4526,6 +4515,11 @@ mute-stream@0.0.7: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s= +nan@2.12.1: + version "2.12.1" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.12.1.tgz#7b1aa193e9aa86057e3c7bbd0ac448e770925552" + integrity sha512-JY7V6lRkStKcKTvHO5NVSQRv+RV+FIL5pvDoLiAtSL9pKlC5x9PKQcZDsq7m4FO4d57mkhC6Z+QhAh3Jdk5JFw== + nan@^2.9.2: version "2.11.1" resolved "https://registry.yarnpkg.com/nan/-/nan-2.11.1.tgz#90e22bccb8ca57ea4cd37cc83d3819b52eea6766" @@ -4628,6 +4622,22 @@ node-libs-browser@^2.0.0: util "^0.10.3" vm-browserify "0.0.4" +node-pre-gyp@0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.12.0.tgz#39ba4bb1439da030295f899e3b520b7785766149" + integrity sha512-4KghwV8vH5k+g2ylT+sLTjy5wmUOb9vPhnM8NHvRf9dHmnW/CndrFXy2aRPaPST6dugXSdHXfeaHQm77PIz/1A== + dependencies: + detect-libc "^1.0.2" + mkdirp "^0.5.1" + needle "^2.2.1" + nopt "^4.0.1" + npm-packlist "^1.1.6" + npmlog "^4.0.2" + rc "^1.2.7" + rimraf "^2.6.1" + semver "^5.3.0" + tar "^4" + node-pre-gyp@^0.10.0: version "0.10.3" resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.10.3.tgz#3070040716afdc778747b61b6887bf78880b80fc"