From da755835acd5e8b39f368cf2c7831b51bce4e23a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Borys=20Tymi=C5=84ski?= Date: Fri, 28 Jun 2019 16:20:43 +0200 Subject: [PATCH 01/32] rename files --- src/controllers/{auth.controller.js => user.controller.js} | 0 src/routes/api/{auth.route.js => user.route.js} | 2 +- src/routes/{api => }/index.js | 2 +- src/services/express.js | 2 +- 4 files changed, 3 insertions(+), 3 deletions(-) rename src/controllers/{auth.controller.js => user.controller.js} (100%) rename src/routes/api/{auth.route.js => user.route.js} (93%) rename src/routes/{api => }/index.js (83%) diff --git a/src/controllers/auth.controller.js b/src/controllers/user.controller.js similarity index 100% rename from src/controllers/auth.controller.js rename to src/controllers/user.controller.js diff --git a/src/routes/api/auth.route.js b/src/routes/api/user.route.js similarity index 93% rename from src/routes/api/auth.route.js rename to src/routes/api/user.route.js index e43a74e..e557c80 100644 --- a/src/routes/api/auth.route.js +++ b/src/routes/api/user.route.js @@ -2,7 +2,7 @@ const express = require('express') const router = express.Router() -const authController = require('../../controllers/auth.controller') +const authController = require('../../controllers/user.controller') const validator = require('express-validation') const { create } = require('../../validations/user.validation') const auth = require('../../middlewares/authorization') diff --git a/src/routes/api/index.js b/src/routes/index.js similarity index 83% rename from src/routes/api/index.js rename to src/routes/index.js index 83c6816..bd2bb4f 100644 --- a/src/routes/api/index.js +++ b/src/routes/index.js @@ -1,7 +1,7 @@ 'use strict' const express = require('express') const router = express.Router() -const authRouter = require('./auth.route') +const authRouter = require('./api/user.route') router.get('/status', (req, res) => { res.send({status: 'OK'}) }) // api status diff --git a/src/services/express.js b/src/services/express.js index b6020a4..c764cab 100644 --- a/src/services/express.js +++ b/src/services/express.js @@ -7,7 +7,7 @@ const cors = require('cors') const helmet = require('helmet') const bodyParser = require('body-parser') const errorHandler = require('../middlewares/error-handler') -const apiRouter = require('../routes/api') +const apiRouter = require('../routes/index') const passport = require('passport') const passportJwt = require('../services/passport') From 7fbddd7a1628d0e7b003c1110cdb9b61781d930a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Borys=20Tymi=C5=84ski?= Date: Fri, 28 Jun 2019 17:07:51 +0200 Subject: [PATCH 02/32] add change from father branch (check if account activated) --- src/models/user.model.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/models/user.model.js b/src/models/user.model.js index 969b711..c76f362 100644 --- a/src/models/user.model.js +++ b/src/models/user.model.js @@ -133,6 +133,8 @@ userSchema.statics = { if (!passwordOK) throw new APIError(`Password mismatch`, httpStatus.UNAUTHORIZED) + if (!user.active) throw new APIError(`User not activated`, httpStatus.UNAUTHORIZED) + return user } } From fc6d3f6a82a44ea2816879fe8f4a0a6814b9b8aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Borys=20Tymi=C5=84ski?= Date: Fri, 28 Jun 2019 17:32:44 +0200 Subject: [PATCH 03/32] split auth and user routers --- src/models/user.model.js | 2 +- src/routes/api/auth.route.js | 18 ++++++++++++++++++ src/routes/api/user.route.js | 22 ++++------------------ src/routes/index.js | 10 +++++++--- 4 files changed, 30 insertions(+), 22 deletions(-) create mode 100644 src/routes/api/auth.route.js diff --git a/src/models/user.model.js b/src/models/user.model.js index c76f362..6e54865 100644 --- a/src/models/user.model.js +++ b/src/models/user.model.js @@ -70,7 +70,7 @@ userSchema.post('save', async function saved (doc, next) { from: 'noreply', to: this.email, subject: 'Confirm creating account', - html: `

Hello new user!

Click link to activate your new account.

Hello developer!

Feel free to change this template ;).

` + html: `

Hello new user!

Click link to activate your new account.

Hello developer!

Feel free to change this template ;).

