From 7aabee9dd5f35886f7565155a7f19454f8a85a9d Mon Sep 17 00:00:00 2001 From: garronej Date: Sun, 26 Feb 2023 16:35:55 +0100 Subject: [PATCH] Add custom login page --- README.md | 17 ++- package.json | 8 +- src/keycloakTheme/KcApp.tsx | 12 +- src/keycloakTheme/kcContext.ts | 30 +++-- src/keycloakTheme/pages/Login.tsx | 192 ++++++++++++++++++++++++++++++ 5 files changed, 231 insertions(+), 28 deletions(-) create mode 100644 src/keycloakTheme/pages/Login.tsx diff --git a/README.md b/README.md index 8531df1..8892c04 100644 --- a/README.md +++ b/README.md @@ -4,26 +4,23 @@ A starter/demo project for [Keycloakify](https://keycloakify.dev) -# ⚠️ Please read the two following notices ⚠️ +# ⚠️ Please read the two following notice ⚠️ -> This starter is for **Component-level customization**, if you only want to customize **the page at the CSS level** -> heads over to [keycloakify-starter](https://github.com/garronej/keycloakify-starter). - -> If you are only looking to create a theme and don't care about integrating it into a React app there +> If you are only looking to create a theme and don't care about integrating it into an React app there > are a lot of things that you can remove from this starter. [Please read this](#standalone-keycloak-theme). # Quick start ```bash yarn -yarn keycloak # Build the theme one time (some assets will be copied to +yarn build-keycloak-theme # Build the theme one time (some assets will be copied to # public/keycloak_static, they are needed to dev your page outside of Keycloak) yarn start # See the Hello World app # Uncomment line 15 of src/keycloakTheme/kcContext, reload https://localhost:3000 # You can now develop your Login pages. # Think your theme is ready? Run -yarn keycloak +yarn build-keycloak-theme # Read the instruction printed on the console to see how to test # your theme on a real Keycloak instance. ``` @@ -66,11 +63,11 @@ More info on the `--external-assets` build option [here](https://docs.keycloakif # Docker ```bash -docker build -f Dockerfile -t garronej/keycloakify-advanced-starter:test . +docker build -f Dockerfile -t codegouvfr/keycloakify-starter:test . #OR (to reproduce how the image is built in the ci workflow): -yarn && yarn build && tar -cvf build.tar ./build && docker build -f Dockerfile.ci -t garronej/keycloakify-advanced-starter:test . && rm build.tar +yarn && yarn build && tar -cvf build.tar ./build && docker build -f Dockerfile.ci -t codegouvfr/keycloakify-starter:test . && rm build.tar -docker run -it -dp 8083:80 garronej/keycloakify-advanced-starter:test +docker run -it -dp 8083:80 garronej/keycloakify-starter:test ``` ## DockerHub credentials diff --git a/package.json b/package.json index 24d25bf..e71d72e 100755 --- a/package.json +++ b/package.json @@ -1,16 +1,16 @@ { - "name": "keycloakify-advanced-starter", - "homepage": "https://demo-app-advanced.keycloakify.dev", + "name": "keycloakify-starter", + "homepage": "https://starter.keycloakify.dev", "version": "1.0.8", "description": "A starter/demo project for keycloakify", "repository": { "type": "git", - "url": "git://github.com/garronej/keycloakify-advanced-starter.git" + "url": "git://github.com/codegouvfr/keycloakify-starter.git" }, "scripts": { "start": "react-scripts start", "build": "react-scripts build", - "keycloak": "yarn build && keycloakify", + "build-keycloak-theme": "yarn build && keycloakify", "download-builtin-keycloak-theme": "download-builtin-keycloak-theme 15.0.2" }, "keycloakify": { diff --git a/src/keycloakTheme/KcApp.tsx b/src/keycloakTheme/KcApp.tsx index a9ea5dd..3f7a561 100644 --- a/src/keycloakTheme/KcApp.tsx +++ b/src/keycloakTheme/KcApp.tsx @@ -3,15 +3,16 @@ import { lazy, Suspense } from "react"; import type { KcContext } from "./kcContext"; import { useI18n } from "./i18n"; import Fallback, { defaultKcProps, type KcProps, type PageProps } from "keycloakify"; -// Here we have overloaded the default template, however you could use the default one with: -//import Template from "keycloakify/lib/Template"; import Template from "./Template"; +import DefaultTemplate from "keycloakify/lib/Template"; -const Login = lazy(() => import("keycloakify/lib/pages/Login")); +const Login = lazy(()=> import("./pages/Login")); +// If you can, favor register-user-profile.ftl over register.ftl, see: https://docs.keycloakify.dev/realtime-input-validation const Register = lazy(() => import("./pages/Register")); const Terms = lazy(() => import("./pages/Terms")); const MyExtraPage1 = lazy(() => import("./pages/MyExtraPage1")); const MyExtraPage2 = lazy(() => import("./pages/MyExtraPage2")); +const Info = lazy(()=> import("keycloakify/lib/pages/Info")); // This is like editing the theme.properties // https://github.com/keycloak/keycloak/blob/11.0.3/themes/src/main/resources/theme/keycloak/login/theme.properties @@ -35,7 +36,10 @@ export default function App(props: { kcContext: KcContext; }) { const pageProps: Omit, "kcContext"> = { i18n, + // Here we have overloaded the default template, however you could use the default one with: + //Template: DefaultTemplate, Template, + // Wether or not we should download the CSS and JS resources that comes with the default Keycloak theme. doFetchDefaultThemeResources: true, ...kcProps, }; @@ -49,6 +53,8 @@ export default function App(props: { kcContext: KcContext; }) { case "terms.ftl": return ; case "my-extra-page-1.ftl": return ; case "my-extra-page-2.ftl": return ; + // We choose to use the default Template for the Info page and to download the theme resources. + case "info.ftl": return ; default: return ; } })()} diff --git a/src/keycloakTheme/kcContext.ts b/src/keycloakTheme/kcContext.ts index b4640ef..ad7ab6d 100644 --- a/src/keycloakTheme/kcContext.ts +++ b/src/keycloakTheme/kcContext.ts @@ -17,7 +17,7 @@ export const { kcContext } = getKcContext< | { pageId: "register.ftl"; authorizedMailDomains: string[]; } >({ // Uncomment to test the login page for development. - // mockPageId: "login.ftl", + //mockPageId: "login.ftl", mockData: [ { pageId: "login.ftl", @@ -34,16 +34,6 @@ export const { kcContext } = getKcContext< pageId: "my-extra-page-2.ftl", someCustomValue: "foo bar baz" }, - { - pageId: "register.ftl", - authorizedMailDomains: [ - "example.com", - "another-example.com", - "*.yet-another-example.com", - "*.example.com", - "hello-world.com" - ] - }, { //NOTE: You will either use register.ftl (legacy) or register-user-profile.ftl, not both pageId: "register-user-profile.ftl", @@ -81,6 +71,24 @@ export const { kcContext } = getKcContext< } ] } + }, + { + pageId: "register.ftl", + authorizedMailDomains: [ + "example.com", + "another-example.com", + "*.yet-another-example.com", + "*.example.com", + "hello-world.com" + ], + // Simulate we got an error with the email field + messagesPerField: { + printIfExists: (fieldName: string, className: T) => { console.log({ fieldName}); return fieldName === "email" ? className : undefined; }, + existsError: (fieldName: string)=> fieldName === "email", + get: (fieldName: string) => `Fake error for ${fieldName}`, + exists: (fieldName: string) => fieldName === "email" + }, + } ] }); diff --git a/src/keycloakTheme/pages/Login.tsx b/src/keycloakTheme/pages/Login.tsx new file mode 100644 index 0000000..a6266dd --- /dev/null +++ b/src/keycloakTheme/pages/Login.tsx @@ -0,0 +1,192 @@ +import { useState, type FormEventHandler } from "react"; +import { clsx } from "keycloakify/lib/tools/clsx"; +import { useConstCallback } from "keycloakify/lib/tools/useConstCallback"; +import type { PageProps } from "keycloakify/lib/KcProps"; +import type { KcContext } from "../kcContext"; +import type { I18n } from "../i18n"; + +export default function Login(props: PageProps, I18n>) { + const { kcContext, i18n, doFetchDefaultThemeResources = true, Template, ...kcProps } = props; + + const { social, realm, url, usernameEditDisabled, login, auth, registrationDisabled } = kcContext; + + const { msg, msgStr } = i18n; + + const [isLoginButtonDisabled, setIsLoginButtonDisabled] = useState(false); + + const onSubmit = useConstCallback>(e => { + e.preventDefault(); + + setIsLoginButtonDisabled(true); + + const formElement = e.target as HTMLFormElement; + + //NOTE: Even if we login with email Keycloak expect username and password in + //the POST request. + formElement.querySelector("input[name='email']")?.setAttribute("name", "username"); + + formElement.submit(); + }); + + return ( +