From 40f511727bf1f4cc060751a157c332526c359c06 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Tim=20D=C3=BCsterhus?= Date: Thu, 16 Jul 2020 14:09:58 +0200 Subject: [PATCH] Add PasswordStrength.js This will need some UI polishing. see #3378 --- .../Core/Ui/User/PasswordStrength.js | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 wcfsetup/install/files/js/WoltLabSuite/Core/Ui/User/PasswordStrength.js diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/User/PasswordStrength.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/User/PasswordStrength.js new file mode 100644 index 0000000000..76887c647b --- /dev/null +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/User/PasswordStrength.js @@ -0,0 +1,96 @@ +/** + * Adds a password strength meter to a password input and exposes + * zxcbn's verdict as sibling input. + * + * @author Tim Duesterhus + * @copyright 2001-2020 WoltLab GmbH + * @license GNU Lesser General Public License + * @module WoltLabSuite/Core/Ui/User/PasswordStrength + */ +define(['zxcvbn', 'Core'], function(zxcvbn, Core) { + "use strict"; + + var STATIC_DICTIONARY = []; + if (elBySel('meta[property="og:site_name"]')) { + STATIC_DICTIONARY.push(elBySel('meta[property="og:site_name"]').getAttribute('content')); + } + + function createMeter(options) { + var meter = elCreate('meter'); + Object.keys(options).forEach(function (key) { + meter[key] = options[key]; + }); + return meter; + } + + function flatMap(array, callback) { + return array.map(callback).reduce(function (carry, item) { + return carry.concat(item); + }, []) + } + + function splitIntoWords(value) { + return [].concat( + value, + value.split(/\W+/) + ); + } + + /** + * @constructor + */ + function PasswordStrength(input, options) { this.init(input, options); } + PasswordStrength.prototype = { + /** + * @param {object} options + */ + init: function(input, options) { + this._input = input; + + this._options = Core.extend({ + relatedInputs: [], + staticDictionary: [], + }, options); + + this._meter = createMeter({ + min: 0, + max: 4, + low: 2, + high: 3, + optimum: 4 + }); + this._input.parentNode.insertBefore(this._meter, this._input.nextSibling); + this._verdictResult = elCreate('input'); + this._verdictResult.type = 'hidden'; + this._verdictResult.name = this._input.name + '_passwordStrengthVerdict'; + this._input.parentNode.insertBefore(this._verdictResult, this._input); + + this._input.addEventListener('input', this._evalute.bind(this)); + this._options.relatedInputs.forEach(function (input) { + input.addEventListener('input', this._evalute.bind(this)); + }.bind(this)); + + this._evalute(); + }, + + _evalute: function() { + var dictionary = flatMap(STATIC_DICTIONARY.concat( + this._options.staticDictionary, + this._options.relatedInputs.map(function (input) { + return input.value + }) + ), splitIntoWords).filter(function (value) { + return value.length > 0; + }); + + // To bound runtime latency for really long passwords, consider sending zxcvbn() only + // the first 100 characters or so of user input. + var verdict = zxcvbn(this._input.value.substr(0, 100), dictionary); + + this._meter.value = verdict.score; + this._verdictResult.value = JSON.stringify(verdict); + }, + }; + + return PasswordStrength; +}); -- 2.20.1