added more style fixes for the login.v2 (#32708)
* added more style fixes for the login.v2 related: #32522 Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com> * fixed grant screen Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com> * test fixes Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com> * fix for code.ftl Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com> * test fixes Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com> * fixed tests Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com> --------- Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com>
This commit is contained in:
parent
125124c2d9
commit
9aad6f650d
28 changed files with 438 additions and 158 deletions
|
@ -12,7 +12,7 @@ public class DeleteAccountActionConfirmPage extends RequiredActions {
|
|||
@FindBy(css = "button[name='cancel-aia']")
|
||||
WebElement cancelActionButton;
|
||||
|
||||
@FindBy(css = "input[type='submit']")
|
||||
@FindBy(css = "button[type='submit']")
|
||||
WebElement confirmActionButton;
|
||||
|
||||
@Override
|
||||
|
|
|
@ -36,10 +36,10 @@ public class LoginActions extends LoginBase {
|
|||
.path("login-actions");
|
||||
}
|
||||
|
||||
@FindBy(css = "input[type='submit']")
|
||||
@FindBy(css = "button[type='submit']")
|
||||
private WebElement submitButton;
|
||||
|
||||
@FindBy(css = "button[type='submit']")
|
||||
@FindBy(css = "button[name='cancel-aia']")
|
||||
private WebElement cancelButton;
|
||||
|
||||
public void submit() {
|
||||
|
|
|
@ -34,10 +34,10 @@ import static org.keycloak.testsuite.util.UIUtils.clickLink;
|
|||
* @author Vaclav Muzikar <vmuzikar@redhat.com>
|
||||
*/
|
||||
public class OAuthGrant extends RequiredActions {
|
||||
@FindBy(css = "input[name=\"accept\"]")
|
||||
@FindBy(css = "button[name=\"accept\"]")
|
||||
private WebElement acceptButton;
|
||||
|
||||
@FindBy(css = "input[name=\"cancel\"]")
|
||||
@FindBy(css = "button[name=\"cancel\"]")
|
||||
private WebElement cancelButton;
|
||||
|
||||
@FindBy(xpath = "//div[@id='kc-oauth']/ul/li/span")
|
||||
|
|
|
@ -37,7 +37,7 @@ public class OneTimeCode extends Authenticate {
|
|||
@FindBy(css = "div[class^='pf-v5-c-alert'], div[class^='alert-error']")
|
||||
private WebElement loginErrorMessage;
|
||||
|
||||
@FindBy(id = "input-error-otp-code")
|
||||
@FindBy(id = "input-error-otp")
|
||||
private WebElement totpInputCodeError;
|
||||
|
||||
public String getOtpLabel() {
|
||||
|
|
|
@ -40,7 +40,7 @@ public class LoginPasswordUpdatePage {
|
|||
@FindBy(id = "password-confirm")
|
||||
private WebElement passwordConfirmInput;
|
||||
|
||||
@FindBy(css = "input[type=\"submit\"]")
|
||||
@FindBy(css = "button[type=\"submit\"]")
|
||||
private WebElement submitButton;
|
||||
|
||||
@FindBy(css = "div[class^='pf-v5-c-alert'], div[class^='alert-error']")
|
||||
|
|
|
@ -32,7 +32,7 @@ public class LoginPasswordResetPage extends LanguageComboboxAwarePage {
|
|||
@FindBy(id = "input-error-username")
|
||||
private WebElement usernameError;
|
||||
|
||||
@FindBy(css = "input[type=\"submit\"]")
|
||||
@FindBy(css = "button[type=\"submit\"]")
|
||||
private WebElement submitButton;
|
||||
|
||||
@FindBy(className = "pf-v5-c-success")
|
||||
|
|
|
@ -32,7 +32,7 @@ public class LoginPasswordUpdatePage extends LogoutSessionsPage {
|
|||
@FindBy(id = "password-confirm")
|
||||
private WebElement passwordConfirmInput;
|
||||
|
||||
@FindBy(css = "input[type=\"submit\"]")
|
||||
@FindBy(css = "button[type=\"submit\"]")
|
||||
private WebElement submitButton;
|
||||
|
||||
@FindBy(css = "div[class^='pf-v5-c-alert'], div[class^='alert-error']")
|
||||
|
|
|
@ -22,6 +22,7 @@ import java.util.stream.Collectors;
|
|||
import org.junit.Assert;
|
||||
import org.keycloak.common.util.Retry;
|
||||
import org.keycloak.testsuite.util.UIUtils;
|
||||
import org.keycloak.testsuite.util.WaitUtils;
|
||||
import org.openqa.selenium.By;
|
||||
import org.openqa.selenium.NoSuchElementException;
|
||||
import org.openqa.selenium.WebElement;
|
||||
|
@ -38,13 +39,13 @@ public class LoginTotpPage extends LanguageComboboxAwarePage {
|
|||
@FindBy(id = "password-token")
|
||||
private WebElement passwordToken;
|
||||
|
||||
@FindBy(css = "input[type=\"submit\"]")
|
||||
@FindBy(css = "button[type=\"submit\"]")
|
||||
private WebElement submitButton;
|
||||
|
||||
@FindBy(css = "div[class^='pf-v5-c-alert'], div[class^='alert-error']")
|
||||
private WebElement loginErrorMessage;
|
||||
|
||||
@FindBy(id = "input-error-otp-code")
|
||||
@FindBy(id = "input-error-otp")
|
||||
private WebElement totpInputCodeError;
|
||||
|
||||
public void login(String totp) {
|
||||
|
@ -88,7 +89,7 @@ public class LoginTotpPage extends LanguageComboboxAwarePage {
|
|||
// If false, we don't expect that credentials combobox is available. If true, we expect that it is available on the page
|
||||
public void assertOtpCredentialSelectorAvailability(boolean expectedAvailability) {
|
||||
try {
|
||||
driver.findElement(By.className("pf-c-tile"));
|
||||
driver.findElement(By.className("pf-v5-c-tile"));
|
||||
Assert.assertTrue(expectedAvailability);
|
||||
} catch (NoSuchElementException nse) {
|
||||
Assert.assertFalse(expectedAvailability);
|
||||
|
@ -113,21 +114,19 @@ public class LoginTotpPage extends LanguageComboboxAwarePage {
|
|||
}
|
||||
|
||||
private By getXPathForLookupAllCards() {
|
||||
return By.xpath("//span[contains(@class, 'pf-c-tile__title')]");
|
||||
return By.xpath("//span[contains(@class, 'pf-v5-c-tile__title')]");
|
||||
}
|
||||
|
||||
private By getCssSelectorForLookupActiveCard() {
|
||||
return By.cssSelector(".pf-c-tile__input:checked + .pf-c-tile .pf-c-tile__title");
|
||||
return By.cssSelector(".pf-m-selected");
|
||||
}
|
||||
|
||||
private By getXPathForLookupCardWithName(String credentialName) {
|
||||
return By.xpath("//label[contains(@class, 'pf-c-tile')][normalize-space() = '"+ credentialName +"']");
|
||||
return By.xpath("//div[contains(@class, 'pf-v5-c-tile')][normalize-space() = '"+ credentialName +"']");
|
||||
}
|
||||
|
||||
|
||||
public void selectOtpCredential(String credentialName) {
|
||||
waitForElement(getCssSelectorForLookupActiveCard());
|
||||
|
||||
WebElement webElement = driver.findElement(
|
||||
getXPathForLookupCardWithName(credentialName));
|
||||
UIUtils.clickLink(webElement);
|
||||
|
|
|
@ -41,9 +41,9 @@ public class OAuthGrantPage extends LanguageComboboxAwarePage {
|
|||
public static final String OFFLINE_ACCESS_CONSENT_TEXT = "Offline Access";
|
||||
public static final String ROLES_CONSENT_TEXT = "User roles";
|
||||
|
||||
@FindBy(css = "input[name=\"accept\"]")
|
||||
@FindBy(css = "button[name=\"accept\"]")
|
||||
private WebElement acceptButton;
|
||||
@FindBy(css = "input[name=\"cancel\"]")
|
||||
@FindBy(css = "button[name=\"cancel\"]")
|
||||
private WebElement cancelButton;
|
||||
|
||||
|
||||
|
|
|
@ -18,11 +18,13 @@ package org.keycloak.testsuite.util.saml;
|
|||
|
||||
import org.keycloak.testsuite.util.SamlClient.Step;
|
||||
import org.keycloak.testsuite.util.SamlClientBuilder;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URI;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import jakarta.ws.rs.core.UriBuilder;
|
||||
import org.apache.http.NameValuePair;
|
||||
|
@ -37,12 +39,12 @@ import org.apache.http.message.BasicNameValuePair;
|
|||
import org.apache.http.util.EntityUtils;
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Element;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.keycloak.testsuite.util.Matchers.statusCodeIsHC;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author hmlnarik
|
||||
*/
|
||||
public class RequiredConsentBuilder implements Step {
|
||||
|
@ -88,18 +90,18 @@ public class RequiredConsentBuilder implements Step {
|
|||
for (Element form : theLoginPage.getElementsByTag("form")) {
|
||||
String method = form.attr("method");
|
||||
String action = form.attr("action");
|
||||
boolean isPost = method != null && "post".equalsIgnoreCase(method);
|
||||
boolean isPost = "post".equalsIgnoreCase(method);
|
||||
|
||||
Element submitButton;
|
||||
if (approveConsent) {
|
||||
submitButton = form.getElementById("kc-login");
|
||||
} else {
|
||||
submitButton = form.getElementById("kc-cancel");
|
||||
}
|
||||
parameters.add(new BasicNameValuePair(submitButton.attr("name"), submitButton.attr("value")));
|
||||
|
||||
for (Element input : form.getElementsByTag("input")) {
|
||||
if (Objects.equals(input.id(), "kc-login")) {
|
||||
if (approveConsent)
|
||||
parameters.add(new BasicNameValuePair(input.attr("name"), input.attr("value")));
|
||||
} else if (Objects.equals(input.id(), "kc-cancel")) {
|
||||
if (!approveConsent)
|
||||
parameters.add(new BasicNameValuePair(input.attr("name"), input.attr("value")));
|
||||
} else {
|
||||
parameters.add(new BasicNameValuePair(input.attr("name"), input.val()));
|
||||
}
|
||||
parameters.add(new BasicNameValuePair(input.attr("name"), input.val()));
|
||||
}
|
||||
|
||||
if (isPost) {
|
||||
|
|
|
@ -181,6 +181,7 @@ public class BrowserFlowTest extends AbstractTestRealmKeycloakTest {
|
|||
Assert.assertTrue(oneTimeCodePage.isOtpLabelPresent());
|
||||
loginTotpPage.assertCurrent();
|
||||
loginTotpPage.assertOtpCredentialSelectorAvailability(true);
|
||||
loginTotpPage.selectOtpCredential("first");
|
||||
|
||||
// Check that selected credential is "first"
|
||||
Assert.assertEquals("first", loginTotpPage.getSelectedOtpCredential());
|
||||
|
@ -207,6 +208,7 @@ public class BrowserFlowTest extends AbstractTestRealmKeycloakTest {
|
|||
Assert.assertTrue(oneTimeCodePage.isOtpLabelPresent());
|
||||
loginTotpPage.assertCurrent();
|
||||
loginTotpPage.assertOtpCredentialSelectorAvailability(true);
|
||||
loginTotpPage.selectOtpCredential(orderedCredentials.get(0));
|
||||
|
||||
// Check that preferred credential is selected
|
||||
Assert.assertEquals(orderedCredentials.get(0), loginTotpPage.getSelectedOtpCredential());
|
||||
|
|
|
@ -1370,7 +1370,7 @@ public class ResetPasswordTest extends AbstractTestRealmKeycloakTest {
|
|||
newPassword.sendKeys("resetPassword");
|
||||
final WebElement confirmPassword = driver.findElement(By.id("password-confirm"));
|
||||
confirmPassword.sendKeys("resetPassword");
|
||||
final WebElement submit = driver.findElement(By.cssSelector("input[type=\"submit\"]"));
|
||||
final WebElement submit = driver.findElement(By.cssSelector("button[type=\"submit\"]"));
|
||||
|
||||
submit.click();
|
||||
}
|
||||
|
|
|
@ -3,12 +3,17 @@
|
|||
<div class="${properties.kcFormActionGroupClass}">
|
||||
<#nested>
|
||||
</div>
|
||||
</div>
|
||||
</#macro>
|
||||
|
||||
<#macro button id name label class=["kcButtonPrimaryClass"]>
|
||||
<#macro button label id="" name="" class=["kcButtonPrimaryClass"]>
|
||||
<button class="<#list class as c>${properties[c]} </#list>" name="${name}" id="${id}" type="submit">${msg(label)}</button>
|
||||
</#macro>
|
||||
|
||||
<#macro buttonLink href label id="" class=["kcButtonSecondaryClass"]>
|
||||
<a id="${id}" href="${href}" class="<#list class as c>${properties[c]} </#list>">${kcSanitize(msg(label))?no_esc}</a>
|
||||
</#macro>
|
||||
|
||||
<#macro loginButton>
|
||||
<@buttons.actionGroup>
|
||||
<@buttons.button id="kc-login" name="login" label="doLogIn" class=["kcButtonPrimaryClass", "kcButtonBlockClass"] />
|
||||
|
|
20
themes/src/main/resources/theme/keycloak.v2/login/code.ftl
Executable file
20
themes/src/main/resources/theme/keycloak.v2/login/code.ftl
Executable file
|
@ -0,0 +1,20 @@
|
|||
<#import "template.ftl" as layout>
|
||||
<#import "field.ftl" as field>
|
||||
<@layout.registrationLayout; section>
|
||||
<#if section = "header">
|
||||
<#if code.success>
|
||||
${msg("codeSuccessTitle")}
|
||||
<#else>
|
||||
${kcSanitize(msg("codeErrorTitle", code.error))}
|
||||
</#if>
|
||||
<#elseif section = "form">
|
||||
<div id="kc-code">
|
||||
<#if code.success>
|
||||
<p>${msg("copyCodeInstruction")}</p>
|
||||
<@field.input name="code" label="" value=code.code />
|
||||
<#else>
|
||||
<p id="error">${kcSanitize(code.error)}</p>
|
||||
</#if>
|
||||
</div>
|
||||
</#if>
|
||||
</@layout.registrationLayout>
|
|
@ -0,0 +1,40 @@
|
|||
<#import "template.ftl" as layout>
|
||||
<#import "buttons.ftl" as buttons>
|
||||
|
||||
<@layout.registrationLayout; section>
|
||||
<!-- template: delete-account-confirm.ftl -->
|
||||
|
||||
<#if section = "header">
|
||||
${msg("deleteAccountConfirm")}
|
||||
|
||||
<#elseif section = "form">
|
||||
|
||||
<form action="${url.loginAction}" class="${properties.kcFormClass!}" id="kc-deleteaccount-form" method="post">
|
||||
|
||||
<div class="${properties.kcAlertClass!} pf-m-warning">
|
||||
<div class="${properties.kcAlertIconClass!}">
|
||||
<i class="${properties.kcFeedbackWarningIcon!}" aria-hidden="true"></i>
|
||||
</div>
|
||||
<span class="${properties.kcAlertTitleClass!}">
|
||||
${msg("irreversibleAction")}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<p>${msg("deletingImplies")}</p>
|
||||
<ul class="pf-v5-c-list" role="list">
|
||||
<li>${msg("loggingOutImmediately")}</li>
|
||||
<li>${msg("errasingData")}</li>
|
||||
</ul>
|
||||
|
||||
<p class="delete-account-text">${msg("finalDeletionConfirmation")}</p>
|
||||
|
||||
<@buttons.actionGroup>
|
||||
<@buttons.button label="doConfirmDelete" class=["kcButtonPrimaryClass"]/>
|
||||
<#if triggered_from_aia>
|
||||
<@buttons.button name="cancel-aia" label="doCancel" class=["kcButtonSecondaryClass"]/>
|
||||
</#if>
|
||||
</@buttons.actionGroup>
|
||||
</form>
|
||||
</#if>
|
||||
|
||||
</@layout.registrationLayout>
|
|
@ -0,0 +1,21 @@
|
|||
<#import "template.ftl" as layout>
|
||||
<#import "buttons.ftl" as buttons>
|
||||
|
||||
<@layout.registrationLayout displayMessage=false; section>
|
||||
<!-- template: delete-credential.ftl -->
|
||||
|
||||
<#if section = "header">
|
||||
${msg("deleteCredentialTitle", credentialLabel)}
|
||||
<#elseif section = "form">
|
||||
<div id="kc-delete-text" class="${properties.kcContentWrapperClass!}">
|
||||
${msg("deleteCredentialMessage", credentialLabel)}
|
||||
</div>
|
||||
|
||||
<form class="${properties.kcFormClass!}" action="${url.loginAction}" method="POST">
|
||||
<@buttons.actionGroup>
|
||||
<@buttons.button name="accept" id="kc-accept" label="doConfirmDelete" class=["kcButtonPrimaryClass"]/>
|
||||
<@buttons.button name="cancel-aia" id="kc-decline" label="doDecline" class=["kcButtonSecondaryClass"]/>
|
||||
</@buttons.actionGroup>
|
||||
</form
|
||||
</#if>
|
||||
</@layout.registrationLayout>
|
|
@ -41,19 +41,19 @@
|
|||
</#if>
|
||||
</#macro>
|
||||
|
||||
<#macro input name label value="" required=false>
|
||||
<#assign error=kcSanitize(messagesPerField.get(name))?no_esc>
|
||||
<#macro input name label value="" required=false autocomplete="off" fieldName=name>
|
||||
<#assign error=kcSanitize(messagesPerField.get(fieldName))?no_esc>
|
||||
<@field.group name=name label=label error=error required=required>
|
||||
<span class="${properties.kcInputClass} <#if error?has_content>${properties.kcError}</#if>">
|
||||
<input id="${name}" name="${name}" value="${value}" type="text" autocomplete="off"
|
||||
<input id="${name}" name="${name}" value="${value}" type="text" autocomplete="${autocomplete}"
|
||||
aria-invalid="<#if error?has_content>true</#if>"/>
|
||||
<@errorIcon error=error/>
|
||||
</span>
|
||||
</@field.group>
|
||||
</#macro>
|
||||
|
||||
<#macro password name label value="" required=false forgotPassword=false>
|
||||
<#assign error=kcSanitize(messagesPerField.get(name))?no_esc>
|
||||
<#macro password name label value="" required=false forgotPassword=false fieldName=name>
|
||||
<#assign error=kcSanitize(messagesPerField.get(fieldName))?no_esc>
|
||||
<@field.group name=name label=label error=error required=required>
|
||||
<div class="${properties.kcInputGroup}">
|
||||
<div class="${properties.kcInputGroupItemClass} ${properties.kcFill}">
|
||||
|
|
59
themes/src/main/resources/theme/keycloak.v2/login/login-oauth-grant.ftl
Executable file
59
themes/src/main/resources/theme/keycloak.v2/login/login-oauth-grant.ftl
Executable file
|
@ -0,0 +1,59 @@
|
|||
<#import "template.ftl" as layout>
|
||||
<#import "buttons.ftl" as buttons>
|
||||
<@layout.registrationLayout bodyClass="oauth"; section>
|
||||
<#if section = "header">
|
||||
<#if client.attributes.logoUri??>
|
||||
<img src="${client.attributes.logoUri}"/>
|
||||
</#if>
|
||||
<p>
|
||||
<#if client.name?has_content>
|
||||
${msg("oauthGrantTitle",advancedMsg(client.name))}
|
||||
<#else>
|
||||
${msg("oauthGrantTitle",client.clientId)}
|
||||
</#if>
|
||||
</p>
|
||||
<#elseif section = "form">
|
||||
<div id="kc-oauth" class="content-area">
|
||||
<h3>${msg("oauthGrantRequest")}</h3>
|
||||
<ul class="${properties.kcListClass!}">
|
||||
<#if oauth.clientScopesRequested??>
|
||||
<#list oauth.clientScopesRequested as clientScope>
|
||||
<li>
|
||||
<span><#if !clientScope.dynamicScopeParameter??>
|
||||
${advancedMsg(clientScope.consentScreenText)}
|
||||
<#else>
|
||||
${advancedMsg(clientScope.consentScreenText)}: <b>${clientScope.dynamicScopeParameter}</b>
|
||||
</#if>
|
||||
</span>
|
||||
</li>
|
||||
</#list>
|
||||
</#if>
|
||||
</ul>
|
||||
<#if client.attributes.policyUri?? || client.attributes.tosUri??>
|
||||
<h3>
|
||||
<#if client.name?has_content>
|
||||
${msg("oauthGrantInformation",advancedMsg(client.name))}
|
||||
<#else>
|
||||
${msg("oauthGrantInformation",client.clientId)}
|
||||
</#if>
|
||||
<#if client.attributes.tosUri??>
|
||||
${msg("oauthGrantReview")}
|
||||
<a href="${client.attributes.tosUri}" target="_blank">${msg("oauthGrantTos")}</a>
|
||||
</#if>
|
||||
<#if client.attributes.policyUri??>
|
||||
${msg("oauthGrantReview")}
|
||||
<a href="${client.attributes.policyUri}" target="_blank">${msg("oauthGrantPolicy")}</a>
|
||||
</#if>
|
||||
</h3>
|
||||
</#if>
|
||||
|
||||
<form class="${properties.kcFormClass} ${properties.kcMarginTopClass!}" action="${url.oauthAction}" method="POST">
|
||||
<input type="hidden" name="code" value="${oauth.code}">
|
||||
<@buttons.actionGroup>
|
||||
<@buttons.button id="kc-login" name="accept" label="doYes"/>
|
||||
<@buttons.button id="kc-cancel" name="cancel" label="doNo" class=["kcButtonSecondaryClass"]/>
|
||||
</@buttons.actionGroup>
|
||||
</form>
|
||||
</div>
|
||||
</#if>
|
||||
</@layout.registrationLayout>
|
45
themes/src/main/resources/theme/keycloak.v2/login/login-otp.ftl
Executable file
45
themes/src/main/resources/theme/keycloak.v2/login/login-otp.ftl
Executable file
|
@ -0,0 +1,45 @@
|
|||
<#import "template.ftl" as layout>
|
||||
<#import "field.ftl" as field>
|
||||
<#import "buttons.ftl" as buttons>
|
||||
<@layout.registrationLayout displayMessage=!messagesPerField.existsError('totp'); section>
|
||||
<!-- template: login-otp.ftl -->
|
||||
|
||||
<#if section="header">
|
||||
${msg("doLogIn")}
|
||||
<#elseif section="form">
|
||||
<form id="kc-otp-login-form" class="${properties.kcFormClass!}" action="${url.loginAction}" method="post">
|
||||
<input id="selectedCredentialId" type="hidden" name="selectedCredentialId" value="${otpLogin.selectedCredentialId!''}">
|
||||
<#if otpLogin.userOtpCredentials?size gt 1>
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<div class="${properties.kcInputWrapperClass!}">
|
||||
<#list otpLogin.userOtpCredentials as otpCredential>
|
||||
<div id="kc-otp-credential-${otpCredential?index}" class="${properties.kcLoginOTPListClass!}"
|
||||
onclick="toggleOTP(${otpCredential?index}, '${otpCredential.id}')">
|
||||
<span class="${properties.kcLoginOTPListItemHeaderClass!}">
|
||||
<span class="${properties.kcLoginOTPListItemIconBodyClass!}">
|
||||
<i class="${properties.kcLoginOTPListItemIconClass!}" aria-hidden="true"></i>
|
||||
</span>
|
||||
<span class="${properties.kcLoginOTPListItemTitleClass!}">${otpCredential.userLabel}</span>
|
||||
</span>
|
||||
</div>
|
||||
</#list>
|
||||
</div>
|
||||
</div>
|
||||
</#if>
|
||||
|
||||
<@field.input name="otp" label=msg("loginOtpOneTime") autocomplete="one-time-code" fieldName="totp" />
|
||||
|
||||
<@buttons.loginButton />
|
||||
</form>
|
||||
<script>
|
||||
function toggleOTP(index, value) {
|
||||
// select the clicked OTP credential
|
||||
document.getElementById("selectedCredentialId").value = value;
|
||||
// remove selected class from all OTP credentials
|
||||
Array.from(document.getElementsByClassName("${properties.kcLoginOTPListSelectedClass!}")).map(i => i.classList.remove("${properties.kcLoginOTPListSelectedClass!}"));
|
||||
// add selected class to the clicked OTP credential
|
||||
document.getElementById("kc-otp-credential-" + index).classList.add("${properties.kcLoginOTPListSelectedClass!}");
|
||||
}
|
||||
</script>
|
||||
</#if>
|
||||
</@layout.registrationLayout>
|
|
@ -0,0 +1,27 @@
|
|||
<#import "template.ftl" as layout>
|
||||
<#import "field.ftl" as field>
|
||||
<#import "buttons.ftl" as buttons>
|
||||
<@layout.registrationLayout displayInfo=true displayMessage=!messagesPerField.existsError('username'); section>
|
||||
<#if section = "header">
|
||||
${msg("emailForgotTitle")}
|
||||
<#elseif section = "form">
|
||||
<form id="kc-reset-password-form" class="${properties.kcFormClass!}" action="${url.loginAction}" method="post">
|
||||
<#assign label>
|
||||
<#if !realm.loginWithEmailAllowed>${msg("username")}<#elseif !realm.registrationEmailAsUsername>${msg("usernameOrEmail")}<#else>${msg("email")}</#if>
|
||||
</#assign>
|
||||
<@field.input name="username" label=label value=auth.attemptedUsername!''/>
|
||||
|
||||
<@buttons.actionGroup>
|
||||
<@buttons.button id="kc-form-buttons" label="doSubmit" class=["kcButtonPrimaryClass", "kcButtonBlockClass"]/>
|
||||
<@buttons.buttonLink href=url.loginUrl label="backToLogin" class=["kcButtonSecondaryClass", "kcButtonBlockClass"]/>
|
||||
</@buttons.actionGroup>
|
||||
|
||||
</form>
|
||||
<#elseif section = "info" >
|
||||
<#if realm.duplicateEmailsAllowed>
|
||||
${msg("emailInstructionUsername")}
|
||||
<#else>
|
||||
${msg("emailInstruction")}
|
||||
</#if>
|
||||
</#if>
|
||||
</@layout.registrationLayout>
|
|
@ -1,79 +1,28 @@
|
|||
<#import "template.ftl" as layout>
|
||||
<#import "password-commons.ftl" as passwordCommons>
|
||||
<#import "field.ftl" as field>
|
||||
<#import "buttons.ftl" as buttons>
|
||||
<@layout.registrationLayout displayMessage=!messagesPerField.existsError('password','password-confirm'); section>
|
||||
<!-- template: login-update-password.ftl -->
|
||||
<#if section = "header">
|
||||
${msg("updatePasswordTitle")}
|
||||
<#elseif section = "form">
|
||||
<form id="kc-passwd-update-form" class="${properties.kcFormClass!}" action="${url.loginAction}" method="post" novalidate="novalidate">
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<label for="password-new" class="${properties.kcLabelClass!}">
|
||||
<span class="pf-v5-c-form__label-text">
|
||||
${msg("passwordNew")}
|
||||
</span>
|
||||
</label>
|
||||
<div class="${properties.kcInputGroup!}">
|
||||
<span class="${properties.kcInputClass!}" dir="ltr">
|
||||
<input type="password" id="password-new" name="password-new" autofocus autocomplete="new-password"
|
||||
aria-invalid="<#if messagesPerField.existsError('password','password-confirm')>true</#if>"
|
||||
/>
|
||||
</span>
|
||||
<button class="${properties.kcFormPasswordVisibilityButtonClass!}" type="button" aria-label="${msg('showPassword')}"
|
||||
aria-controls="password-new" data-password-toggle
|
||||
data-icon-show="${properties.kcFormPasswordVisibilityIconShow!}" data-icon-hide="${properties.kcFormPasswordVisibilityIconHide!}"
|
||||
data-label-show="${msg('showPassword')}" data-label-hide="${msg('hidePassword')}">
|
||||
<i class="${properties.kcFormPasswordVisibilityIconShow!}" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<#if messagesPerField.existsError('password')>
|
||||
<span id="input-error-password" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
|
||||
${kcSanitize(messagesPerField.get('password'))?no_esc}
|
||||
</span>
|
||||
</#if>
|
||||
</div>
|
||||
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<label for="password-confirm" class="${properties.kcLabelClass!}">
|
||||
<span class="pf-v5-c-form__label-text">
|
||||
${msg("passwordConfirm")}
|
||||
</span>
|
||||
</label>
|
||||
<div class="${properties.kcInputGroup!}">
|
||||
<span class="${properties.kcInputClass!}" dir="ltr">
|
||||
<input type="password" id="password-confirm" name="password-confirm"
|
||||
autocomplete="new-password"
|
||||
aria-invalid="<#if messagesPerField.existsError('password-confirm')>true</#if>"
|
||||
/>
|
||||
</span>
|
||||
<button class="${properties.kcFormPasswordVisibilityButtonClass!}" type="button" aria-label="${msg('showPassword')}"
|
||||
aria-controls="password-confirm" data-password-toggle
|
||||
data-icon-show="${properties.kcFormPasswordVisibilityIconShow!}" data-icon-hide="${properties.kcFormPasswordVisibilityIconHide!}"
|
||||
data-label-show="${msg('showPassword')}" data-label-hide="${msg('hidePassword')}">
|
||||
<i class="${properties.kcFormPasswordVisibilityIconShow!}" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<#if messagesPerField.existsError('password-confirm')>
|
||||
<span id="input-error-password-confirm" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
|
||||
${kcSanitize(messagesPerField.get('password-confirm'))?no_esc}
|
||||
</span>
|
||||
</#if>
|
||||
</div>
|
||||
<@field.password name="password-new" label=msg("passwordNew") fieldName="password" />
|
||||
<@field.password name="password-confirm" label=msg("passwordConfirm") />
|
||||
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<@passwordCommons.logoutOtherSessions/>
|
||||
</div>
|
||||
|
||||
<div id="kc-form-buttons" class="${properties.kcFormButtonsClass!} pf-m-action">
|
||||
<div class="pf-v5-c-form__actions">
|
||||
<#if isAppInitiatedAction??>
|
||||
<input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" type="submit" value="${msg("doSubmit")}" />
|
||||
<button class="${properties.kcButtonClass!} ${properties.kcButtonDefaultClass!} ${properties.kcButtonLargeClass!}" type="submit" name="cancel-aia" value="true" />${msg("doCancel")}</button>
|
||||
<#else>
|
||||
<input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonBlockClass!} ${properties.kcButtonLargeClass!}" type="submit" value="${msg("doSubmit")}" />
|
||||
</#if>
|
||||
</div>
|
||||
</div>
|
||||
<@buttons.actionGroup>
|
||||
<#if isAppInitiatedAction??>
|
||||
<@buttons.button label="doSubmit" class=["kcButtonPrimaryClass"]/>
|
||||
<@buttons.button label="doCancel" name="cancel-aia" class=["kcButtonSecondaryClass"]/>
|
||||
<#else>
|
||||
<@buttons.button label="doSubmit" class=["kcButtonPrimaryClass", "kcButtonBlockClass"]/>
|
||||
</#if>
|
||||
</@buttons.actionGroup>
|
||||
</form>
|
||||
</#if>
|
||||
</@layout.registrationLayout>
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
<#assign label>
|
||||
<#if !realm.loginWithEmailAllowed>${msg("username")}<#elseif !realm.registrationEmailAsUsername>${msg("usernameOrEmail")}<#else>${msg("email")}</#if>
|
||||
</#assign>
|
||||
<@field.input name="username" label=label value="${(login.username!'')}" />
|
||||
<@field.input name="username" label=label value=login.username!'' />
|
||||
|
||||
<#if messagesPerField.existsError('username')>
|
||||
<span id="input-error-username" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
<#assign label>
|
||||
<#if !realm.loginWithEmailAllowed>${msg("username")}<#elseif !realm.registrationEmailAsUsername>${msg("usernameOrEmail")}<#else>${msg("email")}</#if>
|
||||
</#assign>
|
||||
<@field.input name="username" label=label value="${(login.username!'')}" />
|
||||
<@field.input name="username" label=label value=login.username!'' />
|
||||
</#if>
|
||||
|
||||
<@field.password name="password" label=msg("password") forgotPassword=realm.resetPasswordAllowed/>
|
||||
|
@ -42,7 +42,7 @@
|
|||
</div>
|
||||
</#if>
|
||||
<#elseif section = "socialProviders" >
|
||||
<#if realm.password && social?? && social.providers?has_content>
|
||||
<#if realm.password && social.providers?? && social.providers?has_content>
|
||||
<div class="pf-v5-c-login__main-footer-band">
|
||||
<p class="pf-v5-c-login__main-footer-band-item">
|
||||
${msg("identity-provider-login-label")}
|
||||
|
|
|
@ -173,7 +173,7 @@
|
|||
<#if message.type = 'error'><span class="${properties.kcFeedbackErrorIcon!}"></span></#if>
|
||||
<#if message.type = 'info'><span class="${properties.kcFeedbackInfoIcon!}"></span></#if>
|
||||
</div>
|
||||
<span class="${properties.kcAlertTitleClass!} kc-feedback-text">${kcSanitize(message.summary)?no_esc}</span>
|
||||
<span class="${properties.kcAlertTitleClass!} kc-feedback-text">${kcSanitize(message.summary)?no_esc}</span>
|
||||
</div>
|
||||
</#if>
|
||||
|
||||
|
@ -181,11 +181,11 @@
|
|||
|
||||
<#if auth?has_content && auth.showTryAnotherWayLink()>
|
||||
<form id="kc-select-try-another-way-form" action="${url.loginAction}" method="post" novalidate="novalidate">
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<input type="hidden" name="tryAnotherWay" value="on"/>
|
||||
<a href="#" id="try-another-way"
|
||||
onclick="document.forms['kc-select-try-another-way-form'].submit();return false;">${msg("doTryAnotherWay")}</a>
|
||||
</div>
|
||||
<input type="hidden" name="tryAnotherWay" value="on"/>
|
||||
<a id="try-another-way" href="javascript:document.forms['kc-select-try-another-way-form'].submit()"
|
||||
class="${properties.kcButtonSecondaryClass} ${properties.kcButtonBlockClass} ${properties.kcMarginTopClass}">
|
||||
${kcSanitize(msg("doTryAnotherWay"))?no_esc}
|
||||
</a>
|
||||
</form>
|
||||
</#if>
|
||||
|
||||
|
|
21
themes/src/main/resources/theme/keycloak.v2/login/terms.ftl
Executable file
21
themes/src/main/resources/theme/keycloak.v2/login/terms.ftl
Executable file
|
@ -0,0 +1,21 @@
|
|||
<#import "template.ftl" as layout>
|
||||
<#import "buttons.ftl" as buttons>
|
||||
|
||||
<@layout.registrationLayout displayMessage=false; section>
|
||||
<!-- template: terms.ftl -->
|
||||
|
||||
<#if section = "header">
|
||||
${msg("termsTitle")}
|
||||
<#elseif section = "form">
|
||||
<div class="${properties.kcContentWrapperClass}">
|
||||
${kcSanitize(msg("termsText"))?no_esc}
|
||||
</div>
|
||||
<form class="${properties.kcFormClass!}" action="${url.loginAction}" method="POST">
|
||||
<@buttons.actionGroup>
|
||||
<@buttons.button name="accept" id="kc-accept" label="doAccept" class=["kcButtonPrimaryClass"]/>
|
||||
<@buttons.button name="cancel" id="kc-decline" label="doDecline" class=["kcButtonSecondaryClass"]/>
|
||||
</@buttons.actionGroup>
|
||||
</form>
|
||||
<div class="clearfix"></div>
|
||||
</#if>
|
||||
</@layout.registrationLayout>
|
|
@ -44,6 +44,7 @@ kcListClass=pf-v5-c-list
|
|||
|
||||
kcButtonClass=pf-v5-c-button
|
||||
kcButtonPrimaryClass=pf-v5-c-button pf-m-primary
|
||||
kcButtonSecondaryClass=pf-v5-c-button pf-m-secondary
|
||||
kcButtonBlockClass=pf-m-block
|
||||
kcButtonLinkClass=pf-v5-c-button pf-m-link
|
||||
kcCommonLogoIdP=pf-v5-c-login__main-footer-links-item
|
||||
|
@ -79,3 +80,11 @@ kcLoginMainBody=pf-v5-c-login__main-body
|
|||
|
||||
kcContentWrapperClass=pf-v5-u-mb-md-on-md
|
||||
kcWebAuthnDefaultIcon=pf-v5-c-icon pf-m-lg
|
||||
kcMarginTopClass=pf-v5-u-mt-md-on-md
|
||||
|
||||
kcLoginOTPListClass=pf-v5-c-tile
|
||||
kcLoginOTPListItemHeaderClass=pf-v5-c-tile__header pf-m-stacked
|
||||
kcLoginOTPListItemIconBodyClass=pf-v5-c-tile__icon
|
||||
kcLoginOTPListItemIconClass=fa fa-mobile
|
||||
kcLoginOTPListItemTitleClass=pf-v5-c-tile__title
|
||||
kcLoginOTPListSelectedClass=pf-m-selected
|
|
@ -127,28 +127,48 @@
|
|||
</#macro>
|
||||
|
||||
<#macro selectTag attribute>
|
||||
<select id="${attribute.name}" name="${attribute.name}" class="${properties.kcInputClass!}"
|
||||
aria-invalid="<#if messagesPerField.existsError('${attribute.name}')>true</#if>"
|
||||
<#if attribute.readOnly>disabled</#if>
|
||||
<#if attribute.annotations.inputType=='multiselect'>multiple</#if>
|
||||
<#if attribute.annotations.inputTypeSize??>size="${attribute.annotations.inputTypeSize}"</#if>
|
||||
>
|
||||
<#if attribute.annotations.inputType=='select'>
|
||||
<option value=""></option>
|
||||
</#if>
|
||||
<div class="${properties.kcInputClass!}">
|
||||
<select id="${attribute.name}" name="${attribute.name}"
|
||||
aria-invalid="<#if messagesPerField.existsError('${attribute.name}')>true</#if>"
|
||||
<#if attribute.readOnly>disabled</#if>
|
||||
<#if attribute.annotations.inputType=='multiselect'>multiple</#if>
|
||||
<#if attribute.annotations.inputTypeSize??>size="${attribute.annotations.inputTypeSize}"</#if>
|
||||
>
|
||||
<#if attribute.annotations.inputType=='select'>
|
||||
<option value=""></option>
|
||||
</#if>
|
||||
|
||||
<#if attribute.annotations.inputOptionsFromValidation?? && attribute.validators[attribute.annotations.inputOptionsFromValidation]?? && attribute.validators[attribute.annotations.inputOptionsFromValidation].options??>
|
||||
<#assign options=attribute.validators[attribute.annotations.inputOptionsFromValidation].options>
|
||||
<#elseif attribute.validators.options?? && attribute.validators.options.options??>
|
||||
<#assign options=attribute.validators.options.options>
|
||||
<#else>
|
||||
<#assign options=[]>
|
||||
</#if>
|
||||
<#if attribute.annotations.inputOptionsFromValidation?? && attribute.validators[attribute.annotations.inputOptionsFromValidation]?? && attribute.validators[attribute.annotations.inputOptionsFromValidation].options??>
|
||||
<#assign options=attribute.validators[attribute.annotations.inputOptionsFromValidation].options>
|
||||
<#elseif attribute.validators.options?? && attribute.validators.options.options??>
|
||||
<#assign options=attribute.validators.options.options>
|
||||
<#else>
|
||||
<#assign options=[]>
|
||||
</#if>
|
||||
|
||||
<#list options as option>
|
||||
<option value="${option}" <#if attribute.values?seq_contains(option)>selected</#if>><@selectOptionLabelText attribute=attribute option=option/></option>
|
||||
</#list>
|
||||
</select>
|
||||
<#list options as option>
|
||||
<option value="${option}" <#if attribute.values?seq_contains(option)>selected</#if>><@selectOptionLabelText attribute=attribute option=option/></option>
|
||||
</#list>
|
||||
</select>
|
||||
<span class="${properties.kcFormControlUtilClass}">
|
||||
<span class="${properties.kcFormControlToggleIcon!}">
|
||||
<svg
|
||||
class="pf-v5-svg"
|
||||
viewBox="0 0 320 512"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
role="img"
|
||||
width="1em"
|
||||
height="1em"
|
||||
>
|
||||
<path
|
||||
d="M31.3 192h257.3c17.8 0 26.7 21.5 14.1 34.1L174.1 354.8c-7.8 7.8-20.5 7.8-28.3 0L17.2 226.1C4.6 213.5 13.5 192 31.3 192z"
|
||||
>
|
||||
</path>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</#macro>
|
||||
|
||||
<#macro inputTagSelects attribute>
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
<#import "template.ftl" as layout>
|
||||
<#import "password-commons.ftl" as passwordCommons>
|
||||
<#import "buttons.ftl" as buttons>
|
||||
|
||||
<@layout.registrationLayout; section>
|
||||
<#if section = "title">
|
||||
title
|
||||
<#elseif section = "header">
|
||||
<span class="${properties.kcWebAuthnKeyIcon!}"></span>
|
||||
${kcSanitize(msg("webauthn-registration-title"))?no_esc}
|
||||
<#elseif section = "form">
|
||||
|
||||
<form id="register" action="${url.loginAction}" method="post">
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<input type="hidden" id="clientDataJSON" name="clientDataJSON"/>
|
||||
<input type="hidden" id="attestationObject" name="attestationObject"/>
|
||||
<input type="hidden" id="publicKeyCredentialId" name="publicKeyCredentialId"/>
|
||||
<input type="hidden" id="authenticatorLabel" name="authenticatorLabel"/>
|
||||
<input type="hidden" id="transports" name="transports"/>
|
||||
<input type="hidden" id="error" name="error"/>
|
||||
<@passwordCommons.logoutOtherSessions/>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<script type="module">
|
||||
import { registerByWebAuthn } from "${url.resourcesPath}/js/webauthnRegister.js";
|
||||
const registerButton = document.getElementById('registerWebAuthn');
|
||||
registerButton.addEventListener("click", function() {
|
||||
const input = {
|
||||
challenge : '${challenge}',
|
||||
userid : '${userid}',
|
||||
username : '${username}',
|
||||
signatureAlgorithms : [<#list signatureAlgorithms as sigAlg>${sigAlg?c},</#list>],
|
||||
rpEntityName : '${rpEntityName}',
|
||||
rpId : '${rpId}',
|
||||
attestationConveyancePreference : '${attestationConveyancePreference}',
|
||||
authenticatorAttachment : '${authenticatorAttachment}',
|
||||
requireResidentKey : '${requireResidentKey}',
|
||||
userVerificationRequirement : '${userVerificationRequirement}',
|
||||
createTimeout : ${createTimeout},
|
||||
excludeCredentialIds : '${excludeCredentialIds}',
|
||||
initLabel : "${msg("webauthn-registration-init-label")?no_esc}",
|
||||
initLabelPrompt : "${msg("webauthn-registration-init-label-prompt")?no_esc}",
|
||||
errmsg : "${msg("webauthn-unsupported-browser-text")?no_esc}"
|
||||
};
|
||||
registerByWebAuthn(input);
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="pf-v5-u-py-lg ${properties.kcFormClass!}">
|
||||
<@buttons.actionGroup>
|
||||
<@buttons.button id="registerWebAuthn" label="doRegisterSecurityKey" />
|
||||
<#if !isSetRetry?has_content && isAppInitiatedAction?has_content>
|
||||
<form action="${url.loginAction}" id="kc-webauthn-settings-form" method="post">
|
||||
<@buttons.button id="cancelWebAuthnAIA" name="cancel-aia" label="doCancel" class=["kcButtonSecondaryClass"]/>
|
||||
</form>
|
||||
</#if>
|
||||
</@buttons.actionGroup>
|
||||
</div>
|
||||
</#if>
|
||||
</@layout.registrationLayout>
|
Loading…
Reference in a new issue