diff options
author | Dylan William Hardison <dylan@hardison.net> | 2017-09-19 17:48:11 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-09-19 17:48:11 +0200 |
commit | 64edd1e8659406d638ec44bf6bc2e69ff7e31ef0 (patch) | |
tree | e11680ca012ae863235c9ea3c74a19684b7be7a6 | |
parent | 5abefb689bc17dc26e747164f9251926faade358 (diff) | |
download | bugzilla-64edd1e8659406d638ec44bf6bc2e69ff7e31ef0.tar.gz bugzilla-64edd1e8659406d638ec44bf6bc2e69ff7e31ef0.tar.xz |
Bug 1400949 - Password requirements not stated
-rw-r--r-- | Bugzilla/Config/Auth.pm | 6 | ||||
-rw-r--r-- | js/account.js | 129 | ||||
-rw-r--r-- | node_modules/passwdqc/src/passwdqc_check.js | 492 | ||||
-rw-r--r-- | skins/standard/admin.css | 65 | ||||
-rw-r--r-- | template/en/default/account/email/confirm-new.html.tmpl | 5 | ||||
-rw-r--r-- | template/en/default/account/password/set-forgotten-password.html.tmpl | 5 | ||||
-rw-r--r-- | template/en/default/account/prefs/account.html.tmpl | 6 | ||||
-rw-r--r-- | template/en/default/account/reset-password.html.tmpl | 4 | ||||
-rw-r--r-- | template/en/default/admin/params/auth.html.tmpl | 7 | ||||
-rw-r--r-- | template/en/default/global/password-features.html.tmpl | 22 |
10 files changed, 536 insertions, 205 deletions
diff --git a/Bugzilla/Config/Auth.pm b/Bugzilla/Config/Auth.pm index 612fd1f3f..965d922d7 100644 --- a/Bugzilla/Config/Auth.pm +++ b/Bugzilla/Config/Auth.pm @@ -158,6 +158,12 @@ sub get_param_list { }, { + name => 'passwdqc_desc', + type => 'l', + default => 'The password must be complex.', + }, + + { name => 'auth_delegation', type => 'b', default => 0, diff --git a/js/account.js b/js/account.js index 8642cadbd..87e1e01f2 100644 --- a/js/account.js +++ b/js/account.js @@ -7,100 +7,6 @@ $(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()) { @@ -112,9 +18,8 @@ $(function() { }; } 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; + var check_password_confirm; if (page == "account") { $("#new_password1, #new_password2, #new_login_name").change(function() { @@ -122,31 +27,21 @@ $(function() { }); } - 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); + if (page == "confirm") { + password1_sel = "#passwd1"; + password2_sel = "#passwd2"; } else { - $("#password-features").hide(); + password1_sel = "#new_password1"; + password2_sel = "#new_password2"; } + check_password_confirm = make_password_confirm($(password1_sel), $(password2_sel)); + + $(password1_sel).on("blur", check_password_confirm); + $(password1_sel).on("change", check_password_confirm); + $(password2_sel).on("input", check_password_confirm); + // account disabling $('#account-disable-toggle') diff --git a/node_modules/passwdqc/src/passwdqc_check.js b/node_modules/passwdqc/src/passwdqc_check.js new file mode 100644 index 000000000..913bcc008 --- /dev/null +++ b/node_modules/passwdqc/src/passwdqc_check.js @@ -0,0 +1,492 @@ +/* + * Copyright (c) 2000-2002,2010,2013 by Solar Designer. See LICENSE. + * Copyright (c) 2014 Parallels, Inc. + */ +({define:typeof define!="undefined"?define:function(deps, factory){module.exports = factory(exports, require("./dictionary"));}}). +define(["exports", "./dictionary"], function(exports, dict){ + var dictionary = dict.dictionary; + + var FIXED_BITS = 15; + + /* + * Calculates the expected number of different characters for a random + * password of a given length. The result is rounded down. We use this + * with the _requested_ minimum length (so longer passwords don't have + * to meet this strict requirement for their length). + */ + function expected_different(charset, length){ + var x, y, z; + + x = ((charset - 1) << FIXED_BITS) / charset; + y = x; + while (--length > 0) + y = (y * x) >> FIXED_BITS; + z = charset * ((1 << FIXED_BITS) - y); + + return (z >> FIXED_BITS)|0; + } + + /* + * A password is too simple if it is too short for its class, or doesn't + * contain enough different characters for its class, or doesn't contain + * enough words for a passphrase. + * + * The biases are added to the length, and they may be positive or negative. + * The passphrase length check uses passphrase_bias instead of bias so that + * zero may be passed for this parameter when the (other) bias is non-zero + * because of a dictionary word, which is perfectly normal for a passphrase. + * The biases do not affect the number of different characters, character + * classes, and word count. + */ + function is_simple(params, newpass, bias, passphrase_bias){ + var length, classes, words, chars, + digits, lowers, uppers, others, unknowns, + c, p; + + length = classes = words = chars = 0; + digits = lowers = uppers = others = unknowns = 0; + p = ' '; + while (c = newpass[length]) { + length++; + + if (!isascii(c)) + unknowns++; + else if (isdigit(c)) + digits++; + else if (islower(c)) + lowers++; + else if (isupper(c)) + uppers++; + else + others++; + /* A word starts when a letter follows a non-letter or when a non-ASCII + * character follows a space character. We treat all non-ASCII characters + * as non-spaces, which is not entirely correct (there's the non-breaking + * space character at 0xa0, 0x9a, or 0xff), but it should not hurt. */ + if (isascii(p)) { + if (isascii(c)) { + if (isalpha(c) && !isalpha(p)) + words++; + } else if (isspace(p)) + words++; + } + p = c; + + /* Count this character just once: when we're not going to see it anymore */ + if(newpass.slice(length).indexOf(c) === -1) + chars++; + } + + length = strlen(newpass); + + if (!length) + return 1; + + /* Upper case characters and digits used in common ways don't increase the + * strength of a password */ + c = newpass[0]; + if (uppers && isascii(c) && isupper(c)) + uppers--; + c = newpass[length - 1]; + if (digits && isascii(c) && isdigit(c)) + digits--; + + /* Count the number of different character classes we've seen. We assume + * that there are no non-ASCII characters for digits. */ + classes = 0; + if (digits) + classes++; + if (lowers) + classes++; + if (uppers) + classes++; + if (others) + classes++; + if (unknowns && classes <= 1 && (!classes || digits || words >= 2)) + classes++; + + for (var min = params.min; classes > 0; classes--) + switch (classes) { + case 1: + if (length + bias >= min[0] && + chars >= expected_different(10, min[0]) - 1) + return 0; + return 1; + + case 2: + if (length + bias >= min[1] && + chars >= expected_different(36, min[1]) - 1) + return 0; + if (!params.passphrase_words || + words < params.passphrase_words) + continue; + if (length + passphrase_bias >= min[2] && + chars >= expected_different(27, min[2]) - 1) + return 0; + continue; + + case 3: + if (length + bias >= min[3] && + chars >= expected_different(62, min[3]) - 1) + return 0; + continue; + + case 4: + if (length + bias >= min[4] && + chars >= expected_different(95, min[4]) - 1) + return 0; + continue; + } + + return 1; + } + + function unify(dst, src){ + for (var i = 0; i < src.length; i++){ + var c = src.charAt(i); + if (isascii(c) && isupper(c)) + c = c.toLowerCase(); + switch (c) { + case 'a': case '@': + c = '4'; break; + case 'e': + c = '3'; break; + /* Unfortunately, if we translate both 'i' and 'l' to '1', this would + * associate these two letters with each other - e.g., "mile" would + * match "MLLE", which is undesired. To solve this, we'd need to test + * different translations separately, which is not implemented yet. */ + case 'i': case '|': + c = '!'; break; + case 'l': + c = '1'; break; + case 'o': + c = '0'; break; + case 's': case '$': + c = '5'; break; + case 't': case '+': + c = '7'; break; + } + dst += c; + } + + return dst; + } + + function reverse(src){ + return src.split("").reverse().join(""); + } + + /* + * Needle is based on haystack if both contain a long enough common + * substring and needle would be too simple for a password with the + * substring either removed with partial length credit for it added + * or partially discounted for the purpose of the length check. + */ + function is_based(params, haystack, needle, original, mode){ + var scratch, length, i, j, p, worst_bias; + + if (!params.match_length) // disabled + return 0; + + if (params.match_length < 0) // misconfigured + return 1; + + scratch = null; + worst_bias = 0; + + length = needle.length; + for (i = 0; i <= length - params.match_length; i++) + for (j = params.match_length; i + j <= length; j++) { + var bias = 0, j1 = j - 1; + var q0 = needle[i], q1 = needle.slice(i+1); + + for (var k=0; k<haystack.length; k++) + if (haystack[k] == q0 && haystack.substring(k+1, k+1+j1) == q1.substring(0,j1)) { // or memcmp() + if ((mode & 0xff) == 0) { // remove & credit + // remove j chars + var pos = length - (i + j); + if (!(mode & 0x100)) // not reversed + pos = i; + + scratch = original.substring(0, pos) + original.substring(pos+j); + + // add credit for match_length - 1 chars + bias = params.match_length - 1; + if (is_simple(params, scratch, bias, bias)) + return 1; + } else { // discount + // Require a 1 character longer match for substrings containing leetspeak + // when matching against dictionary words + bias = -1; + if ((mode & 0xff) == 1) { // words + var pos = i, end = i + j; + if (mode & 0x100) { // reversed + pos = length - end; + end = length - i; + } + for (; pos < end; pos++) + if (!isalpha(original[pos])) { + if (j == params.match_length){ + var cnt = true; + break; + } + bias = 0; + break; + } + if(cnt){ + cnt = false; + continue; + } + } + + // discount j - (match_length + bias) chars + bias += params.match_length - j; + // bias <= -1 + if (bias < worst_bias) { + if (is_simple(params, original, bias, + (mode & 0xff) == 1 ? 0 : bias)) + return 1; + worst_bias = bias; + } + } + } + + // Zero bias implies that there were no matches for this length. If so, + // * there's no reason to try the next substring length (it would result in + // * no matches as well). We break out of the substring length loop and + // * proceed with all substring lengths for the next position in needle. + if (!bias) + break; + } + + return 0; + } + + /* + * Common sequences of characters. + * We don't need to list any of the entire strings in reverse order because the + * code checks the new password in both "unified" and "unified and reversed" + * form against these strings (unifying them first indeed). We also don't have + * to include common repeats of characters (e.g., "777", "!!!", "1000") because + * these are often taken care of by the requirement on the number of different + * characters. + */ + var seq = [ + "0123456789", + "`1234567890-=", + "~!@#$%^&*()_+", + "abcdefghijklmnopqrstuvwxyz", + "a1b2c3d4e5f6g7h8i9j0", + "1a2b3c4d5e6f7g8h9i0j", + "abc123", + "qwertyuiop[]\\asdfghjkl;'zxcvbnm,./", + "qwertyuiop{}|asdfghjkl:\"zxcvbnm<>?", + "qwertyuiopasdfghjklzxcvbnm", + "1qaz2wsx3edc4rfv5tgb6yhn7ujm8ik,9ol.0p;/-['=]\\", + "!qaz@wsx#edc$rfv%tgb^yhn&ujm*ik<(ol>)p:?_{\"+}|", + "qazwsxedcrfvtgbyhnujmikolp", + "1q2w3e4r5t6y7u8i9o0p-[=]", + "q1w2e3r4t5y6u7i8o9p0[-]=\\", + "1qaz1qaz", + "1qaz!qaz", /* can't unify '1' and '!' - see comment in unify() */ + "1qazzaq1", + "zaq!1qaz", + "zaq!2wsx" + ]; + + /* + * This wordlist check is now the least important given the checks above + * and the support for passphrases (which are based on dictionary words, + * and checked by other means). It is still useful to trap simple short + * passwords (if short passwords are allowed) that are word-based, but + * passed the other checks due to uncommon capitalization, digits, and + * special characters. We (mis)use the same set of words that are used + * to generate random passwords. This list is much smaller than those + * used for password crackers, and it doesn't contain common passwords + * that aren't short English words. Perhaps support for large wordlists + * should still be added, even though this is now of little importance. + */ + function is_word_based(params, needle, original, is_reversed){ + var word, unified, i, length, mode; + + if (!params.match_length) /* disabled */ + return null; + + mode = is_reversed | 1; + word = ""; + for (i = 0; i < 0x1000; i++) { + word = dictionary[i]; + length = strlen(word); + if (length < params.match_length) + continue; + + word = unify("", word); + if (is_based(params, word, needle, original, mode)) + return REASON_WORD; + } + + mode = is_reversed | 2; + for (i = 0; i < seq.length; i++) { + unified = unify("", seq[i]); + if (!unified) + return REASON_ERROR; + if (is_based(params, unified, needle, original, mode)) + return REASON_SEQ; + } + + if (params.match_length <= 4) + for (i = 1900; i <= 2039; i++) { + if (is_based(params, i.toString(), needle, original, mode)) + return REASON_SEQ; + } + + return null; + } + + function passwdqc_check(params, newpass, oldpass, pw){ + var truncated, u_newpass, u_reversed, u_oldpass, + u_name, u_gecos, u_dir, reason, length; + + u_newpass = u_reversed = null; + u_oldpass = null; + u_name = u_gecos = u_dir = null; + + reason = REASON_ERROR; + + if (oldpass && oldpass == newpass) + return REASON_SAME; + + length = strlen(newpass); + + if (length < params.min[4]) + return REASON_SHORT; + + if (length > params.max) { + if (params.max == 8) { + truncated = newpass.substr(0, 8); + newpass = truncated; + if (oldpass && !oldpass.substr(0, 8) !== newpass.substr(0, 8)) + return REASON_SAME; + } else { + return REASON_LONG; + } + } + + if (is_simple(params, newpass, 0, 0)) { + reason = REASON_SIMPLE; + if (length < params.min[1] && params.min[1] <= params.max) + reason = REASON_SIMPLESHORT; + return reason; + } + + if (!(u_newpass = unify("", newpass))) + return reason; /* REASON_ERROR */ + if (!(u_reversed = reverse(u_newpass))) + return reason; + if (oldpass && !(u_oldpass = unify("", oldpass))) + return reason; + if (pw) { + if (!(u_name = unify("", pw.pw_name)) || + !(u_gecos = unify("", pw.pw_gecos)) || + !(u_dir = unify("", pw.pw_dir))) + return reason; + } + + if (oldpass && params.similar_deny && + (is_based(params, u_oldpass, u_newpass, newpass, 0) || + is_based(params, u_oldpass, u_reversed, newpass, 0x100))) + return REASON_SIMILAR; + + if (pw && + (is_based(params, u_name, u_newpass, newpass, 0) || + is_based(params, u_name, u_reversed, newpass, 0x100) || + is_based(params, u_gecos, u_newpass, newpass, 0) || + is_based(params, u_gecos, u_reversed, newpass, 0x100) || + is_based(params, u_dir, u_newpass, newpass, 0) || + is_based(params, u_dir, u_reversed, newpass, 0x100))) + return REASON_PERSONAL; + + reason = is_word_based(params, u_newpass, newpass, 0); + if (!reason) + reason = is_word_based(params, u_reversed, newpass, 0x100); + + return reason; + } + + function isascii(c){ + return /^[\x00-\x7F]?$/.test(c); + } + + function isdigit(c){ + return /^\d?$/.test(c); + } + + function islower(c){ + return isalpha(c) && c.toLowerCase() === c; + } + + function isupper(c){ + return isalpha(c) && c.toUpperCase() === c; + } + + function isalpha(c){ + return /^\w?$/.test(c) && c != '_' && /^\D?$/.test(c); + } + + function isspace(c){ + return /^\s?$/.test(c); + } + + function strlen(str){ + var length = str.length, count = 0, ch = 0; + for(var i=0; i < length; i++){ + ch = str.charCodeAt(i); + if(ch <= 127){ + count++; + }else if(ch <= 2047){ + count += 2; + }else if(ch <= 65535){ + count += 3; + }else if(ch <= 2097151){ + count += 4; + }else if(ch <= 67108863){ + count += 5; + }else{ + count += 6; + } + } + + return count; + } + + var REASON_ERROR = "check failed", + REASON_SAME = "is the same as the old one", + REASON_SIMILAR = "is based on the old one", + REASON_SHORT = "too short", + REASON_LONG = "too long", + REASON_SIMPLESHORT = "not enough different characters or classes for this length", + REASON_SIMPLE = "not enough different characters or classes", + REASON_PERSONAL = "based on personal login information", + REASON_WORD = "based on a dictionary word and not a passphrase", + REASON_SEQ = "based on a common sequence of characters and not a passphrase", + INT_MAX = 2147483647; + + var params = { + min: [INT_MAX, 24, 11, 8, 7], + max: 40, + passphrase_words: 3, + match_length: 4, + similar_deny: 1, + random_bits: 47, + flags: 3, + retry: 3 + } + + function check(newpass, oldpass, login, gecos, pms){ + return passwdqc_check(pms || params, newpass, oldpass, login ? { pw_name: login, pw_gecos: gecos } : login); + } + + exports.check = check; + + return exports; +});
\ No newline at end of file diff --git a/skins/standard/admin.css b/skins/standard/admin.css index 96bc4347c..fd0ff2808 100644 --- a/skins/standard/admin.css +++ b/skins/standard/admin.css @@ -337,68 +337,3 @@ input[disabled] { .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 f505268f5..41c2b09a9 100644 --- a/template/en/default/account/email/confirm-new.html.tmpl +++ b/template/en/default/account/email/confirm-new.html.tmpl @@ -26,13 +26,14 @@ javascript_urls = ['js/account.js'] onload = "document.forms['confirm_account_form'].realname.focus();" %] -[% password_complexity = Param('password_complexity') %] - <p> To create your account, you must enter a password in the form below. Your email address and Real Name (if provided) will be shown with changes you make. </p> +<p> + [% Param('passwdqc_desc') FILTER html_light %] +</p> <form id="confirm_account_form" method="post" action="token.cgi"> <input type="hidden" name="t" value="[% token FILTER html %]"> 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 68119252e..b17bd510d 100644 --- a/template/en/default/account/password/set-forgotten-password.html.tmpl +++ b/template/en/default/account/password/set-forgotten-password.html.tmpl @@ -25,7 +25,10 @@ %] <p> - To change your password, enter a new password twice: + To change your password, enter a new password twice. +</p> +<p> + [% Param('passwdqc_desc') FILTER html_light %] </p> <div class="flex"> diff --git a/template/en/default/account/prefs/account.html.tmpl b/template/en/default/account/prefs/account.html.tmpl index c41ea116f..1e7bc25db 100644 --- a/template/en/default/account/prefs/account.html.tmpl +++ b/template/en/default/account/prefs/account.html.tmpl @@ -71,6 +71,12 @@ </tr> [% IF user.authorizer.can_change_password %] <tr> + <td> </td> + <td> + [% Param('passwdqc_desc') FILTER html_light %] + </td> + </tr> + <tr> <th align="right">New password:</th> <td> <input type="password" name="new_password1" id="new_password1"> diff --git a/template/en/default/account/reset-password.html.tmpl b/template/en/default/account/reset-password.html.tmpl index ec57f19dd..ca60c5772 100644 --- a/template/en/default/account/reset-password.html.tmpl +++ b/template/en/default/account/reset-password.html.tmpl @@ -75,6 +75,10 @@ $(function() { [% user.password_change_reason || "You are required to update your password." FILTER html %] </p> +<p> + [% Param('passwdqc_desc') FILTER html_light %] +</p> + <form method="POST" action="reset_password.cgi"> <input type="hidden" name="token" value="[% token FILTER html %]"> <input type="hidden" name="do_save" value="1"> diff --git a/template/en/default/admin/params/auth.html.tmpl b/template/en/default/admin/params/auth.html.tmpl index e19712351..72fcc7e55 100644 --- a/template/en/default/admin/params/auth.html.tmpl +++ b/template/en/default/admin/params/auth.html.tmpl @@ -87,6 +87,10 @@ The size of randomly-generated passphrases in bits (24 to 85). [% END %] +[% desc_passwdqc_desc = BLOCK %] + This should be a short paragraph describing the password requirements in plain English. + Limited HTML allowed. +[% END %] [% param_descs = { auth_env_id => "Environment variable used by external authentication system " _ @@ -197,11 +201,12 @@ "will be permitted to create their own accounts and all accounts " _ "will have to be created by an administrator.", - passwdqc_min => desc_passwdqc_min, + passwdqc_min => desc_passwdqc_min, passwdqc_max => desc_passwdqc_max passwdqc_passphrase_words => desc_passwdqc_passphrase_words, passwdqc_match_length => desc_passwdqc_match_length, passwdqc_random_bits => desc_random_bits, + passwdqc_desc => desc_passwdqc_desc, password_complexity => "Set the complexity required for passwords. In all cases must the passwords " _ diff --git a/template/en/default/global/password-features.html.tmpl b/template/en/default/global/password-features.html.tmpl index ab7ae1d81..60196568f 100644 --- a/template/en/default/global/password-features.html.tmpl +++ b/template/en/default/global/password-features.html.tmpl @@ -6,22 +6,6 @@ # 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="no_constraints"> - 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> +<span id="password-features" + data-password-page="[% password_page FILTER html %]"> +</span> |