diff --git a/README.md b/README.md index 6bbcda7..8fe3afd 100644 --- a/README.md +++ b/README.md @@ -16,8 +16,8 @@ To start a Dev Keycloak instance that can show the pub.solar theme, you need to -e KEYCLOAK_ADMIN_PASSWORD=admin \ -v $(pwd):/opt/keycloak/themes/pub.solar \ -v $(pwd)/.dev-import:/opt/keycloak/data/import \ - quay.io/keycloak/keycloak:23.0.6 \ - start-dev --import-realm --features="declarative-user-profile" + quay.io/keycloak/keycloak:25.0.6 \ + start-dev --import-realm ``` 3. After this, you can start and stop the container using `docker start keycloak-theme-dev` and `docker-stop keycloak-theme-dev`. diff --git a/login/code.ftl b/login/code.ftl index 6830fc4..bb0621d 100644 --- a/login/code.ftl +++ b/login/code.ftl @@ -4,7 +4,7 @@ <#if code.success> ${msg("codeSuccessTitle")} <#else> - ${msg("codeErrorTitle", code.error)} + ${kcSanitize(msg("codeErrorTitle", code.error))} <#elseif section = "form">
@@ -12,7 +12,7 @@

${msg("copyCodeInstruction")}

<#else> -

${code.error}

+

${kcSanitize(code.error)}

diff --git a/login/delete-credential.ftl b/login/delete-credential.ftl new file mode 100644 index 0000000..57f30e7 --- /dev/null +++ b/login/delete-credential.ftl @@ -0,0 +1,15 @@ +<#import "template.ftl" as layout> +<@layout.registrationLayout displayMessage=false; section> + <#if section = "header"> + ${msg("deleteCredentialTitle", credentialLabel)} + <#elseif section = "form"> +
+ ${msg("deleteCredentialMessage", credentialLabel)} +
+
+ + +
+
+ + diff --git a/login/info.ftl b/login/info.ftl index 8da0cb7..d8cb648 100644 --- a/login/info.ftl +++ b/login/info.ftl @@ -2,13 +2,13 @@ <@layout.registrationLayout displayMessage=false; section> <#if section = "header"> <#if messageHeader??> - ${messageHeader} + ${kcSanitize(msg("${messageHeader}"))?no_esc} <#else> ${message.summary} <#elseif section = "form">
-

${message.summary}<#if requiredActions??><#list requiredActions>: <#items as reqActionItem>${msg("requiredAction.${reqActionItem}")}<#sep>, <#else>

+

${message.summary}<#if requiredActions??><#list requiredActions>: <#items as reqActionItem>${kcSanitize(msg("requiredAction.${reqActionItem}"))?no_esc}<#sep>, <#else>

<#if skipLink??> <#else> <#if pageRedirectUri?has_content> diff --git a/login/login-idp-link-confirm-override.ftl b/login/login-idp-link-confirm-override.ftl new file mode 100644 index 0000000..a5b3630 --- /dev/null +++ b/login/login-idp-link-confirm-override.ftl @@ -0,0 +1,12 @@ +<#import "template.ftl" as layout> +<@layout.registrationLayout; section> + <#if section = "header"> + ${msg("confirmOverrideIdpTitle")} + <#elseif section = "form"> +
+ ${msg("pageExpiredMsg1")} ${msg("doClickHere")} + + +
+ + diff --git a/login/login-otp.ftl b/login/login-otp.ftl index b297ee0..a43778d 100644 --- a/login/login-otp.ftl +++ b/login/login-otp.ftl @@ -1,58 +1,58 @@ <#import "template.ftl" as layout> <@layout.registrationLayout displayMessage=!messagesPerField.existsError('totp'); section> - <#if section="header"> - ${msg("doLogIn")} - <#elseif section="form"> -
- <#if otpLogin.userOtpCredentials?size gt 1> -
- <#list otpLogin.userOtpCredentials as otpCredential> - checked="checked"> - - + <#if section="header"> + ${msg("doLogIn")} + <#elseif section="form"> + + <#if otpLogin.userOtpCredentials?size gt 1> +
+
+ <#list otpLogin.userOtpCredentials as otpCredential> + checked="checked"> + + +
+
+ + +
+
+ +
+ +
+ + + <#if messagesPerField.existsError('totp')> + + ${kcSanitize(messagesPerField.get('totp'))?no_esc} + + +
- -
- +
+
+
+
+
- - - <#if messagesPerField.existsError('totp')> - - ${kcSanitize(messagesPerField.get('totp'))?no_esc} - - -
- -
- -
- - - +
+ +
+
+ + + \ No newline at end of file diff --git a/login/login-passkeys-conditional-authenticate.ftl b/login/login-passkeys-conditional-authenticate.ftl new file mode 100644 index 0000000..81be113 --- /dev/null +++ b/login/login-passkeys-conditional-authenticate.ftl @@ -0,0 +1,143 @@ +<#import "template.ftl" as layout> +<@layout.registrationLayout displayInfo=(realm.registrationAllowed && !registrationDisabled??); section> + <#if section = "title"> + title + <#elseif section = "header"> + ${kcSanitize(msg("passkey-login-title"))?no_esc} + <#elseif section = "form"> +
+ + + + + + +
+ +
+ <#if authenticators??> +
+ <#list authenticators.authenticators as authenticator> + + +
+ + <#if shouldDisplayAuthenticators?? && shouldDisplayAuthenticators> + <#if authenticators.authenticators?size gt 1> +

${kcSanitize(msg("passkey-available-authenticators"))?no_esc}

+ + +
+ <#list authenticators.authenticators as authenticator> +
+
+ +
+
+
+ ${kcSanitize(msg('${authenticator.label}'))?no_esc} +
+ + <#if authenticator.transports?? && authenticator.transports.displayNameProperties?has_content> +
+ <#list authenticator.transports.displayNameProperties as nameProperty> + ${kcSanitize(msg('${nameProperty!}'))?no_esc} + <#if nameProperty?has_next> + , + + +
+ + +
+ + ${kcSanitize(msg('passkey-createdAt-label'))?no_esc} + + + ${kcSanitize(authenticator.createdAt)?no_esc} + +
+
+
+
+ +
+ + + +
+
+ <#if realm.password> + + + + +
+
+
+ + + + <#elseif section = "info"> + <#if realm.registrationAllowed && !registrationDisabled??> +
+ ${msg("noAccount")} ${msg("doRegister")} +
+ + + + diff --git a/login/login-password.ftl b/login/login-password.ftl index dc60ed1..aa984b1 100644 --- a/login/login-password.ftl +++ b/login/login-password.ftl @@ -1,55 +1,52 @@ <#import "template.ftl" as layout> <@layout.registrationLayout displayMessage=!messagesPerField.existsError('password'); section> - <#if section = "header"> - ${msg("doLogIn")} - <#elseif section = "form"> -
-
- - - <#if messagesPerField.existsError('password')> - - ${kcSanitize(messagesPerField.get('password'))?no_esc} - - -
+ <#if section = "header"> + ${msg("doLogIn")} + <#elseif section = "form"> +
+
+ +
+
+ +
+ + +
+ <#if messagesPerField.existsError('password')> + + ${kcSanitize(messagesPerField.get('password'))?no_esc} + + +
-
- <#if realm.resetPasswordAllowed> - ${msg("doForgotPassword")} - -
+
+
+
+
+ <#if realm.resetPasswordAllowed> + ${msg("doForgotPassword")} + +
+
-
- -
- -
-
- +
+ +
+ +
+
+ + diff --git a/login/login-recovery-authn-code-config.ftl b/login/login-recovery-authn-code-config.ftl index ef81710..c664fc7 100644 --- a/login/login-recovery-authn-code-config.ftl +++ b/login/login-recovery-authn-code-config.ftl @@ -6,7 +6,7 @@ ${msg("recovery-code-config-header")} <#elseif section = "form"> -
+
@@ -26,7 +26,7 @@ -
+
@@ -40,7 +40,7 @@
- @@ -75,7 +75,7 @@ /* copy recovery codes */ function copyRecoveryCodes() { var tmpTextarea = document.createElement("textarea"); - var codes = document.getElementById("kc-recovery-codes-list").getElementsByTagName("li"); + var codes = document.querySelectorAll("#kc-recovery-codes-list li"); for (i = 0; i < codes.length; i++) { tmpTextarea.value = tmpTextarea.value + codes[i].innerText + "\n"; } @@ -106,7 +106,7 @@ } function parseRecoveryCodeList() { - var recoveryCodes = document.querySelectorAll(".kc-recovery-codes-list li"); + var recoveryCodes = document.querySelectorAll("#kc-recovery-codes-list li"); var recoveryCodeList = ""; for (var i = 0; i < recoveryCodes.length; i++) { @@ -160,7 +160,7 @@ `@page { size: auto; margin-top: 0; } body { width: 480px; } div { list-style-type: none; font-family: monospace } - p:first-of-type { margin-top: 48px }` + p:first-of-type { margin-top: 48px }`; return printFileContent = "" + diff --git a/login/login-recovery-authn-code-input.ftl b/login/login-recovery-authn-code-input.ftl index f6cad67..8d67d43 100644 --- a/login/login-recovery-authn-code-input.ftl +++ b/login/login-recovery-authn-code-input.ftl @@ -1,5 +1,5 @@ <#import "template.ftl" as layout> -<@layout.registrationLayout; section> +<@layout.registrationLayout displayMessage=!messagesPerField.existsError('recoveryCodeInput'); section> <#if section = "header"> ${msg("auth-recovery-code-header")} @@ -11,7 +11,19 @@
- + + + <#if messagesPerField.existsError('recoveryCodeInput')> + + ${kcSanitize(messagesPerField.get('recoveryCodeInput'))?no_esc} + +
diff --git a/login/login-reset-otp.ftl b/login/login-reset-otp.ftl new file mode 100644 index 0000000..d8fc580 --- /dev/null +++ b/login/login-reset-otp.ftl @@ -0,0 +1,33 @@ +<#import "template.ftl" as layout> +<@layout.registrationLayout displayMessage=!messagesPerField.existsError('totp'); section> + <#if section="header"> + ${msg("doLogIn")} + <#elseif section="form"> +
+
+
+