` } transporter.sendMail(mailOptions, function (error, info) { diff --git a/src/routes/api/auth.route.js b/src/routes/api/auth.route.js new file mode 100644 index 0000000..1d930f4 --- /dev/null +++ b/src/routes/api/auth.route.js @@ -0,0 +1,18 @@ +'use strict' + +const express = require('express') +const router = express.Router() +const auth = require('../../middlewares/authorization') + +// Authentication example routes +router.get('/secret1', auth(), (req, res) => { + res.json({ message: 'Anyone can access(only authorized)' }) +}) +router.get('/secret2', auth(['admin']), (req, res) => { + res.json({ message: 'Only admin can access' }) +}) +router.get('/secret3', auth(['user']), (req, res) => { + res.json({ message: 'Only user can access' }) +}) + +module.exports = router diff --git a/src/routes/api/user.route.js b/src/routes/api/user.route.js index e557c80..ec2b8d7 100644 --- a/src/routes/api/user.route.js +++ b/src/routes/api/user.route.js @@ -2,27 +2,13 @@ const express = require('express') const router = express.Router() -const authController = require('../../controllers/user.controller') +const userController = require('../../controllers/user.controller') const validator = require('express-validation') const { create } = require('../../validations/user.validation') -const auth = require('../../middlewares/authorization') -router.post('/register', validator(create), authController.register) // validate and register -router.post('/login', authController.login) // login -router.get('/confirm', authController.confirm) +router.post('/register', validator(create), userController.register) // validate and register +router.post('/login', userController.login) // login -// Authentication example -router.get('/secret1', auth(), (req, res) => { - // example route for auth - res.json({ message: 'Anyone can access(only authorized)' }) -}) -router.get('/secret2', auth(['admin']), (req, res) => { - // example route for auth - res.json({ message: 'Only admin can access' }) -}) -router.get('/secret3', auth(['user']), (req, res) => { - // example route for auth - res.json({ message: 'Only user can access' }) -}) +router.get('/confirm', userController.confirm) module.exports = router diff --git a/src/routes/index.js b/src/routes/index.js index bd2bb4f..f914016 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -1,10 +1,14 @@ 'use strict' + const express = require('express') const router = express.Router() -const authRouter = require('./api/user.route') +const userRouter = require('./api/user.route') +const authRouter = require('./api/auth.route') -router.get('/status', (req, res) => { res.send({status: 'OK'}) }) // api status +// api status +router.get('/status', (req, res) => { res.send({status: 'OK'}) }) -router.use('/auth', authRouter) // mount auth paths +router.use('/user', userRouter) +router.use('/auth', authRouter) module.exports = router From dd836a496816ae8f5b01b703232a4ce6a5ed6608 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Borys=20Tymi=C5=84ski?= Date: Sat, 29 Jun 2019 11:17:36 +0200 Subject: [PATCH 04/32] get rid of transform (from parent branch) --- src/models/user.model.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/models/user.model.js b/src/models/user.model.js index 6e54865..190662e 100644 --- a/src/models/user.model.js +++ b/src/models/user.model.js @@ -60,12 +60,6 @@ userSchema.pre('save', async function save (next) { userSchema.post('save', async function saved (doc, next) { try { - console.log('after save is called') - if (!this.isModified('activationKey')) { - console.log('Not modified.. but what does it mean?') - //return next() - } - const mailOptions = { from: 'noreply', to: this.email, @@ -90,7 +84,7 @@ userSchema.post('save', async function saved (doc, next) { userSchema.method({ transform () { const transformed = {} - const fields = ['id', 'name', 'email', 'createdAt', 'activationKey', 'role'] + const fields = ['id', 'name', 'email', 'createdAt', 'role'] fields.forEach((field) => { transformed[field] = this[field] From 4db00462983f61967d12173cda5d5d0b0182eda7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Borys=20Tymi=C5=84ski?= Date: Sat, 29 Jun 2019 13:47:25 +0200 Subject: [PATCH 05/32] split admin and user --- src/controllers/admin.controller.js | 28 +++++++++++ src/middlewares/authorization.js | 3 +- src/models/admin.model.js | 74 +++++++++++++++++++++++++++++ src/services/mongoose.js | 3 ++ src/services/passport.js | 12 ++++- src/utils/seed.js | 22 +++++++++ 6 files changed, 139 insertions(+), 3 deletions(-) create mode 100644 src/controllers/admin.controller.js create mode 100644 src/models/admin.model.js create mode 100644 src/utils/seed.js diff --git a/src/controllers/admin.controller.js b/src/controllers/admin.controller.js new file mode 100644 index 0000000..c9d2d7e --- /dev/null +++ b/src/controllers/admin.controller.js @@ -0,0 +1,28 @@ +'use strict' + +const Admin = require('../models/admin.model') +const jwt = require('jsonwebtoken') +const config = require('../config') +const httpStatus = require('http-status') + +exports.register = async (req, res, next) => { + try { + const admin = new Admin(req.body) + const savedAdmin = await admin.save() + res.status(httpStatus.CREATED) + res.send(savedAdmin.transform()) + } catch (error) { + return next(Admin.checkDuplicateEmailError(error)) + } +} + +exports.login = async (req, res, next) => { + try { + const admin = await Admin.findAndGenerateToken(req.body) + const payload = {sub: admin.id} + const token = jwt.sign(payload, config.secret) + return res.json({ message: 'OK', token: token }) + } catch (error) { + next(error) + } +} diff --git a/src/middlewares/authorization.js b/src/middlewares/authorization.js index ecad145..3fd4430 100644 --- a/src/middlewares/authorization.js +++ b/src/middlewares/authorization.js @@ -11,8 +11,7 @@ const handleJWT = (req, res, next, roles) => async (err, user, info) => { const error = err || info const logIn = bluebird.promisify(req.logIn) const apiError = new APIError( - error ? error.message : 'Unauthorized', - httpStatus.UNAUTHORIZED + error ? error.message : 'Unauthorized', httpStatus.UNAUTHORIZED ) // log user in diff --git a/src/models/admin.model.js b/src/models/admin.model.js new file mode 100644 index 0000000..a52d064 --- /dev/null +++ b/src/models/admin.model.js @@ -0,0 +1,74 @@ +'use strict' +const mongoose = require('mongoose') +const bcrypt = require('bcrypt-nodejs') +const httpStatus = require('http-status') +const APIError = require('../utils/APIError') +const Schema = mongoose.Schema + +const adminSchema = new Schema({ + email: { + type: String, + required: true, + unique: true, + lowercase: true + }, + password: { + type: String, + required: true, + minlength: 4, + maxlength: 128 + }, + name: { + type: String, + maxlength: 50 + } +}, { + timestamps: true +}) + +adminSchema.pre('save', async function save (next) { + try { + if (!this.isModified('password')) { + return next() + } + + this.password = bcrypt.hashSync(this.password) + + return next() + } catch (error) { + return next(error) + } +}) + +adminSchema.statics = { + checkDuplicateEmailError (err) { + if (err.code === 11000) { + var error = new Error('Email already taken') + error.errors = [{ + field: 'email', + location: 'body', + messages: ['Email already taken'] + }] + error.status = httpStatus.CONFLICT + return error + } + + return err + }, + + async findAndGenerateToken (payload) { + const { email, password } = payload + if (!email) throw new APIError('Email must be provided for login') + + const user = await this.findOne({ email }).exec() + if (!user) throw new APIError(`No user associated with ${email}`, httpStatus.NOT_FOUND) + + const passwordOK = await user.passwordMatches(password) + + if (!passwordOK) throw new APIError(`Password mismatch`, httpStatus.UNAUTHORIZED) + + return user + } +} + +module.exports = mongoose.model('Admin', adminSchema) diff --git a/src/services/mongoose.js b/src/services/mongoose.js index 83fbb95..801aece 100644 --- a/src/services/mongoose.js +++ b/src/services/mongoose.js @@ -2,6 +2,7 @@ const config = require('../config') const mongoose = require('mongoose') +const seed = require('../utils/seed') mongoose.Promise = require('bluebird') mongoose.connection.on('connected', () => { @@ -25,6 +26,8 @@ exports.connect = () => { useNewUrlParser: true }) + seed() + mongoose.set('useCreateIndex', true) return mongoose.connection diff --git a/src/services/passport.js b/src/services/passport.js index 01aa7af..b0fec0b 100644 --- a/src/services/passport.js +++ b/src/services/passport.js @@ -2,6 +2,7 @@ const config = require('../config') const User = require('../models/user.model') +const Admin = require('../models/admin.model') const passportJWT = require('passport-jwt') const ExtractJwt = passportJWT.ExtractJwt @@ -22,7 +23,16 @@ const jwtStrategy = new JwtStrategy(jwtOptions, (jwtPayload, done) => { if (user) { return done(null, user) } else { - return done(null, false) + Admin.findById(jwtPayload.sub, (err, admin) => { + if (err) { + return done(err, null) + } + if (admin) { + return done(null, admin) + } else { + return done(null, false) + } + }) } }) }) diff --git a/src/utils/seed.js b/src/utils/seed.js new file mode 100644 index 0000000..b03356c --- /dev/null +++ b/src/utils/seed.js @@ -0,0 +1,22 @@ +const Admin = require('../models/admin.model') + +async function seed () { + try { + const count = await Admin.countDocuments({}) + + if (count === 0) { + const user = { + name: 'Admin', + email: 'admin@gmail.com', + password: 'admin123' + } + + const admin = new Admin(user) + await admin.save() + } + } catch (err) { + console.error(err) + } +} + +module.exports = seed From 3fad9a8579f169e016f127a98bb691f39479ae2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Borys=20Tymi=C5=84ski?= Date: Sat, 29 Jun 2019 15:13:41 +0200 Subject: [PATCH 06/32] refactor admin authenticate --- src/middlewares/authorization.js | 9 ++++----- src/routes/api/admin.route.js | 0 2 files changed, 4 insertions(+), 5 deletions(-) create mode 100644 src/routes/api/admin.route.js diff --git a/src/middlewares/authorization.js b/src/middlewares/authorization.js index 3fd4430..ef8ff77 100644 --- a/src/middlewares/authorization.js +++ b/src/middlewares/authorization.js @@ -1,13 +1,12 @@ 'use strict' -const User = require('../models/user.model') const passport = require('passport') const APIError = require('../utils/APIError') const httpStatus = require('http-status') const bluebird = require('bluebird') // handleJWT with roles -const handleJWT = (req, res, next, roles) => async (err, user, info) => { +const handleJWT = (req, res, next, role) => async (err, user, info) => { const error = err || info const logIn = bluebird.promisify(req.logIn) const apiError = new APIError( @@ -23,7 +22,7 @@ const handleJWT = (req, res, next, roles) => async (err, user, info) => { } // see if user is authorized to do the action - if (!roles.includes(user.role)) { + if (role.includes('admin') && !user.admin) { return next(new APIError('Forbidden', httpStatus.FORBIDDEN)) } @@ -33,11 +32,11 @@ const handleJWT = (req, res, next, roles) => async (err, user, info) => { } // exports the middleware -const authorize = (roles = User.roles) => (req, res, next) => +const authorize = (role) => (req, res, next) => passport.authenticate( 'jwt', { session: false }, - handleJWT(req, res, next, roles) + handleJWT(req, res, next, role) )(req, res, next) module.exports = authorize diff --git a/src/routes/api/admin.route.js b/src/routes/api/admin.route.js new file mode 100644 index 0000000..e69de29 From 7ddc6d1196342f6959a97f631e2f386e0055fb1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Borys=20Tymi=C5=84ski?= Date: Sat, 29 Jun 2019 15:14:00 +0200 Subject: [PATCH 07/32] add admin model --- src/models/admin.model.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/models/admin.model.js b/src/models/admin.model.js index a52d064..315e03c 100644 --- a/src/models/admin.model.js +++ b/src/models/admin.model.js @@ -21,6 +21,10 @@ const adminSchema = new Schema({ name: { type: String, maxlength: 50 + }, + admin: { + type: Boolean, + default: true } }, { timestamps: true @@ -40,6 +44,23 @@ adminSchema.pre('save', async function save (next) { } }) +adminSchema.method({ + transform () { + const transformed = {} + const fields = ['id', 'name', 'email', 'createdAt'] + + fields.forEach((field) => { + transformed[field] = this[field] + }) + + return transformed + }, + + passwordMatches (password) { + return bcrypt.compareSync(password, this.password) + } +}) + adminSchema.statics = { checkDuplicateEmailError (err) { if (err.code === 11000) { From 9f69097514f0c752a5124e8fe89f428d170d240d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Borys=20Tymi=C5=84ski?= Date: Sat, 29 Jun 2019 15:14:27 +0200 Subject: [PATCH 08/32] add admin route --- src/routes/api/admin.route.js | 11 +++++++++++ src/routes/index.js | 2 ++ 2 files changed, 13 insertions(+) diff --git a/src/routes/api/admin.route.js b/src/routes/api/admin.route.js index e69de29..3d8d6d3 100644 --- a/src/routes/api/admin.route.js +++ b/src/routes/api/admin.route.js @@ -0,0 +1,11 @@ +'use strict' + +const express = require('express') +const router = express.Router() +const adminController = require('../../controllers/admin.controller') +const auth = require('../../middlewares/authorization') + +router.post('/login', adminController.login) +router.post('/register', auth(['admin']), adminController.register) + +module.exports = router diff --git a/src/routes/index.js b/src/routes/index.js index f914016..b3dd6cf 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -2,12 +2,14 @@ const express = require('express') const router = express.Router() +const adminRouter = require('./api/admin.route') const userRouter = require('./api/user.route') const authRouter = require('./api/auth.route') // api status router.get('/status', (req, res) => { res.send({status: 'OK'}) }) +router.use('/admin', adminRouter) router.use('/user', userRouter) router.use('/auth', authRouter) From e526e20cf040d551c9996322ebe3250cb3281f2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Borys=20Tymi=C5=84ski?= Date: Sat, 29 Jun 2019 16:01:37 +0200 Subject: [PATCH 09/32] add additional role check --- src/middlewares/authorization.js | 2 +- src/models/utils/hashPass.js | 0 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 src/models/utils/hashPass.js diff --git a/src/middlewares/authorization.js b/src/middlewares/authorization.js index ef8ff77..68cc8f5 100644 --- a/src/middlewares/authorization.js +++ b/src/middlewares/authorization.js @@ -22,7 +22,7 @@ const handleJWT = (req, res, next, role) => async (err, user, info) => { } // see if user is authorized to do the action - if (role.includes('admin') && !user.admin) { + if (role && role.includes('admin') && !user.admin) { return next(new APIError('Forbidden', httpStatus.FORBIDDEN)) } diff --git a/src/models/utils/hashPass.js b/src/models/utils/hashPass.js new file mode 100644 index 0000000..e69de29 From 3e2b6113a42b8c6e80178db9357d72f9d8c9e2a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Borys=20Tymi=C5=84ski?= Date: Sat, 29 Jun 2019 16:02:00 +0200 Subject: [PATCH 10/32] delete useless only user access --- src/routes/api/auth.route.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/routes/api/auth.route.js b/src/routes/api/auth.route.js index 1d930f4..70fcbf8 100644 --- a/src/routes/api/auth.route.js +++ b/src/routes/api/auth.route.js @@ -11,8 +11,5 @@ router.get('/secret1', auth(), (req, res) => { router.get('/secret2', auth(['admin']), (req, res) => { res.json({ message: 'Only admin can access' }) }) -router.get('/secret3', auth(['user']), (req, res) => { - res.json({ message: 'Only user can access' }) -}) module.exports = router From 8a651ac7cbcec508420c4c89ee5395b978ea9831 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Borys=20Tymi=C5=84ski?= Date: Sun, 30 Jun 2019 12:14:36 +0200 Subject: [PATCH 11/32] move hashPass and checkDup to external function, keep DRY --- src/models/admin.model.js | 15 ++------------- src/models/user.model.js | 15 ++------------- src/models/utils/checkDuplicateEmailError.js | 18 ++++++++++++++++++ src/models/utils/hashPass.js | 19 +++++++++++++++++++ 4 files changed, 41 insertions(+), 26 deletions(-) create mode 100644 src/models/utils/checkDuplicateEmailError.js diff --git a/src/models/admin.model.js b/src/models/admin.model.js index 315e03c..064593a 100644 --- a/src/models/admin.model.js +++ b/src/models/admin.model.js @@ -4,6 +4,7 @@ const bcrypt = require('bcrypt-nodejs') const httpStatus = require('http-status') const APIError = require('../utils/APIError') const Schema = mongoose.Schema +const hashPass = require('./utils/hashPass') const adminSchema = new Schema({ email: { @@ -30,19 +31,7 @@ const adminSchema = new Schema({ timestamps: true }) -adminSchema.pre('save', async function save (next) { - try { - if (!this.isModified('password')) { - return next() - } - - this.password = bcrypt.hashSync(this.password) - - return next() - } catch (error) { - return next(error) - } -}) +hashPass(adminSchema) adminSchema.method({ transform () { diff --git a/src/models/user.model.js b/src/models/user.model.js index 190662e..947e8c9 100644 --- a/src/models/user.model.js +++ b/src/models/user.model.js @@ -5,6 +5,7 @@ const httpStatus = require('http-status') const APIError = require('../utils/APIError') const transporter = require('../services/transporter') const Schema = mongoose.Schema +const hashPass = require('./utils/hashPass') const roles = [ 'user', 'admin' @@ -44,19 +45,7 @@ const userSchema = new Schema({ timestamps: true }) -userSchema.pre('save', async function save (next) { - try { - if (!this.isModified('password')) { - return next() - } - - this.password = bcrypt.hashSync(this.password) - - return next() - } catch (error) { - return next(error) - } -}) +hashPass(userSchema) userSchema.post('save', async function saved (doc, next) { try { diff --git a/src/models/utils/checkDuplicateEmailError.js b/src/models/utils/checkDuplicateEmailError.js new file mode 100644 index 0000000..18995fd --- /dev/null +++ b/src/models/utils/checkDuplicateEmailError.js @@ -0,0 +1,18 @@ +const httpStatus = require('http-status') + +function checkDuplicateEmailError (err) { + if (err.code === 11000) { + var error = new Error('Email already taken') + error.errors = [{ + field: 'email', + location: 'body', + messages: ['Email already taken'] + }] + error.status = httpStatus.CONFLICT + return error + } + + return err +} + +module.exports = checkDuplicateEmailError diff --git a/src/models/utils/hashPass.js b/src/models/utils/hashPass.js index e69de29..8afe020 100644 --- a/src/models/utils/hashPass.js +++ b/src/models/utils/hashPass.js @@ -0,0 +1,19 @@ +const bcrypt = require('bcrypt-nodejs') + +const hashPass = (schema) => { + schema.pre('save', async function save (next) { + try { + if (!this.isModified('password')) { + return next() + } + + this.password = bcrypt.hashSync(this.password) + + return next() + } catch (error) { + return next(error) + } + }) +} + +module.exports = hashPass From 24edc5f7e6e9f9efd63078c490ba60a99121e705 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Borys=20Tymi=C5=84ski?= Date: Sun, 30 Jun 2019 14:06:59 +0200 Subject: [PATCH 12/32] move checkDupEmail to external function, keep DRY --- src/models/admin.model.js | 16 ++-------------- src/models/user.model.js | 16 ++-------------- 2 files changed, 4 insertions(+), 28 deletions(-) diff --git a/src/models/admin.model.js b/src/models/admin.model.js index 064593a..032eb5a 100644 --- a/src/models/admin.model.js +++ b/src/models/admin.model.js @@ -5,6 +5,7 @@ const httpStatus = require('http-status') const APIError = require('../utils/APIError') const Schema = mongoose.Schema const hashPass = require('./utils/hashPass') +const checkDuplicateEmailError = require('./utils/checkDuplicateEmailError') const adminSchema = new Schema({ email: { @@ -51,20 +52,7 @@ adminSchema.method({ }) adminSchema.statics = { - checkDuplicateEmailError (err) { - if (err.code === 11000) { - var error = new Error('Email already taken') - error.errors = [{ - field: 'email', - location: 'body', - messages: ['Email already taken'] - }] - error.status = httpStatus.CONFLICT - return error - } - - return err - }, + checkDuplicateEmailError, async findAndGenerateToken (payload) { const { email, password } = payload diff --git a/src/models/user.model.js b/src/models/user.model.js index 947e8c9..725690a 100644 --- a/src/models/user.model.js +++ b/src/models/user.model.js @@ -6,6 +6,7 @@ const APIError = require('../utils/APIError') const transporter = require('../services/transporter') const Schema = mongoose.Schema const hashPass = require('./utils/hashPass') +const checkDuplicateEmailError = require('./utils/checkDuplicateEmailError') const roles = [ 'user', 'admin' @@ -90,20 +91,7 @@ userSchema.method({ userSchema.statics = { roles, - checkDuplicateEmailError (err) { - if (err.code === 11000) { - var error = new Error('Email already taken') - error.errors = [{ - field: 'email', - location: 'body', - messages: ['Email already taken'] - }] - error.status = httpStatus.CONFLICT - return error - } - - return err - }, + checkDuplicateEmailError, async findAndGenerateToken (payload) { const { email, password } = payload From 02f9238ee8ace1847e5f870057c0aedb625c2bc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Borys=20Tymi=C5=84ski?= Date: Sun, 30 Jun 2019 14:07:53 +0200 Subject: [PATCH 13/32] remove roles from user.model --- src/models/user.model.js | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/models/user.model.js b/src/models/user.model.js index 725690a..4964202 100644 --- a/src/models/user.model.js +++ b/src/models/user.model.js @@ -8,10 +8,6 @@ const Schema = mongoose.Schema const hashPass = require('./utils/hashPass') const checkDuplicateEmailError = require('./utils/checkDuplicateEmailError') -const roles = [ - 'user', 'admin' -] - const userSchema = new Schema({ email: { type: String, @@ -36,11 +32,6 @@ const userSchema = new Schema({ active: { type: Boolean, default: false - }, - role: { - type: String, - default: 'user', - enum: roles } }, { timestamps: true From eee1eb44928b8adfa9de266e9735dff0e214b33b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Borys=20Tymi=C5=84ski?= Date: Sun, 30 Jun 2019 14:08:28 +0200 Subject: [PATCH 14/32] remove roles from user.model fix --- src/models/user.model.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/models/user.model.js b/src/models/user.model.js index 4964202..2b431ab 100644 --- a/src/models/user.model.js +++ b/src/models/user.model.js @@ -80,7 +80,6 @@ userSchema.method({ }) userSchema.statics = { - roles, checkDuplicateEmailError, From 7d430de2dee9adb99b6fc796485e99a5cbb94b8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Borys=20Tymi=C5=84ski?= Date: Sun, 30 Jun 2019 23:27:29 +0200 Subject: [PATCH 15/32] move generateToken function to external file --- src/controllers/admin.controller.js | 5 ++-- src/controllers/user.controller.js | 5 ++-- src/models/utils/findAndGenerateToken.js | 32 ++++++++++++++++++++++++ 3 files changed, 38 insertions(+), 4 deletions(-) create mode 100644 src/models/utils/findAndGenerateToken.js diff --git a/src/controllers/admin.controller.js b/src/controllers/admin.controller.js index c9d2d7e..f2fc717 100644 --- a/src/controllers/admin.controller.js +++ b/src/controllers/admin.controller.js @@ -4,6 +4,7 @@ const Admin = require('../models/admin.model') const jwt = require('jsonwebtoken') const config = require('../config') const httpStatus = require('http-status') +const generateToken = require('../models/utils/findAndGenerateToken') exports.register = async (req, res, next) => { try { @@ -18,8 +19,8 @@ exports.register = async (req, res, next) => { exports.login = async (req, res, next) => { try { - const admin = await Admin.findAndGenerateToken(req.body) - const payload = {sub: admin.id} + const admin = await generateToken(req.body, 'admin') + const payload = { sub: admin.id } const token = jwt.sign(payload, config.secret) return res.json({ message: 'OK', token: token }) } catch (error) { diff --git a/src/controllers/user.controller.js b/src/controllers/user.controller.js index 401cd2d..5c447d2 100644 --- a/src/controllers/user.controller.js +++ b/src/controllers/user.controller.js @@ -5,6 +5,7 @@ const jwt = require('jsonwebtoken') const config = require('../config') const httpStatus = require('http-status') const uuidv1 = require('uuid/v1') +const generateToken = require('../models/utils/findAndGenerateToken') exports.register = async (req, res, next) => { try { @@ -22,7 +23,7 @@ exports.register = async (req, res, next) => { exports.login = async (req, res, next) => { try { - const user = await User.findAndGenerateToken(req.body) + const user = await generateToken(req.body, 'user') const payload = {sub: user.id} const token = jwt.sign(payload, config.secret) return res.json({ message: 'OK', token: token }) @@ -41,4 +42,4 @@ exports.confirm = async (req, res, next) => { } catch (error) { next(error) } -} \ No newline at end of file +} diff --git a/src/models/utils/findAndGenerateToken.js b/src/models/utils/findAndGenerateToken.js new file mode 100644 index 0000000..e15e43e --- /dev/null +++ b/src/models/utils/findAndGenerateToken.js @@ -0,0 +1,32 @@ +'use strict' + +const httpStatus = require('http-status') +const APIError = require('../../utils/APIError') +const User = require('../user.model') +const Admin = require('../admin.model') + +async function findAndGenerateToken (payload, from) { + const { email, password } = payload + if (!email) throw new APIError('Email must be provided for login') + let user + + if (from === 'admin') { + user = await Admin.findOne({ email }).exec() + } + if (from === 'user') { + user = await User.findOne({ email }).exec() + } + + if (!user) throw new APIError(`No user associated with ${email}`, httpStatus.NOT_FOUND) + + const passwordOK = await user.passwordMatches(password) + + if (!passwordOK) throw new APIError(`Password mismatch`, httpStatus.UNAUTHORIZED) + if (from === 'admin' && !user.active) { + throw new APIError(`User not activated`, httpStatus.UNAUTHORIZED) + } + + return user +} + +module.exports = findAndGenerateToken From 3cfd6ba0936d23cea2f724e1b2f270eb50d1e15c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Borys=20Tymi=C5=84ski?= Date: Sun, 30 Jun 2019 23:28:17 +0200 Subject: [PATCH 16/32] move models methods to external files to keep DRY --- src/models/admin.model.js | 24 ++------------ src/models/user.model.js | 34 +++----------------- src/models/utils/checkDuplicateEmailError.js | 2 ++ src/models/utils/hashPass.js | 2 ++ 4 files changed, 11 insertions(+), 51 deletions(-) diff --git a/src/models/admin.model.js b/src/models/admin.model.js index 032eb5a..26dac50 100644 --- a/src/models/admin.model.js +++ b/src/models/admin.model.js @@ -38,11 +38,7 @@ adminSchema.method({ transform () { const transformed = {} const fields = ['id', 'name', 'email', 'createdAt'] - - fields.forEach((field) => { - transformed[field] = this[field] - }) - + fields.forEach((field) => { transformed[field] = this[field] }) return transformed }, @@ -51,22 +47,6 @@ adminSchema.method({ } }) -adminSchema.statics = { - checkDuplicateEmailError, - - async findAndGenerateToken (payload) { - const { email, password } = payload - if (!email) throw new APIError('Email must be provided for login') - - const user = await this.findOne({ email }).exec() - if (!user) throw new APIError(`No user associated with ${email}`, httpStatus.NOT_FOUND) - - const passwordOK = await user.passwordMatches(password) - - if (!passwordOK) throw new APIError(`Password mismatch`, httpStatus.UNAUTHORIZED) - - return user - } -} +adminSchema.statics = { checkDuplicateEmailError } module.exports = mongoose.model('Admin', adminSchema) diff --git a/src/models/user.model.js b/src/models/user.model.js index 2b431ab..2baa8af 100644 --- a/src/models/user.model.js +++ b/src/models/user.model.js @@ -1,8 +1,7 @@ 'use strict' + const mongoose = require('mongoose') const bcrypt = require('bcrypt-nodejs') -const httpStatus = require('http-status') -const APIError = require('../utils/APIError') const transporter = require('../services/transporter') const Schema = mongoose.Schema const hashPass = require('./utils/hashPass') @@ -63,14 +62,10 @@ userSchema.post('save', async function saved (doc, next) { }) userSchema.method({ - transform () { + transform: function () { const transformed = {} - const fields = ['id', 'name', 'email', 'createdAt', 'role'] - - fields.forEach((field) => { - transformed[field] = this[field] - }) - + const fields = ['id', 'name', 'email', 'createdAt'] + fields.forEach(field => { transformed[field] = this[field] }) return transformed }, @@ -79,25 +74,6 @@ userSchema.method({ } }) -userSchema.statics = { - - checkDuplicateEmailError, - - async findAndGenerateToken (payload) { - const { email, password } = payload - if (!email) throw new APIError('Email must be provided for login') - - const user = await this.findOne({ email }).exec() - if (!user) throw new APIError(`No user associated with ${email}`, httpStatus.NOT_FOUND) - - const passwordOK = await user.passwordMatches(password) - - if (!passwordOK) throw new APIError(`Password mismatch`, httpStatus.UNAUTHORIZED) - - if (!user.active) throw new APIError(`User not activated`, httpStatus.UNAUTHORIZED) - - return user - } -} +userSchema.statics = { checkDuplicateEmailError } module.exports = mongoose.model('User', userSchema) diff --git a/src/models/utils/checkDuplicateEmailError.js b/src/models/utils/checkDuplicateEmailError.js index 18995fd..cedaa86 100644 --- a/src/models/utils/checkDuplicateEmailError.js +++ b/src/models/utils/checkDuplicateEmailError.js @@ -1,3 +1,5 @@ +'use strict' + const httpStatus = require('http-status') function checkDuplicateEmailError (err) { diff --git a/src/models/utils/hashPass.js b/src/models/utils/hashPass.js index 8afe020..20c2abd 100644 --- a/src/models/utils/hashPass.js +++ b/src/models/utils/hashPass.js @@ -1,3 +1,5 @@ +'use strict' + const bcrypt = require('bcrypt-nodejs') const hashPass = (schema) => { From caaac7fe426c2fe7ddd3be8efcf7f78fa5d73919 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Borys=20Tymi=C5=84ski?= Date: Sun, 30 Jun 2019 23:37:07 +0200 Subject: [PATCH 17/32] add default admin credentails to .env --- .env.example | 5 ++++- src/config/index.js | 5 +++++ src/utils/seed.js | 9 ++++++--- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/.env.example b/.env.example index 84d2912..71b7529 100644 --- a/.env.example +++ b/.env.example @@ -6,4 +6,7 @@ MONGOTESTURI=mongodb://localhost:27017/test-app APP_SECRET=somekey TRANSPORTER_SERVICE=example-gmail TRANSPORTER_EMAIL=example@email.com -TRANSPORTER_PASSWORD=gmail-application_password \ No newline at end of file +TRANSPORTER_PASSWORD=gmail-application_password +DEFAULT_ADMIN_NAME=admin +DEFAULT_ADMIN_EMAIL=example@email.com +DEFAULT_ADMIN_PASSWORD=password \ No newline at end of file diff --git a/src/config/index.js b/src/config/index.js index 481b9a8..68ce438 100644 --- a/src/config/index.js +++ b/src/config/index.js @@ -13,5 +13,10 @@ module.exports = { service: process.env.TRANSPORTER_SERVICE, email: process.env.TRANSPORTER_EMAIL, password: process.env.TRANSPORTER_PASSWORD + }, + admin: { + name: process.env.DEFAULT_ADMIN_NAME, + email: process.env.DEFAULT_ADMIN_EMAIL, + password: process.env.DEFAULT_ADMIN_PASSWORD } } diff --git a/src/utils/seed.js b/src/utils/seed.js index b03356c..8226ea7 100644 --- a/src/utils/seed.js +++ b/src/utils/seed.js @@ -1,4 +1,7 @@ +'use strict' + const Admin = require('../models/admin.model') +const config = require('../config') async function seed () { try { @@ -6,9 +9,9 @@ async function seed () { if (count === 0) { const user = { - name: 'Admin', - email: 'admin@gmail.com', - password: 'admin123' + name: config.admin.name, + email: config.admin.email, + password: config.admin.password } const admin = new Admin(user) From 63c5774ba7de9a1926d570ccb38757c1448fac3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Borys=20Tymi=C5=84ski?= Date: Mon, 1 Jul 2019 10:56:59 +0200 Subject: [PATCH 18/32] update readme --- README.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 09a4442..f5c782a 100644 --- a/README.md +++ b/README.md @@ -17,16 +17,14 @@ - TRANSPORTER_PASSWORD is password to above email - if gmail you need to generate special app-password, see for further support: https://support.google.com/mail/answer/185833?hl=en) -## Changes from original project +## Changelog - Fixed deprecation warnings with mongoose usage. - Updated dependencies to fix vulnerabilities. - Added email confirmation after registration. +- Distinguish admin and user. ## TODO -- Split users and admins to two collections: - - Rename auth.controller.js to user.controller.js - - create user.route.js and refactor auth.route.js to contain only this demonstration "/secret" routes - Integrate Swagger UI documentation - Write unit tests \ No newline at end of file From c3d36a883df0ff271f6bcc495f2c024651727120 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Borys=20Tymi=C5=84ski?= Date: Mon, 1 Jul 2019 13:12:46 +0200 Subject: [PATCH 19/32] fix check active (not for admins) --- src/models/utils/findAndGenerateToken.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/models/utils/findAndGenerateToken.js b/src/models/utils/findAndGenerateToken.js index e15e43e..2182822 100644 --- a/src/models/utils/findAndGenerateToken.js +++ b/src/models/utils/findAndGenerateToken.js @@ -22,7 +22,7 @@ async function findAndGenerateToken (payload, from) { const passwordOK = await user.passwordMatches(password) if (!passwordOK) throw new APIError(`Password mismatch`, httpStatus.UNAUTHORIZED) - if (from === 'admin' && !user.active) { + if (from === 'user' && !user.active) { throw new APIError(`User not activated`, httpStatus.UNAUTHORIZED) } From cb4021b8da5ea8ae2151cec82abe5a13d3d33a2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Borys=20Tymi=C5=84ski?= Date: Tue, 2 Jul 2019 16:09:14 +0200 Subject: [PATCH 20/32] use config.host in sending mail --- src/models/user.model.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/models/user.model.js b/src/models/user.model.js index 2baa8af..cc5f362 100644 --- a/src/models/user.model.js +++ b/src/models/user.model.js @@ -3,6 +3,7 @@ const mongoose = require('mongoose') const bcrypt = require('bcrypt-nodejs') const transporter = require('../services/transporter') +const config = require('../config') const Schema = mongoose.Schema const hashPass = require('./utils/hashPass') const checkDuplicateEmailError = require('./utils/checkDuplicateEmailError') @@ -44,7 +45,7 @@ userSchema.post('save', async function saved (doc, next) { from: 'noreply', to: this.email, subject: 'Confirm creating account', - html: `

