Toggle visibility of password input fields in login-ftl-based pages

Closes #22067
This commit is contained in:
Andreas Blaettlinger 2023-07-28 11:48:51 +02:00 committed by Pedro Igor
parent 722422a0cf
commit 86c0e338d9
11 changed files with 209 additions and 92 deletions

View file

@ -40,3 +40,35 @@ from the same list of values.
In this release, the role and client mappers now map to a single value from the effective roles of a user when In this release, the role and client mappers now map to a single value from the effective roles of a user when
they are marked as single-valued (`Multivalued` disabled). they are marked as single-valued (`Multivalued` disabled).
= Changes to password fields in Login UI
In this version we want to introduce a toggle to hide/show password inputs.
.Affected pages:
- login.ftl
- login-password.ftl
- login-update-password.ftl
- register.ftl
- register-user-profile.ftl
In general all `<input type="password" name="password" />` are encapsulated within a div now. The input element is followed by a button which toggles the visibility of the password input.
Old code example:
[source,html]
----
<input type="password" id="password" name="password" autocomplete="current-password" style="display:none;"/>
----
New code example:
[source,html]
----
<div class="${properties.kcInputGroup!}">
<input type="password" id="password" name="password" autocomplete="current-password" style="display:none;"/>
<button class="pf-c-button pf-m-control" type="button" aria-label="${msg('showPassword')}"
aria-controls="password" data-password-toggle
data-label-show="${msg('showPassword')}" data-label-hide="${msg('hidePassword')}">
<i class="fa fa-eye" aria-hidden="true"></i>
</button>
</div>
----

View file

