Skip to content

Commit

Permalink
feat: customize OIDC login page
Browse files Browse the repository at this point in the history
  • Loading branch information
vincentchalamon committed Sep 7, 2023
1 parent 40bb831 commit 3cbfaab
Show file tree
Hide file tree
Showing 12 changed files with 184 additions and 20 deletions.
12 changes: 8 additions & 4 deletions .github/workflows/cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ jobs:
runs-on: ubuntu-latest
env:
PHP_DOCKER_IMAGE: eu.gcr.io/${{ secrets.GKE_PROJECT }}/php:${{ github.sha }}
CADDY_DOCKER_IMAGE: eu.gcr.io/${{ secrets.GKE_PROJECT }}/caddy:${{ github.sha }}
PWA_DOCKER_IMAGE: eu.gcr.io/${{ secrets.GKE_PROJECT }}/pwa:${{ github.sha }}
CADDY_DOCKER_IMAGE: eu.gcr.io/${{ secrets.GKE_PROJECT }}/caddy:${{ github.sha }}
KEYCLOAK_DOCKER_IMAGE: eu.gcr.io/${{ secrets.GKE_PROJECT }}/keycloak:${{ github.sha }}
steps:
-
name: Checkout
Expand Down Expand Up @@ -62,17 +63,20 @@ jobs:
name: Docker tag latest
run: |
docker tag $PHP_DOCKER_IMAGE eu.gcr.io/${{ secrets.GKE_PROJECT }}/php:latest
docker tag $CADDY_DOCKER_IMAGE eu.gcr.io/${{ secrets.GKE_PROJECT }}/caddy:latest
docker tag $PWA_DOCKER_IMAGE eu.gcr.io/${{ secrets.GKE_PROJECT }}/pwa:latest
docker tag $CADDY_DOCKER_IMAGE eu.gcr.io/${{ secrets.GKE_PROJECT }}/caddy:latest
docker tag $KEYCLOAK_DOCKER_IMAGE eu.gcr.io/${{ secrets.GKE_PROJECT }}/keycloak:latest
-
name: Docker push
run: |
docker push $PHP_DOCKER_IMAGE
docker push eu.gcr.io/${{ secrets.GKE_PROJECT }}/php:latest
docker push $CADDY_DOCKER_IMAGE
docker push eu.gcr.io/${{ secrets.GKE_PROJECT }}/caddy:latest
docker push $PWA_DOCKER_IMAGE
docker push eu.gcr.io/${{ secrets.GKE_PROJECT }}/pwa:latest
docker push $CADDY_DOCKER_IMAGE
docker push eu.gcr.io/${{ secrets.GKE_PROJECT }}/caddy:latest
docker push $KEYCLOAK_DOCKER_IMAGE
docker push eu.gcr.io/${{ secrets.GKE_PROJECT }}/keycloak:latest
deploy:
name: Deploy
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ jobs:
PHP_DOCKER_IMAGE: eu.gcr.io/${{ secrets.GKE_PROJECT }}/php:latest
PWA_DOCKER_IMAGE: eu.gcr.io/${{ secrets.GKE_PROJECT }}/pwa:latest
CADDY_DOCKER_IMAGE: eu.gcr.io/${{ secrets.GKE_PROJECT }}/caddy:latest
KEYCLOAK_DOCKER_IMAGE: eu.gcr.io/${{ secrets.GKE_PROJECT }}/keycloak:latest
APP_SECRET: ba63418865d58089f7f070e0a437b6d16b1fb970
CADDY_MERCURE_JWT_SECRET: 33b04d361e437e0d7d715600fc24fdefba317154
NEXTAUTH_SECRET: 77e4c3f5a6fb652b6245a5df8a704e04ad90bc7e
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,9 @@ jobs:
--wait \
--namespace=${{ inputs.namespace }} \
--set=app.version=${{ github.sha }} \
--set=keycloak.image.repository=eu.gcr.io/${{ secrets.gke-project }}/keycloak \
--set=keycloak.image.tag=${{ inputs.docker-images-version }} \
--set=keycloak.image.pullPolicy=Always \
--set=keycloak.auth.adminPassword=${{ secrets.keycloak-admin-password }} \
--set=keycloak.extraEnvVars[0].name=KC_HOSTNAME_URL \
--set-string=keycloak.extraEnvVars[0].value=https://${{ inputs.url }}/oidc/ \
Expand Down
21 changes: 21 additions & 0 deletions docker-compose.override.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,24 @@ services:

###> symfony/mercure-bundle ###
###< symfony/mercure-bundle ###

keycloak:
build:
context: ./helm/api-platform/keycloak/
target: keycloak
volumes:
- ./helm/api-platform/keycloak/themes/api-platform-demo:/opt/bitnami/keycloak/themes/api-platform-demo

