diff --git a/conf/keycloak_theme/keywind/.DS_Store b/conf/keycloak_theme/keywind/.DS_Store new file mode 100644 index 0000000000..694ba58ec7 Binary files /dev/null and b/conf/keycloak_theme/keywind/.DS_Store differ diff --git a/conf/keycloak_theme/keywind/login/assets/icons/arrow-top-right-on-square.ftl b/conf/keycloak_theme/keywind/login/assets/icons/arrow-top-right-on-square.ftl new file mode 100644 index 0000000000..81c4bf81d8 --- /dev/null +++ b/conf/keycloak_theme/keywind/login/assets/icons/arrow-top-right-on-square.ftl @@ -0,0 +1,7 @@ +<#-- https://github.com/tailwindlabs/heroicons/blob/master/src/20/solid/arrow-top-right-on-square.svg --> +<#macro kw> + + + + + diff --git a/conf/keycloak_theme/keywind/login/assets/icons/chevron-down.ftl b/conf/keycloak_theme/keywind/login/assets/icons/chevron-down.ftl new file mode 100644 index 0000000000..673ef1191f --- /dev/null +++ b/conf/keycloak_theme/keywind/login/assets/icons/chevron-down.ftl @@ -0,0 +1,6 @@ +<#-- https://github.com/tailwindlabs/heroicons/blob/master/src/20/solid/chevron-down.svg --> +<#macro kw> + + + + diff --git a/conf/keycloak_theme/keywind/login/assets/providers/bitbucket.ftl b/conf/keycloak_theme/keywind/login/assets/providers/bitbucket.ftl new file mode 100644 index 0000000000..068bc73d85 --- /dev/null +++ b/conf/keycloak_theme/keywind/login/assets/providers/bitbucket.ftl @@ -0,0 +1,14 @@ +<#-- https://atlassian.design/resources/logo-library --> +<#macro kw name="Bitbucket"> + + ${name} + + + + + + + + + + diff --git a/conf/keycloak_theme/keywind/login/assets/providers/discord.ftl b/conf/keycloak_theme/keywind/login/assets/providers/discord.ftl new file mode 100644 index 0000000000..8ebecaa487 --- /dev/null +++ b/conf/keycloak_theme/keywind/login/assets/providers/discord.ftl @@ -0,0 +1,7 @@ +<#-- https://discord.com/branding --> +<#macro kw name="Discord"> + + ${name} + + + diff --git a/conf/keycloak_theme/keywind/login/assets/providers/facebook.ftl b/conf/keycloak_theme/keywind/login/assets/providers/facebook.ftl new file mode 100644 index 0000000000..bc692e7594 --- /dev/null +++ b/conf/keycloak_theme/keywind/login/assets/providers/facebook.ftl @@ -0,0 +1,8 @@ +<#-- https://www.facebook.com/brand/resources/facebookapp/logo --> +<#macro kw name="Facebook"> + + ${name} + + + + diff --git a/conf/keycloak_theme/keywind/login/assets/providers/github.ftl b/conf/keycloak_theme/keywind/login/assets/providers/github.ftl new file mode 100644 index 0000000000..9523103c47 --- /dev/null +++ b/conf/keycloak_theme/keywind/login/assets/providers/github.ftl @@ -0,0 +1,7 @@ +<#-- https://github.com/logos --> +<#macro kw name="GitHub"> + + ${name} + + + diff --git a/conf/keycloak_theme/keywind/login/assets/providers/gitlab.ftl b/conf/keycloak_theme/keywind/login/assets/providers/gitlab.ftl new file mode 100644 index 0000000000..4acfc132d1 --- /dev/null +++ b/conf/keycloak_theme/keywind/login/assets/providers/gitlab.ftl @@ -0,0 +1,10 @@ +<#-- https://about.gitlab.com/press/press-kit --> +<#macro kw name="GitLab"> + + ${name} + + + + + + diff --git a/conf/keycloak_theme/keywind/login/assets/providers/google.ftl b/conf/keycloak_theme/keywind/login/assets/providers/google.ftl new file mode 100644 index 0000000000..b536cdbb8e --- /dev/null +++ b/conf/keycloak_theme/keywind/login/assets/providers/google.ftl @@ -0,0 +1,10 @@ +<#-- https://developers.google.com/identity/branding-guidelines --> +<#macro kw name="Google"> + + ${name} + + + + + + diff --git a/conf/keycloak_theme/keywind/login/assets/providers/instagram.ftl b/conf/keycloak_theme/keywind/login/assets/providers/instagram.ftl new file mode 100644 index 0000000000..c4996d8803 --- /dev/null +++ b/conf/keycloak_theme/keywind/login/assets/providers/instagram.ftl @@ -0,0 +1,35 @@ +<#-- https://www.facebook.com/brand/resources/instagram/instagram-brand --> +<#macro kw name="Instagram"> + + ${name} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/conf/keycloak_theme/keywind/login/assets/providers/linkedin.ftl b/conf/keycloak_theme/keywind/login/assets/providers/linkedin.ftl new file mode 100644 index 0000000000..944d143357 --- /dev/null +++ b/conf/keycloak_theme/keywind/login/assets/providers/linkedin.ftl @@ -0,0 +1,7 @@ +<#-- https://brand.linkedin.com/downloads --> +<#macro kw name="LinkedIn"> + + ${name} + + + diff --git a/conf/keycloak_theme/keywind/login/assets/providers/microsoft.ftl b/conf/keycloak_theme/keywind/login/assets/providers/microsoft.ftl new file mode 100644 index 0000000000..408635b842 --- /dev/null +++ b/conf/keycloak_theme/keywind/login/assets/providers/microsoft.ftl @@ -0,0 +1,10 @@ +<#-- https://learn.microsoft.com/azure/active-directory/develop/howto-add-branding-in-azure-ad-apps --> +<#macro kw name="Microsoft"> + + ${name} + + + + + + diff --git a/conf/keycloak_theme/keywind/login/assets/providers/oidc.ftl b/conf/keycloak_theme/keywind/login/assets/providers/oidc.ftl new file mode 100644 index 0000000000..f7954ff5e8 --- /dev/null +++ b/conf/keycloak_theme/keywind/login/assets/providers/oidc.ftl @@ -0,0 +1,9 @@ +<#-- https://openid.net/add-openid/logos --> +<#macro kw name="OpenID"> + + ${name} + + + + + diff --git a/conf/keycloak_theme/keywind/login/assets/providers/openshift.ftl b/conf/keycloak_theme/keywind/login/assets/providers/openshift.ftl new file mode 100644 index 0000000000..e85ddef7eb --- /dev/null +++ b/conf/keycloak_theme/keywind/login/assets/providers/openshift.ftl @@ -0,0 +1,11 @@ +<#-- https://www.redhat.com/technologies/cloud-computing/openshift --> +<#macro kw name="Red Hat OpenShift"> + + ${name} + + + + + + + diff --git a/conf/keycloak_theme/keywind/login/assets/providers/paypal.ftl b/conf/keycloak_theme/keywind/login/assets/providers/paypal.ftl new file mode 100644 index 0000000000..7946e03cd2 --- /dev/null +++ b/conf/keycloak_theme/keywind/login/assets/providers/paypal.ftl @@ -0,0 +1,9 @@ +<#-- https://www.paypal.com --> +<#macro kw name="PayPal"> + + ${name} + + + + + diff --git a/conf/keycloak_theme/keywind/login/assets/providers/providers.ftl b/conf/keycloak_theme/keywind/login/assets/providers/providers.ftl new file mode 100644 index 0000000000..b9c55f6c8a --- /dev/null +++ b/conf/keycloak_theme/keywind/login/assets/providers/providers.ftl @@ -0,0 +1,79 @@ +<#import "./bitbucket.ftl" as bitbucketIcon> +<#import "./discord.ftl" as discordIcon> +<#import "./facebook.ftl" as facebookIcon> +<#import "./github.ftl" as githubIcon> +<#import "./gitlab.ftl" as gitlabIcon> +<#import "./google.ftl" as googleIcon> +<#import "./instagram.ftl" as instagramIcon> +<#import "./linkedin.ftl" as linkedinIcon> +<#import "./microsoft.ftl" as microsoftIcon> +<#import "./oidc.ftl" as oidcIcon> +<#import "./openshift.ftl" as openshiftIcon> +<#import "./paypal.ftl" as paypalIcon> +<#import "./slack.ftl" as slackIcon> +<#import "./stackoverflow.ftl" as stackoverflowIcon> +<#import "./twitter.ftl" as twitterIcon> + +<#macro bitbucket> + <@bitbucketIcon.kw /> + + +<#macro discord> + <@discordIcon.kw /> + + +<#macro facebook> + <@facebookIcon.kw /> + + +<#macro github> + <@githubIcon.kw /> + + +<#macro gitlab> + <@gitlabIcon.kw /> + + +<#macro google> + <@googleIcon.kw /> + + +<#macro instagram> + <@instagramIcon.kw /> + + +<#macro linkedin> + <@linkedinIcon.kw /> + + +<#macro microsoft> + <@microsoftIcon.kw /> + + +<#macro oidc> + <@oidcIcon.kw /> + + +<#macro "openshift-v3"> + <@openshiftIcon.kw /> + + +<#macro "openshift-v4"> + <@openshiftIcon.kw /> + + +<#macro paypal> + <@paypalIcon.kw /> + + +<#macro slack> + <@slackIcon.kw /> + + +<#macro stackoverflow> + <@stackoverflowIcon.kw /> + + +<#macro twitter> + <@twitterIcon.kw /> + diff --git a/conf/keycloak_theme/keywind/login/assets/providers/slack.ftl b/conf/keycloak_theme/keywind/login/assets/providers/slack.ftl new file mode 100644 index 0000000000..d4dffe3bbb --- /dev/null +++ b/conf/keycloak_theme/keywind/login/assets/providers/slack.ftl @@ -0,0 +1,14 @@ +<#-- https://slack.com/media-kit --> +<#macro kw name="Slack"> + + ${name} + + + + + + + + + + diff --git a/conf/keycloak_theme/keywind/login/assets/providers/stackoverflow.ftl b/conf/keycloak_theme/keywind/login/assets/providers/stackoverflow.ftl new file mode 100644 index 0000000000..1ffad8d6a0 --- /dev/null +++ b/conf/keycloak_theme/keywind/login/assets/providers/stackoverflow.ftl @@ -0,0 +1,8 @@ +<#-- https://stackoverflow.design/brand/logo --> +<#macro kw name="Stack Overflow"> + + ${name} + + + + diff --git a/conf/keycloak_theme/keywind/login/assets/providers/twitter.ftl b/conf/keycloak_theme/keywind/login/assets/providers/twitter.ftl new file mode 100644 index 0000000000..2bc7e7e49d --- /dev/null +++ b/conf/keycloak_theme/keywind/login/assets/providers/twitter.ftl @@ -0,0 +1,7 @@ +<#-- https://about.twitter.com/en/who-we-are/brand-toolkit --> +<#macro kw name="Twitter"> + + ${name} + + + diff --git a/conf/keycloak_theme/keywind/login/components/atoms/alert.ftl b/conf/keycloak_theme/keywind/login/components/atoms/alert.ftl new file mode 100644 index 0000000000..58e8309f07 --- /dev/null +++ b/conf/keycloak_theme/keywind/login/components/atoms/alert.ftl @@ -0,0 +1,22 @@ +<#macro kw color=""> + <#switch color> + <#case "error"> + <#assign colorClass="bg-red-100 text-red-600"> + <#break> + <#case "info"> + <#assign colorClass="bg-blue-100 text-blue-600"> + <#break> + <#case "success"> + <#assign colorClass="bg-green-100 text-green-600"> + <#break> + <#case "warning"> + <#assign colorClass="bg-orange-100 text-orange-600"> + <#break> + <#default> + <#assign colorClass="bg-blue-100 text-blue-600"> + + + + diff --git a/conf/keycloak_theme/keywind/login/components/atoms/body.ftl b/conf/keycloak_theme/keywind/login/components/atoms/body.ftl new file mode 100644 index 0000000000..dcc94a06ee --- /dev/null +++ b/conf/keycloak_theme/keywind/login/components/atoms/body.ftl @@ -0,0 +1,5 @@ +<#macro kw> + + <#nested> + + diff --git a/conf/keycloak_theme/keywind/login/components/atoms/button-group.ftl b/conf/keycloak_theme/keywind/login/components/atoms/button-group.ftl new file mode 100644 index 0000000000..459120917e --- /dev/null +++ b/conf/keycloak_theme/keywind/login/components/atoms/button-group.ftl @@ -0,0 +1,5 @@ +<#macro kw> +
+ <#nested> +
+ diff --git a/conf/keycloak_theme/keywind/login/components/atoms/button.ftl b/conf/keycloak_theme/keywind/login/components/atoms/button.ftl new file mode 100644 index 0000000000..eeb0af7b7e --- /dev/null +++ b/conf/keycloak_theme/keywind/login/components/atoms/button.ftl @@ -0,0 +1,33 @@ +<#macro kw color="" component="button" size="" rest...> + <#switch color> + <#case "primary"> + <#assign colorClass="bg-primary-600 text-white focus:ring-primary-600 hover:bg-primary-700"> + <#break> + <#case "secondary"> + <#assign colorClass="bg-secondary-100 text-secondary-600 focus:ring-secondary-600 hover:bg-secondary-200 hover:text-secondary-900"> + <#break> + <#default> + <#assign colorClass="bg-primary-600 text-white focus:ring-primary-600 hover:bg-primary-700"> + + + <#switch size> + <#case "medium"> + <#assign sizeClass="px-4 py-2 text-sm"> + <#break> + <#case "small"> + <#assign sizeClass="px-2 py-1 text-xs"> + <#break> + <#default> + <#assign sizeClass="px-4 py-2 text-sm"> + + + <${component} + class="${colorClass} ${sizeClass} flex justify-center relative rounded-lg w-full focus:outline-none focus:ring-2 focus:ring-offset-2" + + <#list rest as attrName, attrValue> + ${attrName}="${attrValue}" + + > + <#nested> + + diff --git a/conf/keycloak_theme/keywind/login/components/atoms/card.ftl b/conf/keycloak_theme/keywind/login/components/atoms/card.ftl new file mode 100644 index 0000000000..c1e808df18 --- /dev/null +++ b/conf/keycloak_theme/keywind/login/components/atoms/card.ftl @@ -0,0 +1,19 @@ +<#macro kw content="" footer="" header=""> +
+ <#if header?has_content> +
+ ${header} +
+ + <#if content?has_content> +
+ ${content} +
+ + <#if footer?has_content> +
+ ${footer} +
+ +
+ diff --git a/conf/keycloak_theme/keywind/login/components/atoms/checkbox.ftl b/conf/keycloak_theme/keywind/login/components/atoms/checkbox.ftl new file mode 100644 index 0000000000..e47fd619ac --- /dev/null +++ b/conf/keycloak_theme/keywind/login/components/atoms/checkbox.ftl @@ -0,0 +1,19 @@ +<#macro kw checked=false label="" name="" rest...> +
+ checked + + class="border-secondary-200 h-4 rounded text-primary-600 w-4 focus:ring-primary-200 focus:ring-opacity-50" + id="${name}" + name="${name}" + type="checkbox" + + <#list rest as attrName, attrValue> + ${attrName}="${attrValue}" + + > + +
+ diff --git a/conf/keycloak_theme/keywind/login/components/atoms/container.ftl b/conf/keycloak_theme/keywind/login/components/atoms/container.ftl new file mode 100644 index 0000000000..34ead183c2 --- /dev/null +++ b/conf/keycloak_theme/keywind/login/components/atoms/container.ftl @@ -0,0 +1,5 @@ +<#macro kw> +
+ <#nested> +
+ diff --git a/conf/keycloak_theme/keywind/login/components/atoms/form.ftl b/conf/keycloak_theme/keywind/login/components/atoms/form.ftl new file mode 100644 index 0000000000..014bb4f1cc --- /dev/null +++ b/conf/keycloak_theme/keywind/login/components/atoms/form.ftl @@ -0,0 +1,11 @@ +<#macro kw rest...> +
+ ${attrName}="${attrValue}" + + > + <#nested> +
+ diff --git a/conf/keycloak_theme/keywind/login/components/atoms/heading.ftl b/conf/keycloak_theme/keywind/login/components/atoms/heading.ftl new file mode 100644 index 0000000000..7665c01965 --- /dev/null +++ b/conf/keycloak_theme/keywind/login/components/atoms/heading.ftl @@ -0,0 +1,5 @@ +<#macro kw> +

