summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDylan Hardison <dylan@mozilla.com>2015-11-04 23:51:25 +0100
committerDylan Hardison <dylan@mozilla.com>2015-11-04 23:51:25 +0100
commit3238e2d9fcd532807847556514c0519fa0869b14 (patch)
treec9593bb3f49ea28e52ca170fad91e1fc8f2cd707
parent7f43eebe16d93b9ba0eef6a42b570b594dc33da6 (diff)
downloadbugzilla-3238e2d9fcd532807847556514c0519fa0869b14.tar.gz
bugzilla-3238e2d9fcd532807847556514c0519fa0869b14.tar.xz
Bug 1177911 - Determine and implement better password requirements for BMO
-rw-r--r--Bugzilla/Config/Auth.pm5
-rw-r--r--Bugzilla/User.pm19
-rw-r--r--js/account.js140
-rw-r--r--skins/standard/admin.css79
-rw-r--r--template/en/default/account/email/confirm-new.html.tmpl69
-rw-r--r--template/en/default/account/password/set-forgotten-password.html.tmpl62
-rw-r--r--template/en/default/account/prefs/account.html.tmpl218
-rw-r--r--template/en/default/account/reset-password.html.tmpl156
-rw-r--r--template/en/default/global/password-features.html.tmpl27
-rw-r--r--template/en/default/global/user-error.html.tmpl52
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">&nbsp;</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">&nbsp;</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">&nbsp;</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">&nbsp;</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">&#9656;</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">&#9656;</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">&nbsp;</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">&nbsp;</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">&nbsp;</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">&nbsp;</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">&nbsp;</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">&nbsp;</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&amp;loginname=[% locked_user.email FILTER uri %]&amp;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