password: Add password limit inidcator to form.

Fixes: #27922.
This commit is contained in:
Maneesh Shukla
2024-12-10 15:35:37 +05:30
committed by Tim Abbott
parent 4362c8d3c9
commit 16188400f7
6 changed files with 41 additions and 6 deletions

View File

@@ -145,8 +145,8 @@ Form is validated both client-side using jquery-validation (see signup.js) and s
<div class="input-box password-div">
<input id="id_password" class="required" type="password" name="password" autocomplete="new-password"
value="{% if form.password.value() %}{{ form.password.value() }}{% endif %}"
maxlength="{{ MAX_PASSWORD_LENGTH }}"
data-min-length="{{password_min_length}}"
data-max-length="{{ password_max_length }}"
data-min-guesses="{{password_min_guesses}}" required />
<label for="id_password" class="inline-block">{{ _('Password') }}</label>
<i class="fa fa-eye-slash password_visibility_toggle" role="button" tabindex="0"></i>

View File

@@ -31,7 +31,7 @@
<label for="id_new_password1" class="">{{ _('Password') }}</label>
<input id="id_new_password1" class="required" type="password" name="new_password1" autocomplete="new-password"
value="{% if form.new_password1.value() %}{{ form.new_password1.value() }}{% endif %}"
maxlength="100"
data-max-length="{{ password_max_length }}"
data-min-length="{{password_min_length}}"
data-min-guesses="{{password_min_guesses}}" autofocus required />
<i class="fa fa-eye-slash password_visibility_toggle" role="button" tabindex="0"></i>

View File

@@ -25,10 +25,14 @@ export function password_quality(
$password_field: JQuery,
): boolean {
const min_length = Number($password_field.attr("data-min-length"));
const max_length = Number($password_field.attr("data-max-length"));
const min_guesses = Number($password_field.attr("data-min-guesses"));
const result = zxcvbn(password);
const acceptable = password.length >= min_length && result.guesses >= min_guesses;
const acceptable =
password.length >= min_length &&
password.length <= max_length &&
result.guesses >= min_guesses;
if ($bar !== undefined) {
const t = result.crackTimesSeconds.offlineSlowHashing1e4PerSecond;
@@ -52,12 +56,20 @@ export function password_quality(
export function password_warning(password: string, $password_field: JQuery): string {
const min_length = Number($password_field.attr("data-min-length"));
const max_length = Number($password_field.attr("data-max-length"));
if (password.length < min_length) {
return $t(
{defaultMessage: "Password should be at least {length} characters long"},
{length: min_length},
);
} else if (password.length > max_length) {
return $t(
{
defaultMessage: "Maximum password length: {max} characters.",
},
{max: max_length},
);
}
return zxcvbn(password).feedback.warning ?? $t({defaultMessage: "Password is too weak"});
}

View File

@@ -478,6 +478,19 @@ export function set_up(): void {
);
return false;
}
const max_length = realm.password_max_length;
if (new_password && new_password.toString().length > max_length) {
ui_report.error(
$t_html(
{defaultMessage: "Maximum password length: {max_length} characters"},
{max_length},
),
undefined,
$("#dialog_error"),
);
return false;
}
return true;
}

View File

@@ -11,7 +11,7 @@
<label for="new_password" class="modal-field-label">{{t "New password" }}</label>
<div class="password-input-row">
<input type="password" autocomplete="new-password" name="new_password" id="new_password" class="inline-block modal_password_input" value=""
data-min-length="{{password_min_length}}" data-min-guesses="{{password_min_guesses}}" />
data-min-length="{{password_min_length}}" data-min-guesses="{{password_min_guesses}}" data-max-length="{{ password_max_length }}" />
<i class="fa fa-eye-slash password_visibility_toggle tippy-zulip-tooltip" role="button" tabindex="0"></i>
</div>
<div class="progress inline-block" id="pw_strength">

View File

@@ -7,7 +7,7 @@ const {run_test} = require("./lib/test.cjs");
const {password_quality, password_warning} = zrequire("password_quality");
function password_field(min_length, min_guesses) {
function password_field(min_length, max_length, min_guesses) {
const self = {};
self.attr = (name) => {
@@ -16,6 +16,8 @@ function password_field(min_length, min_guesses) {
return min_length;
case "data-min-guesses":
return min_guesses;
case "data-max-length":
return max_length;
/* istanbul ignore next */
default:
throw new Error(`Unknown attribute ${name}`);
@@ -60,7 +62,7 @@ run_test("basics w/progress bar", () => {
assert.equal(warning, "translated: Password should be at least 10 characters long");
password = "foo";
accepted = password_quality(password, $bar, password_field(2, 200));
accepted = password_quality(password, $bar, password_field(2, 200, 10));
assert.ok(accepted);
assert.equal($bar.w, "10.390277164940581%");
assert.equal($bar.added_class, "bar-success");
@@ -73,4 +75,12 @@ run_test("basics w/progress bar", () => {
assert.equal($bar.added_class, "bar-danger");
warning = password_warning(password, password_field(6));
assert.equal(warning, 'Repeated characters like "aaa" are easy to guess.');
// Test a password that's longer than the configured limit.
password = "hfHeo34FksdBChjeruShJ@sidfgusd";
accepted = password_quality(password, $bar, password_field(6, 20, 1e20));
assert.ok(!accepted);
assert.equal($bar.added_class, "bar-danger");
warning = password_warning(password, password_field(6, 20, 1e20));
assert.equal(warning, `translated: Maximum password length: 20 characters.`);
});