+ <#nested> +

+ diff --git a/conf/keycloak_theme/keywind/login/components/atoms/input.ftl b/conf/keycloak_theme/keywind/login/components/atoms/input.ftl new file mode 100644 index 0000000000..01e2897ad9 --- /dev/null +++ b/conf/keycloak_theme/keywind/login/components/atoms/input.ftl @@ -0,0 +1,37 @@ +<#macro + kw + autofocus=false + disabled=false + invalid=false + label="" + message="" + name="" + required=true + rest... +> +
+ + autofocus + <#if disabled>disabled + <#if required>required + + aria-invalid="${invalid?c}" + class="block border-secondary-200 mt-1 rounded-md w-full focus:border-primary-300 focus:ring focus:ring-primary-200 focus:ring-opacity-50 sm:text-sm" + id="${name}" + name="${name}" + placeholder="${label}" + + <#list rest as attrName, attrValue> + ${attrName}="${attrValue}" + + > + <#if invalid?? && message??> +
+ ${message?no_esc} +
+ +
+ diff --git a/conf/keycloak_theme/keywind/login/components/atoms/link.ftl b/conf/keycloak_theme/keywind/login/components/atoms/link.ftl new file mode 100644 index 0000000000..bde766653f --- /dev/null +++ b/conf/keycloak_theme/keywind/login/components/atoms/link.ftl @@ -0,0 +1,30 @@ +<#macro kw color="" component="a" size="" rest...> + <#switch color> + <#case "primary"> + <#assign colorClass="text-primary-600 hover:text-primary-500"> + <#break> + <#case "secondary"> + <#assign colorClass="text-secondary-600 hover:text-secondary-900"> + <#break> + <#default> + <#assign colorClass="text-primary-600 hover:text-primary-500"> + + + <#switch size> + <#case "small"> + <#assign sizeClass="text-sm"> + <#break> + <#default> + <#assign sizeClass=""> + + + <${component} + class="<#compress>${colorClass} ${sizeClass} inline-flex" + + <#list rest as attrName, attrValue> + ${attrName}="${attrValue}" + + > + <#nested> + + diff --git a/conf/keycloak_theme/keywind/login/components/atoms/logo.ftl b/conf/keycloak_theme/keywind/login/components/atoms/logo.ftl new file mode 100644 index 0000000000..f166403e68 --- /dev/null +++ b/conf/keycloak_theme/keywind/login/components/atoms/logo.ftl @@ -0,0 +1,5 @@ +<#macro kw> +
+ <#nested> +
+ diff --git a/conf/keycloak_theme/keywind/login/components/atoms/nav.ftl b/conf/keycloak_theme/keywind/login/components/atoms/nav.ftl new file mode 100644 index 0000000000..81a4abf635 --- /dev/null +++ b/conf/keycloak_theme/keywind/login/components/atoms/nav.ftl @@ -0,0 +1,5 @@ +<#macro kw> +
+ <#nested> +
+ diff --git a/conf/keycloak_theme/keywind/login/components/atoms/radio.ftl b/conf/keycloak_theme/keywind/login/components/atoms/radio.ftl new file mode 100644 index 0000000000..5596d5c4b5 --- /dev/null +++ b/conf/keycloak_theme/keywind/login/components/atoms/radio.ftl @@ -0,0 +1,18 @@ +<#macro kw checked=false id="" label="" rest...> +
+ checked + + class="border-secondary-200 focus:ring-primary-600" + id="${id}" + type="radio" + + <#list rest as attrName, attrValue> + ${attrName}="${attrValue}" + + > + +
+ diff --git a/conf/keycloak_theme/keywind/login/components/molecules/identity-provider.ftl b/conf/keycloak_theme/keywind/login/components/molecules/identity-provider.ftl new file mode 100644 index 0000000000..50c9c81c77 --- /dev/null +++ b/conf/keycloak_theme/keywind/login/components/molecules/identity-provider.ftl @@ -0,0 +1,78 @@ +<#import "/assets/providers/providers.ftl" as providerIcons> + +<#macro kw providers=[]> +
+ ${msg("identity-provider-login-label")} +
+
+ <#list providers as provider> + <#switch provider.alias> + <#case "bitbucket"> + <#assign colorClass="hover:bg-provider-bitbucket/10"> + <#break> + <#case "discord"> + <#assign colorClass="hover:bg-provider-discord/10"> + <#break> + <#case "facebook"> + <#assign colorClass="hover:bg-provider-facebook/10"> + <#break> + <#case "github"> + <#assign colorClass="hover:bg-provider-github/10"> + <#break> + <#case "gitlab"> + <#assign colorClass="hover:bg-provider-gitlab/10"> + <#break> + <#case "google"> + <#assign colorClass="hover:bg-provider-google/10"> + <#break> + <#case "instagram"> + <#assign colorClass="hover:bg-provider-instagram/10"> + <#break> + <#case "linkedin"> + <#assign colorClass="hover:bg-provider-linkedin/10"> + <#break> + <#case "microsoft"> + <#assign colorClass="hover:bg-provider-microsoft/10"> + <#break> + <#case "oidc"> + <#assign colorClass="hover:bg-provider-oidc/10"> + <#break> + <#case "openshift-v3"> + <#assign colorClass="hover:bg-provider-openshift/10"> + <#break> + <#case "openshift-v4"> + <#assign colorClass="hover:bg-provider-openshift/10"> + <#break> + <#case "paypal"> + <#assign colorClass="hover:bg-provider-paypal/10"> + <#break> + <#case "slack"> + <#assign colorClass="hover:bg-provider-slack/10"> + <#break> + <#case "stackoverflow"> + <#assign colorClass="hover:bg-provider-stackoverflow/10"> + <#break> + <#case "twitter"> + <#assign colorClass="hover:bg-provider-twitter/10"> + <#break> + <#default> + <#assign colorClass="hover:bg-secondary-100"> + + + + <#if providerIcons[provider.alias]??> +
+ <@providerIcons[provider.alias] /> +
+ <#else> + ${provider.displayName!} + +
+ +
+ diff --git a/conf/keycloak_theme/keywind/login/components/molecules/locale-provider.ftl b/conf/keycloak_theme/keywind/login/components/molecules/locale-provider.ftl new file mode 100644 index 0000000000..198e5be10d --- /dev/null +++ b/conf/keycloak_theme/keywind/login/components/molecules/locale-provider.ftl @@ -0,0 +1,29 @@ +<#import "/assets/icons/chevron-down.ftl" as icon> +<#import "/components/atoms/link.ftl" as link> + +<#macro kw currentLocale="" locales=[]> +
+ <@link.kw @click="open = true" color="secondary" component="button" type="button"> +
+ ${currentLocale} + <@icon.kw /> +
+ +
+ <#list locales as locale> + <#if currentLocale != locale.label> +
+ <@link.kw color="secondary" href=locale.url size="small"> + ${locale.label} + +
+ + +
+
+ diff --git a/conf/keycloak_theme/keywind/login/components/molecules/username.ftl b/conf/keycloak_theme/keywind/login/components/molecules/username.ftl new file mode 100644 index 0000000000..ba6339389c --- /dev/null +++ b/conf/keycloak_theme/keywind/login/components/molecules/username.ftl @@ -0,0 +1,15 @@ +<#import "/assets/icons/arrow-top-right-on-square.ftl" as icon> +<#import "/components/atoms/link.ftl" as link> + +<#macro kw linkHref="" linkTitle="" name=""> +
+ ${name} + <@link.kw + color="primary" + href=linkHref + title=linkTitle + > + <@icon.kw /> + +
+ diff --git a/conf/keycloak_theme/keywind/login/document.ftl b/conf/keycloak_theme/keywind/login/document.ftl new file mode 100644 index 0000000000..188e16a31d --- /dev/null +++ b/conf/keycloak_theme/keywind/login/document.ftl @@ -0,0 +1,35 @@ +<#macro kw script=""> + ${msg("loginTitle", (realm.displayName!""))} + + + + + + <#if properties.meta?has_content> + <#list properties.meta?split(" ") as meta> + + + + + <#if properties.favicons?has_content> + <#list properties.favicons?split(" ") as favicon> + + + + + <#if properties.styles?has_content> + <#list properties.styles?split(" ") as style> + + + + + <#if script?has_content> + + + + <#if properties.scripts?has_content> + <#list properties.scripts?split(" ") as script> + + + + diff --git a/conf/keycloak_theme/keywind/login/error.ftl b/conf/keycloak_theme/keywind/login/error.ftl new file mode 100644 index 0000000000..52af9c1e64 --- /dev/null +++ b/conf/keycloak_theme/keywind/login/error.ftl @@ -0,0 +1,18 @@ +<#import "template.ftl" as layout> +<#import "components/atoms/alert.ftl" as alert> +<#import "components/atoms/link.ftl" as link> + +<@layout.registrationLayout displayMessage=false; section> + <#if section="header"> + ${kcSanitize(msg("errorTitle"))?no_esc} + <#elseif section="form"> + <@alert.kw color="error">${kcSanitize(message.summary)?no_esc} + <#if !skipLink??> + <#if client?? && client.baseUrl?has_content> + <@link.kw color="secondary" href=client.baseUrl size="small"> + ${kcSanitize(msg("backToApplication"))?no_esc} + + + + + diff --git a/conf/keycloak_theme/keywind/login/features/labels/totp-device.ftl b/conf/keycloak_theme/keywind/login/features/labels/totp-device.ftl new file mode 100644 index 0000000000..98ae12f8d2 --- /dev/null +++ b/conf/keycloak_theme/keywind/login/features/labels/totp-device.ftl @@ -0,0 +1,5 @@ +<#macro kw> + <#compress> + ${msg("loginTotpDeviceName")} <#if totp.otpCredentials?size gte 1>* + + diff --git a/conf/keycloak_theme/keywind/login/features/labels/totp.ftl b/conf/keycloak_theme/keywind/login/features/labels/totp.ftl new file mode 100644 index 0000000000..be5158ebe1 --- /dev/null +++ b/conf/keycloak_theme/keywind/login/features/labels/totp.ftl @@ -0,0 +1,5 @@ +<#macro kw> + <#compress> + ${msg("authenticatorCode")} * + + diff --git a/conf/keycloak_theme/keywind/login/features/labels/username.ftl b/conf/keycloak_theme/keywind/login/features/labels/username.ftl new file mode 100644 index 0000000000..6c01d6b346 --- /dev/null +++ b/conf/keycloak_theme/keywind/login/features/labels/username.ftl @@ -0,0 +1,11 @@ +<#macro kw> + <#compress> + <#if !realm.loginWithEmailAllowed> + ${msg("username")} + <#elseif !realm.registrationEmailAsUsername> + ${msg("usernameOrEmail")} + <#else> + ${msg("email")} + + + diff --git a/conf/keycloak_theme/keywind/login/login-config-totp.ftl b/conf/keycloak_theme/keywind/login/login-config-totp.ftl new file mode 100644 index 0000000000..e0b64c6346 --- /dev/null +++ b/conf/keycloak_theme/keywind/login/login-config-totp.ftl @@ -0,0 +1,110 @@ +<#import "template.ftl" as layout> +<#import "components/atoms/button.ftl" as button> +<#import "components/atoms/button-group.ftl" as buttonGroup> +<#import "components/atoms/form.ftl" as form> +<#import "components/atoms/input.ftl" as input> +<#import "components/atoms/link.ftl" as link> +<#import "features/labels/totp.ftl" as totpLabel> +<#import "features/labels/totp-device.ftl" as totpDeviceLabel> + +<#assign totpLabel><@totpLabel.kw /> +<#assign totpDeviceLabel><@totpDeviceLabel.kw /> + +<@layout.registrationLayout + displayMessage=!messagesPerField.existsError("totp", "userLabel") + displayRequiredFields=false + ; + section +> + <#if section="header"> + ${msg("loginTotpTitle")} + <#elseif section="form"> +
    +
  1. +

    ${msg("loginTotpStep1")}

    + +
  2. + <#if mode?? && mode="manual"> +
  3. +

    ${msg("loginTotpManualStep2")}

    +

    ${totp.totpSecretEncoded}

    +
  4. +
  5. + <@link.kw color="primary" href=totp.qrUrl> + ${msg("loginTotpScanBarcode")} + +
  6. +
  7. +

    ${msg("loginTotpManualStep3")}

    + +
  8. + <#else> +
  9. +

    ${msg("loginTotpStep2")}

    + Figure: Barcode + <@link.kw color="primary" href=totp.manualUrl> + ${msg("loginTotpUnableToScan")} + +
  10. + +
  11. ${msg("loginTotpStep3")}
  12. +
  13. ${msg("loginTotpStep3DeviceName")}
  14. +