${msg("otp-reset-description")}

+ + <#list configuredOtpCredentials.userOtpCredentials as otpCredential> + checked="checked"> + + + +
+
+ +
+
+
+
+
+ + diff --git a/login/login-reset-password.ftl b/login/login-reset-password.ftl index 518e3e6..800faea 100644 --- a/login/login-reset-password.ftl +++ b/login/login-reset-password.ftl @@ -1,50 +1,39 @@ <#import "template.ftl" as layout> <@layout.registrationLayout displayInfo=true displayMessage=!messagesPerField.existsError('username'); section> - <#if section = "header"> - ${msg("emailForgotTitle")} - <#elseif section = "info" > - <#if realm.duplicateEmailsAllowed> - ${msg("emailInstructionUsername")} - <#else> - ${msg("emailInstruction")} - - <#elseif section = "form"> -
-
- - - <#if messagesPerField.existsError('username')> - - ${kcSanitize(messagesPerField.get('username'))?no_esc} - - -
-
-
- -
-
+ <#if section = "header"> + ${msg("emailForgotTitle")} + <#elseif section = "form"> + +
+
+ +
+
+ + <#if messagesPerField.existsError('username')> + + ${kcSanitize(messagesPerField.get('username'))?no_esc} + + +
+
+
+ + <#elseif section = "info" > + <#if realm.duplicateEmailsAllowed> + ${msg("emailInstructionUsername")} + <#else> + ${msg("emailInstruction")} + + diff --git a/login/login-update-password.ftl b/login/login-update-password.ftl index a61efc2..f6414ec 100644 --- a/login/login-update-password.ftl +++ b/login/login-update-password.ftl @@ -5,19 +5,23 @@ ${msg("updatePasswordTitle")} <#elseif section = "form">
- - -
- +
+ + +
<#if messagesPerField.existsError('password')> @@ -32,11 +36,19 @@
- +
+ + +
<#if messagesPerField.existsError('password-confirm')> @@ -60,5 +72,6 @@
+ diff --git a/login/login-update-profile.ftl b/login/login-update-profile.ftl index be579b0..e09f5c3 100644 --- a/login/login-update-profile.ftl +++ b/login/login-update-profile.ftl @@ -1,83 +1,12 @@ <#import "template.ftl" as layout> -<@layout.registrationLayout displayMessage=!messagesPerField.existsError('username','email','firstName','lastName'); section> +<#import "user-profile-commons.ftl" as userProfileCommons> +<@layout.registrationLayout displayMessage=messagesPerField.exists('global') displayRequiredFields=true; section> <#if section = "header"> ${msg("loginProfileTitle")} <#elseif section = "form">
- <#if user.editUsernameAllowed> -
-
- -
-
- - <#if messagesPerField.existsError('username')> - - ${kcSanitize(messagesPerField.get('username'))?no_esc} - - -
-
- - <#if user.editEmailAllowed> -
-
- -
-
- - - <#if messagesPerField.existsError('email')> - - ${kcSanitize(messagesPerField.get('email'))?no_esc} - - -
-
- - -
-
- -
-
- - - <#if messagesPerField.existsError('firstName')> - - ${kcSanitize(messagesPerField.get('firstName'))?no_esc} - - -
-
- -
-
- -
-
- - - <#if messagesPerField.existsError('lastName')> - - ${kcSanitize(messagesPerField.get('lastName'))?no_esc} - - -
-
+ <@userProfileCommons.userProfileFormFields/>
@@ -87,13 +16,13 @@
<#if isAppInitiatedAction??> - - + + <#else> - +
- + \ No newline at end of file diff --git a/login/login-username.ftl b/login/login-username.ftl index a4f9760..4e0d312 100644 --- a/login/login-username.ftl +++ b/login/login-username.ftl @@ -1,111 +1,87 @@ <#import "template.ftl" as layout> <@layout.registrationLayout displayMessage=!messagesPerField.existsError('username') displayInfo=(realm.password && realm.registrationAllowed && !registrationDisabled??); section> - <#if section = "header"> - ${msg("loginAccountTitle")} - <#elseif section = "form"> - <#if realm.password> -
- <#if !usernameHidden??> -
- + <#if section = "header"> + ${msg("loginAccountTitle")} + <#elseif section = "form"> +
+
+ <#if realm.password> + + <#if !usernameHidden??> +
+ - + - <#if messagesPerField.existsError('username')> - - ${kcSanitize(messagesPerField.get('username'))?no_esc} - - -
- + <#if messagesPerField.existsError('username')> + + ${kcSanitize(messagesPerField.get('username'))?no_esc} + + +
+ - <#if realm.rememberMe && !usernameHidden??> -
-
-
- -
+
+
+ <#if realm.rememberMe && !usernameHidden??> +
+ +
+ +
+
+ +
+ +
+ +
-
- - -
-
- - - <#elseif section = "info" > - <#if realm.password && realm.registrationAllowed && !registrationDisabled??> -
- ${msg("noAccount")} ${msg("doRegister")} -
- - <#elseif section = "socialProviders" > - <#if realm.password && social.providers??> -
-
-