@ -284,12 +284,12 @@ public class RegisterWithUserProfileTest extends RegisterTest {
); );
Assert.assertTrue( Assert.assertTrue(
driver.findElement( driver.findElement(
By.cssSelector("form#kc-register-form > div:nth-child(4) > div:nth-child(2) > input#password") By.cssSelector("#password")
).isDisplayed() ).isDisplayed()
); );
Assert.assertTrue( Assert.assertTrue(
driver.findElement( driver.findElement(
By.cssSelector("form#kc-register-form > div:nth-child(5) > div:nth-child(2) > input#password-confirm") By.cssSelector("#password-confirm")
).isDisplayed() ).isDisplayed()
); );
Assert.assertTrue( Assert.assertTrue(
@ -434,12 +434,12 @@ public class RegisterWithUserProfileTest extends RegisterTest {
); );
Assert.assertTrue( Assert.assertTrue(
driver.findElement( driver.findElement(
By.cssSelector("form#kc-register-form > div:nth-child(3) > div:nth-child(2) > input#password") By.cssSelector("#password")
).isDisplayed() ).isDisplayed()
); );
Assert.assertTrue( Assert.assertTrue(
driver.findElement( driver.findElement(
By.cssSelector("form#"+htmlFormId+" > div:nth-child(4) > div:nth-child(2) > input#password-confirm") By.cssSelector("#password-confirm")
).isDisplayed() ).isDisplayed()
); );
Assert.assertTrue( Assert.assertTrue(

View file

@ -10,10 +10,17 @@
<div class="${properties.kcFormGroupClass!} no-bottom-margin"> <div class="${properties.kcFormGroupClass!} no-bottom-margin">
<hr/> <hr/>
<label for="password" class="${properties.kcLabelClass!}">${msg("password")}</label> <label for="password" class="${properties.kcLabelClass!}">${msg("password")}</label>
<div class="${properties.kcInputGroup!}">
<input tabindex="2" id="password" class="${properties.kcInputClass!}" name="password" <input tabindex="2" id="password" class="${properties.kcInputClass!}" name="password"
type="password" autocomplete="on" autofocus type="password" autocomplete="on" autofocus
aria-invalid="<#if messagesPerField.existsError('password')>true</#if>" aria-invalid="<#if messagesPerField.existsError('password')>true</#if>"
/> />
<button class="pf-c-button pf-m-control" type="button" aria-label="${msg('showPassword')}"
aria-controls="password" data-password-toggle
data-label-show="${msg('showPassword')}" data-label-hide="${msg('hidePassword')}">
<i class="fa fa-eye" aria-hidden="true"></i>
</button>
</div>
<#if messagesPerField.existsError('password')> <#if messagesPerField.existsError('password')>
<span id="input-error-password" class="${properties.kcInputErrorMessageClass!}" aria-live="polite"> <span id="input-error-password" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
${kcSanitize(messagesPerField.get('password'))?no_esc} ${kcSanitize(messagesPerField.get('password'))?no_esc}
@ -38,6 +45,7 @@
</form> </form>
</div> </div>
</div> </div>
<script type="module" src="${url.resourcesPath}/js/passwordVisibility.js"></script>
</#if> </#if>
</@layout.registrationLayout> </@layout.registrationLayout>

View file

@ -7,17 +7,31 @@
<form id="kc-passwd-update-form" class="${properties.kcFormClass!}" action="${url.loginAction}" method="post"> <form id="kc-passwd-update-form" class="${properties.kcFormClass!}" action="${url.loginAction}" method="post">
<input type="text" id="username" name="username" value="${username}" autocomplete="username" <input type="text" id="username" name="username" value="${username}" autocomplete="username"
readonly="readonly" style="display:none;"/> readonly="readonly" style="display:none;"/>
<div class="${properties.kcInputGroup!}">
<input type="password" id="password" name="password" autocomplete="current-password" style="display:none;"/> <input type="password" id="password" name="password" autocomplete="current-password" style="display:none;"/>
<button class="pf-c-button pf-m-control" type="button" aria-label="${msg('showPassword')}"
aria-controls="password" data-password-toggle
data-label-show="${msg('showPassword')}" data-label-hide="${msg('hidePassword')}">
<i class="fa fa-eye" aria-hidden="true"></i>
</button>
</div>
<div class="${properties.kcFormGroupClass!}"> <div class="${properties.kcFormGroupClass!}">
<div class="${properties.kcLabelWrapperClass!}"> <div class="${properties.kcLabelWrapperClass!}">
<label for="password-new" class="${properties.kcLabelClass!}">${msg("passwordNew")}</label> <label for="password-new" class="${properties.kcLabelClass!}">${msg("passwordNew")}</label>
</div> </div>
<div class="${properties.kcInputWrapperClass!}"> <div class="${properties.kcInputWrapperClass!}">
<div class="${properties.kcInputGroup!}">
<input type="password" id="password-new" name="password-new" class="${properties.kcInputClass!}" <input type="password" id="password-new" name="password-new" class="${properties.kcInputClass!}"
autofocus autocomplete="new-password" autofocus autocomplete="new-password"
aria-invalid="<#if messagesPerField.existsError('password','password-confirm')>true</#if>" aria-invalid="<#if messagesPerField.existsError('password','password-confirm')>true</#if>"
/> />
<button class="pf-c-button pf-m-control" type="button" aria-label="${msg('showPassword')}"
aria-controls="password-new" data-password-toggle
data-label-show="${msg('showPassword')}" data-label-hide="${msg('hidePassword')}">
<i class="fa fa-eye" aria-hidden="true"></i>
</button>
</div>
<#if messagesPerField.existsError('password')> <#if messagesPerField.existsError('password')>
<span id="input-error-password" class="${properties.kcInputErrorMessageClass!}" aria-live="polite"> <span id="input-error-password" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
@ -32,11 +46,18 @@
<label for="password-confirm" class="${properties.kcLabelClass!}">${msg("passwordConfirm")}</label> <label for="password-confirm" class="${properties.kcLabelClass!}">${msg("passwordConfirm")}</label>
</div> </div>
<div class="${properties.kcInputWrapperClass!}"> <div class="${properties.kcInputWrapperClass!}">
<div class="${properties.kcInputGroup!}">
<input type="password" id="password-confirm" name="password-confirm" <input type="password" id="password-confirm" name="password-confirm"
class="${properties.kcInputClass!}" class="${properties.kcInputClass!}"
autocomplete="new-password" autocomplete="new-password"
aria-invalid="<#if messagesPerField.existsError('password-confirm')>true</#if>" aria-invalid="<#if messagesPerField.existsError('password-confirm')>true</#if>"
/> />
<button class="pf-c-button pf-m-control" type="button" aria-label="${msg('showPassword')}"
aria-controls="password-confirm" data-password-toggle
data-label-show="${msg('showPassword')}" data-label-hide="${msg('hidePassword')}">
<i class="fa fa-eye" aria-hidden="true"></i>
</button>
</div>
<#if messagesPerField.existsError('password-confirm')> <#if messagesPerField.existsError('password-confirm')>
<span id="input-error-password-confirm" class="${properties.kcInputErrorMessageClass!}" aria-live="polite"> <span id="input-error-password-confirm" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
@ -60,5 +81,6 @@
</div> </div>
</div> </div>
</form> </form>
<script type="module" src="${url.resourcesPath}/js/passwordVisibility.js"></script>
</#if> </#if>
</@layout.registrationLayout> </@layout.registrationLayout>

View file

@ -27,9 +27,16 @@
<div class="${properties.kcFormGroupClass!}"> <div class="${properties.kcFormGroupClass!}">
<label for="password" class="${properties.kcLabelClass!}">${msg("password")}</label> <label for="password" class="${properties.kcLabelClass!}">${msg("password")}</label>
<div class="${properties.kcInputGroup!}">
<input tabindex="2" id="password" class="${properties.kcInputClass!}" name="password" type="password" autocomplete="off" <input tabindex="2" id="password" class="${properties.kcInputClass!}" name="password" type="password" autocomplete="off"
aria-invalid="<#if messagesPerField.existsError('username','password')>true</#if>" aria-invalid="<#if messagesPerField.existsError('username','password')>true</#if>"
/> />
<button class="pf-c-button pf-m-control" type="button" aria-label="${msg("showPassword")}"
aria-controls="password" data-password-toggle
data-label-show="${msg('showPassword')}" data-label-hide="${msg('hidePassword')}">
<i class="fa fa-eye" aria-hidden="true"></i>
</button>
</div>
<#if usernameHidden?? && messagesPerField.existsError('username','password')> <#if usernameHidden?? && messagesPerField.existsError('username','password')>
<span id="input-error" class="${properties.kcInputErrorMessageClass!}" aria-live="polite"> <span id="input-error" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
@ -68,8 +75,8 @@
</form> </form>
</#if> </#if>
</div> </div>
</div> </div>
<script type="module" src="${url.resourcesPath}/js/passwordVisibility.js"></script>
<#elseif section = "info" > <#elseif section = "info" >
<#if realm.password && realm.registrationAllowed && !registrationDisabled??> <#if realm.password && realm.registrationAllowed && !registrationDisabled??>
<div id="kc-registration-container"> <div id="kc-registration-container">

View file

@ -88,6 +88,8 @@ password=Password
passwordConfirm=Confirm password passwordConfirm=Confirm password
passwordNew=New Password passwordNew=New Password
passwordNewConfirm=New Password confirmation passwordNewConfirm=New Password confirmation
hidePassword=Hide password
showPassword=Show password
rememberMe=Remember me rememberMe=Remember me
authenticatorCode=One-time code authenticatorCode=One-time code
address=Address address=Address

View file

@ -16,10 +16,17 @@
<label for="password" class="${properties.kcLabelClass!}">${msg("password")}</label> * <label for="password" class="${properties.kcLabelClass!}">${msg("password")}</label> *
</div> </div>
<div class="${properties.kcInputWrapperClass!}"> <div class="${properties.kcInputWrapperClass!}">
<div class="${properties.kcInputGroup!}">
<input type="password" id="password" class="${properties.kcInputClass!}" name="password" <input type="password" id="password" class="${properties.kcInputClass!}" name="password"
autocomplete="new-password" autocomplete="new-password"
aria-invalid="<#if messagesPerField.existsError('password','password-confirm')>true</#if>" aria-invalid="<#if messagesPerField.existsError('password','password-confirm')>true</#if>"
/> />
<button class="pf-c-button pf-m-control" type="button" aria-label="${msg('showPassword')}"
aria-controls="password" data-password-toggle
data-label-show="${msg('showPassword')}" data-label-hide="${msg('hidePassword')}">
<i class="fa fa-eye" aria-hidden="true"></i>
</button>
</div>
<#if messagesPerField.existsError('password')> <#if messagesPerField.existsError('password')>
<span id="input-error-password" class="${properties.kcInputErrorMessageClass!}" aria-live="polite"> <span id="input-error-password" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
@ -35,10 +42,17 @@
class="${properties.kcLabelClass!}">${msg("passwordConfirm")}</label> * class="${properties.kcLabelClass!}">${msg("passwordConfirm")}</label> *
</div> </div>
<div class="${properties.kcInputWrapperClass!}"> <div class="${properties.kcInputWrapperClass!}">
<div class="${properties.kcInputGroup!}">
<input type="password" id="password-confirm" class="${properties.kcInputClass!}" <input type="password" id="password-confirm" class="${properties.kcInputClass!}"
name="password-confirm" name="password-confirm"
aria-invalid="<#if messagesPerField.existsError('password-confirm')>true</#if>" aria-invalid="<#if messagesPerField.existsError('password-confirm')>true</#if>"
/> />
<button class="pf-c-button pf-m-control" type="button" aria-label="${msg('showPassword')}"
aria-controls="password-confirm" data-password-toggle
data-label-show="${msg('showPassword')}" data-label-hide="${msg('hidePassword')}">
<i class="fa fa-eye" aria-hidden="true"></i>
</button>
</div>
<#if messagesPerField.existsError('password-confirm')> <#if messagesPerField.existsError('password-confirm')>
<span id="input-error-password-confirm" class="${properties.kcInputErrorMessageClass!}" aria-live="polite"> <span id="input-error-password-confirm" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
@ -73,5 +87,6 @@
</div> </div>
</div> </div>
</form> </form>
<script type="module" src="${url.resourcesPath}/js/passwordVisibility.js"></script>
</#if> </#if>
</@layout.registrationLayout> </@layout.registrationLayout>

View file

@ -85,10 +85,18 @@
<label for="password" class="${properties.kcLabelClass!}">${msg("password")}</label> <label for="password" class="${properties.kcLabelClass!}">${msg("password")}</label>
</div> </div>
<div class="${properties.kcInputWrapperClass!}"> <div class="${properties.kcInputWrapperClass!}">
<div class="${properties.kcInputGroup!}">
<input type="password" id="password" class="${properties.kcInputClass!}" name="password" <input type="password" id="password" class="${properties.kcInputClass!}" name="password"
autocomplete="new-password" autocomplete="new-password"
aria-invalid="<#if messagesPerField.existsError('password','password-confirm')>true</#if>" aria-invalid="<#if messagesPerField.existsError('password','password-confirm')>true</#if>"
/> />
<button class="pf-c-button pf-m-control" type="button" aria-label="${msg('showPassword')}"
aria-controls="password" data-password-toggle
data-label-show="${msg('showPassword')}" data-label-hide="${msg('hidePassword')}">
<i class="fa fa-eye" aria-hidden="true"></i>
</button>
</div>
<#if messagesPerField.existsError('password')> <#if messagesPerField.existsError('password')>
<span id="input-error-password" class="${properties.kcInputErrorMessageClass!}" aria-live="polite"> <span id="input-error-password" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
@ -104,10 +112,17 @@
class="${properties.kcLabelClass!}">${msg("passwordConfirm")}</label> class="${properties.kcLabelClass!}">${msg("passwordConfirm")}</label>
</div> </div>
<div class="${properties.kcInputWrapperClass!}"> <div class="${properties.kcInputWrapperClass!}">
<div class="${properties.kcInputGroup!}">
<input type="password" id="password-confirm" class="${properties.kcInputClass!}" <input type="password" id="password-confirm" class="${properties.kcInputClass!}"
name="password-confirm" name="password-confirm"
aria-invalid="<#if messagesPerField.existsError('password-confirm')>true</#if>" aria-invalid="<#if messagesPerField.existsError('password-confirm')>true</#if>"
/> />
<button class="pf-c-button pf-m-control" type="button" aria-label="${msg('showPassword')}"
aria-controls="password-confirm" data-password-toggle
data-label-show="${msg('showPassword')}" data-label-hide="${msg('hidePassword')}">
<i class="fa fa-eye" aria-hidden="true"></i>
</button>
</div>
<#if messagesPerField.existsError('password-confirm')> <#if messagesPerField.existsError('password-confirm')>
<span id="input-error-password-confirm" class="${properties.kcInputErrorMessageClass!}" aria-live="polite"> <span id="input-error-password-confirm" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
@ -140,5 +155,6 @@
</div> </div>
</div> </div>
</form> </form>
<script type="module" src="${url.resourcesPath}/js/passwordVisibility.js"></script>
</#if> </#if>
</@layout.registrationLayout> </@layout.registrationLayout>

View file

@ -0,0 +1,15 @@
const toggle = (button) => {
const passwordElement = document.getElementById(button.getAttribute('aria-controls'));
if (passwordElement.type === "password") {
passwordElement.type = "text";
button.children.item(0).classList.replace("fa-eye", "fa-eye-slash");
button.setAttribute("aria-label", button.dataset.labelHide);
} else if(passwordElement.type === "text") {
passwordElement.type = "password";
button.children.item(0).classList.replace("fa-eye-slash", "fa-eye");
button.setAttribute("aria-label", button.dataset.labelShow);
}
}
document.querySelectorAll('[data-password-toggle]')
.forEach(button => button.onclick = () => toggle(button));

View file

@ -24,7 +24,6 @@ p.instruction {
} }
.pf-c-button.pf-m-control { .pf-c-button.pf-m-control {
border: solid var(--pf-global--BorderWidth--sm);
border-color: rgba(230, 230, 230, 0.5); border-color: rgba(230, 230, 230, 0.5);
} }

View file

@ -75,6 +75,7 @@ kcInputClassCheckboxInput=pf-c-check__input
kcInputClassCheckboxLabel=pf-c-check__label kcInputClassCheckboxLabel=pf-c-check__label
kcInputClassRadioCheckboxLabelDisabled=pf-m-disabled kcInputClassRadioCheckboxLabelDisabled=pf-m-disabled
kcInputErrorMessageClass=pf-c-form__helper-text pf-m-error required kc-feedback-text kcInputErrorMessageClass=pf-c-form__helper-text pf-m-error required kc-feedback-text
kcInputGroup=pf-c-input-group
kcInputWrapperClass=col-xs-12 col-sm-12 col-md-12 col-lg-12 kcInputWrapperClass=col-xs-12 col-sm-12 col-md-12 col-lg-12
kcFormOptionsClass=col-xs-12 col-sm-12 col-md-12 col-lg-12 kcFormOptionsClass=col-xs-12 col-sm-12 col-md-12 col-lg-12
kcFormButtonsClass=col-xs-12 col-sm-12 col-md-12 col-lg-12 kcFormButtonsClass=col-xs-12 col-sm-12 col-md-12 col-lg-12