diff --git a/config/lib/seed.js b/config/lib/seed.js index b1b9287ffb..35a76382aa 100644 --- a/config/lib/seed.js +++ b/config/lib/seed.js @@ -29,56 +29,66 @@ var seedAdmin = { roles: ['user', 'admin'] }; - //If production only seed admin if it does not exist if (process.env.NODE_ENV === 'production') { //Add Local Admin - User.find({username: 'admin'}, function (err, users) { + User.find({username: seedAdmin.username}, function (err, users) { if (users.length === 0) { - var password = crypto.randomBytes(64).toString('hex').slice(1, 20); - seedAdmin.password = password; var user = new User(seedAdmin); - // Then save the user - user.save(function (err) { - if (err) { - console.log('Failed to add local admin'); - } else { - console.log(chalk.bold.red('Local admin added with password set to ' + password)); - } - }); + + // generate a random password and save + User.generateRandomPassphrase() + .then(saveUser(user)) + .catch(reportError); + } else { - console.log('Admin user exists'); + console.log(seedAdmin.username + ' user exists'); } }); } else { + //Add Local User - User.find({username: 'user'}).remove(function () { - var password = crypto.randomBytes(64).toString('hex').slice(1, 20); - seedUser.password = password; + User.find({username: seedUser.username}).remove(function () { var user = new User(seedUser); - // Then save the user - user.save(function (err) { - if (err) { - console.log('Failed to add local user'); - } else { - console.log(chalk.bold.red('Local user added with password set to ' + password)); - } - }); - }); + // generate a random password and save + User.generateRandomPassphrase() + .then(saveUser(user)) + .catch(reportError); + }); //Add Local Admin - User.find({username: 'admin'}).remove(function () { - var password = crypto.randomBytes(64).toString('hex').slice(1, 20); - seedAdmin.password = password; + User.find({username: seedAdmin.username}).remove(function () { var user = new User(seedAdmin); + + // generate a random password and save + User.generateRandomPassphrase() + .then(saveUser(user)) + .catch(reportError); + }); +} + +// save the specified user with the password provided from the resolved promise +function saveUser(user) { + return function (password) { + // set the new password + user.password = password; + // Then save the user user.save(function (err) { if (err) { - console.log('Failed to add local admin'); + console.log('Database Seeding:\t\t\tFailed to add local ' + user.username); } else { - console.log(chalk.bold.red('Local admin added with password set to ' + password)); + console.log(chalk.bold.red('Database Seeding:\t\t\tLocal ' + user.username + ' added with password set to ' + password)); } }); - }); + }; +} + +// report the error +function reportError(err) { + console.log(); + console.log('Database Seeding:\t\t\t Failed to generate random password'); + console.log(err); + console.log(); } diff --git a/modules/users/server/models/user.server.model.js b/modules/users/server/models/user.server.model.js index 8f102517aa..ee5dbc6303 100644 --- a/modules/users/server/models/user.server.model.js +++ b/modules/users/server/models/user.server.model.js @@ -7,6 +7,7 @@ var mongoose = require('mongoose'), Schema = mongoose.Schema, crypto = require('crypto'), validator = require('validator'), + generatePassword = require('generate-password'), owasp = require('owasp-password-strength-test'); /** @@ -166,4 +167,39 @@ UserSchema.statics.findUniqueUsername = function (username, suffix, callback) { }); }; +/** +* Generates a random passphrase that passes the owasp test. +* Returns a promise that resolves with the generated passphrase, or rejects with an error if something goes wrong. +* NOTE: Passphrases are only tested against the required owasp strength tests, and not the optional tests. +*/ +UserSchema.statics.generateRandomPassphrase = function () { + return new Promise(function (resolve, reject) { + var password = ''; + + // iterate until the we have a valid passphrase. + // NOTE: Should rarely iterate more than once, but we need this to ensure no repeating characters are present. + while (password.length < 20) { + // build the random password + password = generatePassword.generate({ + length: Math.floor(Math.random() * (20)) + 20, // randomize length between 20 and 40 characters + numbers: true, + symbols: false, + uppercase: true, + excludeSimilarCharacters: true, + }); + + // check if we need to remove any repeating characters. + password = password.replace(/(.)\1{2,}/g, ''); + } + + // Send the rejection back if the passphrase fails to pass the strength test + if (owasp.test(password).errors.length) { + reject(new Error('An unexpected problem occured while generating the random passphrase')); + } else { + // resolve with the validated passphrase + resolve(password); + } + }); +}; + mongoose.model('User', UserSchema); diff --git a/modules/users/tests/server/user.server.model.tests.js b/modules/users/tests/server/user.server.model.tests.js index ad8e6f401b..06761db6e9 100644 --- a/modules/users/tests/server/user.server.model.tests.js +++ b/modules/users/tests/server/user.server.model.tests.js @@ -228,6 +228,22 @@ describe('User Model Unit Tests:', function () { }); }); + it('should validate a randomly generated passphrase from the static schema method', function () { + var _user1 = new User(user1); + + User.generateRandomPassphrase() + .then(function (password) { + _user1.password = password; + _user1.validate(function (err) { + should.not.exist(err); + }); + }) + .catch(function (err) { + should.not.exist(err); + }); + + }); + it('should validate when the password is undefined', function () { var _user1 = new User(user1); _user1.password = undefined; diff --git a/package.json b/package.json index cf9293ae3c..3664739990 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "express": "^4.13.1", "express-session": "^1.11.3", "forever": "~0.14.2", + "generate-password": "^1.1.1", "glob": "^5.0.13", "grunt": "0.4.5", "grunt-cli": "~0.1.13",