Hello new user!

Click link to activate your new account.

Hello developer!

Feel free to change this template ;).

` + html: `

Hello new user!

Click link to activate your new account.

Hello developer!

Feel free to change this template ;).

` } transporter.sendMail(mailOptions, function (error, info) { From 304d9d7102c5323076acae401ad2b680edb1c89e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Borys=20Tymi=C5=84ski?= Date: Tue, 2 Jul 2019 16:09:47 +0200 Subject: [PATCH 21/32] remove unused packages from admin.model --- src/models/admin.model.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/models/admin.model.js b/src/models/admin.model.js index 26dac50..cdedb9b 100644 --- a/src/models/admin.model.js +++ b/src/models/admin.model.js @@ -1,8 +1,6 @@ 'use strict' const mongoose = require('mongoose') const bcrypt = require('bcrypt-nodejs') -const httpStatus = require('http-status') -const APIError = require('../utils/APIError') const Schema = mongoose.Schema const hashPass = require('./utils/hashPass') const checkDuplicateEmailError = require('./utils/checkDuplicateEmailError') From af401da7d068a9c504fd9de9c6df56e2ad96c064 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Borys=20Tymi=C5=84ski?= Date: Tue, 2 Jul 2019 17:43:43 +0200 Subject: [PATCH 22/32] add parameter hook name to hashPass --- src/models/admin.model.js | 2 +- src/models/user.model.js | 2 +- src/models/utils/hashPass.js | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/models/admin.model.js b/src/models/admin.model.js index cdedb9b..c6d5a69 100644 --- a/src/models/admin.model.js +++ b/src/models/admin.model.js @@ -30,7 +30,7 @@ const adminSchema = new Schema({ timestamps: true }) -hashPass(adminSchema) +hashPass(adminSchema, 'save') adminSchema.method({ transform () { diff --git a/src/models/user.model.js b/src/models/user.model.js index cc5f362..bc89b13 100644 --- a/src/models/user.model.js +++ b/src/models/user.model.js @@ -37,7 +37,7 @@ const userSchema = new Schema({ timestamps: true }) -hashPass(userSchema) +hashPass(userSchema, 'save') userSchema.post('save', async function saved (doc, next) { try { diff --git a/src/models/utils/hashPass.js b/src/models/utils/hashPass.js index 20c2abd..2fa9f2a 100644 --- a/src/models/utils/hashPass.js +++ b/src/models/utils/hashPass.js @@ -2,8 +2,8 @@ const bcrypt = require('bcrypt-nodejs') -const hashPass = (schema) => { - schema.pre('save', async function save (next) { +const hashPass = (schema, action) => { + schema.pre(action, async function save (next) { try { if (!this.isModified('password')) { return next() From bc41594eda015ecd5d09be8e3097bb29d9a56588 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Borys=20Tymi=C5=84ski?= Date: Tue, 2 Jul 2019 17:44:39 +0200 Subject: [PATCH 23/32] export user data from passport service --- src/services/passport.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/services/passport.js b/src/services/passport.js index b0fec0b..f55f9da 100644 --- a/src/services/passport.js +++ b/src/services/passport.js @@ -21,6 +21,7 @@ const jwtStrategy = new JwtStrategy(jwtOptions, (jwtPayload, done) => { } if (user) { + exports.user = user return done(null, user) } else { Admin.findById(jwtPayload.sub, (err, admin) => { @@ -28,6 +29,7 @@ const jwtStrategy = new JwtStrategy(jwtOptions, (jwtPayload, done) => { return done(err, null) } if (admin) { + exports.user = admin return done(null, admin) } else { return done(null, false) From fc67a108245f85949abfcfc3a7e76aae49d1a873 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Borys=20Tymi=C5=84ski?= Date: Wed, 3 Jul 2019 18:02:22 +0200 Subject: [PATCH 24/32] rename hostname to base_uri --- .env.example | 1 + src/validations/user.update.validation.js | 0 2 files changed, 1 insertion(+) create mode 100644 src/validations/user.update.validation.js diff --git a/.env.example b/.env.example index 71b7529..2098fed 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,7 @@ NODE_ENV=dev APP="Your App" PORT=3000 +BASE_URI=http://localhost:3000 MONGOURI=mongodb://localhost:27017/yourapp MONGOTESTURI=mongodb://localhost:27017/test-app APP_SECRET=somekey diff --git a/src/validations/user.update.validation.js b/src/validations/user.update.validation.js new file mode 100644 index 0000000..e69de29 From 18891c920385ceff0cd72bc69b1549ffdab66008 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Borys=20Tymi=C5=84ski?= Date: Wed, 3 Jul 2019 18:02:49 +0200 Subject: [PATCH 25/32] add user resetPassKey to model --- src/models/user.model.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/models/user.model.js b/src/models/user.model.js index bc89b13..a4e8779 100644 --- a/src/models/user.model.js +++ b/src/models/user.model.js @@ -32,6 +32,9 @@ const userSchema = new Schema({ active: { type: Boolean, default: false + }, + resetPasswordKey: { + type: String } }, { timestamps: true @@ -45,7 +48,7 @@ userSchema.post('save', async function saved (doc, next) { from: 'noreply', to: this.email, subject: 'Confirm creating account', - html: `