${msg("identity-provider-login-label")}

+ <#elseif section = "info" > + <#if realm.password && realm.registrationAllowed && !registrationDisabled??> +
+ ${msg("noAccount")} ${msg("doRegister")} +
+ + <#elseif section = "socialProviders" > + <#if realm.password && social?? && social.providers?has_content> +
+
+

${msg("identity-provider-login-label")}

- -
+ +
+ - diff --git a/login/login.ftl b/login/login.ftl index 51d2102..a7361e9 100644 --- a/login/login.ftl +++ b/login/login.ftl @@ -1,132 +1,115 @@ <#import "template.ftl" as layout> <@layout.registrationLayout displayMessage=!messagesPerField.existsError('username','password') displayInfo=realm.password && realm.registrationAllowed && !registrationDisabled??; section> - <#if section = "header"> - ${msg("loginAccountTitle")} - <#elseif section = "form"> - <#if realm.password> -
- <#if !usernameHidden??> -
- + <#if section = "header"> + ${msg("loginAccountTitle")} + <#elseif section = "form"> +
+
+ <#if realm.password> + + <#if !usernameHidden??> +
+ - + - <#if messagesPerField.existsError('username','password')> - - ${kcSanitize(messagesPerField.getFirstError('username','password'))?no_esc} - + <#if messagesPerField.existsError('username','password')> + + ${kcSanitize(messagesPerField.getFirstError('username','password'))?no_esc} + + + +
+ + +
+ + +
+ + +
+ + <#if usernameHidden?? && messagesPerField.existsError('username','password')> + + ${kcSanitize(messagesPerField.getFirstError('username','password'))?no_esc} + + + +
+ +
+
+ <#if realm.rememberMe && !usernameHidden??> +
+ +
+ +
+
+ <#if realm.resetPasswordAllowed> + ${msg("doForgotPassword")} + +
+ +
+ +
+ value="${auth.selectedCredential}"/> + +
+ - -
+
+
+ + <#elseif section = "info" > + <#if realm.password && realm.registrationAllowed && !registrationDisabled??> +
+
+ ${msg("noAccount")} ${msg("doRegister")} +
+
+ <#elseif section = "socialProviders" > + <#if realm.password && social?? && social.providers?has_content> +
+
+

