login: Add show password feature to login page.

The show password feature is a functionality to
toggle the visibility of the password fields in forms
so that one can check if they have entered the correct
password or not. We implement this using an eye icon
toggling which converts input field type from password
to text and vice-versa.
Fixes part of #17301.
This commit is contained in:
Gaurav Pandey
2021-04-05 13:12:29 +05:30
committed by Tim Abbott
parent 2ceda13e31
commit fa235e60ff
6 changed files with 87 additions and 2 deletions

View File

@@ -221,4 +221,14 @@ to test 2FA in development, make sure that you login using a
password. You can get the passwords for the default test users using password. You can get the passwords for the default test users using
`./manage.py print_initial_password`. `./manage.py print_initial_password`.
## Password form implementation
By default, Zulip uses `autocomplete=off` for password fields where we
enter the current password, and `autocomplete="new-password"` for
password fields where we create a new account or change the existing
password. This prevents the browser from auto-filling the existing
password.
Visit <https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete> for more details.
[0]: https://github.com/Bouke/django-two-factor-auth [0]: https://github.com/Bouke/django-two-factor-auth

View File

@@ -128,3 +128,33 @@ run_test("adjust_mac_shortcuts mac", (override) => {
assert.equal(test_item.stub.text(), test_item.mac_key); assert.equal(test_item.stub.text(), test_item.mac_key);
} }
}); });
run_test("show password", () => {
const password_selector = ".password_visibility_toggle";
function set_attribute(type) {
$("#id_password").attr("type", type);
}
function check_assertion(type, present_class, absent_class) {
assert.equal($("#id_password").attr("type"), type);
assert($(password_selector).hasClass(present_class));
assert(!$(password_selector).hasClass(absent_class));
}
const ev = {
preventDefault: () => {},
stopPropagation: () => {},
};
set_attribute("password");
common.setup_password_visibility_toggle("#id_password", password_selector);
const handler = $(password_selector).get_on_handler("click");
handler(ev);
check_assertion("text", "fa-eye", "fa-eye-slash");
handler(ev);
check_assertion("password", "fa-eye-slash", "fa-eye");
});

View File

@@ -134,3 +134,27 @@ export function adjust_mac_shortcuts(key_elem_class, require_cmd_style) {
$(this).text(key_text); $(this).text(key_text);
}); });
} }
// See https://zulip.readthedocs.io/en/latest/development/authentication.html#password-form-implementation
// for design details on this feature.
//
// This toggle code would probably be cleaner as 2 functions.
function toggle_password_visibility(password_field_id, password_selector) {
const password_field = $(password_field_id);
if (password_field.attr("type") === "password") {
password_field.attr("type", "text");
$(password_selector).removeClass("fa-eye-slash").addClass("fa-eye");
} else {
password_field.attr("type", "password");
$(password_selector).removeClass("fa-eye").addClass("fa-eye-slash");
}
}
export function setup_password_visibility_toggle(password_field_id, password_selector) {
$(password_selector).on("click", (e) => {
e.preventDefault();
e.stopPropagation();
toggle_password_visibility(password_field_id, password_selector);
});
}

View File

@@ -24,6 +24,11 @@ $(() => {
}); });
} }
common.setup_password_visibility_toggle(
"#id_password",
"#id_password ~ .password_visibility_toggle",
);
function highlight(class_to_add) { function highlight(class_to_add) {
// Set a class on the enclosing control group. // Set a class on the enclosing control group.
return function (element) { return function (element) {

View File

@@ -120,3 +120,18 @@ i.zulip-icon.bot {
*/ */
font-size: 12px; font-size: 12px;
} }
.password-div {
position: relative;
.password_visibility_toggle {
position: absolute;
right: 10px;
top: 42px;
opacity: 0.6;
&:hover {
opacity: 1;
}
}
}

View File

@@ -67,11 +67,12 @@ page can be easily identified in it's respective JavaScript file. -->
</label> </label>
</div> </div>
<div class="input-box no-validation"> <div class="input-box no-validation password-div">
<input id="id_password" name="password" class="required" type="password" <input id="id_password" name="password" class="required" type="password" autocomplete="off"
{% if email %} autofocus {% endif %} {% if email %} autofocus {% endif %}
required /> required />
<label for="id_password" class="control-label">{{ _('Password') }}</label> <label for="id_password" class="control-label">{{ _('Password') }}</label>
<i class="fa fa-eye-slash password_visibility_toggle" role="button" title="{{ _('Toggle password visibility') }}" aria-label="{{ _('Toggle password visibility') }}"></i>
</div> </div>
{% else %} {% else %}
{% include "two_factor/_wizard_forms.html" %} {% include "two_factor/_wizard_forms.html" %}