diff options
author | Dylan Hardison <dylan@mozilla.com> | 2015-11-04 23:51:25 +0100 |
---|---|---|
committer | Dylan Hardison <dylan@mozilla.com> | 2015-11-04 23:51:25 +0100 |
commit | 3238e2d9fcd532807847556514c0519fa0869b14 (patch) | |
tree | c9593bb3f49ea28e52ca170fad91e1fc8f2cd707 | |
parent | 7f43eebe16d93b9ba0eef6a42b570b594dc33da6 (diff) | |
download | bugzilla-3238e2d9fcd532807847556514c0519fa0869b14.tar.gz bugzilla-3238e2d9fcd532807847556514c0519fa0869b14.tar.xz |
Bug 1177911 - Determine and implement better password requirements for BMO
-rw-r--r-- | Bugzilla/Config/Auth.pm | 5 | ||||
-rw-r--r-- | Bugzilla/User.pm | 19 | ||||
-rw-r--r-- | js/account.js | 140 | ||||
-rw-r--r-- | skins/standard/admin.css | 79 | ||||
-rw-r--r-- | template/en/default/account/email/confirm-new.html.tmpl | 69 | ||||
-rw-r--r-- | template/en/default/account/password/set-forgotten-password.html.tmpl | 62 | ||||
-rw-r--r-- | template/en/default/account/prefs/account.html.tmpl | 218 | ||||
-rw-r--r-- | template/en/default/account/reset-password.html.tmpl | 156 | ||||
-rw-r--r-- | template/en/default/global/password-features.html.tmpl | 27 | ||||
-rw-r--r-- | template/en/default/global/user-error.html.tmpl | 52 |
10 files changed, 482 insertions, 345 deletions
diff --git a/Bugzilla/Config/Auth.pm b/Bugzilla/Config/Auth.pm index 36287b107..ac5394f04 100644 --- a/Bugzilla/Config/Auth.pm +++ b/Bugzilla/Config/Auth.pm @@ -132,9 +132,8 @@ sub get_param_list { { name => 'password_complexity', type => 's', - choices => [ 'no_constraints', 'mixed_letters', 'letters_numbers', - 'letters_numbers_specialchars' ], - default => 'no_constraints', + choices => [ 'no_constraints', 'bmo' ], + default => 'bmo', checker => \&check_multi }, diff --git a/Bugzilla/User.pm b/Bugzilla/User.pm index ebd82002f..1a0deed6b 100644 --- a/Bugzilla/User.pm +++ b/Bugzilla/User.pm @@ -2482,15 +2482,16 @@ sub validate_password_check { } my $complexity_level = Bugzilla->params->{password_complexity}; - if ($complexity_level eq 'letters_numbers_specialchars') { - return 'password_not_complex' - if ($password !~ /[[:alpha:]]/ || $password !~ /\d/ || $password !~ /[[:punct:]]/); - } elsif ($complexity_level eq 'letters_numbers') { - return 'password_not_complex' - if ($password !~ /[[:lower:]]/ || $password !~ /[[:upper:]]/ || $password !~ /\d/); - } elsif ($complexity_level eq 'mixed_letters') { - return 'password_not_complex' - if ($password !~ /[[:lower:]]/ || $password !~ /[[:upper:]]/); + if ($complexity_level eq 'bmo') { + my $features = 0; + + $features++ if $password =~ /[a-z]/; + $features++ if $password =~ /[A-Z]/; + $features++ if $password =~ /[0-9]/; + $features++ if $password =~ /[^A-Za-z0-9]/; + $features++ if length($password) > 12; + + return 'password_not_complex' if $features < 3; } # Having done these checks makes us consider the password untainted. diff --git a/js/account.js b/js/account.js index 31c1a50e6..c268bb8e5 100644 --- a/js/account.js +++ b/js/account.js @@ -7,6 +7,146 @@ $(function() { + function make_password_strength($password) { + return function(event) { + var password = $password.val(); + var missing_features = {"upper": true, "lower": true, "numbers": true, "symbols": true, "length12": true}; + var features = [], + charset = 0, + score = 0, + min_features = 3; + + $("#password-meter").show(); + $("#password-meter-label").show(); + + if (password.match(/[A-Z]/)) { + delete missing_features.upper; + features.push("upper"); + charset += 26; + } + if (password.match(/[a-z]/)) { + delete missing_features.lower; + features.push("lower"); + charset += 26; + } + if (password.match(/[0-9]/)) { + delete missing_features.numbers; + features.push("numbers"); + charset += 10; + } + if (password.match(/[^A-Za-z0-9]/)) { + delete missing_features.symbols; + features.push("symbols"); + charset += 30; // there are 30-32 typable characters on a keyboard. + } + if (password.length > 12) { + delete missing_features.length12; + features.push("length12"); + } + + $("#password-features li").removeClass("feature-ok"); + features.forEach(function(name) { + $("#password-feature-" + name).addClass("feature-ok"); + }); + + var entropy = Math.floor(Math.log(charset) * (password.length / Math.log(2))); + if (entropy) { + score = entropy/128; + } + + $password.get(0).setCustomValidity(""); + if (features.length < min_features) { + $("#password-msg") + .text("Password does not meet requirements") + .attr("class", "password-bad"); + $password.get(0).setCustomValidity($("#password-msg").text()); + } + else if (password.length < 8) { + $("#password-msg") + .text("Password is too short") + .attr("class", "password-bad"); + $password.get(0).setCustomValidity($("#password-msg").text()); + } + else { + $("#password-msg") + .text("Password meets requirements") + .attr("class", "password-good"); + $password.get(0).setCustomValidity(""); + } + + if (entropy < 60) { + $("#password-meter") + .removeClass("meter-good meter-ok") + .addClass("meter-bad"); + } + else if (entropy >= 120) { + $("#password-meter") + .removeClass("meter-bad meter-ok") + .addClass("meter-good"); + } + else if (entropy > 60) { + $("#password-meter") + .removeClass("meter-bad meter-good") + .addClass("meter-ok"); + } + + if (score === 0) { + score = 0.01; + $("#password-meter") + .removeClass("meter-good meter-ok") + .addClass("meter-bad"); + } + + $("#password-meter").width(Math.max(0, Math.min($password.width()+10, Math.ceil(($password.width()+10) * score)))); + }; + } + + function make_password_confirm($password1, $password2) { + return function (event) { + if ($password1.val() != $password2.val()) { + $password2.get(0).setCustomValidity("Does not match previous password"); + } + else { + $password2.get(0).setCustomValidity(""); + } + }; + } + var password1_sel, password2_sel; + var complexity = $("#password-features").data("password-complexity"); + var page = $("#password-features").data("password-page"); + var check_password_strength, check_password_confirm; + + if (page == "account") { + $("#new_password1, #new_password2, #new_login_name").change(function() { + $("#old_password").attr("required", true); + }); + } + + if (complexity == "bmo") { + if (page == "confirm") { + password1_sel = "#passwd1"; + password2_sel = "#passwd2"; + } + else { + password1_sel = "#new_password1"; + password2_sel = "#new_password2"; + } + $("#password-features").show(); + + check_password_strength = make_password_strength($(password1_sel)); + check_password_confirm = make_password_confirm($(password1_sel), $(password2_sel)); + + $(password1_sel).on("input", check_password_strength); + $(password1_sel).on("focus", check_password_strength); + + $(password1_sel).on("blur", check_password_confirm); + $(password1_sel).on("change", check_password_confirm); + $(password2_sel).on("input", check_password_confirm); + } + else { + $("#password-features").hide(); + } + // account disabling $('#account-disable-toggle') diff --git a/skins/standard/admin.css b/skins/standard/admin.css index 2b56c1831..b6ac508c2 100644 --- a/skins/standard/admin.css +++ b/skins/standard/admin.css @@ -314,3 +314,82 @@ input[disabled] { float: left; margin-right: 1em; } + +.flex { + display: flex; + flex-flow: row; +} + +.flex-left { + flex: 1; + min-width: 500px; +} + +.flex-right { + flex: 2; +} + +.meter { + width: 25px; + height: 1em; + display: inline-block; +} + +.meter-bad { + background-color: #DD514C; + background-image: linear-gradient(to bottom, #EE5F5B, #C43C35); + background-repeat: repeat-x; +} + +.meter-ok { + background-color: #FAA732; + background-image: linear-gradient(to bottom, #FBB450, #F89406); + background-repeat: repeat-x; +} + +.meter-good { + background-color: #5EB95E; + background-image: linear-gradient(to bottom, #62C462, #57A957); + background-repeat: repeat-x; +} + +.password-bad { + color: #DD514C; +} + +.password-ok { + color: #FAA732; +} + +.password-good { + color: #5EB95E; +} + +#password-features { + display: none; +} + +#password-features li.feature-ok:before { + font-size: normal; + content: "\2611"; + margin-left: -13px; + margin-right: 2px; +} + +#password-features li.feature-ok { + color: #5EB95E; +} + +#password-features li:before { + font-size: normal; + content: "\2610"; + margin-left: -13px; + margin-right: 2px; +} + +#password-features ul { + padding-left: 20px; + text-indent: 2px; + list-style: none; + list-style-position: outside; +} diff --git a/template/en/default/account/email/confirm-new.html.tmpl b/template/en/default/account/email/confirm-new.html.tmpl index 3241030e8..f505268f5 100644 --- a/template/en/default/account/email/confirm-new.html.tmpl +++ b/template/en/default/account/email/confirm-new.html.tmpl @@ -21,8 +21,10 @@ [% title = BLOCK %]Create a new user account for '[% email FILTER html %]'[% END %] [% PROCESS "global/header.html.tmpl" - title = title - onload = "document.forms['confirm_account_form'].realname.focus();" %] + title = title + style_urls = ['skins/standard/admin.css'] + javascript_urls = ['js/account.js'] + onload = "document.forms['confirm_account_form'].realname.focus();" %] [% password_complexity = Param('password_complexity') %] @@ -35,39 +37,36 @@ <form id="confirm_account_form" method="post" action="token.cgi"> <input type="hidden" name="t" value="[% token FILTER html %]"> <input type="hidden" name="a" value="confirm_new_account"> - <table> - <tr> - <th align="right">Email Address:</th> - <td>[% email FILTER html %]</td> - </tr> - <tr> - <th align="right"><small><i>(OPTIONAL)</i></small> <label for="realname">Real Name</label>:</th> - <td><input type="text" id="realname" name="realname" value=""></td> - </tr> - <tr> - <th align="right"><label for="passwd1">Type your password</label>:</th> - <td> - <input type="password" id="passwd1" name="passwd1" value=""> - (Password should be a minimum of [% constants.USER_PASSWORD_MIN_LENGTH FILTER none %] characters long - [% IF password_complexity == "mixed_letters" %] - and must contain at least one UPPER and one lowercase letter - [% ELSIF password_complexity == "letters_numbers" %] - and must contain at least one UPPER and one lowercase letter and a number - [% ELSIF password_complexity == "letters_numbers_specialchars" %] - and must contain at least one letter, a number and a special character - [% END ~%] - .) - </td> - </tr> - <tr> - <th align="right"><label for="passwd2">Confirm your password</label>:</th> - <td><input type="password" id="passwd2" name="passwd2" value=""></td> - </tr> - <tr> - <th align="right"> </th> - <td><input type="submit" id="confirm" value="Create"></td> - </tr> - </table> + <div class="flex"> + <div class="flex-left"> + <table> + <tr> + <th align="right">Email Address:</th> + <td>[% email FILTER html %]</td> + </tr> + <tr> + <th align="right"><small><i>(OPTIONAL)</i></small> <label for="realname">Real Name</label>:</th> + <td><input type="text" id="realname" name="realname" value=""></td> + </tr> + <tr> + <th align="right"><label for="passwd1">Type your password</label>:</th> + <td> + <input type="password" id="passwd1" name="passwd1" value="" required> + </td> + </tr> + <tr> + <th align="right"><label for="passwd2">Confirm your password</label>:</th> + <td><input type="password" id="passwd2" name="passwd2" value="" required"></td> + </tr> + <tr> + <th align="right"> </th> + <td><input type="submit" id="confirm" value="Create"></td> + </tr> + </table> + </div> + + [% INCLUDE "global/password-features.html.tmpl" class="flex-right" password_page="confirm" %] + </div> </form> <p> diff --git a/template/en/default/account/password/set-forgotten-password.html.tmpl b/template/en/default/account/password/set-forgotten-password.html.tmpl index cfeacbb93..68119252e 100644 --- a/template/en/default/account/password/set-forgotten-password.html.tmpl +++ b/template/en/default/account/password/set-forgotten-password.html.tmpl @@ -19,39 +19,47 @@ #%] [% title = "Change Password" %] -[% PROCESS global/header.html.tmpl %] + [% PROCESS global/header.html.tmpl + style_urls = ['skins/standard/admin.css'] + javascript_urls = ['js/account.js'] + %] <p> To change your password, enter a new password twice: </p> -<form method="post" action="token.cgi"> - <input type="hidden" name="t" value="[% token FILTER html %]"> - <input type="hidden" name="a" value="chgpw"> - <table> - <tr> - <th align="right">New Password:</th> - <td> - <input type="password" name="password"> - (minimum [% constants.USER_PASSWORD_MIN_LENGTH FILTER none %] characters) - </td> - </tr> +<div class="flex"> + <div class="flex-left"> + <form method="post" action="token.cgi"> + <input type="hidden" name="t" value="[% token FILTER html %]"> + <input type="hidden" name="a" value="chgpw"> + <table> + <tr> + <th align="right">New Password:</th> + <td> + <input type="password" name="password" id="new_password1" required> + </td> + </tr> - <tr> - <th align="right">New Password Again:</th> - <td> - <input type="password" name="matchpassword"> - </td> - </tr> + <tr> + <th align="right">New Password Again:</th> + <td> + <input type="password" name="matchpassword" id="new_password2" required> + </td> + </tr> - <tr> - <th align="right"> </th> - <td> - <input type="submit" id="update" value="Submit"> - [% INCLUDE mfa/protected.html.tmpl user=token_user %] - </td> - </tr> - </table> -</form> + <tr> + <th align="right"> </th> + <td> + <input type="submit" id="update" value="Submit"> + [% INCLUDE mfa/protected.html.tmpl user=token_user %] + </td> + </tr> + </table> + </form> + </div> + + [% INCLUDE "global/password-features.html.tmpl" class="flex-right" password_page="forgot" %] +</div> [% PROCESS global/footer.html.tmpl %] diff --git a/template/en/default/account/prefs/account.html.tmpl b/template/en/default/account/prefs/account.html.tmpl index 3f838691b..c41ea116f 100644 --- a/template/en/default/account/prefs/account.html.tmpl +++ b/template/en/default/account/prefs/account.html.tmpl @@ -27,129 +27,133 @@ [%# BMO - add hook for displaying user-profile link %] [% Hook.process('start') %] -<table> - <tr> - <th align="right" width="150">Your real name:</th> - <td> - <input size="35" name="realname" value="[% realname FILTER html %]"> - <i>optional, but encouraged</i> - </td> - </tr> - - [%# BMO - moved field hook from end of file to here to group with other account fields %] - [% Hook.process('field') %] - - [% SET can_change = [] %] - [% IF user.authorizer.can_change_password %] - [% can_change.push('password') %] - [% END %] - [% IF user.authorizer.can_change_email && Param('allowemailchange') %] - [% can_change.push('email address') %] - [% END %] - - [% IF can_change.size %] - <tr> - <td></td> - <td><hr></td> - </tr> - <tr> - <td colspan="3"> - Your current password is required to - confirm [% can_change.join(' or ') FILTER html %] - changes. - </td> - </tr> - <tr> - <th align="right">Current password:</th> - <td> - <input type="hidden" name="old_login" value="[% user.login FILTER html %]"> - <input type="password" name="old_password"> - <a href="#" id="forgot-password">I forgot my password</a> - </td> - </tr> - [% IF user.authorizer.can_change_password %] +<div class="flex"> + <div class="flex-left"> + <table> <tr> - <th align="right">New password:</th> + <th align="right" width="150">Your real name:</th> <td> - <input type="password" name="new_password1"> - [% INCLUDE "mfa/protected.html.tmpl" %] + <input size="35" name="realname" value="[% realname FILTER html %]" + placeholder="optional, but encouraged"> </td> </tr> - <tr> - <th align="right">Confirm new password:</th> - <td> - <input type="password" name="new_password2"> - </td> - </tr> - [% END %] + [%# BMO - moved field hook from end of file to here to group with other account fields %] + [% Hook.process('field') %] - [% IF user.authorizer.can_change_email && Param('allowemailchange') %] - [% IF login_change_date %] - [% IF new_login_name %] - <tr> - <th align="right">Pending email address:</th> - <td>[% new_login_name FILTER html %]</td> - </tr> - <tr> - <th align="right">Change request expires:</th> - <td>[% login_change_date FILTER time %]</td> - </tr> - [% ELSE %] + [% SET can_change = [] %] + [% IF user.authorizer.can_change_password %] + [% can_change.push('password') %] + [% END %] + [% IF user.authorizer.can_change_email && Param('allowemailchange') %] + [% can_change.push('email address') %] + [% END %] + + [% IF can_change.size %] + <tr> + <td></td> + <td><hr></td> + </tr> + <tr> + <td colspan="3"> + Your current password is required to + confirm [% can_change.join(' or ') FILTER html %] + changes. + </td> + </tr> + <tr> + <th align="right">Current password:</th> + <td> + <input type="hidden" name="old_login" value="[% user.login FILTER html %]"> + <input type="password" name="old_password" id="old_password"> + <a href="#" id="forgot-password">I forgot my password</a> + </td> + </tr> + [% IF user.authorizer.can_change_password %] <tr> - <th align="right">Confirmed email address:</th> - <td>[% user.login FILTER html %]</td> + <th align="right">New password:</th> + <td> + <input type="password" name="new_password1" id="new_password1"> + [% INCLUDE "mfa/protected.html.tmpl" %] + </td> </tr> <tr> - <th align="right">Completion date:</th> - <td>[% login_change_date FILTER time %]</td> + <th align="right">Confirm new password:</th> + <td> + <input type="password" name="new_password2" id="new_password2"> + </td> </tr> [% END %] - [% ELSE %] + + [% IF user.authorizer.can_change_email && Param('allowemailchange') %] + [% IF login_change_date %] + [% IF new_login_name %] + <tr> + <th align="right">Pending email address:</th> + <td>[% new_login_name FILTER html %]</td> + </tr> + <tr> + <th align="right">Change request expires:</th> + <td>[% login_change_date FILTER time %]</td> + </tr> + [% ELSE %] + <tr> + <th align="right">Confirmed email address:</th> + <td>[% user.login FILTER html %]</td> + </tr> + <tr> + <th align="right">Completion date:</th> + <td>[% login_change_date FILTER time %]</td> + </tr> + [% END %] + [% ELSE %] + <tr> + <th align="right">New email address:</th> + <td> + <input size="35" name="new_login_name" id="new_login_name"> + [% INCLUDE "mfa/protected.html.tmpl" %] + </td> + </tr> + [% END %] + [% END %] <tr> - <th align="right">New email address:</th> - <td> - <input size="35" name="new_login_name"> - [% INCLUDE "mfa/protected.html.tmpl" %] - </td> + <td></td> + <td><hr></td> </tr> [% END %] - [% END %] - <tr> - <td></td> - <td><hr></td> - </tr> - [% END %] - <tr> - <td></td> - <td> - <a href="#" id="account-disable-toggle"><span id="account-disable-spinner">▸</span> Disable My Account</a> - </td> - </tr> - <tr id="account-disable" style="display:none"> - <td></td> - <td> - <p> - Your contributions on bugzilla.mozilla.org will still be visible; - however, your email address and name will be removed in most locations. - We are not able to remove your details that are part of comment text. - </p> - <p> - <input type="checkbox" id="account-disable-confirm"> - I acknowledge that my account will not be functional after it has been - disabled. - </p> - <input type="hidden" name="account_disable" id="account_disable"> - <button type="button" id="account-disable-button" disabled>Disable Account</button> - </td> - </tr> - <tr> - <td></td> - <td><hr></td> - </tr> + <tr> + <td></td> + <td> + <a href="#" id="account-disable-toggle"><span id="account-disable-spinner">▸</span> Disable My Account</a> + </td> + </tr> + <tr id="account-disable" style="display:none"> + <td></td> + <td> + <p> + Your contributions on bugzilla.mozilla.org will still be visible; + however, your email address and name will be removed in most locations. + We are not able to remove your details that are part of comment text. + </p> + <p> + <input type="checkbox" id="account-disable-confirm"> + I acknowledge that my account will not be functional after it has been + disabled. + </p> + <input type="hidden" name="account_disable" id="account_disable"> + <button type="button" id="account-disable-button" disabled>Disable Account</button> + </td> + </tr> + <tr> + <td></td> + <td><hr></td> + </tr> + </table> + </div> -</table> + [% INCLUDE "global/password-features.html.tmpl" class="flex-right" password_page="account" %] +</div> [% tab_footer = BLOCK %] <form action="token.cgi" method="post" id="forgot-form"> diff --git a/template/en/default/account/reset-password.html.tmpl b/template/en/default/account/reset-password.html.tmpl index 2b1d297dc..ec57f19dd 100644 --- a/template/en/default/account/reset-password.html.tmpl +++ b/template/en/default/account/reset-password.html.tmpl @@ -52,78 +52,6 @@ [% inline_js = BLOCK %] $(function() { - - $('#old_password, #new_password1, #new_password2') - .keyup(function() { - var errors = []; - var old = $('#old_password').val(); - var new1 = $('#new_password1').val(); - var new2 = $('#new_password2').val(); - - if (old === '') { - errors.push('Missing current password'); - } - if (new1 === '' || new2 === '') { - errors.push('Missing new password'); - } - else if (new1 !== new2) { - errors.push('New passwords do not match'); - } - else if (new1 === old) { - errors.push('Your new password must be different from your old password'); - } - else if (new1.length < [% constants.USER_PASSWORD_MIN_LENGTH FILTER none %]) { - errors.push('Your password must be at least [% constants.USER_PASSWORD_MIN_LENGTH FILTER none %] long'); - } - else { - var complexity_fn; - [% SWITCH Param('password_complexity') %] - [% CASE 'no_constraints' %] - complexity_fn = function() {}; - [% CASE 'mixed_letters' %] - complexity_fn = function(pass, errors) { - if ( - pass.search(/[a-z]/) == -1 || - pass.search(/[A-Z]/) == -1 - ) { - errors.push('New password is not complex enough'); - } - }; - [% CASE 'letters_numbers' %] - complexity_fn = function(pass, errors) { - if ( - pass.search(/[a-z]/) == -1 || - pass.search(/[A-Z]/) == -1 || - pass.search(/[0-9]/) == -1 - ) { - errors.push('New password is not complex enough'); - } - }; - [% CASE 'letters_numbers_specialchars' %] - complexity_fn = function(pass, errors) { - if ( - pass.search(/[a-z]/) == -1 || - pass.search(/[A-Z]/) == -1 || - pass.search(/[0-9]/) == -1 || - pass.search(/\W/) == -1 - ) { - errors.push('New password is not complex enough'); - } - }; - [% END %] - complexity_fn(new1, errors); - } - - $('#submit').attr('disabled', errors.length > 0); - if ((old !== '' || new1 !== '' || new2 !== '') && errors.length) { - $('#errors').html('<ul><li>' + errors.join('</li><li>') + '</li></ul>'); - } - else { - $('#errors').html(''); - } - }) - .keyup(); - $('#forgot_password') .click(function(event) { event.preventDefault(); @@ -134,9 +62,11 @@ $(function() { [% END %] [% PROCESS global/header.html.tmpl - title = "Password change required" - style = inline_style - javascript = inline_js + title = "Password change required" + style = inline_style + style_urls = ['skins/standard/admin.css'] + javascript = inline_js + javascript_urls = ['js/account.js'] %] <h1>Password Reset</h1> @@ -149,58 +79,48 @@ $(function() { <input type="hidden" name="token" value="[% token FILTER html %]"> <input type="hidden" name="do_save" value="1"> -<div id="password-reset"> - <div class="field-hr"> </div> - <div class="field-row"> - <div class="field-name">Email</div> - <div class="field-value"> - [% user.login FILTER html %] +<div class="flex"> + <div id="password-reset" class="flex-left"> + <div class="field-hr"> </div> + <div class="field-row"> + <div class="field-name">Email</div> + <div class="field-value"> + [% user.login FILTER html %] + </div> </div> - </div> - <div class="field-row"> - <div class="field-name">Current Password</div> - <div class="field-value"> - <input type="password" name="old_password" id="old_password" size="30"> + <div class="field-row"> + <div class="field-name">Current Password</div> + <div class="field-value"> + <input type="password" name="old_password" id="old_password" size="30" required> + </div> </div> - </div> - <div class="field-hr"> </div> - <div id="errors"></div> - <div class="field-row"> - <div class="field-name">New Password</div> - <div class="field-value"> - <input type="password" name="new_password1" id="new_password1" size="30"> + <div class="field-hr"> </div> + <div id="errors"></div> + <div class="field-row"> + <div class="field-name">New Password</div> + <div class="field-value"> + <input type="password" name="new_password1" id="new_password1" size="30" required> + </div> </div> - </div> - <div class="field-row"> - <div class="field-name">New Password</div> - <div class="field-value"> - <input type="password" name="new_password2" id="new_password2" size="30"> - (again) + <div class="field-row"> + <div class="field-name">New Password</div> + <div class="field-value"> + <input type="password" name="new_password2" id="new_password2" size="30" required> + (again) + </div> </div> - </div> - <div class="field-hr"> </div> - <div class="field-row"> - <div class="field-value"> - <input type="submit" id="submit" value="Update Password"> - <a id="forgot_password" href="#">Forgot Password</a> + <div class="field-hr"> </div> + <div class="field-row"> + <div class="field-value"> + <input type="submit" id="submit" value="Update Password"> + <a id="forgot_password" href="#">Forgot Password</a> + </div> </div> </div> + [% INCLUDE "global/password-features.html.tmpl" class="flex-right" password_page="reset_password" %] </div> - </form> -<p id="complexity_rules"> - Your password must be a minimum of [% constants.USER_PASSWORD_MIN_LENGTH FILTER none %] characters long - [% SWITCH Param('password_complexity') %] - [% CASE 'mixed_letters' %] - and must contain at least one UPPER and one lowercase letter - [% CASE 'letters_numbers' %] - and must contain at least one UPPER and one lowercase letter and a number - [% CASE 'letters_numbers_specialchars' %] - and must contain at least one letter, a number and a special character - [% END ~%]. -</p> - <form action="token.cgi" method="post" id="forgot-form"> <input type="hidden" name="loginname" value="[% user.login FILTER html %]"> <input type="hidden" name="a" value="reqpw"> diff --git a/template/en/default/global/password-features.html.tmpl b/template/en/default/global/password-features.html.tmpl new file mode 100644 index 000000000..5d6c0f8c1 --- /dev/null +++ b/template/en/default/global/password-features.html.tmpl @@ -0,0 +1,27 @@ +[%# This Source Code Form is subject to the terms of the Mozilla Public + # License, v. 2.0. If a copy of the MPL was not distributed with this + # file, You can obtain one at http://mozilla.org/MPL/2.0/. + # + # This Source Code Form is "Incompatible With Secondary Licenses", as + # defined by the Mozilla Public License, v. 2.0. + #%] + +<div id="password-features" + style="display: none" + class="[% class FILTER html %]" + data-password-page="[% password_page FILTER html %]" + data-password-complexity="[% Param("password_complexity") FILTER html %]"> + Password must be 8 characters or longer, + and match at least 3 of the following requirements: + + <ul> + <li id="password-feature-upper">uppercase letters</li> + <li id="password-feature-lower">lowercase letters</li> + <li id="password-feature-numbers">numbers</li> + <li id="password-feature-symbols">symbols</li> + <li id="password-feature-length12">longer than 12 characters</li> + </ul> + <div id="password-msg"></div> + + <div id="password-meter-label" style="display: none">Strength: <span id="password-meter" class="meter"></span></div> +</div>
\ No newline at end of file diff --git a/template/en/default/global/user-error.html.tmpl b/template/en/default/global/user-error.html.tmpl index 6996832aa..14399f010 100644 --- a/template/en/default/global/user-error.html.tmpl +++ b/template/en/default/global/user-error.html.tmpl @@ -1503,59 +1503,19 @@ [% ELSIF error == "password_not_complex" %] [% title = "Password Fails Requirements" %] - [% passregex = Param('password_complexity') %] - The password must contain at least one: + The Password must meet three of the following requirements <ul> - [% IF passregex == 'letters_numbers_specialchars' %] - <li>letter</li> - <li>special character</li> - [% ELSIF passregex.search('letters') %] - <li>UPPERCASE letter</li> - <li>lowercase letter</li> - [% END %] - [% IF passregex.search('numbers') %] - <li>digit</li> - [% END %] + <li>uppercase letters</li> + <li>lowercase letters</li> + <li>numbers</li> + <li>symbols</li> + <li>longer than 12 characters</li> </ul> [% IF locked_user %] You must <a href="token.cgi?a=reqpw&loginname=[% locked_user.email FILTER uri %]&token=[% issue_hash_token(['reqpw']) FILTER uri %]"> request a new password</a> in order to log in again. [% END %] - [% ELSIF error == "password_not_complex" %] - [% title = "Password Fails Requirements" %] - [% passregex = Param('password_complexity') %] - Password must contain at least one: - <ul> - [% IF passregex.search('letters') %] - <li>UPPERCASE letter</li> - <li>lowercase letter</li> - [% END %] - [% IF passregex.search('numbers') %] - <li>digit</li> - [% END %] - [% IF passregex.search('specialchars') %] - <li>special character</li> - [% END %] - </ul> - - [% ELSIF error == "password_not_complex" %] - [% title = "Password Fails Requirements" %] - [% passregex = Param('password_complexity') %] - Password must contain at least one: - <ul> - [% IF passregex.search('letters') %] - <li>UPPERCASE letter</li> - <li>lowercase letter</li> - [% END %] - [% IF passregex.search('numbers') %] - <li>digit</li> - [% END %] - [% IF passregex.search('specialchars') %] - <li>special character</li> - [% END %] - </ul> - [% ELSIF error == "product_access_denied" %] [% title = "Product Access Denied" %] Either the product |