+ <@form.kw action=url.loginAction method="post"> + + <#if mode??> + + + <@input.kw + autocomplete="off" + autofocus=true + invalid=messagesPerField.existsError("totp") + label=totpLabel + message=kcSanitize(messagesPerField.get("totp")) + name="totp" + required=false + type="text" + /> + <@input.kw + autocomplete="off" + invalid=messagesPerField.existsError("userLabel") + label=totpDeviceLabel + message=kcSanitize(messagesPerField.get("userLabel")) + name="userLabel" + required=false + type="text" + /> + <@buttonGroup.kw> + <#if isAppInitiatedAction??> + <@button.kw color="primary" type="submit"> + ${msg("doSubmit")} + + <@button.kw color="secondary" name="cancel-aia" type="submit" value="true"> + ${msg("doCancel")} + + <#else> + <@button.kw color="primary" type="submit"> + ${msg("doSubmit")} + + + + + + diff --git a/conf/keycloak_theme/keywind/login/login-idp-link-confirm.ftl b/conf/keycloak_theme/keywind/login/login-idp-link-confirm.ftl new file mode 100644 index 0000000000..9a2554d51f --- /dev/null +++ b/conf/keycloak_theme/keywind/login/login-idp-link-confirm.ftl @@ -0,0 +1,18 @@ +<#import "template.ftl" as layout> +<#import "components/atoms/button.ftl" as button> +<#import "components/atoms/form.ftl" as form> + +<@layout.registrationLayout; section> + <#if section="header"> + ${msg("confirmLinkIdpTitle")} + <#elseif section="form"> + <@form.kw action=url.loginAction method="post"> + <@button.kw color="primary" name="submitAction" type="submit" value="updateProfile"> + ${msg("confirmLinkIdpReviewProfile")} + + <@button.kw color="primary" name="submitAction" type="submit" value="linkAccount"> + ${msg("confirmLinkIdpContinue", idpDisplayName)} + + + + diff --git a/conf/keycloak_theme/keywind/login/login-oauth-grant.ftl b/conf/keycloak_theme/keywind/login/login-oauth-grant.ftl new file mode 100644 index 0000000000..aa4173cbf8 --- /dev/null +++ b/conf/keycloak_theme/keywind/login/login-oauth-grant.ftl @@ -0,0 +1,62 @@ +<#import "template.ftl" as layout> +<#import "components/atoms/button.ftl" as button> +<#import "components/atoms/button-group.ftl" as buttonGroup> +<#import "components/atoms/form.ftl" as form> + +<@layout.registrationLayout; section> + <#if section="header"> + <#if client.attributes.logoUri??> + + +

