annotate core/modules/user/user.es6.js @ 0:c75dbcec494b

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