]> git.ipfire.org Git - thirdparty/bugzilla.git/commitdiff
Bug 1400949 - Password requirements not stated
authorDylan William Hardison <dylan@hardison.net>
Tue, 19 Sep 2017 15:48:11 +0000 (11:48 -0400)
committerGitHub <noreply@github.com>
Tue, 19 Sep 2017 15:48:11 +0000 (11:48 -0400)
Bugzilla/Config/Auth.pm
js/account.js
node_modules/passwdqc/src/passwdqc_check.js [new file with mode: 0644]
skins/standard/admin.css
template/en/default/account/email/confirm-new.html.tmpl
template/en/default/account/password/set-forgotten-password.html.tmpl
template/en/default/account/prefs/account.html.tmpl
template/en/default/account/reset-password.html.tmpl
template/en/default/admin/params/auth.html.tmpl
template/en/default/global/password-features.html.tmpl

index 612fd1f3fa609518f51cbe0bad456e3148770dbd..965d922d7f57dd58b84159bd68ab129280e021e7 100644 (file)
@@ -157,6 +157,12 @@ sub get_param_list {
             checker => \&_check_passwdqc_random_bits,
         },
 
+        {
+            name => 'passwdqc_desc',
+            type => 'l',
+            default => 'The password must be complex.',
+        },
+
         {
             name    => 'auth_delegation',
             type    => 'b',
index 8642cadbd751d026e9e97a8faef548b13a15d3ab..87e1e01f27794eaf051355127f25b3d6de7e81f6 100644 (file)
@@ -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 (file)
index 0000000..913bcc0
--- /dev/null
@@ -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
index 96bc4347c8eee962bb70a1a65e4f4c1057f4aa68..fd0ff2808efa9482508a573ad3689a3041627225 100644 (file)
@@ -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;
-}
index f505268f5b721e82a9986f3a72176ec95860efdb..41c2b09a93bc0addf89c7681dfe0b16e6680b550 100644 (file)
    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 %]">
index 68119252e8eb66d45bd3c7a67b7b373481ac2947..b17bd510d0761e7f54659bd1e7ce6dc62b4ec7b4 100644 (file)
  %]
 
 <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">
index c41ea116fac7ea6a417f0d8330cb3d3d97d84911..1e7bc25db164ace2999f05a75b1fbd6b2d1f177a 100644 (file)
           </td>
         </tr>
         [% IF user.authorizer.can_change_password %]
+          <tr>
+          <td>&nbsp;</td>
+          <td>
+            [% Param('passwdqc_desc') FILTER html_light %]
+          </td>
+          </tr>
           <tr>
             <th align="right">New password:</th>
             <td>
index ec57f19dd3c93105f5557f2d4e1ad595e20579b3..ca60c5772c25f7a3b85f487b01bd6e4e24851a55 100644 (file)
@@ -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">
index e197123519536922e331de2ea5c4658d60fe6c3a..72fcc7e552af15c973e351ddb7c03e35e4ee8196 100644 (file)
   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 " _
                        "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 " _
index ab7ae1d8187760fb7848bafec35cea5e343599f1..60196568f85326832337ad7d5c987df4862061de 100644 (file)
@@ -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>