bd597db829
- This is part of the quest to separate the frontend and server & get rid of all the places where there are cross-requires - At the moment the settings cache is one big shared cache used by the frontend and server liberally - This change doesn't really solve the fundamental problems, as we still depend on events, and requires from inside frontend - However it allows us to control the misuse slightly better by getting rid of restricted requires and turning on that eslint ruleset
128 lines
4.3 KiB
JavaScript
128 lines
4.3 KiB
JavaScript
const _ = require('lodash');
|
|
|
|
const validator = require('@tryghost/validator');
|
|
|
|
const tpl = require('@tryghost/tpl');
|
|
const settingsCache = require('../../shared/settings-cache');
|
|
const urlUtils = require('../../shared/url-utils');
|
|
|
|
const messages = {
|
|
passwordDoesNotComplyLength: 'Your password must be at least {minLength} characters long.',
|
|
passwordDoesNotComplySecurity: 'Sorry, you cannot use an insecure password.'
|
|
};
|
|
|
|
/**
|
|
* 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) {
|
|
const chars = {};
|
|
let allowedOccurancy;
|
|
let 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 valid;
|
|
}
|
|
|
|
/**
|
|
* 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}`
|
|
*/
|
|
function validatePassword(password, email, blogTitle) {
|
|
const validationResult = {isValid: true};
|
|
const disallowedPasswords = ['password', 'ghost', 'passw0rd'];
|
|
let blogUrl = urlUtils.urlFor('home', true);
|
|
|
|
const 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 = tpl(messages.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 = tpl(messages.passwordDoesNotComplySecurity);
|
|
}
|
|
|
|
return validationResult;
|
|
}
|
|
|
|
module.exports = validatePassword;
|