Compare commits

...

41 Commits

Author SHA1 Message Date
2f16a09ab8 Update changelog v0.3.19 2021-04-01 15:21:10 +00:00
183ae98c30 Merge branch 'main' of https://github.com/InseeFrLab/keycloakify into main 2021-04-01 17:16:01 +02:00
ba15e63879 Bump version (changelog ignore) 2021-04-01 17:15:55 +02:00
654277feda Fix previous release 2021-04-01 17:15:28 +02:00
81279a5cc5 Update changelog v0.3.18 2021-04-01 15:04:08 +00:00
59f0a843b0 Merge branch 'main' of https://github.com/InseeFrLab/keycloakify into main 2021-04-01 16:52:53 +02:00
c094f70171 Bump version (changelog ignore) 2021-04-01 16:51:50 +02:00
0858fe6319 Fix error.ftt, Adopt best effort strategy to convert ftl values into JS 2021-04-01 16:51:28 +02:00
5012ec0ccc Update changelog v0.3.17 2021-03-29 04:33:07 +00:00
990a24fab2 Merge branch 'main' of https://github.com/InseeFrLab/keycloakify into main 2021-03-29 06:27:19 +02:00
036b6bf82a Use push instead of replace in keycloak-js adapter to enable going back 2021-03-29 06:27:12 +02:00
8272a02b52 Update changelog v0.3.15 2021-03-28 12:18:37 +00:00
e346b1d9d2 Merge branch 'main' of https://github.com/InseeFrLab/keycloakify into main 2021-03-28 14:14:52 +02:00
2309bd21c6 Remove all reference to --external-assets, broken feature 2021-03-28 14:14:43 +02:00
7d6476c1b5 Update changelog v0.3.14 2021-03-28 11:39:36 +00:00
e892a0e7e6 Merge branch 'main' of https://github.com/InseeFrLab/keycloakify into main 2021-03-28 13:37:07 +02:00
ca5b41e730 Fix standalone mode: imports from js 2021-03-28 13:37:02 +02:00
9b18234112 Update changelog v0.3.13 2021-03-26 16:54:38 +00:00
5274368f47 Merge branch 'main' of https://github.com/InseeFrLab/keycloakify into main 2021-03-26 17:49:36 +01:00
1415c24028 Bump version (changelog ignore) 2021-03-26 17:49:31 +01:00
4a084f5859 Fix previous release (changelog ignore) 2021-03-26 17:49:09 +01:00
a30c9eb0cd Update changelog v0.3.12 2021-03-26 16:35:41 +00:00
85d3b40b8e Merge branch 'main' of https://github.com/InseeFrLab/keycloakify into main 2021-03-26 17:31:06 +01:00
335afec230 Bump version (changelog ignore) 2021-03-26 17:31:01 +01:00
69fa49848a Fix mocksContext 2021-03-26 17:30:04 +01:00
7a09051127 Update changelog v0.3.11 2021-03-26 14:36:48 +00:00
07ee0ecb8b Merge branch 'main' of https://github.com/InseeFrLab/keycloakify into main 2021-03-26 15:29:23 +01:00
6f133428f8 Fix previous build, improve README 2021-03-26 15:29:17 +01:00
4f733736db fmt (changelog ignore) 2021-03-26 14:06:14 +01:00
d96ff13a67 Update changelog v0.3.10 2021-03-26 13:05:25 +00:00
2c1351ce47 fmt (changelog ignore) 2021-03-26 14:04:45 +01:00
96cd56ec77 Merge branch 'main' of https://github.com/InseeFrLab/keycloakify into main 2021-03-26 14:02:46 +01:00
e5b2096d65 Bump version (changelog ignore) 2021-03-26 14:02:40 +01:00
3aa140335f Handle <style> tag, improve documentation 2021-03-26 14:02:14 +01:00
4cafaa2492 Update changelog v0.3.9 2021-03-25 11:54:13 +00:00
9c633a7521 Update readme 2021-03-25 12:48:40 +01:00
e27845ba91 Merge branch 'main' of https://github.com/InseeFrLab/keycloakify into main 2021-03-25 12:01:15 +01:00
2a8708a45b Document --external-assets 2021-03-25 12:01:11 +01:00
6874fa4c24 Update README.md 2021-03-24 09:12:50 +01:00
ba531a4927 Update README.md 2021-03-24 09:12:04 +01:00
20175b57cf Update README.md 2021-03-24 09:10:44 +01:00
14 changed files with 265 additions and 218 deletions

