user.js 7.3 KB

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