+ <#if client.name?has_content> + ${msg("oauthGrantTitle", advancedMsg(client.name))} + <#else> + ${msg("oauthGrantTitle", client.clientId)} + +

+ <#elseif section="form"> +

${msg("oauthGrantRequest")}

+ + <#if client.attributes.policyUri?? || client.attributes.tosUri??> +

+ <#if client.name?has_content> + ${msg("oauthGrantInformation",advancedMsg(client.name))} + <#else> + ${msg("oauthGrantInformation",client.clientId)} + + <#if client.attributes.tosUri??> + ${msg("oauthGrantReview")} + ${msg("oauthGrantTos")} + + <#if client.attributes.policyUri??> + ${msg("oauthGrantReview")} + ${msg("oauthGrantPolicy")} + +

+ + <@form.kw action=url.oauthAction method="post"> + + <@buttonGroup.kw> + <@button.kw color="primary" name="accept" type="submit"> + ${msg("doYes")} + + <@button.kw color="secondary" name="cancel" type="submit"> + ${msg("doNo")} + + + + + diff --git a/conf/keycloak_theme/keywind/login/login-otp.ftl b/conf/keycloak_theme/keywind/login/login-otp.ftl new file mode 100644 index 0000000000..b1bb3b975a --- /dev/null +++ b/conf/keycloak_theme/keywind/login/login-otp.ftl @@ -0,0 +1,50 @@ +<#import "template.ftl" as layout> +<#import "components/atoms/button.ftl" as button> +<#import "components/atoms/button-group.ftl" as buttonGroup> +<#import "components/atoms/form.ftl" as form> +<#import "components/atoms/input.ftl" as input> +<#import "components/atoms/radio.ftl" as radio> +<#import "features/labels/totp.ftl" as totpLabel> + +<#assign totpLabel><@totpLabel.kw /> + +<@layout.registrationLayout + displayMessage=!messagesPerField.existsError("totp") + ; + section +> + <#if section="header"> + ${msg("doLogIn")} + <#elseif section="form"> + <@form.kw action=url.loginAction method="post"> + <#if otpLogin.userOtpCredentials?size gt 1> +
+ <#list otpLogin.userOtpCredentials as otpCredential> + <@radio.kw + checked=(otpCredential.id == otpLogin.selectedCredentialId) + id="kw-otp-credential-${otpCredential?index}" + label=otpCredential.userLabel + name="selectedCredentialId" + tabindex=otpCredential?index + value=otpCredential.id + /> + +
+ + <@input.kw + autocomplete="off" + autofocus=true + invalid=messagesPerField.existsError("totp") + label=totpLabel + message=kcSanitize(messagesPerField.get("totp")) + name="otp" + type="text" + /> + <@buttonGroup.kw> + <@button.kw color="primary" name="submitAction" type="submit"> + ${msg("doLogIn")} + + + + + diff --git a/conf/keycloak_theme/keywind/login/login-page-expired.ftl b/conf/keycloak_theme/keywind/login/login-page-expired.ftl new file mode 100644 index 0000000000..2b6288d946 --- /dev/null +++ b/conf/keycloak_theme/keywind/login/login-page-expired.ftl @@ -0,0 +1,18 @@ +<#import "template.ftl" as layout> +<#import "components/atoms/button.ftl" as button> +<#import "components/atoms/button-group.ftl" as buttonGroup> + +<@layout.registrationLayout; section> + <#if section="header"> + ${msg("pageExpiredTitle")} + <#elseif section="form"> + <@buttonGroup.kw> + <@button.kw color="primary" component="a" href=url.loginRestartFlowUrl> + ${msg("doTryAgain")} + + <@button.kw color="secondary" component="a" href=url.loginAction> + ${msg("doContinue")} + + + + diff --git a/conf/keycloak_theme/keywind/login/login-password.ftl b/conf/keycloak_theme/keywind/login/login-password.ftl new file mode 100644 index 0000000000..54e7d9dca4 --- /dev/null +++ b/conf/keycloak_theme/keywind/login/login-password.ftl @@ -0,0 +1,39 @@ +<#import "template.ftl" as layout> +<#import "components/atoms/button.ftl" as button> +<#import "components/atoms/button-group.ftl" as buttonGroup> +<#import "components/atoms/form.ftl" as form> +<#import "components/atoms/input.ftl" as input> +<#import "components/atoms/link.ftl" as link> + +<@layout.registrationLayout displayMessage=!messagesPerField.existsError("password"); section> + <#if section="header"> + ${msg("doLogIn")} + <#elseif section="form"> + <@form.kw + action=url.loginAction + method="post" + onsubmit="login.disabled = true; return true;" + > + <@input.kw + autofocus=true + invalid=messagesPerField.existsError("password") + label=msg("password") + message=kcSanitize(messagesPerField.get("password"))?no_esc + name="password" + type="password" + /> + <#if realm.resetPasswordAllowed> +
+ <@link.kw color="primary" href=url.loginResetCredentialsUrl size="small"> + ${msg("doForgotPassword")} + +
+ + <@buttonGroup.kw> + <@button.kw color="primary" name="login" type="submit"> + ${msg("doLogIn")} + + + + + diff --git a/conf/keycloak_theme/keywind/login/login-recovery-authn-code-config.ftl b/conf/keycloak_theme/keywind/login/login-recovery-authn-code-config.ftl new file mode 100644 index 0000000000..186d710802 --- /dev/null +++ b/conf/keycloak_theme/keywind/login/login-recovery-authn-code-config.ftl @@ -0,0 +1,91 @@ +<#import "template.ftl" as layout> +<#import "components/atoms/alert.ftl" as alert> +<#import "components/atoms/button.ftl" as button> +<#import "components/atoms/button-group.ftl" as buttonGroup> +<#import "components/atoms/checkbox.ftl" as checkbox> +<#import "components/atoms/form.ftl" as form> + +<@layout.registrationLayout script="dist/recoveryCodes.js"; section> + <#if section="header"> + ${msg("recovery-code-config-header")} + <#elseif section="form"> +
+ <@alert.kw color="warning"> +
+

