Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(upgrade): Upgrade Keycloakify to v11 #247

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,4 @@ jspm_packages
/build
/storybook-static
/node-modules
/public/keycloakify-dev-resources
8 changes: 1 addition & 7 deletions .storybook/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,7 @@ import type { StorybookConfig } from "@storybook/react-vite";

const config: StorybookConfig = {
stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"],
addons: [
"@storybook/addon-links",
"@storybook/addon-essentials",
"@storybook/addon-onboarding",
"@storybook/addon-interactions",
"@storybook/addon-queryparams",
],
addons: ["@storybook/addon-queryparams"],
framework: {
name: "@storybook/react-vite",
options: {},
Expand Down
229 changes: 37 additions & 192 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,223 +1,68 @@
<p align="center">
<i>🚀 A starter/demo project for <a href="https://keycloakify.dev">Keycloakify</a> v9 🚀</i>
<i>🚀 <a href="https://keycloakify.dev">Keycloakify</a> v11 starter 🚀</i>
<br/>
<br/>
<img src="https://github.com/codegouvfr/keycloakify-starter/workflows/ci/badge.svg?branch=main">
<br/>
<br/>
<a href="https://starter.keycloakify.dev">Authenticated React SPA</a>
</p>

# Introduction

This repo constitutes an easily reusable setup for a Keycloak theme project OR for a Vite SPA React App that generates a
Keycloak theme that goes along with it.
If you are only looking to create a Keycloak theme (and not a Keycloak theme and an App that share the same codebase) there are a lot of things that you can remove from this starter: [Please read this section of the README](#standalone-keycloak-theme).

This starter is based on Vite. There is also [a Webpack based starter](https://github.com/keycloakify/keycloakify-starter-cra).

> 📣 Looking for a library for redirecting your user to Keycloak when they click on the 'Login' button?
> Check out [oidc-spa](https://oidc-spa.dev) It's made by us and it's used in the [src/App](https://github.com/keycloakify/keycloakify-starter/tree/main/src/App) of this starter.

# Quick start

```bash
git clone https://github.com/keycloakify/keycloakify-starter

cd keycloakify-starter

yarn # install dependencies (it's like npm install)

yarn storybook # Start Storybook
# This is by far the best way to develop your theme
# This enable to quickly see your pages in isolation and in different states.
# You can create stories even for pages that you haven't explicitly overloaded. See src/keycloak-theme/login/pages/LoginResetPassword.stories.tsx
# See Keycloakify's storybook for if you need a starting point for your stories: https://github.com/keycloakify/keycloakify/tree/main/stories

yarn dev # See the Hello World app
# Uncomment line 97 of src/keycloak-theme/login/kcContext where it reads: `mockPageId: "login.ftl"`, reload https://localhost:3000
# You can now see the login.ftl page with the mock data. (Don't forget to comment it back when you're done)

# Install mvn (Maven) if not already done. On mac it's 'brew install maven', on Ubuntu/Debian it's 'sudo apt-get install maven'

yarn build-keycloak-theme # Actually build the theme (generates the .jar to be imported in Keycloak)
# Read the instruction printed on the console to see how to test
# your theme on a real Keycloak instance.

npx eject-keycloak-page # Prompt that let you select the pages you want to customize
# This CLI tools is not guaranty to work, you can always copy paste pages
# from the Keycloakify repo.
# After you ejected a page you need to edit the src/keycloak-theme/login(or admin)/KcApp.tsx file
# You need to add a case in the switch for the page you just imported in your project.
# Look how it's done for the Login page and replicate for your new page.

npx initialize-email-theme # For initializing your email theme
# Note that Keycloakify does not feature React integration for email yet.

npx download-builtin-keycloak-theme # For downloading the default theme (as a reference)
# Look for the files in dist_keycloak/src/main/resources/theme/{base,keycloak}
yarn install # Or use an other package manager, just be sure to delete the yarn.lock if you use another package manager.
```

# Theme variant
# Testing the theme locally

Keycloakify enables you to create different variant for a single theme.
This enable you to have a single jar that embed two or more theme variant.
[Documentation](https://docs.keycloakify.dev/testing-your-theme)

![Theme variant](https://content.gitbook.com/content/FcBKODbZbNDgm0rc6a9K/blobs/9iKgs2rv2Kfb2pbs4dRz/image.png)
Generally, the recommended way to test the theme is either through using `Storybook`, in which we simply run `yarn storybook`, or through using `yarn dev`. If you decided to test using the latter method, you need to un-comment the part of the code inside `main.tsx` to get the Mock Context. Specifically, include the following

You can enable this feature by providing multiple theme name in the Keycloakify build option.
[See documentation](https://docs.keycloakify.dev/build-options#themename)
```
import { getKcContextMock } from "./login/KcPageStory";

if (import.meta.env.DEV) {
window.kcContext = getKcContextMock({
pageId: "login.ftl", // or whatever page you want to test
overrides: {},
});
}
```

# The CI workflow
# How to customize the theme

- To release **don't create a tag manually**, the CI do it for you. Just update the `package.json`'s version field and push.
- The `.jar` files that bundle the Keycloak theme will be attached as an asset with every GitHub release. [Example](https://github.com/InseeFrLab/keycloakify-starter/releases/tag/v0.1.0). The permalink to download the latest version is: `https://github.com/USER/PROJECT/releases/latest/download/keycloak-theme.jar`.
For this demo repo it's [here](https://github.com/codegouvfr/keycloakify-starter/releases/latest/download/keycloak-theme.jar)
- This CI is configured to publish [the app](https://starter.keycloakify.dev) on [GitHub Pages](https://github.com/codegouvfr/keycloakify-starter/blob/3617a71deb1a6544c3584aa8d6d2241647abd48c/.github/workflows/ci.yaml#L51-L76) and on [DockerHub](https://github.com/codegouvfr/keycloakify-starter/blob/3617a71deb1a6544c3584aa8d6d2241647abd48c/.github/workflows/ci.yaml#L78-L123) (as a Ngnix based docker image). In practice you probably want one or the other but not both... or neither if you are just building a theme (and not a theme + an app).
If you want to enable the CI to publish on DockerHub on your behalf go to repository `Settings` tab, then `Secrets` you will need to add two new secrets:
`DOCKERHUB_TOKEN`, you Dockerhub authorization token.
`DOCKERHUB_USERNAME`, Your Dockerhub username.
We deploy the demo app at [starter.keycloakify.dev](https://starter.keycloakify.dev) using GitHub page on the branch `gh-pages` (you have to enable it).
To configure your own domain name update the homepage field of the `package.json` and potentially the `base` option in the `vite.config.ts`.
Regarding DNS configuration you can refer to [this documentation](https://docs.gitlanding.dev/using-a-custom-domain-name).
- The CI publishes the app docker image on DockerHub. `<org>/<repo>:main` for each **commit** on `main`, `<org>/<repo>:<feature-branch-name>` for each **pull-request** on `main`
and when **releasing a new version**: `<org>/<repo>:latest` and `<org>/<repo>:X.Y.Z`
[See on DockerHub](https://hub.docker.com/r/codegouvfr/keycloakify-starter)
[Documentation](https://docs.keycloakify.dev/customization-strategies)

![image](https://user-images.githubusercontent.com/6702424/229296422-9d522707-114e-4282-93f7-01ca38c3a1e0.png)
# Building the theme

![image](https://user-images.githubusercontent.com/6702424/229296556-a69f2dc9-4653-475c-9c89-d53cf33dc05a.png)
You need to have [Maven](https://maven.apache.org/) installed to build the theme (Maven >= 3.1.1, Java >= 7).
The `mvn` command must be in the $PATH.

# The storybook
- On macOS: `brew install maven`
- On Debian/Ubuntu: `sudo apt-get install maven`
- On Windows: `choco install openjdk` and `choco install maven` (Or download from [here](https://maven.apache.org/download.cgi))

![image](https://github.com/keycloakify/keycloakify/assets/6702424/a18ac1ff-dcfd-4b8c-baed-dcda5aa1d762)
You can build the keycloak theme (to be imported over to Coursemology2 repo) by executing the following command

```bash
yarn
yarn storybook
yarn build-keycloak-theme
```

# Docker
Note that by default Keycloakify generates multiple .jar files for different versions of Keycloak.
You can customize this behavior, see documentation [here](https://docs.keycloakify.dev/targeting-specific-keycloak-versions).

Instructions for building and running the react app (`src/App`) that is collocated with our Keycloak theme.
In Coursemology, at this moment, we use Keycloak v24. When those multiple .jar are created, please note that:

```bash
docker build -f Dockerfile -t keycloakify/keycloakify-starter:main .
docker run -it -dp 8083:80 keycloakify/keycloakify-starter:main
# You can access the app at http://localhost:8083
```
- For Keycloak v23 and above (which is what we're using), please use `coursemology-keycloakify-keycloak-theme-{ver_number}.jar`
- Otherwise, please use `retrocompat-coursemology-keycloakify-keycloak-theme-{ver_number}.jar`

# I only want a Keycloak theme
Dated (2025/01/20), the version number is 8.0.0. For future developers, should this repo be updated, please change the `ver-number` accordingly

If you are only looking to create a Keycloak theme and not a Theme + a React app, you can run theses few commands to refactor the template
and remove unnecessary files.
# GitHub Actions

```bash
cd path/to/keycloakify-starter
rm -r src/App
mv src/keycloak-theme/* src/
rm -r src/keycloak-theme

cat << EOF > src/main.tsx
import { createRoot } from "react-dom/client";
import { StrictMode, lazy, Suspense } from "react";
import { kcContext as kcLoginThemeContext } from "./login/kcContext";
import { kcContext as kcAccountThemeContext } from "./account/kcContext";

const KcLoginThemeApp = lazy(() => import("./login/KcApp"));
const KcAccountThemeApp = lazy(() => import("./account/KcApp"));

createRoot(document.getElementById("root")!).render(
<StrictMode>
<Suspense>
{(()=>{

if( kcLoginThemeContext !== undefined ){
return <KcLoginThemeApp kcContext={kcLoginThemeContext} />;
}

if( kcAccountThemeContext !== undefined ){
return <KcAccountThemeApp kcContext={kcAccountThemeContext} />;
}

throw new Error(
"This app is a Keycloak theme" +
"It isn't meant to be deployed outside of Keycloak"
);

})()}
</Suspense>
</StrictMode>
);

EOF

rm .dockerignore Dockerfile nginx.conf

cat << EOF > .github/workflows/ci.yaml
name: ci
on:
push:
branches:
- main
pull_request:
branches:
- main

jobs:

test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
- uses: bahmutov/npm-install@v1
- run: yarn build
- run: npx keycloakify

check_if_version_upgraded:
name: Check if version upgrade
if: github.event_name == 'push'
runs-on: ubuntu-latest
needs: test
outputs:
from_version: \${{ steps.step1.outputs.from_version }}
to_version: \${{ steps.step1.outputs.to_version }}
is_upgraded_version: \${{ steps.step1.outputs.is_upgraded_version }}
steps:
- uses: garronej/[email protected]
id: step1
with:
action_name: is_package_json_version_upgraded
branch: \${{ github.head_ref || github.ref }}

create_github_release:
runs-on: ubuntu-latest
needs: check_if_version_upgraded
# We create a release only if the version have been upgraded and we are on a default branch
if: needs.check_if_version_upgraded.outputs.is_upgraded_version == 'true' && github.event_name == 'push'
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
- uses: bahmutov/npm-install@v1
- run: yarn build
- run: npx keycloakify
- run: mv dist_keycloak/target/retrocompat-*.jar retrocompat-keycloak-theme.jar
- run: mv dist_keycloak/target/*.jar keycloak-theme.jar
- uses: softprops/action-gh-release@v1
with:
name: Release v\${{ needs.check_if_version_upgraded.outputs.to_version }}
tag_name: v\${{ needs.check_if_version_upgraded.outputs.to_version }}
target_commitish: \${{ github.head_ref || github.ref }}
generate_release_notes: true
draft: false
files: |
retrocompat-keycloak-theme.jar
keycloak-theme.jar
env:
GITHUB_TOKEN: \${{ secrets.GITHUB_TOKEN }}

EOF
```
The starter comes with a generic GitHub Actions workflow that builds the theme and publishes
the jars [as GitHub releases artifacts](https://github.com/keycloakify/keycloakify-starter/releases/tag/v10.0.0).
To release a new version **just update the `package.json` version and push**.

You can also remove `oidc-spa`, `powerhooks`, `zod` and `tsafe` from your dependencies.
To enable the workflow go to your fork of this repository on GitHub then navigate to:
`Settings` > `Actions` > `Workflow permissions`, select `Read and write permissions`.
10 changes: 2 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "keycloakify-starter",
"homepage": "https://starter.keycloakify.dev",
"version": "6.1.7",
"version": "8.0.0",
"description": "A starter/demo project for keycloakify",
"repository": {
"type": "git",
Expand Down Expand Up @@ -29,20 +29,14 @@
"@emotion/react": "^11.13.5",
"@emotion/styled": "^11.13.5",
"framer-motion": "^11.2.0",
"keycloakify": "^9.6.6",
"keycloakify": "^11.8.9",
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
"devDependencies": {
"@storybook/addon-essentials": "^8.1.11",
"@storybook/addon-interactions": "^8.1.11",
"@storybook/addon-links": "^8.1.11",
"@storybook/addon-onboarding": "^8.1.11",
"@storybook/addon-queryparams": "^7.0.1",
"@storybook/blocks": "^8.1.11",
"@storybook/react": "^8.1.11",
"@storybook/react-vite": "^8.1.11",
"@storybook/test": "^8.1.11",
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"@typescript-eslint/eslint-plugin": "^7.0.0",
Expand Down
26 changes: 8 additions & 18 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,15 @@
import { lazy, Suspense } from "react";

import Providers from "./lib/components/wrappers/Providers";
import { kcContext as kcLoginThemeContext } from "./login/kcContext";

const KcLoginThemeApp = lazy(() => import("./login/KcApp"));
import { KcPage } from "./kc.gen";

const App = (): JSX.Element => {
return (
<Suspense>
{(() => {
if (kcLoginThemeContext !== undefined) {
return <KcLoginThemeApp kcContext={kcLoginThemeContext} />;
}
if (!window.kcContext) {
throw new Error(
"This app is a Keycloak theme" +
"It isn't meant to be deployed outside of Keycloak",
);
}

throw new Error(
"This app is a Keycloak theme" +
"It isn't meant to be deployed outside of Keycloak",
);
})()}
</Suspense>
);
return <KcPage kcContext={window.kcContext} />;
};

const AppWithProviders = (): JSX.Element => {
Expand Down
51 changes: 51 additions & 0 deletions src/kc.gen.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// This file is auto-generated by the `update-kc-gen` command. Do not edit it manually.
// Hash: 9ae0fc6db02c7aaca5511c810584dc11912602c9a6a8c8f778a301e8e52807fc

/* eslint-disable */

// @ts-nocheck

// noinspection JSUnusedGlobalSymbols

import { lazy, Suspense, type ReactNode } from "react";

export type ThemeName = "coursemology-keycloakify";

export const themeNames: ThemeName[] = ["coursemology-keycloakify"];

export type KcEnvName = "MY_ENV_VARIABLE";

export const kcEnvNames: KcEnvName[] = ["MY_ENV_VARIABLE"];

export const kcEnvDefaults: Record<KcEnvName, string> = {
MY_ENV_VARIABLE: "",
};

/**
* NOTE: Do not import this type except maybe in your entrypoint.
* If you need to import the KcContext import it either from src/login/KcContext.ts or src/account/KcContext.ts.
* Depending on the theme type you are working on.
*/
export type KcContext = import("./login/KcContext").KcContext;

declare global {
interface Window {
kcContext?: KcContext;
}
}

export const KcLoginPage = lazy(() => import("./login/KcPage"));

export function KcPage(props: { kcContext: KcContext; fallback?: ReactNode }) {
const { kcContext, fallback } = props;
return (
<Suspense fallback={fallback}>
{(() => {
switch (kcContext.themeType) {
case "login":
return <KcLoginPage kcContext={kcContext} />;
}
})()}
</Suspense>
);
}
Loading
Loading