From bd3554a4f1e8cfaac5b7d565a8b7f6a3546b510b Mon Sep 17 00:00:00 2001 From: d34thwings Date: Fri, 12 May 2017 13:16:49 +0000 Subject: [PATCH] feat(dashboard): Force password change on user --- config/test.js | 4 +- package.json | 1 - server/dashboard/index.js | 15 ++++++ .../dashboard/routes/get-change-password.js | 10 ++++ server/dashboard/routes/index.js | 4 ++ .../dashboard/routes/post-change-password.js | 46 +++++++++++++++++++ server/dashboard/routes/post-create-user.js | 2 +- server/dashboard/views/change-password.hbs | 31 +++++++++++++ server/users/models/user.model.js | 1 + server/users/routes/cget-users.js | 2 +- server/users/routes/get-me.js | 2 +- server/users/routes/get-users.js | 2 +- server/users/routes/post-users.js | 2 +- server/users/routes/put-users.js | 2 +- test/.eslintrc | 5 -- yarn.lock | 4 -- 16 files changed, 115 insertions(+), 18 deletions(-) create mode 100644 server/dashboard/routes/get-change-password.js create mode 100644 server/dashboard/routes/post-change-password.js create mode 100644 server/dashboard/views/change-password.hbs diff --git a/config/test.js b/config/test.js index d214eb3..9294e6f 100644 --- a/config/test.js +++ b/config/test.js @@ -9,13 +9,13 @@ module.exports = { }, }, mongodb: { - host: 'mongo', + host: 'localhost', port: 27017, database: 'lvconnect_test', }, kue: { redis: { - host: 'redis', + host: 'localhost', port: 6379, db: 0, }, diff --git a/package.json b/package.json index 5172c68..ad62cc9 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,6 @@ "chai": "^3.5.0", "eslint": "^3.8.1", "eslint-config-airbnb-base": "^11.0.0", - "eslint-plugin-babel": "^4.0.0", "eslint-plugin-import": "^2.0.1", "mocha": "^3.0.2", "nodemon": "^1.10.2" diff --git a/server/dashboard/index.js b/server/dashboard/index.js index 64ec45a..0c3355e 100644 --- a/server/dashboard/index.js +++ b/server/dashboard/index.js @@ -17,6 +17,21 @@ exports.register = (server, options, next) => { }); server.route(routes); + // Middleware to force user to reset his password + const ignoredRoutes = ['/assets', '/login', '/change-password']; + server.ext({ + type: 'onPreHandler', + method(req, res) { + if (!ignoredRoutes.some(r => req.path.includes(r)) && req.auth.credentials.needPasswordChange) { + return res().redirect('/dashboard/change-password'); + } + return res.continue(); + }, + options: { + sandbox: 'plugin', + }, + }); + next(); }; diff --git a/server/dashboard/routes/get-change-password.js b/server/dashboard/routes/get-change-password.js new file mode 100644 index 0000000..e62bb65 --- /dev/null +++ b/server/dashboard/routes/get-change-password.js @@ -0,0 +1,10 @@ +module.exports = { + method: 'GET', + path: '/dashboard/change-password', + config: { auth: 'session' }, + handler(req, res) { + res.view('change-password', { + pageTitle: 'Password change', + }); + }, +}; diff --git a/server/dashboard/routes/index.js b/server/dashboard/routes/index.js index b8b8d9c..505f9e7 100644 --- a/server/dashboard/routes/index.js +++ b/server/dashboard/routes/index.js @@ -13,6 +13,8 @@ const getEditApplication = require('./get-edit-app'); const postCreateApplication = require('./post-create-app'); const postEditApplication = require('./post-edit-app'); const deleteApplication = require('./delete-app'); +const getChangePassword = require('./get-change-password'); +const postChangePassword = require('./post-change-password'); module.exports = [ getDashboard, @@ -30,4 +32,6 @@ module.exports = [ postCreateApplication, postEditApplication, deleteApplication, + getChangePassword, + postChangePassword, ]; diff --git a/server/dashboard/routes/post-change-password.js b/server/dashboard/routes/post-change-password.js new file mode 100644 index 0000000..d8bfe07 --- /dev/null +++ b/server/dashboard/routes/post-change-password.js @@ -0,0 +1,46 @@ +const Joi = require('joi'); + +module.exports = { + method: 'POST', + path: '/dashboard/change-password', + config: { + auth: 'session', + validate: { + payload: { + plainPassword: Joi.string().min(6).required(), + plainPasswordCheck: Joi.string().required(), + }, + }, + }, + handler(req, res) { + if (req.payload.plainPassword !== req.payload.plainPasswordCheck) { + return res.view('change-password', { + pageTitle: 'Password change', + error: 'Passwords don\'t match', + }).code(401); + } + + const user = req.auth.credentials; + + return user.comparePassword(req.payload.plainPassword) + .then((isSamePassword) => { + if (isSamePassword) { + return res.view('change-password', { + pageTitle: 'Password change', + error: 'You must choose a password different from the previous one', + }).code(401); + } + + return user.hashPassword(req.payload.plainPassword) + .then(() => { + user.needPasswordChange = false; + return user.save(); + }) + .then(() => res.redirect('/dashboard')); + }) + .catch(() => res.view('change-password', { + pageTitle: 'Password change', + error: 'An unknown error occurred', + }).code(401)); + }, +}; diff --git a/server/dashboard/routes/post-create-user.js b/server/dashboard/routes/post-create-user.js index 949e0f7..816c361 100644 --- a/server/dashboard/routes/post-create-user.js +++ b/server/dashboard/routes/post-create-user.js @@ -14,6 +14,7 @@ module.exports = { firstName: Joi.string().min(2).required(), lastName: Joi.string().min(2).required(), plainPassword: Joi.string().min(6).required(), + plainPasswordCheck: Joi.string().required(), email: Joi.string().email().required(), description: Joi.string().max(255).allow(''), roles: Joi.array().items(Joi.string().valid(validRoles)).min(1).single() @@ -21,7 +22,6 @@ module.exports = { githubHandle: Joi.string().allow(''), trelloHandle: Joi.string().allow(''), city: Joi.string().valid(validCities).required(), - plainPasswordCheck: Joi.string().required(), }), failAction: (req, res, src, error) => { res.view('create-user', { diff --git a/server/dashboard/views/change-password.hbs b/server/dashboard/views/change-password.hbs new file mode 100644 index 0000000..e52936f --- /dev/null +++ b/server/dashboard/views/change-password.hbs @@ -0,0 +1,31 @@ +
+
+
+