${msg("recovery-code-config-warning-title")}

+

${msg("recovery-code-config-warning-message")}

+
+ + +
+ <@button.kw @click="print" color="secondary" size="small" type="button"> + ${msg("recovery-codes-print")} + + <@button.kw @click="download" color="secondary" size="small" type="button"> + ${msg("recovery-codes-download")} + + <@button.kw @click="copy" color="secondary" size="small" type="button"> + ${msg("recovery-codes-copy")} + +
+ <@form.kw action=url.loginAction method="post"> + + + + <@checkbox.kw + label=msg("recovery-codes-confirmation-message") + name="kcRecoveryCodesConfirmationCheck" + required="required" + x\-ref="confirmationCheck" + /> + <@buttonGroup.kw> + <#if isAppInitiatedAction??> + <@button.kw color="primary" type="submit"> + ${msg("recovery-codes-action-complete")} + + <@button.kw + @click="$refs.confirmationCheck.required = false" + color="secondary" + name="cancel-aia" + type="submit" + value="true" + > + ${msg("recovery-codes-action-cancel")} + + <#else> + <@button.kw color="primary" type="submit"> + ${msg("recovery-codes-action-complete")} + + + + +
+ + + + diff --git a/conf/keycloak_theme/keywind/login/login-recovery-authn-code-input.ftl b/conf/keycloak_theme/keywind/login/login-recovery-authn-code-input.ftl new file mode 100644 index 0000000000..a46bcfa09e --- /dev/null +++ b/conf/keycloak_theme/keywind/login/login-recovery-authn-code-input.ftl @@ -0,0 +1,26 @@ +<#import "template.ftl" as layout> +<#import "components/atoms/button.ftl" as button> +<#import "components/atoms/button-group.ftl" as buttonGroup> +<#import "components/atoms/form.ftl" as form> +<#import "components/atoms/input.ftl" as input> + +<@layout.registrationLayout; section> + <#if section="header"> + ${msg("auth-recovery-code-header")} + <#elseif section="form"> + <@form.kw action=url.loginAction method="post"> + <@input.kw + autocomplete="off" + autofocus=true + label=msg("auth-recovery-code-prompt", recoveryAuthnCodesInputBean.codeNumber?c) + name="recoveryCodeInput" + type="text" + /> + <@buttonGroup.kw> + <@button.kw color="primary" name="login" type="submit"> + ${msg("doLogIn")} + + + + + diff --git a/conf/keycloak_theme/keywind/login/login-reset-password.ftl b/conf/keycloak_theme/keywind/login/login-reset-password.ftl new file mode 100644 index 0000000000..b0516aae82 --- /dev/null +++ b/conf/keycloak_theme/keywind/login/login-reset-password.ftl @@ -0,0 +1,44 @@ +<#import "template.ftl" as layout> +<#import "components/atoms/button.ftl" as button> +<#import "components/atoms/button-group.ftl" as buttonGroup> +<#import "components/atoms/form.ftl" as form> +<#import "components/atoms/input.ftl" as input> +<#import "components/atoms/link.ftl" as link> +<#import "features/labels/username.ftl" as usernameLabel> + +<#assign usernameLabel><@usernameLabel.kw /> + +<@layout.registrationLayout + displayInfo=true + displayMessage=!messagesPerField.existsError("username") + ; + section +> + <#if section="header"> + ${msg("emailForgotTitle")} + <#elseif section="form"> + <@form.kw action=url.loginAction method="post"> + <@input.kw + autocomplete=realm.loginWithEmailAllowed?string("email", "username") + autofocus=true + invalid=messagesPerField.existsError("username") + label=usernameLabel + message=kcSanitize(messagesPerField.get("username")) + name="username" + type="text" + value=(auth?has_content && auth.showUsername())?then(auth.attemptedUsername, '') + /> + <@buttonGroup.kw> + <@button.kw color="primary" type="submit"> + ${msg("doSubmit")} + + + + <#elseif section="info"> + ${msg("emailInstruction")} + <#elseif section="nav"> + <@link.kw color="secondary" href=url.loginUrl size="small"> + ${kcSanitize(msg("backToLogin"))?no_esc} + + + diff --git a/conf/keycloak_theme/keywind/login/login-update-password.ftl b/conf/keycloak_theme/keywind/login/login-update-password.ftl new file mode 100644 index 0000000000..ed82380e2f --- /dev/null +++ b/conf/keycloak_theme/keywind/login/login-update-password.ftl @@ -0,0 +1,64 @@ +<#import "template.ftl" as layout> +<#import "components/atoms/button.ftl" as button> +<#import "components/atoms/button-group.ftl" as buttonGroup> +<#import "components/atoms/checkbox.ftl" as checkbox> +<#import "components/atoms/form.ftl" as form> +<#import "components/atoms/input.ftl" as input> + +<@layout.registrationLayout + displayMessage=!messagesPerField.existsError("password", "password-confirm") + ; + section +> + <#if section="header"> + ${msg("updatePasswordTitle")} + <#elseif section="form"> + <@form.kw action=url.loginAction method="post"> + + + <@input.kw + autocomplete="new-password" + autofocus=true + invalid=messagesPerField.existsError("password", "password-confirm") + label=msg("passwordNew") + name="password-new" + type="password" + /> + <@input.kw + autocomplete="new-password" + invalid=messagesPerField.existsError("password-confirm") + label=msg("passwordConfirm") + message=kcSanitize(messagesPerField.get("password-confirm")) + name="password-confirm" + type="password" + /> + <#if isAppInitiatedAction??> + <@checkbox.kw + checked=true + label=msg("logoutOtherSessions") + name="logout-sessions" + value="on" + /> + + <@buttonGroup.kw> + <#if isAppInitiatedAction??> + <@button.kw color="primary" type="submit"> + ${msg("doSubmit")} + + <@button.kw color="secondary" name="cancel-aia" type="submit" value="true"> + ${msg("doCancel")} + + <#else> + <@button.kw color="primary" type="submit"> + ${msg("doSubmit")} + + + + + + diff --git a/conf/keycloak_theme/keywind/login/login-update-profile.ftl b/conf/keycloak_theme/keywind/login/login-update-profile.ftl new file mode 100644 index 0000000000..306bad944a --- /dev/null +++ b/conf/keycloak_theme/keywind/login/login-update-profile.ftl @@ -0,0 +1,71 @@ +<#import "template.ftl" as layout> +<#import "components/atoms/button.ftl" as button> +<#import "components/atoms/button-group.ftl" as buttonGroup> +<#import "components/atoms/form.ftl" as form> +<#import "components/atoms/input.ftl" as input> + +<@layout.registrationLayout + displayMessage=!messagesPerField.existsError("email", "firstName", "lastName", "username") + ; + section +> + <#if section="header"> + ${msg("loginProfileTitle")} + <#elseif section="form"> + <@form.kw action=url.loginAction method="post"> + <#if user.editUsernameAllowed> + <@input.kw + autocomplete="username" + autofocus=true + invalid=messagesPerField.existsError("username") + label=msg("username") + message=kcSanitize(messagesPerField.get("username")) + name="username" + type="text" + value=(user.username)!'' + /> + + <@input.kw + autocomplete="email" + invalid=messagesPerField.existsError("email") + label=msg("email") + message=kcSanitize(messagesPerField.get("email")) + name="email" + type="email" + value=(user.email)!'' + /> + <@input.kw + autocomplete="given-name" + invalid=messagesPerField.existsError("firstName") + label=msg("firstName") + message=kcSanitize(messagesPerField.get("firstName")) + name="firstName" + type="text" + value=(user.firstName)!'' + /> + <@input.kw + autocomplete="family-name" + invalid=messagesPerField.existsError("lastName") + label=msg("lastName") + message=kcSanitize(messagesPerField.get("lastName")) + name="lastName" + type="text" + value=(user.lastName)!'' + /> + <@buttonGroup.kw> + <#if isAppInitiatedAction??> + <@button.kw color="primary" type="submit"> + ${msg("doSubmit")} + + <@button.kw color="secondary" name="cancel-aia" type="submit" value="true"> + ${msg("doCancel")} + + <#else> + <@button.kw color="primary" type="submit"> + ${msg("doSubmit")} + + + + + + diff --git a/conf/keycloak_theme/keywind/login/login-username.ftl b/conf/keycloak_theme/keywind/login/login-username.ftl new file mode 100644 index 0000000000..b8064b2da0 --- /dev/null +++ b/conf/keycloak_theme/keywind/login/login-username.ftl @@ -0,0 +1,71 @@ +<#import "template.ftl" as layout> +<#import "components/atoms/button.ftl" as button> +<#import "components/atoms/button-group.ftl" as buttonGroup> +<#import "components/atoms/checkbox.ftl" as checkbox> +<#import "components/atoms/form.ftl" as form> +<#import "components/atoms/input.ftl" as input> +<#import "components/atoms/link.ftl" as link> +<#import "components/molecules/identity-provider.ftl" as identityProvider> +<#import "features/labels/username.ftl" as usernameLabel> + +<#assign usernameLabel><@usernameLabel.kw /> + +<@layout.registrationLayout + displayInfo=realm.password && realm.registrationAllowed && !registrationDisabled?? + displayMessage=!messagesPerField.existsError("username") + ; + section +> + <#if section="header"> + ${msg("loginAccountTitle")} + <#elseif section="form"> + <#if realm.password> + <@form.kw + action=url.loginAction + method="post" + onsubmit="login.disabled = true; return true;" + > + <#if !usernameHidden??> + <@input.kw + autocomplete=realm.loginWithEmailAllowed?string("email", "username") + autofocus=true + disabled=usernameEditDisabled?? + invalid=messagesPerField.existsError("username") + label=usernameLabel + message=kcSanitize(messagesPerField.get("username"))?no_esc + name="username" + type="text" + value=(login.username)!'' + /> + + <#if realm.rememberMe && !usernameHidden??> +
+ <@checkbox.kw + checked=login.rememberMe?? + label=msg("rememberMe") + name="rememberMe" + /> +
+ + <@buttonGroup.kw> + <@button.kw color="primary" name="login" type="submit"> + ${msg("doLogIn")} + + + + + <#elseif section="info"> + <#if realm.password && realm.registrationAllowed && !registrationDisabled??> +
+ ${msg("noAccount")} + <@link.kw color="primary" href=url.registrationUrl> + ${msg("doRegister")} + +
+ + <#elseif section="socialProviders"> + <#if realm.password && social.providers??> + <@identityProvider.kw providers=social.providers /> + + + diff --git a/conf/keycloak_theme/keywind/login/login.ftl b/conf/keycloak_theme/keywind/login/login.ftl new file mode 100644 index 0000000000..3084138178 --- /dev/null +++ b/conf/keycloak_theme/keywind/login/login.ftl @@ -0,0 +1,88 @@ +<#import "template.ftl" as layout> +<#import "components/atoms/button.ftl" as button> +<#import "components/atoms/button-group.ftl" as buttonGroup> +<#import "components/atoms/checkbox.ftl" as checkbox> +<#import "components/atoms/form.ftl" as form> +<#import "components/atoms/input.ftl" as input> +<#import "components/atoms/link.ftl" as link> +<#import "components/molecules/identity-provider.ftl" as identityProvider> +<#import "features/labels/username.ftl" as usernameLabel> + +<#assign usernameLabel><@usernameLabel.kw /> + +<@layout.registrationLayout + displayInfo=realm.password && realm.registrationAllowed && !registrationDisabled?? + displayMessage=!messagesPerField.existsError("username", "password") + ; + section +> + <#if section="header"> + ${msg("loginAccountTitle")} + <#elseif section="form"> + <#if realm.password> + <@form.kw + action=url.loginAction + method="post" + onsubmit="login.disabled = true; return true;" + > + + <@input.kw + autocomplete=realm.loginWithEmailAllowed?string("email", "username") + autofocus=true + disabled=usernameEditDisabled?? + invalid=messagesPerField.existsError("username", "password") + label=usernameLabel + message=kcSanitize(messagesPerField.getFirstError("username", "password")) + name="username" + type="text" + value=(login.username)!'username' + /> + <@input.kw + invalid=messagesPerField.existsError("username", "password") + label=msg("password") + name="password" + type="password" + value=(login.password)!'password' + /> + <#if realm.rememberMe && !usernameEditDisabled?? || realm.resetPasswordAllowed> +
+ <#if realm.rememberMe && !usernameEditDisabled??> + <@checkbox.kw + checked=login.rememberMe?? + label=msg("rememberMe") + name="rememberMe" + /> + + <#if realm.resetPasswordAllowed> + <@link.kw color="primary" href=url.loginResetCredentialsUrl size="small"> + ${msg("doForgotPassword")} + + +
+ + <@buttonGroup.kw> + <@button.kw color="primary" name="login" type="submit"> + ${msg("doLogIn")} + + + + + <#elseif section="info"> + <#if realm.password && realm.registrationAllowed && !registrationDisabled??> +
+ ${msg("noAccount")} + <@link.kw color="primary" href=url.registrationUrl> + ${msg("doRegister")} + +
+ + <#elseif section="socialProviders"> + <#if realm.password && social.providers??> + <@identityProvider.kw providers=social.providers /> + + + diff --git a/conf/keycloak_theme/keywind/login/logout-confirm.ftl b/conf/keycloak_theme/keywind/login/logout-confirm.ftl new file mode 100644 index 0000000000..e7ec486263 --- /dev/null +++ b/conf/keycloak_theme/keywind/login/logout-confirm.ftl @@ -0,0 +1,25 @@ +<#import "template.ftl" as layout> +<#import "components/atoms/button.ftl" as button> +<#import "components/atoms/form.ftl" as form> +<#import "components/atoms/link.ftl" as link> + +<@layout.registrationLayout; section> + <#if section="header"> + ${msg("logoutConfirmTitle")} + <#elseif section="form"> +