View File

@ -1,3 +1,47 @@
### **0.3.19** (2021-04-01)
- Fix previous release
### **0.3.18** (2021-04-01)
- Fix error.ftt, Adopt best effort strategy to convert ftl values into JS
### **0.3.17** (2021-03-29)
- Use push instead of replace in keycloak-js adapter to enable going back
### **0.3.15** (2021-03-28)
- Remove all reference to --external-assets, broken feature
### **0.3.14** (2021-03-28)
- Fix standalone mode: imports from js
### **0.3.13** (2021-03-26)
### **0.3.12** (2021-03-26)
- Fix mocksContext
### **0.3.11** (2021-03-26)
- Fix previous build, improve README
### **0.3.10** (2021-03-26)
- Handle <style> tag, improve documentation
### **0.3.9** (2021-03-25)
- Update readme
- Document --external-assets
- Update README.md
- Update README.md
- Update README.md
### **0.3.8** (2021-03-22)
- Make standalone mode the default

View File

@ -20,8 +20,10 @@
The problem:
![keycloak_before](https://user-images.githubusercontent.com/6702424/108838381-dbbbcf80-75d3-11eb-8ae8-db41563ef9db.gif)
<p align="center">
<i>Without keycloakify:</i><br>
<img src="https://user-images.githubusercontent.com/6702424/108838381-dbbbcf80-75d3-11eb-8ae8-db41563ef9db.gif">
</p>
When we redirected to Keycloak the user suffers from a harsh context switch.
Keycloak does offer a way to customize theses pages but it requires a lot of raw HTML/CSS hacking
to reproduce the look and feel of a specific app. Not mentioning the maintenance cost of such an endeavour.
@ -29,14 +31,12 @@ to reproduce the look and feel of a specific app. Not mentioning the maintenance
Wouldn't it be great if we could just design the login and register pages as if they where part of our app?
Here is `yarn add keycloakify` for you 🍸
TODO: Insert video after.
<p align="center">
<i>With keycloakify:</i><br>
<img src="https://github.com/InseeFrLab/keycloakify/releases/download/v0.3.8/keycloakify_after.gif">
</p>
Tested with the following Keycloak versions:
- [11.0.3](https://hub.docker.com/layers/jboss/keycloak/11.0.3/images/sha256-4438f1e51c1369371cb807dffa526e1208086b3ebb9cab009830a178de949782?context=explore)
- [12.0.4](https://hub.docker.com/layers/jboss/keycloak/12.0.4/images/sha256-67e0c88e69bd0c7aef972c40bdeb558a974013a28b3668ca790ed63a04d70584?context=explore)
**Disclaimer**: This tool will be maintained to stay compatible with Keycloak v11 and up, however, the default pages you will get
(before you customize it) will always be the ones of the Keycloak v11.
**TL;DR**: [Here](https://github.com/garronej/keycloakify-demo-app) is a Hello World React project with Keycloakify set up.
- [Motivations](#motivations)
- [How to use](#how-to-use)
@ -46,37 +46,47 @@ Tested with the following Keycloak versions:
- [Changing the look **and** feel](#changing-the-look-and-feel)
- [Hot reload](#hot-reload)
- [GitHub Actions](#github-actions)
- [REQUIREMENTS](#requirements)
- [Requirements](#requirements)
- [Limitations](#limitations)
- [`process.env.PUBLIC_URL` not supported.](#processenvpublic_url-not-supported)
- [`@font-face` importing fonts from the `src/` dir](#font-face-importing-fonts-from-thesrc-dir)
- [Example of setup that **won't** work](#example-of-setup-that-wont-work)
- [Workarounds](#workarounds)
- [Implement context persistence (optional)](#implement-context-persistence-optional)
- [API Reference](#api-reference)
- [The build tool](#the-build-tool)
- [Implement context persistance (optional)](#implement-context-persistance-optional)
# How to use
## Setting up the build tool
Add `keycloakify` to the dev dependencies of your project `npm install --save-dev keycloakify` or `yarn add --dev keycloakify`
then configure your `package.json` build's script to build the keycloak's theme by adding `&& build-keycloak-theme`.
Typically you will get:
`package.json`
[`package.json`](https://github.com/garronej/keycloakify-demo-app/blob/main/package.json)
```json
"devDependencies": {
"homepage": "https://URL.OF/YOUR-APP"
"dependencies": {
"keycloakify": "^0.0.10"
},
"scripts": {
"keycloak": "yarn build && build-keycloak-theme",
},
```
`"homepage"` must be specified only if the url path is not `/`
(Onl `/YOUR-APP` matters `URL.OF` don't have to be the actual domain)
Then run `yarn keycloak` or `npm run keycloak`, you will be provided with instructions
about how to load the theme into Keycloak.
It is mandatory that you specify the url where your app will be available
using the `homepage` field.
Once you've edited your `package.json` you can install your new
dependency with `yarn install` and build the keycloak theme with
`yarn keycloak`.
Once the build is complete instructions about how to load
the theme into Keycloak are printed in the console.
## Developing your login and register pages in your React app
### Just changing the look
The fist approach is to only arr/replace the default class names by your
The first approach is to only arr/replace the default class names by your
own.
```tsx
@ -159,30 +169,50 @@ Checkout [this concrete example](https://github.com/garronej/keycloakify-demo-ap
[Here is a demo repo](https://github.com/garronej/keycloakify-demo-app) to show how to automate
the building and publishing of the theme (the .jar file).
# REQUIREMENTS
# Requirements
Tested with the following Keycloak versions:
- [11.0.3](https://hub.docker.com/layers/jboss/keycloak/11.0.3/images/sha256-4438f1e51c1369371cb807dffa526e1208086b3ebb9cab009830a178de949782?context=explore)
- [12.0.4](https://hub.docker.com/layers/jboss/keycloak/12.0.4/images/sha256-67e0c88e69bd0c7aef972c40bdeb558a974013a28b3668ca790ed63a04d70584?context=explore)
This tool will be maintained to stay compatible with Keycloak v11 and up, however, the default pages you will get
(before you customize it) will always be the ones of the Keycloak v11.
This tools assumes you are bundling your app with Webpack (tested with 4.44.2) .
It assumes there is a `build/` directory at the root of your react project directory containing a `index.html` file
and a `static/` directory generated by webpack.
and a `build/static/` directory generated by webpack.
**All this is defaults with [`create-react-app`](https://create-react-app.dev)** (tested with 4.0.3=)
- For building the theme: `mvn` (Maven) must be installed
- For development, (testing the theme in a local container ): `rm`, `mkdir`, `wget`, `unzip` are assumed to be available
- For development (testing the theme in a local container ): `rm`, `mkdir`, `wget`, `unzip` are assumed to be available
and `docker` up and running.
NOTE: This build tool has only be tested on MacOS.
# API Reference
# Limitations
## The build tool
## `process.env.PUBLIC_URL` not supported.
Part of the lib that runs with node, at build time.
You won't be able to [import things from your public directory in your JavaScript code](https://create-react-app.dev/docs/using-the-public-folder/#adding-assets-outside-of-the-module-system). (This isn't recommended anyway).
- `npx build-keycloak-theme`: Builds the theme, the CWD is assumed to be the root of your react project.
- `npx download-sample-keycloak-themes`: Downloads the keycloak default themes (for development purposes)
## `@font-face` importing fonts from the `src/` dir
### Example of setup that **won't** work
# Implement context persistance (optional)
- We have a `fonts/` directory in `src/`
- We import the font like this [`src: url("/fonts/my-font.woff2") format("woff2");`(https://github.com/garronej/keycloakify-demo-app/blob/07d54a3012ef354ee12b1374c6f7ad1cb125d56b/src/fonts.scss#L4) in a `.scss` a file.
### Workarounds
If it is possible, use Google Fonts or any other font provider.
If you want to host your font recommended approach is to move your fonts into the `public`
directory and to place your `@font-face` statements in the `public/index.html`.
Example [here]().
You can also [use your explicit url](https://github.com/garronej/keycloakify-demo-app/blob/2de8a9eb6f5de9c94f9cd3991faad0377e63268c/src/fonts.scss#L16) but don't forget [`Access-Control-Allow-Origin`](https://github.com/garronej/keycloakify-demo-app/blob/2de8a9eb6f5de9c94f9cd3991faad0377e63268c/nginx.conf#L17-L19).
# Implement context persistence (optional)
If, before logging in, a user has selected a specific language
you don't want it to be reset to default when the user gets redirected to
@ -240,3 +270,12 @@ keycloakInstance.init({
If you really want to go the extra miles and avoid having the white
flash of the blank html before the js bundle have been evaluated
[here is a snippet](https://github.com/InseeFrLab/onyxia-ui/blob/a77eb502870cfe6878edd0d956c646d28746d053/public/index.html#L5-L54) that you can place in your `public/index.html` if you are using `powerhooks/useGlobalState`.
# API Reference
## The build tool
Part of the lib that runs with node, at build time.
- `npx build-keycloak-theme`: Builds the theme, the CWD is assumed to be the root of your react project.
- `npx download-sample-keycloak-themes`: Downloads the keycloak default themes (for development purposes)

View File

@ -1,6 +1,6 @@
{
"name": "keycloakify",
"version": "0.3.8",
"version": "0.3.19",
"description": "Keycloak theme generator for Reacts app",
"repository": {
"type": "git",
@ -14,8 +14,7 @@
"grant-exec-perms": "node dist/bin/tools/grant-exec-perms.js",
"test": "node dist/test",
"copy-files": "copyfiles -u 1 src/**/*.ftl src/**/*.xml src/**/*.js dist/",
"generate-messages": "node dist/bin/generate-i18n-messages.js",
"watch": "tsc -w"
"generate-messages": "node dist/bin/generate-i18n-messages.js"
},
"bin": {
"build-keycloak-theme": "dist/bin/build-keycloak-theme/index.js",

View File

@ -1,17 +1,27 @@
<script>const _=
{
"url": {
"loginAction": "${url.loginAction?no_esc}",
"resourcesPath": "${url.resourcesPath?no_esc}",
"resourcesCommonPath": "${url.resourcesCommonPath?no_esc}",
"loginRestartFlowUrl": "${url.loginRestartFlowUrl?no_esc}",
"loginUrl": "${url.loginUrl?no_esc}"
},
"url": (function (){
<#if url?has_content>
return {
"loginAction": "${(url.loginAction!'')?no_esc}",
"resourcesPath": "${(url.resourcesPath!'')?no_esc}",
"resourcesCommonPath": "${(url.resourcesCommonPath!'')?no_esc}",
"loginRestartFlowUrl": "${(url.loginRestartFlowUrl!'')?no_esc}",
"loginUrl": "${(url.loginUrl!'')?no_esc}"
};
</#if>
return undefined;
})(),
"realm": {
"displayName": "${realm.displayName!''}" || undefined,
"displayNameHtml": "${realm.displayNameHtml!''}" || undefined,
"internationalizationEnabled": ${realm.internationalizationEnabled?c},
"registrationEmailAsUsername": ${realm.registrationEmailAsUsername?c},
"internationalizationEnabled": ${(realm.internationalizationEnabled!false)?c},
"registrationEmailAsUsername": ${(realm.registrationEmailAsUsername!false)?c},
},
"locale": (function (){
@ -20,23 +30,17 @@
return {
"supported": (function(){
<#if realm.internationalizationEnabled>
var out= [];
var out= [];
<#list locale.supported as lng>
out.push({
"url": "${lng.url?no_esc}",
"label": "${lng.label}",
"languageTag": "${lng.languageTag}"
});
</#list>
<#list locale.supported as lng>
out.push({
"url": "${lng.url?no_esc}",
"label": "${lng.label}",
"languageTag": "${lng.languageTag}"
});
</#list>
return out;
</#if>
return undefined;
return out;
})(),
"current": "${locale.current}"
@ -46,10 +50,10 @@
return undefined;
})(),
"auth": (function (){
<#if auth?has_content>
var out= {
@ -71,9 +75,9 @@
</#if>
return undefined;
})(),
"scripts": (function(){

View File

@ -2,7 +2,8 @@
import cheerio from "cheerio";
import {
replaceImportFromStaticInJsCode,
replaceImportsFromStaticInJsCode,
replaceImportsInInlineCssCode,
generateCssCodeToDefineGlobals
} from "../replaceImportFromStatic";
import fs from "fs";
@ -18,7 +19,7 @@ function loadAdjacentFile(fileBasename: string) {
.toString("utf8");
};
function loadFtlFile(ftlFileBasename: PageId | "template.ftl") {
function loadFtlFile(ftlFileBasename: PageId | "common.ftl") {
try {
return loadAdjacentFile(ftlFileBasename)
@ -29,40 +30,42 @@ function loadFtlFile(ftlFileBasename: PageId | "template.ftl") {
}
}
export type Mode = {
type: "standalone";
urlPathname: string;
} | {
type: "external assets";
urlPathname: string;
urlOrigin: string;
}
export function generateFtlFilesCodeFactory(
params: {
ftlValuesGlobalName: string;
cssGlobalsToDefine: Record<string, string>;
indexHtmlCode: string;
mode: Mode;
urlPathname: string;
}
) {
const { ftlValuesGlobalName, cssGlobalsToDefine, indexHtmlCode, mode } = params;
const { ftlValuesGlobalName, cssGlobalsToDefine, indexHtmlCode, urlPathname } = params;
const $ = cheerio.load(indexHtmlCode);
$("script:not([src])").each((...[, element]) => {
const { fixedJsCode } = replaceImportFromStaticInJsCode({
const { fixedJsCode } = replaceImportsFromStaticInJsCode({
ftlValuesGlobalName,
"jsCode": $(element).html()!,
mode
"jsCode": $(element).html()!
});
$(element).text(fixedJsCode);
});
$("style").each((...[, element]) => {
const { fixedCssCode } = replaceImportsInInlineCssCode({
"cssCode": $(element).html()!,
"urlPathname": params.urlPathname
});
$(element).text(fixedCssCode);
});
([
["link", "href"],
["script", "src"],
@ -75,31 +78,20 @@ export function generateFtlFilesCodeFactory(
return;
}
switch (mode.type) {
case "external assets":
$(element).attr(
attrName,
href.replace(/^\//, `${mode.urlOrigin}/`)
);
break;
case "standalone":
$(element).attr(
attrName,
href.replace(
new RegExp(`^${mode.urlPathname.replace(/\//g, "\\/")}`),
"${url.resourcesPath}/build/"
)
);
break;
}
$(element).attr(
attrName,
href.replace(
new RegExp(`^${urlPathname.replace(/\//g, "\\/")}`),
"${url.resourcesPath}/build/"
)
);
})
);
//FTL is no valid html, we can't insert with cheerio, we put placeholder for injecting later.
const ftlCommonPlaceholders = {
'{ "x": "vIdLqMeOed9sdLdIdOxdK0d" }': loadFtlFile("template.ftl"),
'{ "x": "vIdLqMeOed9sdLdIdOxdK0d" }': loadFtlFile("common.ftl"),
'<!-- xIdLqMeOedErIdLsPdNdI9dSlxI -->':
[
'<#if scripts??>',
@ -119,7 +111,7 @@ export function generateFtlFilesCodeFactory(
'<style>',
generateCssCodeToDefineGlobals({
cssGlobalsToDefine,
"urlPathname": mode.urlPathname
urlPathname
}).cssCodeToPrependInHead,
'</style>',
''

View File

@ -3,10 +3,10 @@ import { transformCodebase } from "../tools/transformCodebase";
import * as fs from "fs";
import { join as pathJoin } from "path";
import {
replaceImportFromStaticInCssCode,
replaceImportFromStaticInJsCode
replaceImportsInCssCode,
replaceImportsFromStaticInJsCode
} from "./replaceImportFromStatic";
import { generateFtlFilesCodeFactory, pageIds, Mode } from "./generateFtl";
import { generateFtlFilesCodeFactory, pageIds } from "./generateFtl";
import { builtinThemesUrl } from "../install-builtin-keycloak-themes";
import { downloadAndUnzip } from "../tools/downloadAndUnzip";
import * as child_process from "child_process";
@ -20,11 +20,11 @@ export function generateKeycloakThemeResources(
themeName: string;
reactAppBuildDirPath: string;
keycloakThemeBuildingDirPath: string;
mode: Mode;
urlPathname: string;
}
) {
const { themeName, reactAppBuildDirPath, keycloakThemeBuildingDirPath, mode } = params;
const { themeName, reactAppBuildDirPath, keycloakThemeBuildingDirPath, urlPathname } = params;
const themeDirPath = pathJoin(keycloakThemeBuildingDirPath, "src", "main", "resources", "theme", themeName, "login");
@ -45,34 +45,31 @@ export function generateKeycloakThemeResources(
return undefined;
}
if (mode.type === "standalone") {
if (/\.css?$/i.test(filePath)) {
if (/\.css?$/i.test(filePath)) {
const { cssGlobalsToDefine, fixedCssCode } = replaceImportFromStaticInCssCode(
{ "cssCode": sourceCode.toString("utf8") }
);
const { cssGlobalsToDefine, fixedCssCode } = replaceImportsInCssCode(
{ "cssCode": sourceCode.toString("utf8") }
);
allCssGlobalsToDefine = {
...allCssGlobalsToDefine,
...cssGlobalsToDefine
};
allCssGlobalsToDefine = {
...allCssGlobalsToDefine,
...cssGlobalsToDefine
};
return { "modifiedSourceCode": Buffer.from(fixedCssCode, "utf8") };
return { "modifiedSourceCode": Buffer.from(fixedCssCode, "utf8") };
}
}
if (/\.js?$/i.test(filePath)) {
const { fixedJsCode } = replaceImportFromStaticInJsCode({
"jsCode": sourceCode.toString("utf8"),
ftlValuesGlobalName,
mode
});
if (/\.js?$/i.test(filePath)) {
return { "modifiedSourceCode": Buffer.from(fixedJsCode, "utf8") };
const { fixedJsCode } = replaceImportsFromStaticInJsCode({
"jsCode": sourceCode.toString("utf8"),
ftlValuesGlobalName
});
}
return { "modifiedSourceCode": Buffer.from(fixedJsCode, "utf8") };
}
@ -87,7 +84,7 @@ export function generateKeycloakThemeResources(
"indexHtmlCode": fs.readFileSync(
pathJoin(reactAppBuildDirPath, "index.html")
).toString("utf8"),
mode
urlPathname
});
pageIds.forEach(pageId => {

View File

@ -11,8 +11,6 @@ import { URL } from "url";
const reactProjectDirPath = process.cwd();
const doUseExternalAssets = process.argv[2]?.toLowerCase() === "--external-assets";
const parsedPackageJson: ParsedPackageJson = require(pathJoin(reactProjectDirPath, "package.json"));
export const keycloakThemeBuildingDirPath = pathJoin(reactProjectDirPath, "build_keycloak");
@ -26,8 +24,7 @@ if (require.main === module) {
keycloakThemeBuildingDirPath,
"reactAppBuildDirPath": pathJoin(reactProjectDirPath, "build"),
"themeName": parsedPackageJson.name,
"mode": (() => {
"urlPathname": (() => {
const url = (() => {
@ -39,37 +36,12 @@ if (require.main === module) {
})();
const urlPathname =
url === undefined ?
"/" :
url.pathname.replace(/([^/])$/, "$1/");
return url === undefined ?
"/" :
url.pathname.replace(/([^/])$/, "$1/");
return !doUseExternalAssets ?
{
"type": "standalone",
urlPathname
} as const
:
{
"type": "external assets",
urlPathname,
"urlOrigin": (() => {
if (url === undefined) {
console.error("ERROR: You must specify 'homepage' in your package.json");
process.exit(-1);
}
return url.origin;
})()
} as const;
})()
});
const { jarFilePath } = generateJavaStackFiles({

View File

@ -1,45 +1,45 @@
import * as crypto from "crypto";
type Mode = {
type: "standalone";
} | {
type: "external assets";
urlOrigin: string;
urlPathname: string;
}
export function replaceImportFromStaticInJsCode(
export function replaceImportsFromStaticInJsCode(
params: {
ftlValuesGlobalName: string;
jsCode: string;
mode: Mode;
}
): { fixedJsCode: string; } {
const { jsCode, ftlValuesGlobalName, mode } = params;
const fixedJsCode = (() => {
switch (mode.type) {
case "standalone":
return jsCode!.replace(
/[a-z]+\.[a-z]+\+"static\//g,
`window.${ftlValuesGlobalName}.url.resourcesPath + "/build/static/`
);
case "external assets":
return jsCode!.replace(
/[a-z]+\.[a-z]+\+"static\//g,
`"${mode.urlOrigin}${mode.urlPathname}static/`
);
}
})();
const { jsCode, ftlValuesGlobalName } = params;
const fixedJsCode = jsCode.replace(
/[a-z]+\.[a-z]+\+"static\//g,
`window.${ftlValuesGlobalName}.url.resourcesPath + "/build/static/`
);
return { fixedJsCode };
}
export function replaceImportFromStaticInCssCode(
export function replaceImportsInInlineCssCode(
params: {
cssCode: string;
urlPathname: string;
}
): { fixedCssCode: string; } {
const { cssCode, urlPathname } = params;
const fixedCssCode = cssCode.replace(
urlPathname === "/" ?
/url\(\/([^/][^)]+)\)/g :
new RegExp(`url\\(${urlPathname}([^)]+)\\)`, "g"),
(...[, group]) => `url(${"${url.resourcesPath}/build/" + group})`
);
return { fixedCssCode };
}
export function replaceImportsInCssCode(
params: {
cssCode: string;
}
@ -52,7 +52,7 @@ export function replaceImportFromStaticInCssCode(
const cssGlobalsToDefine: Record<string, string> = {};
new Set(cssCode.match(/url\(\/[^)]+\)[^;}]*/g) ?? [])
new Set(cssCode.match(/url\(\/[^/][^)]+\)[^;}]*/g) ?? [])
.forEach(match =>
cssGlobalsToDefine[
"url" + crypto

View File

@ -25,7 +25,7 @@ export type TemplateProps = {
showUsernameNode?: ReactNode;
formNode: ReactNode;
infoNode?: ReactNode;
} & { kcContext: KcContext.Template; } & KcTemplateProps;
} & { kcContext: KcContext; } & KcTemplateProps;
export const Template = memo((props: TemplateProps) => {

View File

@ -10,12 +10,17 @@ import type { LanguageLabel } from "./i18n/KcLanguageTag";
type ExtractAfterStartingWith<Prefix extends string, StrEnum> =
StrEnum extends `${Prefix}${infer U}` ? U : never;
/** Take theses type definition with a grain of salt.
* Some values might be undefined on some pages.
* (ex: url.loginAction is undefined on error.ftl)
*/
export type KcContext =
KcContext.Login | KcContext.Register | KcContext.Info |
KcContext.Error | KcContext.LoginResetPassword | KcContext.LoginVerifyEmail;
export declare namespace KcContext {
export type Template = {
export type Common = {
url: {
loginAction: string;
resourcesPath: string;
@ -27,7 +32,7 @@ export declare namespace KcContext {
displayName?: string;
displayNameHtml?: string;
internationalizationEnabled: boolean;
registrationEmailAsUsername: boolean; //<---
registrationEmailAsUsername: boolean;
};
/** Undefined if !realm.internationalizationEnabled */
locale?: {
@ -55,7 +60,7 @@ export declare namespace KcContext {
isAppInitiatedAction: boolean;
};
export type Login = Template & {
export type Login = Common & {
pageId: "login.ftl";
url: {
loginResetCredentialsUrl: string;
@ -88,7 +93,7 @@ export declare namespace KcContext {
};
};
export type Register = Template & {
export type Register = Common & {
pageId: "register.ftl";
url: {
registrationAction: string;
@ -120,7 +125,7 @@ export declare namespace KcContext {
recaptchaSiteKey?: string;
};
export type Info = Template & {
export type Info = Common & {
pageId: "info.ftl";
messageHeader?: string;
requiredActions?: ExtractAfterStartingWith<"requiredAction.", MessageKey>[];
@ -132,21 +137,21 @@ export declare namespace KcContext {
}
};
export type Error = Template & {
export type Error = Common & {
pageId: "error.ftl";
client?: {
baseUrl?: string;
}
};
export type LoginResetPassword = Template & {
export type LoginResetPassword = Common & {
pageId: "login-reset-password.ftl";
realm: {
loginWithEmailAllowed: boolean;
}
};
export type LoginVerifyEmail = Template & {
export type LoginVerifyEmail = Common & {
pageId: "login-verify-email.ftl";
};

View File

@ -6,11 +6,11 @@ import { getKcLanguageTagLabel } from "../i18n/KcLanguageTag";
//NOTE: Aside because we want to be able to import them from node
import { resourcesCommonPath, resourcesPath } from "./urlResourcesPath";
export const kcTemplateContext: KcContext.Template = {
const kcCommonContext: KcContext.Common = {
"url": {
"loginAction": "#",
"resourcesPath": "/" + resourcesPath,
"resourcesCommonPath": "/" + resourcesCommonPath,
"resourcesPath": `${process.env["PUBLIC_URL"]}/${resourcesPath}`,
"resourcesCommonPath": `${process.env["PUBLIC_URL"]}/${resourcesCommonPath}`,
"loginRestartFlowUrl": "/auth/realms/myrealm/login-actions/restart?client_id=account&tab_id=HoAx28ja4xg",
"loginUrl": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg",
},
@ -111,7 +111,7 @@ export const kcTemplateContext: KcContext.Template = {
};
Object.defineProperty(
kcTemplateContext.locale!,
kcCommonContext.locale!,
"current",
{
"get": () => getKcLanguageTagLabel(getEvtKcLanguage().state),
@ -120,22 +120,22 @@ Object.defineProperty(
);
export const kcLoginContext: KcContext.Login = {
...kcTemplateContext,
...kcCommonContext,
"pageId": "login.ftl",
"url": {
...kcTemplateContext.url,
...kcCommonContext.url,
"loginResetCredentialsUrl": "/auth/realms/myrealm/login-actions/reset-credentials?client_id=account&tab_id=HoAx28ja4xg",
"registrationUrl": "/auth/realms/myrealm/login-actions/registration?client_id=account&tab_id=HoAx28ja4xg"
},
"realm": {
...kcTemplateContext.realm,
...kcCommonContext.realm,
"loginWithEmailAllowed": true,
"rememberMe": true,
"password": true,
"resetPasswordAllowed": true,
"registrationAllowed": true
},
"auth": kcTemplateContext.auth!,
"auth": kcCommonContext.auth!,
"social": {
"displayInfo": true
},
@ -147,7 +147,7 @@ export const kcLoginContext: KcContext.Login = {
};
export const kcRegisterContext: KcContext.Register = {
...kcTemplateContext,
...kcCommonContext,
"url": {
...kcLoginContext.url,
"registrationAction": "http://localhost:8080/auth/realms/myrealm/login-actions/registration?session_code=gwZdUeO7pbYpFTRxiIxRg_QtzMbtFTKrNu6XW_f8asM&execution=12146ce0-b139-4bbd-b25b-0eccfee6577e&client_id=account&tab_id=uS8lYfebLa0"
@ -166,7 +166,7 @@ export const kcRegisterContext: KcContext.Register = {
};
export const kcInfoContext: KcContext.Info ={
...kcTemplateContext,
...kcCommonContext,
"pageId": "info.ftl",
"messageHeader": "<Message header>",
"requiredActions": undefined,
@ -178,7 +178,7 @@ export const kcInfoContext: KcContext.Info ={
};
export const kcErrorContext: KcContext.Error = {
...kcTemplateContext,
...kcCommonContext,
"pageId": "error.ftl",
"client": {
"baseUrl": "#"
@ -186,16 +186,16 @@ export const kcErrorContext: KcContext.Error = {
};
export const kcLoginResetPasswordContext: KcContext.LoginResetPassword = {
...kcTemplateContext,
...kcCommonContext,
"pageId": "login-reset-password.ftl",
"realm":{
...kcTemplateContext.realm,
...kcCommonContext.realm,
"loginWithEmailAllowed": false
}
};
export const kcLoginVerifyEmailContext: KcContext.LoginVerifyEmail = {
...kcTemplateContext,
...kcCommonContext,
"pageId": "login-verify-email.ftl"
};

View File

@ -66,13 +66,12 @@ export function createKeycloakAdapter(
return {
"login": options => {
window.location.replace(
window.location.href=
transformUrlBeforeRedirect(
keycloakInstance.createLoginUrl(
options
)
)
);
);
return neverResolvingPromise;
},
"logout": options => {
@ -86,13 +85,13 @@ export function createKeycloakAdapter(
return neverResolvingPromise;
},
"register": options => {
window.location.replace(
window.location.href =
transformUrlBeforeRedirect(
keycloakInstance.createRegisterUrl(
options
)
)
);
);
return neverResolvingPromise;
},
"accountManagement": () => {

View File

@ -12,9 +12,6 @@ generateKeycloakThemeResources({
"themeName": "keycloakify-demo-app",
"reactAppBuildDirPath": pathJoin(sampleReactProjectDirPath, "build"),
"keycloakThemeBuildingDirPath": pathJoin(sampleReactProjectDirPath, "build_keycloak_theme"),
"mode": {
"type": "standalone",
"urlPathname": "/keycloakify-demo-app/"
}
"urlPathname": "/keycloakify-demo-app/"
});

View File

@ -1,11 +1,11 @@
import { 
replaceImportFromStaticInJsCode,
replaceImportFromStaticInCssCode,
replaceImportsFromStaticInJsCode,
replaceImportsInCssCode,
generateCssCodeToDefineGlobals
} from "../bin/build-keycloak-theme/replaceImportFromStatic";
const { fixedJsCode } = replaceImportFromStaticInJsCode({
const { fixedJsCode } = replaceImportsFromStaticInJsCode({
"ftlValuesGlobalName": "keycloakFtlValues",
"jsCode": `
function f() {
@ -19,13 +19,12 @@ const { fixedJsCode } = replaceImportFromStaticInJsCode({
3: "0664cdc0"
}[e] + ".chunk.js"
}
`,
"mode": { "type": "standalone" }
`
});
console.log({ fixedJsCode });
const { fixedCssCode, cssGlobalsToDefine } = replaceImportFromStaticInCssCode({
const { fixedCssCode, cssGlobalsToDefine } = replaceImportsInCssCode({
"cssCode": `
.my-div {