mirror of
https://github.com/TryGhost/Ghost.git
synced 2023-12-13 21:00:40 +01:00
✨ Improved password validation rules (#9171)
refs #9150 - Moves the password length fn from `models/user` to `data/validation` where the other validator functions live. - Added password validation rules. Password rules added: - Disallow obviously bad passwords: '1234567890', 'qwertyuiop', 'asdfghjkl;' and 'asdfghjklm' for example - Disallow passwords that contain the words 'password' or 'ghost' - Disallow passwords that match the user's email address - Disallow passwords that match the blog domain or blog title - Disallow passwords that include 50% or more of the same characters: 'aaaaaaaaaa', '1111111111' and 'ababababab' for example. - Password validation returns an `Object` now, that includes an `isValid` and `message` property to differentiate between the two error messages (password too short or password insecure). - Use a catch predicate in `api/authentication` on `passwordReset`, so the correct `ValidationError` will be thrown during the password reset flow rather then an `UnauthorizedError`. - When in setup flow, the blog title is not available yet from `settingsCache`. We therefore supply it from the received form data in the user model `setup` method to have it accessible for the validation.
This commit is contained in:
parent
05729d2f29
commit
c8cbbc4eb6
|
@ -321,8 +321,14 @@ authentication = {
|
||||||
updatedUser.set('status', 'active');
|
updatedUser.set('status', 'active');
|
||||||
return updatedUser.save(options);
|
return updatedUser.save(options);
|
||||||
})
|
})
|
||||||
|
.catch(errors.ValidationError, function (err) {
|
||||||
|
return Promise.reject(err);
|
||||||
|
})
|
||||||
.catch(function (err) {
|
.catch(function (err) {
|
||||||
throw new errors.UnauthorizedError({err: err});
|
if (errors.utils.isIgnitionError(err)) {
|
||||||
|
return Promise.reject(err);
|
||||||
|
}
|
||||||
|
return Promise.reject(new errors.UnauthorizedError({err: err}));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,10 @@ var schema = require('../schema').tables,
|
||||||
Promise = require('bluebird'),
|
Promise = require('bluebird'),
|
||||||
errors = require('../../errors'),
|
errors = require('../../errors'),
|
||||||
i18n = require('../../i18n'),
|
i18n = require('../../i18n'),
|
||||||
|
settingsCache = require('../../settings/cache'),
|
||||||
|
utils = require('../../utils/url'),
|
||||||
|
|
||||||
|
validatePassword,
|
||||||
validateSchema,
|
validateSchema,
|
||||||
validateSettings,
|
validateSettings,
|
||||||
validate;
|
validate;
|
||||||
|
@ -15,6 +18,41 @@ function assertString(input) {
|
||||||
assert(typeof input === 'string', 'Validator js validates strings only');
|
assert(typeof input === 'string', 'Validator js validates strings only');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Counts repeated characters in a string. When 50% or more characters are the same,
|
||||||
|
* we return false and therefore invalidate the string.
|
||||||
|
* @param {String} stringToTest The password string to check.
|
||||||
|
* @return {Boolean}
|
||||||
|
*/
|
||||||
|
function characterOccurance(stringToTest) {
|
||||||
|
var chars = {},
|
||||||
|
allowedOccurancy,
|
||||||
|
valid = true;
|
||||||
|
|
||||||
|
stringToTest = _.toString(stringToTest);
|
||||||
|
allowedOccurancy = stringToTest.length / 2;
|
||||||
|
|
||||||
|
// Loop through string and accumulate character counts
|
||||||
|
_.each(stringToTest, function (char) {
|
||||||
|
if (!chars[char]) {
|
||||||
|
chars[char] = 1;
|
||||||
|
} else {
|
||||||
|
chars[char] += 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// check if any of the accumulated chars exceed the allowed occurancy
|
||||||
|
// of 50% of the words' length.
|
||||||
|
_.forIn(chars, function (charCount) {
|
||||||
|
if (charCount >= allowedOccurancy) {
|
||||||
|
valid = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return valid;
|
||||||
|
}
|
||||||
|
|
||||||
// extends has been removed in validator >= 5.0.0, need to monkey-patch it back in
|
// extends has been removed in validator >= 5.0.0, need to monkey-patch it back in
|
||||||
validator.extend = function (name, fn) {
|
validator.extend = function (name, fn) {
|
||||||
validator[name] = function () {
|
validator[name] = function () {
|
||||||
|
@ -45,6 +83,84 @@ validator.extend('isSlug', function isSlug(str) {
|
||||||
return validator.matches(str, /^[a-z0-9\-_]+$/);
|
return validator.matches(str, /^[a-z0-9\-_]+$/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validation against simple password rules
|
||||||
|
* Returns false when validation fails and true for a valid password
|
||||||
|
* @param {String} password The password string to check.
|
||||||
|
* @param {String} email The users email address to validate agains password.
|
||||||
|
* @param {String} blogTitle Optional blogTitle value, when blog title is not set yet, e. g. in setup process.
|
||||||
|
* @return {Object} example for returned validation Object:
|
||||||
|
* invalid password: `validationResult: {isValid: false, message: 'Sorry, you cannot use an insecure password.'}`
|
||||||
|
* valid password: `validationResult: {isValid: true}`
|
||||||
|
*/
|
||||||
|
validatePassword = function validatePassword(password, email, blogTitle) {
|
||||||
|
var validationResult = {isValid: true},
|
||||||
|
disallowedPasswords = ['password', 'ghost', 'passw0rd'],
|
||||||
|
blogUrl = utils.urlFor('home', true),
|
||||||
|
badPasswords = [
|
||||||
|
'1234567890',
|
||||||
|
'qwertyuiop',
|
||||||
|
'qwertzuiop',
|
||||||
|
'asdfghjkl;',
|
||||||
|
'abcdefghij',
|
||||||
|
'0987654321',
|
||||||
|
'1q2w3e4r5t',
|
||||||
|
'12345asdfg'
|
||||||
|
];
|
||||||
|
|
||||||
|
blogTitle = blogTitle ? blogTitle : settingsCache.get('title');
|
||||||
|
blogUrl = blogUrl.replace(/^http(s?):\/\//, '');
|
||||||
|
|
||||||
|
// password must be longer than 10 characters
|
||||||
|
if (!validator.isLength(password, 10)) {
|
||||||
|
validationResult.isValid = false;
|
||||||
|
validationResult.message = i18n.t('errors.models.user.passwordDoesNotComplyLength', {minLength: 10});
|
||||||
|
|
||||||
|
return validationResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
// dissallow password from badPasswords list (e. g. '1234567890')
|
||||||
|
_.each(badPasswords, function (badPassword) {
|
||||||
|
if (badPassword === password) {
|
||||||
|
validationResult.isValid = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// password must not match with users' email
|
||||||
|
if (email && email.toLowerCase() === password.toLowerCase()) {
|
||||||
|
validationResult.isValid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// password must not contain the words 'ghost', 'password', or 'passw0rd'
|
||||||
|
_.each(disallowedPasswords, function (disallowedPassword) {
|
||||||
|
if (password.toLowerCase().indexOf(disallowedPassword) >= 0) {
|
||||||
|
validationResult.isValid = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// password must not match with blog title
|
||||||
|
if (blogTitle && blogTitle.toLowerCase() === password.toLowerCase()) {
|
||||||
|
validationResult.isValid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// password must not match with blog URL (without protocol, with or without trailing slash)
|
||||||
|
if (blogUrl && (blogUrl.toLowerCase() === password.toLowerCase() || blogUrl.toLowerCase().replace(/\/$/, '') === password.toLowerCase())) {
|
||||||
|
validationResult.isValid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// dissallow passwords where 50% or more of characters are the same
|
||||||
|
if (!characterOccurance(password)) {
|
||||||
|
validationResult.isValid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generic error message for the rules where no dedicated error massage is set
|
||||||
|
if (!validationResult.isValid && !validationResult.message) {
|
||||||
|
validationResult.message = i18n.t('errors.models.user.passwordDoesNotComplySecurity');
|
||||||
|
}
|
||||||
|
|
||||||
|
return validationResult;
|
||||||
|
};
|
||||||
|
|
||||||
// Validation against schema attributes
|
// Validation against schema attributes
|
||||||
// values are checked against the validation objects from schema.js
|
// values are checked against the validation objects from schema.js
|
||||||
validateSchema = function validateSchema(tableName, model) {
|
validateSchema = function validateSchema(tableName, model) {
|
||||||
|
@ -174,6 +290,7 @@ validate = function validate(value, key, validations) {
|
||||||
module.exports = {
|
module.exports = {
|
||||||
validate: validate,
|
validate: validate,
|
||||||
validator: validator,
|
validator: validator,
|
||||||
|
validatePassword: validatePassword,
|
||||||
validateSchema: validateSchema,
|
validateSchema: validateSchema,
|
||||||
validateSettings: validateSettings
|
validateSettings: validateSettings
|
||||||
};
|
};
|
||||||
|
|
|
@ -27,10 +27,6 @@ var _ = require('lodash'),
|
||||||
User,
|
User,
|
||||||
Users;
|
Users;
|
||||||
|
|
||||||
function validatePasswordLength(password) {
|
|
||||||
return validator.isLength(password, 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* generate a random salt and then hash the password with that salt
|
* generate a random salt and then hash the password with that salt
|
||||||
*/
|
*/
|
||||||
|
@ -106,7 +102,8 @@ User = ghostBookshelf.Model.extend({
|
||||||
*/
|
*/
|
||||||
onSaving: function onSaving(newPage, attr, options) {
|
onSaving: function onSaving(newPage, attr, options) {
|
||||||
var self = this,
|
var self = this,
|
||||||
tasks = [];
|
tasks = [],
|
||||||
|
passwordValidation = {};
|
||||||
|
|
||||||
ghostBookshelf.Model.prototype.onSaving.apply(this, arguments);
|
ghostBookshelf.Model.prototype.onSaving.apply(this, arguments);
|
||||||
|
|
||||||
|
@ -169,11 +166,13 @@ User = ghostBookshelf.Model.extend({
|
||||||
if (this.get('status') !== 'inactive') {
|
if (this.get('status') !== 'inactive') {
|
||||||
this.set('status', 'locked');
|
this.set('status', 'locked');
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
|
// CASE: we're not importing data, run the validations
|
||||||
|
passwordValidation = validation.validatePassword(this.get('password'), this.get('email'));
|
||||||
|
|
||||||
// don't ever validate passwords when importing
|
if (!passwordValidation.isValid) {
|
||||||
if (!options.importing && !validatePasswordLength(this.get('password'))) {
|
return Promise.reject(new errors.ValidationError({message: passwordValidation.message}));
|
||||||
return Promise.reject(new errors.ValidationError({message: i18n.t('errors.models.user.passwordDoesNotComplyLength', {minLength: 10})}));
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.hashPassword = (function hashPassword() {
|
tasks.hashPassword = (function hashPassword() {
|
||||||
|
@ -563,10 +562,13 @@ User = ghostBookshelf.Model.extend({
|
||||||
*/
|
*/
|
||||||
setup: function setup(data, options) {
|
setup: function setup(data, options) {
|
||||||
var self = this,
|
var self = this,
|
||||||
userData = this.filterData(data);
|
userData = this.filterData(data),
|
||||||
|
passwordValidation = {};
|
||||||
|
|
||||||
if (!validatePasswordLength(userData.password)) {
|
passwordValidation = validation.validatePassword(userData.password, userData.email, data.blogTitle);
|
||||||
return Promise.reject(new errors.ValidationError({message: i18n.t('errors.models.user.passwordDoesNotComplyLength', {minLength: 10})}));
|
|
||||||
|
if (!passwordValidation.isValid) {
|
||||||
|
return Promise.reject(new errors.ValidationError({message: passwordValidation.message}));
|
||||||
}
|
}
|
||||||
|
|
||||||
options = this.filterOptions(options, 'setup');
|
options = this.filterOptions(options, 'setup');
|
||||||
|
|
|
@ -229,6 +229,7 @@
|
||||||
"onlyOneRolePerUserSupported": "Only one role per user is supported at the moment.",
|
"onlyOneRolePerUserSupported": "Only one role per user is supported at the moment.",
|
||||||
"methodDoesNotSupportOwnerRole": "This method does not support assigning the owner role",
|
"methodDoesNotSupportOwnerRole": "This method does not support assigning the owner role",
|
||||||
"passwordDoesNotComplyLength": "Your password must be at least {minLength} characters long.",
|
"passwordDoesNotComplyLength": "Your password must be at least {minLength} characters long.",
|
||||||
|
"passwordDoesNotComplySecurity": "Sorry, you cannot use an insecure password.",
|
||||||
"notEnoughPermission": "You do not have permission to perform this action",
|
"notEnoughPermission": "You do not have permission to perform this action",
|
||||||
"noUserWithEnteredEmailAddr": "There is no user with that email address.",
|
"noUserWithEnteredEmailAddr": "There is no user with that email address.",
|
||||||
"userIsInactive": "The user with that email address is inactive.",
|
"userIsInactive": "The user with that email address is inactive.",
|
||||||
|
|
|
@ -231,8 +231,8 @@ describe('Authentication API', function () {
|
||||||
.send({
|
.send({
|
||||||
passwordreset: [{
|
passwordreset: [{
|
||||||
token: token,
|
token: token,
|
||||||
newPassword: 'abcdefghij',
|
newPassword: 'thisissupersafe',
|
||||||
ne2Password: 'abcdefghij'
|
ne2Password: 'thisissupersafe'
|
||||||
}]
|
}]
|
||||||
})
|
})
|
||||||
.expect('Content-Type', /json/)
|
.expect('Content-Type', /json/)
|
||||||
|
|
|
@ -66,7 +66,7 @@ describe('Authentication API', function () {
|
||||||
var setupData = {
|
var setupData = {
|
||||||
name: 'test user',
|
name: 'test user',
|
||||||
email: 'test@example.com',
|
email: 'test@example.com',
|
||||||
password: 'areallygoodpassword',
|
password: 'thisissupersafe',
|
||||||
blogTitle: 'a test blog'
|
blogTitle: 'a test blog'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -103,7 +103,7 @@ describe('Authentication API', function () {
|
||||||
var setupData = {
|
var setupData = {
|
||||||
name: 'test user',
|
name: 'test user',
|
||||||
email: 'test@example.com',
|
email: 'test@example.com',
|
||||||
password: 'areallygoodpassword',
|
password: 'thisissupersafe',
|
||||||
blogTitle: 'a test blog'
|
blogTitle: 'a test blog'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -128,7 +128,7 @@ describe('Authentication API', function () {
|
||||||
var setupData = {
|
var setupData = {
|
||||||
name: 'test user',
|
name: 'test user',
|
||||||
email: 'test@example.com',
|
email: 'test@example.com',
|
||||||
password: 'areallygoodpassword'
|
password: 'thisissupersafe'
|
||||||
};
|
};
|
||||||
|
|
||||||
AuthAPI.setup({setup: [setupData]}).then(function (result) {
|
AuthAPI.setup({setup: [setupData]}).then(function (result) {
|
||||||
|
@ -223,7 +223,7 @@ describe('Authentication API', function () {
|
||||||
var setupData = {
|
var setupData = {
|
||||||
name: 'test user',
|
name: 'test user',
|
||||||
email: 'test@example.com',
|
email: 'test@example.com',
|
||||||
password: 'areallygoodpassword',
|
password: 'thisissupersafe',
|
||||||
blogTitle: 'a test blog'
|
blogTitle: 'a test blog'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -273,7 +273,7 @@ describe('Authentication API', function () {
|
||||||
token: invite.get('token'),
|
token: invite.get('token'),
|
||||||
email: invite.get('email'),
|
email: invite.get('email'),
|
||||||
name: invite.get('email'),
|
name: invite.get('email'),
|
||||||
password: 'eightcharacterslong'
|
password: 'tencharacterslong'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
@ -313,7 +313,7 @@ describe('Authentication API', function () {
|
||||||
token: invite.get('token'),
|
token: invite.get('token'),
|
||||||
email: invite.get('email'),
|
email: invite.get('email'),
|
||||||
name: invite.get('email'),
|
name: invite.get('email'),
|
||||||
password: 'eightcharacterslong'
|
password: 'tencharacterslong'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
@ -409,7 +409,7 @@ describe('Authentication API', function () {
|
||||||
var user = {
|
var user = {
|
||||||
name: 'uninvited user',
|
name: 'uninvited user',
|
||||||
email: 'notinvited@example.com',
|
email: 'notinvited@example.com',
|
||||||
password: '1234567890',
|
password: 'thisissupersafe',
|
||||||
status: 'active'
|
status: 'active'
|
||||||
},
|
},
|
||||||
options = {
|
options = {
|
||||||
|
@ -507,7 +507,7 @@ describe('Authentication API', function () {
|
||||||
var setupData = {
|
var setupData = {
|
||||||
name: 'test user',
|
name: 'test user',
|
||||||
email: 'test@example.com',
|
email: 'test@example.com',
|
||||||
password: 'areallygoodpassword',
|
password: 'thisissupersafe',
|
||||||
blogTitle: 'a test blog'
|
blogTitle: 'a test blog'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -540,7 +540,7 @@ describe('Authentication API', function () {
|
||||||
var setupData = {
|
var setupData = {
|
||||||
name: 'test user',
|
name: 'test user',
|
||||||
email: 'test@example.com',
|
email: 'test@example.com',
|
||||||
password: 'areallygoodpassword',
|
password: 'thisissupersafe',
|
||||||
blogTitle: 'a test blog'
|
blogTitle: 'a test blog'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -573,7 +573,7 @@ describe('Authentication API', function () {
|
||||||
var setupData = {
|
var setupData = {
|
||||||
name: 'test user',
|
name: 'test user',
|
||||||
email: 'test@example.com',
|
email: 'test@example.com',
|
||||||
password: 'areallygoodpassword',
|
password: 'thisissupersafe',
|
||||||
blogTitle: 'a test blog'
|
blogTitle: 'a test blog'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -501,13 +501,13 @@ describe('Users API', function () {
|
||||||
{
|
{
|
||||||
users: [{
|
users: [{
|
||||||
name: 'newname',
|
name: 'newname',
|
||||||
password: 'newpassword'
|
password: 'thisissupersafe'
|
||||||
}]
|
}]
|
||||||
}, _.extend({}, context.author, {id: userIdFor.author})
|
}, _.extend({}, context.author, {id: userIdFor.author})
|
||||||
).then(function () {
|
).then(function () {
|
||||||
return models.User.findOne({id: userIdFor.author}).then(function (response) {
|
return models.User.findOne({id: userIdFor.author}).then(function (response) {
|
||||||
response.get('name').should.eql('newname');
|
response.get('name').should.eql('newname');
|
||||||
response.get('password').should.not.eql('newpassword');
|
response.get('password').should.not.eql('thisissupersafe');
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
}).catch(done);
|
}).catch(done);
|
||||||
|
|
|
@ -123,7 +123,7 @@ describe('User Model', function run() {
|
||||||
|
|
||||||
// avoid side-effects!
|
// avoid side-effects!
|
||||||
userData = _.cloneDeep(userData);
|
userData = _.cloneDeep(userData);
|
||||||
userData.password = 1234567890;
|
userData.password = 109674836589;
|
||||||
|
|
||||||
// mocha supports promises
|
// mocha supports promises
|
||||||
return UserModel.add(userData, context).then(function (createdUser) {
|
return UserModel.add(userData, context).then(function (createdUser) {
|
||||||
|
@ -560,17 +560,115 @@ describe('User Model', function run() {
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
describe('success', function () {
|
it('too short password', function (done) {
|
||||||
it('can change password', function (done) {
|
UserModel.changePassword({
|
||||||
|
newPassword: '12345678',
|
||||||
|
ne2Password: '12345678',
|
||||||
|
oldPassword: 'Sl1m3rson99',
|
||||||
|
user_id: testUtils.DataGenerator.Content.users[0].id
|
||||||
|
}, testUtils.context.owner).then(function () {
|
||||||
|
done(new Error('expected error!'));
|
||||||
|
}).catch(function (err) {
|
||||||
|
(err instanceof errors.ValidationError).should.eql(true);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('very bad password', function (done) {
|
||||||
UserModel.changePassword({
|
UserModel.changePassword({
|
||||||
newPassword: '1234567890',
|
newPassword: '1234567890',
|
||||||
ne2Password: '1234567890',
|
ne2Password: '1234567890',
|
||||||
oldPassword: 'Sl1m3rson99',
|
oldPassword: 'Sl1m3rson99',
|
||||||
user_id: testUtils.DataGenerator.Content.users[0].id
|
user_id: testUtils.DataGenerator.Content.users[0].id
|
||||||
|
}, testUtils.context.owner).then(function () {
|
||||||
|
done(new Error('expected error!'));
|
||||||
|
}).catch(function (err) {
|
||||||
|
(err instanceof errors.ValidationError).should.eql(true);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('password matches users email adress', function (done) {
|
||||||
|
UserModel.changePassword({
|
||||||
|
newPassword: 'jbloggs@example.com',
|
||||||
|
ne2Password: 'jbloggs@example.com',
|
||||||
|
oldPassword: 'Sl1m3rson99',
|
||||||
|
user_id: testUtils.DataGenerator.Content.users[0].id
|
||||||
|
}, testUtils.context.owner).then(function () {
|
||||||
|
done(new Error('expected error!'));
|
||||||
|
}).catch(function (err) {
|
||||||
|
(err instanceof errors.ValidationError).should.eql(true);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('password contains words "ghost" or "password"', function (done) {
|
||||||
|
UserModel.changePassword({
|
||||||
|
newPassword: 'onepassword',
|
||||||
|
ne2Password: 'onepassword',
|
||||||
|
oldPassword: 'Sl1m3rson99',
|
||||||
|
user_id: testUtils.DataGenerator.Content.users[0].id
|
||||||
|
}, testUtils.context.owner).then(function () {
|
||||||
|
done(new Error('expected error!'));
|
||||||
|
}).catch(function (err) {
|
||||||
|
(err instanceof errors.ValidationError).should.eql(true);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('password matches blog URL', function (done) {
|
||||||
|
UserModel.changePassword({
|
||||||
|
newPassword: '127.0.0.1:2369',
|
||||||
|
ne2Password: '127.0.0.1:2369',
|
||||||
|
oldPassword: 'Sl1m3rson99',
|
||||||
|
user_id: testUtils.DataGenerator.Content.users[0].id
|
||||||
|
}, testUtils.context.owner).then(function () {
|
||||||
|
done(new Error('expected error!'));
|
||||||
|
}).catch(function (err) {
|
||||||
|
(err instanceof errors.ValidationError).should.eql(true);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('password contains repeating chars', function (done) {
|
||||||
|
UserModel.changePassword({
|
||||||
|
newPassword: 'cdcdcdcdcd',
|
||||||
|
ne2Password: 'cdcdcdcdcd',
|
||||||
|
oldPassword: 'Sl1m3rson99',
|
||||||
|
user_id: testUtils.DataGenerator.Content.users[0].id
|
||||||
|
}, testUtils.context.owner).then(function () {
|
||||||
|
done(new Error('expected error!'));
|
||||||
|
}).catch(function (err) {
|
||||||
|
(err instanceof errors.ValidationError).should.eql(true);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('password contains repeating numbers', function (done) {
|
||||||
|
UserModel.changePassword({
|
||||||
|
newPassword: '1231111111',
|
||||||
|
ne2Password: '1231111111',
|
||||||
|
oldPassword: 'Sl1m3rson99',
|
||||||
|
user_id: testUtils.DataGenerator.Content.users[0].id
|
||||||
|
}, testUtils.context.owner).then(function () {
|
||||||
|
done(new Error('expected error!'));
|
||||||
|
}).catch(function (err) {
|
||||||
|
(err instanceof errors.ValidationError).should.eql(true);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('success', function () {
|
||||||
|
it('can change password', function (done) {
|
||||||
|
UserModel.changePassword({
|
||||||
|
newPassword: 'thisissupersafe',
|
||||||
|
ne2Password: 'thisissupersafe',
|
||||||
|
oldPassword: 'Sl1m3rson99',
|
||||||
|
user_id: testUtils.DataGenerator.Content.users[0].id
|
||||||
}, testUtils.context.owner).then(function (user) {
|
}, testUtils.context.owner).then(function (user) {
|
||||||
user.get('password').should.not.eql('1234567890');
|
user.get('password').should.not.eql('thisissupersafe');
|
||||||
done();
|
done();
|
||||||
}).catch(done);
|
}).catch(done);
|
||||||
});
|
});
|
||||||
|
@ -584,7 +682,7 @@ describe('User Model', function run() {
|
||||||
var userData = {
|
var userData = {
|
||||||
name: 'Max Mustermann',
|
name: 'Max Mustermann',
|
||||||
email: 'test@ghost.org',
|
email: 'test@ghost.org',
|
||||||
password: '1234567890'
|
password: 'thisissupersafe'
|
||||||
};
|
};
|
||||||
|
|
||||||
UserModel.setup(userData, {id: 1})
|
UserModel.setup(userData, {id: 1})
|
||||||
|
|
|
@ -12,6 +12,7 @@ describe('Validation', function () {
|
||||||
);
|
);
|
||||||
|
|
||||||
validation.validate.should.be.a.Function();
|
validation.validate.should.be.a.Function();
|
||||||
|
validation.validatePassword.should.be.a.Function();
|
||||||
validation.validateSchema.should.be.a.Function();
|
validation.validateSchema.should.be.a.Function();
|
||||||
validation.validateSettings.should.be.a.Function();
|
validation.validateSettings.should.be.a.Function();
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue