annotate core/modules/user/user.es6.js @ 19:fa3358dc1485 tip

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents 129ea1e6d783
children
rev   line source
Chris@0 1 /**
Chris@0 2 * @file
Chris@0 3 * User behaviors.
Chris@0 4 */
Chris@0 5
Chris@17 6 (function($, Drupal, drupalSettings) {
Chris@0 7 /**
Chris@0 8 * Attach handlers to evaluate the strength of any password fields and to
Chris@0 9 * check that its confirmation is correct.
Chris@0 10 *
Chris@0 11 * @type {Drupal~behavior}
Chris@0 12 *
Chris@0 13 * @prop {Drupal~behaviorAttach} attach
Chris@0 14 * Attaches password strength indicator and other relevant validation to
Chris@0 15 * password fields.
Chris@0 16 */
Chris@0 17 Drupal.behaviors.password = {
Chris@0 18 attach(context, settings) {
Chris@17 19 const $passwordInput = $(context)
Chris@17 20 .find('input.js-password-field')
Chris@17 21 .once('password');
Chris@0 22
Chris@0 23 if ($passwordInput.length) {
Chris@0 24 const translate = settings.password;
Chris@0 25
Chris@0 26 const $passwordInputParent = $passwordInput.parent();
Chris@0 27 const $passwordInputParentWrapper = $passwordInputParent.parent();
Chris@0 28 let $passwordSuggestions;
Chris@0 29
Chris@0 30 // Add identifying class to password element parent.
Chris@0 31 $passwordInputParent.addClass('password-parent');
Chris@0 32
Chris@0 33 // Add the password confirmation layer.
Chris@0 34 $passwordInputParentWrapper
Chris@0 35 .find('input.js-password-confirm')
Chris@0 36 .parent()
Chris@17 37 .append(
Chris@17 38 `<div aria-live="polite" aria-atomic="true" class="password-confirm js-password-confirm">${
Chris@17 39 translate.confirmTitle
Chris@17 40 } <span></span></div>`,
Chris@17 41 )
Chris@0 42 .addClass('confirm-parent');
Chris@0 43
Chris@17 44 const $confirmInput = $passwordInputParentWrapper.find(
Chris@17 45 'input.js-password-confirm',
Chris@17 46 );
Chris@17 47 const $confirmResult = $passwordInputParentWrapper.find(
Chris@17 48 'div.js-password-confirm',
Chris@17 49 );
Chris@0 50 const $confirmChild = $confirmResult.find('span');
Chris@0 51
Chris@0 52 // If the password strength indicator is enabled, add its markup.
Chris@0 53 if (settings.password.showStrengthIndicator) {
Chris@17 54 const 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">${
Chris@17 55 translate.strengthTitle
Chris@17 56 } <span class="password-strength__text js-password-strength__text"></span></div></div>`;
Chris@17 57 $confirmInput
Chris@17 58 .parent()
Chris@17 59 .after('<div class="password-suggestions description"></div>');
Chris@0 60 $passwordInputParent.append(passwordMeter);
Chris@17 61 $passwordSuggestions = $passwordInputParentWrapper
Chris@17 62 .find('div.password-suggestions')
Chris@17 63 .hide();
Chris@0 64 }
Chris@0 65
Chris@0 66 // Check that password and confirmation inputs match.
Chris@17 67 const passwordCheckMatch = function(confirmInputVal) {
Chris@0 68 const success = $passwordInput.val() === confirmInputVal;
Chris@0 69 const confirmClass = success ? 'ok' : 'error';
Chris@0 70
Chris@0 71 // Fill in the success message and set the class accordingly.
Chris@17 72 $confirmChild
Chris@17 73 .html(translate[`confirm${success ? 'Success' : 'Failure'}`])
Chris@17 74 .removeClass('ok error')
Chris@17 75 .addClass(confirmClass);
Chris@0 76 };
Chris@0 77
Chris@0 78 // Check the password strength.
Chris@17 79 const passwordCheck = function() {
Chris@0 80 if (settings.password.showStrengthIndicator) {
Chris@0 81 // Evaluate the password strength.
Chris@17 82 const result = Drupal.evaluatePasswordStrength(
Chris@17 83 $passwordInput.val(),
Chris@17 84 settings.password,
Chris@17 85 );
Chris@0 86
Chris@0 87 // Update the suggestions for how to improve the password.
Chris@0 88 if ($passwordSuggestions.html() !== result.message) {
Chris@0 89 $passwordSuggestions.html(result.message);
Chris@0 90 }
Chris@0 91
Chris@0 92 // Only show the description box if a weakness exists in the
Chris@0 93 // password.
Chris@0 94 $passwordSuggestions.toggle(result.strength !== 100);
Chris@0 95
Chris@0 96 // Adjust the length of the strength indicator.
Chris@17 97 $passwordInputParent
Chris@17 98 .find('.js-password-strength__indicator')
Chris@0 99 .css('width', `${result.strength}%`)
Chris@0 100 .removeClass('is-weak is-fair is-good is-strong')
Chris@0 101 .addClass(result.indicatorClass);
Chris@0 102
Chris@0 103 // Update the strength indication text.
Chris@17 104 $passwordInputParent
Chris@17 105 .find('.js-password-strength__text')
Chris@17 106 .html(result.indicatorText);
Chris@0 107 }
Chris@0 108
Chris@0 109 // Check the value in the confirm input and show results.
Chris@0 110 if ($confirmInput.val()) {
Chris@0 111 passwordCheckMatch($confirmInput.val());
Chris@0 112 $confirmResult.css({ visibility: 'visible' });
Chris@17 113 } else {
Chris@0 114 $confirmResult.css({ visibility: 'hidden' });
Chris@0 115 }
Chris@0 116 };
Chris@0 117
Chris@0 118 // Monitor input events.
Chris@0 119 $passwordInput.on('input', passwordCheck);
Chris@0 120 $confirmInput.on('input', passwordCheck);
Chris@0 121 }
Chris@0 122 },
Chris@0 123 };
Chris@0 124
Chris@0 125 /**
Chris@0 126 * Evaluate the strength of a user's password.
Chris@0 127 *
Chris@0 128 * Returns the estimated strength and the relevant output message.
Chris@0 129 *
Chris@0 130 * @param {string} password
Chris@0 131 * The password to evaluate.
Chris@0 132 * @param {object} translate
Chris@0 133 * An object containing the text to display for each strength level.
Chris@0 134 *
Chris@0 135 * @return {object}
Chris@0 136 * An object containing strength, message, indicatorText and indicatorClass.
Chris@0 137 */
Chris@17 138 Drupal.evaluatePasswordStrength = function(password, translate) {
Chris@0 139 password = password.trim();
Chris@0 140 let indicatorText;
Chris@0 141 let indicatorClass;
Chris@0 142 let weaknesses = 0;
Chris@0 143 let strength = 100;
Chris@0 144 let msg = [];
Chris@0 145
Chris@0 146 const hasLowercase = /[a-z]/.test(password);
Chris@0 147 const hasUppercase = /[A-Z]/.test(password);
Chris@0 148 const hasNumbers = /[0-9]/.test(password);
Chris@0 149 const hasPunctuation = /[^a-zA-Z0-9]/.test(password);
Chris@0 150
Chris@0 151 // If there is a username edit box on the page, compare password to that,
Chris@0 152 // otherwise use value from the database.
Chris@0 153 const $usernameBox = $('input.username');
Chris@17 154 const username =
Chris@17 155 $usernameBox.length > 0 ? $usernameBox.val() : translate.username;
Chris@0 156
Chris@0 157 // Lose 5 points for every character less than 12, plus a 30 point penalty.
Chris@0 158 if (password.length < 12) {
Chris@0 159 msg.push(translate.tooShort);
Chris@17 160 strength -= (12 - password.length) * 5 + 30;
Chris@0 161 }
Chris@0 162
Chris@0 163 // Count weaknesses.
Chris@0 164 if (!hasLowercase) {
Chris@0 165 msg.push(translate.addLowerCase);
Chris@0 166 weaknesses++;
Chris@0 167 }
Chris@0 168 if (!hasUppercase) {
Chris@0 169 msg.push(translate.addUpperCase);
Chris@0 170 weaknesses++;
Chris@0 171 }
Chris@0 172 if (!hasNumbers) {
Chris@0 173 msg.push(translate.addNumbers);
Chris@0 174 weaknesses++;
Chris@0 175 }
Chris@0 176 if (!hasPunctuation) {
Chris@0 177 msg.push(translate.addPunctuation);
Chris@0 178 weaknesses++;
Chris@0 179 }
Chris@0 180
Chris@0 181 // Apply penalty for each weakness (balanced against length penalty).
Chris@0 182 switch (weaknesses) {
Chris@0 183 case 1:
Chris@0 184 strength -= 12.5;
Chris@0 185 break;
Chris@0 186
Chris@0 187 case 2:
Chris@0 188 strength -= 25;
Chris@0 189 break;
Chris@0 190
Chris@0 191 case 3:
Chris@0 192 strength -= 40;
Chris@0 193 break;
Chris@0 194
Chris@0 195 case 4:
Chris@0 196 strength -= 40;
Chris@0 197 break;
Chris@0 198 }
Chris@0 199
Chris@0 200 // Check if password is the same as the username.
Chris@0 201 if (password !== '' && password.toLowerCase() === username.toLowerCase()) {
Chris@0 202 msg.push(translate.sameAsUsername);
Chris@0 203 // Passwords the same as username are always very weak.
Chris@0 204 strength = 5;
Chris@0 205 }
Chris@0 206
Chris@0 207 // Based on the strength, work out what text should be shown by the
Chris@0 208 // password strength meter.
Chris@0 209 if (strength < 60) {
Chris@0 210 indicatorText = translate.weak;
Chris@0 211 indicatorClass = 'is-weak';
Chris@17 212 } else if (strength < 70) {
Chris@0 213 indicatorText = translate.fair;
Chris@0 214 indicatorClass = 'is-fair';
Chris@17 215 } else if (strength < 80) {
Chris@0 216 indicatorText = translate.good;
Chris@0 217 indicatorClass = 'is-good';
Chris@17 218 } else if (strength <= 100) {
Chris@0 219 indicatorText = translate.strong;
Chris@0 220 indicatorClass = 'is-strong';
Chris@0 221 }
Chris@0 222
Chris@0 223 // Assemble the final message.
Chris@17 224 msg = `${translate.hasWeaknesses}<ul><li>${msg.join(
Chris@17 225 '</li><li>',
Chris@17 226 )}</li></ul>`;
Chris@0 227
Chris@0 228 return {
Chris@0 229 strength,
Chris@0 230 message: msg,
Chris@0 231 indicatorText,
Chris@0 232 indicatorClass,
Chris@0 233 };
Chris@0 234 };
Chris@17 235 })(jQuery, Drupal, drupalSettings);