Change password

+
+
+ You've been requested to change your password for security reasons. Choose a new password bellow : + +
+ + +
+ +
+ + +
+ + {{ error }} + + +
+ +
+ +
+
+
diff --git a/server/users/models/user.model.js b/server/users/models/user.model.js index 0e8371f..9914d25 100644 --- a/server/users/models/user.model.js +++ b/server/users/models/user.model.js @@ -18,6 +18,7 @@ const userSchema = new mongoose.Schema({ createdAt: { type: Date, default: Date.now }, description: String, city: String, + needPasswordChange: { type: Boolean, default: true }, }); userSchema.virtual('profilePictureUrl').get(function getProfilePictureUrl(value) { diff --git a/server/users/routes/cget-users.js b/server/users/routes/cget-users.js index add30ca..75a0898 100644 --- a/server/users/routes/cget-users.js +++ b/server/users/routes/cget-users.js @@ -25,7 +25,7 @@ module.exports = { .where(email ? { email: { $eq: email } } : null) .limit(limit) .skip(page * limit || 0) - .select('-password -thirdParty') + .select('-password -thirdParty -needPasswordChange') .exec(); const countPromise = User.count(); diff --git a/server/users/routes/get-me.js b/server/users/routes/get-me.js index df56b97..b1a39ac 100644 --- a/server/users/routes/get-me.js +++ b/server/users/routes/get-me.js @@ -7,6 +7,6 @@ module.exports = { pre: [hasScopeInList('profile:get')], }, handler(req, res) { - return res.mongodb(req.auth.credentials.user, ['password']); + return res.mongodb(req.auth.credentials.user, ['password', 'thirdParty', 'needPasswordChange']); }, }; diff --git a/server/users/routes/get-users.js b/server/users/routes/get-users.js index b061393..5e0aa29 100644 --- a/server/users/routes/get-users.js +++ b/server/users/routes/get-users.js @@ -21,7 +21,7 @@ module.exports = { const userPromise = User .findOne({ _id: req.params.user }) - .select('-password -thirdParty') + .select('-password -thirdParty -needPasswordChange') .exec() .then((user) => { if (!user) { diff --git a/server/users/routes/post-users.js b/server/users/routes/post-users.js index 858b2ec..aa225b4 100644 --- a/server/users/routes/post-users.js +++ b/server/users/routes/post-users.js @@ -41,6 +41,6 @@ module.exports = { return Promise.reject(Boom.wrap(err)); }); - res.mongodb(userPromise, ['password']).code(201); + res.mongodb(userPromise, ['password', 'thirdParty', 'needPasswordChange']).code(201); }, }; diff --git a/server/users/routes/put-users.js b/server/users/routes/put-users.js index 97eaf7e..28199f4 100644 --- a/server/users/routes/put-users.js +++ b/server/users/routes/put-users.js @@ -52,6 +52,6 @@ module.exports = { return Promise.resolve(savedUser); }); - return res.mongodb(userPromise, ['password', 'thirdParty']); + return res.mongodb(userPromise, ['password', 'thirdParty', 'needPasswordChange']); }, }; diff --git a/test/.eslintrc b/test/.eslintrc index 0cdb004..6900bed 100644 --- a/test/.eslintrc +++ b/test/.eslintrc @@ -1,19 +1,14 @@ { "parser": "babel-eslint", "extends": ["airbnb-base"], - "plugins": [ - "babel" - ], "env": { "node": true, "mocha": true }, "rules": { "prefer-arrow-callback": 0, - "generator-star-spacing": 0, "func-names": 0, "import/no-extraneous-dependencies": 0, - "babel/generator-star-spacing": 1, "no-underscore-dangle": 0 } } diff --git a/yarn.lock b/yarn.lock index 6f36b00..d256655 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1095,10 +1095,6 @@ eslint-module-utils@^2.0.0: debug "2.2.0" pkg-dir "^1.0.0" -eslint-plugin-babel@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-babel/-/eslint-plugin-babel-4.0.1.tgz#77de74dabd67a6bef3b16bf258f5804e971e7349" - eslint-plugin-import@^2.0.1: version "2.2.0" resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.2.0.tgz#72ba306fad305d67c4816348a4699a4229ac8b4e"