keycloak-config-cli:
image: bitnami/keycloak-config-cli:5-debian-11
environment:
KEYCLOAK_URL: http://caddy/oidc/
KEYCLOAK_USER: ${KEYCLOAK_ADMIN_USER:-admin}
KEYCLOAK_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD:-!ChangeMe!}
KEYCLOAK_AVAILABILITYCHECK_ENABLED: true
KEYCLOAK_AVAILABILITYCHECK_TIMEOUT: 120s
IMPORT_FILES_LOCATIONS: "/config/*"
depends_on:
- keycloak
volumes:
- ./helm/api-platform/keycloak/config:/config
1 change: 1 addition & 0 deletions docker-compose.prod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ services:
POSTGRES_PASSWORD: ${KEYCLOAK_POSTGRES_PASSWORD}

keycloak:
image: ${KEYCLOAK_DOCKER_IMAGE}
environment:
KEYCLOAK_PRODUCTION: "true"
KEYCLOAK_ADMIN_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD}
Expand Down
17 changes: 2 additions & 15 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ services:
- keycloak_db_data:/var/lib/postgresql/data

keycloak:
image: bitnami/keycloak:${KEYCLOAK_VERSION:-21-debian-11}
image: app_keycloak
environment:
KEYCLOAK_DATABASE_HOST: keycloak-database
KEYCLOAK_DATABASE_NAME: ${KEYCLOAK_POSTGRES_DB:-keycloak}
Expand All @@ -106,27 +106,14 @@ services:
KEYCLOAK_HTTP_RELATIVE_PATH: /oidc/
# https://www.keycloak.org/server/hostname
KC_HOSTNAME_URL: https://${SERVER_NAME:-localhost}/oidc/
KC_HOSTNAME_ADMIN_URL: https://${SERVER_NAME:-localhost}/oidc/
depends_on:
- keycloak-database
ports:
- target: 8080
published: 8080
protocol: tcp

keycloak-config-cli:
image: bitnami/keycloak-config-cli:5-debian-11
environment:
KEYCLOAK_URL: http://caddy/oidc/
KEYCLOAK_USER: ${KEYCLOAK_ADMIN_USER:-admin}
KEYCLOAK_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD:-!ChangeMe!}
KEYCLOAK_AVAILABILITYCHECK_ENABLED: true
KEYCLOAK_AVAILABILITYCHECK_TIMEOUT: 120s
IMPORT_FILES_LOCATIONS: "/config/*"
depends_on:
- keycloak
volumes:
- ./helm/api-platform/keycloak/config:/config

volumes:
php_socket:
caddy_data:
Expand Down
18 changes: 18 additions & 0 deletions helm/api-platform/keycloak/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#syntax=docker/dockerfile:1.4




# Versions
FROM bitnami/keycloak:22-debian-11 AS keycloak_upstream


# The different stages of this Dockerfile are meant to be built into separate images
# https://docs.docker.com/develop/develop-images/multistage-build/#stop-at-a-specific-build-stage
# https://docs.docker.com/compose/compose-file/#target


# Keycloak image
FROM keycloak_upstream AS keycloak

COPY --link themes/api-platform-demo /opt/bitnami/keycloak/themes/api-platform-demo
1 change: 1 addition & 0 deletions helm/api-platform/keycloak/config/realm-demo.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"enabled": true,
"registrationAllowed": false,
"accessCodeLifespan": 1,
"loginTheme": "api-platform-demo",
"users": [
{
"username": "chuck.norris",
Expand Down
112 changes: 112 additions & 0 deletions helm/api-platform/keycloak/themes/api-platform-demo/login/login.ftl
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
<#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!}">
<p>
Login as user: [email protected] / Pa55w0rd<br />
Login as admin: [email protected] / Pa55w0rd
</p>

<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>
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
parent=keycloak
import=common/keycloak
5 changes: 4 additions & 1 deletion helm/api-platform/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,10 @@ postgresql:
keycloak:
enabled: true
image:
tag: 21-debian-11
repository: "chart-example.local/api-platform/keycloak"
pullPolicy: IfNotPresent
# Overrides the image tag whose default is the chart appVersion.
tag: ""
service:
type: ClusterIP
extraEnvVars:
Expand Down
11 changes: 11 additions & 0 deletions pwa/tests/User.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ test.describe("User authentication", () => {
await page.getByText("Log in").waitFor({ state: "hidden" });
// @ts-ignore assert declared on test.ts
await expect(page).toBeOnLoginPage();
await expect(page.locator("#kc-header-wrapper")).toContainText("API Platform - Demo");
await expect(page.locator("#kc-form-login")).toContainText("Login as user: [email protected]");
await expect(page.locator("#kc-form-login")).toContainText("Login as admin: [email protected]");
await userPage.login();

await expect(page.getByText("Log in")).toHaveCount(0);
Expand All @@ -26,5 +29,13 @@ test.describe("User authentication", () => {

await expect(page.getByText("Log in")).toBeVisible();
await expect(page.getByText("Sign out")).toHaveCount(0);

// I should be logged out from Keycloak also
await page.getByText("Log in").click();
// @ts-ignore assert declared on test.ts
await expect(page).toBeOnLoginPage();
await expect(page.locator("#kc-header-wrapper")).toContainText("API Platform - Demo");
await expect(page.locator("#kc-form-login")).toContainText("Login as user: [email protected]");
await expect(page.locator("#kc-form-login")).toContainText("Login as admin: [email protected]");
});
});

0 comments on commit 3cbfaab

Please sign in to comment.