MDL-79769 login: Add password toggle icon to the login screen

This commit is contained in:
David Woloszyn
2024-03-08 15:54:11 +11:00
parent b2fa19f45d
commit cb6acfc46a
15 changed files with 325 additions and 1 deletions
+12
View File
@@ -135,6 +135,18 @@ if ($hassiteconfig) {
$temp->add($setting);
$ADMIN->add('authsettings', $temp);
// Toggle password visiblity icon.
$temp->add(new admin_setting_configselect('loginpasswordtoggle',
new lang_string('auth_loginpasswordtoggle', 'auth'),
new lang_string('auth_loginpasswordtoggle_desc', 'auth'),
TOGGLE_SENSITIVE_SMALL_SCREENS_ONLY,
[
TOGGLE_SENSITIVE_DISABLED => get_string('disabled', 'admin'),
TOGGLE_SENSITIVE_ENABLED => get_string('enabled', 'admin'),
TOGGLE_SENSITIVE_SMALL_SCREENS_ONLY => get_string('smallscreensonly', 'admin'),
],
));
$temp = new admin_externalpage('authtestsettings', get_string('testsettings', 'core_auth'), new moodle_url("/auth/test_settings.php"), 'moodle/site:config', true);
$ADMIN->add('authsettings', $temp);
+11
View File
@@ -75,6 +75,10 @@ class login implements renderable, templatable {
public $maintenance;
/** @var string ReCaptcha element HTML. */
public $recaptcha;
/** @var bool Toggle the password visibility icon. */
public $togglepassword;
/** @var bool Toggle the password visibility icon for small screens only. */
public $smallscreensonly;
/**
* Constructor.
@@ -130,6 +134,11 @@ class login implements renderable, templatable {
require_once($CFG->libdir . '/recaptchalib_v2.php');
$this->recaptcha = recaptcha_get_challenge_html(RECAPTCHA_API_URL, $CFG->recaptchapublickey);
}
// Toggle password visibility icon.
$this->togglepassword = get_config('core', 'loginpasswordtoggle') == TOGGLE_SENSITIVE_ENABLED ||
get_config('core', 'loginpasswordtoggle') == TOGGLE_SENSITIVE_SMALL_SCREENS_ONLY;
$this->smallscreensonly = get_config('core', 'loginpasswordtoggle') == TOGGLE_SENSITIVE_SMALL_SCREENS_ONLY;
}
/**
@@ -175,6 +184,8 @@ class login implements renderable, templatable {
$data->maintenance = format_text($this->maintenance, FORMAT_MOODLE);
$data->languagemenu = $this->languagemenu;
$data->recaptcha = $this->recaptcha;
$data->togglepassword = $this->togglepassword;
$data->smallscreensonly = $this->smallscreensonly;
return $data;
}
+18
View File
@@ -101,3 +101,21 @@ Feature: Test if the login form provides the correct feedback
And I press "Log in"
And I press the tab key
Then the focused element is "Username" "field"
Scenario: Display the password visibility toggle icon
Given the following config values are set as admin:
| loginpasswordtoggle | 1 |
When I follow "Log in"
Then "Toggle sensitive" "button" should be visible
And the following config values are set as admin:
| loginpasswordtoggle | 0 |
And I reload the page
And "Toggle sensitive" "button" should not be visible
Scenario: Display the password visibility toggle icon for small screens only
Given the following config values are set as admin:
| loginpasswordtoggle | 2 |
When I follow "Log in"
Then "Toggle sensitive" "button" should not be visible
And I change the viewport size to "mobile"
And "Toggle sensitive" "button" should be visible
+1
View File
@@ -1312,6 +1312,7 @@ $string['sixtyfourbitsrequired'] = 'It has been detected that your site is not u
$string['sixtyfourbitswarning'] = 'It has been detected that your site is not using a 64-bit PHP version. It is recommended that you upgrade your site to ensure future compatibility.';
$string['slasharguments'] = 'Use slash arguments';
$string['slashargumentswarning'] = 'It is recommended that the use of slash arguments is enabled. In future it will be required. For more details, see the documentation <a href="https://docs.moodle.org/en/admin/environment/slasharguments">Using slash arguments</a>.';
$string['smallscreensonly'] = 'Small screens only';
$string['smtp'] = 'SMTP';
$string['smtpauthtype'] = 'SMTP Auth Type';
$string['smtpdetail'] = 'Simple Mail Transfer Protocol (SMTP) settings for sending email.';
+2
View File
@@ -41,6 +41,8 @@ $string['auth_changepasswordurl_expl'] = 'Specify the url to send users who have
$string['auth_changingemailaddress'] = 'You have requested a change of email address, from {$a->oldemail} to {$a->newemail}. For security reasons, we are sending you an email message at the new address to confirm that it belongs to you. Your email address will be updated as soon as you open the URL sent to you in that message.';
$string['authinstructions'] = 'Leave this blank for the default login instructions to be displayed on the login page. If you want to provide custom login instructions, enter them here.';
$string['auth_invalidnewemailkey'] = 'Error: if you are trying to confirm a change of email address, you may have made a mistake in copying the URL we sent you by email. Please copy the address and try again.';
$string['auth_loginpasswordtoggle'] = 'Password visibility toggle';
$string['auth_loginpasswordtoggle_desc'] = 'Add an icon to the password field on the login screen that allows users to show or hide their entered password.';
$string['auth_loginrecaptcha'] = 'Enable reCAPTCHA for login';
$string['auth_loginrecaptcha_desc'] = 'Add a visual/audio confirmation form element to the login page. This reduces the risk of unwarranted login attempts. See <a target="_blank" href="https://www.google.com/recaptcha">Google reCAPTCHA</a> for more details. ';
$string['auth_multiplehosts'] = 'Multiple hosts OR addresses can be specified (eg host1.com;host2.com;host3.com) or (eg xxx.xxx.xxx.xxx;xxx.xxx.xxx.xxx)';
+1
View File
@@ -87,5 +87,6 @@ $string['suggestions'] = 'Suggestions';
$string['time'] = 'Time';
$string['timeunit'] = 'Time unit';
$string['timing'] = 'Timing';
$string['togglesensitive'] = 'Toggle sensitive';
$string['unmaskpassword'] = 'Unmask';
$string['year'] = 'Year';
+10
View File
@@ -0,0 +1,10 @@
define("core/togglesensitive",["exports","core/pagehelpers","core/templates","core/pending","core/prefetch","core/notification"],(function(_exports,_pagehelpers,_templates,_pending,_prefetch,_notification){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}
/**
* JS module for toggling the sensitive input visibility (e.g. passwords, keys).
*
* @module core/togglesensitive
* @copyright 2023 David Woloszyn <david.woloszyn@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_templates=_interopRequireDefault(_templates),_pending=_interopRequireDefault(_pending),_prefetch=_interopRequireDefault(_prefetch),_notification=_interopRequireDefault(_notification);const SELECTORS_BUTTON=".toggle-sensitive-btn",PIX_EYE="t/hide",PIX_EYE_SLASH="t/show";let sensitiveElementId,smallScreensOnly;_exports.init=function(elementId){let isSmallScreensOnly=arguments.length>1&&void 0!==arguments[1]&&arguments[1];const sensitiveInput=document.getElementById(elementId);null!==sensitiveInput&&(sensitiveElementId=elementId,smallScreensOnly=isSmallScreensOnly,_prefetch.default.prefetchTemplate("core/form_input_toggle_sensitive"),renderSensitiveToggle(sensitiveInput).then((()=>{})).catch(_notification.default.exception),registerListenerEvents())};const renderSensitiveToggle=async sensitiveInput=>{const{html:html}=await _templates.default.renderForPromise("core/form_input_toggle_sensitive",{smallscreensonly:smallScreensOnly,sensitiveinput:sensitiveInput.outerHTML});sensitiveInput.outerHTML=html},registerListenerEvents=()=>{document.addEventListener("click",handleButtonInteraction),smallScreensOnly&&window.addEventListener("resize",handleScreenResizing)},handleButtonInteraction=event=>{const toggleButton=event.target.closest(SELECTORS_BUTTON);if(toggleButton){const sensitiveInput=document.getElementById(sensitiveElementId);toggleSensitiveVisibility(sensitiveInput,toggleButton)}},handleScreenResizing=()=>{if(!(0,_pagehelpers.isExtraSmall)()){const sensitiveInput=document.getElementById(sensitiveElementId),toggleButton=sensitiveInput.parentNode.querySelector(SELECTORS_BUTTON);toggleSensitiveVisibility(sensitiveInput,toggleButton,!0)}},toggleSensitiveVisibility=function(sensitiveInput,toggleButton){let force=arguments.length>2&&void 0!==arguments[2]&&arguments[2];const pendingPromise=new _pending.default("core/togglesensitive:toggle");let type,icon;!0===force?(type="password",icon=PIX_EYE):(type="password"===sensitiveInput.getAttribute("type")?"text":"password",icon="password"===sensitiveInput.getAttribute("type")?PIX_EYE_SLASH:PIX_EYE),sensitiveInput.setAttribute("type",type),_templates.default.renderPix(icon,"core").then((icon=>{toggleButton.innerHTML=icon,pendingPromise.resolve()})).catch(_notification.default.exception)}}));
//# sourceMappingURL=togglesensitive.min.js.map
File diff suppressed because one or more lines are too long
+150
View File
@@ -0,0 +1,150 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* JS module for toggling the sensitive input visibility (e.g. passwords, keys).
*
* @module core/togglesensitive
* @copyright 2023 David Woloszyn <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import {isExtraSmall} from 'core/pagehelpers';
import Templates from 'core/templates';
import Pending from 'core/pending';
import Prefetch from 'core/prefetch';
import Notification from 'core/notification';
const SELECTORS = {
BUTTON: '.toggle-sensitive-btn',
ICON: '.toggle-sensitive-btn .icon',
};
const PIX = {
EYE: 't/hide',
EYE_SLASH: 't/show',
};
let sensitiveElementId;
let smallScreensOnly;
/**
* Entrypoint of the js.
*
* @method init
* @param {String} elementId Form button element.
* @param {boolean} isSmallScreensOnly Is this for small screens only?
*/
export const init = (elementId, isSmallScreensOnly = false) => {
const sensitiveInput = document.getElementById(elementId);
if (sensitiveInput === null) {
// Exit early if invalid element id passed.
return;
}
sensitiveElementId = elementId;
smallScreensOnly = isSmallScreensOnly;
Prefetch.prefetchTemplate('core/form_input_toggle_sensitive');
// Render the sensitive input with a toggle button.
renderSensitiveToggle(sensitiveInput).then(() => {
return;
}).catch(Notification.exception);
// Register event listeners.
registerListenerEvents();
};
/**
* Render the new input html with toggle button and update the incoming html.
*
* @method renderSensitiveToggle
* @param {HTMLElement} sensitiveInput HTML element for the sensitive input.
*/
const renderSensitiveToggle = async(sensitiveInput) => {
const {html} = await Templates.renderForPromise(
'core/form_input_toggle_sensitive',
{
smallscreensonly: smallScreensOnly,
sensitiveinput: sensitiveInput.outerHTML,
}
);
sensitiveInput.outerHTML = html;
};
/**
* Register event listeners.
*
* @method registerListenerEvents
*/
const registerListenerEvents = () => {
// Toggle the sensitive input visibility when interacting with the toggle button.
document.addEventListener('click', handleButtonInteraction);
// For small screens only, hide all sensitive inputs when the screen is enlarged.
if (smallScreensOnly) {
window.addEventListener('resize', handleScreenResizing);
}
};
/**
* Handle events trigger by interacting with the toggle button.
*
* @method handleButtonInteraction
* @param {Event} event The button event.
*/
const handleButtonInteraction = (event) => {
const toggleButton = event.target.closest(SELECTORS.BUTTON);
if (toggleButton) {
const sensitiveInput = document.getElementById(sensitiveElementId);
toggleSensitiveVisibility(sensitiveInput, toggleButton);
}
};
/**
* Handle events trigger by resizing the screen.
*
* @method handleScreenResizing
*/
const handleScreenResizing = () => {
if (!isExtraSmall()) {
const sensitiveInput = document.getElementById(sensitiveElementId);
const toggleButton = sensitiveInput.parentNode.querySelector(SELECTORS.BUTTON);
toggleSensitiveVisibility(sensitiveInput, toggleButton, true);
}
};
/**
* Toggle the sensitive input visibility and its associated icon.
*
* @method toggleSensitiveVisibility
* @param {HTMLInputElement} sensitiveInput The sensitive input element.
* @param {HTMLElement} toggleButton The toggle button.
* @param {boolean} force Force the input back to password type.
*/
const toggleSensitiveVisibility = (sensitiveInput, toggleButton, force = false) => {
const pendingPromise = new Pending('core/togglesensitive:toggle');
let type;
let icon;
if (force === true) {
type = 'password';
icon = PIX.EYE;
} else {
type = sensitiveInput.getAttribute('type') === 'password' ? 'text' : 'password';
icon = sensitiveInput.getAttribute('type') === 'password' ? PIX.EYE_SLASH : PIX.EYE;
}
sensitiveInput.setAttribute('type', type);
Templates.renderPix(icon, 'core').then((icon) => {
toggleButton.innerHTML = icon;
pendingPromise.resolve();
return;
}).catch(Notification.exception);
};
+15
View File
@@ -601,6 +601,21 @@ define('CONTACT_SUPPORT_ANYONE', 2);
*/
define('MAX_PASSWORD_CHARACTERS', 128);
/**
* Toggle sensitive feature is disabled. Used for sensitive inputs (passwords, tokens, keys).
*/
define('TOGGLE_SENSITIVE_DISABLED', 0);
/**
* Toggle sensitive feature is enabled. Used for sensitive inputs (passwords, tokens, keys).
*/
define('TOGGLE_SENSITIVE_ENABLED', 1);
/**
* Toggle sensitive feature is enabled for small screens only. Used for sensitive inputs (passwords, tokens, keys).
*/
define('TOGGLE_SENSITIVE_SMALL_SCREENS_ONLY', 2);
// PARAMETER HANDLING.
/**
@@ -0,0 +1,45 @@
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template core/form_input_toggle_sensitive
Moodle template for toggling senstive inputs.
Classes required for JS:
* none
Data attributes required for JS:
* none
Context variables required for this template:
* none
Example context (json):
{
"smallscreensonly": true,
"sensitiveinput": "<input type=\"password\" name=\"password\" id=\"password\">"
}
}}
<div class="toggle-sensitive-wrapper {{#smallscreensonly}}small-screens-only{{/smallscreensonly}} input-group mb-3">
{{{sensitiveinput}}}
<div class="input-group-append">
<button class="toggle-sensitive-btn btn btn-secondary" type="button" {{!
!}}aria-label="{{#str}}togglesensitive, form{{/str}}">
{{#pix}}t/hide, core{{/pix}}
</button>
</div>
</div>
+8 -1
View File
@@ -91,7 +91,9 @@
"sitename": "Beer & Chips",
"logintoken": "randomstring",
"maintenance": "For full access to this site, you need to login in as an admin.",
"languagemenu": "Choose language"
"languagemenu": "Choose language",
"togglepassword": true,
"smallscreensonly": true
}
}}
@@ -214,6 +216,11 @@
{{#js}}
{{^error}}
{{#togglepassword}}
require(['core/togglesensitive'], function(ToggleSensitive) {
ToggleSensitive.init("password", {{smallscreensonly}});
});
{{/togglepassword}}
{{#autofocusform}}
var userNameField = document.getElementById('username');
if (userNameField.value.length == 0) {
+21
View File
@@ -480,3 +480,24 @@ textarea[data-auto-rows] {
padding-left: 0;
}
}
// Remove the margins that come with the icon.
.toggle-sensitive-btn .icon {
margin-right: 0;
}
// Input group elements display border radius depending on the proximity of other elements.
// This corrects border radius of the input when 'small screen only' mode hides the toggle button.
@media (min-width: 576px) {
.toggle-sensitive-wrapper.small-screens-only.input-group:not(.has-validation) > {
.form-control:not(:last-child) {
@include border-radius($input-border-radius);
}
.form-control-lg:not(:last-child) {
@include border-radius($input-border-radius-lg);
}
.input-group-append .toggle-sensitive-btn {
display: none;
}
}
}
+15
View File
@@ -33262,6 +33262,21 @@ textarea[data-auto-rows] {
padding-left: 0;
}
.toggle-sensitive-btn .icon {
margin-right: 0;
}
@media (min-width: 576px) {
.toggle-sensitive-wrapper.small-screens-only.input-group:not(.has-validation) > .form-control:not(:last-child) {
border-radius: 0.5rem;
}
.toggle-sensitive-wrapper.small-screens-only.input-group:not(.has-validation) > .form-control-lg:not(:last-child) {
border-radius: 0.6rem;
}
.toggle-sensitive-wrapper.small-screens-only.input-group:not(.has-validation) > .input-group-append .toggle-sensitive-btn {
display: none;
}
}
.pagelayout-login #region-main {
border: 0;
background-color: inherit;
+15
View File
@@ -33262,6 +33262,21 @@ textarea[data-auto-rows] {
padding-left: 0;
}
.toggle-sensitive-btn .icon {
margin-right: 0;
}
@media (min-width: 576px) {
.toggle-sensitive-wrapper.small-screens-only.input-group:not(.has-validation) > .form-control:not(:last-child) {
border-radius: 0.25rem;
}
.toggle-sensitive-wrapper.small-screens-only.input-group:not(.has-validation) > .form-control-lg:not(:last-child) {
border-radius: 0.3rem;
}
.toggle-sensitive-wrapper.small-screens-only.input-group:not(.has-validation) > .input-group-append .toggle-sensitive-btn {
display: none;
}
}
.pagelayout-login #region-main {
border: 0;
background-color: inherit;