${msg("identity-provider-login-label")}

-
- - - - - <#if usernameHidden?? && messagesPerField.existsError('username','password')> - - ${kcSanitize(messagesPerField.getFirstError('username','password'))?no_esc} - - - - <#if realm.resetPasswordAllowed> - - -
- -
- <#if realm.rememberMe && !usernameHidden??> -
- -
- -
- -
- value="${auth.selectedCredential}"/> - -
- + - <#elseif section = "info" > - <#if realm.password && realm.registrationAllowed && !registrationDisabled??> -
-
- ${msg("noAccount")} - ${msg("doRegister")} - -
-
- - <#elseif section = "socialProviders" > - <#if realm.password && social.providers??> -
-
-

${msg("identity-provider-login-label")}

- - -
- - - diff --git a/login/register-commons.ftl b/login/register-commons.ftl new file mode 100644 index 0000000..7007797 --- /dev/null +++ b/login/register-commons.ftl @@ -0,0 +1,27 @@ +<#macro termsAcceptance> + <#if termsAcceptanceRequired??> +
+
+ ${msg("termsTitle")} +
+ ${kcSanitize(msg("termsText"))?no_esc} +
+
+
+
+
+ + +
+ <#if messagesPerField.existsError('termsAccepted')> +
+ + ${kcSanitize(messagesPerField.get('termsAccepted'))?no_esc} + +
+ +
+ + diff --git a/login/register.ftl b/login/register.ftl index 62e1eb1..f640848 100644 --- a/login/register.ftl +++ b/login/register.ftl @@ -1,112 +1,112 @@ <#import "template.ftl" as layout> -<@layout.registrationLayout displayMessage=!messagesPerField.existsError('firstName','lastName','email','username','password','password-confirm'); section> - <#if section = "header"> - ${msg("registerTitle")} - <#elseif section = "form"> -
- + <#if passwordRequired?? && (attribute.name == 'username' || (attribute.name == 'email' && realm.registrationEmailAsUsername))> +
+
+ * +
+
+
+ + +
- <#if messagesPerField.existsError('lastName')> - - ${kcSanitize(messagesPerField.get('lastName'))?no_esc} - - - + <#if messagesPerField.existsError('password')> + + ${kcSanitize(messagesPerField.get('password'))?no_esc} + + +
+
- <#if !realm.registrationEmailAsUsername> -
- - +
+
+ * +
+
+
+ + +
- <#if messagesPerField.existsError('username')> - - ${kcSanitize(messagesPerField.get('username'))?no_esc} - - -
- + <#if messagesPerField.existsError('password-confirm')> + + ${kcSanitize(messagesPerField.get('password-confirm'))?no_esc} + + +
+
+ + + -
- - + <@registerCommons.termsAcceptance/> - <#if messagesPerField.existsError('email')> - - ${kcSanitize(messagesPerField.get('email'))?no_esc} - - -
+ <#if recaptchaRequired?? && (recaptchaVisible!false)> +
+
+
+
+
+ - <#if passwordRequired??> -
- - +
+ - <#if messagesPerField.existsError('password')> - - ${kcSanitize(messagesPerField.get('password'))?no_esc} - - -
- -
- - - - <#if messagesPerField.existsError('password-confirm')> - - ${kcSanitize(messagesPerField.get('password-confirm'))?no_esc} - - -
- - - <#if recaptchaRequired??> -
-
-
- - -
- -
- - - + <#if recaptchaRequired?? && !(recaptchaVisible!false)> + +
+ +
+ <#else> +
+ +
+ +
+ + + diff --git a/login/select-authenticator.ftl b/login/select-authenticator.ftl index 6a2abd4..4fbe2d3 100644 --- a/login/select-authenticator.ftl +++ b/login/select-authenticator.ftl @@ -1,12 +1,6 @@ <#import "template.ftl" as layout> <@layout.registrationLayout displayInfo=false; section> <#if section = "header" || section = "show-username"> - <#if section = "header"> ${msg("loginChooseAuthenticator")} @@ -15,13 +9,13 @@
<#list auth.authenticationSelections as authenticationSelection> -
+ -
diff --git a/login/template.ftl b/login/template.ftl index f8dd10b..95a506a 100644 --- a/login/template.ftl +++ b/login/template.ftl @@ -1,43 +1,62 @@ <#macro registrationLayout bodyClass="" displayInfo=false displayMessage=true displayRequiredFields=false> - - + + lang="${locale.currentLanguageTag}"> - - - + + + - + <#if properties.meta?has_content> + <#list properties.meta?split(' ') as meta> + + + - ${msg("loginTitle",(realm.displayName!''))} + ${msg("loginTitle",(realm.displayName!''))} - + - + - - + + - <#if properties.stylesCommon?has_content> - <#list properties.stylesCommon?split(' ') as style> - - - - <#if properties.styles?has_content> - <#list properties.styles?split(' ') as style> - - - - <#if properties.scripts?has_content> - <#list properties.scripts?split(' ') as script> - - - - <#if properties.scriptsCommon?has_content> - <#list properties.scriptsCommon?split(' ') as script> - - - + <#if properties.stylesCommon?has_content> + <#list properties.stylesCommon?split(' ') as style> + + + + <#if properties.styles?has_content> + <#list properties.styles?split(' ') as style> + + + + <#if properties.scripts?has_content> + <#list properties.scripts?split(' ') as script> + + + + + + <#if scripts??> + <#list scripts as script> + + + + diff --git a/login/theme.properties b/login/theme.properties index 04e01f7..1092c27 100644 --- a/login/theme.properties +++ b/login/theme.properties @@ -16,3 +16,10 @@ kcInputErrorMessageClass=ps-form-group--error kcInputClass=ps-input kcWebAuthnKeyIcon=pficon pficon-key + +kcAuthenticatorPasswordClass=ps-button +kcAuthenticatorWebAuthnClass=ps-button +kcAuthenticatorWebAuthnPasswordlessClass=ps-button + +kcFormPasswordVisibilityIconShow=fa fa-eye +kcFormPasswordVisibilityIconHide=fa fa-eye-slash diff --git a/login/update-email.ftl b/login/update-email.ftl index e63b012..1650e25 100644 --- a/login/update-email.ftl +++ b/login/update-email.ftl @@ -1,27 +1,12 @@ <#import "template.ftl" as layout> <#import "password-commons.ftl" as passwordCommons> -<@layout.registrationLayout displayMessage=!messagesPerField.existsError('email'); section> +<#import "user-profile-commons.ftl" as userProfileCommons> +<@layout.registrationLayout displayMessage=messagesPerField.exists('global') displayRequiredFields=true; section> <#if section = "header"> ${msg("updateEmailTitle")} <#elseif section = "form">
-
-
- -
-
- - - <#if messagesPerField.existsError('email')> - - ${kcSanitize(messagesPerField.get('email'))?no_esc} - - -
-
+ <@userProfileCommons.userProfileFormFields/>
diff --git a/login/user-profile-commons.ftl b/login/user-profile-commons.ftl index 140eea3..8aa885d 100644 --- a/login/user-profile-commons.ftl +++ b/login/user-profile-commons.ftl @@ -3,27 +3,31 @@ <#list profile.attributes as attribute> - <#assign groupName = attribute.group!""> - <#if groupName != currentGroup> - <#assign currentGroup=groupName> - <#if currentGroup != "" > -
+ <#assign group = (attribute.group)!""> + <#if group != currentGroup> + <#assign currentGroup=group> + <#if currentGroup != ""> +
+ data-${key}="${value}" + + > - <#assign groupDisplayHeader=attribute.groupDisplayHeader!""> + <#assign groupDisplayHeader=group.displayHeader!""> <#if groupDisplayHeader != ""> - <#assign groupHeaderText=advancedMsg(attribute.groupDisplayHeader)!groupName> + <#assign groupHeaderText=advancedMsg(groupDisplayHeader)!group> <#else> - <#assign groupHeaderText=groupName> + <#assign groupHeaderText=group.name!"">
- +
- <#assign groupDisplayDescription=attribute.groupDisplayDescription!""> + <#assign groupDisplayDescription=group.displayDescription!""> <#if groupDisplayDescription != ""> - <#assign groupDescriptionText=advancedMsg(attribute.groupDisplayDescription)!""> + <#assign groupDescriptionText=advancedMsg(groupDisplayDescription)!"">
- +
@@ -53,6 +57,10 @@
<#nested "afterField" attribute> + + <#list profile.html5DataAnnotations?keys as key> + + <#macro inputFieldByType attribute> @@ -69,16 +77,22 @@ <@inputTagSelects attribute=attribute/> <#break> <#default> - <@inputTag attribute=attribute/> + <#if attribute.multivalued && attribute.values?has_content> + <#list attribute.values as value> + <@inputTag attribute=attribute value=value!''/> + + <#else> + <@inputTag attribute=attribute value=attribute.value!''/> + -<#macro inputTag attribute> - + disabled <#if attribute.autocomplete??>autocomplete="${attribute.autocomplete}" - <#if attribute.annotations.inputTypePlaceholder??>placeholder="${attribute.annotations.inputTypePlaceholder}" + <#if attribute.annotations.inputTypePlaceholder??>placeholder="${advancedMsg(attribute.annotations.inputTypePlaceholder)}" <#if attribute.annotations.inputTypePattern??>pattern="${attribute.annotations.inputTypePattern}" <#if attribute.annotations.inputTypeSize??>size="${attribute.annotations.inputTypeSize}" <#if attribute.annotations.inputTypeMaxlength??>maxlength="${attribute.annotations.inputTypeMaxlength}" @@ -86,6 +100,10 @@ <#if attribute.annotations.inputTypeMax??>max="${attribute.annotations.inputTypeMax}" <#if attribute.annotations.inputTypeMin??>min="${attribute.annotations.inputTypeMin}" <#if attribute.annotations.inputTypeStep??>step="${attribute.annotations.inputTypeStep}" + <#if attribute.annotations.inputTypeStep??>step="${attribute.annotations.inputTypeStep}" + <#list attribute.html5DataAnnotations as key, value> + data-${key}="${value}" + /> @@ -128,13 +146,14 @@ <#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 options??> - <#list options as option> + <#list options as option> - - + + @@ -144,21 +163,22 @@ <#assign classDiv=properties.kcInputClassRadio!> <#assign classInput=properties.kcInputClassRadioInput!> <#assign classLabel=properties.kcInputClassRadioLabel!> - <#else> + <#else> <#assign inputType='checkbox'> <#assign classDiv=properties.kcInputClassCheckbox!> <#assign classInput=properties.kcInputClassCheckboxInput!> <#assign classLabel=properties.kcInputClassCheckboxLabel!> - + <#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 options??> - <#list options as option> + <#list options as option>
- - - + <#macro selectOptionLabelText attribute option> @@ -184,4 +202,4 @@ - \ No newline at end of file + diff --git a/login/webauthn-authenticate.ftl b/login/webauthn-authenticate.ftl index 00eb269..8148e80 100644 --- a/login/webauthn-authenticate.ftl +++ b/login/webauthn-authenticate.ftl @@ -1,168 +1,102 @@ - <#import "template.ftl" as layout> - <@layout.registrationLayout; section> - <#if section = "title"> - title - <#elseif section = "header"> - ${kcSanitize(msg("webauthn-login-title"))?no_esc} - <#elseif section = "form"> -
- - - - - - - - +<#import "template.ftl" as layout> +<@layout.registrationLayout displayInfo=(realm.registrationAllowed && !registrationDisabled??); section> + <#if section = "title"> + title + <#elseif section = "header"> + ${kcSanitize(msg("webauthn-login-title"))?no_esc} + <#elseif section = "form"> +
+
+ + + + + + +
-
- <#if authenticators??> -
- <#list authenticators.authenticators as authenticator> - - -
- - <#if shouldDisplayAuthenticators?? && shouldDisplayAuthenticators> - <#if authenticators.authenticators?size gt 1> -

