Skip to content

Commit

Permalink
Style/keycloak theme (naturalsolutions#38)
Browse files Browse the repository at this point in the history
* style: wip: add keycloak theme

- styles & templates
- command to load them automatically when in dev
- volume to edit them easily

Co-authored-by: Naomi-Fischer

* style: rename folder to customtheme

To avoid too much personalization

* style(keycloak): theme in dockerfile & i18n realm

* fix(keycloak): add default locale

* ci: update path to mounted volume

* style: lower logo size

* style: add background image & more css
  • Loading branch information
mvergez authored Aug 31, 2023
1 parent f5b248c commit 30d842e
Show file tree
Hide file tree
Showing 9 changed files with 311 additions and 2 deletions.
2 changes: 2 additions & 0 deletions docker/docker-compose.override.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ services:
- "8888:8080"
volumes:
- ../keycloak/realm.json:/opt/keycloak/data/import/realm.json
- ../keycloak/theme/customtheme:/opt/keycloak/themes/customtheme
command: start-dev --spi-theme-static-max-age=-1 --spi-theme-cache-themes=false --spi-theme-cache-templates=false --import-realm

doc:
image: squidfunk/mkdocs-material:8.3.9
Expand Down
1 change: 1 addition & 0 deletions keycloak/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ FROM quay.io/keycloak/keycloak:21.1.0

COPY --from=ubi-micro-build /mnt/rootfs /
COPY ./realm.json /opt/keycloak/data/import/realm.json
COPY ./theme/customtheme /opt/keycloak/themes/customtheme


6 changes: 4 additions & 2 deletions keycloak/realm.json
Original file line number Diff line number Diff line change
Expand Up @@ -656,6 +656,7 @@
"frontchannelLogout" : true,
"protocol" : "openid-connect",
"attributes" : {
"login_theme" : "customtheme",
"tls-client-certificate-bound-access-tokens" : "false",
"oidc.ciba.grant.enabled" : "false",
"backchannel.logout.session.required" : "true",
Expand Down Expand Up @@ -1461,8 +1462,9 @@
}
} ]
},
"internationalizationEnabled" : false,
"supportedLocales" : [ ],
"internationalizationEnabled" : true,
"supportedLocales" : [ "en", "fr" ],
"defaultLocale" : "en",
"authenticationFlows" : [ {
"id" : "d3c990a3-d06a-44f9-bbc8-f065e9dce1a6",
"alias" : "Account verification options",
Expand Down
107 changes: 107 additions & 0 deletions keycloak/theme/customtheme/login/login.ftl
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
<#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">
<div id="kc-form">
<div id="kc-form-wrapper">
<#if realm.password>
<form id="kc-form-login" onsubmit="login.disabled = true; return true;" action="${url.loginAction}" method="post">
<#if !usernameHidden??>
<div class="${properties.kcFormGroupClass!}">
<label for="username" class="${properties.kcLabelClass!}"><#if !realm.loginWithEmailAllowed>${msg("username")}<#elseif !realm.registrationEmailAsUsername>${msg("usernameOrEmail")}<#else>${msg("email")}</#if></label>

<input tabindex="1" id="username" class="${properties.kcInputClass!}" name="username" value="${(login.username!'')}" type="text" autofocus autocomplete="off"
aria-invalid="<#if messagesPerField.existsError('username','password')>true</#if>"
/>

<#if messagesPerField.existsError('username','password')>
<span id="input-error" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
${kcSanitize(messagesPerField.getFirstError('username','password'))?no_esc}
</span>
</#if>

</div>
</#if>

<div class="${properties.kcFormGroupClass!}">
<label for="password" class="${properties.kcLabelClass!}">${msg("password")}</label>

<input tabindex="2" id="password" class="${properties.kcInputClass!}" name="password" type="password" autocomplete="off"
aria-invalid="<#if messagesPerField.existsError('username','password')>true</#if>"
/>

<#if usernameHidden?? && messagesPerField.existsError('username','password')>
<span id="input-error" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
${kcSanitize(messagesPerField.getFirstError('username','password'))?no_esc}
</span>
</#if>

</div>

<div class="${properties.kcFormGroupClass!} ${properties.kcFormSettingClass!}">
<div id="kc-form-options">
<#if realm.rememberMe && !usernameHidden??>
<div class="checkbox">
<label>
<#if login.rememberMe??>
<input tabindex="3" id="rememberMe" name="rememberMe" type="checkbox" checked> ${msg("rememberMe")}
<#else>
<input tabindex="3" id="rememberMe" name="rememberMe" type="checkbox"> ${msg("rememberMe")}
</#if>
</label>
</div>
</#if>
</div>
<div class="${properties.kcFormOptionsWrapperClass!}">
<#if realm.resetPasswordAllowed>
<span><a tabindex="5" href="${url.loginResetCredentialsUrl}">${msg("doForgotPassword")}</a></span>
</#if>
</div>

</div>

<div id="kc-form-buttons" class="${properties.kcFormGroupClass!}">
<input type="hidden" id="id-hidden-input" name="credentialId" <#if auth.selectedCredential?has_content>value="${auth.selectedCredential}"</#if>/>
<input tabindex="4" class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonBlockClass!} ${properties.kcButtonLargeClass!}" name="login" id="kc-login" type="submit" value="${msg("doLogIn")}"/>
</div>
</form>
</#if>
</div>

</div>
<#elseif section = "info" >
<#if realm.password && realm.registrationAllowed && !registrationDisabled??>
<div id="kc-registration-container">
<div id="kc-registration">
<span>${msg("noAccount")} <a tabindex="6"
href="${url.registrationUrl}">${msg("doRegister")}</a></span>
</div>
</div>
</#if>
<#elseif section = "socialProviders" >
<#if realm.password && social.providers??>
<div id="kc-social-providers" class="${properties.kcFormSocialAccountSectionClass!}">
<hr/>
<h4>${msg("identity-provider-login-label")}</h4>

<ul class="${properties.kcFormSocialAccountListClass!} <#if social.providers?size gt 3>${properties.kcFormSocialAccountListGridClass!}</#if>">
<#list social.providers as p>
<li>
<a id="social-${p.alias}" class="${properties.kcFormSocialAccountListButtonClass!} <#if social.providers?size gt 3>${properties.kcFormSocialAccountGridItem!}</#if>"
type="button" href="${p.loginUrl}">
<#if p.iconClasses?has_content>
<i class="${properties.kcCommonLogoIdP!} ${p.iconClasses!}" aria-hidden="true"></i>
<span class="${properties.kcFormSocialAccountNameClass!} kc-social-icon-text">${p.displayName!}</span>
<#else>
<span class="${properties.kcFormSocialAccountNameClass!}">${p.displayName!}</span>
</#if>
</a>
</li>
</#list>
</ul>
</div>
</#if>
</#if>

</@layout.registrationLayout>
39 changes: 39 additions & 0 deletions keycloak/theme/customtheme/login/resources/css/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
body {
background-image: url('../img/background.png') !important;
background-repeat: no-repeat !important;
background-size: cover !important;
background-position: bottom !important;
}

#geocamlogo {
max-width: 200px;
height: auto;
}

