Compare commits
67 Commits
v4.2.14-be
...
v4.5.1
Author | SHA1 | Date | |
---|---|---|---|
7a3c74020d | |||
7509170dd0 | |||
cd17a97916 | |||
d5e690f964 | |||
a19bd20b6b | |||
f78526dfff | |||
11d6a2020f | |||
fabd48a22c | |||
e2ea98b5ef | |||
4473ab0704 | |||
1f68cc305a | |||
ec2543551f | |||
7b0bedc755 | |||
ab054ca515 | |||
1b49c7804c | |||
764a288b1a | |||
fc6910bc2c | |||
91dd1dcddc | |||
97e6aaca65 | |||
af5ff1ecfb | |||
c9b53b0d3a | |||
d05a62e1ea | |||
a83eec31d8 | |||
729503fe31 | |||
7137ff4257 | |||
6db11a7433 | |||
8666aa62dd | |||
eedcd7a2a6 | |||
e3e8fb663a | |||
6e663210ee | |||
42cd0fe2f0 | |||
daac05c1ad | |||
1d63c393a3 | |||
e8a3751b32 | |||
cb8a41d5be | |||
a8d4f7e23c | |||
93bcdac3be | |||
32d0388556 | |||
633a32ffd6 | |||
cb8b165c8e | |||
57134359b9 | |||
377436e46a | |||
51129aaeff | |||
a2bd5050ff | |||
128c416ce7 | |||
7184773521 | |||
1138313028 | |||
46bd319ebe | |||
cfcc48259c | |||
785ce7a8ab | |||
ad5de216b0 | |||
26b80d6af7 | |||
a8623d8066 | |||
86ab9f72a5 | |||
b3892dab8d | |||
57a5d034dd | |||
cee9569581 | |||
159429da6e | |||
a292cb0b4b | |||
d70985d8d2 | |||
484f95f5d2 | |||
6e0553af9b | |||
cb18d3d765 | |||
f316f38ae5 | |||
5f07cb374b | |||
96d31e07c3 | |||
99a5efe36c |
2
.gitignore
vendored
2
.gitignore
vendored
@ -44,3 +44,5 @@ jspm_packages
|
|||||||
|
|
||||||
/sample_react_project/
|
/sample_react_project/
|
||||||
/.yarn_home/
|
/.yarn_home/
|
||||||
|
|
||||||
|
.idea
|
||||||
|
69
CHANGELOG.md
69
CHANGELOG.md
@ -1,3 +1,72 @@
|
|||||||
|
### **4.5.1** (2022-01-18)
|
||||||
|
|
||||||
|
- fix previous version
|
||||||
|
|
||||||
|
## **4.5.0** (2022-01-18)
|
||||||
|
|
||||||
|
- Read public/CNAME for domain name in --externel-assets mode
|
||||||
|
|
||||||
|
## **4.4.0** (2022-01-01)
|
||||||
|
|
||||||
|
- Merge pull request #73 from lazToum/main
|
||||||
|
|
||||||
|
(feature) added login-page-expired.ftl
|
||||||
|
- added login-page-expired.ftl
|
||||||
|
- Add update instruction for 4.3.0
|
||||||
|
|
||||||
|
## **4.3.0** (2021-12-27)
|
||||||
|
|
||||||
|
- Merge pull request #72 from praiz/main
|
||||||
|
|
||||||
|
feat(*): added login-update-password
|
||||||
|
- feat(*): added login-update-password
|
||||||
|
|
||||||
|
### **4.2.21** (2021-12-27)
|
||||||
|
|
||||||
|
- update dependencies
|
||||||
|
|
||||||
|
### **4.2.19** (2021-12-21)
|
||||||
|
|
||||||
|
- Merge pull request #70 from VBustamante/patch-1
|
||||||
|
- Added realm name field to KcContext mocks object
|
||||||
|
- Merge pull request #69 from VBustamante/patch-1
|
||||||
|
|
||||||
|
Adding name field to realm in KcContext type
|
||||||
|
- Adding name field to realm in KcContext type
|
||||||
|
|
||||||
|
### **4.2.18** (2021-12-17)
|
||||||
|
|
||||||
|
- Improve css url() import (fix CRA 5)
|
||||||
|
|
||||||
|
### **4.2.17** (2021-12-16)
|
||||||
|
|
||||||
|
- Fix path.join polyfill
|
||||||
|
|
||||||
|
### **4.2.16** (2021-12-16)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### **4.2.15** (2021-12-16)
|
||||||
|
|
||||||
|
- use custom polyfill for path.join (fix webpack 5 build)
|
||||||
|
|
||||||
|
### **4.2.14** (2021-12-12)
|
||||||
|
|
||||||
|
- Merge pull request #65 from InseeFrLab/doge_ftl_errors
|
||||||
|
|
||||||
|
Prevent ftl errors in Keycloak log
|
||||||
|
- Encourage users to report errors in logs
|
||||||
|
- Fix ftl error related to url.loginAction in saml-post-form.ftl
|
||||||
|
- Ftl prevent error with updateProfileCtx
|
||||||
|
- Ftl prevent error with auth.attemptedUsername
|
||||||
|
- Fix ftl error as comment formatting
|
||||||
|
- Merge remote-tracking branch 'origin/main' into doge_ftl_errors
|
||||||
|
- Update README, remove all instruction about errors in logs
|
||||||
|
- Avoid error in Keycloak logs, fix long template loading time
|
||||||
|
- Add missing collon in README sample code
|
||||||
|
|
||||||
|
Add miss ','
|
||||||
|
|
||||||
### **4.2.13** (2021-12-08)
|
### **4.2.13** (2021-12-08)
|
||||||
|
|
||||||
- Fix broken link about how to import fonts #62
|
- Fix broken link about how to import fonts #62
|
||||||
|
111
README.md
111
README.md
@ -5,11 +5,21 @@
|
|||||||
<i>🔏 Create Keycloak themes using React 🔏</i>
|
<i>🔏 Create Keycloak themes using React 🔏</i>
|
||||||
<br>
|
<br>
|
||||||
<br>
|
<br>
|
||||||
<img src="https://github.com/garronej/keycloakify/workflows/ci/badge.svg?branch=develop">
|
<a href="https://github.com/garronej/keycloakify/actions">
|
||||||
<img src="https://img.shields.io/bundlephobia/minzip/keycloakify">
|
<img src="https://github.com/garronej/keycloakify/workflows/ci/badge.svg?branch=main">
|
||||||
<img src="https://img.shields.io/npm/dw/keycloakify">
|
</a>
|
||||||
<img src="https://img.shields.io/npm/l/keycloakify">
|
<a href="https://bundlephobia.com/package/keycloakify">
|
||||||
<img src="https://camo.githubusercontent.com/0f9fcc0ac1b8617ad4989364f60f78b2d6b32985ad6a508f215f14d8f897b8d3/68747470733a2f2f62616467656e2e6e65742f62616467652f547970655363726970742f7374726963742532302546302539462539322541412f626c7565">
|
<img src="https://img.shields.io/bundlephobia/minzip/keycloakify">
|
||||||
|
</a>
|
||||||
|
<a href="https://www.npmjs.com/package/keycloakify">
|
||||||
|
<img src="https://img.shields.io/npm/dw/keycloakify">
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/garronej/keycloakify/blob/main/LICENSE">
|
||||||
|
<img src="https://img.shields.io/npm/l/keycloakify">
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/InseeFrLab/keycloakify/blob/729503fe31a155a823f46dd66ad4ff34ca274e0a/tsconfig.json#L14">
|
||||||
|
<img src="https://camo.githubusercontent.com/0f9fcc0ac1b8617ad4989364f60f78b2d6b32985ad6a508f215f14d8f897b8d3/68747470733a2f2f62616467656e2e6e65742f62616467652f547970655363726970742f7374726963742532302546302539462539322541412f626c7565">
|
||||||
|
</a>
|
||||||
<a href="https://github.com/thomasdarimont/awesome-keycloak">
|
<a href="https://github.com/thomasdarimont/awesome-keycloak">
|
||||||
<img src="https://awesome.re/mentioned-badge.svg"/>
|
<img src="https://awesome.re/mentioned-badge.svg"/>
|
||||||
</a>
|
</a>
|
||||||
@ -20,10 +30,9 @@
|
|||||||
<img src="https://user-images.githubusercontent.com/6702424/110260457-a1c3d380-7fac-11eb-853a-80459b65626b.png">
|
<img src="https://user-images.githubusercontent.com/6702424/110260457-a1c3d380-7fac-11eb-853a-80459b65626b.png">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
**NEW in v4**
|
> **New in v4.4.0**: Feature [`login-page-expired.ftl`](https://user-images.githubusercontent.com/6702424/147856832-38c042a7-9fc8-473f-9595-e00123095ca6.png).
|
||||||
|
> Every time a page is added it's a breaking change for non CSS-only theme.
|
||||||
- Out of the box [frontend form validation](#user-profile-and-frontend-form-validation) 🥳
|
> Change [this](https://github.com/garronej/keycloakify-demo-app/blob/812754109c61157741f4a0b222026deb1538a02d/src/KcApp/KcApp.tsx#L18) and [this](https://github.com/garronej/keycloakify-demo-app/blob/812754109c61157741f4a0b222026deb1538a02d/src/KcApp/KcApp.tsx#L39) to update.
|
||||||
- Improvements (and breaking changes in `import { useKcMessage } from "keycloakify"`.
|
|
||||||
|
|
||||||
# Motivations
|
# Motivations
|
||||||
|
|
||||||
@ -71,13 +80,12 @@ If you already have a Keycloak custom theme, it can be easily ported to Keycloak
|
|||||||
- [GitHub Actions](#github-actions)
|
- [GitHub Actions](#github-actions)
|
||||||
- [Limitations](#limitations)
|
- [Limitations](#limitations)
|
||||||
- [`process.env.PUBLIC_URL` not supported.](#processenvpublic_url-not-supported)
|
- [`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)
|
- [`@font-face` importing fonts from the `src/` dir](#font-face-importing-fonts-from-the-src-dir)
|
||||||
- [Example of setup that **won't** work](#example-of-setup-that-wont-work)
|
- [Example of setup that **won't** work](#example-of-setup-that-wont-work)
|
||||||
- [Possible workarounds](#possible-workarounds)
|
- [Possible workarounds](#possible-workarounds)
|
||||||
- [Implement context persistence (optional)](#implement-context-persistence-optional)
|
- [Implement context persistence (optional)](#implement-context-persistence-optional)
|
||||||
- [Kickstart video](#kickstart-video)
|
- [Kickstart video](#kickstart-video)
|
||||||
- [About the errors related to `objectToJson` in Keycloak logs.](#about-the-errors-related-to-objecttojson-in-keycloak-logs)
|
- [FTL errors related to `ftl_object_to_js_code_declaring_an_object` in Keycloak logs.](#ftl-errors-related-to-ftl_object_to_js_code_declaring_an_object-in-keycloak-logs)
|
||||||
- [The pages take too long to load ?](#the-pages-take-too-long-to-load-)
|
|
||||||
- [Adding custom message (to `i18n/useKcMessage.tsx`)](#adding-custom-message-to-i18nusekcmessagetsx)
|
- [Adding custom message (to `i18n/useKcMessage.tsx`)](#adding-custom-message-to-i18nusekcmessagetsx)
|
||||||
- [Email domain whitelist](#email-domain-whitelist)
|
- [Email domain whitelist](#email-domain-whitelist)
|
||||||
- [Changelog highlights](#changelog-highlights)
|
- [Changelog highlights](#changelog-highlights)
|
||||||
@ -99,7 +107,7 @@ Tested with the following Keycloak versions:
|
|||||||
This tool will be maintained to stay compatible with Keycloak v11 and up, however, the default pages you will get
|
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 Keycloak v11.
|
(before you customize it) will always be the ones of Keycloak v11.
|
||||||
|
|
||||||
This tool assumes you are bundling your app with Webpack (tested with 4.44.2) .
|
This tool assumes you are bundling your app with Webpack (tested with the versions that ships with CRA v4.44.2 and v5.0.0) .
|
||||||
It assumes there is a `build/` directory at the root of your react project directory containing a `index.html` file
|
It assumes there is a `build/` directory at the root of your react project directory containing a `index.html` file
|
||||||
and a `build/static/` directory generated by webpack.
|
and a `build/static/` directory generated by webpack.
|
||||||
For more information see [this issue](https://github.com/InseeFrLab/keycloakify/issues/5#issuecomment-832296432)
|
For more information see [this issue](https://github.com/InseeFrLab/keycloakify/issues/5#issuecomment-832296432)
|
||||||
@ -107,7 +115,7 @@ For more information see [this issue](https://github.com/InseeFrLab/keycloakify/
|
|||||||
**All this is defaults with [`create-react-app`](https://create-react-app.dev)** (tested with 4.0.3)
|
**All this is defaults with [`create-react-app`](https://create-react-app.dev)** (tested with 4.0.3)
|
||||||
|
|
||||||
- `mvn` ([Maven](https://maven.apache.org/)), `rm`, `mkdir`, `curl`, `unzip` are assumed to be available.
|
- `mvn` ([Maven](https://maven.apache.org/)), `rm`, `mkdir`, `curl`, `unzip` are assumed to be available.
|
||||||
- `docker` must be up and running when running `yarn keycloak`.
|
- `docker` must be up and running when running `start_keycloak_testing_container.sh` (Instructions provided after running `yarn keycloak`).
|
||||||
|
|
||||||
## My framework doesn’t seem to be supported, what can I do?
|
## My framework doesn’t seem to be supported, what can I do?
|
||||||
|
|
||||||
@ -152,27 +160,23 @@ your index should look something like:
|
|||||||
`src/index.tsx`
|
`src/index.tsx`
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
import { App } from "./<wherever>/App";
|
import { App } from "./<wherever>/App";
|
||||||
import {
|
import { KcApp, defaultKcProps, getKcContext } from "keycloakify";
|
||||||
KcApp,
|
import { css } from "tss-react/@emotion/css";
|
||||||
defaultKcProps,
|
|
||||||
getKcContext
|
|
||||||
} from "keycloakify";
|
|
||||||
import { css } from "tss-react/@emotion/css";
|
|
||||||
|
|
||||||
const { kcContext } = getKcContext();
|
const { kcContext } = getKcContext();
|
||||||
|
|
||||||
const myClassName = css({ "color": "red" });
|
const myClassName = css({ "color": "red" });
|
||||||
|
|
||||||
reactDom.render(
|
reactDom.render(
|
||||||
<KcApp
|
<KcApp
|
||||||
kcContext={kcContext}
|
kcContext={kcContext}
|
||||||
{...{
|
{...{
|
||||||
...defaultKcProps,
|
...defaultKcProps,
|
||||||
"kcHeaderWrapperClass": myClassName
|
"kcHeaderWrapperClass": myClassName,
|
||||||
}}
|
}}
|
||||||
/>
|
/>,
|
||||||
document.getElementById("root")
|
document.getElementById("root"),
|
||||||
);
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -213,8 +217,8 @@ reactDom.render(
|
|||||||
<img src="https://user-images.githubusercontent.com/6702424/114326299-6892fc00-9b34-11eb-8d75-85696e55458f.png">
|
<img src="https://user-images.githubusercontent.com/6702424/114326299-6892fc00-9b34-11eb-8d75-85696e55458f.png">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
Example of a customization using only CSS: [here](https://github.com/InseeFrLab/onyxia-ui/blob/012639d62327a9a56be80c46e32c32c9497b82db/src/app/components/KcApp.tsx)
|
Example of a customization using only CSS: [here](https://github.com/InseeFrLab/onyxia-web/blob/012639d62327a9a56be80c46e32c32c9497b82db/src/app/components/KcApp.tsx)
|
||||||
(the [index.tsx](https://github.com/InseeFrLab/onyxia-ui/blob/012639d62327a9a56be80c46e32c32c9497b82db/src/app/index.tsx#L89-L94) )
|
(the [index.tsx](https://github.com/InseeFrLab/onyxia-web/blob/012639d62327a9a56be80c46e32c32c9497b82db/src/app/index.tsx#L89-L94) )
|
||||||
and the result you can expect:
|
and the result you can expect:
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
@ -229,7 +233,7 @@ If you want to go beyond only customizing the CSS you can re-implement some of t
|
|||||||
pages or even add new ones.
|
pages or even add new ones.
|
||||||
|
|
||||||
If you want to go this way checkout the demo setup provided [here](https://github.com/garronej/keycloakify-demo-app/tree/look_and_feel).
|
If you want to go this way checkout the demo setup provided [here](https://github.com/garronej/keycloakify-demo-app/tree/look_and_feel).
|
||||||
If you prefer a real life example you can checkout [onyxia-web's source](https://github.com/InseeFrLab/onyxia-web/tree/main/src/app/components/KcApp).
|
If you prefer a real life example you can checkout [onyxia-web's source](https://github.com/InseeFrLab/onyxia-web/tree/main/src/ui/components/KcApp).
|
||||||
The web app is in production [here](https://datalab.sspcloud.fr).
|
The web app is in production [here](https://datalab.sspcloud.fr).
|
||||||
|
|
||||||
Main takeaways are:
|
Main takeaways are:
|
||||||
@ -291,7 +295,7 @@ If you are specifically building a theme to integrate with an app or a website t
|
|||||||
to first browse unauthenticated before logging in, you will get a significant
|
to first browse unauthenticated before logging in, you will get a significant
|
||||||
performance boost if you jump through those hoops:
|
performance boost if you jump through those hoops:
|
||||||
|
|
||||||
- Provide the url of your app in the `homepage` field of package.json. [ex](https://github.com/garronej/keycloakify-demo-app/blob/7847cc70ef374ab26a6cc7953461cf25603e9a6d/package.json#L2)
|
- Provide the url of your app in the `homepage` field of package.json. [ex](https://github.com/garronej/keycloakify-demo-app/blob/7847cc70ef374ab26a6cc7953461cf25603e9a6d/package.json#L2) or in a `public/CNAME` file. [ex](https://github.com/garronej/keycloakify-demo-app/blob/main/public/CNAME).
|
||||||
- Build the theme using `npx build-keycloak-theme --external-assets` [ex](https://github.com/garronej/keycloakify-demo-app/blob/7847cc70ef374ab26a6cc7953461cf25603e9a6d/.github/workflows/ci.yaml#L21)
|
- Build the theme using `npx build-keycloak-theme --external-assets` [ex](https://github.com/garronej/keycloakify-demo-app/blob/7847cc70ef374ab26a6cc7953461cf25603e9a6d/.github/workflows/ci.yaml#L21)
|
||||||
- Enable [long-term assets caching](https://create-react-app.dev/docs/production-build/#static-file-caching) on the server hosting your app.
|
- Enable [long-term assets caching](https://create-react-app.dev/docs/production-build/#static-file-caching) on the server hosting your app.
|
||||||
- Make sure not to build your app and the keycloak theme separately
|
- Make sure not to build your app and the keycloak theme separately
|
||||||
@ -358,7 +362,7 @@ the building and publishing of the theme (the .jar file).
|
|||||||
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).
|
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).
|
(This isn't recommended anyway).
|
||||||
|
|
||||||
## `@font-face` importing fonts from the `src/` dir
|
## `@font-face` importing fonts from the `src/` dir
|
||||||
|
|
||||||
If you are building the theme with [--external-assets](#enable-loading-in-a-blink-of-a-eye-of-login-pages-)
|
If you are building the theme with [--external-assets](#enable-loading-in-a-blink-of-a-eye-of-login-pages-)
|
||||||
this limitation doesn't apply, you can import fonts however you see fit.
|
this limitation doesn't apply, you can import fonts however you see fit.
|
||||||
@ -435,42 +439,29 @@ keycloakInstance.init({
|
|||||||
|
|
||||||
If you really want to go the extra miles and avoid having the white
|
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
|
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`.
|
[here is a snippet](https://github.com/InseeFrLab/onyxia-web/blob/e1c1f309aaa3d5f860df39ba0b75cce89c88a9de/public/index.html#L117-L166) that you can place in your `public/index.html` if you are using `powerhooks/useGlobalState`.
|
||||||
|
|
||||||
# Kickstart video
|
# Kickstart video
|
||||||
|
|
||||||
_NOTE: keycloak-react-theming was renamed keycloakify since this video was recorded_
|
_NOTE: keycloak-react-theming was renamed keycloakify since this video was recorded_
|
||||||
[](https://youtu.be/xTz0Rj7i2v8)
|
[](https://youtu.be/xTz0Rj7i2v8)
|
||||||
|
|
||||||
# About the errors related to `objectToJson` in Keycloak logs.
|
# FTL errors related to `ftl_object_to_js_code_declaring_an_object` in Keycloak logs.
|
||||||
|
|
||||||
The logs of your keycloak server will always show this kind of errors every time a client request a page:
|
If you ever encounter one of these errors:
|
||||||
|
|
||||||
```log
|
```log
|
||||||
FTL stack trace ("~" means nesting-related):
|
FTL stack trace ("~" means nesting-related):
|
||||||
- Failed at: #local value = object[key] [in template "login.ftl" in macro "objectToJson_please_ignore_errors" at line 70, column 21]
|
- Failed at: #local value = object[key] [in template "login.ftl" in macro "ftl_object_to_js_code_declaring_an_object" at line 70, column 21]
|
||||||
- Reached through: @compress [in template "login.ftl" in macro "objectToJson_please_ignore_errors" at line 36, column 5]
|
- Reached through: @compress [in template "login.ftl" in macro "ftl_object_to_js_code_declaring_an_object" at line 36, column 5]
|
||||||
- Reached through: @objectToJson_please_ignore_errors object=value depth=(dep... [in template "login.ftl" in macro "objectToJson_please_ignore_errors" at line 81, column 27]
|
- Reached through: @ftl_object_to_js_code_declaring_an_object object=value depth=(dep... [in template "login.ftl" in macro "ftl_object_to_js_code_declaring_an_object" at line 81, column 27]
|
||||||
- Reached through: @compress [in template "login.ftl" in macro "objectToJson_please_ignore_errors" at line 36, column 5]
|
- Reached through: @compress [in template "login.ftl" in macro "ftl_object_to_js_code_declaring_an_object" at line 36, column 5]
|
||||||
- Reached through: @objectToJson_please_ignore_errors object=(.data_model) de... [in template "login.ftl" at line 163, column 43]
|
- Reached through: @ftl_object_to_js_code_declaring_an_object object=(.data_model) de... [in template "login.ftl" at line 163, column 43]
|
||||||
```
|
```
|
||||||
|
|
||||||
Theses are expected to show up in the log.
|
It's just noise, they can be safely ignored.
|
||||||
Unfortunately, there is nothing I know of that can be done to avoid them or even mute them.
|
You can, however, and are encouraged to, report any that you would spot.
|
||||||
They can be, however, safely ignored.
|
Just open an issue about it and I will release a patched version of Keycloakify in the better delays.
|
||||||
|
|
||||||
To [converts the `.ftl` values into a JavaScript object](https://github.com/InseeFrLab/keycloakify/blob/main/src/bin/build-keycloak-theme/generateFtl/common.ftl)
|
|
||||||
without making assumptions on the `.data_model` we have to do things that throws.
|
|
||||||
It's all-right because every statement that can fail is inside an `<#attempt><#recorver>` block but it results in errors being printed to the logs.
|
|
||||||
|
|
||||||
# The pages take too long to load ?
|
|
||||||
|
|
||||||
The problem of templates taking a long time to load only happens in the test environment, when you have a console logging all the above-mentioned `.ftl` warnings in real time. Logging all those warnings is what takes time. Once in production page load is way faster.
|
|
||||||
|
|
||||||
If you run the docker container locally we acknowledge that the loading time is getting out of hand.
|
|
||||||
We are [in the process](https://github.com/InseeFrLab/keycloakify/pull/63) of resolving this issue.
|
|
||||||
|
|
||||||
In the meantime we recommend [to run the docker container as a background task](https://youtu.be/F29Z1GaH-jk).
|
|
||||||
|
|
||||||
# Adding custom message (to `i18n/useKcMessage.tsx`)
|
# Adding custom message (to `i18n/useKcMessage.tsx`)
|
||||||
|
|
||||||
@ -488,6 +479,10 @@ and `kcRegisterContext["authorizedMailDomains"]` to validate on.
|
|||||||
|
|
||||||
# Changelog highlights
|
# Changelog highlights
|
||||||
|
|
||||||
|
> **New in v4.3.0**: Feature [`login-update-password.ftl`](https://user-images.githubusercontent.com/6702424/147517600-6191cf72-93dd-437b-a35c-47180142063e.png).
|
||||||
|
> Every time a page is added it's a breaking change for non CSS-only theme.
|
||||||
|
> Change [this](https://github.com/garronej/keycloakify-demo-app/blob/df664c13c77ce3c53ac7df0622d94d04e76d3f9f/src/KcApp/KcApp.tsx#L17) and [this](https://github.com/garronej/keycloakify-demo-app/blob/df664c13c77ce3c53ac7df0622d94d04e76d3f9f/src/KcApp/KcApp.tsx#L37) to update.
|
||||||
|
|
||||||
## v4
|
## v4
|
||||||
|
|
||||||
- Out of the box [frontend form validation](#user-profile-and-frontend-form-validation) 🥳
|
- Out of the box [frontend form validation](#user-profile-and-frontend-form-validation) 🥳
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "keycloakify",
|
"name": "keycloakify",
|
||||||
"version": "4.2.13",
|
"version": "4.5.1",
|
||||||
"description": "Keycloak theme generator for Reacts app",
|
"description": "Keycloak theme generator for Reacts app",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@ -77,11 +77,11 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cheerio": "^1.0.0-rc.5",
|
"cheerio": "^1.0.0-rc.5",
|
||||||
"evt": "2.0.0-beta.38",
|
"evt": "2.0.0-beta.39",
|
||||||
"minimal-polyfills": "^2.2.1",
|
"minimal-polyfills": "^2.2.1",
|
||||||
"path-browserify": "^1.0.1",
|
"path-browserify": "^1.0.1",
|
||||||
"react-markdown": "^5.0.3",
|
"react-markdown": "^5.0.3",
|
||||||
"scripting-tools": "^0.19.13",
|
"scripting-tools": "^0.19.13",
|
||||||
"tsafe": "^0.8.1"
|
"tsafe": "^0.9.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import { join as pathJoin, relative as pathRelative, basename as pathBasename }
|
|||||||
import * as child_process from "child_process";
|
import * as child_process from "child_process";
|
||||||
import { generateDebugFiles, containerLaunchScriptBasename } from "./generateDebugFiles";
|
import { generateDebugFiles, containerLaunchScriptBasename } from "./generateDebugFiles";
|
||||||
import { URL } from "url";
|
import { URL } from "url";
|
||||||
|
import * as fs from "fs";
|
||||||
|
|
||||||
type ParsedPackageJson = {
|
type ParsedPackageJson = {
|
||||||
name: string;
|
name: string;
|
||||||
@ -41,7 +42,17 @@ export function main() {
|
|||||||
const url = (() => {
|
const url = (() => {
|
||||||
const { homepage } = parsedPackageJson;
|
const { homepage } = parsedPackageJson;
|
||||||
|
|
||||||
return homepage === undefined ? undefined : new URL(homepage);
|
if (homepage !== undefined) {
|
||||||
|
return new URL(homepage);
|
||||||
|
}
|
||||||
|
|
||||||
|
const cnameFilePath = pathJoin(reactProjectDirPath, "public", "CNAME");
|
||||||
|
|
||||||
|
if (fs.existsSync(cnameFilePath)) {
|
||||||
|
return new URL(`https://${fs.readFileSync(cnameFilePath).toString("utf8").replace(/\s+$/, "")}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -1,28 +0,0 @@
|
|||||||
|
|
||||||
Object.defineProperty(
|
|
||||||
Object,
|
|
||||||
"deepAssign",
|
|
||||||
{
|
|
||||||
"value": function callee(target, source) {
|
|
||||||
Object.keys(source).forEach(function (key) {
|
|
||||||
var value = source[key];
|
|
||||||
if (target[key] === undefined) {
|
|
||||||
target[key] = value;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (value instanceof Object) {
|
|
||||||
if (value instanceof Array) {
|
|
||||||
value.forEach(function (entry) {
|
|
||||||
target[key].push(entry);
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
callee(target[key], value);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
target[key] = value;
|
|
||||||
});
|
|
||||||
return target;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
@ -1,208 +0,0 @@
|
|||||||
<script>const _=
|
|
||||||
<#macro objectToJson_please_ignore_errors object depth>
|
|
||||||
<@compress>
|
|
||||||
|
|
||||||
<#local isHash = false>
|
|
||||||
<#attempt>
|
|
||||||
<#local isHash = object?is_hash || object?is_hash_ex>
|
|
||||||
<#recover>
|
|
||||||
/* can't evaluate if object is hash */
|
|
||||||
undefined
|
|
||||||
<#return>
|
|
||||||
</#attempt>
|
|
||||||
<#if isHash>
|
|
||||||
|
|
||||||
<#local keys = "">
|
|
||||||
|
|
||||||
<#attempt>
|
|
||||||
<#local keys = object?keys>
|
|
||||||
<#recover>
|
|
||||||
/* can't list keys of object */
|
|
||||||
undefined
|
|
||||||
<#return>
|
|
||||||
</#attempt>
|
|
||||||
|
|
||||||
{${'\n'}
|
|
||||||
|
|
||||||
<#list keys as key>
|
|
||||||
|
|
||||||
<#if key == "class">
|
|
||||||
/* skipping "class" property of object */
|
|
||||||
<#continue>
|
|
||||||
</#if>
|
|
||||||
|
|
||||||
<#local value = "">
|
|
||||||
|
|
||||||
<#attempt>
|
|
||||||
<#local value = object[key]>
|
|
||||||
<#recover>
|
|
||||||
/* couldn't dereference ${key} of object */
|
|
||||||
<#continue>
|
|
||||||
</#attempt>
|
|
||||||
|
|
||||||
<#if depth gt 7>
|
|
||||||
/* Avoid calling recustively too many times depth: ${depth}, key: ${key} */
|
|
||||||
<#continue>
|
|
||||||
</#if>
|
|
||||||
|
|
||||||
"${key}": <@objectToJson_please_ignore_errors object=value depth=depth+1/>,
|
|
||||||
|
|
||||||
</#list>
|
|
||||||
|
|
||||||
}${'\n'}
|
|
||||||
|
|
||||||
<#return>
|
|
||||||
|
|
||||||
</#if>
|
|
||||||
|
|
||||||
|
|
||||||
<#local isMethod = "">
|
|
||||||
<#attempt>
|
|
||||||
<#local isMethod = object?is_method>
|
|
||||||
<#recover>
|
|
||||||
/* can't test if object is a method */
|
|
||||||
undefined
|
|
||||||
<#return>
|
|
||||||
</#attempt>
|
|
||||||
|
|
||||||
<#if isMethod>
|
|
||||||
undefined
|
|
||||||
<#return>
|
|
||||||
</#if>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<#local isBoolean = "">
|
|
||||||
<#attempt>
|
|
||||||
<#local isBoolean = object?is_boolean>
|
|
||||||
<#recover>
|
|
||||||
/* can't test if object is a boolean */
|
|
||||||
undefined
|
|
||||||
<#return>
|
|
||||||
</#attempt>
|
|
||||||
|
|
||||||
<#if isBoolean>
|
|
||||||
${object?c}
|
|
||||||
<#return>
|
|
||||||
</#if>
|
|
||||||
|
|
||||||
|
|
||||||
<#local isEnumerable = "">
|
|
||||||
<#attempt>
|
|
||||||
<#local isEnumerable = object?is_enumerable>
|
|
||||||
<#recover>
|
|
||||||
/* can't test if object is enumerable */
|
|
||||||
undefined
|
|
||||||
<#return>
|
|
||||||
</#attempt>
|
|
||||||
|
|
||||||
<#if isEnumerable>
|
|
||||||
|
|
||||||
[${'\n'}
|
|
||||||
|
|
||||||
<#list object as item>
|
|
||||||
|
|
||||||
<@objectToJson_please_ignore_errors object=item depth=depth+1/>,
|
|
||||||
|
|
||||||
</#list>
|
|
||||||
|
|
||||||
]${'\n'}
|
|
||||||
|
|
||||||
<#return>
|
|
||||||
</#if>
|
|
||||||
|
|
||||||
|
|
||||||
<#attempt>
|
|
||||||
"${object?replace('"', '\\"')?no_esc}"
|
|
||||||
<#recover>
|
|
||||||
/* couldn't convert into string non hash, non method, non boolean, non enumerable object */
|
|
||||||
undefined;
|
|
||||||
<#return>
|
|
||||||
</#attempt>
|
|
||||||
|
|
||||||
|
|
||||||
</@compress>
|
|
||||||
</#macro>
|
|
||||||
|
|
||||||
(()=>{
|
|
||||||
|
|
||||||
const nonAutomaticallyConvertible = {
|
|
||||||
"messagesPerField": {
|
|
||||||
|
|
||||||
<#assign fieldNames = ["global", "userLabel", "username", "email", "firstName", "lastName", "password", "password-confirm"]>
|
|
||||||
|
|
||||||
<#attempt>
|
|
||||||
<#list profile.attributes as attribute>
|
|
||||||
<#assign fieldNames += [attribute.name]>
|
|
||||||
</#list>
|
|
||||||
<#recover>
|
|
||||||
</#attempt>
|
|
||||||
|
|
||||||
"printIfExists": function (fieldName, x) {
|
|
||||||
<#list fieldNames as fieldName>
|
|
||||||
if(fieldName === "${fieldName}" ){
|
|
||||||
<#attempt>
|
|
||||||
return "${messagesPerField.printIfExists(fieldName,'1')}" ? x : undefined;
|
|
||||||
<#recover>
|
|
||||||
</#attempt>
|
|
||||||
}
|
|
||||||
</#list>
|
|
||||||
throw new Error("There is no " + fieldName + " field");
|
|
||||||
},
|
|
||||||
"existsError": function (fieldName) {
|
|
||||||
<#list fieldNames as fieldName>
|
|
||||||
if(fieldName === "${fieldName}" ){
|
|
||||||
<#attempt>
|
|
||||||
return <#if messagesPerField.existsError('${fieldName}')>true<#else>false</#if>;
|
|
||||||
<#recover>
|
|
||||||
</#attempt>
|
|
||||||
}
|
|
||||||
</#list>
|
|
||||||
throw new Error("There is no " + fieldName + " field");
|
|
||||||
},
|
|
||||||
"get": function (fieldName) {
|
|
||||||
<#list fieldNames as fieldName>
|
|
||||||
if(fieldName === "${fieldName}" ){
|
|
||||||
<#attempt>
|
|
||||||
<#if messagesPerField.existsError('${fieldName}')>
|
|
||||||
return "${messagesPerField.get('${fieldName}')?no_esc}";
|
|
||||||
</#if>
|
|
||||||
<#recover>
|
|
||||||
</#attempt>
|
|
||||||
}
|
|
||||||
</#list>
|
|
||||||
throw new Error("There is no " + fieldName + " field");
|
|
||||||
},
|
|
||||||
"exists": function (fieldName) {
|
|
||||||
<#list fieldNames as fieldName>
|
|
||||||
if(fieldName === "${fieldName}" ){
|
|
||||||
<#attempt>
|
|
||||||
return <#if messagesPerField.exists('${fieldName}')>true<#else>false</#if>;
|
|
||||||
<#recover>
|
|
||||||
</#attempt>
|
|
||||||
}
|
|
||||||
</#list>
|
|
||||||
throw new Error("There is no " + fieldName + " field");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"msg": function(){ throw new Error("use import { useKcMessage } from 'keycloakify'"); },
|
|
||||||
"advancedMsg": function(){ throw new Error("use import { useKcMessage } from 'keycloakify'"); }
|
|
||||||
};
|
|
||||||
|
|
||||||
const out = {};
|
|
||||||
|
|
||||||
Object.deepAssign(
|
|
||||||
out,
|
|
||||||
//Removing all the undefined
|
|
||||||
JSON.parse(JSON.stringify(<@objectToJson_please_ignore_errors object=.data_model depth=0 />))
|
|
||||||
);
|
|
||||||
|
|
||||||
Object.deepAssign(
|
|
||||||
out,
|
|
||||||
nonAutomaticallyConvertible
|
|
||||||
);
|
|
||||||
|
|
||||||
return out;
|
|
||||||
|
|
||||||
})()
|
|
||||||
</script>
|
|
@ -0,0 +1,290 @@
|
|||||||
|
<script>const _=
|
||||||
|
<#assign pageId="PAGE_ID_xIgLsPgGId9D8e">
|
||||||
|
(()=>{
|
||||||
|
|
||||||
|
const out =
|
||||||
|
${ftl_object_to_js_code_declaring_an_object(.data_model, [])?no_esc};
|
||||||
|
|
||||||
|
out["msg"]= function(){ throw new Error("use import { useKcMessage } from 'keycloakify'"); };
|
||||||
|
out["advancedMsg"]= function(){ throw new Error("use import { useKcMessage } from 'keycloakify'"); };
|
||||||
|
|
||||||
|
out["messagesPerField"]= {
|
||||||
|
<#assign fieldNames = [
|
||||||
|
"global", "userLabel", "username", "email", "firstName", "lastName", "password", "password-confirm",
|
||||||
|
"totp", "totpSecret", "SAMLRequest", "SAMLResponse", "relayState", "device_user_code", "code",
|
||||||
|
"password-new", "rememberMe", "login", "authenticationExecution", "cancel-aia", "clientDataJSON",
|
||||||
|
"authenticatorData", "signature", "credentialId", "userHandle", "error", "authn_use_chk", "authenticationExecution",
|
||||||
|
"isSetRetry", "try-again", "attestationObject", "publicKeyCredentialId", "authenticatorLabel"
|
||||||
|
]>
|
||||||
|
|
||||||
|
<#attempt>
|
||||||
|
<#if profile?? && profile.attributes?? && profile.attributes?is_enumerable>
|
||||||
|
<#list profile.attributes as attribute>
|
||||||
|
<#if fieldNames?seq_contains(attribute.name)>
|
||||||
|
<#continue>
|
||||||
|
</#if>
|
||||||
|
<#assign fieldNames += [attribute.name]>
|
||||||
|
</#list>
|
||||||
|
</#if>
|
||||||
|
<#recover>
|
||||||
|
</#attempt>
|
||||||
|
|
||||||
|
"printIfExists": function (fieldName, x) {
|
||||||
|
<#list fieldNames as fieldName>
|
||||||
|
if(fieldName === "${fieldName}" ){
|
||||||
|
<#attempt>
|
||||||
|
return "${messagesPerField.printIfExists(fieldName,'1')}" ? x : undefined;
|
||||||
|
<#recover>
|
||||||
|
</#attempt>
|
||||||
|
}
|
||||||
|
</#list>
|
||||||
|
throw new Error("There is no " + fieldName + " field");
|
||||||
|
},
|
||||||
|
"existsError": function (fieldName) {
|
||||||
|
<#list fieldNames as fieldName>
|
||||||
|
if(fieldName === "${fieldName}" ){
|
||||||
|
<#attempt>
|
||||||
|
return <#if messagesPerField.existsError('${fieldName}')>true<#else>false</#if>;
|
||||||
|
<#recover>
|
||||||
|
</#attempt>
|
||||||
|
}
|
||||||
|
</#list>
|
||||||
|
throw new Error("There is no " + fieldName + " field");
|
||||||
|
},
|
||||||
|
"get": function (fieldName) {
|
||||||
|
<#list fieldNames as fieldName>
|
||||||
|
if(fieldName === "${fieldName}" ){
|
||||||
|
<#attempt>
|
||||||
|
<#if messagesPerField.existsError('${fieldName}')>
|
||||||
|
return "${messagesPerField.get('${fieldName}')?no_esc}";
|
||||||
|
</#if>
|
||||||
|
<#recover>
|
||||||
|
</#attempt>
|
||||||
|
}
|
||||||
|
</#list>
|
||||||
|
throw new Error("There is no " + fieldName + " field");
|
||||||
|
},
|
||||||
|
"exists": function (fieldName) {
|
||||||
|
<#list fieldNames as fieldName>
|
||||||
|
if(fieldName === "${fieldName}" ){
|
||||||
|
<#attempt>
|
||||||
|
return <#if messagesPerField.exists('${fieldName}')>true<#else>false</#if>;
|
||||||
|
<#recover>
|
||||||
|
</#attempt>
|
||||||
|
}
|
||||||
|
</#list>
|
||||||
|
throw new Error("There is no " + fieldName + " field");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
out["pageId"] = "${pageId}";
|
||||||
|
|
||||||
|
return out;
|
||||||
|
|
||||||
|
})()
|
||||||
|
<#function ftl_object_to_js_code_declaring_an_object object path>
|
||||||
|
|
||||||
|
<#local isHash = "">
|
||||||
|
<#attempt>
|
||||||
|
<#local isHash = object?is_hash || object?is_hash_ex>
|
||||||
|
<#recover>
|
||||||
|
<#return "ABORT: Can't evaluate if " + path?join(".") + " is hash">
|
||||||
|
</#attempt>
|
||||||
|
|
||||||
|
<#if isHash>
|
||||||
|
|
||||||
|
<#if path?size gt 10>
|
||||||
|
<#return "ABORT: Too many recursive calls">
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
<#local keys = "">
|
||||||
|
|
||||||
|
<#attempt>
|
||||||
|
<#local keys = object?keys>
|
||||||
|
<#recover>
|
||||||
|
<#return "ABORT: We can't list keys on this object">
|
||||||
|
</#attempt>
|
||||||
|
|
||||||
|
|
||||||
|
<#local out_seq = []>
|
||||||
|
|
||||||
|
<#list keys as key>
|
||||||
|
|
||||||
|
<#if ["class","declaredConstructors","superclass","declaringClass" ]?seq_contains(key) >
|
||||||
|
<#continue>
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
<#if
|
||||||
|
(
|
||||||
|
["loginUpdatePasswordUrl", "loginUpdateProfileUrl", "loginUsernameReminderUrl", "loginUpdateTotpUrl"]?seq_contains(key) &&
|
||||||
|
are_same_path(path, ["url"])
|
||||||
|
) || (
|
||||||
|
key == "updateProfileCtx" &&
|
||||||
|
are_same_path(path, [])
|
||||||
|
) || (
|
||||||
|
<#-- https://github.com/InseeFrLab/keycloakify/pull/65#issuecomment-991896344 -->
|
||||||
|
key == "loginAction" &&
|
||||||
|
are_same_path(path, ["url"]) &&
|
||||||
|
pageId == "saml-post-form.ftl"
|
||||||
|
)
|
||||||
|
>
|
||||||
|
<#local out_seq += ["/*If you need '" + key + "' on " + pageId + ", please submit an issue to the Keycloakify repo*/"]>
|
||||||
|
<#continue>
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
<#if key == "attemptedUsername" && are_same_path(path, ["auth"])>
|
||||||
|
|
||||||
|
<#attempt>
|
||||||
|
<#-- https://github.com/keycloak/keycloak/blob/3a2bf0c04bcde185e497aaa32d0bb7ab7520cf4a/themes/src/main/resources/theme/base/login/template.ftl#L63 -->
|
||||||
|
<#if !(auth?has_content && auth.showUsername() && !auth.showResetCredentials())>
|
||||||
|
<#continue>
|
||||||
|
</#if>
|
||||||
|
<#recover>
|
||||||
|
</#attempt>
|
||||||
|
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
<#attempt>
|
||||||
|
<#if !object[key]??>
|
||||||
|
<#continue>
|
||||||
|
</#if>
|
||||||
|
<#recover>
|
||||||
|
<#local out_seq += ["/*Couldn't test if '" + key + "' is available on this object*/"]>
|
||||||
|
<#continue>
|
||||||
|
</#attempt>
|
||||||
|
|
||||||
|
<#local propertyValue = "">
|
||||||
|
|
||||||
|
<#attempt>
|
||||||
|
<#local propertyValue = object[key]>
|
||||||
|
<#recover>
|
||||||
|
<#local out_seq += ["/*Couldn't dereference '" + key + "' on this object*/"]>
|
||||||
|
<#continue>
|
||||||
|
</#attempt>
|
||||||
|
|
||||||
|
<#local rec_out = ftl_object_to_js_code_declaring_an_object(propertyValue, path + [ key ])>
|
||||||
|
|
||||||
|
<#if rec_out?starts_with("ABORT:")>
|
||||||
|
|
||||||
|
<#local errorMessage = rec_out?remove_beginning("ABORT:")>
|
||||||
|
|
||||||
|
<#if errorMessage != " It's a method" >
|
||||||
|
<#local out_seq += ["/*" + key + ": " + errorMessage + "*/"]>
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
<#continue>
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
<#local out_seq += ['"' + key + '": ' + rec_out + ","]>
|
||||||
|
|
||||||
|
</#list>
|
||||||
|
|
||||||
|
<#return (["{"] + out_seq?map(str -> ""?right_pad(4 * (path?size + 1)) + str) + [ ""?right_pad(4 * path?size) + "}"])?join("\n")>
|
||||||
|
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
<#local isMethod = "">
|
||||||
|
<#attempt>
|
||||||
|
<#local isMethod = object?is_method>
|
||||||
|
<#recover>
|
||||||
|
<#return "ABORT: Can't test if it'sa method.">
|
||||||
|
</#attempt>
|
||||||
|
|
||||||
|
<#if isMethod>
|
||||||
|
<#return "ABORT: It's a method">
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
<#local isBoolean = "">
|
||||||
|
<#attempt>
|
||||||
|
<#local isBoolean = object?is_boolean>
|
||||||
|
<#recover>
|
||||||
|
<#return "ABORT: Can't test if it's a boolean">
|
||||||
|
</#attempt>
|
||||||
|
|
||||||
|
<#if isBoolean>
|
||||||
|
<#return object?c>
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
<#local isEnumerable = "">
|
||||||
|
<#attempt>
|
||||||
|
<#local isEnumerable = object?is_enumerable>
|
||||||
|
<#recover>
|
||||||
|
<#return "ABORT: Can't test if it's an enumerable">
|
||||||
|
</#attempt>
|
||||||
|
|
||||||
|
|
||||||
|
<#if isEnumerable>
|
||||||
|
|
||||||
|
<#local out_seq = []>
|
||||||
|
|
||||||
|
<#local i = 0>
|
||||||
|
|
||||||
|
<#list object as array_item>
|
||||||
|
|
||||||
|
<#local rec_out = ftl_object_to_js_code_declaring_an_object(array_item, path + [ i ])>
|
||||||
|
|
||||||
|
<#local i = i + 1>
|
||||||
|
|
||||||
|
<#if rec_out?starts_with("ABORT:")>
|
||||||
|
|
||||||
|
<#local errorMessage = rec_out?remove_beginning("ABORT:")>
|
||||||
|
|
||||||
|
<#if errorMessage != " It's a method" >
|
||||||
|
<#local out_seq += ["/*" + i?string + ": " + errorMessage + "*/"]>
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
<#continue>
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
<#local out_seq += [rec_out + ","]>
|
||||||
|
|
||||||
|
</#list>
|
||||||
|
|
||||||
|
<#return (["["] + out_seq?map(str -> ""?right_pad(4 * (path?size + 1)) + str) + [ ""?right_pad(4 * path?size) + "]"])?join("\n")>
|
||||||
|
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
<#attempt>
|
||||||
|
<#return '"' + object?js_string + '"'>;
|
||||||
|
<#recover>
|
||||||
|
</#attempt>
|
||||||
|
|
||||||
|
<#return "ABORT: Couldn't convert into string non hash, non method, non boolean, non enumerable object">
|
||||||
|
|
||||||
|
</#function>
|
||||||
|
<#function are_same_path path searchedPath>
|
||||||
|
|
||||||
|
<#if path?size != path?size>
|
||||||
|
<#return false>
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
<#local i=0>
|
||||||
|
|
||||||
|
<#list path as property>
|
||||||
|
|
||||||
|
<#local searchedProperty=searchedPath[i]>
|
||||||
|
|
||||||
|
<#if searchedProperty?is_string && searchedProperty == "*">
|
||||||
|
<#continue>
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
<#if searchedProperty?is_string && !property?is_string>
|
||||||
|
<#return false>
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
<#if searchedProperty?is_number && !property?is_number>
|
||||||
|
<#return false>
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
<#if searchedProperty?string != property?string>
|
||||||
|
<#return false>
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
<#local i+= 1>
|
||||||
|
|
||||||
|
</#list>
|
||||||
|
|
||||||
|
<#return true>
|
||||||
|
|
||||||
|
</#function>
|
||||||
|
</script>
|
@ -16,15 +16,13 @@ export const pageIds = [
|
|||||||
"terms.ftl",
|
"terms.ftl",
|
||||||
"login-otp.ftl",
|
"login-otp.ftl",
|
||||||
"login-update-profile.ftl",
|
"login-update-profile.ftl",
|
||||||
|
"login-update-password.ftl",
|
||||||
"login-idp-link-confirm.ftl",
|
"login-idp-link-confirm.ftl",
|
||||||
|
"login-page-expired.ftl",
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
export type PageId = typeof pageIds[number];
|
export type PageId = typeof pageIds[number];
|
||||||
|
|
||||||
function loadAdjacentFile(fileBasename: string) {
|
|
||||||
return fs.readFileSync(pathJoin(__dirname, fileBasename)).toString("utf8");
|
|
||||||
}
|
|
||||||
|
|
||||||
export function generateFtlFilesCodeFactory(params: {
|
export function generateFtlFilesCodeFactory(params: {
|
||||||
cssGlobalsToDefine: Record<string, string>;
|
cssGlobalsToDefine: Record<string, string>;
|
||||||
indexHtmlCode: string;
|
indexHtmlCode: string;
|
||||||
@ -77,8 +75,11 @@ export function generateFtlFilesCodeFactory(params: {
|
|||||||
);
|
);
|
||||||
|
|
||||||
//FTL is no valid html, we can't insert with cheerio, we put placeholder for injecting later.
|
//FTL is no valid html, we can't insert with cheerio, we put placeholder for injecting later.
|
||||||
const ftlPlaceholders = {
|
const replaceValueBySearchValue = {
|
||||||
'{ "x": "vIdLqMeOed9sdLdIdOxdK0d" }': loadAdjacentFile("common.ftl").match(/^<script>const _=((?:.|\n)+)<\/script>[\n]?$/)![1],
|
'{ "x": "vIdLqMeOed9sdLdIdOxdK0d" }': fs
|
||||||
|
.readFileSync(pathJoin(__dirname, "ftl_object_to_js_code_declaring_an_object.ftl"))
|
||||||
|
.toString("utf8")
|
||||||
|
.match(/^<script>const _=((?:.|\n)+)<\/script>[\n]?$/)![1],
|
||||||
"<!-- xIdLqMeOedErIdLsPdNdI9dSlxI -->": [
|
"<!-- xIdLqMeOedErIdLsPdNdI9dSlxI -->": [
|
||||||
"<#if scripts??>",
|
"<#if scripts??>",
|
||||||
" <#list scripts as script>",
|
" <#list scripts as script>",
|
||||||
@ -88,8 +89,6 @@ export function generateFtlFilesCodeFactory(params: {
|
|||||||
].join("\n"),
|
].join("\n"),
|
||||||
};
|
};
|
||||||
|
|
||||||
const pageSpecificCodePlaceholder = "<!-- dIddLqMeOedErIdLsPdNdI9dSl42sw -->";
|
|
||||||
|
|
||||||
$("head").prepend(
|
$("head").prepend(
|
||||||
[
|
[
|
||||||
...(Object.keys(cssGlobalsToDefine).length === 0
|
...(Object.keys(cssGlobalsToDefine).length === 0
|
||||||
@ -105,18 +104,10 @@ export function generateFtlFilesCodeFactory(params: {
|
|||||||
"",
|
"",
|
||||||
]),
|
]),
|
||||||
"<script>",
|
"<script>",
|
||||||
loadAdjacentFile("Object.deepAssign.js"),
|
` window.${ftlValuesGlobalName}= ${objectKeys(replaceValueBySearchValue)[0]};`,
|
||||||
"</script>",
|
|
||||||
"<script>",
|
|
||||||
` window.${ftlValuesGlobalName}= Object.assign(`,
|
|
||||||
` {},`,
|
|
||||||
` ${objectKeys(ftlPlaceholders)[0]}`,
|
|
||||||
" );",
|
|
||||||
"</script>",
|
"</script>",
|
||||||
"",
|
"",
|
||||||
pageSpecificCodePlaceholder,
|
objectKeys(replaceValueBySearchValue)[1],
|
||||||
"",
|
|
||||||
objectKeys(ftlPlaceholders)[1],
|
|
||||||
].join("\n"),
|
].join("\n"),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -129,19 +120,13 @@ export function generateFtlFilesCodeFactory(params: {
|
|||||||
|
|
||||||
const $ = cheerio.load(partiallyFixedIndexHtmlCode);
|
const $ = cheerio.load(partiallyFixedIndexHtmlCode);
|
||||||
|
|
||||||
let ftlCode = $.html().replace(
|
let ftlCode = $.html();
|
||||||
pageSpecificCodePlaceholder,
|
|
||||||
[
|
|
||||||
"<script>",
|
|
||||||
` Object.deepAssign(`,
|
|
||||||
` window.${ftlValuesGlobalName},`,
|
|
||||||
` { "pageId": "${pageId}" }`,
|
|
||||||
" );",
|
|
||||||
"</script>",
|
|
||||||
].join("\n"),
|
|
||||||
);
|
|
||||||
|
|
||||||
objectKeys(ftlPlaceholders).forEach(id => (ftlCode = ftlCode.replace(id, ftlPlaceholders[id])));
|
Object.entries({
|
||||||
|
...replaceValueBySearchValue,
|
||||||
|
//If updated, don't forget to change in the ftl script as well.
|
||||||
|
"PAGE_ID_xIgLsPgGId9D8e": pageId,
|
||||||
|
}).map(([searchValue, replaceValue]) => (ftlCode = ftlCode.replace(searchValue, replaceValue)));
|
||||||
|
|
||||||
return { ftlCode };
|
return { ftlCode };
|
||||||
}
|
}
|
||||||
|
@ -37,7 +37,7 @@ export function replaceImportsInInlineCssCode(params: { cssCode: string; urlPath
|
|||||||
const { cssCode, urlPathname, urlOrigin } = params;
|
const { cssCode, urlPathname, urlOrigin } = params;
|
||||||
|
|
||||||
const fixedCssCode = cssCode.replace(
|
const fixedCssCode = cssCode.replace(
|
||||||
urlPathname === "/" ? /url\(\/([^/][^)]+)\)/g : new RegExp(`url\\(${urlPathname}([^)]+)\\)`, "g"),
|
urlPathname === "/" ? /url\(["']?\/([^/][^)"']+)["']?\)/g : new RegExp(`url\\(["']?${urlPathname}([^)"']+)["']?\\)`, "g"),
|
||||||
(...[, group]) => `url(${urlOrigin === undefined ? "${url.resourcesPath}/build/" + group : params.urlOrigin + urlPathname + group})`,
|
(...[, group]) => `url(${urlOrigin === undefined ? "${url.resourcesPath}/build/" + group : params.urlOrigin + urlPathname + group})`,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -52,7 +52,7 @@ export function replaceImportsInCssCode(params: { cssCode: string }): {
|
|||||||
|
|
||||||
const cssGlobalsToDefine: Record<string, string> = {};
|
const cssGlobalsToDefine: Record<string, string> = {};
|
||||||
|
|
||||||
new Set(cssCode.match(/url\(\/[^/][^)]+\)[^;}]*/g) ?? []).forEach(
|
new Set(cssCode.match(/url\(["']?\/[^/][^)"']+["']?\)[^;}]*/g) ?? []).forEach(
|
||||||
match => (cssGlobalsToDefine["url" + crypto.createHash("sha256").update(match).digest("hex").substring(0, 15)] = match),
|
match => (cssGlobalsToDefine["url" + crypto.createHash("sha256").update(match).digest("hex").substring(0, 15)] = match),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -10,8 +10,10 @@ import { LoginResetPassword } from "./LoginResetPassword";
|
|||||||
import { LoginVerifyEmail } from "./LoginVerifyEmail";
|
import { LoginVerifyEmail } from "./LoginVerifyEmail";
|
||||||
import { Terms } from "./Terms";
|
import { Terms } from "./Terms";
|
||||||
import { LoginOtp } from "./LoginOtp";
|
import { LoginOtp } from "./LoginOtp";
|
||||||
|
import { LoginUpdatePassword } from "./LoginUpdatePassword";
|
||||||
import { LoginUpdateProfile } from "./LoginUpdateProfile";
|
import { LoginUpdateProfile } from "./LoginUpdateProfile";
|
||||||
import { LoginIdpLinkConfirm } from "./LoginIdpLinkConfirm";
|
import { LoginIdpLinkConfirm } from "./LoginIdpLinkConfirm";
|
||||||
|
import { LoginPageExpired } from "./LoginPageExpired";
|
||||||
|
|
||||||
export const KcApp = memo(({ kcContext, ...props }: { kcContext: KcContextBase } & KcProps) => {
|
export const KcApp = memo(({ kcContext, ...props }: { kcContext: KcContextBase } & KcProps) => {
|
||||||
switch (kcContext.pageId) {
|
switch (kcContext.pageId) {
|
||||||
@ -33,9 +35,13 @@ export const KcApp = memo(({ kcContext, ...props }: { kcContext: KcContextBase }
|
|||||||
return <Terms {...{ kcContext, ...props }} />;
|
return <Terms {...{ kcContext, ...props }} />;
|
||||||
case "login-otp.ftl":
|
case "login-otp.ftl":
|
||||||
return <LoginOtp {...{ kcContext, ...props }} />;
|
return <LoginOtp {...{ kcContext, ...props }} />;
|
||||||
|
case "login-update-password.ftl":
|
||||||
|
return <LoginUpdatePassword {...{ kcContext, ...props }} />;
|
||||||
case "login-update-profile.ftl":
|
case "login-update-profile.ftl":
|
||||||
return <LoginUpdateProfile {...{ kcContext, ...props }} />;
|
return <LoginUpdateProfile {...{ kcContext, ...props }} />;
|
||||||
case "login-idp-link-confirm.ftl":
|
case "login-idp-link-confirm.ftl":
|
||||||
return <LoginIdpLinkConfirm {...{ kcContext, ...props }} />;
|
return <LoginIdpLinkConfirm {...{ kcContext, ...props }} />;
|
||||||
|
case "login-page-expired.ftl":
|
||||||
|
return <LoginPageExpired {...{ kcContext, ...props }} />;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -4,7 +4,7 @@ import type { KcProps } from "./KcProps";
|
|||||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||||
import { useKcMessage } from "../i18n/useKcMessage";
|
import { useKcMessage } from "../i18n/useKcMessage";
|
||||||
import { headInsert } from "../tools/headInsert";
|
import { headInsert } from "../tools/headInsert";
|
||||||
import { join as pathJoin } from "path";
|
import { pathJoin } from "../tools/pathJoin";
|
||||||
import { useCssAndCx } from "tss-react";
|
import { useCssAndCx } from "tss-react";
|
||||||
|
|
||||||
export const LoginOtp = memo(({ kcContext, ...props }: { kcContext: KcContextBase.LoginOtp } & KcProps) => {
|
export const LoginOtp = memo(({ kcContext, ...props }: { kcContext: KcContextBase.LoginOtp } & KcProps) => {
|
||||||
|
36
src/lib/components/LoginPageExpired.tsx
Normal file
36
src/lib/components/LoginPageExpired.tsx
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import { memo } from "react";
|
||||||
|
import { Template } from "./Template";
|
||||||
|
import type { KcProps } from "./KcProps";
|
||||||
|
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||||
|
import { useKcMessage } from "../i18n/useKcMessage";
|
||||||
|
|
||||||
|
export const LoginPageExpired = memo(({ kcContext, ...props }: { kcContext: KcContextBase.LoginPageExpired } & KcProps) => {
|
||||||
|
const { url } = kcContext;
|
||||||
|
|
||||||
|
const { msg } = useKcMessage();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Template
|
||||||
|
{...{ kcContext, ...props }}
|
||||||
|
doFetchDefaultThemeResources={true}
|
||||||
|
displayMessage={false}
|
||||||
|
headerNode={msg("pageExpiredTitle")}
|
||||||
|
formNode={
|
||||||
|
<>
|
||||||
|
<p id="instruction1" className="instruction">
|
||||||
|
{msg("pageExpiredMsg1")}
|
||||||
|
<a id="loginRestartLink" href={url.loginRestartFlowUrl}>
|
||||||
|
{msg("doClickHere")}
|
||||||
|
</a>{" "}
|
||||||
|
.<br />
|
||||||
|
{msg("pageExpiredMsg2")}{" "}
|
||||||
|
<a id="loginContinueLink" href={url.loginAction}>
|
||||||
|
{msg("doClickHere")}
|
||||||
|
</a>{" "}
|
||||||
|
.
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
117
src/lib/components/LoginUpdatePassword.tsx
Normal file
117
src/lib/components/LoginUpdatePassword.tsx
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
import { memo } from "react";
|
||||||
|
import { Template } from "./Template";
|
||||||
|
import type { KcProps } from "./KcProps";
|
||||||
|
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||||
|
import { useKcMessage } from "../i18n/useKcMessage";
|
||||||
|
import { useCssAndCx } from "tss-react";
|
||||||
|
|
||||||
|
export const LoginUpdatePassword = memo(({ kcContext, ...props }: { kcContext: KcContextBase.LoginUpdatePassword } & KcProps) => {
|
||||||
|
const { cx } = useCssAndCx();
|
||||||
|
|
||||||
|
const { msg, msgStr } = useKcMessage();
|
||||||
|
|
||||||
|
const { url, messagesPerField, isAppInitiatedAction, username } = kcContext;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Template
|
||||||
|
{...{ kcContext, ...props }}
|
||||||
|
doFetchDefaultThemeResources={true}
|
||||||
|
headerNode={msg("updatePasswordTitle")}
|
||||||
|
formNode={
|
||||||
|
<form id="kc-passwd-update-form" className={cx(props.kcFormClass)} action={url.loginAction} method="post">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="username"
|
||||||
|
name="username"
|
||||||
|
value={username}
|
||||||
|
readOnly={true}
|
||||||
|
autoComplete="username"
|
||||||
|
style={{ display: "none" }}
|
||||||
|
/>
|
||||||
|
<input type="password" id="password" name="password" autoComplete="current-password" style={{ display: "none" }} />
|
||||||
|
|
||||||
|
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("password", props.kcFormGroupErrorClass))}>
|
||||||
|
<div className={cx(props.kcLabelWrapperClass)}>
|
||||||
|
<label htmlFor="password-new" className={cx(props.kcLabelClass)}>
|
||||||
|
{msg("passwordNew")}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className={cx(props.kcInputWrapperClass)}>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
id="password-new"
|
||||||
|
name="password-new"
|
||||||
|
autoFocus
|
||||||
|
autoComplete="new-password"
|
||||||
|
className={cx(props.kcInputClass)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("password-confirm", props.kcFormGroupErrorClass))}>
|
||||||
|
<div className={cx(props.kcLabelWrapperClass)}>
|
||||||
|
<label htmlFor="password-confirm" className={cx(props.kcLabelClass)}>
|
||||||
|
{msg("passwordConfirm")}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className={cx(props.kcInputWrapperClass)}>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
id="password-confirm"
|
||||||
|
name="password-confirm"
|
||||||
|
autoComplete="new-password"
|
||||||
|
className={cx(props.kcInputClass)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={cx(props.kcFormGroupClass)}>
|
||||||
|
<div id="kc-form-options" className={cx(props.kcFormOptionsClass)}>
|
||||||
|
<div className={cx(props.kcFormOptionsWrapperClass)}>
|
||||||
|
{isAppInitiatedAction && (
|
||||||
|
<div className="checkbox">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" id="logout-sessions" name="logout-sessions" value="on" checked />
|
||||||
|
{msgStr("logoutOtherSessions")}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="kc-form-buttons" className={cx(props.kcFormButtonsClass)}>
|
||||||
|
{isAppInitiatedAction ? (
|
||||||
|
<>
|
||||||
|
<input
|
||||||
|
className={cx(props.kcButtonClass, props.kcButtonPrimaryClass, props.kcButtonLargeClass)}
|
||||||
|
type="submit"
|
||||||
|
defaultValue={msgStr("doSubmit")}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
className={cx(props.kcButtonClass, props.kcButtonDefaultClass, props.kcButtonLargeClass)}
|
||||||
|
type="submit"
|
||||||
|
name="cancel-aia"
|
||||||
|
value="true"
|
||||||
|
>
|
||||||
|
{msg("doCancel")}
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<input
|
||||||
|
className={cx(
|
||||||
|
props.kcButtonClass,
|
||||||
|
props.kcButtonPrimaryClass,
|
||||||
|
props.kcButtonBlockClass,
|
||||||
|
props.kcButtonLargeClass,
|
||||||
|
)}
|
||||||
|
type="submit"
|
||||||
|
defaultValue={msgStr("doSubmit")}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
@ -9,7 +9,7 @@ import { getBestMatchAmongKcLanguageTag } from "../i18n/KcLanguageTag";
|
|||||||
import { getKcLanguageTagLabel } from "../i18n/KcLanguageTag";
|
import { getKcLanguageTagLabel } from "../i18n/KcLanguageTag";
|
||||||
import { useCallbackFactory } from "powerhooks/useCallbackFactory";
|
import { useCallbackFactory } from "powerhooks/useCallbackFactory";
|
||||||
import { headInsert } from "../tools/headInsert";
|
import { headInsert } from "../tools/headInsert";
|
||||||
import { join as pathJoin } from "path";
|
import { pathJoin } from "../tools/pathJoin";
|
||||||
import { useConstCallback } from "powerhooks/useConstCallback";
|
import { useConstCallback } from "powerhooks/useConstCallback";
|
||||||
import type { KcTemplateProps } from "./KcProps";
|
import type { KcTemplateProps } from "./KcProps";
|
||||||
import { useCssAndCx } from "tss-react";
|
import { useCssAndCx } from "tss-react";
|
||||||
|
@ -21,8 +21,10 @@ export type KcContextBase =
|
|||||||
| KcContextBase.LoginVerifyEmail
|
| KcContextBase.LoginVerifyEmail
|
||||||
| KcContextBase.Terms
|
| KcContextBase.Terms
|
||||||
| KcContextBase.LoginOtp
|
| KcContextBase.LoginOtp
|
||||||
|
| KcContextBase.LoginUpdatePassword
|
||||||
| KcContextBase.LoginUpdateProfile
|
| KcContextBase.LoginUpdateProfile
|
||||||
| KcContextBase.LoginIdpLinkConfirm;
|
| KcContextBase.LoginIdpLinkConfirm
|
||||||
|
| KcContextBase.LoginPageExpired;
|
||||||
|
|
||||||
export declare namespace KcContextBase {
|
export declare namespace KcContextBase {
|
||||||
export type Common = {
|
export type Common = {
|
||||||
@ -34,6 +36,7 @@ export declare namespace KcContextBase {
|
|||||||
loginUrl: string;
|
loginUrl: string;
|
||||||
};
|
};
|
||||||
realm: {
|
realm: {
|
||||||
|
name: string;
|
||||||
displayName?: string;
|
displayName?: string;
|
||||||
displayNameHtml?: string;
|
displayNameHtml?: string;
|
||||||
internationalizationEnabled: boolean;
|
internationalizationEnabled: boolean;
|
||||||
@ -191,6 +194,11 @@ export declare namespace KcContextBase {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type LoginUpdatePassword = Common & {
|
||||||
|
pageId: "login-update-password.ftl";
|
||||||
|
username: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type LoginUpdateProfile = Common & {
|
export type LoginUpdateProfile = Common & {
|
||||||
pageId: "login-update-profile.ftl";
|
pageId: "login-update-profile.ftl";
|
||||||
user: {
|
user: {
|
||||||
@ -206,6 +214,10 @@ export declare namespace KcContextBase {
|
|||||||
pageId: "login-idp-link-confirm.ftl";
|
pageId: "login-idp-link-confirm.ftl";
|
||||||
idpAlias: string;
|
idpAlias: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type LoginPageExpired = Common & {
|
||||||
|
pageId: "login-page-expired.ftl";
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Attribute = {
|
export type Attribute = {
|
||||||
|
@ -5,7 +5,7 @@ import { getKcLanguageTagLabel } from "../../i18n/KcLanguageTag";
|
|||||||
//NOTE: Aside because we want to be able to import them from node
|
//NOTE: Aside because we want to be able to import them from node
|
||||||
import { resourcesCommonPath, resourcesPath } from "./urlResourcesPath";
|
import { resourcesCommonPath, resourcesPath } from "./urlResourcesPath";
|
||||||
import { id } from "tsafe/id";
|
import { id } from "tsafe/id";
|
||||||
import { join as pathJoin } from "path";
|
import { pathJoin } from "../../tools/pathJoin";
|
||||||
|
|
||||||
const PUBLIC_URL = process.env["PUBLIC_URL"] ?? "/";
|
const PUBLIC_URL = process.env["PUBLIC_URL"] ?? "/";
|
||||||
|
|
||||||
@ -18,6 +18,7 @@ export const kcContextCommonMock: KcContextBase.Common = {
|
|||||||
"loginUrl": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg",
|
"loginUrl": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg",
|
||||||
},
|
},
|
||||||
"realm": {
|
"realm": {
|
||||||
|
"name": "myrealm",
|
||||||
"displayName": "myrealm",
|
"displayName": "myrealm",
|
||||||
"displayNameHtml": "myrealm",
|
"displayNameHtml": "myrealm",
|
||||||
"internationalizationEnabled": true,
|
"internationalizationEnabled": true,
|
||||||
@ -345,6 +346,11 @@ export const kcContextMocks: KcContextBase[] = [
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
id<KcContextBase.LoginUpdatePassword>({
|
||||||
|
...kcContextCommonMock,
|
||||||
|
"pageId": "login-update-password.ftl",
|
||||||
|
"username": "anUsername",
|
||||||
|
}),
|
||||||
id<KcContextBase.LoginUpdateProfile>({
|
id<KcContextBase.LoginUpdateProfile>({
|
||||||
...kcContextCommonMock,
|
...kcContextCommonMock,
|
||||||
"pageId": "login-update-profile.ftl",
|
"pageId": "login-update-profile.ftl",
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { join as pathJoin } from "path";
|
import { pathJoin } from "../../tools/pathJoin";
|
||||||
|
|
||||||
export const subDirOfPublicDirBasename = "keycloak_static";
|
export const subDirOfPublicDirBasename = "keycloak_static";
|
||||||
export const resourcesPath = pathJoin(subDirOfPublicDirBasename, "/resources");
|
export const resourcesPath = pathJoin(subDirOfPublicDirBasename, "resources");
|
||||||
export const resourcesCommonPath = pathJoin(subDirOfPublicDirBasename, "/resources_common");
|
export const resourcesCommonPath = pathJoin(subDirOfPublicDirBasename, "resources_common");
|
||||||
|
6
src/lib/tools/pathJoin.ts
Normal file
6
src/lib/tools/pathJoin.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export function pathJoin(...path: string[]): string {
|
||||||
|
return path
|
||||||
|
.map((part, i) => (i === 0 ? part : part.replace(/^\/+/, "")))
|
||||||
|
.map((part, i) => (i === path.length - 1 ? part : part.replace(/\/+$/, "")))
|
||||||
|
.join("/");
|
||||||
|
}
|
14
yarn.lock
14
yarn.lock
@ -545,6 +545,15 @@ evt@2.0.0-beta.38:
|
|||||||
run-exclusive "^2.2.14"
|
run-exclusive "^2.2.14"
|
||||||
tsafe "^0.4.1"
|
tsafe "^0.4.1"
|
||||||
|
|
||||||
|
evt@2.0.0-beta.39:
|
||||||
|
version "2.0.0-beta.39"
|
||||||
|
resolved "https://registry.yarnpkg.com/evt/-/evt-2.0.0-beta.39.tgz#3c859a83b35940f7eecfb5f148f03b7cbf3fee51"
|
||||||
|
integrity sha512-XxJkaHrFWBrzjTbnr5LJYXkGkADsAXReZfq2lFu3Kf1iCEw5/5ibrdXu3bQdWW6xkZ8qwAHT3STU9zYcCl09BA==
|
||||||
|
dependencies:
|
||||||
|
minimal-polyfills "^2.2.1"
|
||||||
|
run-exclusive "^2.2.14"
|
||||||
|
tsafe "^0.4.1"
|
||||||
|
|
||||||
execa@^5.1.1:
|
execa@^5.1.1:
|
||||||
version "5.1.1"
|
version "5.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd"
|
resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd"
|
||||||
@ -1469,6 +1478,11 @@ tsafe@^0.8.1:
|
|||||||
resolved "https://registry.yarnpkg.com/tsafe/-/tsafe-0.8.1.tgz#9af7e1540bc04313a82d60c98056a5017c8b086b"
|
resolved "https://registry.yarnpkg.com/tsafe/-/tsafe-0.8.1.tgz#9af7e1540bc04313a82d60c98056a5017c8b086b"
|
||||||
integrity sha512-EfPjxQHzndQAV/uh0SMGP26Wg3dCuaw8dRv2VPEuGHen5qzg2oqsMvZw2wkQFkiMisZq2fm95m5lheimW2Fpvg==
|
integrity sha512-EfPjxQHzndQAV/uh0SMGP26Wg3dCuaw8dRv2VPEuGHen5qzg2oqsMvZw2wkQFkiMisZq2fm95m5lheimW2Fpvg==
|
||||||
|
|
||||||
|
tsafe@^0.9.0:
|
||||||
|
version "0.9.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/tsafe/-/tsafe-0.9.0.tgz#8394e5fdf81e690c97e2b8be4180a079a4a19bfb"
|
||||||
|
integrity sha512-wmbu8pI/xmW69b13HoS8WbTcSlRTDjIut9ACblBjVZVTk0vsMRXdoh1k1jMu5EzKNohBavKHhqNOOsccSR7XCA==
|
||||||
|
|
||||||
tslib@^1.9.0:
|
tslib@^1.9.0:
|
||||||
version "1.14.1"
|
version "1.14.1"
|
||||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
|
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
|
||||||
|
Reference in New Issue
Block a user