1/** 2 * @file 3 * User behaviors. 4 */ 5 6(($, Drupal) => { 7 /** 8 * An object containing CSS classes used for password widget. 9 * 10 * @type {object} 11 * @prop {string} passwordParent - A CSS class for the parent element. 12 * @prop {string} passwordsMatch - A CSS class indicating password match. 13 * @prop {string} passwordsNotMatch - A CSS class indicating passwords 14 * doesn't match. 15 * @prop {string} passwordWeak - A CSS class indicating weak password 16 * strength. 17 * @prop {string} passwordFair - A CSS class indicating fair password 18 * strength. 19 * @prop {string} passwordGood - A CSS class indicating good password 20 * strength. 21 * @prop {string} passwordStrong - A CSS class indicating strong password 22 * strength. 23 * @prop {string} widgetInitial - Initial CSS class that should be removed 24 * on a state change. 25 * @prop {string} passwordEmpty - A CSS class indicating password has not 26 * been filled. 27 * @prop {string} passwordFilled - A CSS class indicating password has 28 * been filled. 29 * @prop {string} confirmEmpty - A CSS class indicating password 30 * confirmation has not been filled. 31 * @prop {string} confirmFilled - A CSS class indicating password 32 * confirmation has been filled. 33 */ 34 Drupal.user = { 35 password: { 36 css: { 37 passwordParent: 'password-parent', 38 passwordsMatch: 'ok', 39 passwordsNotMatch: 'error', 40 passwordWeak: 'is-weak', 41 passwordFair: 'is-fair', 42 passwordGood: 'is-good', 43 passwordStrong: 'is-strong', 44 widgetInitial: '', 45 passwordEmpty: '', 46 passwordFilled: '', 47 confirmEmpty: '', 48 confirmFilled: '', 49 }, 50 }, 51 }; 52 53 /** 54 * Attach handlers to evaluate the strength of any password fields and to 55 * check that its confirmation is correct. 56 * 57 * @type {Drupal~behavior} 58 * 59 * @prop {Drupal~behaviorAttach} attach 60 * Attaches password strength indicator and other relevant validation to 61 * password fields. 62 */ 63 Drupal.behaviors.password = { 64 attach(context, settings) { 65 const cssClasses = Drupal.user.password.css; 66 once('password', 'input.js-password-field', context).forEach((value) => { 67 const $mainInput = $(value); 68 const $mainInputParent = $mainInput 69 .parent() 70 .addClass(cssClasses.passwordParent); 71 const $passwordWidget = $mainInput.closest( 72 '.js-form-type-password-confirm', 73 ); 74 const $confirmInput = $passwordWidget.find('input.js-password-confirm'); 75 const $passwordConfirmMessage = $( 76 Drupal.theme('passwordConfirmMessage', settings.password), 77 ); 78 79 const $passwordMatchStatus = $passwordConfirmMessage 80 .find('[data-drupal-selector="password-match-status-text"]') 81 .first(); 82 83 const $confirmInputParent = $confirmInput 84 .parent() 85 .addClass('confirm-parent') 86 .append($passwordConfirmMessage); 87 88 // List of classes to be removed from the strength bar on a state 89 // change. 90 const passwordStrengthBarClassesToRemove = [ 91 cssClasses.passwordWeak || '', 92 cssClasses.passwordFair || '', 93 cssClasses.passwordGood || '', 94 cssClasses.passwordStrong || '', 95 ] 96 .join(' ') 97 .trim(); 98 99 // List of classes to be removed from the text wrapper on a state 100 // change. 101 const confirmTextWrapperClassesToRemove = [ 102 cssClasses.passwordsMatch || '', 103 cssClasses.passwordsNotMatch || '', 104 ] 105 .join(' ') 106 .trim(); 107 108 // List of classes to be removed from the widget on a state change. 109 const widgetClassesToRemove = [ 110 cssClasses.widgetInitial || '', 111 cssClasses.passwordEmpty || '', 112 cssClasses.passwordFilled || '', 113 cssClasses.confirmEmpty || '', 114 cssClasses.confirmFilled || '', 115 ] 116 .join(' ') 117 .trim(); 118 119 const password = {}; 120 121 // If the password strength indicator is enabled, add its markup. 122 if (settings.password.showStrengthIndicator) { 123 const $passwordStrength = $( 124 Drupal.theme('passwordStrength', settings.password), 125 ); 126 password.$strengthBar = $passwordStrength 127 .find('[data-drupal-selector="password-strength-indicator"]') 128 .first(); 129 password.$strengthTextWrapper = $passwordStrength 130 .find('[data-drupal-selector="password-strength-text"]') 131 .first(); 132 password.$suggestions = $( 133 Drupal.theme('passwordSuggestions', settings.password, []), 134 ); 135 136 password.$suggestions.hide(); 137 $mainInputParent.append($passwordStrength); 138 $confirmInputParent.after(password.$suggestions); 139 } 140 141 /** 142 * Adds classes to the widget indicating if the elements are filled. 143 */ 144 const addWidgetClasses = () => { 145 $passwordWidget 146 .addClass( 147 $mainInput[0].value
148 ? cssClasses.passwordFilled 149 : cssClasses.passwordEmpty, 150 ) 151 .addClass( 152 $confirmInput[0].value 153 ? cssClasses.confirmFilled 154 : cssClasses.confirmEmpty, 155 ); 156 }; 157 158 /** 159 * Check that password and confirmation inputs match. 160 * 161 * @param {string} confirmInputVal 162 * The value of the confirm input. 163 */ 164 const passwordCheckMatch = (confirmInputVal) => { 165 const passwordsAreMatching = $mainInput[0].value === confirmInputVal; 166 const confirmClass = passwordsAreMatching 167 ? cssClasses.passwordsMatch 168 : cssClasses.passwordsNotMatch; 169 const confirmMessage = passwordsAreMatching 170 ? settings.password.confirmSuccess 171 : settings.password.confirmFailure; 172 173 // Update the success message and set the class if needed. 174 if ( 175 !$passwordMatchStatus.hasClass(confirmClass) || 176 !$passwordMatchStatus.html() === confirmMessage 177 ) { 178 if (confirmTextWrapperClassesToRemove) { 179 $passwordMatchStatus.removeClass( 180 confirmTextWrapperClassesToRemove, 181 ); 182 } 183 $passwordMatchStatus.html(confirmMessage).addClass(confirmClass); 184 } 185 }; 186 187 /** 188 * Checks the password strength. 189 */ 190 const passwordCheck = () => { 191 if (settings.password.showStrengthIndicator) { 192 // Evaluate the password strength. 193 const result = Drupal.evaluatePasswordStrength( 194 $mainInput[0].value, 195 settings.password, 196 ); 197 const $currentPasswordSuggestions = $( 198 Drupal.theme( 199 'passwordSuggestions', 200 settings.password, 201 result.messageTips, 202 ), 203 ); 204 205 // Update the suggestions for how to improve the password if needed. 206 if ( 207 password.$suggestions.html() !== 208 $currentPasswordSuggestions.html() 209 ) { 210 password.$suggestions.replaceWith($currentPasswordSuggestions); 211 password.$suggestions = $currentPasswordSuggestions.toggle( 212 // Only show the description box if a weakness exists in the 213 // password. 214 result.strength !== 100, 215 ); 216 } 217 218 if (passwordStrengthBarClassesToRemove) { 219 password.$strengthBar.removeClass( 220 passwordStrengthBarClassesToRemove, 221 ); 222 } 223 // Adjust the length of the strength indicator. 224 password.$strengthBar[0].style.width = `${result.strength}%`; 225 password.$strengthBar.addClass(result.indicatorClass); 226 227 // Update the strength indication text. 228 password.$strengthTextWrapper.html(result.indicatorText); 229 } 230 231 // Check the value in the confirm input and show results. 232 if ($confirmInput[0].value) { 233 passwordCheckMatch($confirmInput[0].value); 234 $passwordConfirmMessage[0].style.visibility = 'visible'; 235 } else { 236 $passwordConfirmMessage[0].style.visibility = 'hidden'; 237 } 238 239 if (widgetClassesToRemove) { 240 $passwordWidget.removeClass(widgetClassesToRemove); 241 addWidgetClasses(); 242 } 243 }; 244 245 if (widgetClassesToRemove) { 246 addWidgetClasses(); 247 } 248 249 // Monitor input events. 250 $mainInput.on('input', passwordCheck); 251 $confirmInput.on('input', passwordCheck); 252 }); 253 }, 254 }; 255 256 /** 257 * Evaluate the strength of a user's password. 258 * 259 * Returns the estimated strength and the relevant output message. 260 * 261 * @param {string} password 262 * The password to evaluate. 263 * @param {object} passwordSettings 264 * A password settings object containing the text to display and the CSS 265 * classes for each strength level. 266 * 267 * @return {object}
268 * An object containing strength, message, indicatorText and indicatorClass. 269 */ 270 Drupal.evaluatePasswordStrength = (password, passwordSettings) => { 271 password = password.trim(); 272 let indicatorText; 273 let indicatorClass; 274 let weaknesses = 0; 275 let strength = 100; 276 let msg = []; 277 278 const hasLowercase = /[a-z]/.test(password); 279 const hasUppercase = /[A-Z]/.test(password); 280 const hasNumbers = /[0-9]/.test(password); 281 const hasPunctuation = /[^a-zA-Z0-9]/.test(password); 282 283 // If there is a username edit box on the page, compare password to that, 284 // otherwise use value from the database. 285 const $usernameBox = $('input.username'); 286 const username = 287 $usernameBox.length > 0 288 ? $usernameBox[0].value 289 : passwordSettings.username; 290 291 // Lose 5 points for every character less than 12, plus a 30 point penalty. 292 if (password.length < 12) { 293 msg.push(passwordSettings.tooShort); 294 strength -= (12 - password.length) * 5 + 30; 295 } 296 297 // Count weaknesses. 298 if (!hasLowercase) { 299 msg.push(passwordSettings.addLowerCase); 300 weaknesses += 1; 301 } 302 if (!hasUppercase) { 303 msg.push(passwordSettings.addUpperCase); 304 weaknesses += 1; 305 } 306 if (!hasNumbers) { 307 msg.push(passwordSettings.addNumbers); 308 weaknesses += 1; 309 } 310 if (!hasPunctuation) { 311 msg.push(passwordSettings.addPunctuation); 312 weaknesses += 1; 313 } 314 315 // Apply penalty for each weakness (balanced against length penalty). 316 switch (weaknesses) { 317 case 1: 318 strength -= 12.5; 319 break; 320 321 case 2: 322 strength -= 25; 323 break; 324 325 case 3: 326 case 4: 327 strength -= 40; 328 break; 329 } 330 331 // Check if password is the same as the username. 332 if (password !== '' && password.toLowerCase() === username.toLowerCase()) { 333 msg.push(passwordSettings.sameAsUsername); 334 // Passwords the same as username are always very weak. 335 strength = 5; 336 } 337 338 const cssClasses = Drupal.user.password.css; 339 340 // Based on the strength, work out what text should be shown by the 341 // password strength meter. 342 if (strength < 60) { 343 indicatorText = passwordSettings.weak; 344 indicatorClass = cssClasses.passwordWeak; 345 } else if (strength < 70) { 346 indicatorText = passwordSettings.fair; 347 indicatorClass = cssClasses.passwordFair; 348 } else if (strength < 80) { 349 indicatorText = passwordSettings.good; 350 indicatorClass = cssClasses.passwordGood; 351 } else if (strength <= 100) { 352 indicatorText = passwordSettings.strong; 353 indicatorClass = cssClasses.passwordStrong; 354 } 355 356 // Assemble the final message while keeping the original message array. 357 const messageTips = msg; 358 msg = `${passwordSettings.hasWeaknesses}<ul><li>${msg.join( 359 '</li><li>', 360 )}</li></ul>`; 361 362 return { 363 strength, 364 indicatorText, 365 indicatorClass, 366 messageTips, 367 }; 368 }; 369})(jQuery, Drupal);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.