.card-pf{
border-radius: 25px;
border-top: none;
box-shadow: 0px 1px 5px 0px rgba(0, 0, 0, 0.12), 0px 2px 2px 0px rgba(0, 0, 0, 0.14), 0px 3px 1px -2px rgba(0, 0, 0, 0.20);
}

#username:focus, #password:focus, #username:hover, #password:hover {
border-bottom-color: #2FA37C;
}

#username, #password {
background-color: #FFFFFF;
outline: none !important;
box-shadow: none !important;
border-left: none;
border-top: none;
border-right: none;
}


#kc-form-buttons input {
border-radius: 50px;
background: #2FA37C;
width: fit-content;
padding: 8px 32px;
margin: 0 auto;
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
155 changes: 155 additions & 0 deletions keycloak/theme/customtheme/login/template.ftl
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
<#macro registrationLayout bodyClass="" displayInfo=false displayMessage=true displayRequiredFields=false>
<!DOCTYPE html>
<html class="${properties.kcHtmlClass!}"<#if realm.internationalizationEnabled> lang="${locale.currentLanguageTag}"</#if>>

<head>
<meta charset="utf-8">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="robots" content="noindex, nofollow">

<#if properties.meta?has_content>
<#list properties.meta?split(' ') as meta>
<meta name="${meta?split('==')[0]}" content="${meta?split('==')[1]}"/>
</#list>
</#if>
<title>GeoCam</title>
<link rel="icon" href="${url.resourcesPath}/img/favicon.ico" />
<#if properties.stylesCommon?has_content>
<#list properties.stylesCommon?split(' ') as style>
<link href="${url.resourcesCommonPath}/${style}" rel="stylesheet" />
</#list>
</#if>
<#if properties.styles?has_content>
<#list properties.styles?split(' ') as style>
<link href="${url.resourcesPath}/${style}" rel="stylesheet" />
</#list>
</#if>
<#if properties.scripts?has_content>
<#list properties.scripts?split(' ') as script>
<script src="${url.resourcesPath}/${script}" type="text/javascript"></script>
</#list>
</#if>
<#if scripts??>
<#list scripts as script>
<script src="${script}" type="text/javascript"></script>
</#list>
</#if>
</head>