Hello new user!

Click link to activate your new account.

Hello developer!

Feel free to change this template ;).

` + html: `

Hello new user!

Click link to activate your new account.

Hello developer!

Feel free to change this template ;).

` } transporter.sendMail(mailOptions, function (error, info) { From e1fe8edca9569356a77694b61f011d496c97b860 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Borys=20Tymi=C5=84ski?= Date: Wed, 3 Jul 2019 18:04:22 +0200 Subject: [PATCH 26/32] add update, reset.sendMail and reset.updatePass methods --- src/controllers/user.controller.js | 60 ++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/src/controllers/user.controller.js b/src/controllers/user.controller.js index 5c447d2..ad734d9 100644 --- a/src/controllers/user.controller.js +++ b/src/controllers/user.controller.js @@ -6,6 +6,9 @@ const config = require('../config') const httpStatus = require('http-status') const uuidv1 = require('uuid/v1') const generateToken = require('../models/utils/findAndGenerateToken') +const passport = require('../services/passport') +const bcrypt = require('bcrypt-nodejs') +const transporter = require('../services/transporter') exports.register = async (req, res, next) => { try { @@ -32,6 +35,21 @@ exports.login = async (req, res, next) => { } } +exports.update = async (req, res, next) => { + try { + if (!passport.user) return res.status(httpStatus.FORBIDDEN) + const user = await User.findOne({ 'email': passport.user.email }).exec() + if (!user.passwordMatches(req.body.password)) return res.status(httpStatus.FORBIDDEN) + if (req.body.password) req.body.password = bcrypt.hashSync(req.body.password) + await User.findOneAndUpdate( + { '_id': passport.user._id }, + { $set: req.body } + ) + } catch (error) { + next(error) + } +} + exports.confirm = async (req, res, next) => { try { await User.findOneAndUpdate( @@ -43,3 +61,45 @@ exports.confirm = async (req, res, next) => { next(error) } } + +exports.reset = { + async sendMail (req, res, next) { + try { + const resetPasswordKey = uuidv1() + await User.findOneAndUpdate( + { '_id': passport.user._id }, + { 'resetPasswordKey': resetPasswordKey } + ) + const mailOptions = { + from: 'noreply', + to: passport.user.email, + subject: 'Reset password', + html: `

