Skip to content

Commit

Permalink
Add password complexity checks for openhab-cloud account (#80)
Browse files Browse the repository at this point in the history
All places, where a password can be set will now go through a new class,
UserPassword, which will check the password complexity before setting the
password to the user account. It also provides a check method to check the
complexity requirements before a user account will be created. If the
complexity rules aren't met, the save of the password will be denied and an
error message with the complexity rules is shown.

Fixes #75
  • Loading branch information
FlorianSW authored and digitaldan committed Apr 22, 2017
1 parent cc5ccc2 commit 2dc275a
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 7 deletions.
31 changes: 24 additions & 7 deletions routes/account.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ var uuid = require('uuid');
var mailer = require('../mailer');
var logger = require('../logger');
var app = require('../app');
var UserPassword = require('../userpassword');

exports.lostpasswordget = function(req, res) {
res.render('lostpassword', {title: "Lost my password", user: req.user,
Expand Down Expand Up @@ -106,8 +107,10 @@ exports.lostpasswordresetpost = function(req, res) {
if (lostPassword && !error) {
User.findOne({_id: lostPassword.user}, function(error, lostUser) {
if (lostUser && !error) {
lostUser.password = req.body.password;
lostUser.save(function(error) {
var userPassword, result;

userPassword = new UserPassword(lostUser);
result = userPassword.setPassword(req.body.password, function(error) {
if (!error) {
lostPassword.used = true;
lostPassword.save();
Expand All @@ -118,6 +121,11 @@ exports.lostpasswordresetpost = function(req, res) {
res.redirect('/');
}
});

if (!result) {
UserPassword.printPasswordNotComplexEnoughError(req);
res.redirect('/lostpasswordreset?resetCode=' + req.body.resetCode);
}
} else {
req.flash('error', 'There was an error while processing your request');
res.redirect('/');
Expand Down Expand Up @@ -183,11 +191,14 @@ exports.accountpasswordpost = function(req, res) {
res.redirect('/account');
} else {
if (req.body.password == req.body.password1) {
user = req.user;
user.password = req.body.password;
user.save();
req.flash('info', 'Password successfully changed');
res.redirect('/account');
userPassword = new UserPassword(req.user);
if (!userPassword.setPassword(req.body.password)) {
UserPassword.printPasswordNotComplexEnoughError(req);
res.redirect('/account');
} else {
req.flash('info', 'Password successfully changed');
res.redirect('/account');
}
} else {
req.flash('error', 'Passwords don\'t match');
res.redirect('/account');
Expand Down Expand Up @@ -347,6 +358,12 @@ exports.registerpost = function(req, res) {
res.render('login', { title: "Login / Sign up", user: req.user,
errormessages:req.flash('error'), infomessages:req.flash('info') });
} else {
if (!UserPassword.isComplexEnough(req.body.password)) {
UserPassword.printPasswordNotComplexEnoughError(req);
res.render('login', { title: "Login / Sign up", user: req.user,
errormessages:req.flash('error'), infomessages:req.flash('info') });
return;
}
User.register(req.body.username, req.body.password, function(err, user) {
if (err) {
req.flash('error', "An error occured during registration, please contact support");
Expand Down
6 changes: 6 additions & 0 deletions routes/users.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ var User = require('../models/user');
var Openhab = require('../models/openhab');
var UserDevice = require('../models/userdevice');
var logger = require('../logger');
var UserPassword = require('../userpassword');
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var ObjectId = mongoose.SchemaTypes.ObjectId;
Expand Down Expand Up @@ -60,6 +61,11 @@ exports.usersaddpost = function(req, res) {
res.redirect('/users/add');
} else {
if (req.body.password == req.body.password1) {
if (!UserPassword.isComplexEnough(req.body.password)) {
UserPassword.printPasswordNotComplexEnoughError(req);
res.redirect('/users/add');
return;
}
User.findOne({username: req.body.username}, function(error, checkUser) {
if (!error) {
if (checkUser) {
Expand Down
102 changes: 102 additions & 0 deletions userpassword.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
var User = require('./models/user');

/**
* A helper class for setting the password of an arbitrary user.
*
* @param user The user where the password change attempts should happen
* @constructor
*/
var UserPassword = function(user) {
if (!(user instanceof User)) {
throw new Error('UserPassword can only be instantiated with an User object, ' +
user.constructor + ' given.');
}
this.user = user;
};

/**
* Verifies the password complexity rules for the given plain text password and, if it
* matches the requirements, sets the new password to the given user.
*
* The complexity rules includes:
* - at least 4 characters
* - has at least two characters of the following categories: uppercase letters, lowercase letters, numbers, special characters
*
* @param {string} password
* @param fn Optional function to pass to the save function of User
* @return {boolean} True, if saving the password was initiated, false otherwise. False is garantueed to mean, that the
* password does not meet the complexity requirements.
*/
UserPassword.prototype.setPassword = function(password, fn) {
if (!UserPassword.isComplexEnough(password)) {
return false;
}

this.user.password = password;
if (typeof fn !== 'function') {
fn = undefined;
}
this.user.save(fn);

return true;
};

/**
* Helper function for checking the complexity of a password.
*
* @param password
* @returns {boolean}
*/
UserPassword.isComplexEnough = function(password) {
var matches = 0;

if (password.length < 4) {
return false;
}

// match against all lowercase letters
if (password.match(/^(?=.*[a-z]).+$/)) {
matches++;
}

// match against all uppercase letters
if (password.match(/^(?=.*[A-Z]).+$/)) {
matches++;
}

// match against all numbers
if (password.match(/^(?=.*\d).+$/)) {
matches++;
}

// match against a reasonable set of special characters
if (password.match(/^(?=.*[-+_!@#$%^&*.,?]).+$/)) {
matches++;
}

if (matches < 2) {
return false;
}

return true;
};

/**
* Prints a message to the given request, that the entered password was not complex enough, while giving the complexity
* rules for passwords.
*
* @param req
*/
UserPassword.printPasswordNotComplexEnoughError = function(req) {
var message = [];
message.push('The password does not meet the password complexity requirements. Please try again.');
message.push('Your password must be at least 4 characters long and need to contain at least 2 different characters from the following groups:');
message.push(' * Lowercase letters (a, b, c, ...)');
message.push(' * Uppercase letters (A, B, C, ...)');
message.push(' * Numbers (1, 2, 3, ...)');
message.push(' * Special characters out of: -+_!@#$%^&*.,?');

req.flash('error', message);
};

module.exports = UserPassword;
8 changes: 8 additions & 0 deletions views/alerts.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,15 @@
<div class="span12">
<div class="alert alert-error fade in">
<button type="button" class="close" data-dismiss="alert">×</button>
<% if(errormessages.length === 1) { %>
<%= errormessages %>
<% } else { %>
<ul>
<% errormessages.forEach(function(err) { %>
<li><%= err %></li>
<% }) %>
</ul>
<% } %>
</div>
</div>
</div>
Expand Down

0 comments on commit 2dc275a

Please sign in to comment.