6 (function ($, Drupal, drupalSettings) {
11 * Attach handlers to evaluate the strength of any password fields and to
12 * check that its confirmation is correct.
14 * @type {Drupal~behavior}
16 * @prop {Drupal~behaviorAttach} attach
17 * Attaches password strength indicator and other relevant validation to
20 Drupal.behaviors.password = {
21 attach: function (context, settings) {
22 var $passwordInput = $(context).find('input.js-password-field').once('password');
24 if ($passwordInput.length) {
25 var translate = settings.password;
27 var $passwordInputParent = $passwordInput.parent();
28 var $passwordInputParentWrapper = $passwordInputParent.parent();
29 var $passwordSuggestions;
31 // Add identifying class to password element parent.
32 $passwordInputParent.addClass('password-parent');
34 // Add the password confirmation layer.
35 $passwordInputParentWrapper
36 .find('input.js-password-confirm')
38 .append('<div aria-live="polite" aria-atomic="true" class="password-confirm js-password-confirm">' + translate.confirmTitle + ' <span></span></div>')
39 .addClass('confirm-parent');
41 var $confirmInput = $passwordInputParentWrapper.find('input.js-password-confirm');
42 var $confirmResult = $passwordInputParentWrapper.find('div.js-password-confirm');
43 var $confirmChild = $confirmResult.find('span');
45 // If the password strength indicator is enabled, add its markup.
46 if (settings.password.showStrengthIndicator) {
47 var passwordMeter = '<div class="password-strength"><div class="password-strength__meter"><div class="password-strength__indicator js-password-strength__indicator"></div></div><div aria-live="polite" aria-atomic="true" class="password-strength__title">' + translate.strengthTitle + ' <span class="password-strength__text js-password-strength__text"></span></div></div>';
48 $confirmInput.parent().after('<div class="password-suggestions description"></div>');
49 $passwordInputParent.append(passwordMeter);
50 $passwordSuggestions = $passwordInputParentWrapper.find('div.password-suggestions').hide();
53 // Check that password and confirmation inputs match.
54 var passwordCheckMatch = function (confirmInputVal) {
55 var success = $passwordInput.val() === confirmInputVal;
56 var confirmClass = success ? 'ok' : 'error';
58 // Fill in the success message and set the class accordingly.
59 $confirmChild.html(translate['confirm' + (success ? 'Success' : 'Failure')])
60 .removeClass('ok error').addClass(confirmClass);
63 // Check the password strength.
64 var passwordCheck = function () {
65 if (settings.password.showStrengthIndicator) {
66 // Evaluate the password strength.
67 var result = Drupal.evaluatePasswordStrength($passwordInput.val(), settings.password);
69 // Update the suggestions for how to improve the password.
70 if ($passwordSuggestions.html() !== result.message) {
71 $passwordSuggestions.html(result.message);
74 // Only show the description box if a weakness exists in the
76 $passwordSuggestions.toggle(result.strength !== 100);
78 // Adjust the length of the strength indicator.
79 $passwordInputParent.find('.js-password-strength__indicator')
80 .css('width', result.strength + '%')
81 .removeClass('is-weak is-fair is-good is-strong')
82 .addClass(result.indicatorClass);
84 // Update the strength indication text.
85 $passwordInputParent.find('.js-password-strength__text').html(result.indicatorText);
88 // Check the value in the confirm input and show results.
89 if ($confirmInput.val()) {
90 passwordCheckMatch($confirmInput.val());
91 $confirmResult.css({visibility: 'visible'});
94 $confirmResult.css({visibility: 'hidden'});
98 // Monitor input events.
99 $passwordInput.on('input', passwordCheck);
100 $confirmInput.on('input', passwordCheck);
106 * Evaluate the strength of a user's password.
108 * Returns the estimated strength and the relevant output message.
110 * @param {string} password
111 * The password to evaluate.
112 * @param {object} translate
113 * An object containing the text to display for each strength level.
116 * An object containing strength, message, indicatorText and indicatorClass.
118 Drupal.evaluatePasswordStrength = function (password, translate) {
119 password = password.trim();
126 var hasLowercase = /[a-z]/.test(password);
127 var hasUppercase = /[A-Z]/.test(password);
128 var hasNumbers = /[0-9]/.test(password);
129 var hasPunctuation = /[^a-zA-Z0-9]/.test(password);
131 // If there is a username edit box on the page, compare password to that,
132 // otherwise use value from the database.
133 var $usernameBox = $('input.username');
134 var username = ($usernameBox.length > 0) ? $usernameBox.val() : translate.username;
136 // Lose 5 points for every character less than 12, plus a 30 point penalty.
137 if (password.length < 12) {
138 msg.push(translate.tooShort);
139 strength -= ((12 - password.length) * 5) + 30;
144 msg.push(translate.addLowerCase);
148 msg.push(translate.addUpperCase);
152 msg.push(translate.addNumbers);
155 if (!hasPunctuation) {
156 msg.push(translate.addPunctuation);
160 // Apply penalty for each weakness (balanced against length penalty).
161 switch (weaknesses) {
179 // Check if password is the same as the username.
180 if (password !== '' && password.toLowerCase() === username.toLowerCase()) {
181 msg.push(translate.sameAsUsername);
182 // Passwords the same as username are always very weak.
186 // Based on the strength, work out what text should be shown by the
187 // password strength meter.
189 indicatorText = translate.weak;
190 indicatorClass = 'is-weak';
192 else if (strength < 70) {
193 indicatorText = translate.fair;
194 indicatorClass = 'is-fair';
196 else if (strength < 80) {
197 indicatorText = translate.good;
198 indicatorClass = 'is-good';
200 else if (strength <= 100) {
201 indicatorText = translate.strong;
202 indicatorClass = 'is-strong';
205 // Assemble the final message.
206 msg = translate.hasWeaknesses + '<ul><li>' + msg.join('</li><li>') + '</li></ul>';
211 indicatorText: indicatorText,
212 indicatorClass: indicatorClass
217 })(jQuery, Drupal, drupalSettings);