[KEYCLOAK-12426] Add username to the login form + ability to reset login
This commit is contained in:
parent
85dc1b3653
commit
d3f6937a23
14 changed files with 121 additions and 50 deletions
|
@ -191,12 +191,7 @@ public abstract class AbstractUsernameFormAuthenticator extends AbstractFormAuth
|
|||
public boolean validatePassword(AuthenticationFlowContext context, UserModel user, MultivaluedMap<String, String> inputData, boolean clearUser) {
|
||||
String password = inputData.getFirst(CredentialRepresentation.PASSWORD);
|
||||
if (password == null || password.isEmpty()) {
|
||||
context.getEvent().user(user);
|
||||
context.getEvent().error(Errors.INVALID_USER_CREDENTIALS);
|
||||
Response challengeResponse = challenge(context, getDefaultChallengeMessage(context));
|
||||
context.forceChallenge(challengeResponse);
|
||||
context.clearUser();
|
||||
return false;
|
||||
return badPasswordHandler(context, user, clearUser,true);
|
||||
}
|
||||
|
||||
if (isTemporarilyDisabledByBruteForce(context, user)) return false;
|
||||
|
@ -204,17 +199,26 @@ public abstract class AbstractUsernameFormAuthenticator extends AbstractFormAuth
|
|||
if (password != null && !password.isEmpty() && context.getSession().userCredentialManager().isValid(context.getRealm(), user, UserCredentialModel.password(password))) {
|
||||
return true;
|
||||
} else {
|
||||
context.getEvent().user(user);
|
||||
context.getEvent().error(Errors.INVALID_USER_CREDENTIALS);
|
||||
Response challengeResponse = challenge(context, getDefaultChallengeMessage(context));
|
||||
context.failureChallenge(AuthenticationFlowError.INVALID_CREDENTIALS, challengeResponse);
|
||||
if (clearUser) {
|
||||
context.clearUser();
|
||||
}
|
||||
return false;
|
||||
return badPasswordHandler(context, user, clearUser,false);
|
||||
}
|
||||
}
|
||||
|
||||
// Set up AuthenticationFlowContext error.
|
||||
private boolean badPasswordHandler(AuthenticationFlowContext context, UserModel user, boolean clearUser,boolean isEmptyPassword) {
|
||||
context.getEvent().user(user);
|
||||
context.getEvent().error(Errors.INVALID_USER_CREDENTIALS);
|
||||
Response challengeResponse = challenge(context, getDefaultChallengeMessage(context));
|
||||
if(isEmptyPassword) {
|
||||
context.forceChallenge(challengeResponse);
|
||||
}else{
|
||||
context.failureChallenge(AuthenticationFlowError.INVALID_CREDENTIALS, challengeResponse);
|
||||
}
|
||||
|
||||
if (clearUser) {
|
||||
context.clearUser();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected boolean isTemporarilyDisabledByBruteForce(AuthenticationFlowContext context, UserModel user) {
|
||||
if (context.getRealm().isBruteForceProtected()) {
|
||||
|
|
|
@ -49,7 +49,11 @@ public class AuthenticationContextBean {
|
|||
|
||||
|
||||
public boolean showUsername() {
|
||||
return context != null && context.getUser() != null && context.getAuthenticationSession() != null;
|
||||
return context != null && context.getUser() != null && context.getAuthenticationSession() != null && page!=LoginFormsPages.ERROR;
|
||||
}
|
||||
|
||||
public boolean showResetCredentials() {
|
||||
return showUsername() && page == LoginFormsPages.LOGIN_RESET_PASSWORD;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ public abstract class LanguageComboboxAwarePage extends AbstractPage {
|
|||
@FindBy(id = "try-another-way")
|
||||
private WebElement tryAnotherWayLink;
|
||||
|
||||
@FindBy(id = "attempted-username")
|
||||
@FindBy(id = "kc-attempted-username")
|
||||
private WebElement attemptedUsernameLabel;
|
||||
|
||||
// TODO: This won't be a link, but some kind of an icon once we do better design
|
||||
|
@ -82,7 +82,7 @@ public abstract class LanguageComboboxAwarePage extends AbstractPage {
|
|||
|
||||
public static void assertAttemptedUsernameAvailability(WebDriver driver, boolean expectedAvailability) {
|
||||
try {
|
||||
driver.findElement(By.id("attempted-username"));
|
||||
driver.findElement(By.id("kc-attempted-username"));
|
||||
Assert.assertTrue(expectedAvailability);
|
||||
} catch (NoSuchElementException nse) {
|
||||
Assert.assertFalse(expectedAvailability);
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
*/
|
||||
package org.keycloak.testsuite.pages;
|
||||
|
||||
import org.openqa.selenium.By;
|
||||
import org.openqa.selenium.NoSuchElementException;
|
||||
import org.openqa.selenium.WebElement;
|
||||
import org.openqa.selenium.support.FindBy;
|
||||
|
@ -73,7 +74,12 @@ public class LoginConfigTotpPage extends AbstractPage {
|
|||
}
|
||||
|
||||
public boolean isCurrent() {
|
||||
return PageUtils.getPageTitle(driver).equals("Mobile Authenticator Setup");
|
||||
try {
|
||||
driver.findElement(By.id("totp"));
|
||||
return true;
|
||||
} catch (Throwable t) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void open() {
|
||||
|
|
|
@ -40,6 +40,7 @@ public class LoginPasswordResetPage extends LanguageComboboxAwarePage {
|
|||
private WebElement backToLogin;
|
||||
|
||||
public void changePassword(String username) {
|
||||
usernameInput.clear();
|
||||
usernameInput.sendKeys(username);
|
||||
|
||||
submitButton.click();
|
||||
|
|
|
@ -58,14 +58,12 @@ public class LoginTotpPage extends LanguageComboboxAwarePage {
|
|||
}
|
||||
|
||||
public boolean isCurrent() {
|
||||
if (driver.getTitle().startsWith("Log in to ")) {
|
||||
try {
|
||||
driver.findElement(By.id("otp"));
|
||||
return true;
|
||||
} catch (Throwable t) {
|
||||
}
|
||||
try {
|
||||
driver.findElement(By.id("otp"));
|
||||
return true;
|
||||
} catch (Throwable t) {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -57,11 +57,6 @@ public class PasswordPage extends LanguageComboboxAwarePage {
|
|||
}
|
||||
|
||||
public boolean isCurrent(String realm) {
|
||||
// Check the title
|
||||
if (!DroneUtils.getCurrentDriver().getTitle().equals("Log in to " + realm) && !DroneUtils.getCurrentDriver().getTitle().equals("Anmeldung bei " + realm)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check there is NO username field
|
||||
try {
|
||||
driver.findElement(By.id("username"));
|
||||
|
@ -72,6 +67,7 @@ public class PasswordPage extends LanguageComboboxAwarePage {
|
|||
|
||||
// Check there is password field
|
||||
try {
|
||||
driver.findElement(By.id("kc-attempted-username"));
|
||||
driver.findElement(By.id("password"));
|
||||
} catch (NoSuchElementException nfe) {
|
||||
return false;
|
||||
|
|
|
@ -3,7 +3,6 @@ package org.keycloak.testsuite.pages;
|
|||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.keycloak.testsuite.util.DroneUtils;
|
||||
import org.openqa.selenium.By;
|
||||
import org.openqa.selenium.NoSuchElementException;
|
||||
|
@ -35,11 +34,7 @@ public class SelectAuthenticatorPage extends LanguageComboboxAwarePage {
|
|||
.stream()
|
||||
.filter(webElement -> webElement.getAttribute("selected") != null)
|
||||
.findFirst()
|
||||
.orElseThrow(() -> {
|
||||
|
||||
return new AssertionError("Selected login method not found");
|
||||
|
||||
})
|
||||
.orElseThrow(() -> new AssertionError("Selected login method not found"))
|
||||
.getText();
|
||||
}
|
||||
|
||||
|
|
|
@ -9,10 +9,13 @@
|
|||
<label for="username" class="${properties.kcLabelClass!}"><#if !realm.loginWithEmailAllowed>${msg("username")}<#elseif !realm.registrationEmailAsUsername>${msg("usernameOrEmail")}<#else>${msg("email")}</#if></label>
|
||||
</div>
|
||||
<div class="${properties.kcInputWrapperClass!}">
|
||||
<input type="text" id="username" name="username" class="${properties.kcInputClass!}" autofocus/>
|
||||
<#if auth?has_content && auth.showUsername()>
|
||||
<input type="text" id="username" name="username" class="${properties.kcInputClass!}" autofocus value="${auth.attemptedUsername}"/>
|
||||
<#else>
|
||||
<input type="text" id="username" name="username" class="${properties.kcInputClass!}" autofocus/>
|
||||
</#if>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="${properties.kcFormGroupClass!} ${properties.kcFormSettingClass!}">
|
||||
<div id="kc-form-options" class="${properties.kcFormOptionsClass!}">
|
||||
<div class="${properties.kcFormOptionsWrapperClass!}">
|
||||
|
|
|
@ -83,6 +83,8 @@ offlineAccessScopeConsentText=Offline Access
|
|||
samlRoleListScopeConsentText=My Roles
|
||||
rolesScopeConsentText=User roles
|
||||
|
||||
restartLoginTooltip=Restart login
|
||||
|
||||
loginTotpIntro=You need to set up a One Time Password generator to access this account
|
||||
loginTotpStep1=Install one of the following applications on your mobile
|
||||
loginTotpStep2=Open the application and scan the barcode
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<#import "template.ftl" as layout>
|
||||
<@layout.registrationLayout displayInfo=true; section>
|
||||
<#if section = "header">
|
||||
<#if section = "header" || section = "show-username">
|
||||
<script type="text/javascript">
|
||||
// Fill up the two hidden and submit the form
|
||||
function fillAndSubmit() {
|
||||
|
|
|
@ -52,11 +52,30 @@
|
|||
</div>
|
||||
</div>
|
||||
</#if>
|
||||
<h1 id="kc-page-title"><#nested "header"></h1>
|
||||
<#if !(auth?has_content && auth.showUsername() && !auth.showResetCredentials())>
|
||||
<h1 id="kc-page-title"><#nested "header"></h1>
|
||||
<#else>
|
||||
<#nested "show-username">
|
||||
</#if>
|
||||
</header>
|
||||
<div id="kc-content">
|
||||
<div id="kc-content-wrapper">
|
||||
|
||||
<#if auth?has_content && auth.showUsername() && !auth.showResetCredentials()>
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<div id="kc-username">
|
||||
<label id="kc-attempted-username">${auth.attemptedUsername}</label>
|
||||
<a id="reset-login" href="${url.loginRestartFlowUrl}">
|
||||
<div class="kc-login-tooltip">
|
||||
<i class="${properties.kcResetFlowIcon!}"></i>
|
||||
<span class="kc-tooltip-text">${msg("restartLoginTooltip")}</span>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<hr/>
|
||||
</#if>
|
||||
|
||||
<#-- App-initiated actions should not see warning messages about the need to complete the action -->
|
||||
<#-- during login. -->
|
||||
<#if displayMessage && message?has_content && (message.type != 'warning' || !isAppInitiatedAction??)>
|
||||
|
@ -69,16 +88,6 @@
|
|||
</div>
|
||||
</#if>
|
||||
|
||||
<#if auth?has_content && auth.showUsername() >
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<label id="attempted-username">${auth.attemptedUsername}</label>
|
||||
<a href="${url.loginRestartFlowUrl}" id="reset-login">Reset Login</a>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
</#if>
|
||||
|
||||
<#nested "form">
|
||||
|
||||
<#if auth?has_content && auth.showTryAnotherWayLink() >
|
||||
|
|
|
@ -116,6 +116,17 @@ div.kc-logo-text span {
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
#kc-attempted-username{
|
||||
font-size: 20px;
|
||||
font-family:inherit;
|
||||
font-weight: normal;
|
||||
padding-right:10px;
|
||||
}
|
||||
|
||||
#kc-username{
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* #kc-content-wrapper {
|
||||
overflow-y: hidden;
|
||||
} */
|
||||
|
@ -216,6 +227,47 @@ ul#kc-totp-supported-apps {
|
|||
margin-top: 0;
|
||||
}
|
||||
|
||||
.kc-login-tooltip{
|
||||
position:relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.kc-login-tooltip .kc-tooltip-text{
|
||||
top:-3px;
|
||||
left:160%;
|
||||
background-color: black;
|
||||
visibility: hidden;
|
||||
color: #fff;
|
||||
|
||||
min-width:130px;
|
||||
text-align: center;
|
||||
border-radius: 2px;
|
||||
box-shadow:0 1px 8px rgba(0,0,0,0.6);
|
||||
padding: 5px;
|
||||
|
||||
position: absolute;
|
||||
opacity:0;
|
||||
transition:opacity 0.5s;
|
||||
}
|
||||
|
||||
/* Show tooltip */
|
||||
.kc-login-tooltip:hover .kc-tooltip-text {
|
||||
visibility: visible;
|
||||
opacity:0.7;
|
||||
}
|
||||
|
||||
/* Arrow for tooltip */
|
||||
.kc-login-tooltip .kc-tooltip-text::after {
|
||||
content: " ";
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
right: 100%;
|
||||
margin-top: -5px;
|
||||
border-width: 5px;
|
||||
border-style: solid;
|
||||
border-color: transparent black transparent transparent;
|
||||
}
|
||||
|
||||
.zocial,
|
||||
a.zocial {
|
||||
width: 100%;
|
||||
|
|
|
@ -35,6 +35,7 @@ kcFeedbackWarningIcon=pficon pficon-warning-triangle-o
|
|||
kcFeedbackSuccessIcon=pficon pficon-ok
|
||||
kcFeedbackInfoIcon=pficon pficon-info
|
||||
|
||||
kcResetFlowIcon=pficon pficon-arrow fa-2x
|
||||
|
||||
kcFormClass=form-horizontal
|
||||
kcFormGroupClass=form-group
|
||||
|
|
Loading…
Reference in a new issue