Hello user!

Click link to reset your password.

Hello developer!

Feel free to change this template ;).

` + } + + transporter.sendMail(mailOptions, function (error, info) { + if (error) { + next(error) + } else { + return res.json({ message: 'OK' }) + } + }) + } catch (error) { + next(error) + } + }, + async updatePass (req, res, next) { + try { + if (!req.query.key) return res.status(httpStatus.BAD_REQUEST) + const newPassword = uuidv1() + const newPasswordHash = bcrypt.hashSync(newPassword) + await User.findOneAndUpdate( + { 'resetPasswordKey': req.query.key }, + { $set: { 'password': newPasswordHash, 'resetPasswordKey': '' } } + ) + return res.json({ message: 'OK', newPassword: newPassword }) + } catch (error) { + next(error) + } + } +} From d40e71e22d35acd1a47455aaa366b887ac918f88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Borys=20Tymi=C5=84ski?= Date: Wed, 3 Jul 2019 18:04:56 +0200 Subject: [PATCH 27/32] add update user validator --- src/validations/user.validation.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/validations/user.validation.js b/src/validations/user.validation.js index 046de48..b1b8b1e 100644 --- a/src/validations/user.validation.js +++ b/src/validations/user.validation.js @@ -10,5 +10,16 @@ module.exports = { password: Joi.string().min(6).max(128).required(), name: Joi.string().max(128).required() } + }, + update: { + body: { + _id: Joi.any().forbidden(), + email: Joi.any().forbidden(), + password: Joi.string().min(6).max(128), + name: Joi.string().max(128), + activationKey: Joi.any().forbidden(), + active: Joi.any().forbidden(), + resetPasswordKey: Joi.any().forbidden() + } } } From 219cafb024c6fc65854175846ff0d28016fd4d8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Borys=20Tymi=C5=84ski?= Date: Wed, 3 Jul 2019 18:05:30 +0200 Subject: [PATCH 28/32] add reset and update user routes --- src/routes/api/user.route.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/routes/api/user.route.js b/src/routes/api/user.route.js index ec2b8d7..c8865d0 100644 --- a/src/routes/api/user.route.js +++ b/src/routes/api/user.route.js @@ -5,10 +5,15 @@ const router = express.Router() const userController = require('../../controllers/user.controller') const validator = require('express-validation') const { create } = require('../../validations/user.validation') +const auth = require('../../middlewares/authorization') -router.post('/register', validator(create), userController.register) // validate and register -router.post('/login', userController.login) // login +router.post('/register', validator(create), userController.register) +router.post('/login', userController.login) +router.post('/resetStart', auth(), userController.reset.sendMail) router.get('/confirm', userController.confirm) +router.get('/resetConfirm', userController.reset.updatePass) + +router.put('/update', auth(), userController.update) module.exports = router From bd96df88b45e3328349b85a821250d5653754622 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Borys=20Tymi=C5=84ski?= Date: Wed, 3 Jul 2019 18:05:59 +0200 Subject: [PATCH 29/32] use base_uri from env in config --- src/config/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/config/index.js b/src/config/index.js index 68ce438..370aa39 100644 --- a/src/config/index.js +++ b/src/config/index.js @@ -2,6 +2,7 @@ require('dotenv').config() // load .env file module.exports = { port: process.env.PORT, + baseURI: process.env.BASE_URI, app: process.env.APP, env: process.env.NODE_ENV, secret: process.env.APP_SECRET, From 826ead676c2a9ffa17cd65e18526867358818d86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Borys=20Tymi=C5=84ski?= Date: Wed, 3 Jul 2019 18:08:07 +0200 Subject: [PATCH 30/32] useFindAndModify set to false (mongoose deprecation fix) --- src/services/mongoose.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/services/mongoose.js b/src/services/mongoose.js index 801aece..5c35128 100644 --- a/src/services/mongoose.js +++ b/src/services/mongoose.js @@ -29,6 +29,7 @@ exports.connect = () => { seed() mongoose.set('useCreateIndex', true) + mongoose.set('useFindAndModify', false) return mongoose.connection } From 41dff02f32196bc10d0da05c27a10ed8c06d5523 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Borys=20Tymi=C5=84ski?= Date: Wed, 3 Jul 2019 19:51:31 +0200 Subject: [PATCH 31/32] use update user validator --- src/validations/user.update.validation.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/validations/user.update.validation.js diff --git a/src/validations/user.update.validation.js b/src/validations/user.update.validation.js deleted file mode 100644 index e69de29..0000000 From 7147d612f70d4b795a55605c1cedc727946577e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Borys=20Tymi=C5=84ski?= Date: Wed, 3 Jul 2019 19:52:34 +0200 Subject: [PATCH 32/32] fix update user method --- src/controllers/user.controller.js | 16 +++++++++++++--- src/routes/api/user.route.js | 3 ++- src/validations/user.validation.js | 1 + 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/controllers/user.controller.js b/src/controllers/user.controller.js index ad734d9..a7e0298 100644 --- a/src/controllers/user.controller.js +++ b/src/controllers/user.controller.js @@ -9,6 +9,7 @@ const generateToken = require('../models/utils/findAndGenerateToken') const passport = require('../services/passport') const bcrypt = require('bcrypt-nodejs') const transporter = require('../services/transporter') +const APIError = require('../utils/APIError') exports.register = async (req, res, next) => { try { @@ -37,14 +38,23 @@ exports.login = async (req, res, next) => { exports.update = async (req, res, next) => { try { - if (!passport.user) return res.status(httpStatus.FORBIDDEN) + if (!passport.user || !req.body.password) { + res.status(httpStatus.UNAUTHORIZED) + return res.send(new APIError(`Password mismatch`, httpStatus.UNAUTHORIZED)) + } + const user = await User.findOne({ 'email': passport.user.email }).exec() - if (!user.passwordMatches(req.body.password)) return res.status(httpStatus.FORBIDDEN) - if (req.body.password) req.body.password = bcrypt.hashSync(req.body.password) + if (!user.passwordMatches(req.body.password)) { + res.status(httpStatus.UNAUTHORIZED) + return res.send(new APIError(`Password mismatch`, httpStatus.UNAUTHORIZED)) + } + + if (req.body.newPassword) req.body.password = bcrypt.hashSync(req.body.newPassword) await User.findOneAndUpdate( { '_id': passport.user._id }, { $set: req.body } ) + return res.json({ message: 'OK' }) } catch (error) { next(error) } diff --git a/src/routes/api/user.route.js b/src/routes/api/user.route.js index c8865d0..e94e8aa 100644 --- a/src/routes/api/user.route.js +++ b/src/routes/api/user.route.js @@ -5,6 +5,7 @@ const router = express.Router() const userController = require('../../controllers/user.controller') const validator = require('express-validation') const { create } = require('../../validations/user.validation') +const { update } = require('../../validations/user.validation') const auth = require('../../middlewares/authorization') router.post('/register', validator(create), userController.register) @@ -14,6 +15,6 @@ router.post('/resetStart', auth(), userController.reset.sendMail) router.get('/confirm', userController.confirm) router.get('/resetConfirm', userController.reset.updatePass) -router.put('/update', auth(), userController.update) +router.put('/update', validator(update), auth(), userController.update) module.exports = router diff --git a/src/validations/user.validation.js b/src/validations/user.validation.js index b1b8b1e..9e4b02f 100644 --- a/src/validations/user.validation.js +++ b/src/validations/user.validation.js @@ -16,6 +16,7 @@ module.exports = { _id: Joi.any().forbidden(), email: Joi.any().forbidden(), password: Joi.string().min(6).max(128), + newPassword: Joi.string().min(6).max(128), name: Joi.string().max(128), activationKey: Joi.any().forbidden(), active: Joi.any().forbidden(),