${msg("logoutConfirmHeader")}

+ <@form.kw action=url.logoutConfirmAction method="post"> + + <@button.kw color="primary" name="confirmLogout" type="submit" value=msg('doLogout')> + ${msg("doLogout")} + + + <#if !logoutConfirm.skipLink> + <#if (client.baseUrl)?has_content> + <@link.kw color="secondary" href=client.baseUrl size="small"> + ${kcSanitize(msg("backToApplication"))?no_esc} + + + + + diff --git a/conf/keycloak_theme/keywind/login/register.ftl b/conf/keycloak_theme/keywind/login/register.ftl new file mode 100644 index 0000000000..c1a2f061fb --- /dev/null +++ b/conf/keycloak_theme/keywind/login/register.ftl @@ -0,0 +1,88 @@ +<#import "template.ftl" as layout> +<#import "components/atoms/button.ftl" as button> +<#import "components/atoms/button-group.ftl" as buttonGroup> +<#import "components/atoms/form.ftl" as form> +<#import "components/atoms/input.ftl" as input> +<#import "components/atoms/link.ftl" as link> + +<@layout.registrationLayout + displayMessage=!messagesPerField.existsError("firstName", "lastName", "email", "username", "password", "password-confirm") + ; + section +> + <#if section="header"> + ${msg("registerTitle")} + <#elseif section="form"> + <@form.kw action=url.registrationAction method="post"> + <@input.kw + autocomplete="given-name" + autofocus=true + invalid=messagesPerField.existsError("firstName") + label=msg("firstName") + message=kcSanitize(messagesPerField.get("firstName")) + name="firstName" + type="text" + value=(register.formData.firstName)!'' + /> + <@input.kw + autocomplete="family-name" + invalid=messagesPerField.existsError("lastName") + label=msg("lastName") + message=kcSanitize(messagesPerField.get("lastName")) + name="lastName" + type="text" + value=(register.formData.lastName)!'' + /> + <@input.kw + autocomplete="email" + invalid=messagesPerField.existsError("email") + label=msg("email") + message=kcSanitize(messagesPerField.get("email")) + name="email" + type="email" + value=(register.formData.email)!'' + /> + <#if !realm.registrationEmailAsUsername> + <@input.kw + autocomplete="username" + invalid=messagesPerField.existsError("username") + label=msg("username") + message=kcSanitize(messagesPerField.get("username")) + name="username" + type="text" + value=(register.formData.username)!'' + /> + + <#if passwordRequired??> + <@input.kw + autocomplete="new-password" + invalid=messagesPerField.existsError("password", "password-confirm") + label=msg("password") + message=kcSanitize(messagesPerField.getFirstError("password", "password-confirm")) + name="password" + type="password" + /> + <@input.kw + autocomplete="new-password" + invalid=messagesPerField.existsError("password-confirm") + label=msg("passwordConfirm") + message=kcSanitize(messagesPerField.get("password-confirm")) + name="password-confirm" + type="password" + /> + + <#if recaptchaRequired??> +
+ + <@buttonGroup.kw> + <@button.kw color="primary" type="submit"> + ${msg("doRegister")} + + + + <#elseif section="nav"> + <@link.kw color="secondary" href=url.loginUrl size="small"> + ${kcSanitize(msg("backToLogin"))?no_esc} + + + diff --git a/conf/keycloak_theme/keywind/login/resources/dist/assets/index-a7b84447.js b/conf/keycloak_theme/keywind/login/resources/dist/assets/index-a7b84447.js new file mode 100644 index 0000000000..c1b2f3c662 --- /dev/null +++ b/conf/keycloak_theme/keywind/login/resources/dist/assets/index-a7b84447.js @@ -0,0 +1 @@ +var s={};Object.defineProperty(s,"__esModule",{value:!0});function v(e,r,a){var l;if(a===void 0&&(a={}),!r.codes){r.codes={};for(var n=0;n=8&&(t-=8,c[u++]=255&i>>t)}if(t>=r.bits||255&i<<8-t)throw new SyntaxError("Unexpected end of data");return c}function o(e,r,a){a===void 0&&(a={});for(var l=a,n=l.pad,b=n===void 0?!0:n,c=(1<r.bits;)i-=r.bits,t+=r.chars[c&u>>i];if(i&&(t+=r.chars[c&u<Te&&R.splice(t,1)}function $r(){!Me&&!Ce&&(Ce=!0,queueMicrotask(Rr))}function Rr(){Ce=!1,Me=!0;for(let e=0;ee.effect(t,{scheduler:r=>{Ie?Ir(r):r()}}),yt=e.raw}function ct(e){z=e}function Lr(e){let t=()=>{};return[n=>{let i=z(n);return e._x_effects||(e._x_effects=new Set,e._x_runEffects=()=>{e._x_effects.forEach(o=>o())}),e._x_effects.add(i),t=()=>{i!==void 0&&(e._x_effects.delete(i),Z(i))},i},()=>{t()}]}var xt=[],bt=[],mt=[];function Fr(e){mt.push(e)}function wt(e,t){typeof t=="function"?(e._x_cleanups||(e._x_cleanups=[]),e._x_cleanups.push(t)):(t=e,bt.push(t))}function Kr(e){xt.push(e)}function Dr(e,t,r){e._x_attributeCleanups||(e._x_attributeCleanups={}),e._x_attributeCleanups[t]||(e._x_attributeCleanups[t]=[]),e._x_attributeCleanups[t].push(r)}function Et(e,t){e._x_attributeCleanups&&Object.entries(e._x_attributeCleanups).forEach(([r,n])=>{(t===void 0||t.includes(r))&&(n.forEach(i=>i()),delete e._x_attributeCleanups[r])})}var qe=new MutationObserver(Je),We=!1;function Ve(){qe.observe(document,{subtree:!0,childList:!0,attributes:!0,attributeOldValue:!0}),We=!0}function St(){Br(),qe.disconnect(),We=!1}var U=[],Ee=!1;function Br(){U=U.concat(qe.takeRecords()),U.length&&!Ee&&(Ee=!0,queueMicrotask(()=>{kr(),Ee=!1}))}function kr(){Je(U),U.length=0}function x(e){if(!We)return e();St();let t=e();return Ve(),t}var Ue=!1,ae=[];function zr(){Ue=!0}function Hr(){Ue=!1,Je(ae),ae=[]}function Je(e){if(Ue){ae=ae.concat(e);return}let t=[],r=[],n=new Map,i=new Map;for(let o=0;os.nodeType===1&&t.push(s)),e[o].removedNodes.forEach(s=>s.nodeType===1&&r.push(s))),e[o].type==="attributes")){let s=e[o].target,a=e[o].attributeName,u=e[o].oldValue,c=()=>{n.has(s)||n.set(s,[]),n.get(s).push({name:a,value:s.getAttribute(a)})},l=()=>{i.has(s)||i.set(s,[]),i.get(s).push(a)};s.hasAttribute(a)&&u===null?c():s.hasAttribute(a)?(l(),c()):l()}i.forEach((o,s)=>{Et(s,o)}),n.forEach((o,s)=>{xt.forEach(a=>a(s,o))});for(let o of r)if(!t.includes(o)&&(bt.forEach(s=>s(o)),o._x_cleanups))for(;o._x_cleanups.length;)o._x_cleanups.pop()();t.forEach(o=>{o._x_ignoreSelf=!0,o._x_ignore=!0});for(let o of t)r.includes(o)||o.isConnected&&(delete o._x_ignoreSelf,delete o._x_ignore,mt.forEach(s=>s(o)),o._x_ignore=!0,o._x_ignoreSelf=!0);t.forEach(o=>{delete o._x_ignoreSelf,delete o._x_ignore}),t=null,r=null,n=null,i=null}function At(e){return ee(K(e))}function X(e,t,r){return e._x_dataStack=[t,...K(r||e)],()=>{e._x_dataStack=e._x_dataStack.filter(n=>n!==t)}}function lt(e,t){let r=e._x_dataStack[0];Object.entries(t).forEach(([n,i])=>{r[n]=i})}function K(e){return e._x_dataStack?e._x_dataStack:typeof ShadowRoot=="function"&&e instanceof ShadowRoot?K(e.host):e.parentNode?K(e.parentNode):[]}function ee(e){let t=new Proxy({},{ownKeys:()=>Array.from(new Set(e.flatMap(r=>Object.keys(r)))),has:(r,n)=>e.some(i=>i.hasOwnProperty(n)),get:(r,n)=>(e.find(i=>{if(i.hasOwnProperty(n)){let o=Object.getOwnPropertyDescriptor(i,n);if(o.get&&o.get._x_alreadyBound||o.set&&o.set._x_alreadyBound)return!0;if((o.get||o.set)&&o.enumerable){let s=o.get,a=o.set,u=o;s=s&&s.bind(t),a=a&&a.bind(t),s&&(s._x_alreadyBound=!0),a&&(a._x_alreadyBound=!0),Object.defineProperty(i,n,{...u,get:s,set:a})}return!0}return!1})||{})[n],set:(r,n,i)=>{let o=e.find(s=>s.hasOwnProperty(n));return o?o[n]=i:e[e.length-1][n]=i,!0}});return t}function Ot(e){let t=n=>typeof n=="object"&&!Array.isArray(n)&&n!==null,r=(n,i="")=>{Object.entries(Object.getOwnPropertyDescriptors(n)).forEach(([o,{value:s,enumerable:a}])=>{if(a===!1||s===void 0)return;let u=i===""?o:`${i}.${o}`;typeof s=="object"&&s!==null&&s._x_interceptor?n[o]=s.initialize(e,u,o):t(s)&&s!==n&&!(s instanceof Element)&&r(s,u)})};return r(e)}function Ct(e,t=()=>{}){let r={initialValue:void 0,_x_interceptor:!0,initialize(n,i,o){return e(this.initialValue,()=>qr(n,i),s=>Pe(n,i,s),i,o)}};return t(r),n=>{if(typeof n=="object"&&n!==null&&n._x_interceptor){let i=r.initialize.bind(r);r.initialize=(o,s,a)=>{let u=n.initialize(o,s,a);return r.initialValue=u,i(o,s,a)}}else r.initialValue=n;return r}}function qr(e,t){return t.split(".").reduce((r,n)=>r[n],e)}function Pe(e,t,r){if(typeof t=="string"&&(t=t.split(".")),t.length===1)e[t[0]]=r;else{if(t.length===0)throw error;return e[t[0]]||(e[t[0]]={}),Pe(e[t[0]],t.slice(1),r)}}var Mt={};function S(e,t){Mt[e]=t}function $e(e,t){return Object.entries(Mt).forEach(([r,n])=>{Object.defineProperty(e,`$${r}`,{get(){let[i,o]=Rt(t);return i={interceptor:Ct,...i},wt(t,o),n(t,i)},enumerable:!1})}),e}function Wr(e,t,r,...n){try{return r(...n)}catch(i){Y(i,e,t)}}function Y(e,t,r=void 0){Object.assign(e,{el:t,expression:r}),console.warn(`Alpine Expression Error: ${e.message} + +${r?'Expression: "'+r+`" + +`:""}`,t),setTimeout(()=>{throw e},0)}var se=!0;function Vr(e){let t=se;se=!1,e(),se=t}function F(e,t,r={}){let n;return m(e,t)(i=>n=i,r),n}function m(...e){return Tt(...e)}var Tt=It;function Ur(e){Tt=e}function It(e,t){let r={};$e(r,e);let n=[r,...K(e)],i=typeof t=="function"?Jr(n,t):Yr(n,t,e);return Wr.bind(null,e,t,i)}function Jr(e,t){return(r=()=>{},{scope:n={},params:i=[]}={})=>{let o=t.apply(ee([n,...e]),i);ue(r,o)}}var Se={};function Gr(e,t){if(Se[e])return Se[e];let r=Object.getPrototypeOf(async function(){}).constructor,n=/^[\n\s]*if.*\(.*\)/.test(e)||/^(let|const)\s/.test(e)?`(async()=>{ ${e} })()`:e,o=(()=>{try{return new r(["__self","scope"],`with (scope) { __self.result = ${n} }; __self.finished = true; return __self.result;`)}catch(s){return Y(s,t,e),Promise.resolve()}})();return Se[e]=o,o}function Yr(e,t,r){let n=Gr(t,r);return(i=()=>{},{scope:o={},params:s=[]}={})=>{n.result=void 0,n.finished=!1;let a=ee([o,...e]);if(typeof n=="function"){let u=n(n,a).catch(c=>Y(c,r,t));n.finished?(ue(i,n.result,a,s,r),n.result=void 0):u.then(c=>{ue(i,c,a,s,r)}).catch(c=>Y(c,r,t)).finally(()=>n.result=void 0)}}}function ue(e,t,r,n,i){if(se&&typeof t=="function"){let o=t.apply(r,n);o instanceof Promise?o.then(s=>ue(e,s,r,n)).catch(s=>Y(s,i,t)):e(o)}else typeof t=="object"&&t instanceof Promise?t.then(o=>e(o)):e(t)}var Ge="x-";function H(e=""){return Ge+e}function Qr(e){Ge=e}var Re={};function g(e,t){return Re[e]=t,{before(r){if(!Re[r]){console.warn("Cannot find directive `${directive}`. `${name}` will use the default order of execution");return}const n=$.indexOf(r);$.splice(n>=0?n:$.indexOf("DEFAULT"),0,e)}}}function Ye(e,t,r){if(t=Array.from(t),e._x_virtualDirectives){let o=Object.entries(e._x_virtualDirectives).map(([a,u])=>({name:a,value:u})),s=Pt(o);o=o.map(a=>s.find(u=>u.name===a.name)?{name:`x-bind:${a.name}`,value:`"${a.value}"`}:a),t=t.concat(o)}let n={};return t.map(Lt((o,s)=>n[o]=s)).filter(Kt).map(en(n,r)).sort(tn).map(o=>Xr(e,o))}function Pt(e){return Array.from(e).map(Lt()).filter(t=>!Kt(t))}var je=!1,V=new Map,$t=Symbol();function Zr(e){je=!0;let t=Symbol();$t=t,V.set(t,[]);let r=()=>{for(;V.get(t).length;)V.get(t).shift()();V.delete(t)},n=()=>{je=!1,r()};e(r),n()}function Rt(e){let t=[],r=a=>t.push(a),[n,i]=Lr(e);return t.push(i),[{Alpine:re,effect:n,cleanup:r,evaluateLater:m.bind(m,e),evaluate:F.bind(F,e)},()=>t.forEach(a=>a())]}function Xr(e,t){let r=()=>{},n=Re[t.type]||r,[i,o]=Rt(e);Dr(e,t.original,o);let s=()=>{e._x_ignore||e._x_ignoreSelf||(n.inline&&n.inline(e,t,i),n=n.bind(n,e,t,i),je?V.get($t).push(n):n())};return s.runCleanups=o,s}var jt=(e,t)=>({name:r,value:n})=>(r.startsWith(e)&&(r=r.replace(e,t)),{name:r,value:n}),Nt=e=>e;function Lt(e=()=>{}){return({name:t,value:r})=>{let{name:n,value:i}=Ft.reduce((o,s)=>s(o),{name:t,value:r});return n!==t&&e(n,t),{name:n,value:i}}}var Ft=[];function Qe(e){Ft.push(e)}function Kt({name:e}){return Dt().test(e)}var Dt=()=>new RegExp(`^${Ge}([^:^.]+)\\b`);function en(e,t){return({name:r,value:n})=>{let i=r.match(Dt()),o=r.match(/:([a-zA-Z0-9\-:]+)/),s=r.match(/\.[^.\]]+(?=[^\]]*$)/g)||[],a=t||e[r]||r;return{type:i?i[1]:null,value:o?o[1]:null,modifiers:s.map(u=>u.replace(".","")),expression:n,original:a}}}var Ne="DEFAULT",$=["ignore","ref","data","id","bind","init","for","model","modelable","transition","show","if",Ne,"teleport"];function tn(e,t){let r=$.indexOf(e.type)===-1?Ne:e.type,n=$.indexOf(t.type)===-1?Ne:t.type;return $.indexOf(r)-$.indexOf(n)}function J(e,t,r={}){e.dispatchEvent(new CustomEvent(t,{detail:r,bubbles:!0,composed:!0,cancelable:!0}))}function M(e,t){if(typeof ShadowRoot=="function"&&e instanceof ShadowRoot){Array.from(e.children).forEach(i=>M(i,t));return}let r=!1;if(t(e,()=>r=!0),r)return;let n=e.firstElementChild;for(;n;)M(n,t),n=n.nextElementSibling}function D(e,...t){console.warn(`Alpine Warning: ${e}`,...t)}function rn(){document.body||D("Unable to initialize. Trying to load Alpine before `` is available. Did you forget to add `defer` in Alpine's ` diff --git a/conf/keycloak_theme/keywind/login/webauthn-error.ftl b/conf/keycloak_theme/keywind/login/webauthn-error.ftl new file mode 100644 index 0000000000..852d1e3d5f --- /dev/null +++ b/conf/keycloak_theme/keywind/login/webauthn-error.ftl @@ -0,0 +1,34 @@ +<#import "template.ftl" as layout> +<#import "components/atoms/button.ftl" as button> +<#import "components/atoms/button-group.ftl" as buttonGroup> + +<@layout.registrationLayout displayMessage=true; section> + <#if section="header"> + ${kcSanitize(msg("webauthn-error-title"))?no_esc} + <#elseif section="form"> +
+
+ + +
+ <@buttonGroup.kw> + <@button.kw + @click="$refs.executionValueInput.value = '${execution}'; $refs.isSetRetryInput.value = 'retry'; $refs.errorCredentialForm.submit()" + color="primary" + name="try-again" + tabindex="4" + type="button" + > + ${kcSanitize(msg("doTryAgain"))?no_esc} + + <#if isAppInitiatedAction??> +
+ <@button.kw color="secondary" name="cancel-aia" type="submit" value="true"> + ${msg("doCancel")} + +
+ + +
+ + diff --git a/conf/keycloak_theme/keywind/login/webauthn-register.ftl b/conf/keycloak_theme/keywind/login/webauthn-register.ftl new file mode 100644 index 0000000000..57f4dad87b --- /dev/null +++ b/conf/keycloak_theme/keywind/login/webauthn-register.ftl @@ -0,0 +1,54 @@ +<#import "template.ftl" as layout> +<#import "components/atoms/button.ftl" as button> +<#import "components/atoms/button-group.ftl" as buttonGroup> + +<@layout.registrationLayout script="dist/webAuthnRegister.js"; section> + <#if section="title"> + title + <#elseif section="header"> + ${kcSanitize(msg("webauthn-registration-title"))?no_esc} + <#elseif section="form"> +
+
+ + + + + + +
+ <@buttonGroup.kw> + <@button.kw @click="registerSecurityKey" color="primary" type="submit"> + ${msg("doRegister")} + + <#if !isSetRetry?has_content && isAppInitiatedAction?has_content> +
+ <@button.kw color="secondary" name="cancel-aia" type="submit" value="true"> + ${msg("doCancel")} + +
+ + +
+ + + + diff --git a/conf/sample.env_web-client b/conf/sample.env_web-client index edadbeb868..55ee5e9563 100644 --- a/conf/sample.env_web-client +++ b/conf/sample.env_web-client @@ -5,10 +5,10 @@ NEXT_PUBLIC_DOWNLOAD_APP_IOS=# NEXT_PUBLIC_DOWNLOAD_APP_ANDROID=# KEYCLOAK_CLIENT_ID=hasura KEYCLOAK_CLIENT_SECRET=oMtCPAV7diKpE564SBspgKj4HqlKM4Hy -AUTH_ISSUER=http://localhost:8088/realms/hasura +AUTH_ISSUER=http://localhost:8088/realms/$KEYCLOAK_CLIENT_ID NEXTAUTH_URL=http://localhost:3000 NEXTAUTH_SECRET=my-secret -END_SESSION_URL=http://localhost:8088/realms/hasura/protocol/openid-connect/logout -REFRESH_TOKEN_URL=http://localhost:8088/realms/hasura/protocol/openid-connect/token +END_SESSION_URL=http://localhost:8088/realms/$KEYCLOAK_CLIENT_ID/protocol/openid-connect/logout +REFRESH_TOKEN_URL=http://localhost:8088/realms/$KEYCLOAK_CLIENT_ID/protocol/openid-connect/token HASURA_ADMIN_TOKEN=myadminsecretkey NEXT_PUBLIC_GRAPHQL_ENGINE_URL=localhost:8080 diff --git a/docker-compose.yml b/docker-compose.yml index 621e26287a..14f571d67f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -17,6 +17,7 @@ services: KEYCLOAK_ADMIN_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD-admin} volumes: - ./conf/keycloak_conf:/opt/keycloak/data/import + - ./conf/keycloak_theme/keywind:/opt/keycloak/themes/keywind ports: - "8088:8088" depends_on: @@ -60,7 +61,7 @@ services: condition: service_started healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8080/healthz"] - interval: 5s + interval: 10s timeout: 10s retries: 30 networks: