import Mixin from '@ember/object/mixin'; import validator from 'validator'; const BAD_PASSWORDS = [ '1234567890', 'qwertyuiop', 'qwertzuiop', 'asdfghjkl;', 'abcdefghij', '0987654321', '1q2w3e4r5t', '12345asdfg' ]; const DISALLOWED_PASSWORDS = ['ghost', 'password', 'passw0rd']; export default Mixin.create({ /** * Counts repeated characters if 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} */ _characterOccurance(stringToTest) { let chars = {}; let allowedOccurancy; let valid = true; allowedOccurancy = stringToTest.length / 2; // Loop through string and accumulate character counts for (let i = 0; i < stringToTest.length; i += 1) { if (!chars[stringToTest[i]]) { chars[stringToTest[i]] = 1; } else { chars[stringToTest[i]] += 1; } } // check if any of the accumulated chars exceed the allowed occurancy // of 50% of the words' length. for (let charCount in chars) { if (chars[charCount] >= allowedOccurancy) { valid = false; return valid; } } return valid; }, passwordValidation(model, password, errorTarget) { let blogUrl = model.config?.blogUrl || window.location.host; let blogTitle = model.blogTitle || model.config?.blogTitle; let blogUrlWithSlash; // the password that needs to be validated can differ from the password in the // passed model, e. g. for password changes or reset. password = password || model.password; errorTarget = errorTarget || 'password'; blogUrl = blogUrl.replace(/^http(s?):\/\//, ''); blogUrlWithSlash = blogUrl.match(/\/$/) ? blogUrl : `${blogUrl}/`; blogTitle = blogTitle ? blogTitle.trim().toLowerCase() : blogTitle; // password must be longer than 10 characters if (!validator.isLength(password || '', 10)) { model.errors.add(errorTarget, 'Password must be at least 10 characters long.'); return this.invalidate(); } password = password.toString(); // dissallow password from badPasswords list (e. g. '1234567890') BAD_PASSWORDS.forEach((badPassword) => { if (badPassword === password) { model.errors.add(errorTarget, 'Sorry, you cannot use an insecure password.'); this.invalidate(); } }); // password must not match with users' email if (password.toLowerCase() === model.email.toLowerCase()) { model.errors.add(errorTarget, 'Sorry, you cannot use an insecure password.'); this.invalidate(); } // password must not contain the words 'ghost', 'password', or 'passw0rd' DISALLOWED_PASSWORDS.forEach((disallowedPassword) => { if (password.toLowerCase().indexOf(disallowedPassword) >= 0) { model.errors.add(errorTarget, 'Sorry, you cannot use an insecure password.'); this.invalidate(); } }); // password must not match with blog title if (password.toLowerCase() === blogTitle) { model.errors.add(errorTarget, 'Sorry, you cannot use an insecure password.'); this.invalidate(); } // password must not match with blog URL (without protocol, with or without trailing slash) if (password.toLowerCase() === blogUrl || password.toLowerCase() === blogUrlWithSlash) { model.errors.add(errorTarget, 'Sorry, you cannot use an insecure password.'); this.invalidate(); } // dissallow passwords where 50% or more of characters are the same if (!this._characterOccurance(password)) { model.errors.add(errorTarget, 'Sorry, you cannot use an insecure password.'); this.invalidate(); } } });