<body class="${properties.kcBodyClass!}">
<div class="${properties.kcLoginClass!}">
<div id="kc-header" class="${properties.kcHeaderClass!}">
<div id="kc-header-wrapper"
class="${properties.kcHeaderWrapperClass!}"><img id="geocamlogo" src="${url.resourcesPath}/img/logo.png">
</div>
</div>
<div class="${properties.kcFormCardClass!}">
<header class="${properties.kcFormHeaderClass!}">
<#if realm.internationalizationEnabled && locale.supported?size gt 1>
<div class="${properties.kcLocaleMainClass!}" id="kc-locale">
<div id="kc-locale-wrapper" class="${properties.kcLocaleWrapperClass!}">
<div id="kc-locale-dropdown" class="${properties.kcLocaleDropDownClass!}">
<a href="#" id="kc-current-locale-link">${locale.current}</a>
<ul class="${properties.kcLocaleListClass!}">
<#list locale.supported as l>
<li class="${properties.kcLocaleListItemClass!}">
<a class="${properties.kcLocaleItemClass!}" href="${l.url}">${l.label}</a>
</li>
</#list>
</ul>
</div>
</div>
</div>
</#if>
<#if !(auth?has_content && auth.showUsername() && !auth.showResetCredentials())>
<#if displayRequiredFields>
<div class="${properties.kcContentWrapperClass!}">
<div class="${properties.kcLabelWrapperClass!} subtitle">
<span class="subtitle"><span class="required">*</span> ${msg("requiredFields")}</span>
</div>
<div class="col-md-10">
<h1 id="kc-page-title"><#nested "header"></h1>
</div>
</div>
<#else>
<h1 id="kc-page-title"><#nested "header"></h1>
</#if>
<#else>
<#if displayRequiredFields>
<div class="${properties.kcContentWrapperClass!}">
<div class="${properties.kcLabelWrapperClass!} subtitle">
<span class="subtitle"><span class="required">*</span> ${msg("requiredFields")}</span>
</div>
<div class="col-md-10">
<#nested "show-username">
<div id="kc-username" class="${properties.kcFormGroupClass!}">
<label id="kc-attempted-username">${auth.attemptedUsername}</label>
<a id="reset-login" href="${url.loginRestartFlowUrl}" aria-label="${msg("restartLoginTooltip")}">
<div class="kc-login-tooltip">
<i class="${properties.kcResetFlowIcon!}"></i>
<span class="kc-tooltip-text">${msg("restartLoginTooltip")}</span>
</div>
</a>
</div>
</div>
</div>
<#else>
<#nested "show-username">
<div id="kc-username" class="${properties.kcFormGroupClass!}">
<label id="kc-attempted-username">${auth.attemptedUsername}</label>
<a id="reset-login" href="${url.loginRestartFlowUrl}" aria-label="${msg("restartLoginTooltip")}">
<div class="kc-login-tooltip">
<i class="${properties.kcResetFlowIcon!}"></i>
<span class="kc-tooltip-text">${msg("restartLoginTooltip")}</span>
</div>
</a>
</div>
</#if>
</#if>
</header>
<div id="kc-content">
<div id="kc-content-wrapper">

<#-- App-initiated actions should not see warning messages about the need to complete the action -->
<#-- during login. -->
<#if displayMessage && message?has_content && (message.type != 'warning' || !isAppInitiatedAction??)>
<div class="alert-${message.type} ${properties.kcAlertClass!} pf-m-<#if message.type = 'error'>danger<#else>${message.type}</#if>">
<div class="pf-c-alert__icon">
<#if message.type = 'success'><span class="${properties.kcFeedbackSuccessIcon!}"></span></#if>
<#if message.type = 'warning'><span class="${properties.kcFeedbackWarningIcon!}"></span></#if>
<#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!}">${kcSanitize(message.summary)?no_esc}</span>
</div>
</#if>

<#nested "form">

<#if auth?has_content && auth.showTryAnotherWayLink()>
<form id="kc-select-try-another-way-form" action="${url.loginAction}" method="post">
<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>
</form>
</#if>

<#nested "socialProviders">

<#if displayInfo>
<div id="kc-info" class="${properties.kcSignUpClass!}">
<div id="kc-info-wrapper" class="${properties.kcInfoAreaWrapperClass!}">
<#nested "info">
</div>
</div>
</#if>
</div>
</div>

</div>
</div>
</body>
</html>
</#macro>
3 changes: 3 additions & 0 deletions keycloak/theme/customtheme/login/theme.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
parent=keycloak
import=common/keycloak
styles=web_modules/@fontawesome/fontawesome-free/css/icons/all.css web_modules/@patternfly/react-core/dist/styles/base.css web_modules/@patternfly/react-core/dist/styles/app.css node_modules/patternfly/dist/css/patternfly.min.css node_modules/patternfly/dist/css/patternfly-additions.min.css css/login.css css/styles.css

0 comments on commit 30d842e

Please sign in to comment.