${kcSanitize(msg("webauthn-available-authenticators"))?no_esc}

- - -
- <#list authenticators.authenticators as authenticator> -
-
- -
-
-
- ${kcSanitize(msg('${authenticator.label}'))?no_esc} -
- - <#if authenticator.transports?? && authenticator.transports.displayNameProperties?has_content> -
- <#list authenticator.transports.displayNameProperties as nameProperty> - ${kcSanitize(msg('${nameProperty!}'))?no_esc} - <#if nameProperty?has_next> - , - +
+ <#if authenticators??> +
+ <#list authenticators.authenticators as authenticator> + -
+ + + <#if shouldDisplayAuthenticators?? && shouldDisplayAuthenticators> + <#if authenticators.authenticators?size gt 1> +

${kcSanitize(msg("webauthn-available-authenticators"))?no_esc}

+ + +
+ <#list authenticators.authenticators as authenticator> +
+
+ +
+
+
+ ${kcSanitize(msg('${authenticator.label}'))?no_esc} +
+ + <#if authenticator.transports?? && authenticator.transports.displayNameProperties?has_content> +
+ <#list authenticator.transports.displayNameProperties as nameProperty> + ${kcSanitize(msg('${nameProperty!}'))?no_esc} + <#if nameProperty?has_next> + , + + +
+ + +
+ + ${kcSanitize(msg('webauthn-createdAt-label'))?no_esc} + + + ${kcSanitize(authenticator.createdAt)?no_esc} + +
+
+
+
+ +
+ -
- - ${kcSanitize(msg('webauthn-createdAt-label'))?no_esc} - - - ${kcSanitize(authenticator.createdAt)?no_esc} - -
-
-
+
+
-
- - - -
-
-
-
- - - - function checkAllowCredentials() { - let allowCredentials = []; - let authn_use = document.forms['authn_select'].authn_use_chk; - - if (authn_use !== undefined) { - if (authn_use.length === undefined) { - allowCredentials.push({ - id: base64url.decode(authn_use.value, {loose: true}), - type: 'public-key', - }); - } else { - for (let i = 0; i < authn_use.length; i++) { - allowCredentials.push({ - id: base64url.decode(authn_use[i].value, {loose: true}), - type: 'public-key', - }); - } - } - } - doAuthenticate(allowCredentials); - } - - - function doAuthenticate(allowCredentials) { - - // Check if WebAuthn is supported by this browser - if (!window.PublicKeyCredential) { - $("#error").val("${msg("webauthn-unsupported-browser-text")?no_esc}"); - $("#webauth").submit(); - return; - } - - let challenge = "${challenge}"; - let userVerification = "${userVerification}"; - let rpId = "${rpId}"; - let publicKey = { - rpId : rpId, - challenge: base64url.decode(challenge, { loose: true }) - }; - - let createTimeout = ${createTimeout}; - if (createTimeout !== 0) publicKey.timeout = createTimeout * 1000; - - if (allowCredentials.length) { - publicKey.allowCredentials = allowCredentials; - } - - if (userVerification !== 'not specified') publicKey.userVerification = userVerification; - - navigator.credentials.get({publicKey}) - .then((result) => { - window.result = result; - - let clientDataJSON = result.response.clientDataJSON; - let authenticatorData = result.response.authenticatorData; - let signature = result.response.signature; - - $("#clientDataJSON").val(base64url.encode(new Uint8Array(clientDataJSON), { pad: false })); - $("#authenticatorData").val(base64url.encode(new Uint8Array(authenticatorData), { pad: false })); - $("#signature").val(base64url.encode(new Uint8Array(signature), { pad: false })); - $("#credentialId").val(result.id); - if(result.response.userHandle) { - $("#userHandle").val(base64url.encode(new Uint8Array(result.response.userHandle), { pad: false })); - } - $("#webauth").submit(); - }) - .catch((err) => { - $("#error").val(err); - $("#webauth").submit(); - }) - ; - } - - - <#elseif section = "info"> - - - + <#elseif section = "info"> + <#if realm.registrationAllowed && !registrationDisabled??> +
+ ${msg("noAccount")} ${msg("doRegister")} +
+ + + diff --git a/login/webauthn-register.ftl b/login/webauthn-register.ftl index 3083ce5..af4c950 100644 --- a/login/webauthn-register.ftl +++ b/login/webauthn-register.ftl @@ -1,11 +1,11 @@ - <#import "template.ftl" as layout> - <#import "password-commons.ftl" as passwordCommons> +<#import "template.ftl" as layout> +<#import "password-commons.ftl" as passwordCommons> - <@layout.registrationLayout; section> +<@layout.registrationLayout; section> <#if section = "title"> title <#elseif section = "header"> - + ${kcSanitize(msg("webauthn-registration-title"))?no_esc} <#elseif section = "form"> @@ -21,179 +21,44 @@
- - - + <#if !isSetRetry?has_content && isAppInitiatedAction?has_content>
- +
- +