Compare commits

..

1 Commits

Author SHA1 Message Date
8ec11d81f6 Enabling shorter import paths [automatic] 2021-02-23 08:52:37 +00:00
375 changed files with 5773 additions and 41930 deletions

3
.gitattributes vendored
View File

@ -1,3 +0,0 @@
src/lib/i18n/generated_kcMessages/* linguist-documentation
src/bin/build-keycloak-theme/index.ts -linguist-detectable
src/bin/install-builtin-keycloak-themes.ts -linguist-detectable

25
.github/release.yaml vendored
View File

@ -1,25 +0,0 @@
changelog:
exclude:
labels:
- ignore-for-release
authors:
- octocat
categories:
- title: Breaking Changes 🛠
labels:
- breaking
- title: Exciting New Features 🎉
labels:
- feature
- title: Fixes 🔧
labels:
- fix
- title: Documentation 🔧
labels:
- docs
- title: CI 👷
labels:
- ci
- title: Other Changes
labels:
- '*'

View File

@ -1,141 +0,0 @@
name: ci
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
test_lint:
runs-on: ubuntu-latest
if: ${{ !github.event.created && github.repository != 'garronej/ts-ci' }}
steps:
- uses: actions/checkout@v2.3.4
- uses: actions/setup-node@v2.1.3
- uses: bahmutov/npm-install@v1
- name: If this step fails run 'npm run lint' and 'npm run format' then commit again.
run: |
PACKAGE_MANAGER=npm
if [ -f "./yarn.lock" ]; then
PACKAGE_MANAGER=yarn
fi
$PACKAGE_MANAGER run format:check
test:
runs-on: macos-10.15
needs: test_lint
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
strategy:
matrix:
node: [ '15', '14' ]
name: Test with Node v${{ matrix.node }}
steps:
- name: Tell if project is using npm or yarn
id: step1
uses: garronej/ts-ci@v1.1.4
with:
action_name: tell_if_project_uses_npm_or_yarn
- uses: actions/checkout@v2.3.4
- uses: actions/setup-node@v2.1.3
with:
node-version: ${{ matrix.node }}
- uses: bahmutov/npm-install@v1
- if: steps.step1.outputs.npm_or_yarn == 'yarn'
run: |
yarn build
yarn test
- if: steps.step1.outputs.npm_or_yarn == 'npm'
run: |
npm run build
npm test
check_if_version_upgraded:
name: Check if version upgrade
# We run this only if it's a push on the default branch or if it's a PR from a
# branch (meaning not a PR from a fork). It would be more straightforward to test if secrets.NPM_TOKEN is
# defined but GitHub Action don't allow it yet.
if: |
github.event_name == 'push' ||
github.event.pull_request.head.repo.owner.login == github.event.pull_request.base.repo.owner.login
runs-on: ubuntu-latest
needs: test
outputs:
from_version: ${{ steps.step1.outputs.from_version }}
to_version: ${{ steps.step1.outputs.to_version }}
is_upgraded_version: ${{ steps.step1.outputs.is_upgraded_version }}
is_release_beta: ${{steps.step1.outputs.is_release_beta }}
steps:
- uses: garronej/ts-ci@v1.1.4
id: step1
with:
action_name: is_package_json_version_upgraded
branch: ${{ github.head_ref || github.ref }}
create_github_release:
runs-on: ubuntu-latest
# We create a release only if the version have been upgraded and we are on a default branch
# PR on the default branch can release beta but not real release
if: |
needs.check_if_version_upgraded.outputs.is_upgraded_version == 'true' &&
(
github.event_name == 'push' ||
needs.check_if_version_upgraded.outputs.is_release_beta == 'true'
)
needs:
- check_if_version_upgraded
steps:
- uses: softprops/action-gh-release@v1
with:
name: Release v${{ needs.check_if_version_upgraded.outputs.to_version }}
tag_name: v${{ needs.check_if_version_upgraded.outputs.to_version }}
target_commitish: ${{ github.head_ref || github.ref }}
generate_release_notes: true
draft: false
prerelease: ${{ needs.check_if_version_upgraded.outputs.is_release_beta == 'true' }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
publish_on_npm:
runs-on: ubuntu-latest
needs:
- create_github_release
- check_if_version_upgraded
steps:
- uses: actions/checkout@v2.3.4
with:
ref: ${{ github.ref }}
- uses: actions/setup-node@v2.1.3
with:
node-version: '15'
registry-url: https://registry.npmjs.org/
- uses: bahmutov/npm-install@v1
- run: |
PACKAGE_MANAGER=npm
if [ -f "./yarn.lock" ]; then
PACKAGE_MANAGER=yarn
fi
$PACKAGE_MANAGER run build
- run: npx -y -p denoify@0.6.5 denoify_enable_short_npm_import_path
env:
DRY_RUN: "0"
- name: Publishing on NPM
run: |
if [ "$(npm show . version)" = "$VERSION" ]; then
echo "This version is already published"
exit 0
fi
if [ "$NODE_AUTH_TOKEN" = "" ]; then
echo "Can't publish on NPM, You must first create a secret called NPM_TOKEN that contains your NPM auth token. https://help.github.com/en/actions/automating-your-workflow-with-github-actions/creating-and-using-encrypted-secrets"
false
fi
EXTRA_ARGS=""
if [ "$IS_BETA" = "true" ]; then
EXTRA_ARGS="--tag beta"
fi
npm publish $EXTRA_ARGS
env:
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
VERSION: ${{ needs.check_if_version_upgraded.outputs.to_version }}
IS_BETA: ${{ needs.check_if_version_upgraded.outputs.is_release_beta }}

7
.gitignore vendored
View File

@ -41,12 +41,5 @@ jspm_packages
.DS_Store
/dist
/dist_test
/sample_react_project/
/.yarn_home/
.idea
/keycloak_email
/build_keycloak

View File

@ -1,9 +0,0 @@
node_modules/
/dist/
/dist_test/
/CHANGELOG.md
/.yarn_home/
/src/test/apps/
/src/tools/types/
/sample_react_project
/build_keycloak/

View File

@ -1,11 +0,0 @@
{
"printWidth": 150,
"tabWidth": 4,
"useTabs": false,
"semi": true,
"singleQuote": false,
"quoteProps": "preserve",
"trailingComma": "all",
"bracketSpacing": true,
"arrowParens": "avoid"
}

48
CHANGELOG.md Normal file
View File

@ -0,0 +1,48 @@
### **0.0.10** (2021-02-23)
- Remove extra closing bracket
### **0.0.9** (2021-02-22)
- fix container startup script
- minor update
### **0.0.8** (2021-02-21)
- Include theme properties
### **0.0.7** (2021-02-21)
- fix build
- Fix bundle
### **0.0.6** (2021-02-21)
- Include missing files in the release bundle
### **0.0.5** (2021-02-21)
- Bump version number
- Make the install faster
### **0.0.4** (2021-02-21)
- Fix script visibility
### **0.0.3** (2021-02-21)
- Do not run tests on window
- Add script for downloading base themes
- Generate debug files to be able to test the container
- Fix many little bugs
- refactor
- Almoste there
- Things are starting to take form
- Seems to be working
- First draft
- Remove eslint and prettyer
### **0.0.2** (2021-02-20)
- Update package.json

View File

@ -1,3 +0,0 @@
Looking to contribute? Thank you! PR are more than welcome.
Please refers to [this documentation page](https://docs.keycloakify.dev/contributing) that will help you get started.

135
README.md
View File

@ -1,129 +1,40 @@
<p align="center">
<img src="https://user-images.githubusercontent.com/6702424/109387840-eba11f80-7903-11eb-9050-db1dad883f78.png">
<img src="https://user-images.githubusercontent.com/6702424/80216211-00ef5280-863e-11ea-81de-59f3a3d4b8e4.png">
</p>
<p align="center">
<i>🔏 Create Keycloak themes using React 🔏</i>
<i>Provides a way to customize Keycloak login and register pages with React</i>
<br>
<br>
<a href="https://github.com/garronej/keycloakify/actions">
<img src="https://github.com/garronej/keycloakify/workflows/ci/badge.svg?branch=main">
</a>
<a href="https://bundlephobia.com/package/keycloakify">
<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/dm/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">
<img src="https://awesome.re/mentioned-badge.svg"/>
</a>
<p align="center">
<a href="https://www.keycloakify.dev">Home</a>
-
<a href="https://docs.keycloakify.dev">Documentation</a>
<img src="https://github.com/garronej/keycloak-react-theming/workflows/ci/badge.svg?branch=develop">
<img src="https://img.shields.io/bundlephobia/minzip/keycloak-react-theming">
<img src="https://img.shields.io/npm/dw/keycloak-react-theming">
<img src="https://img.shields.io/npm/l/keycloak-react-theming">
</p>
</p>
<p align="center">
<i>Ultimately this build tool generates a Keycloak theme <a href="https://www.keycloakify.dev">Learn more</a></i>
<img src="https://user-images.githubusercontent.com/6702424/110260457-a1c3d380-7fac-11eb-853a-80459b65626b.png">
<a href="https://github.com/garronej/keycloak-react-theming">Home</a>
-
<a href="https://github.com/garronej/keycloak-react-theming">Documentation</a>
</p>
# Changelog highlights
# REQUIREMENT
## For building the theme:
## 5.8.0
- `mvn` must be installed
- [React.lazy()](https://reactjs.org/docs/code-splitting.html#reactlazy) support 🎉. [#141](https://github.com/InseeFrLab/keycloakify/issues/141)
## For development, (testing the theme on a docker container ect ):
## 5.7.0
- `rm`
- `mkdir` )
- `wget`
- `unzip`
- Feat `logout-confirm.ftl`. [PR](https://github.com/InseeFrLab/keycloakify/pull/120)
Tested on MacOS
## 5.6.4
# USAGE
Fix `login-verify-email.ftl` page. [Before](https://user-images.githubusercontent.com/6702424/177436014-0bad22c4-5bfb-45bb-8fc9-dad65143cd0c.png) - [After](https://user-images.githubusercontent.com/6702424/177435797-ec5d7db3-84cf-49cb-8efc-3427a81f744e.png)
## Build the theme:
`npx build-keycloak-theme`
## v5.6.0
## (Optional/Debug) Download more themes:
Add support for `login-config-totp.ftl` page [#127](https://github.com/InseeFrLab/keycloakify/pull/127).
## v5.3.0
Rename `keycloak_theme_email` to `keycloak_email`.
If you already had a `keycloak_theme_email` you should rename it `keycloak_email`.
## v5.0.0
[Migration guide](https://github.com/garronej/keycloakify-demo-app/blob/a5b6a50f24bc25e082931f5ad9ebf47492acd12a/src/index.tsx#L46-L63)
New i18n system.
Import of terms and services have changed. [See example](https://github.com/garronej/keycloakify-demo-app/blob/a5b6a50f24bc25e082931f5ad9ebf47492acd12a/src/index.tsx#L46-L63).
## v4.10.0
Add `login-idp-link-email.ftl` page [See PR](https://github.com/InseeFrLab/keycloakify/pull/92).
## v4.8.0
[Email template customization.](#email-template-customization)
## v4.7.4
**M1 Mac** support (for testing locally with a dockerized Keycloak).
## v4.7.2
> WARNING: This is broken.
> Testing with local Keycloak container working with M1 Mac. Thanks to [@eduardosanzb](https://github.com/InseeFrLab/keycloakify/issues/43#issuecomment-975699658).
> Be aware: When running M1s you are testing with Keycloak v15 else the local container spun will be a Keycloak v16.1.0.
## v4.7.0
Register with user profile enabled: Out of the box `options` validator support.
[Example](https://user-images.githubusercontent.com/6702424/158911163-81e6bbe8-feb0-4dc8-abff-de199d7a678e.mov)
## v4.6.0
`tss-react` and `powerhooks` are no longer peer dependencies of `keycloakify`.
After updating Keycloakify you can remove `tss-react` and `powerhooks` from your dependencies if you don't use them explicitly.
## v4.5.3
There is a new recommended way to setup highly customized theme. See [here](https://github.com/garronej/keycloakify-demo-app/blob/look_and_feel/src/KcApp/KcApp.tsx).
Unlike with [the previous recommended method](https://github.com/garronej/keycloakify-demo-app/blob/a51660578bea15fb3e506b8a2b78e1056c6d68bb/src/KcApp/KcApp.tsx),
with this new method your theme wont break on minor Keycloakify update.
## 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
- Out of the box [frontend form validation](#user-profile-and-frontend-form-validation) 🥳
- Improvements (and breaking changes in `import { useKcMessage } from "keycloakify"`.
## v3
No breaking changes except that `@emotion/react`, [`tss-react`](https://www.npmjs.com/package/tss-react) and [`powerhooks`](https://www.npmjs.com/package/powerhooks) are now `peerDependencies` instead of being just dependencies.
It's important to avoid problem when using `keycloakify` alongside [`mui`](https://mui.com) and
[when passing params from the app to the login page](https://github.com/InseeFrLab/keycloakify#implement-context-persistence-optional).
## v2.5
- Feature [Use advanced message](https://github.com/InseeFrLab/keycloakify/blob/59f106bf9e210b63b190826da2bf5f75fc8b7644/src/lib/i18n/useKcMessage.tsx#L53-L66)
and [`messagesPerFields`](https://github.com/InseeFrLab/keycloakify/blob/59f106bf9e210b63b190826da2bf5f75fc8b7644/src/lib/getKcContext/KcContextBase.ts#L70-L75) (implementation [here](https://github.com/InseeFrLab/keycloakify/blob/59f106bf9e210b63b190826da2bf5f75fc8b7644/src/bin/build-keycloak-theme/generateFtl/common.ftl#L130-L189))
- Test container now uses Keycloak version `15.0.2`.
## v2
- It's now possible to implement custom `.ftl` pages.
- Support for Keycloak plugins that introduce non standard ftl values.
(Like for example [this plugin](https://github.com/micedre/keycloak-mail-whitelisting) that define `authorizedMailDomains` in `register.ftl`).
`npx download-sample-keycloak-themes`

View File

@ -0,0 +1,5 @@
/** Files for being able to run a hot reload keycloak container */
export declare function generateDebugFiles(params: {
packageJsonName: string;
keycloakThemeBuildingDirPath: string;
}): void;

View File

@ -0,0 +1,65 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateDebugFiles = void 0;
var fs = __importStar(require("fs"));
var path_1 = require("path");
/** Files for being able to run a hot reload keycloak container */
function generateDebugFiles(params) {
var packageJsonName = params.packageJsonName, keycloakThemeBuildingDirPath = params.keycloakThemeBuildingDirPath;
fs.writeFileSync(path_1.join(keycloakThemeBuildingDirPath, "Dockerfile"), Buffer.from([
"FROM jboss/keycloak:11.0.3",
"",
"USER root",
"",
"WORKDIR /",
"",
"ADD configuration /opt/jboss/keycloak/standalone/configuration/",
"",
'ENTRYPOINT [ "/opt/jboss/tools/docker-entrypoint.sh" ]',
].join("\n"), "utf8"));
var dockerImage = packageJsonName + "/keycloak-hot-reload";
var containerName = "keycloak-testing-container";
fs.writeFileSync(path_1.join(keycloakThemeBuildingDirPath, "start_keycloak_testing_container.sh"), Buffer.from([
"#!/bin/bash",
"",
"docker rm " + containerName + " || true",
"",
"docker build . -t " + dockerImage,
"",
"docker run \\",
" -p 8080:8080 \\",
"\t--name " + containerName + " \\",
" -e KEYCLOAK_USER=admin \\",
" -e KEYCLOAK_PASSWORD=admin \\",
"\t-v " + path_1.join(keycloakThemeBuildingDirPath, "src", "main", "resources", "theme", packageJsonName) + ":/opt/jboss/keycloak/themes/" + packageJsonName + ":rw \\",
"\t-it " + dockerImage + ":latest",
""
].join("\n"), "utf8"), { "mode": 493 });
var standaloneHaFilePath = path_1.join(keycloakThemeBuildingDirPath, "configuration", "standalone-ha.xml");
try {
fs.mkdirSync(path_1.dirname(standaloneHaFilePath));
}
catch (_a) { }
fs.writeFileSync(standaloneHaFilePath, fs.readFileSync(path_1.join(__dirname, path_1.basename(standaloneHaFilePath))));
}
exports.generateDebugFiles = generateDebugFiles;
//# sourceMappingURL=index.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/bin/build-keycloak-theme/generateDebugFiles/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;AACA,qCAAyB;AACzB,6BAA0F;AAE1F,kEAAkE;AAClE,SAAgB,kBAAkB,CAC9B,MAGC;IAGO,IAAA,eAAe,GAAmC,MAAM,gBAAzC,EAAE,4BAA4B,GAAK,MAAM,6BAAX,CAAY;IAEjE,EAAE,CAAC,aAAa,CACZ,WAAQ,CAAC,4BAA4B,EAAE,YAAY,CAAC,EACpD,MAAM,CAAC,IAAI,CACP;QACI,4BAA4B;QAC5B,EAAE;QACF,WAAW;QACX,EAAE;QACF,WAAW;QACX,EAAE;QACF,iEAAiE;QACjE,EAAE;QACF,wDAAwD;KAC3D,CAAC,IAAI,CAAC,IAAI,CAAC,EACZ,MAAM,CACT,CACJ,CAAC;IAEF,IAAM,WAAW,GAAM,eAAe,yBAAsB,CAAC;IAC7D,IAAM,aAAa,GAAG,4BAA4B,CAAC;IAEnD,EAAE,CAAC,aAAa,CACZ,WAAQ,CAAC,4BAA4B,EAAE,qCAAqC,CAAC,EAC7E,MAAM,CAAC,IAAI,CACP;QACI,aAAa;QACb,EAAE;QACF,eAAa,aAAa,aAAU;QACpC,EAAE;QACF,uBAAqB,WAAa;QAClC,EAAE;QACF,eAAe;QACf,kBAAkB;QAClB,cAAW,aAAa,QAAK;QAC7B,4BAA4B;QAC5B,gCAAgC;QAChC,UAAO,WAAQ,CAAC,4BAA4B,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,eAAe,CAAC,oCACpE,eAAe,WAAQ;QACtD,WAAQ,WAAW,YAAS;QAC5B,EAAE;KACL,CAAC,IAAI,CAAC,IAAI,CAAC,EACZ,MAAM,CACT,EACD,EAAE,MAAM,EAAE,GAAK,EAAE,CACpB,CAAC;IAEF,IAAM,oBAAoB,GAAG,WAAQ,CAAC,4BAA4B,EAAE,eAAe,EAAE,mBAAmB,CAAC,CAAC;IAE1G,IAAI;QAAE,EAAE,CAAC,SAAS,CAAC,cAAW,CAAC,oBAAoB,CAAC,CAAC,CAAC;KAAE;IAAC,WAAM,GAAG;IAElE,EAAE,CAAC,aAAa,CACZ,oBAAoB,EACpB,EAAE,CAAC,YAAY,CAAC,WAAQ,CAAC,SAAS,EAAE,eAAY,CAAC,oBAAoB,CAAC,CAAC,CAAC,CAC3E,CAAC;AAEN,CAAC;AAhED,gDAgEC"}

View File

@ -0,0 +1,666 @@
<?xml version='1.0' encoding='UTF-8'?>
<server xmlns="urn:jboss:domain:13.0">
<extensions>
<extension module="org.jboss.as.clustering.infinispan"/>
<extension module="org.jboss.as.clustering.jgroups"/>
<extension module="org.jboss.as.connector"/>
<extension module="org.jboss.as.deployment-scanner"/>
<extension module="org.jboss.as.ee"/>
<extension module="org.jboss.as.ejb3"/>
<extension module="org.jboss.as.jaxrs"/>
<extension module="org.jboss.as.jmx"/>
<extension module="org.jboss.as.jpa"/>
<extension module="org.jboss.as.logging"/>
<extension module="org.jboss.as.mail"/>
<extension module="org.jboss.as.modcluster"/>
<extension module="org.jboss.as.naming"/>
<extension module="org.jboss.as.remoting"/>
<extension module="org.jboss.as.security"/>
<extension module="org.jboss.as.transactions"/>
<extension module="org.jboss.as.weld"/>
<extension module="org.keycloak.keycloak-server-subsystem"/>
<extension module="org.wildfly.extension.bean-validation"/>
<extension module="org.wildfly.extension.core-management"/>
<extension module="org.wildfly.extension.elytron"/>
<extension module="org.wildfly.extension.io"/>
<extension module="org.wildfly.extension.microprofile.config-smallrye"/>
<extension module="org.wildfly.extension.microprofile.health-smallrye"/>
<extension module="org.wildfly.extension.microprofile.metrics-smallrye"/>
<extension module="org.wildfly.extension.request-controller"/>
<extension module="org.wildfly.extension.security.manager"/>
<extension module="org.wildfly.extension.undertow"/>
</extensions>
<management>
<security-realms>
<security-realm name="ManagementRealm">
<authentication>
<local default-user="$local" skip-group-loading="true"/>
<properties path="mgmt-users.properties" relative-to="jboss.server.config.dir"/>
</authentication>
<authorization map-groups-to-roles="false">
<properties path="mgmt-groups.properties" relative-to="jboss.server.config.dir"/>
</authorization>
</security-realm>
<security-realm name="ApplicationRealm">
<server-identities>
<ssl>
<keystore path="application.keystore" relative-to="jboss.server.config.dir" keystore-password="password" alias="server" key-password="password" generate-self-signed-certificate-host="localhost"/>
</ssl>
</server-identities>
<authentication>
<local default-user="$local" allowed-users="*" skip-group-loading="true"/>
<properties path="application-users.properties" relative-to="jboss.server.config.dir"/>
</authentication>
<authorization>
<properties path="application-roles.properties" relative-to="jboss.server.config.dir"/>
</authorization>
</security-realm>
</security-realms>
<audit-log>
<formatters>
<json-formatter name="json-formatter"/>
</formatters>
<handlers>
<file-handler name="file" formatter="json-formatter" path="audit-log.log" relative-to="jboss.server.data.dir"/>
</handlers>
<logger log-boot="true" log-read-only="false" enabled="false">
<handlers>
<handler name="file"/>
</handlers>
</logger>
</audit-log>
<management-interfaces>
<http-interface security-realm="ManagementRealm">
<http-upgrade enabled="true"/>
<socket-binding http="management-http"/>
</http-interface>
</management-interfaces>
<access-control provider="simple">
<role-mapping>
<role name="SuperUser">
<include>
<user name="$local"/>
</include>
</role>
</role-mapping>
</access-control>
</management>
<profile>
<subsystem xmlns="urn:jboss:domain:logging:8.0">
<console-handler name="CONSOLE">
<formatter>
<named-formatter name="COLOR-PATTERN"/>
</formatter>
</console-handler>
<logger category="com.arjuna">
<level name="WARN"/>
</logger>
<logger category="io.jaegertracing.Configuration">
<level name="WARN"/>
</logger>
<logger category="org.jboss.as.config">
<level name="DEBUG"/>
</logger>
<logger category="sun.rmi">
<level name="WARN"/>
</logger>
<logger category="org.keycloak">
<level name="${env.KEYCLOAK_LOGLEVEL:INFO}"/>
</logger>
<root-logger>
<level name="${env.ROOT_LOGLEVEL:INFO}"/>
<handlers>
<handler name="CONSOLE"/>
</handlers>
</root-logger>
<formatter name="PATTERN">
<pattern-formatter pattern="%d{yyyy-MM-dd HH:mm:ss,SSS} %-5p [%c] (%t) %s%e%n"/>
</formatter>
<formatter name="COLOR-PATTERN">
<pattern-formatter pattern="%K{level}%d{HH:mm:ss,SSS} %-5p [%c] (%t) %s%e%n"/>
</formatter>
</subsystem>
<subsystem xmlns="urn:jboss:domain:bean-validation:1.0"/>
<subsystem xmlns="urn:jboss:domain:core-management:1.0"/>
<subsystem xmlns="urn:jboss:domain:datasources:6.0">
<datasources>
<datasource jndi-name="java:jboss/datasources/ExampleDS" pool-name="ExampleDS" enabled="true" use-java-context="true" statistics-enabled="${wildfly.datasources.statistics-enabled:${wildfly.statistics-enabled:false}}">
<connection-url>jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE</connection-url>
<driver>h2</driver>
<security>
<user-name>sa</user-name>
<password>sa</password>
</security>
</datasource>
<datasource jndi-name="java:jboss/datasources/KeycloakDS" pool-name="KeycloakDS" enabled="true" use-java-context="true" statistics-enabled="${wildfly.datasources.statistics-enabled:${wildfly.statistics-enabled:false}}">
<connection-url>jdbc:h2:${jboss.server.data.dir}/keycloak;AUTO_SERVER=TRUE</connection-url>
<driver>h2</driver>
<pool>
<max-pool-size>100</max-pool-size>
</pool>
<security>
<user-name>sa</user-name>
<password>sa</password>
</security>
</datasource>
<drivers>
<driver name="h2" module="com.h2database.h2">
<xa-datasource-class>org.h2.jdbcx.JdbcDataSource</xa-datasource-class>
</driver>
</drivers>
</datasources>
</subsystem>
<subsystem xmlns="urn:jboss:domain:deployment-scanner:2.0">
<deployment-scanner path="deployments" relative-to="jboss.server.base.dir" scan-interval="5000" runtime-failure-causes-rollback="${jboss.deployment.scanner.rollback.on.failure:false}"/>
</subsystem>
<subsystem xmlns="urn:jboss:domain:ee:5.0">
<spec-descriptor-property-replacement>false</spec-descriptor-property-replacement>
<concurrent>
<context-services>
<context-service name="default" jndi-name="java:jboss/ee/concurrency/context/default" use-transaction-setup-provider="true"/>
</context-services>
<managed-thread-factories>
<managed-thread-factory name="default" jndi-name="java:jboss/ee/concurrency/factory/default" context-service="default"/>
</managed-thread-factories>
<managed-executor-services>
<managed-executor-service name="default" jndi-name="java:jboss/ee/concurrency/executor/default" context-service="default" hung-task-threshold="60000" keepalive-time="5000"/>
</managed-executor-services>
<managed-scheduled-executor-services>
<managed-scheduled-executor-service name="default" jndi-name="java:jboss/ee/concurrency/scheduler/default" context-service="default" hung-task-threshold="60000" keepalive-time="3000"/>
</managed-scheduled-executor-services>
</concurrent>
<default-bindings context-service="java:jboss/ee/concurrency/context/default" datasource="java:jboss/datasources/ExampleDS" managed-executor-service="java:jboss/ee/concurrency/executor/default" managed-scheduled-executor-service="java:jboss/ee/concurrency/scheduler/default" managed-thread-factory="java:jboss/ee/concurrency/factory/default"/>
</subsystem>
<subsystem xmlns="urn:jboss:domain:ejb3:7.0">
<session-bean>
<stateless>
<bean-instance-pool-ref pool-name="slsb-strict-max-pool"/>
</stateless>
<stateful default-access-timeout="5000" cache-ref="distributable" passivation-disabled-cache-ref="simple"/>
<singleton default-access-timeout="5000"/>
</session-bean>
<pools>
<bean-instance-pools>
<strict-max-pool name="mdb-strict-max-pool" derive-size="from-cpu-count" instance-acquisition-timeout="5" instance-acquisition-timeout-unit="MINUTES"/>
<strict-max-pool name="slsb-strict-max-pool" derive-size="from-worker-pools" instance-acquisition-timeout="5" instance-acquisition-timeout-unit="MINUTES"/>
</bean-instance-pools>
</pools>
<caches>
<cache name="simple"/>
<cache name="distributable" passivation-store-ref="infinispan" aliases="passivating clustered"/>
</caches>
<passivation-stores>
<passivation-store name="infinispan" cache-container="ejb" max-size="10000"/>
</passivation-stores>
<async thread-pool-name="default"/>
<timer-service thread-pool-name="default" default-data-store="default-file-store">
<data-stores>
<file-data-store name="default-file-store" path="timer-service-data" relative-to="jboss.server.data.dir"/>
</data-stores>
</timer-service>
<remote connector-ref="http-remoting-connector" thread-pool-name="default">
<channel-creation-options>
<option name="MAX_OUTBOUND_MESSAGES" value="1234" type="remoting"/>
</channel-creation-options>
</remote>
<thread-pools>
<thread-pool name="default">
<max-threads count="10"/>
<keepalive-time time="60" unit="seconds"/>
</thread-pool>
</thread-pools>
<default-security-domain value="other"/>
<default-missing-method-permissions-deny-access value="true"/>
<statistics enabled="${wildfly.ejb3.statistics-enabled:${wildfly.statistics-enabled:false}}"/>
<log-system-exceptions value="true"/>
</subsystem>
<subsystem xmlns="urn:wildfly:elytron:10.0" final-providers="combined-providers" disallowed-providers="OracleUcrypto">
<providers>
<aggregate-providers name="combined-providers">
<providers name="elytron"/>
<providers name="openssl"/>
</aggregate-providers>
<provider-loader name="elytron" module="org.wildfly.security.elytron"/>
<provider-loader name="openssl" module="org.wildfly.openssl"/>
</providers>
<audit-logging>
<file-audit-log name="local-audit" path="audit.log" relative-to="jboss.server.log.dir" format="JSON"/>
</audit-logging>
<security-domains>
<security-domain name="ApplicationDomain" default-realm="ApplicationRealm" permission-mapper="default-permission-mapper">
<realm name="ApplicationRealm" role-decoder="groups-to-roles"/>
<realm name="local"/>
</security-domain>
<security-domain name="ManagementDomain" default-realm="ManagementRealm" permission-mapper="default-permission-mapper">
<realm name="ManagementRealm" role-decoder="groups-to-roles"/>
<realm name="local" role-mapper="super-user-mapper"/>
</security-domain>
</security-domains>
<security-realms>
<identity-realm name="local" identity="$local"/>
<properties-realm name="ApplicationRealm">
<users-properties path="application-users.properties" relative-to="jboss.server.config.dir" digest-realm-name="ApplicationRealm"/>
<groups-properties path="application-roles.properties" relative-to="jboss.server.config.dir"/>
</properties-realm>
<properties-realm name="ManagementRealm">
<users-properties path="mgmt-users.properties" relative-to="jboss.server.config.dir" digest-realm-name="ManagementRealm"/>
<groups-properties path="mgmt-groups.properties" relative-to="jboss.server.config.dir"/>
</properties-realm>
</security-realms>
<mappers>
<simple-permission-mapper name="default-permission-mapper" mapping-mode="first">
<permission-mapping>
<principal name="anonymous"/>
<permission-set name="default-permissions"/>
</permission-mapping>
<permission-mapping match-all="true">
<permission-set name="login-permission"/>
<permission-set name="default-permissions"/>
</permission-mapping>
</simple-permission-mapper>
<constant-realm-mapper name="local" realm-name="local"/>
<simple-role-decoder name="groups-to-roles" attribute="groups"/>
<constant-role-mapper name="super-user-mapper">
<role name="SuperUser"/>
</constant-role-mapper>
</mappers>
<permission-sets>
<permission-set name="login-permission">
<permission class-name="org.wildfly.security.auth.permission.LoginPermission"/>
</permission-set>
<permission-set name="default-permissions">
<permission class-name="org.wildfly.extension.batch.jberet.deployment.BatchPermission" module="org.wildfly.extension.batch.jberet" target-name="*"/>
<permission class-name="org.wildfly.transaction.client.RemoteTransactionPermission" module="org.wildfly.transaction.client"/>
<permission class-name="org.jboss.ejb.client.RemoteEJBPermission" module="org.jboss.ejb-client"/>
</permission-set>
</permission-sets>
<http>
<http-authentication-factory name="management-http-authentication" security-domain="ManagementDomain" http-server-mechanism-factory="global">
<mechanism-configuration>
<mechanism mechanism-name="DIGEST">
<mechanism-realm realm-name="ManagementRealm"/>
</mechanism>
</mechanism-configuration>
</http-authentication-factory>
<provider-http-server-mechanism-factory name="global"/>
</http>
<sasl>
<sasl-authentication-factory name="application-sasl-authentication" sasl-server-factory="configured" security-domain="ApplicationDomain">
<mechanism-configuration>
<mechanism mechanism-name="JBOSS-LOCAL-USER" realm-mapper="local"/>
<mechanism mechanism-name="DIGEST-MD5">
<mechanism-realm realm-name="ApplicationRealm"/>
</mechanism>
</mechanism-configuration>
</sasl-authentication-factory>
<sasl-authentication-factory name="management-sasl-authentication" sasl-server-factory="configured" security-domain="ManagementDomain">
<mechanism-configuration>
<mechanism mechanism-name="JBOSS-LOCAL-USER" realm-mapper="local"/>
<mechanism mechanism-name="DIGEST-MD5">
<mechanism-realm realm-name="ManagementRealm"/>
</mechanism>
</mechanism-configuration>
</sasl-authentication-factory>
<configurable-sasl-server-factory name="configured" sasl-server-factory="elytron">
<properties>
<property name="wildfly.sasl.local-user.default-user" value="$local"/>
</properties>
</configurable-sasl-server-factory>
<mechanism-provider-filtering-sasl-server-factory name="elytron" sasl-server-factory="global">
<filters>
<filter provider-name="WildFlyElytron"/>
</filters>
</mechanism-provider-filtering-sasl-server-factory>
<provider-sasl-server-factory name="global"/>
</sasl>
</subsystem>
<subsystem xmlns="urn:jboss:domain:infinispan:10.0">
<cache-container name="keycloak" module="org.keycloak.keycloak-model-infinispan">
<transport lock-timeout="60000"/>
<local-cache name="realms">
<object-memory size="10000"/>
</local-cache>
<local-cache name="users">
<object-memory size="10000"/>
</local-cache>
<local-cache name="authorization">
<object-memory size="10000"/>
</local-cache>
<local-cache name="keys">
<object-memory size="1000"/>
<expiration max-idle="3600000"/>
</local-cache>
<replicated-cache name="work"/>
<distributed-cache name="sessions" owners="1"/>
<distributed-cache name="authenticationSessions" owners="1"/>
<distributed-cache name="offlineSessions" owners="1"/>
<distributed-cache name="clientSessions" owners="1"/>
<distributed-cache name="offlineClientSessions" owners="1"/>
<distributed-cache name="loginFailures" owners="1"/>
<distributed-cache name="actionTokens" owners="2">
<object-memory size="-1"/>
<expiration interval="300000" max-idle="-1"/>
</distributed-cache>
</cache-container>
<cache-container name="server" aliases="singleton cluster" default-cache="default" module="org.wildfly.clustering.server">
<transport lock-timeout="60000"/>
<replicated-cache name="default">
<transaction mode="BATCH"/>
</replicated-cache>
</cache-container>
<cache-container name="web" default-cache="dist" module="org.wildfly.clustering.web.infinispan">
<transport lock-timeout="60000"/>
<replicated-cache name="sso">
<locking isolation="REPEATABLE_READ"/>
<transaction mode="BATCH"/>
</replicated-cache>
<distributed-cache name="dist">
<locking isolation="REPEATABLE_READ"/>
<transaction mode="BATCH"/>
<file-store/>
</distributed-cache>
<distributed-cache name="routing"/>
</cache-container>
<cache-container name="ejb" aliases="sfsb" default-cache="dist" module="org.wildfly.clustering.ejb.infinispan">
<transport lock-timeout="60000"/>
<distributed-cache name="dist">
<locking isolation="REPEATABLE_READ"/>
<transaction mode="BATCH"/>
<file-store/>
</distributed-cache>
</cache-container>
<cache-container name="hibernate" module="org.infinispan.hibernate-cache">
<transport lock-timeout="60000"/>
<local-cache name="local-query">
<object-memory size="10000"/>
<expiration max-idle="100000"/>
</local-cache>
<invalidation-cache name="entity">
<transaction mode="NON_XA"/>
<object-memory size="10000"/>
<expiration max-idle="100000"/>
</invalidation-cache>
<replicated-cache name="timestamps"/>
</cache-container>
</subsystem>
<subsystem xmlns="urn:jboss:domain:io:3.0">
<worker name="default"/>
<buffer-pool name="default"/>
</subsystem>
<subsystem xmlns="urn:jboss:domain:jaxrs:2.0"/>
<subsystem xmlns="urn:jboss:domain:jca:5.0">
<archive-validation enabled="true" fail-on-error="true" fail-on-warn="false"/>
<bean-validation enabled="true"/>
<default-workmanager>
<short-running-threads>
<core-threads count="50"/>
<queue-length count="50"/>
<max-threads count="50"/>
<keepalive-time time="10" unit="seconds"/>
</short-running-threads>
<long-running-threads>
<core-threads count="50"/>
<queue-length count="50"/>
<max-threads count="50"/>
<keepalive-time time="10" unit="seconds"/>
</long-running-threads>
</default-workmanager>
<cached-connection-manager/>
</subsystem>
<subsystem xmlns="urn:jboss:domain:jgroups:8.0">
<channels default="ee">
<channel name="ee" stack="udp" cluster="ejb"/>
</channels>
<stacks>
<stack name="udp">
<transport type="UDP" socket-binding="jgroups-udp"/>
<protocol type="PING"/>
<protocol type="MERGE3"/>
<socket-protocol type="FD_SOCK" socket-binding="jgroups-udp-fd"/>
<protocol type="FD_ALL"/>
<protocol type="VERIFY_SUSPECT"/>
<protocol type="pbcast.NAKACK2"/>
<protocol type="UNICAST3"/>
<protocol type="pbcast.STABLE"/>
<protocol type="pbcast.GMS"/>
<protocol type="UFC"/>
<protocol type="MFC"/>
<protocol type="FRAG3"/>
</stack>
<stack name="tcp">
<transport type="TCP" socket-binding="jgroups-tcp"/>
<socket-protocol type="MPING" socket-binding="jgroups-mping"/>
<protocol type="MERGE3"/>
<socket-protocol type="FD_SOCK" socket-binding="jgroups-tcp-fd"/>
<protocol type="FD_ALL"/>
<protocol type="VERIFY_SUSPECT"/>
<protocol type="pbcast.NAKACK2"/>
<protocol type="UNICAST3"/>
<protocol type="pbcast.STABLE"/>
<protocol type="pbcast.GMS"/>
<protocol type="MFC"/>
<protocol type="FRAG3"/>
</stack>
</stacks>
</subsystem>
<subsystem xmlns="urn:jboss:domain:jmx:1.3">
<expose-resolved-model/>
<expose-expression-model/>
<remoting-connector/>
</subsystem>
<subsystem xmlns="urn:jboss:domain:jpa:1.1">
<jpa default-datasource="" default-extended-persistence-inheritance="DEEP"/>
</subsystem>
<subsystem xmlns="urn:jboss:domain:keycloak-server:1.1">
<web-context>auth</web-context>
<providers>
<provider>
classpath:${jboss.home.dir}/providers/*
</provider>
</providers>
<master-realm-name>master</master-realm-name>
<scheduled-task-interval>900</scheduled-task-interval>
<theme>
<staticMaxAge>-1</staticMaxAge>
<cacheThemes>false</cacheThemes>
<cacheTemplates>false</cacheTemplates>
<welcomeTheme>${env.KEYCLOAK_WELCOME_THEME:keycloak}</welcomeTheme>
<default>${env.KEYCLOAK_DEFAULT_THEME:keycloak}</default>
<dir>${jboss.home.dir}/themes</dir>
</theme>
<spi name="eventsStore">
<provider name="jpa" enabled="true">
<properties>
<property name="exclude-events" value="[&quot;REFRESH_TOKEN&quot;]"/>
</properties>
</provider>
</spi>
<spi name="userCache">
<provider name="default" enabled="true"/>
</spi>
<spi name="userSessionPersister">
<default-provider>jpa</default-provider>
</spi>
<spi name="timer">
<default-provider>basic</default-provider>
</spi>
<spi name="connectionsHttpClient">
<provider name="default" enabled="true"/>
</spi>
<spi name="connectionsJpa">
<provider name="default" enabled="true">
<properties>
<property name="dataSource" value="java:jboss/datasources/KeycloakDS"/>
<property name="initializeEmpty" value="true"/>
<property name="migrationStrategy" value="update"/>
<property name="migrationExport" value="${jboss.home.dir}/keycloak-database-update.sql"/>
</properties>
</provider>
</spi>
<spi name="realmCache">
<provider name="default" enabled="true"/>
</spi>
<spi name="connectionsInfinispan">
<default-provider>default</default-provider>
<provider name="default" enabled="true">
<properties>
<property name="cacheContainer" value="java:jboss/infinispan/container/keycloak"/>
</properties>
</provider>
</spi>
<spi name="jta-lookup">
<default-provider>${keycloak.jta.lookup.provider:jboss}</default-provider>
<provider name="jboss" enabled="true"/>
</spi>
<spi name="publicKeyStorage">
<provider name="infinispan" enabled="true">
<properties>
<property name="minTimeBetweenRequests" value="10"/>
</properties>
</provider>
</spi>
<spi name="x509cert-lookup">
<default-provider>${keycloak.x509cert.lookup.provider:default}</default-provider>
<provider name="default" enabled="true"/>
</spi>
<spi name="hostname">
<default-provider>${keycloak.hostname.provider:default}</default-provider>
<provider name="default" enabled="true">
<properties>
<property name="frontendUrl" value="${keycloak.frontendUrl:}"/>
<property name="forceBackendUrlToFrontendUrl" value="false"/>
</properties>
</provider>
<provider name="fixed" enabled="true">
<properties>
<property name="hostname" value="${keycloak.hostname.fixed.hostname:localhost}"/>
<property name="httpPort" value="${keycloak.hostname.fixed.httpPort:-1}"/>
<property name="httpsPort" value="${keycloak.hostname.fixed.httpsPort:-1}"/>
<property name="alwaysHttps" value="${keycloak.hostname.fixed.alwaysHttps:false}"/>
</properties>
</provider>
</spi>
</subsystem>
<subsystem xmlns="urn:jboss:domain:mail:4.0">
<mail-session name="default" jndi-name="java:jboss/mail/Default">
<smtp-server outbound-socket-binding-ref="mail-smtp"/>
</mail-session>
</subsystem>
<subsystem xmlns="urn:wildfly:microprofile-config-smallrye:1.0"/>
<subsystem xmlns="urn:wildfly:microprofile-health-smallrye:2.0" security-enabled="false" empty-liveness-checks-status="${env.MP_HEALTH_EMPTY_LIVENESS_CHECKS_STATUS:UP}" empty-readiness-checks-status="${env.MP_HEALTH_EMPTY_READINESS_CHECKS_STATUS:UP}"/>
<subsystem xmlns="urn:wildfly:microprofile-metrics-smallrye:2.0" security-enabled="false" exposed-subsystems="*" prefix="${wildfly.metrics.prefix:wildfly}"/>
<subsystem xmlns="urn:jboss:domain:modcluster:5.0">
<proxy name="default" advertise-socket="modcluster" listener="ajp">
<dynamic-load-provider>
<load-metric type="cpu"/>
</dynamic-load-provider>
</proxy>
</subsystem>
<subsystem xmlns="urn:jboss:domain:naming:2.0">
<remote-naming/>
</subsystem>
<subsystem xmlns="urn:jboss:domain:remoting:4.0">
<http-connector name="http-remoting-connector" connector-ref="default" security-realm="ApplicationRealm"/>
</subsystem>
<subsystem xmlns="urn:jboss:domain:request-controller:1.0"/>
<subsystem xmlns="urn:jboss:domain:security:2.0">
<security-domains>
<security-domain name="other" cache-type="default">
<authentication>
<login-module code="Remoting" flag="optional">
<module-option name="password-stacking" value="useFirstPass"/>
</login-module>
<login-module code="RealmDirect" flag="required">
<module-option name="password-stacking" value="useFirstPass"/>
</login-module>
</authentication>
</security-domain>
<security-domain name="jboss-web-policy" cache-type="default">
<authorization>
<policy-module code="Delegating" flag="required"/>
</authorization>
</security-domain>
<security-domain name="jaspitest" cache-type="default">
<authentication-jaspi>
<login-module-stack name="dummy">
<login-module code="Dummy" flag="optional"/>
</login-module-stack>
<auth-module code="Dummy"/>
</authentication-jaspi>
</security-domain>
<security-domain name="jboss-ejb-policy" cache-type="default">
<authorization>
<policy-module code="Delegating" flag="required"/>
</authorization>
</security-domain>
</security-domains>
</subsystem>
<subsystem xmlns="urn:jboss:domain:security-manager:1.0">
<deployment-permissions>
<maximum-set>
<permission class="java.security.AllPermission"/>
</maximum-set>
</deployment-permissions>
</subsystem>
<subsystem xmlns="urn:jboss:domain:transactions:5.0">
<core-environment node-identifier="${jboss.tx.node.id:1}">
<process-id>
<uuid/>
</process-id>
</core-environment>
<recovery-environment socket-binding="txn-recovery-environment" status-socket-binding="txn-status-manager"/>
<coordinator-environment statistics-enabled="${wildfly.transactions.statistics-enabled:${wildfly.statistics-enabled:false}}"/>
<object-store path="tx-object-store" relative-to="jboss.server.data.dir"/>
</subsystem>
<subsystem xmlns="urn:jboss:domain:undertow:11.0" default-server="default-server" default-virtual-host="default-host" default-servlet-container="default" default-security-domain="other" statistics-enabled="${wildfly.undertow.statistics-enabled:${wildfly.statistics-enabled:false}}">
<buffer-cache name="default"/>
<server name="default-server">
<ajp-listener name="ajp" socket-binding="ajp"/>
<http-listener name="default" read-timeout="30000" socket-binding="http" redirect-socket="https" proxy-address-forwarding="${env.PROXY_ADDRESS_FORWARDING:false}" enable-http2="true"/>
<https-listener name="https" read-timeout="30000" socket-binding="https" proxy-address-forwarding="${env.PROXY_ADDRESS_FORWARDING:false}" security-realm="ApplicationRealm" enable-http2="true"/>
<host name="default-host" alias="localhost">
<location name="/" handler="welcome-content"/>
<http-invoker security-realm="ApplicationRealm"/>
</host>
</server>
<servlet-container name="default">
<jsp-config/>
<websockets/>
</servlet-container>
<handlers>
<file name="welcome-content" path="${jboss.home.dir}/welcome-content"/>
</handlers>
</subsystem>
<subsystem xmlns="urn:jboss:domain:weld:4.0"/>
</profile>
<interfaces>
<interface name="management">
<inet-address value="${jboss.bind.address.management:127.0.0.1}"/>
</interface>
<interface name="private">
<inet-address value="${jboss.bind.address.private:127.0.0.1}"/>
</interface>
<interface name="public">
<inet-address value="${jboss.bind.address:127.0.0.1}"/>
</interface>
</interfaces>
<socket-binding-group name="standard-sockets" default-interface="public" port-offset="${jboss.socket.binding.port-offset:0}">
<socket-binding name="ajp" port="${jboss.ajp.port:8009}"/>
<socket-binding name="http" port="${jboss.http.port:8080}"/>
<socket-binding name="https" port="${jboss.https.port:8443}"/>
<socket-binding name="jgroups-mping" interface="private" multicast-address="${jboss.default.multicast.address:230.0.0.4}" multicast-port="45700"/>
<socket-binding name="jgroups-tcp" interface="private" port="7600"/>
<socket-binding name="jgroups-tcp-fd" interface="private" port="57600"/>
<socket-binding name="jgroups-udp" interface="private" port="55200" multicast-address="${jboss.default.multicast.address:230.0.0.4}" multicast-port="45688"/>
<socket-binding name="jgroups-udp-fd" interface="private" port="54200"/>
<socket-binding name="management-http" interface="management" port="${jboss.management.http.port:9990}"/>
<socket-binding name="management-https" interface="management" port="${jboss.management.https.port:9993}"/>
<socket-binding name="modcluster" multicast-address="${jboss.modcluster.multicast.address:224.0.1.105}" multicast-port="23364"/>
<socket-binding name="txn-recovery-environment" port="4712"/>
<socket-binding name="txn-status-manager" port="4713"/>
<outbound-socket-binding name="mail-smtp">
<remote-destination host="localhost" port="25"/>
</outbound-socket-binding>
</socket-binding-group>
</server>

View File

@ -0,0 +1,11 @@
export declare function generateFtlFilesCodeFactory(params: {
ftlValuesGlobalName: string;
cssGlobalsToDefine: Record<string, string>;
indexHtmlCode: string;
}): {
generateFtlFilesCode: (params: {
pageBasename: "login.ftl" | "register.ftl";
}) => {
ftlCode: string;
};
};

View File

@ -0,0 +1,98 @@
"use strict";
var __read = (this && this.__read) || function (o, n) {
var m = typeof Symbol === "function" && o[Symbol.iterator];
if (!m) return o;
var i = m.call(o), r, ar = [], e;
try {
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
}
catch (error) { e = { error: error }; }
finally {
try {
if (r && !r.done && (m = i["return"])) m.call(i);
}
finally { if (e) throw e.error; }
}
return ar;
};
var __spread = (this && this.__spread) || function () {
for (var ar = [], i = 0; i < arguments.length; i++) ar = ar.concat(__read(arguments[i]));
return ar;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateFtlFilesCodeFactory = void 0;
var cheerio_1 = __importDefault(require("cheerio"));
var replaceImportFromStatic_1 = require("./replaceImportFromStatic");
function generateFtlFilesCodeFactory(params) {
var ftlValuesGlobalName = params.ftlValuesGlobalName, cssGlobalsToDefine = params.cssGlobalsToDefine, indexHtmlCode = params.indexHtmlCode;
var $ = cheerio_1.default.load(indexHtmlCode);
$("script:not([src])").each(function () {
var _a = [];
for (var _i = 0; _i < arguments.length; _i++) {
_a[_i] = arguments[_i];
}
var _b = __read(_a, 2), element = _b[1];
var fixedJsCode = replaceImportFromStatic_1.replaceImportFromStaticInJsCode({
ftlValuesGlobalName: ftlValuesGlobalName,
"jsCode": $(element).html()
}).fixedJsCode;
$(element).text(fixedJsCode);
});
[
["link", "href"],
["script", "src"],
].forEach(function (_a) {
var _b = __read(_a, 2), selector = _b[0], attrName = _b[1];
return $(selector).each(function () {
var _a = [];
for (var _i = 0; _i < arguments.length; _i++) {
_a[_i] = arguments[_i];
}
var _b = __read(_a, 2), element = _b[1];
var href = $(element).attr(attrName);
if (!(href === null || href === void 0 ? void 0 : href.startsWith("/"))) {
return;
}
$(element).attr(attrName, "${url.resourcesPath}" + href);
});
});
$("head").prepend(__spread((Object.keys(cssGlobalsToDefine).length === 0 ? [] : [
'',
'<style>',
replaceImportFromStatic_1.generateCssCodeToDefineGlobals({ cssGlobalsToDefine: cssGlobalsToDefine }).cssCodeToPrependInHead,
'</style>',
''
]), [
'<script>',
' Object.assign(',
" window." + ftlValuesGlobalName + ",",
' {',
' "url": {',
' "loginAction": "${url.loginAction}",',
' "resourcesPath": "${url.resourcesPath}"',
' }',
' }',
' );',
'</script>',
''
]).join("\n"));
var partiallyFixedIndexHtmlCode = $.html();
function generateFtlFilesCode(params) {
var pageBasename = params.pageBasename;
var $ = cheerio_1.default.load(partiallyFixedIndexHtmlCode);
$("head").prepend([
'',
'<script>',
" window." + ftlValuesGlobalName + " = { \"pageBasename\": \"" + pageBasename + "\" };",
'</script>',
''
].join("\n"));
return { "ftlCode": $.html() };
}
return { generateFtlFilesCode: generateFtlFilesCode };
}
exports.generateFtlFilesCodeFactory = generateFtlFilesCodeFactory;
//# sourceMappingURL=generateFtl.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"generateFtl.js","sourceRoot":"","sources":["../../src/bin/build-keycloak-theme/generateFtl.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAEA,oDAA8B;AAC9B,qEAGmC;AAEnC,SAAgB,2BAA2B,CACvC,MAIC;IAGO,IAAA,mBAAmB,GAAwC,MAAM,oBAA9C,EAAE,kBAAkB,GAAoB,MAAM,mBAA1B,EAAE,aAAa,GAAK,MAAM,cAAX,CAAY;IAE1E,IAAM,CAAC,GAAG,iBAAO,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;IAEtC,CAAC,CAAC,mBAAmB,CAAC,CAAC,IAAI,CAAC;QAAC,YAAc;aAAd,UAAc,EAAd,qBAAc,EAAd,IAAc;YAAd,uBAAc;;QAAd,IAAA,KAAA,aAAc,EAAR,OAAO,QAAA,CAAC;QAE/B,IAAA,WAAW,GAAK,yDAA+B,CAAC;YACpD,mBAAmB,qBAAA;YACnB,QAAQ,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,EAAG;SAC/B,CAAC,YAHiB,CAGhB;QAEH,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAEjC,CAAC,CAAC,CAAC;IAGF;QACG,CAAC,MAAM,EAAE,MAAM,CAAC;QAChB,CAAC,QAAQ,EAAE,KAAK,CAAC;KACV,CAAC,OAAO,CAAC,UAAC,EAAoB;YAApB,KAAA,aAAoB,EAAnB,QAAQ,QAAA,EAAE,QAAQ,QAAA;QACpC,OAAA,CAAC,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC;YAAC,YAAc;iBAAd,UAAc,EAAd,qBAAc,EAAd,IAAc;gBAAd,uBAAc;;YAAd,IAAA,KAAA,aAAc,EAAR,OAAO,QAAA,CAAC;YAE5B,IAAM,IAAI,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YAEvC,IAAI,EAAC,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,UAAU,CAAC,GAAG,EAAC,EAAE;gBACxB,OAAO;aACV;YAED,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,QAAQ,EAAE,sBAAsB,GAAG,IAAI,CAAC,CAAC;QAE7D,CAAC,CAAC;IAVF,CAUE,CACL,CAAC;IAEF,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO,CACb,SACO,CAAC,MAAM,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QACpD,EAAE;QACF,SAAS;QACT,wDAA8B,CAC1B,EAAE,kBAAkB,oBAAA,EAAE,CACzB,CAAC,sBAAsB;QACxB,UAAU;QACV,EAAE;KACL,CAAC;QAEF,UAAU;QACV,oBAAoB;QACpB,oBAAkB,mBAAmB,MAAG;QACxC,WAAW;QACX,sBAAsB;QACtB,sDAAsD;QACtD,yDAAyD;QACzD,eAAe;QACf,WAAW;QACX,QAAQ;QACR,WAAW;QACX,EAAE;OACJ,IAAI,CAAC,IAAI,CAAC,CACf,CAAC;IAGF,IAAM,2BAA2B,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IAE7C,SAAS,oBAAoB,CACzB,MAEC;QAGO,IAAA,YAAY,GAAK,MAAM,aAAX,CAAY;QAEhC,IAAM,CAAC,GAAG,iBAAO,CAAC,IAAI,CAAC,2BAA2B,CAAC,CAAC;QAEpD,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO,CACb;YACI,EAAE;YACF,UAAU;YACV,eAAa,mBAAmB,iCAAyB,YAAY,UAAM;YAC3E,WAAW;YACX,EAAE;SACL,CAAC,IAAI,CAAC,IAAI,CAAC,CAEf,CAAC;QAEF,OAAO,EAAE,SAAS,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;IAEnC,CAAC;IAED,OAAO,EAAE,oBAAoB,sBAAA,EAAE,CAAC;AAGpC,CAAC;AAnGD,kEAmGC"}

View File

@ -0,0 +1,9 @@
export declare type ParsedPackageJson = {
name: string;
version: string;
homepage?: string;
};
export declare function generateJavaStackFiles(params: {
parsedPackageJson: ParsedPackageJson;
keycloakThemeBuildingDirPath: string;
}): void;

View File

@ -0,0 +1,71 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateJavaStackFiles = void 0;
var url = __importStar(require("url"));
var fs = __importStar(require("fs"));
var path_1 = require("path");
function generateJavaStackFiles(params) {
var _a = params.parsedPackageJson, name = _a.name, version = _a.version, homepage = _a.homepage, keycloakThemeBuildingDirPath = params.keycloakThemeBuildingDirPath;
{
var pomFileCode = (function generatePomFileCode() {
var groupId = (function () {
var _a, _b;
var fallbackGroupId = "there.was.no.homepage.field.in.the.package.json." + name;
return (!homepage ?
fallbackGroupId : (_b = (_a = url.parse(homepage).host) === null || _a === void 0 ? void 0 : _a.split(".").reverse().join(".")) !== null && _b !== void 0 ? _b : fallbackGroupId) + ".keycloak";
})();
var artefactId = name + "-keycloak-theme";
var pomFileCode = [
"<?xml version=\"1.0\"?>",
"<project xmlns=\"http://maven.apache.org/POM/4.0.0\"",
"\txmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"",
"\txsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd\">",
"\t<modelVersion>4.0.0</modelVersion>",
"\t<groupId>" + groupId + "</groupId>",
"\t<artifactId>" + artefactId + "</artifactId>",
"\t<version>" + version + "</version>",
"\t<name>" + artefactId + "</name>",
"\t<description />",
"</project>"
].join("\n");
return { pomFileCode: pomFileCode };
})().pomFileCode;
fs.writeFileSync(path_1.join(keycloakThemeBuildingDirPath, "pom.xml"), Buffer.from(pomFileCode, "utf8"));
}
{
var themeManifestFilePath = path_1.join(keycloakThemeBuildingDirPath, "src", "main", "resources", "META-INF", "keycloak-themes.json");
try {
fs.mkdirSync(path_1.dirname(themeManifestFilePath));
}
catch (_b) { }
fs.writeFileSync(themeManifestFilePath, Buffer.from(JSON.stringify({
"themes": [
{
"name": name,
"types": ["login"]
}
]
}, null, 2), "utf8"));
}
}
exports.generateJavaStackFiles = generateJavaStackFiles;
//# sourceMappingURL=generateJavaStackFiles.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"generateJavaStackFiles.js","sourceRoot":"","sources":["../../src/bin/build-keycloak-theme/generateJavaStackFiles.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;AACA,uCAA2B;AAC3B,qCAAyB;AACzB,6BAAgE;AAQhE,SAAgB,sBAAsB,CAClC,MAGC;IAIG,IAAA,KAEA,MAAM,kBAFwC,EAAzB,IAAI,UAAA,EAAE,OAAO,aAAA,EAAE,QAAQ,cAAA,EAC5C,4BAA4B,GAC5B,MAAM,6BADsB,CACrB;IAEX;QAEY,IAAA,WAAW,GAAK,CAAC,SAAS,mBAAmB;YAGjD,IAAM,OAAO,GAAG,CAAC;;gBAEb,IAAM,eAAe,GAAG,qDAAmD,IAAM,CAAC;gBAElF,OAAO,CAAC,CAAC,QAAQ,CAAC,CAAC;oBACf,eAAe,CAAC,CAAC,aACjB,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,IAAI,0CAAE,KAAK,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAAC,GAAG,oCAAK,eAAe,CAC9E,GAAG,WAAW,CAAC;YAEpB,CAAC,CAAC,EAAE,CAAC;YAEL,IAAM,UAAU,GAAM,IAAI,oBAAiB,CAAC;YAE5C,IAAM,WAAW,GAAG;gBAChB,yBAAuB;gBACvB,sDAAoD;gBACpD,2DAAwD;gBACxD,sGAAmG;gBACnG,sCAAqC;gBACrC,gBAAa,OAAO,eAAY;gBAChC,mBAAgB,UAAU,kBAAe;gBACzC,gBAAa,OAAO,eAAY;gBAChC,aAAU,UAAU,YAAS;gBAC7B,mBAAkB;gBAClB,YAAY;aACf,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAEb,OAAO,EAAE,WAAW,aAAA,EAAE,CAAC;QAE3B,CAAC,CAAC,EAAE,YAhCe,CAgCd;QAEL,EAAE,CAAC,aAAa,CACZ,WAAQ,CAAC,4BAA4B,EAAE,SAAS,CAAC,EACjD,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,CACnC,CAAC;KAEL;IAED;QAEI,IAAM,qBAAqB,GAAG,WAAQ,CAClC,4BAA4B,EAAE,KAAK,EAAE,MAAM,EAC3C,WAAW,EAAE,UAAU,EAAE,sBAAsB,CAClD,CAAC;QAEF,IAAI;YAEA,EAAE,CAAC,SAAS,CAAC,cAAW,CAAC,qBAAqB,CAAC,CAAC,CAAC;SAEpD;QAAC,WAAM,GAAG;QAEX,EAAE,CAAC,aAAa,CACZ,qBAAqB,EACrB,MAAM,CAAC,IAAI,CACP,IAAI,CAAC,SAAS,CAAC;YACX,QAAQ,EAAE;gBACN;oBACI,MAAM,EAAE,IAAI;oBACZ,OAAO,EAAE,CAAC,OAAO,CAAC;iBACrB;aACJ;SACJ,EAAE,IAAI,EAAE,CAAC,CAAC,EACX,MAAM,CACT,CACJ,CAAC;KAEL;AAEL,CAAC;AArFD,wDAqFC"}

View File

@ -0,0 +1,5 @@
export declare function generateKeycloakThemeResources(params: {
themeName: string;
reactAppBuildDirPath: string;
keycloakThemeBuildingDirPath: string;
}): void;

View File

@ -0,0 +1,76 @@
"use strict";
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateKeycloakThemeResources = void 0;
var transformCodebase_1 = require("../../tools/transformCodebase");
var fs = __importStar(require("fs"));
var path_1 = require("path");
var replaceImportFromStatic_1 = require("./replaceImportFromStatic");
var generateFtl_1 = require("./generateFtl");
var ftlValuesGlobalName = "keycloakFtlValues";
function generateKeycloakThemeResources(params) {
var themeName = params.themeName, reactAppBuildDirPath = params.reactAppBuildDirPath, keycloakThemeBuildingDirPath = params.keycloakThemeBuildingDirPath;
var themeDirPath = path_1.join(keycloakThemeBuildingDirPath, "src", "main", "resources", "theme", themeName, "login");
var allCssGlobalsToDefine = {};
transformCodebase_1.transformCodebase({
"destDirPath": path_1.join(themeDirPath, "resources"),
"srcDirPath": reactAppBuildDirPath,
"transformSourceCodeString": function (_a) {
var filePath = _a.filePath, sourceCode = _a.sourceCode;
if (/\.css?$/i.test(filePath)) {
var _b = replaceImportFromStatic_1.replaceImportFromStaticInCssCode({ "cssCode": sourceCode.toString("utf8") }), cssGlobalsToDefine = _b.cssGlobalsToDefine, fixedCssCode = _b.fixedCssCode;
allCssGlobalsToDefine = __assign(__assign({}, allCssGlobalsToDefine), cssGlobalsToDefine);
return { "modifiedSourceCode": Buffer.from(fixedCssCode, "utf8") };
}
if (/\.js?$/i.test(filePath)) {
var fixedJsCode = replaceImportFromStatic_1.replaceImportFromStaticInJsCode({
"jsCode": sourceCode.toString("utf8"),
ftlValuesGlobalName: ftlValuesGlobalName
}).fixedJsCode;
return { "modifiedSourceCode": Buffer.from(fixedJsCode, "utf8") };
}
return { "modifiedSourceCode": sourceCode };
}
});
var generateFtlFilesCode = generateFtl_1.generateFtlFilesCodeFactory({
"cssGlobalsToDefine": allCssGlobalsToDefine,
ftlValuesGlobalName: ftlValuesGlobalName,
"indexHtmlCode": fs.readFileSync(path_1.join(reactAppBuildDirPath, "index.html")).toString("utf8")
}).generateFtlFilesCode;
["login.ftl", "register.ftl"].forEach(function (pageBasename) {
var ftlCode = generateFtlFilesCode({ pageBasename: pageBasename }).ftlCode;
fs.writeFileSync(path_1.join(themeDirPath, pageBasename), Buffer.from(ftlCode, "utf8"));
});
fs.writeFileSync(path_1.join(themeDirPath, "theme.properties"), Buffer.from("parent=base\n", "utf8"));
}
exports.generateKeycloakThemeResources = generateKeycloakThemeResources;
//# sourceMappingURL=generateKeycloakThemeResources.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"generateKeycloakThemeResources.js","sourceRoot":"","sources":["../../src/bin/build-keycloak-theme/generateKeycloakThemeResources.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACA,mEAAkE;AAClE,qCAAyB;AACzB,6BAAwC;AACxC,qEAGmC;AACnC,6CAA4D;AAE5D,IAAM,mBAAmB,GAAG,mBAAmB,CAAC;AAEhD,SAAgB,8BAA8B,CAC1C,MAIC;IAGO,IAAA,SAAS,GAAyD,MAAM,UAA/D,EAAE,oBAAoB,GAAmC,MAAM,qBAAzC,EAAE,4BAA4B,GAAK,MAAM,6BAAX,CAAY;IAEjF,IAAM,YAAY,GAAG,WAAQ,CAAC,4BAA4B,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;IAErH,IAAI,qBAAqB,GAA2B,EAAE,CAAC;IAEvD,qCAAiB,CAAC;QACd,aAAa,EAAE,WAAQ,CAAC,YAAY,EAAE,WAAW,CAAC;QAClD,YAAY,EAAE,oBAAoB;QAClC,2BAA2B,EAAE,UAAC,EAAwB;gBAAtB,QAAQ,cAAA,EAAE,UAAU,gBAAA;YAEhD,IAAI,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE;gBAErB,IAAA,KAAuC,0DAAgC,CACzE,EAAE,SAAS,EAAE,UAAU,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAC7C,EAFO,kBAAkB,wBAAA,EAAE,YAAY,kBAEvC,CAAC;gBAEF,qBAAqB,yBACd,qBAAqB,GACrB,kBAAkB,CACxB,CAAC;gBAEF,OAAO,EAAE,oBAAoB,EAAE,MAAM,CAAC,IAAI,CAAC,YAAY,EAAE,MAAM,CAAC,EAAE,CAAC;aAEtE;YAED,IAAI,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE;gBAElB,IAAA,WAAW,GAAK,yDAA+B,CAAC;oBACpD,QAAQ,EAAE,UAAU,CAAC,QAAQ,CAAC,MAAM,CAAC;oBACrC,mBAAmB,qBAAA;iBACtB,CAAC,YAHiB,CAGhB;gBAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,EAAE,CAAC;aAErE;YAED,OAAO,EAAE,oBAAoB,EAAE,UAAU,EAAE,CAAC;QAEhD,CAAC;KACJ,CAAC,CAAC;IAEK,IAAA,oBAAoB,GAAK,yCAA2B,CAAC;QACzD,oBAAoB,EAAE,qBAAqB;QAC3C,mBAAmB,qBAAA;QACnB,eAAe,EAAE,EAAE,CAAC,YAAY,CAC5B,WAAQ,CAAC,oBAAoB,EAAE,YAAY,CAAC,CAC/C,CAAC,QAAQ,CAAC,MAAM,CAAC;KACrB,CAAC,qBAN0B,CAMzB;IAEF,CAAC,WAAW,EAAE,cAAc,CAAW,CAAC,OAAO,CAAC,UAAA,YAAY;QAEjD,IAAA,OAAO,GAAK,oBAAoB,CAAC,EAAE,YAAY,cAAA,EAAE,CAAC,QAA3C,CAA4C;QAE3D,EAAE,CAAC,aAAa,CACZ,WAAQ,CAAC,YAAY,EAAE,YAAY,CAAC,EACpC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAC/B,CAAA;IAEL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,aAAa,CACZ,WAAQ,CAAC,YAAY,EAAE,kBAAkB,CAAC,EAC1C,MAAM,CAAC,IAAI,CAAC,eAAe,EAAE,MAAM,CAAC,CACvC,CAAC;AAEN,CAAC;AA1ED,wEA0EC"}

2
bin/build-keycloak-theme/index.d.ts vendored Normal file
View File

@ -0,0 +1,2 @@
#!/usr/bin/env node
export declare const keycloakThemeBuildingDirPath: string;

View File

@ -0,0 +1,48 @@
#!/usr/bin/env node
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.keycloakThemeBuildingDirPath = void 0;
var generateKeycloakThemeResources_1 = require("./generateKeycloakThemeResources");
var generateJavaStackFiles_1 = require("./generateJavaStackFiles");
var path_1 = require("path");
var child_process = __importStar(require("child_process"));
var generateDebugFiles_1 = require("./generateDebugFiles");
var reactProjectDirPath = process.cwd();
var parsedPackageJson = require(path_1.join(reactProjectDirPath, "package.json"));
exports.keycloakThemeBuildingDirPath = path_1.join(reactProjectDirPath, "build_keycloak");
if (require.main === module) {
generateKeycloakThemeResources_1.generateKeycloakThemeResources({
keycloakThemeBuildingDirPath: exports.keycloakThemeBuildingDirPath,
"reactAppBuildDirPath": path_1.join(reactProjectDirPath, "build"),
"themeName": parsedPackageJson.name
});
generateJavaStackFiles_1.generateJavaStackFiles({
parsedPackageJson: parsedPackageJson,
keycloakThemeBuildingDirPath: exports.keycloakThemeBuildingDirPath
});
child_process.execSync("mvn package", { "cwd": exports.keycloakThemeBuildingDirPath });
generateDebugFiles_1.generateDebugFiles({
keycloakThemeBuildingDirPath: exports.keycloakThemeBuildingDirPath,
"packageJsonName": parsedPackageJson.name
});
}
//# sourceMappingURL=index.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/bin/build-keycloak-theme/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAEA,mFAAkF;AAClF,mEAAkE;AAElE,6BAAwC;AACxC,2DAA+C;AAC/C,2DAA0D;AAE1D,IAAM,mBAAmB,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;AAE1C,IAAM,iBAAiB,GAAsB,OAAO,CAAC,WAAQ,CAAC,mBAAmB,EAAE,cAAc,CAAC,CAAC,CAAC;AAEvF,QAAA,4BAA4B,GAAG,WAAQ,CAAC,mBAAmB,EAAE,gBAAgB,CAAC,CAAC;AAE5F,IAAI,OAAO,CAAC,IAAI,KAAK,MAAM,EAAE;IAEzB,+DAA8B,CAAC;QAC3B,4BAA4B,sCAAA;QAC5B,sBAAsB,EAAE,WAAQ,CAAC,mBAAmB,EAAE,OAAO,CAAC;QAC9D,WAAW,EAAE,iBAAiB,CAAC,IAAI;KACtC,CAAC,CAAC;IAEH,+CAAsB,CAAC;QACnB,iBAAiB,mBAAA;QACjB,4BAA4B,sCAAA;KAC/B,CAAC,CAAC;IAEH,aAAa,CAAC,QAAQ,CAClB,aAAa,EACb,EAAE,KAAK,EAAE,oCAA4B,EAAE,CAC1C,CAAC;IAEF,uCAAkB,CAAC;QACf,4BAA4B,sCAAA;QAC5B,iBAAiB,EAAE,iBAAiB,CAAC,IAAI;KAC5C,CAAC,CAAC;CAEN"}

View File

@ -0,0 +1,17 @@
export declare function replaceImportFromStaticInJsCode(params: {
ftlValuesGlobalName: string;
jsCode: string;
}): {
fixedJsCode: string;
};
export declare function replaceImportFromStaticInCssCode(params: {
cssCode: string;
}): {
fixedCssCode: string;
cssGlobalsToDefine: Record<string, string>;
};
export declare function generateCssCodeToDefineGlobals(params: {
cssGlobalsToDefine: Record<string, string>;
}): {
cssCodeToPrependInHead: string;
};

View File

@ -0,0 +1,93 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __read = (this && this.__read) || function (o, n) {
var m = typeof Symbol === "function" && o[Symbol.iterator];
if (!m) return o;
var i = m.call(o), r, ar = [], e;
try {
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
}
catch (error) { e = { error: error }; }
finally {
try {
if (r && !r.done && (m = i["return"])) m.call(i);
}
finally { if (e) throw e.error; }
}
return ar;
};
var __spread = (this && this.__spread) || function () {
for (var ar = [], i = 0; i < arguments.length; i++) ar = ar.concat(__read(arguments[i]));
return ar;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateCssCodeToDefineGlobals = exports.replaceImportFromStaticInCssCode = exports.replaceImportFromStaticInJsCode = void 0;
var crypto = __importStar(require("crypto"));
function replaceImportFromStaticInJsCode(params) {
var jsCode = params.jsCode, ftlValuesGlobalName = params.ftlValuesGlobalName;
var fixedJsCode = jsCode.replace(/"static\//g, "window." + ftlValuesGlobalName + ".url.resourcesPath.replace(/^\\//,\"\") + \"/\" + \"static/");
return { fixedJsCode: fixedJsCode };
}
exports.replaceImportFromStaticInJsCode = replaceImportFromStaticInJsCode;
function replaceImportFromStaticInCssCode(params) {
var _a;
var cssCode = params.cssCode;
var cssGlobalsToDefine = {};
new Set((_a = cssCode.match(/(url\(\/[^)]+\))/g)) !== null && _a !== void 0 ? _a : [])
.forEach(function (match) {
return cssGlobalsToDefine["url" + crypto
.createHash("sha256")
.update(match)
.digest("hex")
.substring(0, 15)] = match;
});
var fixedCssCode = cssCode;
Object.keys(cssGlobalsToDefine).forEach(function (cssVariableName) {
//NOTE: split/join pattern ~ replace all
return fixedCssCode =
fixedCssCode.split(cssGlobalsToDefine[cssVariableName])
.join("var(--" + cssVariableName + ")");
});
return { fixedCssCode: fixedCssCode, cssGlobalsToDefine: cssGlobalsToDefine };
}
exports.replaceImportFromStaticInCssCode = replaceImportFromStaticInCssCode;
function generateCssCodeToDefineGlobals(params) {
var cssGlobalsToDefine = params.cssGlobalsToDefine;
return {
"cssCodeToPrependInHead": __spread([
":root {"
], Object.keys(cssGlobalsToDefine)
.map(function (cssVariableName) { return [
"--" + cssVariableName + ":",
[
"url(",
"${url.resourcesPath}" +
cssGlobalsToDefine[cssVariableName].match(/^url\(([^)]+)\)$/)[1],
")"
].join("")
].join(" "); })
.map(function (line) { return " " + line + ";"; }), [
"}"
]).join("\n")
};
}
exports.generateCssCodeToDefineGlobals = generateCssCodeToDefineGlobals;
//# sourceMappingURL=replaceImportFromStatic.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"replaceImportFromStatic.js","sourceRoot":"","sources":["../../src/bin/build-keycloak-theme/replaceImportFromStatic.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACA,6CAAiC;AAEjC,SAAgB,+BAA+B,CAC3C,MAGC;IAGO,IAAA,MAAM,GAA0B,MAAM,OAAhC,EAAE,mBAAmB,GAAK,MAAM,oBAAX,CAAY;IAE/C,IAAM,WAAW,GAAG,MAAO,CAAC,OAAO,CAC/B,YAAY,EACZ,YAAU,mBAAmB,gEAAwD,CACxF,CAAC;IAEF,OAAO,EAAE,WAAW,aAAA,EAAE,CAAC;AAE3B,CAAC;AAhBD,0EAgBC;AAED,SAAgB,gCAAgC,CAC5C,MAEC;;IAMO,IAAA,OAAO,GAAK,MAAM,QAAX,CAAY;IAE3B,IAAM,kBAAkB,GAA2B,EAAE,CAAC;IAEtD,IAAI,GAAG,OAAC,OAAO,CAAC,KAAK,CAAC,mBAAmB,CAAC,mCAAI,EAAE,CAAC;SAC5C,OAAO,CAAC,UAAA,KAAK;QACV,OAAA,kBAAkB,CAClB,KAAK,GAAG,MAAM;aACT,UAAU,CAAC,QAAQ,CAAC;aACpB,MAAM,CAAC,KAAK,CAAC;aACb,MAAM,CAAC,KAAK,CAAC;aACb,SAAS,CAAC,CAAC,EAAE,EAAE,CAAC,CACpB,GAAG,KAAK;IANT,CAMS,CACZ,CAAC;IAEN,IAAI,YAAY,GAAG,OAAO,CAAC;IAE3B,MAAM,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC,OAAO,CACnC,UAAA,eAAe;QACX,wCAAwC;QACxC,OAAA,YAAY;YACZ,YAAY,CAAC,KAAK,CAAC,kBAAkB,CAAC,eAAe,CAAC,CAAC;iBAClD,IAAI,CAAC,WAAS,eAAe,MAAG,CAAC;IAFtC,CAEsC,CAC7C,CAAC;IAEF,OAAO,EAAE,YAAY,cAAA,EAAE,kBAAkB,oBAAA,EAAE,CAAC;AAEhD,CAAC;AApCD,4EAoCC;AAED,SAAgB,8BAA8B,CAC1C,MAEC;IAKO,IAAA,kBAAkB,GAAK,MAAM,mBAAX,CAAY;IAEtC,OAAO;QACH,wBAAwB,EAAE;YACtB,SAAS;WACN,MAAM,CAAC,IAAI,CAAC,kBAAkB,CAAC;aAC7B,GAAG,CAAC,UAAA,eAAe,IAAI,OAAA;YACpB,OAAK,eAAe,MAAG;YACvB;gBACI,MAAM;gBACN,sBAAsB;oBACtB,kBAAkB,CAAC,eAAe,CAAC,CAAC,KAAK,CAAC,kBAAkB,CAAE,CAAC,CAAC,CAAC;gBACjE,GAAG;aACN,CAAC,IAAI,CAAC,EAAE,CAAC;SACb,CAAC,IAAI,CAAC,GAAG,CAAC,EARa,CAQb,CAAC;aACX,GAAG,CAAC,UAAA,IAAI,IAAI,OAAA,SAAO,IAAI,MAAG,EAAd,CAAc,CAAC;YAChC,GAAG;WACL,IAAI,CAAC,IAAI,CAAC;KACf,CAAC;AAEN,CAAC;AA5BD,wEA4BC"}

View File

@ -0,0 +1,2 @@
#!/usr/bin/env node
export {};

View File

@ -0,0 +1,58 @@
#!/usr/bin/env node
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __read = (this && this.__read) || function (o, n) {
var m = typeof Symbol === "function" && o[Symbol.iterator];
if (!m) return o;
var i = m.call(o), r, ar = [], e;
try {
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
}
catch (error) { e = { error: error }; }
finally {
try {
if (r && !r.done && (m = i["return"])) m.call(i);
}
finally { if (e) throw e.error; }
}
return ar;
};
var __spread = (this && this.__spread) || function () {
for (var ar = [], i = 0; i < arguments.length; i++) ar = ar.concat(__read(arguments[i]));
return ar;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
var fs = __importStar(require("fs"));
var path_1 = require("path");
var build_keycloak_theme_1 = require("./build-keycloak-theme");
var child_process_1 = __importDefault(require("child_process"));
if (!fs.existsSync(build_keycloak_theme_1.keycloakThemeBuildingDirPath)) {
console.log("Error: The keycloak theme need to be build");
process.exit(1);
}
var url = "https://github.com/garronej/keycloak-react-theming/releases/download/v0.0.1/other_keycloak_thems.zip";
__spread([
"wget " + url
], ["unzip", "rm"].map(function (prg) { return prg + " " + path_1.basename(url); })).forEach(function (cmd) { return child_process_1.default.execSync(cmd, { "cwd": path_1.join(build_keycloak_theme_1.keycloakThemeBuildingDirPath, "src", "main", "resources", "theme") }); });
//# sourceMappingURL=download-sample-keycloak-themes.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"download-sample-keycloak-themes.js","sourceRoot":"","sources":["../src/bin/download-sample-keycloak-themes.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAEA,qCAAyB;AACzB,6BAAkE;AAClE,+DAAsE;AACtE,gEAA0C;AAE1C,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,mDAA4B,CAAC,EAAE;IAC9C,OAAO,CAAC,GAAG,CAAC,4CAA4C,CAAC,CAAC;IAC1D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;CACnB;AAED,IAAM,GAAG,GAAG,sGAAsG,CAAC;AAEnH;IACI,UAAQ,GAAK;GACV,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,GAAG,CAAC,UAAA,GAAG,IAAI,OAAG,GAAG,SAAI,eAAY,CAAC,GAAG,CAAG,EAA7B,CAA6B,CAAC,EAC9D,OAAO,CAAC,UAAA,GAAG,IAAI,OAAA,uBAAa,CAAC,QAAQ,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,WAAQ,CAAC,mDAA4B,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,OAAO,CAAC,EAAE,CAAC,EAAnH,CAAmH,CAAC,CAAC"}

1
lib/inex.d.ts vendored Normal file
View File

@ -0,0 +1 @@
export {};

3
lib/inex.js Normal file
View File

@ -0,0 +1,3 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=inex.js.map

1
lib/inex.js.map Normal file
View File

@ -0,0 +1 @@
{"version":3,"file":"inex.js","sourceRoot":"","sources":["../src/lib/inex.ts"],"names":[],"mappings":""}

1708
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,88 +1,77 @@
{
"name": "keycloakify",
"version": "6.0.0-beta.2",
"description": "Keycloak theme generator for Reacts app",
"name": "keycloak-react-theming",
"version": "0.0.10",
"description": "Provides a way to customize Keycloak login and register pages with React",
"repository": {
"type": "git",
"url": "git://github.com/garronej/keycloakify.git"
"url": "git://github.com/garronej/keycloak-react-theming.git"
},
"main": "dist/lib/index.js",
"types": "dist/lib/index.d.ts",
"scripts": {
"build": "rimraf dist/ && tsc -p src/bin && tsc -p src/lib && yarn grant-exec-perms && yarn copy-files dist/",
"build:test": "rimraf dist_test/ && tsc -p src/test && yarn copy-files dist_test/",
"grant-exec-perms": "node dist/bin/tools/grant-exec-perms.js",
"copy-files": "copyfiles -u 1 src/**/*.ftl",
"test": "yarn build:test && node dist_test/test/bin && node dist_test/test/lib",
"generate-messages": "node dist/bin/generate-i18n-messages.js",
"link_in_test_app": "node dist/bin/link_in_test_app.js",
"_format": "prettier '**/*.{ts,tsx,json,md}'",
"format": "yarn _format --write",
"format:check": "yarn _format --list-different"
},
"bin": {
"build-keycloak-theme": "dist/bin/build-keycloak-theme/index.js",
"create-keycloak-email-directory": "dist/bin/create-keycloak-email-directory.js",
"download-builtin-keycloak-theme": "dist/bin/download-builtin-keycloak-theme.js"
},
"lint-staged": {
"*.{ts,tsx,json,md}": [
"prettier --write"
]
},
"husky": {
"hooks": {
"pre-commit": "lint-staged -v"
}
"build-keycloak-theme": "bin/build-keycloak-theme/index.js",
"download-sample-keycloak-themes": "bin/download-sample-keycloak-themes.js"
},
"author": "u/garronej",
"license": "MIT",
"files": [
"src/",
"dist/",
"!dist/tsconfig.tsbuildinfo"
"src/bin/build-keycloak-theme/generateDebugFiles/index.ts",
"src/bin/build-keycloak-theme/generateDebugFiles/standalone-ha.xml",
"src/bin/build-keycloak-theme/generateFtl.ts",
"src/bin/build-keycloak-theme/generateJavaStackFiles.ts",
"src/bin/build-keycloak-theme/generateKeycloakThemeResources.ts",
"src/bin/build-keycloak-theme/index.ts",
"src/bin/build-keycloak-theme/replaceImportFromStatic.ts",
"src/bin/download-sample-keycloak-themes.ts",
"src/lib/inex.ts",
"src/tools/crawl.ts",
"src/tools/transformCodebase.ts",
"bin/build-keycloak-theme/generateDebugFiles/index.d.ts",
"bin/build-keycloak-theme/generateDebugFiles/index.js",
"bin/build-keycloak-theme/generateDebugFiles/index.js.map",
"bin/build-keycloak-theme/generateDebugFiles/standalone-ha.xml",
"bin/build-keycloak-theme/generateFtl.d.ts",
"bin/build-keycloak-theme/generateFtl.js",
"bin/build-keycloak-theme/generateFtl.js.map",
"bin/build-keycloak-theme/generateJavaStackFiles.d.ts",
"bin/build-keycloak-theme/generateJavaStackFiles.js",
"bin/build-keycloak-theme/generateJavaStackFiles.js.map",
"bin/build-keycloak-theme/generateKeycloakThemeResources.d.ts",
"bin/build-keycloak-theme/generateKeycloakThemeResources.js",
"bin/build-keycloak-theme/generateKeycloakThemeResources.js.map",
"bin/build-keycloak-theme/index.d.ts",
"bin/build-keycloak-theme/index.js",
"bin/build-keycloak-theme/index.js.map",
"bin/build-keycloak-theme/replaceImportFromStatic.d.ts",
"bin/build-keycloak-theme/replaceImportFromStatic.js",
"bin/build-keycloak-theme/replaceImportFromStatic.js.map",
"bin/download-sample-keycloak-themes.d.ts",
"bin/download-sample-keycloak-themes.js",
"bin/download-sample-keycloak-themes.js.map",
"lib/inex.d.ts",
"lib/inex.js",
"lib/inex.js.map",
"tools/crawl.d.ts",
"tools/crawl.js",
"tools/crawl.js.map",
"tools/transformCodebase.d.ts",
"tools/transformCodebase.js",
"tools/transformCodebase.js.map"
],
"keywords": [
"bluehats",
"keycloak",
"react",
"theme",
"FreeMarker",
"ftl",
"login",
"register"
"theme"
],
"homepage": "https://github.com/garronej/keycloakify",
"peerDependencies": {
"@emotion/react": "^11.4.1",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
},
"homepage": "https://github.com/garronej/keycloak-react-theming",
"devDependencies": {
"@emotion/react": "^11.4.1",
"@types/memoizee": "^0.4.7",
"@types/node": "^17.0.25",
"@types/react": "18.0.9",
"@types/node": "^10.0.0",
"copyfiles": "^2.4.1",
"husky": "^4.3.8",
"lint-staged": "^11.0.0",
"prettier": "^2.3.0",
"properties-parser": "^0.3.1",
"react": "18.1.0",
"rimraf": "^3.0.2",
"typescript": "^4.2.3"
"denoify": "^0.6.4",
"scripting-tools": "^0.19.13",
"typescript": "^4.1.5"
},
"dependencies": {
"@octokit/rest": "^18.12.0",
"cheerio": "^1.0.0-rc.5",
"cli-select": "^1.1.2",
"evt": "^2.3.1",
"memoizee": "^0.4.15",
"minimal-polyfills": "^2.2.1",
"path-browserify": "^1.0.1",
"powerhooks": "^0.20.10",
"react-markdown": "^5.0.3",
"scripting-tools": "^0.19.13",
"tsafe": "^0.10.1",
"tss-react": "^3.7.1"
"cheerio": "^1.0.0-rc.5"
}
}

View File

@ -1,27 +0,0 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"baseBranches": ["main", "landingpage"],
"extends": ["config:base"],
"dependencyDashboard": false,
"bumpVersion": "patch",
"rangeStrategy": "bump",
"ignorePaths": [".github/**"],
"branchPrefix": "renovate_",
"vulnerabilityAlerts": {
"enabled": false
},
"packageRules": [
{
"packagePatterns": ["*"],
"excludePackagePatterns": ["tss-react", "powerhooks", "tsafe", "evt"],
"enabled": false
},
{
"packagePatterns": ["tss-react", "powerhooks", "tsafe", "evt"],
"matchUpdateTypes": ["minor", "patch"],
"automerge": true,
"automergeType": "branch",
"groupName": "garronej_modules_update"
}
]
}

View File

@ -1,155 +0,0 @@
import { generateKeycloakThemeResources } from "./generateKeycloakThemeResources";
import { generateJavaStackFiles } from "./generateJavaStackFiles";
import { join as pathJoin, relative as pathRelative, basename as pathBasename } from "path";
import * as child_process from "child_process";
import { generateStartKeycloakTestingContainer } from "./generateStartKeycloakTestingContainer";
import { URL } from "url";
import * as fs from "fs";
type ParsedPackageJson = {
name: string;
version: string;
homepage?: string;
};
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");
export const keycloakThemeEmailDirPath = pathJoin(keycloakThemeBuildingDirPath, "..", "keycloak_email");
function sanitizeThemeName(name: string) {
return name
.replace(/^@(.*)/, "$1")
.split("/")
.join("-");
}
export function main() {
console.log("🔏 Building the keycloak theme...⌚");
const extraPagesId: string[] = (parsedPackageJson as any)["keycloakify"]?.["extraPages"] ?? [];
const extraThemeProperties: string[] = (parsedPackageJson as any)["keycloakify"]?.["extraThemeProperties"] ?? [];
const themeName = sanitizeThemeName(parsedPackageJson.name);
const { doBundleEmailTemplate } = generateKeycloakThemeResources({
keycloakThemeBuildingDirPath,
keycloakThemeEmailDirPath,
"reactAppBuildDirPath": pathJoin(reactProjectDirPath, "build"),
themeName,
...(() => {
const url = (() => {
const { homepage } = parsedPackageJson;
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 {
"urlPathname": url === undefined ? "/" : url.pathname.replace(/([^/])$/, "$1/"),
"urlOrigin": !doUseExternalAssets
? undefined
: (() => {
if (url === undefined) {
console.error("ERROR: You must specify 'homepage' in your package.json");
process.exit(-1);
}
return url.origin;
})(),
};
})(),
extraPagesId,
extraThemeProperties,
//We have to leave it at that otherwise we break our default theme.
//Problem is that we can't guarantee that the the old resources
//will still be available on the newer keycloak version.
"keycloakVersion": "11.0.3",
});
const { jarFilePath } = generateJavaStackFiles({
"version": parsedPackageJson.version,
themeName,
"homepage": parsedPackageJson.homepage,
keycloakThemeBuildingDirPath,
doBundleEmailTemplate,
});
child_process.execSync("mvn package", {
"cwd": keycloakThemeBuildingDirPath,
});
//We want, however, to test in a container running the latest Keycloak version
const containerKeycloakVersion = "18.0.2";
generateStartKeycloakTestingContainer({
keycloakThemeBuildingDirPath,
themeName,
"keycloakVersion": containerKeycloakVersion,
});
console.log(
[
"",
`✅ Your keycloak theme has been generated and bundled into ./${pathRelative(reactProjectDirPath, jarFilePath)} 🚀`,
`It is to be placed in "/opt/keycloak/providers" in the container running a quay.io/keycloak/keycloak Docker image.`,
"",
//TODO: Restore when we find a good Helm chart for Keycloak.
//"Using Helm (https://github.com/codecentric/helm-charts), edit to reflect:",
"",
"value.yaml: ",
" extraInitContainers: |",
" - name: realm-ext-provider",
" image: curlimages/curl",
" imagePullPolicy: IfNotPresent",
" command:",
" - sh",
" args:",
" - -c",
` - curl -L -f -S -o /extensions/${pathBasename(jarFilePath)} https://AN.URL.FOR/${pathBasename(jarFilePath)}`,
" volumeMounts:",
" - name: extensions",
" mountPath: /extensions",
" ",
" extraVolumeMounts: |",
" - name: extensions",
" mountPath: /opt/keycloak/providers",
" extraEnv: |",
" - name: KEYCLOAK_USER",
" value: admin",
" - name: KEYCLOAK_PASSWORD",
" value: xxxxxxxxx",
" - name: JAVA_OPTS",
" value: -Dkeycloak.profile=preview",
"",
"",
`To test your theme locally you can spin up a Keycloak ${containerKeycloakVersion} container image with the theme pre loaded by running:`,
"",
`👉 $ ./${pathRelative(reactProjectDirPath, pathJoin(keycloakThemeBuildingDirPath, generateStartKeycloakTestingContainer.basename))} 👈`,
"",
"Test with different Keycloak versions by editing the .sh file. see available versions here: https://quay.io/repository/keycloak/keycloak?tab=tags",
"",
"Once your container is up and running: ",
"- Log into the admin console 👉 http://localhost:8080/admin username: admin, password: admin 👈",
'- Create a realm named "myrealm"',
'- Create a client with ID: "myclient", "Root URL": "https://www.keycloak.org/app/" and "Valid redirect URIs": "https://www.keycloak.org/app/*"',
`- Select Login Theme: ${themeName} (don't forget to save at the bottom of the page)`,
`- Go to 👉 https://www.keycloak.org/app/ 👈 Click "Save" then "Sign in". You should see your login page`,
"",
"Video demoing this process: https://youtu.be/N3wlBoH4hKg",
"",
].join("\n"),
);
}

View File

@ -1 +0,0 @@
export const ftlValuesGlobalName = "kcContext";

View File

@ -0,0 +1,70 @@
import * as fs from "fs";
import { join as pathJoin, dirname as pathDirname, basename as pathBasename } from "path";
/** Files for being able to run a hot reload keycloak container */
export function generateDebugFiles(
params: {
packageJsonName: string;
keycloakThemeBuildingDirPath: string;
}
) {
const { packageJsonName, keycloakThemeBuildingDirPath } = params;
fs.writeFileSync(
pathJoin(keycloakThemeBuildingDirPath, "Dockerfile"),
Buffer.from(
[
"FROM jboss/keycloak:11.0.3",
"",
"USER root",
"",
"WORKDIR /",
"",
"ADD configuration /opt/jboss/keycloak/standalone/configuration/",
"",
'ENTRYPOINT [ "/opt/jboss/tools/docker-entrypoint.sh" ]',
].join("\n"),
"utf8"
)
);
const dockerImage = `${packageJsonName}/keycloak-hot-reload`;
const containerName = "keycloak-testing-container";
fs.writeFileSync(
pathJoin(keycloakThemeBuildingDirPath, "start_keycloak_testing_container.sh"),
Buffer.from(
[
"#!/bin/bash",
"",
`docker rm ${containerName} || true`,
"",
`docker build . -t ${dockerImage}`,
"",
"docker run \\",
" -p 8080:8080 \\",
` --name ${containerName} \\`,
" -e KEYCLOAK_USER=admin \\",
" -e KEYCLOAK_PASSWORD=admin \\",
` -v ${pathJoin(keycloakThemeBuildingDirPath, "src", "main", "resources", "theme", packageJsonName)
}:/opt/jboss/keycloak/themes/${packageJsonName}:rw \\`,
` -it ${dockerImage}:latest`,
""
].join("\n"),
"utf8"
),
{ "mode": 0o755 }
);
const standaloneHaFilePath = pathJoin(keycloakThemeBuildingDirPath, "configuration", "standalone-ha.xml");
try { fs.mkdirSync(pathDirname(standaloneHaFilePath)); } catch { }
fs.writeFileSync(
standaloneHaFilePath,
fs.readFileSync(pathJoin(__dirname, pathBasename(standaloneHaFilePath)))
);
}

View File

@ -0,0 +1,666 @@
<?xml version='1.0' encoding='UTF-8'?>
<server xmlns="urn:jboss:domain:13.0">
<extensions>
<extension module="org.jboss.as.clustering.infinispan"/>
<extension module="org.jboss.as.clustering.jgroups"/>
<extension module="org.jboss.as.connector"/>
<extension module="org.jboss.as.deployment-scanner"/>
<extension module="org.jboss.as.ee"/>
<extension module="org.jboss.as.ejb3"/>
<extension module="org.jboss.as.jaxrs"/>
<extension module="org.jboss.as.jmx"/>
<extension module="org.jboss.as.jpa"/>
<extension module="org.jboss.as.logging"/>
<extension module="org.jboss.as.mail"/>
<extension module="org.jboss.as.modcluster"/>
<extension module="org.jboss.as.naming"/>
<extension module="org.jboss.as.remoting"/>
<extension module="org.jboss.as.security"/>
<extension module="org.jboss.as.transactions"/>
<extension module="org.jboss.as.weld"/>
<extension module="org.keycloak.keycloak-server-subsystem"/>
<extension module="org.wildfly.extension.bean-validation"/>
<extension module="org.wildfly.extension.core-management"/>
<extension module="org.wildfly.extension.elytron"/>
<extension module="org.wildfly.extension.io"/>
<extension module="org.wildfly.extension.microprofile.config-smallrye"/>
<extension module="org.wildfly.extension.microprofile.health-smallrye"/>
<extension module="org.wildfly.extension.microprofile.metrics-smallrye"/>
<extension module="org.wildfly.extension.request-controller"/>
<extension module="org.wildfly.extension.security.manager"/>
<extension module="org.wildfly.extension.undertow"/>
</extensions>
<management>
<security-realms>
<security-realm name="ManagementRealm">
<authentication>
<local default-user="$local" skip-group-loading="true"/>
<properties path="mgmt-users.properties" relative-to="jboss.server.config.dir"/>
</authentication>
<authorization map-groups-to-roles="false">
<properties path="mgmt-groups.properties" relative-to="jboss.server.config.dir"/>
</authorization>
</security-realm>
<security-realm name="ApplicationRealm">
<server-identities>
<ssl>
<keystore path="application.keystore" relative-to="jboss.server.config.dir" keystore-password="password" alias="server" key-password="password" generate-self-signed-certificate-host="localhost"/>
</ssl>
</server-identities>
<authentication>
<local default-user="$local" allowed-users="*" skip-group-loading="true"/>
<properties path="application-users.properties" relative-to="jboss.server.config.dir"/>
</authentication>
<authorization>
<properties path="application-roles.properties" relative-to="jboss.server.config.dir"/>
</authorization>
</security-realm>
</security-realms>
<audit-log>
<formatters>
<json-formatter name="json-formatter"/>
</formatters>
<handlers>
<file-handler name="file" formatter="json-formatter" path="audit-log.log" relative-to="jboss.server.data.dir"/>
</handlers>
<logger log-boot="true" log-read-only="false" enabled="false">
<handlers>
<handler name="file"/>
</handlers>
</logger>
</audit-log>
<management-interfaces>
<http-interface security-realm="ManagementRealm">
<http-upgrade enabled="true"/>
<socket-binding http="management-http"/>
</http-interface>
</management-interfaces>
<access-control provider="simple">
<role-mapping>
<role name="SuperUser">
<include>
<user name="$local"/>
</include>
</role>
</role-mapping>
</access-control>
</management>
<profile>
<subsystem xmlns="urn:jboss:domain:logging:8.0">
<console-handler name="CONSOLE">
<formatter>
<named-formatter name="COLOR-PATTERN"/>
</formatter>
</console-handler>
<logger category="com.arjuna">
<level name="WARN"/>
</logger>
<logger category="io.jaegertracing.Configuration">
<level name="WARN"/>
</logger>
<logger category="org.jboss.as.config">
<level name="DEBUG"/>
</logger>
<logger category="sun.rmi">
<level name="WARN"/>
</logger>
<logger category="org.keycloak">
<level name="${env.KEYCLOAK_LOGLEVEL:INFO}"/>
</logger>
<root-logger>
<level name="${env.ROOT_LOGLEVEL:INFO}"/>
<handlers>
<handler name="CONSOLE"/>
</handlers>
</root-logger>
<formatter name="PATTERN">
<pattern-formatter pattern="%d{yyyy-MM-dd HH:mm:ss,SSS} %-5p [%c] (%t) %s%e%n"/>
</formatter>
<formatter name="COLOR-PATTERN">
<pattern-formatter pattern="%K{level}%d{HH:mm:ss,SSS} %-5p [%c] (%t) %s%e%n"/>
</formatter>
</subsystem>
<subsystem xmlns="urn:jboss:domain:bean-validation:1.0"/>
<subsystem xmlns="urn:jboss:domain:core-management:1.0"/>
<subsystem xmlns="urn:jboss:domain:datasources:6.0">
<datasources>
<datasource jndi-name="java:jboss/datasources/ExampleDS" pool-name="ExampleDS" enabled="true" use-java-context="true" statistics-enabled="${wildfly.datasources.statistics-enabled:${wildfly.statistics-enabled:false}}">
<connection-url>jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE</connection-url>
<driver>h2</driver>
<security>
<user-name>sa</user-name>
<password>sa</password>
</security>
</datasource>
<datasource jndi-name="java:jboss/datasources/KeycloakDS" pool-name="KeycloakDS" enabled="true" use-java-context="true" statistics-enabled="${wildfly.datasources.statistics-enabled:${wildfly.statistics-enabled:false}}">
<connection-url>jdbc:h2:${jboss.server.data.dir}/keycloak;AUTO_SERVER=TRUE</connection-url>
<driver>h2</driver>
<pool>
<max-pool-size>100</max-pool-size>
</pool>
<security>
<user-name>sa</user-name>
<password>sa</password>
</security>
</datasource>
<drivers>
<driver name="h2" module="com.h2database.h2">
<xa-datasource-class>org.h2.jdbcx.JdbcDataSource</xa-datasource-class>
</driver>
</drivers>
</datasources>
</subsystem>
<subsystem xmlns="urn:jboss:domain:deployment-scanner:2.0">
<deployment-scanner path="deployments" relative-to="jboss.server.base.dir" scan-interval="5000" runtime-failure-causes-rollback="${jboss.deployment.scanner.rollback.on.failure:false}"/>
</subsystem>
<subsystem xmlns="urn:jboss:domain:ee:5.0">
<spec-descriptor-property-replacement>false</spec-descriptor-property-replacement>
<concurrent>
<context-services>
<context-service name="default" jndi-name="java:jboss/ee/concurrency/context/default" use-transaction-setup-provider="true"/>
</context-services>
<managed-thread-factories>
<managed-thread-factory name="default" jndi-name="java:jboss/ee/concurrency/factory/default" context-service="default"/>
</managed-thread-factories>
<managed-executor-services>
<managed-executor-service name="default" jndi-name="java:jboss/ee/concurrency/executor/default" context-service="default" hung-task-threshold="60000" keepalive-time="5000"/>
</managed-executor-services>
<managed-scheduled-executor-services>
<managed-scheduled-executor-service name="default" jndi-name="java:jboss/ee/concurrency/scheduler/default" context-service="default" hung-task-threshold="60000" keepalive-time="3000"/>
</managed-scheduled-executor-services>
</concurrent>
<default-bindings context-service="java:jboss/ee/concurrency/context/default" datasource="java:jboss/datasources/ExampleDS" managed-executor-service="java:jboss/ee/concurrency/executor/default" managed-scheduled-executor-service="java:jboss/ee/concurrency/scheduler/default" managed-thread-factory="java:jboss/ee/concurrency/factory/default"/>
</subsystem>
<subsystem xmlns="urn:jboss:domain:ejb3:7.0">
<session-bean>
<stateless>
<bean-instance-pool-ref pool-name="slsb-strict-max-pool"/>
</stateless>
<stateful default-access-timeout="5000" cache-ref="distributable" passivation-disabled-cache-ref="simple"/>
<singleton default-access-timeout="5000"/>
</session-bean>
<pools>
<bean-instance-pools>
<strict-max-pool name="mdb-strict-max-pool" derive-size="from-cpu-count" instance-acquisition-timeout="5" instance-acquisition-timeout-unit="MINUTES"/>
<strict-max-pool name="slsb-strict-max-pool" derive-size="from-worker-pools" instance-acquisition-timeout="5" instance-acquisition-timeout-unit="MINUTES"/>
</bean-instance-pools>
</pools>
<caches>
<cache name="simple"/>
<cache name="distributable" passivation-store-ref="infinispan" aliases="passivating clustered"/>
</caches>
<passivation-stores>
<passivation-store name="infinispan" cache-container="ejb" max-size="10000"/>
</passivation-stores>
<async thread-pool-name="default"/>
<timer-service thread-pool-name="default" default-data-store="default-file-store">
<data-stores>
<file-data-store name="default-file-store" path="timer-service-data" relative-to="jboss.server.data.dir"/>
</data-stores>
</timer-service>
<remote connector-ref="http-remoting-connector" thread-pool-name="default">
<channel-creation-options>
<option name="MAX_OUTBOUND_MESSAGES" value="1234" type="remoting"/>
</channel-creation-options>
</remote>
<thread-pools>
<thread-pool name="default">
<max-threads count="10"/>
<keepalive-time time="60" unit="seconds"/>
</thread-pool>
</thread-pools>
<default-security-domain value="other"/>
<default-missing-method-permissions-deny-access value="true"/>
<statistics enabled="${wildfly.ejb3.statistics-enabled:${wildfly.statistics-enabled:false}}"/>
<log-system-exceptions value="true"/>
</subsystem>
<subsystem xmlns="urn:wildfly:elytron:10.0" final-providers="combined-providers" disallowed-providers="OracleUcrypto">
<providers>
<aggregate-providers name="combined-providers">
<providers name="elytron"/>
<providers name="openssl"/>
</aggregate-providers>
<provider-loader name="elytron" module="org.wildfly.security.elytron"/>
<provider-loader name="openssl" module="org.wildfly.openssl"/>
</providers>
<audit-logging>
<file-audit-log name="local-audit" path="audit.log" relative-to="jboss.server.log.dir" format="JSON"/>
</audit-logging>
<security-domains>
<security-domain name="ApplicationDomain" default-realm="ApplicationRealm" permission-mapper="default-permission-mapper">
<realm name="ApplicationRealm" role-decoder="groups-to-roles"/>
<realm name="local"/>
</security-domain>
<security-domain name="ManagementDomain" default-realm="ManagementRealm" permission-mapper="default-permission-mapper">
<realm name="ManagementRealm" role-decoder="groups-to-roles"/>
<realm name="local" role-mapper="super-user-mapper"/>
</security-domain>
</security-domains>
<security-realms>
<identity-realm name="local" identity="$local"/>
<properties-realm name="ApplicationRealm">
<users-properties path="application-users.properties" relative-to="jboss.server.config.dir" digest-realm-name="ApplicationRealm"/>
<groups-properties path="application-roles.properties" relative-to="jboss.server.config.dir"/>
</properties-realm>
<properties-realm name="ManagementRealm">
<users-properties path="mgmt-users.properties" relative-to="jboss.server.config.dir" digest-realm-name="ManagementRealm"/>
<groups-properties path="mgmt-groups.properties" relative-to="jboss.server.config.dir"/>
</properties-realm>
</security-realms>
<mappers>
<simple-permission-mapper name="default-permission-mapper" mapping-mode="first">
<permission-mapping>
<principal name="anonymous"/>
<permission-set name="default-permissions"/>
</permission-mapping>
<permission-mapping match-all="true">
<permission-set name="login-permission"/>
<permission-set name="default-permissions"/>
</permission-mapping>
</simple-permission-mapper>
<constant-realm-mapper name="local" realm-name="local"/>
<simple-role-decoder name="groups-to-roles" attribute="groups"/>
<constant-role-mapper name="super-user-mapper">
<role name="SuperUser"/>
</constant-role-mapper>
</mappers>
<permission-sets>
<permission-set name="login-permission">
<permission class-name="org.wildfly.security.auth.permission.LoginPermission"/>
</permission-set>
<permission-set name="default-permissions">
<permission class-name="org.wildfly.extension.batch.jberet.deployment.BatchPermission" module="org.wildfly.extension.batch.jberet" target-name="*"/>
<permission class-name="org.wildfly.transaction.client.RemoteTransactionPermission" module="org.wildfly.transaction.client"/>
<permission class-name="org.jboss.ejb.client.RemoteEJBPermission" module="org.jboss.ejb-client"/>
</permission-set>
</permission-sets>
<http>
<http-authentication-factory name="management-http-authentication" security-domain="ManagementDomain" http-server-mechanism-factory="global">
<mechanism-configuration>
<mechanism mechanism-name="DIGEST">
<mechanism-realm realm-name="ManagementRealm"/>
</mechanism>
</mechanism-configuration>
</http-authentication-factory>
<provider-http-server-mechanism-factory name="global"/>
</http>
<sasl>
<sasl-authentication-factory name="application-sasl-authentication" sasl-server-factory="configured" security-domain="ApplicationDomain">
<mechanism-configuration>
<mechanism mechanism-name="JBOSS-LOCAL-USER" realm-mapper="local"/>
<mechanism mechanism-name="DIGEST-MD5">
<mechanism-realm realm-name="ApplicationRealm"/>
</mechanism>
</mechanism-configuration>
</sasl-authentication-factory>
<sasl-authentication-factory name="management-sasl-authentication" sasl-server-factory="configured" security-domain="ManagementDomain">
<mechanism-configuration>
<mechanism mechanism-name="JBOSS-LOCAL-USER" realm-mapper="local"/>
<mechanism mechanism-name="DIGEST-MD5">
<mechanism-realm realm-name="ManagementRealm"/>
</mechanism>
</mechanism-configuration>
</sasl-authentication-factory>
<configurable-sasl-server-factory name="configured" sasl-server-factory="elytron">
<properties>
<property name="wildfly.sasl.local-user.default-user" value="$local"/>
</properties>
</configurable-sasl-server-factory>
<mechanism-provider-filtering-sasl-server-factory name="elytron" sasl-server-factory="global">
<filters>
<filter provider-name="WildFlyElytron"/>
</filters>
</mechanism-provider-filtering-sasl-server-factory>
<provider-sasl-server-factory name="global"/>
</sasl>
</subsystem>
<subsystem xmlns="urn:jboss:domain:infinispan:10.0">
<cache-container name="keycloak" module="org.keycloak.keycloak-model-infinispan">
<transport lock-timeout="60000"/>
<local-cache name="realms">
<object-memory size="10000"/>
</local-cache>
<local-cache name="users">
<object-memory size="10000"/>
</local-cache>
<local-cache name="authorization">
<object-memory size="10000"/>
</local-cache>
<local-cache name="keys">
<object-memory size="1000"/>
<expiration max-idle="3600000"/>
</local-cache>
<replicated-cache name="work"/>
<distributed-cache name="sessions" owners="1"/>
<distributed-cache name="authenticationSessions" owners="1"/>
<distributed-cache name="offlineSessions" owners="1"/>
<distributed-cache name="clientSessions" owners="1"/>
<distributed-cache name="offlineClientSessions" owners="1"/>
<distributed-cache name="loginFailures" owners="1"/>
<distributed-cache name="actionTokens" owners="2">
<object-memory size="-1"/>
<expiration interval="300000" max-idle="-1"/>
</distributed-cache>
</cache-container>
<cache-container name="server" aliases="singleton cluster" default-cache="default" module="org.wildfly.clustering.server">
<transport lock-timeout="60000"/>
<replicated-cache name="default">
<transaction mode="BATCH"/>
</replicated-cache>
</cache-container>
<cache-container name="web" default-cache="dist" module="org.wildfly.clustering.web.infinispan">
<transport lock-timeout="60000"/>
<replicated-cache name="sso">
<locking isolation="REPEATABLE_READ"/>
<transaction mode="BATCH"/>
</replicated-cache>
<distributed-cache name="dist">
<locking isolation="REPEATABLE_READ"/>
<transaction mode="BATCH"/>
<file-store/>
</distributed-cache>
<distributed-cache name="routing"/>
</cache-container>
<cache-container name="ejb" aliases="sfsb" default-cache="dist" module="org.wildfly.clustering.ejb.infinispan">
<transport lock-timeout="60000"/>
<distributed-cache name="dist">
<locking isolation="REPEATABLE_READ"/>
<transaction mode="BATCH"/>
<file-store/>
</distributed-cache>
</cache-container>
<cache-container name="hibernate" module="org.infinispan.hibernate-cache">
<transport lock-timeout="60000"/>
<local-cache name="local-query">
<object-memory size="10000"/>
<expiration max-idle="100000"/>
</local-cache>
<invalidation-cache name="entity">
<transaction mode="NON_XA"/>
<object-memory size="10000"/>
<expiration max-idle="100000"/>
</invalidation-cache>
<replicated-cache name="timestamps"/>
</cache-container>
</subsystem>
<subsystem xmlns="urn:jboss:domain:io:3.0">
<worker name="default"/>
<buffer-pool name="default"/>
</subsystem>
<subsystem xmlns="urn:jboss:domain:jaxrs:2.0"/>
<subsystem xmlns="urn:jboss:domain:jca:5.0">
<archive-validation enabled="true" fail-on-error="true" fail-on-warn="false"/>
<bean-validation enabled="true"/>
<default-workmanager>
<short-running-threads>
<core-threads count="50"/>
<queue-length count="50"/>
<max-threads count="50"/>
<keepalive-time time="10" unit="seconds"/>
</short-running-threads>
<long-running-threads>
<core-threads count="50"/>
<queue-length count="50"/>
<max-threads count="50"/>
<keepalive-time time="10" unit="seconds"/>
</long-running-threads>
</default-workmanager>
<cached-connection-manager/>
</subsystem>
<subsystem xmlns="urn:jboss:domain:jgroups:8.0">
<channels default="ee">
<channel name="ee" stack="udp" cluster="ejb"/>
</channels>
<stacks>
<stack name="udp">
<transport type="UDP" socket-binding="jgroups-udp"/>
<protocol type="PING"/>
<protocol type="MERGE3"/>
<socket-protocol type="FD_SOCK" socket-binding="jgroups-udp-fd"/>
<protocol type="FD_ALL"/>
<protocol type="VERIFY_SUSPECT"/>
<protocol type="pbcast.NAKACK2"/>
<protocol type="UNICAST3"/>
<protocol type="pbcast.STABLE"/>
<protocol type="pbcast.GMS"/>
<protocol type="UFC"/>
<protocol type="MFC"/>
<protocol type="FRAG3"/>
</stack>
<stack name="tcp">
<transport type="TCP" socket-binding="jgroups-tcp"/>
<socket-protocol type="MPING" socket-binding="jgroups-mping"/>
<protocol type="MERGE3"/>
<socket-protocol type="FD_SOCK" socket-binding="jgroups-tcp-fd"/>
<protocol type="FD_ALL"/>
<protocol type="VERIFY_SUSPECT"/>
<protocol type="pbcast.NAKACK2"/>
<protocol type="UNICAST3"/>
<protocol type="pbcast.STABLE"/>
<protocol type="pbcast.GMS"/>
<protocol type="MFC"/>
<protocol type="FRAG3"/>
</stack>
</stacks>
</subsystem>
<subsystem xmlns="urn:jboss:domain:jmx:1.3">
<expose-resolved-model/>
<expose-expression-model/>
<remoting-connector/>
</subsystem>
<subsystem xmlns="urn:jboss:domain:jpa:1.1">
<jpa default-datasource="" default-extended-persistence-inheritance="DEEP"/>
</subsystem>
<subsystem xmlns="urn:jboss:domain:keycloak-server:1.1">
<web-context>auth</web-context>
<providers>
<provider>
classpath:${jboss.home.dir}/providers/*
</provider>
</providers>
<master-realm-name>master</master-realm-name>
<scheduled-task-interval>900</scheduled-task-interval>
<theme>
<staticMaxAge>-1</staticMaxAge>
<cacheThemes>false</cacheThemes>
<cacheTemplates>false</cacheTemplates>
<welcomeTheme>${env.KEYCLOAK_WELCOME_THEME:keycloak}</welcomeTheme>
<default>${env.KEYCLOAK_DEFAULT_THEME:keycloak}</default>
<dir>${jboss.home.dir}/themes</dir>
</theme>
<spi name="eventsStore">
<provider name="jpa" enabled="true">
<properties>
<property name="exclude-events" value="[&quot;REFRESH_TOKEN&quot;]"/>
</properties>
</provider>
</spi>
<spi name="userCache">
<provider name="default" enabled="true"/>
</spi>
<spi name="userSessionPersister">
<default-provider>jpa</default-provider>
</spi>
<spi name="timer">
<default-provider>basic</default-provider>
</spi>
<spi name="connectionsHttpClient">
<provider name="default" enabled="true"/>
</spi>
<spi name="connectionsJpa">
<provider name="default" enabled="true">
<properties>
<property name="dataSource" value="java:jboss/datasources/KeycloakDS"/>
<property name="initializeEmpty" value="true"/>
<property name="migrationStrategy" value="update"/>
<property name="migrationExport" value="${jboss.home.dir}/keycloak-database-update.sql"/>
</properties>
</provider>
</spi>
<spi name="realmCache">
<provider name="default" enabled="true"/>
</spi>
<spi name="connectionsInfinispan">
<default-provider>default</default-provider>
<provider name="default" enabled="true">
<properties>
<property name="cacheContainer" value="java:jboss/infinispan/container/keycloak"/>
</properties>
</provider>
</spi>
<spi name="jta-lookup">
<default-provider>${keycloak.jta.lookup.provider:jboss}</default-provider>
<provider name="jboss" enabled="true"/>
</spi>
<spi name="publicKeyStorage">
<provider name="infinispan" enabled="true">
<properties>
<property name="minTimeBetweenRequests" value="10"/>
</properties>
</provider>
</spi>
<spi name="x509cert-lookup">
<default-provider>${keycloak.x509cert.lookup.provider:default}</default-provider>
<provider name="default" enabled="true"/>
</spi>
<spi name="hostname">
<default-provider>${keycloak.hostname.provider:default}</default-provider>
<provider name="default" enabled="true">
<properties>
<property name="frontendUrl" value="${keycloak.frontendUrl:}"/>
<property name="forceBackendUrlToFrontendUrl" value="false"/>
</properties>
</provider>
<provider name="fixed" enabled="true">
<properties>
<property name="hostname" value="${keycloak.hostname.fixed.hostname:localhost}"/>
<property name="httpPort" value="${keycloak.hostname.fixed.httpPort:-1}"/>
<property name="httpsPort" value="${keycloak.hostname.fixed.httpsPort:-1}"/>
<property name="alwaysHttps" value="${keycloak.hostname.fixed.alwaysHttps:false}"/>
</properties>
</provider>
</spi>
</subsystem>
<subsystem xmlns="urn:jboss:domain:mail:4.0">
<mail-session name="default" jndi-name="java:jboss/mail/Default">
<smtp-server outbound-socket-binding-ref="mail-smtp"/>
</mail-session>
</subsystem>
<subsystem xmlns="urn:wildfly:microprofile-config-smallrye:1.0"/>
<subsystem xmlns="urn:wildfly:microprofile-health-smallrye:2.0" security-enabled="false" empty-liveness-checks-status="${env.MP_HEALTH_EMPTY_LIVENESS_CHECKS_STATUS:UP}" empty-readiness-checks-status="${env.MP_HEALTH_EMPTY_READINESS_CHECKS_STATUS:UP}"/>
<subsystem xmlns="urn:wildfly:microprofile-metrics-smallrye:2.0" security-enabled="false" exposed-subsystems="*" prefix="${wildfly.metrics.prefix:wildfly}"/>
<subsystem xmlns="urn:jboss:domain:modcluster:5.0">
<proxy name="default" advertise-socket="modcluster" listener="ajp">
<dynamic-load-provider>
<load-metric type="cpu"/>
</dynamic-load-provider>
</proxy>
</subsystem>
<subsystem xmlns="urn:jboss:domain:naming:2.0">
<remote-naming/>
</subsystem>
<subsystem xmlns="urn:jboss:domain:remoting:4.0">
<http-connector name="http-remoting-connector" connector-ref="default" security-realm="ApplicationRealm"/>
</subsystem>
<subsystem xmlns="urn:jboss:domain:request-controller:1.0"/>
<subsystem xmlns="urn:jboss:domain:security:2.0">
<security-domains>
<security-domain name="other" cache-type="default">
<authentication>
<login-module code="Remoting" flag="optional">
<module-option name="password-stacking" value="useFirstPass"/>
</login-module>
<login-module code="RealmDirect" flag="required">
<module-option name="password-stacking" value="useFirstPass"/>
</login-module>
</authentication>
</security-domain>
<security-domain name="jboss-web-policy" cache-type="default">
<authorization>
<policy-module code="Delegating" flag="required"/>
</authorization>
</security-domain>
<security-domain name="jaspitest" cache-type="default">
<authentication-jaspi>
<login-module-stack name="dummy">
<login-module code="Dummy" flag="optional"/>
</login-module-stack>
<auth-module code="Dummy"/>
</authentication-jaspi>
</security-domain>
<security-domain name="jboss-ejb-policy" cache-type="default">
<authorization>
<policy-module code="Delegating" flag="required"/>
</authorization>
</security-domain>
</security-domains>
</subsystem>
<subsystem xmlns="urn:jboss:domain:security-manager:1.0">
<deployment-permissions>
<maximum-set>
<permission class="java.security.AllPermission"/>
</maximum-set>
</deployment-permissions>
</subsystem>
<subsystem xmlns="urn:jboss:domain:transactions:5.0">
<core-environment node-identifier="${jboss.tx.node.id:1}">
<process-id>
<uuid/>
</process-id>
</core-environment>
<recovery-environment socket-binding="txn-recovery-environment" status-socket-binding="txn-status-manager"/>
<coordinator-environment statistics-enabled="${wildfly.transactions.statistics-enabled:${wildfly.statistics-enabled:false}}"/>
<object-store path="tx-object-store" relative-to="jboss.server.data.dir"/>
</subsystem>
<subsystem xmlns="urn:jboss:domain:undertow:11.0" default-server="default-server" default-virtual-host="default-host" default-servlet-container="default" default-security-domain="other" statistics-enabled="${wildfly.undertow.statistics-enabled:${wildfly.statistics-enabled:false}}">
<buffer-cache name="default"/>
<server name="default-server">
<ajp-listener name="ajp" socket-binding="ajp"/>
<http-listener name="default" read-timeout="30000" socket-binding="http" redirect-socket="https" proxy-address-forwarding="${env.PROXY_ADDRESS_FORWARDING:false}" enable-http2="true"/>
<https-listener name="https" read-timeout="30000" socket-binding="https" proxy-address-forwarding="${env.PROXY_ADDRESS_FORWARDING:false}" security-realm="ApplicationRealm" enable-http2="true"/>
<host name="default-host" alias="localhost">
<location name="/" handler="welcome-content"/>
<http-invoker security-realm="ApplicationRealm"/>
</host>
</server>
<servlet-container name="default">
<jsp-config/>
<websockets/>
</servlet-container>
<handlers>
<file name="welcome-content" path="${jboss.home.dir}/welcome-content"/>
</handlers>
</subsystem>
<subsystem xmlns="urn:jboss:domain:weld:4.0"/>
</profile>
<interfaces>
<interface name="management">
<inet-address value="${jboss.bind.address.management:127.0.0.1}"/>
</interface>
<interface name="private">
<inet-address value="${jboss.bind.address.private:127.0.0.1}"/>
</interface>
<interface name="public">
<inet-address value="${jboss.bind.address:127.0.0.1}"/>
</interface>
</interfaces>
<socket-binding-group name="standard-sockets" default-interface="public" port-offset="${jboss.socket.binding.port-offset:0}">
<socket-binding name="ajp" port="${jboss.ajp.port:8009}"/>
<socket-binding name="http" port="${jboss.http.port:8080}"/>
<socket-binding name="https" port="${jboss.https.port:8443}"/>
<socket-binding name="jgroups-mping" interface="private" multicast-address="${jboss.default.multicast.address:230.0.0.4}" multicast-port="45700"/>
<socket-binding name="jgroups-tcp" interface="private" port="7600"/>
<socket-binding name="jgroups-tcp-fd" interface="private" port="57600"/>
<socket-binding name="jgroups-udp" interface="private" port="55200" multicast-address="${jboss.default.multicast.address:230.0.0.4}" multicast-port="45688"/>
<socket-binding name="jgroups-udp-fd" interface="private" port="54200"/>
<socket-binding name="management-http" interface="management" port="${jboss.management.http.port:9990}"/>
<socket-binding name="management-https" interface="management" port="${jboss.management.https.port:9993}"/>
<socket-binding name="modcluster" multicast-address="${jboss.modcluster.multicast.address:224.0.1.105}" multicast-port="23364"/>
<socket-binding name="txn-recovery-environment" port="4712"/>
<socket-binding name="txn-status-manager" port="4713"/>
<outbound-socket-binding name="mail-smtp">
<remote-destination host="localhost" port="25"/>
</outbound-socket-binding>
</socket-binding-group>
</server>

View File

@ -0,0 +1,108 @@
import cheerio from "cheerio";
import {
replaceImportFromStaticInJsCode,
generateCssCodeToDefineGlobals
} from "./replaceImportFromStatic";
export function generateFtlFilesCodeFactory(
params: {
ftlValuesGlobalName: string;
cssGlobalsToDefine: Record<string, string>;
indexHtmlCode: string;
}
) {
const { ftlValuesGlobalName, cssGlobalsToDefine, indexHtmlCode } = params;
const $ = cheerio.load(indexHtmlCode);
$("script:not([src])").each((...[, element]) => {
const { fixedJsCode } = replaceImportFromStaticInJsCode({
ftlValuesGlobalName,
"jsCode": $(element).html()!
});
$(element).text(fixedJsCode);
});
([
["link", "href"],
["script", "src"],
] as const).forEach(([selector, attrName]) =>
$(selector).each((...[, element]) => {
const href = $(element).attr(attrName);
if (!href?.startsWith("/")) {
return;
}
$(element).attr(attrName, "${url.resourcesPath}" + href);
})
);
$("head").prepend(
[
...(Object.keys(cssGlobalsToDefine).length === 0 ? [] : [
'',
'<style>',
generateCssCodeToDefineGlobals(
{ cssGlobalsToDefine }
).cssCodeToPrependInHead,
'</style>',
''
]),
'<script>',
' Object.assign(',
` window.${ftlValuesGlobalName},`,
' {',
' "url": {',
' "loginAction": "${url.loginAction}",',
' "resourcesPath": "${url.resourcesPath}"',
' }',
' }',
' );',
'</script>',
''
].join("\n"),
);
const partiallyFixedIndexHtmlCode = $.html();
function generateFtlFilesCode(
params: {
pageBasename: "login.ftl" | "register.ftl"
}
): { ftlCode: string; } {
const { pageBasename } = params;
const $ = cheerio.load(partiallyFixedIndexHtmlCode);
$("head").prepend(
[
'',
'<script>',
` window.${ftlValuesGlobalName} = { "pageBasename": "${pageBasename}" };`,
'</script>',
''
].join("\n"),
);
return { "ftlCode": $.html() };
}
return { generateFtlFilesCode };
}

View File

@ -1,341 +0,0 @@
<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) {
<#if !messagesPerField?? >
return undefined;
</#if>
<#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) {
<#if !messagesPerField?? >
return false;
</#if>
<#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) {
<#if !messagesPerField?? >
return '';
</#if>
<#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) {
<#if !messagesPerField?? >
return false;
</#if>
<#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 (reports with saml-post-form.ftl) -->
<#-- https://github.com/InseeFrLab/keycloakify/issues/91#issue-1212319466 (reports with error.ftl and Kc18) -->
<#-- https://github.com/InseeFrLab/keycloakify/issues/109#issuecomment-1134610163 -->
key == "loginAction" &&
are_same_path(path, ["url"]) &&
["saml-post-form.ftl", "error.ftl", "info.ftl"]?seq_contains(pageId) &&
!(auth?has_content && auth.showTryAnotherWayLink())
) || (
["contextData", "idpConfig", "idp", "authenticationSession"]?seq_contains(key) &&
are_same_path(path, ["brokerContext"]) &&
["login-idp-link-confirm.ftl", "login-idp-link-email.ftl" ]?seq_contains(pageId)
) || (
key == "identityProviderBrokerCtx" &&
are_same_path(path, []) &&
["login-idp-link-confirm.ftl", "login-idp-link-email.ftl" ]?seq_contains(pageId)
) || (
["masterAdminClient", "delegateForUpdate", "defaultRole"]?seq_contains(key) &&
are_same_path(path, ["realm"])
)
>
<#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>
<#if are_same_path(path, ["auth", "showUsername"])>
<#attempt>
<#return auth.showUsername()?c>
<#recover>
<#return "ABORT: Couldn't evaluate auth.showUsername()">
</#attempt>
</#if>
<#if are_same_path(path, ["auth", "showResetCredentials"])>
<#attempt>
<#return auth.showResetCredentials()?c>
<#recover>
<#return "ABORT: Couldn't evaluate auth.showResetCredentials()">
</#attempt>
</#if>
<#if are_same_path(path, ["auth", "showTryAnotherWayLink"])>
<#attempt>
<#return auth.showTryAnotherWayLink()?c>
<#recover>
<#return "ABORT: Couldn't evaluate auth.showTryAnotherWayLink()">
</#attempt>
</#if>
<#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 != searchedPath?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>

View File

@ -1,139 +0,0 @@
import cheerio from "cheerio";
import { replaceImportsFromStaticInJsCode, replaceImportsInInlineCssCode, generateCssCodeToDefineGlobals } from "../replaceImportFromStatic";
import * as fs from "fs";
import { join as pathJoin } from "path";
import { objectKeys } from "tsafe/objectKeys";
import { ftlValuesGlobalName } from "../ftlValuesGlobalName";
// https://github.com/keycloak/keycloak/blob/main/services/src/main/java/org/keycloak/forms/login/freemarker/Templates.java
export const pageIds = [
"login.ftl",
"register.ftl",
"register-user-profile.ftl",
"info.ftl",
"error.ftl",
"login-reset-password.ftl",
"login-verify-email.ftl",
"terms.ftl",
"login-otp.ftl",
"login-update-profile.ftl",
"login-update-password.ftl",
"login-idp-link-confirm.ftl",
"login-idp-link-email.ftl",
"login-page-expired.ftl",
"login-config-totp.ftl",
"logout-confirm.ftl",
] as const;
export type PageId = typeof pageIds[number];
export function generateFtlFilesCodeFactory(params: {
cssGlobalsToDefine: Record<string, string>;
indexHtmlCode: string;
urlPathname: string;
urlOrigin: undefined | string;
}) {
const { cssGlobalsToDefine, indexHtmlCode, urlPathname, urlOrigin } = params;
const $ = cheerio.load(indexHtmlCode);
$("script:not([src])").each((...[, element]) => {
const { fixedJsCode } = replaceImportsFromStaticInJsCode({
"jsCode": $(element).html()!,
urlOrigin,
});
$(element).text(fixedJsCode);
});
$("style").each((...[, element]) => {
const { fixedCssCode } = replaceImportsInInlineCssCode({
"cssCode": $(element).html()!,
"urlPathname": params.urlPathname,
urlOrigin,
});
$(element).text(fixedCssCode);
});
(
[
["link", "href"],
["script", "src"],
] as const
).forEach(([selector, attrName]) =>
$(selector).each((...[, element]) => {
const href = $(element).attr(attrName);
if (href === undefined) {
return;
}
$(element).attr(
attrName,
urlOrigin !== undefined
? href.replace(/^\//, `${urlOrigin}/`)
: 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 replaceValueBySearchValue = {
'{ "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 -->": [
"<#if scripts??>",
" <#list scripts as script>",
' <script src="${script}" type="text/javascript"></script>',
" </#list>",
"</#if>",
].join("\n"),
};
$("head").prepend(
[
...(Object.keys(cssGlobalsToDefine).length === 0
? []
: [
"",
"<style>",
generateCssCodeToDefineGlobals({
cssGlobalsToDefine,
urlPathname,
}).cssCodeToPrependInHead,
"</style>",
"",
]),
"<script>",
` window.${ftlValuesGlobalName}= ${objectKeys(replaceValueBySearchValue)[0]};`,
"</script>",
"",
objectKeys(replaceValueBySearchValue)[1],
].join("\n"),
);
const partiallyFixedIndexHtmlCode = $.html();
function generateFtlFilesCode(params: { pageId: string }): {
ftlCode: string;
} {
const { pageId } = params;
const $ = cheerio.load(partiallyFixedIndexHtmlCode);
let ftlCode = $.html();
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 { generateFtlFilesCode };
}

View File

@ -1 +0,0 @@
export * from "./generateFtl";

View File

@ -1,38 +1,43 @@
import * as url from "url";
import * as fs from "fs";
import { join as pathJoin, dirname as pathDirname } from "path";
export function generateJavaStackFiles(params: {
export type ParsedPackageJson = {
name: string;
version: string;
themeName: string;
homepage?: string;
keycloakThemeBuildingDirPath: string;
doBundleEmailTemplate: boolean;
}): {
jarFilePath: string;
} {
const { themeName, version, homepage, keycloakThemeBuildingDirPath, doBundleEmailTemplate } = params;
};
export function generateJavaStackFiles(
params: {
parsedPackageJson: ParsedPackageJson;
keycloakThemeBuildingDirPath: string;
}
): void {
const {
parsedPackageJson: { name, version, homepage },
keycloakThemeBuildingDirPath
} = params;
{
const { pomFileCode } = (function generatePomFileCode(): {
pomFileCode: string;
} {
const groupId = (() => {
const fallbackGroupId = `there.was.no.homepage.field.in.the.package.json.${themeName}`;
return (
(!homepage
? fallbackGroupId
: url
.parse(homepage)
.host?.replace(/:[0-9]+$/, "")
?.split(".")
.reverse()
.join(".") ?? fallbackGroupId) + ".keycloak"
);
const { pomFileCode } = (function generatePomFileCode(): { pomFileCode: string; } {
const groupId = (() => {
const fallbackGroupId = `there.was.no.homepage.field.in.the.package.json.${name}`;
return (!homepage ?
fallbackGroupId :
url.parse(homepage).host?.split(".").reverse().join(".") ?? fallbackGroupId
) + ".keycloak";
})();
const artefactId = `${themeName}-keycloak-theme`;
const artefactId = `${name}-keycloak-theme`;
const pomFileCode = [
`<?xml version="1.0"?>`,
@ -45,43 +50,49 @@ export function generateJavaStackFiles(params: {
` <version>${version}</version>`,
` <name>${artefactId}</name>`,
` <description />`,
`</project>`,
`</project>`
].join("\n");
return { pomFileCode };
})();
fs.writeFileSync(pathJoin(keycloakThemeBuildingDirPath, "pom.xml"), Buffer.from(pomFileCode, "utf8"));
fs.writeFileSync(
pathJoin(keycloakThemeBuildingDirPath, "pom.xml"),
Buffer.from(pomFileCode, "utf8")
);
}
{
const themeManifestFilePath = pathJoin(keycloakThemeBuildingDirPath, "src", "main", "resources", "META-INF", "keycloak-themes.json");
const themeManifestFilePath = pathJoin(
keycloakThemeBuildingDirPath, "src", "main",
"resources", "META-INF", "keycloak-themes.json"
);
try {
fs.mkdirSync(pathDirname(themeManifestFilePath));
} catch {}
} catch { }
fs.writeFileSync(
themeManifestFilePath,
Buffer.from(
JSON.stringify(
{
"themes": [
{
"name": themeName,
"types": ["login", ...(doBundleEmailTemplate ? ["email"] : [])],
},
],
},
null,
2,
),
"utf8",
),
JSON.stringify({
"themes": [
{
"name": name,
"types": ["login"]
}
]
}, null, 2),
"utf8"
)
);
}
return {
"jarFilePath": pathJoin(keycloakThemeBuildingDirPath, "target", `${themeName}-${version}.jar`),
};
}

View File

@ -1,168 +1,88 @@
import { transformCodebase } from "../tools/transformCodebase";
import * as fs from "fs";
import { join as pathJoin, basename as pathBasename } from "path";
import { replaceImportsInCssCode, replaceImportsFromStaticInJsCode } from "./replaceImportFromStatic";
import { generateFtlFilesCodeFactory, pageIds } from "./generateFtl";
import { downloadBuiltinKeycloakTheme } from "../download-builtin-keycloak-theme";
import * as child_process from "child_process";
import { mockTestingResourcesCommonPath, mockTestingResourcesPath, mockTestingSubDirOfPublicDirBasename } from "../mockTestingResourcesPath";
import { isInside } from "../tools/isInside";
export function generateKeycloakThemeResources(params: {
themeName: string;
reactAppBuildDirPath: string;
keycloakThemeBuildingDirPath: string;
keycloakThemeEmailDirPath: string;
urlPathname: string;
//If urlOrigin is not undefined then it means --externals-assets
urlOrigin: undefined | string;
extraPagesId: string[];
extraThemeProperties: string[];
keycloakVersion: string;
}): { doBundleEmailTemplate: boolean } {
const {
themeName,
reactAppBuildDirPath,
keycloakThemeBuildingDirPath,
keycloakThemeEmailDirPath,
urlPathname,
urlOrigin,
extraPagesId,
extraThemeProperties,
keycloakVersion,
} = params;
import { transformCodebase } from "../../tools/transformCodebase";
import * as fs from "fs";
import { join as pathJoin } from "path";
import {
replaceImportFromStaticInCssCode,
replaceImportFromStaticInJsCode
} from "./replaceImportFromStatic";
import { generateFtlFilesCodeFactory } from "./generateFtl";
const ftlValuesGlobalName = "keycloakFtlValues";
export function generateKeycloakThemeResources(
params: {
themeName: string;
reactAppBuildDirPath: string;
keycloakThemeBuildingDirPath: string;
}
) {
const { themeName, reactAppBuildDirPath, keycloakThemeBuildingDirPath } = params;
const themeDirPath = pathJoin(keycloakThemeBuildingDirPath, "src", "main", "resources", "theme", themeName, "login");
let allCssGlobalsToDefine: Record<string, string> = {};
transformCodebase({
"destDirPath": urlOrigin === undefined ? pathJoin(themeDirPath, "resources", "build") : reactAppBuildDirPath,
"destDirPath": pathJoin(themeDirPath, "resources"),
"srcDirPath": reactAppBuildDirPath,
"transformSourceCode": ({ filePath, sourceCode }) => {
//NOTE: Prevent cycles, excludes the folder we generated for debug in public/
if (
urlOrigin === undefined &&
isInside({
"dirPath": pathJoin(reactAppBuildDirPath, mockTestingSubDirOfPublicDirBasename),
filePath,
})
) {
return undefined;
}
"transformSourceCodeString": ({ filePath, sourceCode }) => {
if (urlOrigin === undefined && /\.css?$/i.test(filePath)) {
const { cssGlobalsToDefine, fixedCssCode } = replaceImportsInCssCode({
"cssCode": sourceCode.toString("utf8"),
});
if (/\.css?$/i.test(filePath)) {
const { cssGlobalsToDefine, fixedCssCode } = replaceImportFromStaticInCssCode(
{ "cssCode": sourceCode.toString("utf8") }
);
allCssGlobalsToDefine = {
...allCssGlobalsToDefine,
...cssGlobalsToDefine,
...cssGlobalsToDefine
};
return {
"modifiedSourceCode": Buffer.from(fixedCssCode, "utf8"),
};
return { "modifiedSourceCode": Buffer.from(fixedCssCode, "utf8") };
}
if (/\.js?$/i.test(filePath)) {
const { fixedJsCode } = replaceImportsFromStaticInJsCode({
const { fixedJsCode } = replaceImportFromStaticInJsCode({
"jsCode": sourceCode.toString("utf8"),
urlOrigin,
ftlValuesGlobalName
});
return {
"modifiedSourceCode": Buffer.from(fixedJsCode, "utf8"),
};
return { "modifiedSourceCode": Buffer.from(fixedJsCode, "utf8") };
}
return urlOrigin === undefined ? { "modifiedSourceCode": sourceCode } : undefined;
},
});
return { "modifiedSourceCode": sourceCode };
let doBundleEmailTemplate: boolean;
email: {
if (!fs.existsSync(keycloakThemeEmailDirPath)) {
console.log(
[
`Not bundling email template because ${pathBasename(keycloakThemeEmailDirPath)} does not exist`,
`To start customizing the email template, run: 👉 npx create-keycloak-email-directory 👈`,
].join("\n"),
);
doBundleEmailTemplate = false;
break email;
}
doBundleEmailTemplate = true;
transformCodebase({
"srcDirPath": keycloakThemeEmailDirPath,
"destDirPath": pathJoin(themeDirPath, "..", "email"),
});
}
});
const { generateFtlFilesCode } = generateFtlFilesCodeFactory({
"cssGlobalsToDefine": allCssGlobalsToDefine,
"indexHtmlCode": fs.readFileSync(pathJoin(reactAppBuildDirPath, "index.html")).toString("utf8"),
urlPathname,
urlOrigin,
ftlValuesGlobalName,
"indexHtmlCode": fs.readFileSync(
pathJoin(reactAppBuildDirPath, "index.html")
).toString("utf8")
});
[...pageIds, ...extraPagesId].forEach(pageId => {
const { ftlCode } = generateFtlFilesCode({ pageId });
(["login.ftl", "register.ftl"] as const).forEach(pageBasename => {
fs.mkdirSync(themeDirPath, { "recursive": true });
fs.writeFileSync(pathJoin(themeDirPath, pageId), Buffer.from(ftlCode, "utf8"));
});
{
const tmpDirPath = pathJoin(themeDirPath, "..", "tmp_xxKdLpdIdLd");
downloadBuiltinKeycloakTheme({
keycloakVersion,
"destDirPath": tmpDirPath,
});
const themeResourcesDirPath = pathJoin(themeDirPath, "resources");
transformCodebase({
"srcDirPath": pathJoin(tmpDirPath, "keycloak", "login", "resources"),
"destDirPath": themeResourcesDirPath,
});
const reactAppPublicDirPath = pathJoin(reactAppBuildDirPath, "..", "public");
transformCodebase({
"srcDirPath": pathJoin(tmpDirPath, "keycloak", "common", "resources"),
"destDirPath": pathJoin(themeResourcesDirPath, pathBasename(mockTestingResourcesCommonPath)),
});
transformCodebase({
"srcDirPath": themeResourcesDirPath,
"destDirPath": pathJoin(reactAppPublicDirPath, mockTestingResourcesPath),
});
const keycloakResourcesWithinPublicDirPath = pathJoin(reactAppPublicDirPath, mockTestingSubDirOfPublicDirBasename);
const { ftlCode } = generateFtlFilesCode({ pageBasename });
fs.writeFileSync(
pathJoin(keycloakResourcesWithinPublicDirPath, "README.txt"),
Buffer.from(
["This is just a test folder that helps develop", "the login and register page without having to run a Keycloak container"].join(" "),
),
);
pathJoin(themeDirPath, pageBasename),
Buffer.from(ftlCode, "utf8")
)
fs.writeFileSync(pathJoin(keycloakResourcesWithinPublicDirPath, ".gitignore"), Buffer.from("*", "utf8"));
child_process.execSync(`rm -r ${tmpDirPath}`);
}
});
fs.writeFileSync(
pathJoin(themeDirPath, "theme.properties"),
Buffer.from("parent=keycloak".concat("\n\n", extraThemeProperties.join("\n\n")), "utf8"),
Buffer.from("parent=base\n", "utf8")
);
return { doBundleEmailTemplate };
}

View File

@ -1,44 +0,0 @@
import * as fs from "fs";
import { join as pathJoin } from "path";
generateStartKeycloakTestingContainer.basename = "start_keycloak_testing_container.sh";
const containerName = "keycloak-testing-container";
/** Files for being able to run a hot reload keycloak container */
export function generateStartKeycloakTestingContainer(params: { keycloakVersion: string; themeName: string; keycloakThemeBuildingDirPath: string }) {
const { themeName, keycloakThemeBuildingDirPath, keycloakVersion } = params;
fs.writeFileSync(
pathJoin(keycloakThemeBuildingDirPath, generateStartKeycloakTestingContainer.basename),
Buffer.from(
[
"#!/bin/bash",
"",
`docker rm ${containerName} || true`,
"",
`cd ${keycloakThemeBuildingDirPath}`,
"",
"docker run \\",
" -p 8080:8080 \\",
` --name ${containerName} \\`,
" -e KEYCLOAK_ADMIN=admin \\",
" -e KEYCLOAK_ADMIN_PASSWORD=admin \\",
" -e JAVA_OPTS=-Dkeycloak.profile=preview \\",
` -v ${pathJoin(
keycloakThemeBuildingDirPath,
"src",
"main",
"resources",
"theme",
themeName,
)}:/opt/keycloak/themes/${themeName}:rw \\`,
` -it quay.io/keycloak/keycloak:${keycloakVersion} \\`,
` start-dev`,
"",
].join("\n"),
"utf8",
),
{ "mode": 0o755 },
);
}

View File

@ -1,8 +1,39 @@
#!/usr/bin/env node
export * from "./build-keycloak-theme";
import { main } from "./build-keycloak-theme";
import { generateKeycloakThemeResources } from "./generateKeycloakThemeResources";
import { generateJavaStackFiles } from "./generateJavaStackFiles";
import type { ParsedPackageJson } from "./generateJavaStackFiles";
import { join as pathJoin } from "path";
import * as child_process from "child_process";
import { generateDebugFiles } from "./generateDebugFiles";
const reactProjectDirPath = process.cwd();
const parsedPackageJson: ParsedPackageJson = require(pathJoin(reactProjectDirPath, "package.json"));
export const keycloakThemeBuildingDirPath = pathJoin(reactProjectDirPath, "build_keycloak");
if (require.main === module) {
main();
generateKeycloakThemeResources({
keycloakThemeBuildingDirPath,
"reactAppBuildDirPath": pathJoin(reactProjectDirPath, "build"),
"themeName": parsedPackageJson.name
});
generateJavaStackFiles({
parsedPackageJson,
keycloakThemeBuildingDirPath
});
child_process.execSync(
"mvn package",
{ "cwd": keycloakThemeBuildingDirPath }
);
generateDebugFiles({
keycloakThemeBuildingDirPath,
"packageJsonName": parsedPackageJson.name
});
}

View File

@ -1,116 +1,91 @@
import * as crypto from "crypto";
import { ftlValuesGlobalName } from "./ftlValuesGlobalName";
export function replaceImportsFromStaticInJsCode(params: { jsCode: string; urlOrigin: undefined | string }): { fixedJsCode: string } {
/*
NOTE:
export function replaceImportFromStaticInJsCode(
params: {
ftlValuesGlobalName: string;
jsCode: string;
}
): { fixedJsCode: string; } {
When we have urlOrigin defined it means that
we are building with --external-assets
so we have to make sur that the fixed js code will run
inside and outside keycloak.
const { jsCode, ftlValuesGlobalName } = params;
When urlOrigin isn't defined we can assume the fixedJsCode
will always run in keycloak context.
*/
const { jsCode, urlOrigin } = params;
const fixedJsCode = jsCode
.replace(
/([a-zA-Z]+)\.([a-zA-Z]+)=function\(([a-zA-Z]+)\){return"static\/js\/"/g,
(...[, n, u, e]) => `
${n}[(function(){
${
urlOrigin === undefined
? `
Object.defineProperty(${n}, "p", {
get: function() { return window.${ftlValuesGlobalName}.url.resourcesPath; },
set: function (){}
});
`
: `
var p= "";
Object.defineProperty(${n}, "p", {
get: function() { return ("${ftlValuesGlobalName}" in window ? "${urlOrigin}" : "") + p; },
set: function (value){ p = value;}
});
`
}
return "${u}";
})()] = function(${e}) { return "${urlOrigin === undefined ? "/build/" : ""}static/js/"`,
)
.replace(/([a-zA-Z]+\.[a-zA-Z]+)\+"static\//g, (...[, group]) =>
urlOrigin === undefined
? `window.${ftlValuesGlobalName}.url.resourcesPath + "/build/static/`
: `("${ftlValuesGlobalName}" in window ? "${urlOrigin}" : "") + ${group} + "static/`,
)
//TODO: Write a test case for this
.replace(/".chunk.css",([a-zA-Z])+=([a-zA-Z]+\.[a-zA-Z]+)\+([a-zA-Z]+),/, (...[, group1, group2, group3]) =>
urlOrigin === undefined
? `".chunk.css",${group1} = window.${ftlValuesGlobalName}.url.resourcesPath + "/build/" + ${group3},`
: `".chunk.css",${group1} = ("${ftlValuesGlobalName}" in window ? "${urlOrigin}" : "") + ${group2} + ${group3},`,
);
return { fixedJsCode };
}
export function replaceImportsInInlineCssCode(params: { cssCode: string; urlPathname: string; urlOrigin: undefined | string }): {
fixedCssCode: string;
} {
const { cssCode, urlPathname, urlOrigin } = params;
const fixedCssCode = cssCode.replace(
urlPathname === "/" ? /url\(["']?\/([^/][^)"']+)["']?\)/g : new RegExp(`url\\(["']?${urlPathname}([^)"']+)["']?\\)`, "g"),
(...[, group]) => `url(${urlOrigin === undefined ? "${url.resourcesPath}/build/" + group : params.urlOrigin + urlPathname + group})`,
const fixedJsCode = jsCode!.replace(
/"static\//g,
`window.${ftlValuesGlobalName}.url.resourcesPath.replace(/^\\//,"") + "/" + "static/`
);
return { fixedCssCode };
return { fixedJsCode };
}
export function replaceImportsInCssCode(params: { cssCode: string }): {
export function replaceImportFromStaticInCssCode(
params: {
cssCode: string;
}
): {
fixedCssCode: string;
cssGlobalsToDefine: Record<string, string>;
} {
const { cssCode } = params;
const cssGlobalsToDefine: Record<string, string> = {};
new Set(cssCode.match(/url\(["']?\/[^/][^)"']+["']?\)[^;}]*/g) ?? []).forEach(
match => (cssGlobalsToDefine["url" + crypto.createHash("sha256").update(match).digest("hex").substring(0, 15)] = match),
);
new Set(cssCode.match(/(url\(\/[^)]+\))/g) ?? [])
.forEach(match =>
cssGlobalsToDefine[
"url" + crypto
.createHash("sha256")
.update(match)
.digest("hex")
.substring(0, 15)
] = match
);
let fixedCssCode = cssCode;
Object.keys(cssGlobalsToDefine).forEach(
cssVariableName =>
//NOTE: split/join pattern ~ replace all
(fixedCssCode = fixedCssCode.split(cssGlobalsToDefine[cssVariableName]).join(`var(--${cssVariableName})`)),
fixedCssCode =
fixedCssCode.split(cssGlobalsToDefine[cssVariableName])
.join(`var(--${cssVariableName})`)
);
return { fixedCssCode, cssGlobalsToDefine };
}
export function generateCssCodeToDefineGlobals(params: { cssGlobalsToDefine: Record<string, string>; urlPathname: string }): {
export function generateCssCodeToDefineGlobals(
params: {
cssGlobalsToDefine: Record<string, string>;
}
): {
cssCodeToPrependInHead: string;
} {
const { cssGlobalsToDefine, urlPathname } = params;
const { cssGlobalsToDefine } = params;
return {
"cssCodeToPrependInHead": [
":root {",
...Object.keys(cssGlobalsToDefine)
.map(cssVariableName =>
.map(cssVariableName => [
`--${cssVariableName}:`,
[
`--${cssVariableName}:`,
cssGlobalsToDefine[cssVariableName].replace(
new RegExp(`url\\(${urlPathname.replace(/\//g, "\\/")}`, "g"),
"url(${url.resourcesPath}/build/",
),
].join(" "),
)
"url(",
"${url.resourcesPath}" +
cssGlobalsToDefine[cssVariableName].match(/^url\(([^)]+)\)$/)![1],
")"
].join("")
].join(" "))
.map(line => ` ${line};`),
"}",
].join("\n"),
"}"
].join("\n")
};
}

View File

@ -1,36 +0,0 @@
#!/usr/bin/env node
import { downloadBuiltinKeycloakTheme } from "./download-builtin-keycloak-theme";
import { keycloakThemeEmailDirPath } from "./build-keycloak-theme";
import { join as pathJoin, basename as pathBasename } from "path";
import { transformCodebase } from "./tools/transformCodebase";
import { promptKeycloakVersion } from "./promptKeycloakVersion";
import * as fs from "fs";
if (require.main === module) {
(async () => {
if (fs.existsSync(keycloakThemeEmailDirPath)) {
console.log(`There is already a ./${pathBasename(keycloakThemeEmailDirPath)} directory in your project. Aborting.`);
process.exit(-1);
}
const { keycloakVersion } = await promptKeycloakVersion();
const builtinKeycloakThemeTmpDirPath = pathJoin(keycloakThemeEmailDirPath, "..", "tmp_xIdP3_builtin_keycloak_theme");
downloadBuiltinKeycloakTheme({
keycloakVersion,
"destDirPath": builtinKeycloakThemeTmpDirPath,
});
transformCodebase({
"srcDirPath": pathJoin(builtinKeycloakThemeTmpDirPath, "base", "email"),
"destDirPath": keycloakThemeEmailDirPath,
});
console.log(`./${pathBasename(keycloakThemeEmailDirPath)} ready to be customized`);
fs.rmSync(builtinKeycloakThemeTmpDirPath, { "recursive": true, "force": true });
})();
}

View File

@ -1,33 +0,0 @@
#!/usr/bin/env node
import { keycloakThemeBuildingDirPath } from "./build-keycloak-theme";
import { join as pathJoin } from "path";
import { downloadAndUnzip } from "./tools/downloadAndUnzip";
import { promptKeycloakVersion } from "./promptKeycloakVersion";
export function downloadBuiltinKeycloakTheme(params: { keycloakVersion: string; destDirPath: string }) {
const { keycloakVersion, destDirPath } = params;
for (const ext of ["", "-community"]) {
downloadAndUnzip({
"destDirPath": destDirPath,
"url": `https://github.com/keycloak/keycloak/archive/refs/tags/${keycloakVersion}.zip`,
"pathOfDirToExtractInArchive": `keycloak-${keycloakVersion}/themes/src/main/resources${ext}/theme`,
});
}
}
if (require.main === module) {
(async () => {
const { keycloakVersion } = await promptKeycloakVersion();
const destDirPath = pathJoin(keycloakThemeBuildingDirPath, "src", "main", "resources", "theme");
console.log(`Downloading builtins theme of Keycloak ${keycloakVersion} here ${destDirPath}`);
downloadBuiltinKeycloakTheme({
keycloakVersion,
destDirPath,
});
})();
}

View File

@ -0,0 +1,18 @@
#!/usr/bin/env node
import * as fs from "fs";
import { join as pathJoin, basename as pathBasename } from "path";
import { keycloakThemeBuildingDirPath } from "./build-keycloak-theme";
import child_process from "child_process";
if (!fs.existsSync(keycloakThemeBuildingDirPath)) {
console.log("Error: The keycloak theme need to be build");
process.exit(1);
}
const url = "https://github.com/garronej/keycloak-react-theming/releases/download/v0.0.1/other_keycloak_thems.zip";
[
`wget ${url}`,
...["unzip", "rm"].map(prg => `${prg} ${pathBasename(url)}`),
].forEach(cmd => child_process.execSync(cmd, { "cwd": pathJoin(keycloakThemeBuildingDirPath, "src", "main", "resources", "theme") }));

View File

@ -1,81 +0,0 @@
import "minimal-polyfills/Object.fromEntries";
import * as fs from "fs";
import { join as pathJoin, relative as pathRelative, dirname as pathDirname } from "path";
import { crawl } from "./tools/crawl";
import { downloadBuiltinKeycloakTheme } from "./download-builtin-keycloak-theme";
import { getProjectRoot } from "./tools/getProjectRoot";
import { rm_rf, rm_r } from "./tools/rm";
//NOTE: To run without argument when we want to generate src/i18n/generated_kcMessages files,
// update the version array for generating for newer version.
//@ts-ignore
const propertiesParser = require("properties-parser");
for (const keycloakVersion of ["11.0.3", "15.0.2", "18.0.1"]) {
console.log({ keycloakVersion });
const tmpDirPath = pathJoin(getProjectRoot(), "tmp_xImOef9dOd44");
rm_rf(tmpDirPath);
downloadBuiltinKeycloakTheme({
keycloakVersion,
"destDirPath": tmpDirPath,
});
type Dictionary = { [idiomId: string]: string };
const record: { [typeOfPage: string]: { [language: string]: Dictionary } } = {};
{
const baseThemeDirPath = pathJoin(tmpDirPath, "base");
crawl(baseThemeDirPath).forEach(filePath => {
const match = filePath.match(/^([^/]+)\/messages\/messages_([^.]+)\.properties$/);
if (match === null) {
return;
}
const [, typeOfPage, language] = match;
(record[typeOfPage] ??= {})[language.replace(/_/g, "-")] = Object.fromEntries(
Object.entries(propertiesParser.parse(fs.readFileSync(pathJoin(baseThemeDirPath, filePath)).toString("utf8"))).map(
([key, value]: any) => [key, value.replace(/''/g, "'")],
),
);
});
}
rm_r(tmpDirPath);
Object.keys(record).forEach(pageType => {
const recordForPageType = record[pageType];
Object.keys(recordForPageType).forEach(language => {
const filePath = pathJoin(getProjectRoot(), "src", "lib", "i18n", "generated_messages", keycloakVersion, pageType, `${language}.ts`);
fs.mkdirSync(pathDirname(filePath), { "recursive": true });
fs.writeFileSync(
filePath,
Buffer.from(
[
`//This code was automatically generated by running ${pathRelative(getProjectRoot(), __filename)}`,
"//PLEASE DO NOT EDIT MANUALLY",
"",
"/* spell-checker: disable */",
`const messages= ${JSON.stringify(recordForPageType[language], null, 2)};`,
"",
"export default messages;",
"/* spell-checker: enable */",
].join("\n"),
"utf8",
),
);
console.log(`${filePath} wrote`);
});
});
}

View File

@ -1,102 +0,0 @@
import { execSync } from "child_process";
import { join as pathJoin, relative as pathRelative } from "path";
import * as fs from "fs";
const keycloakifyDirPath = pathJoin(__dirname, "..", "..");
fs.writeFileSync(
pathJoin(keycloakifyDirPath, "dist", "package.json"),
Buffer.from(
JSON.stringify(
(() => {
const packageJsonParsed = JSON.parse(fs.readFileSync(pathJoin(keycloakifyDirPath, "package.json")).toString("utf8"));
return {
...packageJsonParsed,
"main": packageJsonParsed["main"].replace(/^dist\//, ""),
"types": packageJsonParsed["types"].replace(/^dist\//, ""),
};
})(),
null,
2,
),
"utf8",
),
);
const commonThirdPartyDeps = (() => {
const namespaceModuleNames = ["@emotion"];
const standaloneModuleNames = ["react", "@types/react", "powerhooks", "tss-react", "evt"];
return [
...namespaceModuleNames
.map(namespaceModuleName =>
fs
.readdirSync(pathJoin(keycloakifyDirPath, "node_modules", namespaceModuleName))
.map(submoduleName => `${namespaceModuleName}/${submoduleName}`),
)
.reduce((prev, curr) => [...prev, ...curr], []),
...standaloneModuleNames,
];
})();
const yarnHomeDirPath = pathJoin(keycloakifyDirPath, ".yarn_home");
execSync(["rm -rf", "mkdir"].map(cmd => `${cmd} ${yarnHomeDirPath}`).join(" && "));
const execYarnLink = (params: { targetModuleName?: string; cwd: string }) => {
const { targetModuleName, cwd } = params;
const cmd = ["yarn", "link", ...(targetModuleName !== undefined ? [targetModuleName] : [])].join(" ");
console.log(`$ cd ${pathRelative(keycloakifyDirPath, cwd) || "."} && ${cmd}`);
execSync(cmd, {
cwd,
"env": {
...process.env,
"HOME": yarnHomeDirPath,
},
});
};
const testAppNames = [process.argv[2] ?? "keycloakify-demo-app"] as const;
const getTestAppPath = (testAppName: typeof testAppNames[number]) => pathJoin(keycloakifyDirPath, "..", testAppName);
testAppNames.forEach(testAppName => execSync("yarn install", { "cwd": getTestAppPath(testAppName) }));
console.log("=== Linking common dependencies ===");
const total = commonThirdPartyDeps.length;
let current = 0;
commonThirdPartyDeps.forEach(commonThirdPartyDep => {
current++;
console.log(`${current}/${total} ${commonThirdPartyDep}`);
const localInstallPath = pathJoin(
...[keycloakifyDirPath, "node_modules", ...(commonThirdPartyDep.startsWith("@") ? commonThirdPartyDep.split("/") : [commonThirdPartyDep])],
);
execYarnLink({ "cwd": localInstallPath });
testAppNames.forEach(testAppName =>
execYarnLink({
"cwd": getTestAppPath(testAppName),
"targetModuleName": commonThirdPartyDep,
}),
);
});
console.log("=== Linking in house dependencies ===");
execYarnLink({ "cwd": pathJoin(keycloakifyDirPath, "dist") });
testAppNames.forEach(testAppName =>
execYarnLink({
"cwd": getTestAppPath(testAppName),
"targetModuleName": "keycloakify",
}),
);

View File

@ -1,5 +0,0 @@
import { pathJoin } from "./tools/pathJoin";
export const mockTestingSubDirOfPublicDirBasename = "keycloak_static";
export const mockTestingResourcesPath = pathJoin(mockTestingSubDirOfPublicDirBasename, "resources");
export const mockTestingResourcesCommonPath = pathJoin(mockTestingResourcesPath, "resources_common");

View File

@ -1,47 +0,0 @@
import { getLatestsSemVersionedTagFactory } from "./tools/octokit-addons/getLatestsSemVersionedTag";
import { Octokit } from "@octokit/rest";
import cliSelect from "cli-select";
export async function promptKeycloakVersion() {
const { getLatestsSemVersionedTag } = (() => {
const { octokit } = (() => {
const githubToken = process.env.GITHUB_TOKEN;
const octokit = new Octokit(githubToken === undefined ? undefined : { "auth": githubToken });
return { octokit };
})();
const { getLatestsSemVersionedTag } = getLatestsSemVersionedTagFactory({ octokit });
return { getLatestsSemVersionedTag };
})();
console.log("Initialize the directory with email template from which keycloak version?");
const tags = [
...(await getLatestsSemVersionedTag({
"count": 10,
"doIgnoreBeta": true,
"owner": "keycloak",
"repo": "keycloak",
}).then(arr => arr.map(({ tag }) => tag))),
"11.0.3",
];
if (process.env["GITHUB_ACTIONS"] === "true") {
return { "keycloakVersion": tags[0] };
}
const { value: keycloakVersion } = await cliSelect<string>({
"values": tags,
}).catch(() => {
console.log("Aborting");
process.exit(-1);
});
console.log(keycloakVersion);
return { keycloakVersion };
}

View File

@ -1,73 +0,0 @@
export type NpmModuleVersion = {
major: number;
minor: number;
patch: number;
betaPreRelease?: number;
};
export namespace NpmModuleVersion {
export function parse(versionStr: string): NpmModuleVersion {
const match = versionStr.match(/^([0-9]+)\.([0-9]+)\.([0-9]+)(?:-beta.([0-9]+))?/);
if (!match) {
throw new Error(`${versionStr} is not a valid NPM version`);
}
return {
"major": parseInt(match[1]),
"minor": parseInt(match[2]),
"patch": parseInt(match[3]),
...(() => {
const str = match[4];
return str === undefined ? {} : { "betaPreRelease": parseInt(str) };
})(),
};
}
export function stringify(v: NpmModuleVersion) {
return `${v.major}.${v.minor}.${v.patch}${v.betaPreRelease === undefined ? "" : `-beta.${v.betaPreRelease}`}`;
}
/**
*
* v1 < v2 => -1
* v1 === v2 => 0
* v1 > v2 => 1
*
*/
export function compare(v1: NpmModuleVersion, v2: NpmModuleVersion): -1 | 0 | 1 {
const sign = (diff: number): -1 | 0 | 1 => (diff === 0 ? 0 : diff < 0 ? -1 : 1);
const noUndefined = (n: number | undefined) => n ?? Infinity;
for (const level of ["major", "minor", "patch", "betaPreRelease"] as const) {
if (noUndefined(v1[level]) !== noUndefined(v2[level])) {
return sign(noUndefined(v1[level]) - noUndefined(v2[level]));
}
}
return 0;
}
/*
console.log(compare(parse("3.0.0-beta.3"), parse("3.0.0")) === -1 )
console.log(compare(parse("3.0.0-beta.3"), parse("3.0.0-beta.4")) === -1 )
console.log(compare(parse("3.0.0-beta.3"), parse("4.0.0")) === -1 )
*/
export function bumpType(params: { versionBehindStr: string; versionAheadStr: string }): "major" | "minor" | "patch" | "betaPreRelease" | "same" {
const versionAhead = parse(params.versionAheadStr);
const versionBehind = parse(params.versionBehindStr);
if (compare(versionBehind, versionAhead) === 1) {
throw new Error(`Version regression ${versionBehind} -> ${versionAhead}`);
}
for (const level of ["major", "minor", "patch", "betaPreRelease"] as const) {
if (versionBehind[level] !== versionAhead[level]) {
return level;
}
}
return "same";
}
}

View File

@ -1,32 +0,0 @@
import { basename as pathBasename, join as pathJoin } from "path";
import { execSync } from "child_process";
import * as fs from "fs";
import { transformCodebase } from "./transformCodebase";
import { rm_rf, rm, rm_r } from "./rm";
/** assert url ends with .zip */
export function downloadAndUnzip(params: { url: string; destDirPath: string; pathOfDirToExtractInArchive?: string }) {
const { url, destDirPath, pathOfDirToExtractInArchive } = params;
const tmpDirPath = pathJoin(destDirPath, "..", "tmp_xxKdOxnEdx");
const zipFilePath = pathBasename(url);
rm_rf(tmpDirPath);
fs.mkdirSync(tmpDirPath, { "recursive": true });
execSync(`curl -L ${url} -o ${zipFilePath}`, { "cwd": tmpDirPath });
execSync(`unzip -o ${zipFilePath}${pathOfDirToExtractInArchive === undefined ? "" : ` "${pathOfDirToExtractInArchive}/**/*"`}`, {
"cwd": tmpDirPath,
});
rm(pathBasename(url), { "cwd": tmpDirPath });
transformCodebase({
"srcDirPath": pathOfDirToExtractInArchive === undefined ? tmpDirPath : pathJoin(tmpDirPath, pathOfDirToExtractInArchive),
destDirPath,
});
rm_r(tmpDirPath);
}

View File

@ -1,19 +0,0 @@
import * as fs from "fs";
import * as path from "path";
function getProjectRootRec(dirPath: string): string {
if (fs.existsSync(path.join(dirPath, "package.json"))) {
return dirPath;
}
return getProjectRootRec(path.join(dirPath, ".."));
}
let result: string | undefined = undefined;
export function getProjectRoot(): string {
if (result !== undefined) {
return result;
}
return (result = getProjectRootRec(__dirname));
}

View File

@ -1,10 +0,0 @@
import { getProjectRoot } from "./getProjectRoot";
import { join as pathJoin } from "path";
import * as child_process from "child_process";
import * as fs from "fs";
Object.entries<string>(JSON.parse(fs.readFileSync(pathJoin(getProjectRoot(), "package.json")).toString("utf8"))["bin"]).forEach(([, scriptPath]) =>
child_process.execSync(`chmod +x ${scriptPath}`, {
"cwd": getProjectRoot(),
}),
);

View File

@ -1,7 +0,0 @@
import { relative as pathRelative } from "path";
export function isInside(params: { dirPath: string; filePath: string }) {
const { dirPath, filePath } = params;
return !pathRelative(dirPath, filePath).startsWith("..");
}

View File

@ -1,40 +0,0 @@
import { listTagsFactory } from "./listTags";
import type { Octokit } from "@octokit/rest";
import { NpmModuleVersion } from "../NpmModuleVersion";
export function getLatestsSemVersionedTagFactory(params: { octokit: Octokit }) {
const { octokit } = params;
async function getLatestsSemVersionedTag(params: { owner: string; repo: string; doIgnoreBeta: boolean; count: number }): Promise<
{
tag: string;
version: NpmModuleVersion;
}[]
> {
const { owner, repo, doIgnoreBeta, count } = params;
const semVersionedTags: { tag: string; version: NpmModuleVersion }[] = [];
const { listTags } = listTagsFactory({ octokit });
for await (const tag of listTags({ owner, repo })) {
let version: NpmModuleVersion;
try {
version = NpmModuleVersion.parse(tag.replace(/^[vV]?/, ""));
} catch {
continue;
}
if (doIgnoreBeta && version.betaPreRelease !== undefined) {
continue;
}
semVersionedTags.push({ tag, version });
}
return semVersionedTags.sort(({ version: vX }, { version: vY }) => NpmModuleVersion.compare(vY, vX)).slice(0, count);
}
return { getLatestsSemVersionedTag };
}

View File

@ -1,49 +0,0 @@
import type { Octokit } from "@octokit/rest";
const per_page = 99;
export function listTagsFactory(params: { octokit: Octokit }) {
const { octokit } = params;
const octokit_repo_listTags = async (params: { owner: string; repo: string; per_page: number; page: number }) => {
return octokit.repos.listTags(params);
};
async function* listTags(params: { owner: string; repo: string }): AsyncGenerator<string> {
const { owner, repo } = params;
let page = 1;
while (true) {
const resp = await octokit_repo_listTags({
owner,
repo,
per_page,
"page": page++,
});
for (const branch of resp.data.map(({ name }) => name)) {
yield branch;
}
if (resp.data.length < 99) {
break;
}
}
}
/** Returns the same "latest" tag as deno.land/x, not actually the latest though */
async function getLatestTag(params: { owner: string; repo: string }): Promise<string | undefined> {
const { owner, repo } = params;
const itRes = await listTags({ owner, repo }).next();
if (itRes.done) {
return undefined;
}
return itRes.value;
}
return { listTags, getLatestTag };
}

View File

@ -1,6 +0,0 @@
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("/");
}

View File

@ -1,31 +0,0 @@
import { execSync } from "child_process";
function rmInternal(params: { pathToRemove: string; args: string | undefined; cwd: string | undefined }) {
const { pathToRemove, args, cwd } = params;
execSync(`rm ${args ? `-${args} ` : ""}${pathToRemove.replace(/ /g, "\\ ")}`, cwd !== undefined ? { cwd } : undefined);
}
export function rm(pathToRemove: string, options?: { cwd: string }) {
rmInternal({
pathToRemove,
"args": undefined,
"cwd": options?.cwd,
});
}
export function rm_r(pathToRemove: string, options?: { cwd: string }) {
rmInternal({
pathToRemove,
"args": "r",
"cwd": options?.cwd,
});
}
export function rm_rf(pathToRemove: string, options?: { cwd: string }) {
rmInternal({
pathToRemove,
"args": "rf",
"cwd": options?.cwd,
});
}

View File

@ -1,46 +0,0 @@
import * as fs from "fs";
import * as path from "path";
import { crawl } from "./crawl";
import { id } from "tsafe/id";
type TransformSourceCode = (params: { sourceCode: Buffer; filePath: string }) =>
| {
modifiedSourceCode: Buffer;
newFileName?: string;
}
| undefined;
/** Apply a transformation function to every file of directory */
export function transformCodebase(params: { srcDirPath: string; destDirPath: string; transformSourceCode?: TransformSourceCode }) {
const {
srcDirPath,
destDirPath,
transformSourceCode = id<TransformSourceCode>(({ sourceCode }) => ({
"modifiedSourceCode": sourceCode,
})),
} = params;
for (const file_relative_path of crawl(srcDirPath)) {
const filePath = path.join(srcDirPath, file_relative_path);
const transformSourceCodeResult = transformSourceCode({
"sourceCode": fs.readFileSync(filePath),
"filePath": path.join(srcDirPath, file_relative_path),
});
if (transformSourceCodeResult === undefined) {
continue;
}
fs.mkdirSync(path.dirname(path.join(destDirPath, file_relative_path)), {
"recursive": true,
});
const { newFileName, modifiedSourceCode } = transformSourceCodeResult;
fs.writeFileSync(
path.join(path.dirname(path.join(destDirPath, file_relative_path)), newFileName ?? path.basename(file_relative_path)),
modifiedSourceCode,
);
}
}

View File

@ -1,11 +0,0 @@
{
"extends": "../../tsproject.json",
"compilerOptions": {
"module": "CommonJS",
"target": "ES5",
"esModuleInterop": true,
"lib": ["es2015", "DOM", "ES2019.Object"],
"outDir": "../../dist/bin",
"rootDir": "."
}
}

View File

@ -1,34 +0,0 @@
import React, { memo } from "react";
import Template from "./Template";
import type { KcProps } from "./KcProps";
import type { KcContextBase } from "../getKcContext/KcContextBase";
import type { I18n } from "../i18n";
const Error = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.Error; i18n: I18n } & KcProps) => {
const { message, client } = kcContext;
const { msg } = i18n;
return (
<Template
{...{ kcContext, i18n, ...props }}
doFetchDefaultThemeResources={true}
displayMessage={false}
headerNode={msg("errorTitle")}
formNode={
<div id="kc-error-message">
<p className="instruction">{message.summary}</p>
{client !== undefined && client.baseUrl !== undefined && (
<p>
<a id="backToApplication" href={client.baseUrl}>
{msg("backToApplication")}
</a>
</p>
)}
</div>
}
/>
);
});
export default Error;

View File

@ -1,51 +0,0 @@
import React, { memo } from "react";
import Template from "./Template";
import type { KcProps } from "./KcProps";
import { assert } from "../tools/assert";
import type { KcContextBase } from "../getKcContext/KcContextBase";
import type { I18n } from "../i18n";
const Info = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.Info; i18n: I18n } & KcProps) => {
const { msgStr, msg } = i18n;
assert(kcContext.message !== undefined);
const { messageHeader, message, requiredActions, skipLink, pageRedirectUri, actionUri, client } = kcContext;
return (
<Template
{...{ kcContext, i18n, ...props }}
doFetchDefaultThemeResources={true}
displayMessage={false}
headerNode={messageHeader !== undefined ? <>{messageHeader}</> : <>{message.summary}</>}
formNode={
<div id="kc-info-message">
<p className="instruction">
{message.summary}
{requiredActions !== undefined && (
<b>{requiredActions.map(requiredAction => msgStr(`requiredAction.${requiredAction}` as const)).join(",")}</b>
)}
</p>
{!skipLink && pageRedirectUri !== undefined ? (
<p>
<a href={pageRedirectUri}>{msg("backToApplication")}</a>
</p>
) : actionUri !== undefined ? (
<p>
<a href={actionUri}>{msg("proceedWithAction")}</a>
</p>
) : (
client.baseUrl !== undefined && (
<p>
<a href={client.baseUrl}>{msg("backToApplication")}</a>
</p>
)
)}
</div>
}
/>
);
});
export default Info;

View File

@ -1,81 +0,0 @@
import React, { lazy, memo, Suspense } from "react";
import type { KcContextBase } from "../getKcContext/KcContextBase";
import type { KcProps } from "./KcProps";
import { __unsafe_useI18n as useI18n } from "../i18n";
import type { I18n } from "../i18n";
const Login = lazy(() => import("./Login"));
const Register = lazy(() => import("./Register"));
const RegisterUserProfile = lazy(() => import("./RegisterUserProfile"));
const Info = lazy(() => import("./Info"));
const Error = lazy(() => import("./Error"));
const LoginResetPassword = lazy(() => import("./LoginResetPassword"));
const LoginVerifyEmail = lazy(() => import("./LoginVerifyEmail"));
const Terms = lazy(() => import("./Terms"));
const LoginOtp = lazy(() => import("./LoginOtp"));
const LoginUpdatePassword = lazy(() => import("./LoginUpdatePassword"));
const LoginUpdateProfile = lazy(() => import("./LoginUpdateProfile"));
const LoginIdpLinkConfirm = lazy(() => import("./LoginIdpLinkConfirm"));
const LoginPageExpired = lazy(() => import("./LoginPageExpired"));
const LoginIdpLinkEmail = lazy(() => import("./LoginIdpLinkEmail"));
const LoginConfigTotp = lazy(() => import("./LoginConfigTotp"));
const LogoutConfirm = lazy(() => import("./LogoutConfirm"));
const KcApp = memo(({ kcContext, i18n: userProvidedI18n, ...props }: { kcContext: KcContextBase; i18n?: I18n } & KcProps) => {
const i18n = (function useClosure() {
const i18n = useI18n({
kcContext,
"extraMessages": {},
"doSkip": userProvidedI18n !== undefined,
});
return userProvidedI18n ?? i18n;
})();
if (i18n === null) {
return null;
}
return (
<Suspense>
{(() => {
switch (kcContext.pageId) {
case "login.ftl":
return <Login {...{ kcContext, i18n, ...props }} />;
case "register.ftl":
return <Register {...{ kcContext, i18n, ...props }} />;
case "register-user-profile.ftl":
return <RegisterUserProfile {...{ kcContext, i18n, ...props }} />;
case "info.ftl":
return <Info {...{ kcContext, i18n, ...props }} />;
case "error.ftl":
return <Error {...{ kcContext, i18n, ...props }} />;
case "login-reset-password.ftl":
return <LoginResetPassword {...{ kcContext, i18n, ...props }} />;
case "login-verify-email.ftl":
return <LoginVerifyEmail {...{ kcContext, i18n, ...props }} />;
case "terms.ftl":
return <Terms {...{ kcContext, i18n, ...props }} />;
case "login-otp.ftl":
return <LoginOtp {...{ kcContext, i18n, ...props }} />;
case "login-update-password.ftl":
return <LoginUpdatePassword {...{ kcContext, i18n, ...props }} />;
case "login-update-profile.ftl":
return <LoginUpdateProfile {...{ kcContext, i18n, ...props }} />;
case "login-idp-link-confirm.ftl":
return <LoginIdpLinkConfirm {...{ kcContext, i18n, ...props }} />;
case "login-idp-link-email.ftl":
return <LoginIdpLinkEmail {...{ kcContext, i18n, ...props }} />;
case "login-page-expired.ftl":
return <LoginPageExpired {...{ kcContext, i18n, ...props }} />;
case "login-config-totp.ftl":
return <LoginConfigTotp {...{ kcContext, i18n, ...props }} />;
case "logout-confirm.ftl":
return <LogoutConfirm {...{ kcContext, i18n, ...props }} />;
}
})()}
</Suspense>
);
});
export default KcApp;

View File

@ -1,203 +0,0 @@
import { allPropertiesValuesToUndefined } from "../tools/allPropertiesValuesToUndefined";
import { assert } from "tsafe/assert";
/** Class names can be provided as an array or separated by whitespace */
export type KcPropsGeneric<CssClasses extends string> = {
[key in CssClasses]: readonly string[] | string | undefined;
};
export type KcTemplateClassKey =
| "stylesCommon"
| "styles"
| "scripts"
| "kcHtmlClass"
| "kcLoginClass"
| "kcHeaderClass"
| "kcHeaderWrapperClass"
| "kcFormCardClass"
| "kcFormCardAccountClass"
| "kcFormHeaderClass"
| "kcLocaleWrapperClass"
| "kcContentWrapperClass"
| "kcLabelWrapperClass"
| "kcFormGroupClass"
| "kcResetFlowIcon"
| "kcFeedbackSuccessIcon"
| "kcFeedbackWarningIcon"
| "kcFeedbackErrorIcon"
| "kcFeedbackInfoIcon"
| "kcFormSocialAccountContentClass"
| "kcFormSocialAccountClass"
| "kcSignUpClass"
| "kcInfoAreaWrapperClass";
export type KcTemplateProps = KcPropsGeneric<KcTemplateClassKey>;
export const defaultKcTemplateProps = {
"stylesCommon": [
"node_modules/patternfly/dist/css/patternfly.min.css",
"node_modules/patternfly/dist/css/patternfly-additions.min.css",
"lib/zocial/zocial.css",
],
"styles": ["css/login.css"],
"scripts": [],
"kcHtmlClass": ["login-pf"],
"kcLoginClass": ["login-pf-page"],
"kcContentWrapperClass": ["row"],
"kcHeaderClass": ["login-pf-page-header"],
"kcHeaderWrapperClass": [],
"kcFormCardClass": ["card-pf"],
"kcFormCardAccountClass": ["login-pf-accounts"],
"kcFormSocialAccountClass": ["login-pf-social-section"],
"kcFormSocialAccountContentClass": ["col-xs-12", "col-sm-6"],
"kcFormHeaderClass": ["login-pf-header"],
"kcLocaleWrapperClass": [],
"kcFeedbackErrorIcon": ["pficon", "pficon-error-circle-o"],
"kcFeedbackWarningIcon": ["pficon", "pficon-warning-triangle-o"],
"kcFeedbackSuccessIcon": ["pficon", "pficon-ok"],
"kcFeedbackInfoIcon": ["pficon", "pficon-info"],
"kcResetFlowIcon": ["pficon", "pficon-arrow fa-2x"],
"kcFormGroupClass": ["form-group"],
"kcLabelWrapperClass": ["col-xs-12", "col-sm-12", "col-md-12", "col-lg-12"],
"kcSignUpClass": ["login-pf-signup"],
"kcInfoAreaWrapperClass": [],
} as const;
assert<typeof defaultKcTemplateProps extends KcTemplateProps ? true : false>();
/** Tu use if you don't want any default */
export const allClearKcTemplateProps = allPropertiesValuesToUndefined(defaultKcTemplateProps);
assert<typeof allClearKcTemplateProps extends KcTemplateProps ? true : false>();
export type KcProps = KcPropsGeneric<
| KcTemplateClassKey
| "kcLogoLink"
| "kcLogoClass"
| "kcContainerClass"
| "kcContentClass"
| "kcFeedbackAreaClass"
| "kcLocaleClass"
| "kcAlertIconClasserror"
| "kcFormAreaClass"
| "kcFormSocialAccountListClass"
| "kcFormSocialAccountDoubleListClass"
| "kcFormSocialAccountListLinkClass"
| "kcWebAuthnKeyIcon"
| "kcFormClass"
| "kcFormGroupErrorClass"
| "kcLabelClass"
| "kcInputClass"
| "kcInputErrorMessageClass"
| "kcInputWrapperClass"
| "kcFormOptionsClass"
| "kcFormButtonsClass"
| "kcFormSettingClass"
| "kcTextareaClass"
| "kcInfoAreaClass"
| "kcFormGroupHeader"
| "kcButtonClass"
| "kcButtonPrimaryClass"
| "kcButtonDefaultClass"
| "kcButtonLargeClass"
| "kcButtonBlockClass"
| "kcInputLargeClass"
| "kcSrOnlyClass"
| "kcSelectAuthListClass"
| "kcSelectAuthListItemClass"
| "kcSelectAuthListItemInfoClass"
| "kcSelectAuthListItemLeftClass"
| "kcSelectAuthListItemBodyClass"
| "kcSelectAuthListItemDescriptionClass"
| "kcSelectAuthListItemHeadingClass"
| "kcSelectAuthListItemHelpTextClass"
| "kcAuthenticatorDefaultClass"
| "kcAuthenticatorPasswordClass"
| "kcAuthenticatorOTPClass"
| "kcAuthenticatorWebAuthnClass"
| "kcAuthenticatorWebAuthnPasswordlessClass"
| "kcSelectOTPListClass"
| "kcSelectOTPListItemClass"
| "kcAuthenticatorOtpCircleClass"
| "kcSelectOTPItemHeadingClass"
| "kcFormOptionsWrapperClass"
>;
export const defaultKcProps = {
...defaultKcTemplateProps,
"kcLogoLink": "http://www.keycloak.org",
"kcLogoClass": "login-pf-brand",
"kcContainerClass": "container-fluid",
"kcContentClass": ["col-sm-8", "col-sm-offset-2", "col-md-6", "col-md-offset-3", "col-lg-6", "col-lg-offset-3"],
"kcFeedbackAreaClass": ["col-md-12"],
"kcLocaleClass": ["col-xs-12", "col-sm-1"],
"kcAlertIconClasserror": ["pficon", "pficon-error-circle-o"],
"kcFormAreaClass": ["col-sm-10", "col-sm-offset-1", "col-md-8", "col-md-offset-2", "col-lg-8", "col-lg-offset-2"],
"kcFormSocialAccountListClass": ["login-pf-social", "list-unstyled", "login-pf-social-all"],
"kcFormSocialAccountDoubleListClass": ["login-pf-social-double-col"],
"kcFormSocialAccountListLinkClass": ["login-pf-social-link"],
"kcWebAuthnKeyIcon": ["pficon", "pficon-key"],
"kcFormClass": ["form-horizontal"],
"kcFormGroupErrorClass": ["has-error"],
"kcLabelClass": ["control-label"],
"kcInputClass": ["form-control"],
"kcInputErrorMessageClass": ["pf-c-form__helper-text", "pf-m-error", "required", "kc-feedback-text"],
"kcInputWrapperClass": ["col-xs-12", "col-sm-12", "col-md-12", "col-lg-12"],
"kcFormOptionsClass": ["col-xs-12", "col-sm-12", "col-md-12", "col-lg-12"],
"kcFormButtonsClass": ["col-xs-12", "col-sm-12", "col-md-12", "col-lg-12"],
"kcFormSettingClass": ["login-pf-settings"],
"kcTextareaClass": ["form-control"],
"kcInfoAreaClass": ["col-xs-12", "col-sm-4", "col-md-4", "col-lg-5", "details"],
// user-profile grouping
"kcFormGroupHeader": ["pf-c-form__group"],
// css classes for form buttons main class used for all buttons
"kcButtonClass": ["btn"],
// classes defining priority of the button - primary or default (there is typically only one priority button for the form)
"kcButtonPrimaryClass": ["btn-primary"],
"kcButtonDefaultClass": ["btn-default"],
// classes defining size of the button
"kcButtonLargeClass": ["btn-lg"],
"kcButtonBlockClass": ["btn-block"],
// css classes for input
"kcInputLargeClass": ["input-lg"],
// css classes for form accessability
"kcSrOnlyClass": ["sr-only"],
// css classes for select-authenticator form
"kcSelectAuthListClass": ["list-group", "list-view-pf"],
"kcSelectAuthListItemClass": ["list-group-item", "list-view-pf-stacked"],
"kcSelectAuthListItemInfoClass": ["list-view-pf-main-info"],
"kcSelectAuthListItemLeftClass": ["list-view-pf-left"],
"kcSelectAuthListItemBodyClass": ["list-view-pf-body"],
"kcSelectAuthListItemDescriptionClass": ["list-view-pf-description"],
"kcSelectAuthListItemHeadingClass": ["list-group-item-heading"],
"kcSelectAuthListItemHelpTextClass": ["list-group-item-text"],
// css classes for the authenticators
"kcAuthenticatorDefaultClass": ["fa", "list-view-pf-icon-lg"],
"kcAuthenticatorPasswordClass": ["fa", "fa-unlock list-view-pf-icon-lg"],
"kcAuthenticatorOTPClass": ["fa", "fa-mobile", "list-view-pf-icon-lg"],
"kcAuthenticatorWebAuthnClass": ["fa", "fa-key", "list-view-pf-icon-lg"],
"kcAuthenticatorWebAuthnPasswordlessClass": ["fa", "fa-key", "list-view-pf-icon-lg"],
//css classes for the OTP Login Form
"kcSelectOTPListClass": ["card-pf", "card-pf-view", "card-pf-view-select", "card-pf-view-single-select"],
"kcSelectOTPListItemClass": ["card-pf-body", "card-pf-top-element"],
"kcAuthenticatorOtpCircleClass": ["fa", "fa-mobile", "card-pf-icon-circle"],
"kcSelectOTPItemHeadingClass": ["card-pf-title", "text-center"],
"kcFormOptionsWrapperClass": [],
} as const;
assert<typeof defaultKcProps extends KcProps ? true : false>();
/** Tu use if you don't want any default */
export const allClearKcProps = allPropertiesValuesToUndefined(defaultKcProps);
assert<typeof allClearKcProps extends KcProps ? true : false>();

View File

@ -1,195 +0,0 @@
import React, { useState, memo } from "react";
import Template from "./Template";
import type { KcProps } from "./KcProps";
import type { KcContextBase } from "../getKcContext/KcContextBase";
import { useCssAndCx } from "tss-react";
import { useConstCallback } from "powerhooks/useConstCallback";
import type { FormEventHandler } from "react";
import type { I18n } from "../i18n";
const Login = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.Login; i18n: I18n } & KcProps) => {
const { social, realm, url, usernameEditDisabled, login, auth, registrationDisabled } = kcContext;
const { msg, msgStr } = i18n;
const { cx } = useCssAndCx();
const [isLoginButtonDisabled, setIsLoginButtonDisabled] = useState(false);
const onSubmit = useConstCallback<FormEventHandler<HTMLFormElement>>(e => {
e.preventDefault();
setIsLoginButtonDisabled(true);
const formElement = e.target as HTMLFormElement;
//NOTE: Even if we login with email Keycloak expect username and password in
//the POST request.
formElement.querySelector("input[name='email']")?.setAttribute("name", "username");
formElement.submit();
});
return (
<Template
{...{ kcContext, i18n, ...props }}
doFetchDefaultThemeResources={true}
displayInfo={social.displayInfo}
displayWide={realm.password && social.providers !== undefined}
headerNode={msg("doLogIn")}
formNode={
<div id="kc-form" className={cx(realm.password && social.providers !== undefined && props.kcContentWrapperClass)}>
<div
id="kc-form-wrapper"
className={cx(realm.password && social.providers && [props.kcFormSocialAccountContentClass, props.kcFormSocialAccountClass])}
>
{realm.password && (
<form id="kc-form-login" onSubmit={onSubmit} action={url.loginAction} method="post">
<div className={cx(props.kcFormGroupClass)}>
{(() => {
const label = !realm.loginWithEmailAllowed
? "username"
: realm.registrationEmailAsUsername
? "email"
: "usernameOrEmail";
const autoCompleteHelper: typeof label = label === "usernameOrEmail" ? "username" : label;
return (
<>
<label htmlFor={autoCompleteHelper} className={cx(props.kcLabelClass)}>
{msg(label)}
</label>
<input
tabIndex={1}
id={autoCompleteHelper}
className={cx(props.kcInputClass)}
//NOTE: This is used by Google Chrome auto fill so we use it to tell
//the browser how to pre fill the form but before submit we put it back
//to username because it is what keycloak expects.
name={autoCompleteHelper}
defaultValue={login.username ?? ""}
type="text"
{...(usernameEditDisabled
? { "disabled": true }
: {
"autoFocus": true,
"autoComplete": "off",
})}
/>
</>
);
})()}
</div>
<div className={cx(props.kcFormGroupClass)}>
<label htmlFor="password" className={cx(props.kcLabelClass)}>
{msg("password")}
</label>
<input
tabIndex={2}
id="password"
className={cx(props.kcInputClass)}
name="password"
type="password"
autoComplete="off"
/>
</div>
<div className={cx(props.kcFormGroupClass, props.kcFormSettingClass)}>
<div id="kc-form-options">
{realm.rememberMe && !usernameEditDisabled && (
<div className="checkbox">
<label>
<input
tabIndex={3}
id="rememberMe"
name="rememberMe"
type="checkbox"
{...(login.rememberMe
? {
"checked": true,
}
: {})}
/>
{msg("rememberMe")}
</label>
</div>
)}
</div>
<div className={cx(props.kcFormOptionsWrapperClass)}>
{realm.resetPasswordAllowed && (
<span>
<a tabIndex={5} href={url.loginResetCredentialsUrl}>
{msg("doForgotPassword")}
</a>
</span>
)}
</div>
</div>
<div id="kc-form-buttons" className={cx(props.kcFormGroupClass)}>
<input
type="hidden"
id="id-hidden-input"
name="credentialId"
{...(auth?.selectedCredential !== undefined
? {
"value": auth.selectedCredential,
}
: {})}
/>
<input
tabIndex={4}
className={cx(
props.kcButtonClass,
props.kcButtonPrimaryClass,
props.kcButtonBlockClass,
props.kcButtonLargeClass,
)}
name="login"
id="kc-login"
type="submit"
value={msgStr("doLogIn")}
disabled={isLoginButtonDisabled}
/>
</div>
</form>
)}
</div>
{realm.password && social.providers !== undefined && (
<div id="kc-social-providers" className={cx(props.kcFormSocialAccountContentClass, props.kcFormSocialAccountClass)}>
<ul
className={cx(
props.kcFormSocialAccountListClass,
social.providers.length > 4 && props.kcFormSocialAccountDoubleListClass,
)}
>
{social.providers.map(p => (
<li key={p.providerId} className={cx(props.kcFormSocialAccountListLinkClass)}>
<a href={p.loginUrl} id={`zocial-${p.alias}`} className={cx("zocial", p.providerId)}>
<span>{p.displayName}</span>
</a>
</li>
))}
</ul>
</div>
)}
</div>
}
infoNode={
realm.password &&
realm.registrationAllowed &&
!registrationDisabled && (
<div id="kc-registration">
<span>
{msg("noAccount")}
<a tabIndex={6} href={url.registrationUrl}>
{msg("doRegister")}
</a>
</span>
</div>
)
}
/>
);
});
export default Login;

View File

@ -1,186 +0,0 @@
import React, { memo } from "react";
import Template from "./Template";
import type { KcProps } from "./KcProps";
import type { KcContextBase } from "../getKcContext/KcContextBase";
import { useCssAndCx } from "tss-react";
import type { I18n } from "../i18n";
const LoginConfigTotp = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.LoginConfigTotp; i18n: I18n } & KcProps) => {
const { url, isAppInitiatedAction, totp, mode, messagesPerField } = kcContext;
const { cx } = useCssAndCx();
const { msg, msgStr } = i18n;
const algToKeyUriAlg: Record<KcContextBase.LoginConfigTotp["totp"]["policy"]["algorithm"], string> = {
HmacSHA1: "SHA1",
HmacSHA256: "SHA256",
HmacSHA512: "SHA512",
};
return (
<Template
{...{ kcContext, i18n, ...props }}
doFetchDefaultThemeResources={true}
headerNode={msg("loginTotpTitle")}
formNode={
<>
<ol id="kc-totp-settings">
<li>
<p>{msg("loginTotpStep1")}</p>
<ul id="kc-totp-supported-apps">
{totp.policy.supportedApplications.map(app => (
<li>{app}</li>
))}
</ul>
</li>
{mode && mode == "manual" ? (
<>
<li>
<p>{msg("loginTotpManualStep2")}</p>
<p>
<span id="kc-totp-secret-key">{totp.totpSecretEncoded}</span>
</p>
<p>
<a href={totp.qrUrl} id="mode-barcode">
{msg("loginTotpScanBarcode")}
</a>
</p>
</li>
<li>
<p>{msg("loginTotpManualStep3")}</p>
<p>
<ul>
<li id="kc-totp-type">
{msg("loginTotpType")}: {msg(`loginTotp.${totp.policy.type}`)}
</li>
<li id="kc-totp-algorithm">
{msg("loginTotpAlgorithm")}: {algToKeyUriAlg?.[totp.policy.algorithm] ?? totp.policy.algorithm}
</li>
<li id="kc-totp-digits">
{msg("loginTotpDigits")}: {totp.policy.digits}
</li>
{totp.policy.type === "totp" ? (
<li id="kc-totp-period">
{msg("loginTotpInterval")}: {totp.policy.period}
</li>
) : (
<li id="kc-totp-counter">
{msg("loginTotpCounter")}: {totp.policy.initialCounter}
</li>
)}
</ul>
</p>
</li>
</>
) : (
<li>
<p>{msg("loginTotpStep2")}</p>
<img id="kc-totp-secret-qr-code" src={`data:image/png;base64, ${totp.totpSecretQrCode}`} alt="Figure: Barcode" />
<br />
<p>
<a href={totp.manualUrl} id="mode-manual">
{msg("loginTotpUnableToScan")}
</a>
</p>
</li>
)}
<li>
<p>{msg("loginTotpStep3")}</p>
<p>{msg("loginTotpStep3DeviceName")}</p>
</li>
</ol>
<form action={url.loginAction} className={cx(props.kcFormClass)} id="kc-totp-settings-form" method="post">
<div className={cx(props.kcFormGroupClass)}>
<div className={cx(props.kcInputWrapperClass)}>
<label htmlFor="totp" className={cx(props.kcLabelClass)}>
{msg("authenticatorCode")}
</label>{" "}
<span className="required">*</span>
</div>
<div className={cx(props.kcInputWrapperClass)}>
<input
type="text"
id="totp"
name="totp"
autoComplete="off"
className={cx(props.kcInputClass)}
aria-invalid={messagesPerField.existsError("totp")}
/>
{messagesPerField.existsError("totp") && (
<span id="input-error-otp-code" className={cx(props.kcInputErrorMessageClass)} aria-live="polite">
{messagesPerField.get("totp")}
</span>
)}
</div>
<input type="hidden" id="totpSecret" name="totpSecret" value={totp.totpSecret} />
{mode && <input type="hidden" id="mode" value={mode} />}
</div>
<div className={cx(props.kcFormGroupClass)}>
<div className={cx(props.kcInputWrapperClass)}>
<label htmlFor="userLabel" className={cx(props.kcLabelClass)}>
{msg("loginTotpDeviceName")}
</label>{" "}
{totp.otpCredentials.length >= 1 && <span className="required">*</span>}
</div>
<div className={cx(props.kcInputWrapperClass)}>
<input
type="text"
id="userLabel"
name="userLabel"
autoComplete="off"
className={cx(props.kcInputClass)}
aria-invalid={messagesPerField.existsError("userLabel")}
/>
{messagesPerField.existsError("userLabel") && (
<span id="input-error-otp-label" className={cx(props.kcInputErrorMessageClass)} aria-live="polite">
{messagesPerField.get("userLabel")}
</span>
)}
</div>
</div>
{isAppInitiatedAction ? (
<>
<input
type="submit"
className={cx(props.kcButtonClass, props.kcButtonPrimaryClass, props.kcButtonLargeClass)}
id="saveTOTPBtn"
value={msgStr("doSubmit")}
/>
<button
type="submit"
className={cx(
props.kcButtonClass,
props.kcButtonDefaultClass,
props.kcButtonLargeClass,
props.kcButtonLargeClass,
)}
id="cancelTOTPBtn"
name="cancel-aia"
value="true"
>
${msg("doCancel")}
</button>
</>
) : (
<input
type="submit"
className={cx(props.kcButtonClass, props.kcButtonPrimaryClass, props.kcButtonLargeClass)}
id="saveTOTPBtn"
value={msgStr("doSubmit")}
/>
)}
</form>
</>
}
/>
);
});
export default LoginConfigTotp;

View File

@ -1,48 +0,0 @@
import React, { memo } from "react";
import Template from "./Template";
import type { KcProps } from "./KcProps";
import type { KcContextBase } from "../getKcContext/KcContextBase";
import { useCssAndCx } from "tss-react";
import type { I18n } from "../i18n";
const LoginIdpLinkConfirm = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.LoginIdpLinkConfirm; i18n: I18n } & KcProps) => {
const { url, idpAlias } = kcContext;
const { msg } = i18n;
const { cx } = useCssAndCx();
return (
<Template
{...{ kcContext, i18n, ...props }}
doFetchDefaultThemeResources={true}
headerNode={msg("confirmLinkIdpTitle")}
formNode={
<form id="kc-register-form" action={url.loginAction} method="post">
<div className={cx(props.kcFormGroupClass)}>
<button
type="submit"
className={cx(props.kcButtonClass, props.kcButtonDefaultClass, props.kcButtonBlockClass, props.kcButtonLargeClass)}
name="submitAction"
id="updateProfile"
value="updateProfile"
>
{msg("confirmLinkIdpReviewProfile")}
</button>
<button
type="submit"
className={cx(props.kcButtonClass, props.kcButtonDefaultClass, props.kcButtonBlockClass, props.kcButtonLargeClass)}
name="submitAction"
id="linkAccount"
value="linkAccount"
>
{msg("confirmLinkIdpContinue", idpAlias)}
</button>
</div>
</form>
}
/>
);
});
export default LoginIdpLinkConfirm;

View File

@ -1,34 +0,0 @@
import React, { memo } from "react";
import Template from "./Template";
import type { KcProps } from "./KcProps";
import type { KcContextBase } from "../getKcContext/KcContextBase";
import type { I18n } from "../i18n";
const LoginIdpLinkEmail = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.LoginIdpLinkEmail; i18n: I18n } & KcProps) => {
const { url, realm, brokerContext, idpAlias } = kcContext;
const { msg } = i18n;
return (
<Template
{...{ kcContext, i18n, ...props }}
doFetchDefaultThemeResources={true}
headerNode={msg("emailLinkIdpTitle", idpAlias)}
formNode={
<>
<p id="instruction1" className="instruction">
{msg("emailLinkIdp1", idpAlias, brokerContext.username, realm.displayName)}
</p>
<p id="instruction2" className="instruction">
{msg("emailLinkIdp2")} <a href={url.loginAction}>{msg("doClickHere")}</a> {msg("emailLinkIdp3")}
</p>
<p id="instruction3" className="instruction">
{msg("emailLinkIdp4")} <a href={url.loginAction}>{msg("doClickHere")}</a> {msg("emailLinkIdp5")}
</p>
</>
}
/>
);
});
export default LoginIdpLinkEmail;

View File

@ -1,113 +0,0 @@
import React, { useEffect, memo } from "react";
import Template from "./Template";
import type { KcProps } from "./KcProps";
import type { KcContextBase } from "../getKcContext/KcContextBase";
import { headInsert } from "../tools/headInsert";
import { pathJoin } from "../../bin/tools/pathJoin";
import { useCssAndCx } from "tss-react";
import type { I18n } from "../i18n";
const LoginOtp = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.LoginOtp; i18n: I18n } & KcProps) => {
const { otpLogin, url } = kcContext;
const { cx } = useCssAndCx();
const { msg, msgStr } = i18n;
useEffect(() => {
let isCleanedUp = false;
headInsert({
"type": "javascript",
"src": pathJoin(kcContext.url.resourcesCommonPath, "node_modules/jquery/dist/jquery.min.js"),
}).then(() => {
if (isCleanedUp) return;
evaluateInlineScript();
});
return () => {
isCleanedUp = true;
};
}, []);
return (
<Template
{...{ kcContext, i18n, ...props }}
doFetchDefaultThemeResources={true}
headerNode={msg("doLogIn")}
formNode={
<form id="kc-otp-login-form" className={cx(props.kcFormClass)} action={url.loginAction} method="post">
{otpLogin.userOtpCredentials.length > 1 && (
<div className={cx(props.kcFormGroupClass)}>
<div className={cx(props.kcInputWrapperClass)}>
{otpLogin.userOtpCredentials.map(otpCredential => (
<div key={otpCredential.id} className={cx(props.kcSelectOTPListClass)}>
<input type="hidden" value="${otpCredential.id}" />
<div className={cx(props.kcSelectOTPListItemClass)}>
<span className={cx(props.kcAuthenticatorOtpCircleClass)} />
<h2 className={cx(props.kcSelectOTPItemHeadingClass)}>{otpCredential.userLabel}</h2>
</div>
</div>
))}
</div>
</div>
)}
<div className={cx(props.kcFormGroupClass)}>
<div className={cx(props.kcLabelWrapperClass)}>
<label htmlFor="otp" className={cx(props.kcLabelClass)}>
{msg("loginOtpOneTime")}
</label>
</div>
<div className={cx(props.kcInputWrapperClass)}>
<input id="otp" name="otp" autoComplete="off" type="text" className={cx(props.kcInputClass)} autoFocus />
</div>
</div>
<div className={cx(props.kcFormGroupClass)}>
<div id="kc-form-options" className={cx(props.kcFormOptionsClass)}>
<div className={cx(props.kcFormOptionsWrapperClass)} />
</div>
<div id="kc-form-buttons" className={cx(props.kcFormButtonsClass)}>
<input
className={cx(props.kcButtonClass, props.kcButtonPrimaryClass, props.kcButtonBlockClass, props.kcButtonLargeClass)}
name="login"
id="kc-login"
type="submit"
value={msgStr("doLogIn")}
/>
</div>
</div>
</form>
}
/>
);
});
declare const $: any;
function evaluateInlineScript() {
$(document).ready(function () {
// Card Single Select
$(".card-pf-view-single-select").click(function (this: any) {
if ($(this).hasClass("active")) {
$(this).removeClass("active");
$(this).children().removeAttr("name");
} else {
$(".card-pf-view-single-select").removeClass("active");
$(".card-pf-view-single-select").children().removeAttr("name");
$(this).addClass("active");
$(this).children().attr("name", "selectedCredentialId");
}
});
var defaultCred = $(".card-pf-view-single-select")[0];
if (defaultCred) {
defaultCred.click();
}
});
}
export default LoginOtp;

View File

@ -1,38 +0,0 @@
import React, { memo } from "react";
import Template from "./Template";
import type { KcProps } from "./KcProps";
import type { KcContextBase } from "../getKcContext/KcContextBase";
import type { I18n } from "../i18n";
const LoginPageExpired = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.LoginPageExpired; i18n: I18n } & KcProps) => {
const { url } = kcContext;
const { msg } = i18n;
return (
<Template
{...{ kcContext, i18n, ...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>
</>
}
/>
);
});
export default LoginPageExpired;

View File

@ -1,68 +0,0 @@
import React, { memo } from "react";
import Template from "./Template";
import type { KcProps } from "./KcProps";
import type { KcContextBase } from "../getKcContext/KcContextBase";
import { useCssAndCx } from "tss-react";
import type { I18n } from "../i18n";
const LoginResetPassword = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.LoginResetPassword; i18n: I18n } & KcProps) => {
const { url, realm, auth } = kcContext;
const { msg, msgStr } = i18n;
const { cx } = useCssAndCx();
return (
<Template
{...{ kcContext, i18n, ...props }}
doFetchDefaultThemeResources={true}
displayMessage={false}
headerNode={msg("emailForgotTitle")}
formNode={
<form id="kc-reset-password-form" className={cx(props.kcFormClass)} action={url.loginAction} method="post">
<div className={cx(props.kcFormGroupClass)}>
<div className={cx(props.kcLabelWrapperClass)}>
<label htmlFor="username" className={cx(props.kcLabelClass)}>
{!realm.loginWithEmailAllowed
? msg("username")
: !realm.registrationEmailAsUsername
? msg("usernameOrEmail")
: msg("email")}
</label>
</div>
<div className={cx(props.kcInputWrapperClass)}>
<input
type="text"
id="username"
name="username"
className={cx(props.kcInputClass)}
autoFocus
defaultValue={auth !== undefined && auth.showUsername ? auth.attemptedUsername : undefined}
/>
</div>
</div>
<div className={cx(props.kcFormGroupClass, props.kcFormSettingClass)}>
<div id="kc-form-options" className={cx(props.kcFormOptionsClass)}>
<div className={cx(props.kcFormOptionsWrapperClass)}>
<span>
<a href={url.loginUrl}>{msg("backToLogin")}</a>
</span>
</div>
</div>
<div id="kc-form-buttons" className={cx(props.kcFormButtonsClass)}>
<input
className={cx(props.kcButtonClass, props.kcButtonPrimaryClass, props.kcButtonBlockClass, props.kcButtonLargeClass)}
type="submit"
value={msgStr("doSubmit")}
/>
</div>
</div>
</form>
}
infoNode={msg("emailInstruction")}
/>
);
});
export default LoginResetPassword;

View File

@ -1,119 +0,0 @@
import React, { memo } from "react";
import Template from "./Template";
import type { KcProps } from "./KcProps";
import type { KcContextBase } from "../getKcContext/KcContextBase";
import { useCssAndCx } from "tss-react";
import type { I18n } from "../i18n";
const LoginUpdatePassword = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.LoginUpdatePassword; i18n: I18n } & KcProps) => {
const { cx } = useCssAndCx();
const { msg, msgStr } = i18n;
const { url, messagesPerField, isAppInitiatedAction, username } = kcContext;
return (
<Template
{...{ kcContext, i18n, ...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>
}
/>
);
});
export default LoginUpdatePassword;

View File

@ -1,122 +0,0 @@
import React, { memo } from "react";
import Template from "./Template";
import type { KcProps } from "./KcProps";
import type { KcContextBase } from "../getKcContext/KcContextBase";
import { useCssAndCx } from "tss-react";
import type { I18n } from "../i18n";
const LoginUpdateProfile = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.LoginUpdateProfile; i18n: I18n } & KcProps) => {
const { cx } = useCssAndCx();
const { msg, msgStr } = i18n;
const { url, user, messagesPerField, isAppInitiatedAction } = kcContext;
return (
<Template
{...{ kcContext, i18n, ...props }}
doFetchDefaultThemeResources={true}
headerNode={msg("loginProfileTitle")}
formNode={
<form id="kc-update-profile-form" className={cx(props.kcFormClass)} action={url.loginAction} method="post">
{user.editUsernameAllowed && (
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("username", props.kcFormGroupErrorClass))}>
<div className={cx(props.kcLabelWrapperClass)}>
<label htmlFor="username" className={cx(props.kcLabelClass)}>
{msg("username")}
</label>
</div>
<div className={cx(props.kcInputWrapperClass)}>
<input
type="text"
id="username"
name="username"
defaultValue={user.username ?? ""}
className={cx(props.kcInputClass)}
/>
</div>
</div>
)}
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("email", props.kcFormGroupErrorClass))}>
<div className={cx(props.kcLabelWrapperClass)}>
<label htmlFor="email" className={cx(props.kcLabelClass)}>
{msg("email")}
</label>
</div>
<div className={cx(props.kcInputWrapperClass)}>
<input type="text" id="email" name="email" defaultValue={user.email ?? ""} className={cx(props.kcInputClass)} />
</div>
</div>
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("firstName", props.kcFormGroupErrorClass))}>
<div className={cx(props.kcLabelWrapperClass)}>
<label htmlFor="firstName" className={cx(props.kcLabelClass)}>
{msg("firstName")}
</label>
</div>
<div className={cx(props.kcInputWrapperClass)}>
<input
type="text"
id="firstName"
name="firstName"
defaultValue={user.firstName ?? ""}
className={cx(props.kcInputClass)}
/>
</div>
</div>
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("lastName", props.kcFormGroupErrorClass))}>
<div className={cx(props.kcLabelWrapperClass)}>
<label htmlFor="lastName" className={cx(props.kcLabelClass)}>
{msg("lastName")}
</label>
</div>
<div className={cx(props.kcInputWrapperClass)}>
<input type="text" id="lastName" name="lastName" defaultValue={user.lastName ?? ""} 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)} />
</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>
}
/>
);
});
export default LoginUpdateProfile;

View File

@ -1,34 +0,0 @@
import React, { memo } from "react";
import Template from "./Template";
import type { KcProps } from "./KcProps";
import type { KcContextBase } from "../getKcContext/KcContextBase";
import type { I18n } from "../i18n";
const LoginVerifyEmail = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.LoginVerifyEmail; i18n: I18n } & KcProps) => {
const { msg } = i18n;
const { url, user } = kcContext;
return (
<Template
{...{ kcContext, i18n, ...props }}
doFetchDefaultThemeResources={true}
displayMessage={false}
headerNode={msg("emailVerifyTitle")}
formNode={
<>
<p className="instruction">{msg("emailVerifyInstruction1", user?.email)}</p>
<p className="instruction">
{msg("emailVerifyInstruction2")}
<br />
<a href={url.loginAction}>{msg("doClickHere")}</a>
&nbsp;
{msg("emailVerifyInstruction3")}
</p>
</>
}
/>
);
});
export default LoginVerifyEmail;

View File

@ -1,62 +0,0 @@
import React, { memo } from "react";
import { useCssAndCx } from "tss-react";
import Template from "./Template";
import type { KcProps } from "./KcProps";
import type { KcContextBase } from "../getKcContext/KcContextBase";
import type { I18n } from "../i18n";
const LogoutConfirm = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.LogoutConfirm; i18n: I18n } & KcProps) => {
const { url, client, logoutConfirm } = kcContext;
const { cx } = useCssAndCx();
const { msg, msgStr } = i18n;
return (
<Template
{...{ kcContext, i18n, ...props }}
doFetchDefaultThemeResources={true}
displayMessage={false}
headerNode={msg("logoutConfirmTitle")}
formNode={
<>
<div id="kc-logout-confirm" className="content-area">
<p className="instruction">{msg("logoutConfirmHeader")}</p>
<form className="form-actions" action={url.logoutConfirmAction} method="POST">
<input type="hidden" name="session_code" value={logoutConfirm.code} />
<div className={cx(props.kcFormGroupClass)}>
<div id="kc-form-options">
<div className={cx(props.kcFormOptionsWrapperClass)}></div>
</div>
<div id="kc-form-buttons" className={cx(props.kcFormGroupClass)}>
<input
tabIndex={4}
className={cx(
props.kcButtonClass,
props.kcButtonPrimaryClass,
props.kcButtonBlockClass,
props.kcButtonLargeClass,
)}
name="confirmLogout"
id="kc-logout"
type="submit"
value={msgStr("doLogout")}
/>
</div>
</div>
</form>
<div id="kc-info-message">
{!logoutConfirm.skipLink && client.baseUrl && (
<p>
<a href={client.baseUrl} dangerouslySetInnerHTML={{ __html: msgStr("backToApplication") }} />
</p>
)}
</div>
</div>
</>
}
/>
);
});
export default LogoutConfirm;

View File

@ -1,158 +0,0 @@
import React, { memo } from "react";
import Template from "./Template";
import type { KcProps } from "./KcProps";
import type { KcContextBase } from "../getKcContext/KcContextBase";
import { useCssAndCx } from "tss-react";
import type { I18n } from "../i18n";
const Register = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.Register; i18n: I18n } & KcProps) => {
const { url, messagesPerField, register, realm, passwordRequired, recaptchaRequired, recaptchaSiteKey } = kcContext;
const { msg, msgStr } = i18n;
const { cx } = useCssAndCx();
return (
<Template
{...{ kcContext, i18n, ...props }}
doFetchDefaultThemeResources={true}
headerNode={msg("registerTitle")}
formNode={
<form id="kc-register-form" className={cx(props.kcFormClass)} action={url.registrationAction} method="post">
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("firstName", props.kcFormGroupErrorClass))}>
<div className={cx(props.kcLabelWrapperClass)}>
<label htmlFor="firstName" className={cx(props.kcLabelClass)}>
{msg("firstName")}
</label>
</div>
<div className={cx(props.kcInputWrapperClass)}>
<input
type="text"
id="firstName"
className={cx(props.kcInputClass)}
name="firstName"
defaultValue={register.formData.firstName ?? ""}
/>
</div>
</div>
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("lastName", props.kcFormGroupErrorClass))}>
<div className={cx(props.kcLabelWrapperClass)}>
<label htmlFor="lastName" className={cx(props.kcLabelClass)}>
{msg("lastName")}
</label>
</div>
<div className={cx(props.kcInputWrapperClass)}>
<input
type="text"
id="lastName"
className={cx(props.kcInputClass)}
name="lastName"
defaultValue={register.formData.lastName ?? ""}
/>
</div>
</div>
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("email", props.kcFormGroupErrorClass))}>
<div className={cx(props.kcLabelWrapperClass)}>
<label htmlFor="email" className={cx(props.kcLabelClass)}>
{msg("email")}
</label>
</div>
<div className={cx(props.kcInputWrapperClass)}>
<input
type="text"
id="email"
className={cx(props.kcInputClass)}
name="email"
defaultValue={register.formData.email ?? ""}
autoComplete="email"
/>
</div>
</div>
{!realm.registrationEmailAsUsername && (
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("username", props.kcFormGroupErrorClass))}>
<div className={cx(props.kcLabelWrapperClass)}>
<label htmlFor="username" className={cx(props.kcLabelClass)}>
{msg("username")}
</label>
</div>
<div className={cx(props.kcInputWrapperClass)}>
<input
type="text"
id="username"
className={cx(props.kcInputClass)}
name="username"
defaultValue={register.formData.username ?? ""}
autoComplete="username"
/>
</div>
</div>
)}
{passwordRequired && (
<>
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("password", props.kcFormGroupErrorClass))}>
<div className={cx(props.kcLabelWrapperClass)}>
<label htmlFor="password" className={cx(props.kcLabelClass)}>
{msg("password")}
</label>
</div>
<div className={cx(props.kcInputWrapperClass)}>
<input
type="password"
id="password"
className={cx(props.kcInputClass)}
name="password"
autoComplete="new-password"
/>
</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" className={cx(props.kcInputClass)} name="password-confirm" />
</div>
</div>
</>
)}
{recaptchaRequired && (
<div className="form-group">
<div className={cx(props.kcInputWrapperClass)}>
<div className="g-recaptcha" data-size="compact" data-sitekey={recaptchaSiteKey}></div>
</div>
</div>
)}
<div className={cx(props.kcFormGroupClass)}>
<div id="kc-form-options" className={cx(props.kcFormOptionsClass)}>
<div className={cx(props.kcFormOptionsWrapperClass)}>
<span>
<a href={url.loginUrl}>{msg("backToLogin")}</a>
</span>
</div>
</div>
<div id="kc-form-buttons" className={cx(props.kcFormButtonsClass)}>
<input
className={cx(props.kcButtonClass, props.kcButtonPrimaryClass, props.kcButtonBlockClass, props.kcButtonLargeClass)}
type="submit"
value={msgStr("doRegister")}
/>
</div>
</div>
</form>
}
/>
);
});
export default Register;

View File

@ -1,220 +0,0 @@
import React, { useMemo, memo, useEffect, useState, Fragment } from "react";
import Template from "./Template";
import type { KcProps } from "./KcProps";
import type { KcContextBase, Attribute } from "../getKcContext/KcContextBase";
import { useCssAndCx } from "tss-react";
import type { ReactComponent } from "../tools/ReactComponent";
import { useCallbackFactory } from "powerhooks/useCallbackFactory";
import { useFormValidationSlice } from "../useFormValidationSlice";
import type { I18n } from "../i18n";
const RegisterUserProfile = memo(({ kcContext, i18n, ...props_ }: { kcContext: KcContextBase.RegisterUserProfile; i18n: I18n } & KcProps) => {
const { url, messagesPerField, recaptchaRequired, recaptchaSiteKey } = kcContext;
const { msg, msgStr } = i18n;
const { cx, css } = useCssAndCx();
const props = useMemo(
() => ({
...props_,
"kcFormGroupClass": cx(props_.kcFormGroupClass, css({ "marginBottom": 20 })),
}),
[cx, css],
);
const [isFomSubmittable, setIsFomSubmittable] = useState(false);
return (
<Template
{...{ kcContext, i18n, ...props }}
displayMessage={messagesPerField.exists("global")}
displayRequiredFields={true}
doFetchDefaultThemeResources={true}
headerNode={msg("registerTitle")}
formNode={
<form id="kc-register-form" className={cx(props.kcFormClass)} action={url.registrationAction} method="post">
<UserProfileFormFields kcContext={kcContext} onIsFormSubmittableValueChange={setIsFomSubmittable} i18n={i18n} {...props} />
{recaptchaRequired && (
<div className="form-group">
<div className={cx(props.kcInputWrapperClass)}>
<div className="g-recaptcha" data-size="compact" data-sitekey={recaptchaSiteKey} />
</div>
</div>
)}
<div className={cx(props.kcFormGroupClass)}>
<div id="kc-form-options" className={cx(props.kcFormOptionsClass)}>
<div className={cx(props.kcFormOptionsWrapperClass)}>
<span>
<a href={url.loginUrl}>{msg("backToLogin")}</a>
</span>
</div>
</div>
<div id="kc-form-buttons" className={cx(props.kcFormButtonsClass)}>
<input
className={cx(props.kcButtonClass, props.kcButtonPrimaryClass, props.kcButtonBlockClass, props.kcButtonLargeClass)}
type="submit"
value={msgStr("doRegister")}
disabled={!isFomSubmittable}
/>
</div>
</div>
</form>
}
/>
);
});
type UserProfileFormFieldsProps = { kcContext: KcContextBase.RegisterUserProfile; i18n: I18n } & KcProps &
Partial<Record<"BeforeField" | "AfterField", ReactComponent<{ attribute: Attribute }>>> & {
onIsFormSubmittableValueChange: (isFormSubmittable: boolean) => void;
};
const UserProfileFormFields = memo(({ kcContext, onIsFormSubmittableValueChange, i18n, ...props }: UserProfileFormFieldsProps) => {
const { cx, css } = useCssAndCx();
const { advancedMsg } = i18n;
const {
formValidationState: { fieldStateByAttributeName, isFormSubmittable },
formValidationReducer,
attributesWithPassword,
} = useFormValidationSlice({
kcContext,
i18n,
});
useEffect(() => {
onIsFormSubmittableValueChange(isFormSubmittable);
}, [isFormSubmittable]);
const onChangeFactory = useCallbackFactory(
(
[name]: [string],
[
{
target: { value },
},
]: [React.ChangeEvent<HTMLInputElement | HTMLSelectElement>],
) =>
formValidationReducer({
"action": "update value",
name,
"newValue": value,
}),
);
const onBlurFactory = useCallbackFactory(([name]: [string]) =>
formValidationReducer({
"action": "focus lost",
name,
}),
);
let currentGroup = "";
return (
<>
{attributesWithPassword.map((attribute, i) => {
const { group = "", groupDisplayHeader = "", groupDisplayDescription = "" } = attribute;
const { value, displayableErrors } = fieldStateByAttributeName[attribute.name];
const formGroupClassName = cx(props.kcFormGroupClass, displayableErrors.length !== 0 && props.kcFormGroupErrorClass);
return (
<Fragment key={i}>
{group !== currentGroup && (currentGroup = group) !== "" && (
<div className={formGroupClassName}>
<div className={cx(props.kcContentWrapperClass)}>
<label id={`header-${group}`} className={cx(props.kcFormGroupHeader)}>
{advancedMsg(groupDisplayHeader) || currentGroup}
</label>
</div>
{groupDisplayDescription !== "" && (
<div className={cx(props.kcLabelWrapperClass)}>
<label id={`description-${group}`} className={`${cx(props.kcLabelClass)}`}>
{advancedMsg(groupDisplayDescription)}
</label>
</div>
)}
</div>
)}
<div className={formGroupClassName}>
<div className={cx(props.kcLabelWrapperClass)}>
<label htmlFor={attribute.name} className={cx(props.kcLabelClass)}>
{advancedMsg(attribute.displayName ?? "")}
</label>
{attribute.required && <>*</>}
</div>
<div className={cx(props.kcInputWrapperClass)}>
{(() => {
const { options } = attribute.validators;
if (options !== undefined) {
return (
<select
id={attribute.name}
name={attribute.name}
onChange={onChangeFactory(attribute.name)}
onBlur={onBlurFactory(attribute.name)}
value={value}
>
{options.options.map(option => (
<option key={option} value={option}>
{option}
</option>
))}
</select>
);
}
return (
<input
type={(() => {
switch (attribute.name) {
case "password-confirm":
case "password":
return "password";
default:
return "text";
}
})()}
id={attribute.name}
name={attribute.name}
value={value}
onChange={onChangeFactory(attribute.name)}
className={cx(props.kcInputClass)}
aria-invalid={displayableErrors.length !== 0}
disabled={attribute.readOnly}
autoComplete={attribute.autocomplete}
onBlur={onBlurFactory(attribute.name)}
/>
);
})()}
{displayableErrors.length !== 0 && (
<span
id={`input-error-${attribute.name}`}
className={cx(
props.kcInputErrorMessageClass,
css({
"position": displayableErrors.length === 1 ? "absolute" : undefined,
"& > span": { "display": "block" },
}),
)}
aria-live="polite"
>
{displayableErrors.map(({ errorMessage }) => errorMessage)}
</span>
)}
</div>
</div>
</Fragment>
);
})}
</>
);
});
export default RegisterUserProfile;

View File

@ -1,255 +0,0 @@
import React, { useReducer, useEffect, memo } from "react";
import type { ReactNode } from "react";
import type { KcContextBase } from "../getKcContext/KcContextBase";
import { assert } from "../tools/assert";
import { useCallbackFactory } from "powerhooks/useCallbackFactory";
import { headInsert } from "../tools/headInsert";
import { pathJoin } from "../../bin/tools/pathJoin";
import { useConstCallback } from "powerhooks/useConstCallback";
import type { KcTemplateProps } from "./KcProps";
import { useCssAndCx } from "tss-react";
import type { I18n } from "../i18n";
export type TemplateProps = {
displayInfo?: boolean;
displayMessage?: boolean;
displayRequiredFields?: boolean;
displayWide?: boolean;
showAnotherWayIfPresent?: boolean;
headerNode: ReactNode;
showUsernameNode?: ReactNode;
formNode: ReactNode;
infoNode?: ReactNode;
/** If you write your own page you probably want
* to avoid pulling the default theme assets.
*/
doFetchDefaultThemeResources: boolean;
} & { kcContext: KcContextBase; i18n: I18n } & KcTemplateProps;
const Template = memo((props: TemplateProps) => {
const {
displayInfo = false,
displayMessage = true,
displayRequiredFields = false,
displayWide = false,
showAnotherWayIfPresent = true,
headerNode,
showUsernameNode = null,
formNode,
infoNode = null,
kcContext,
i18n,
doFetchDefaultThemeResources,
} = props;
const { cx } = useCssAndCx();
useEffect(() => {
console.log("Rendering this page with react using keycloakify");
}, []);
const { msg, changeLocale, labelBySupportedLanguageTag, currentLanguageTag } = i18n;
const onChangeLanguageClickFactory = useCallbackFactory(([kcLanguageTag]: [string]) => changeLocale(kcLanguageTag));
const onTryAnotherWayClick = useConstCallback(() => (document.forms["kc-select-try-another-way-form" as never].submit(), false));
const { realm, locale, auth, url, message, isAppInitiatedAction } = kcContext;
const [isExtraCssLoaded, setExtraCssLoaded] = useReducer(() => true, false);
useEffect(() => {
if (!doFetchDefaultThemeResources) {
setExtraCssLoaded();
return;
}
let isUnmounted = false;
const cleanups: (() => void)[] = [];
const toArr = (x: string | readonly string[] | undefined) => (typeof x === "string" ? x.split(" ") : x ?? []);
Promise.all(
[
...toArr(props.stylesCommon).map(relativePath => pathJoin(url.resourcesCommonPath, relativePath)),
...toArr(props.styles).map(relativePath => pathJoin(url.resourcesPath, relativePath)),
]
.reverse()
.map(href =>
headInsert({
"type": "css",
href,
"position": "prepend",
}),
),
).then(() => {
if (isUnmounted) {
return;
}
setExtraCssLoaded();
});
toArr(props.scripts).forEach(relativePath =>
headInsert({
"type": "javascript",
"src": pathJoin(url.resourcesPath, relativePath),
}),
);
if (props.kcHtmlClass !== undefined) {
const htmlClassList = document.getElementsByTagName("html")[0].classList;
const tokens = cx(props.kcHtmlClass).split(" ");
htmlClassList.add(...tokens);
cleanups.push(() => htmlClassList.remove(...tokens));
}
return () => {
isUnmounted = true;
cleanups.forEach(f => f());
};
}, [props.kcHtmlClass]);
if (!isExtraCssLoaded) {
return null;
}
return (
<div className={cx(props.kcLoginClass)}>
<div id="kc-header" className={cx(props.kcHeaderClass)}>
<div id="kc-header-wrapper" className={cx(props.kcHeaderWrapperClass)}>
{msg("loginTitleHtml", realm.displayNameHtml)}
</div>
</div>
<div className={cx(props.kcFormCardClass, displayWide && props.kcFormCardAccountClass)}>
<header className={cx(props.kcFormHeaderClass)}>
{realm.internationalizationEnabled && (assert(locale !== undefined), true) && locale.supported.length > 1 && (
<div id="kc-locale">
<div id="kc-locale-wrapper" className={cx(props.kcLocaleWrapperClass)}>
<div className="kc-dropdown" id="kc-locale-dropdown">
<a href="#" id="kc-current-locale-link">
{labelBySupportedLanguageTag[currentLanguageTag]}
</a>
<ul>
{locale.supported.map(({ languageTag }) => (
<li key={languageTag} className="kc-dropdown-item">
<a href="#" onClick={onChangeLanguageClickFactory(languageTag)}>
{labelBySupportedLanguageTag[languageTag]}
</a>
</li>
))}
</ul>
</div>
</div>
</div>
)}
{!(auth !== undefined && auth.showUsername && !auth.showResetCredentials) ? (
displayRequiredFields ? (
<div className={cx(props.kcContentWrapperClass)}>
<div className={cx(props.kcLabelWrapperClass, "subtitle")}>
<span className="subtitle">
<span className="required">*</span>
{msg("requiredFields")}
</span>
</div>
<div className="col-md-10">
<h1 id="kc-page-title">{headerNode}</h1>
</div>
</div>
) : (
<h1 id="kc-page-title">{headerNode}</h1>
)
) : displayRequiredFields ? (
<div className={cx(props.kcContentWrapperClass)}>
<div className={cx(props.kcLabelWrapperClass, "subtitle")}>
<span className="subtitle">
<span className="required">*</span> {msg("requiredFields")}
</span>
</div>
<div className="col-md-10">
{showUsernameNode}
<div className={cx(props.kcFormGroupClass)}>
<div id="kc-username">
<label id="kc-attempted-username">{auth?.attemptedUsername}</label>
<a id="reset-login" href={url.loginRestartFlowUrl}>
<div className="kc-login-tooltip">
<i className={cx(props.kcResetFlowIcon)}></i>
<span className="kc-tooltip-text">{msg("restartLoginTooltip")}</span>
</div>
</a>
</div>
</div>
</div>
</div>
) : (
<>
{showUsernameNode}
<div className={cx(props.kcFormGroupClass)}>
<div id="kc-username">
<label id="kc-attempted-username">{auth?.attemptedUsername}</label>
<a id="reset-login" href={url.loginRestartFlowUrl}>
<div className="kc-login-tooltip">
<i className={cx(props.kcResetFlowIcon)}></i>
<span className="kc-tooltip-text">{msg("restartLoginTooltip")}</span>
</div>
</a>
</div>
</div>
</>
)}
</header>
<div id="kc-content">
<div id="kc-content-wrapper">
{/* App-initiated actions should not see warning messages about the need to complete the action during login. */}
{displayMessage && message !== undefined && (message.type !== "warning" || !isAppInitiatedAction) && (
<div className={cx("alert", `alert-${message.type}`)}>
{message.type === "success" && <span className={cx(props.kcFeedbackSuccessIcon)}></span>}
{message.type === "warning" && <span className={cx(props.kcFeedbackWarningIcon)}></span>}
{message.type === "error" && <span className={cx(props.kcFeedbackErrorIcon)}></span>}
{message.type === "info" && <span className={cx(props.kcFeedbackInfoIcon)}></span>}
<span
className="kc-feedback-text"
dangerouslySetInnerHTML={{
"__html": message.summary,
}}
/>
</div>
)}
{formNode}
{auth !== undefined && auth.showTryAnotherWayLink && showAnotherWayIfPresent && (
<form
id="kc-select-try-another-way-form"
action={url.loginAction}
method="post"
className={cx(displayWide && props.kcContentWrapperClass)}
>
<div className={cx(displayWide && [props.kcFormSocialAccountContentClass, props.kcFormSocialAccountClass])}>
<div className={cx(props.kcFormGroupClass)}>
<input type="hidden" name="tryAnotherWay" value="on" />
<a href="#" id="try-another-way" onClick={onTryAnotherWayClick}>
{msg("doTryAnotherWay")}
</a>
</div>
</div>
</form>
)}
{displayInfo && (
<div id="kc-info" className={cx(props.kcSignUpClass)}>
<div id="kc-info-wrapper" className={cx(props.kcInfoAreaWrapperClass)}>
{infoNode}
</div>
</div>
)}
</div>
</div>
</div>
</div>
);
});
export default Template;

View File

@ -1,102 +0,0 @@
import React, { useEffect, memo } from "react";
import Template from "./Template";
import type { KcProps } from "./KcProps";
import type { KcContextBase } from "../getKcContext/KcContextBase";
import { useCssAndCx } from "tss-react";
import { Evt } from "evt";
import { useRerenderOnStateChange } from "evt/hooks";
import { assert } from "tsafe/assert";
import { fallbackLanguageTag } from "../i18n";
import type { I18n } from "../i18n";
import memoize from "memoizee";
import { useConst } from "powerhooks/useConst";
import { useConstCallback } from "powerhooks/useConstCallback";
export const evtTermMarkdown = Evt.create<string | undefined>(undefined);
export type KcContextLike = {
locale?: {
currentLanguageTag: string;
};
};
assert<KcContextBase extends KcContextLike ? true : false>();
/** Allow to avoid bundling the terms and download it on demand*/
export function useDownloadTerms(params: {
kcContext: KcContextLike;
downloadTermMarkdown: (params: { currentLanguageTag: string }) => Promise<string>;
}) {
const { kcContext } = params;
const { downloadTermMarkdownMemoized } = (function useClosure() {
const { downloadTermMarkdown } = params;
const downloadTermMarkdownConst = useConstCallback(downloadTermMarkdown);
const downloadTermMarkdownMemoized = useConst(() =>
memoize((currentLanguageTag: string) => downloadTermMarkdownConst({ currentLanguageTag }), { "promise": true }),
);
return { downloadTermMarkdownMemoized };
})();
useEffect(() => {
downloadTermMarkdownMemoized(kcContext.locale?.currentLanguageTag ?? fallbackLanguageTag).then(
thermMarkdown => (evtTermMarkdown.state = thermMarkdown),
);
}, []);
}
const Terms = memo(({ kcContext, i18n, ...props }: { kcContext: KcContextBase.Terms; i18n: I18n } & KcProps) => {
const { msg, msgStr } = i18n;
useRerenderOnStateChange(evtTermMarkdown);
const { cx } = useCssAndCx();
const { url } = kcContext;
if (evtTermMarkdown.state === undefined) {
return null;
}
return (
<Template
{...{ kcContext, i18n, ...props }}
doFetchDefaultThemeResources={true}
displayMessage={false}
headerNode={msg("termsTitle")}
formNode={
<>
<div id="kc-terms-text">{evtTermMarkdown.state}</div>
<form className="form-actions" action={url.loginAction} method="POST">
<input
className={cx(
props.kcButtonClass,
props.kcButtonClass,
props.kcButtonClass,
props.kcButtonPrimaryClass,
props.kcButtonLargeClass,
)}
name="accept"
id="kc-accept"
type="submit"
value={msgStr("doAccept")}
/>
<input
className={cx(props.kcButtonClass, props.kcButtonDefaultClass, props.kcButtonLargeClass)}
name="cancel"
id="kc-decline"
type="submit"
value={msgStr("doDecline")}
/>
</form>
<div className="clearfix" />
</>
}
/>
);
});
export default Terms;

View File

@ -1,392 +0,0 @@
import type { PageId } from "../../bin/build-keycloak-theme/generateFtl";
import { assert } from "tsafe/assert";
import type { Equals } from "tsafe";
import type { MessageKeyBase } from "../i18n";
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 KcContextBase =
| KcContextBase.Login
| KcContextBase.Register
| KcContextBase.RegisterUserProfile
| KcContextBase.Info
| KcContextBase.Error
| KcContextBase.LoginResetPassword
| KcContextBase.LoginVerifyEmail
| KcContextBase.Terms
| KcContextBase.LoginOtp
| KcContextBase.LoginUpdatePassword
| KcContextBase.LoginUpdateProfile
| KcContextBase.LoginIdpLinkConfirm
| KcContextBase.LoginIdpLinkEmail
| KcContextBase.LoginPageExpired
| KcContextBase.LoginConfigTotp
| KcContextBase.LogoutConfirm;
export declare namespace KcContextBase {
export type Common = {
url: {
loginAction: string;
resourcesPath: string;
resourcesCommonPath: string;
loginRestartFlowUrl: string;
loginUrl: string;
};
realm: {
name: string;
displayName?: string;
displayNameHtml?: string;
internationalizationEnabled: boolean;
registrationEmailAsUsername: boolean;
};
/** Undefined if !realm.internationalizationEnabled */
locale?: {
supported: {
url: string;
label: string;
languageTag: string;
}[];
currentLanguageTag: string;
};
auth?: {
showUsername?: boolean;
showResetCredentials?: boolean;
showTryAnotherWayLink?: boolean;
attemptedUsername?: string;
};
scripts: string[];
message?: {
type: "success" | "warning" | "error" | "info";
summary: string;
};
client: {
clientId: string;
name?: string;
description?: string;
};
isAppInitiatedAction: boolean;
messagesPerField: {
printIfExists: <T>(fieldName: string, x: T) => T | undefined;
existsError: (fieldName: string) => boolean;
get: (fieldName: string) => string;
exists: (fieldName: string) => boolean;
};
};
export type Login = Common & {
pageId: "login.ftl";
url: {
loginResetCredentialsUrl: string;
registrationUrl: string;
};
realm: {
loginWithEmailAllowed: boolean;
rememberMe: boolean;
password: boolean;
resetPasswordAllowed: boolean;
registrationAllowed: boolean;
};
auth: {
selectedCredential?: string;
};
registrationDisabled: boolean;
login: {
username?: string;
rememberMe?: boolean;
};
usernameEditDisabled: boolean;
social: {
displayInfo: boolean;
providers?: {
loginUrl: string;
alias: string;
providerId: string;
displayName: string;
}[];
};
};
export type RegisterCommon = Common & {
url: {
registrationAction: string;
};
passwordRequired: boolean;
recaptchaRequired: boolean;
recaptchaSiteKey?: string;
social: {
displayInfo: boolean;
providers?: {
loginUrl: string;
alias: string;
providerId: string;
displayName: string;
}[];
};
};
export type Register = RegisterCommon & {
pageId: "register.ftl";
register: {
formData: {
firstName?: string;
displayName?: string;
lastName?: string;
email?: string;
username?: string;
};
};
};
export type RegisterUserProfile = RegisterCommon & {
pageId: "register-user-profile.ftl";
profile: {
context: "REGISTRATION_PROFILE";
attributes: Attribute[];
attributesByName: Record<string, Attribute>;
};
};
export type Info = Common & {
pageId: "info.ftl";
messageHeader?: string;
requiredActions?: ExtractAfterStartingWith<"requiredAction.", MessageKeyBase>[];
skipLink: boolean;
pageRedirectUri?: string;
actionUri?: string;
client: {
baseUrl?: string;
};
};
export type Error = Common & {
pageId: "error.ftl";
client?: {
baseUrl?: string;
};
message: NonNullable<Common["message"]>;
};
export type LoginResetPassword = Common & {
pageId: "login-reset-password.ftl";
realm: {
loginWithEmailAllowed: boolean;
};
};
export type LoginVerifyEmail = Common & {
pageId: "login-verify-email.ftl";
//NOTE: Optional because maybe it wasn't defined in older keycloak versions.
user?: {
email: string;
};
};
export type Terms = Common & {
pageId: "terms.ftl";
};
export type LoginOtp = Common & {
pageId: "login-otp.ftl";
otpLogin: {
userOtpCredentials: { id: string; userLabel: string }[];
};
};
export type LoginUpdatePassword = Common & {
pageId: "login-update-password.ftl";
username: string;
};
export type LoginUpdateProfile = Common & {
pageId: "login-update-profile.ftl";
user: {
editUsernameAllowed: boolean;
username?: string;
email?: string;
firstName?: string;
lastName?: string;
};
};
export type LoginIdpLinkConfirm = Common & {
pageId: "login-idp-link-confirm.ftl";
idpAlias: string;
};
export type LoginIdpLinkEmail = Common & {
pageId: "login-idp-link-email.ftl";
brokerContext: {
username: string;
};
idpAlias: string;
};
export type LoginPageExpired = Common & {
pageId: "login-page-expired.ftl";
};
export type LoginConfigTotp = Common & {
pageId: "login-config-totp.ftl";
mode?: "qr" | "manual" | undefined | null;
totp: {
totpSecretEncoded: string;
qrUrl: string;
policy: {
supportedApplications: string[];
algorithm: "HmacSHA1" | "HmacSHA256" | "HmacSHA512";
digits: number;
lookAheadWindow: number;
} & (
| {
type: "totp";
period: number;
}
| {
type: "hotp";
initialCounter: number;
}
);
totpSecretQrCode: string;
manualUrl: string;
totpSecret: string;
otpCredentials: { id: string; userLabel: string }[];
};
};
export type LogoutConfirm = Common & {
pageId: "logout-confirm.ftl";
url: {
logoutConfirmAction: string;
};
client: {
baseUrl?: string;
};
logoutConfirm: {
code: string;
skipLink?: boolean;
};
};
}
export type Attribute = {
name: string;
displayName?: string;
required: boolean;
value?: string;
group?: string;
groupDisplayHeader?: string;
groupDisplayDescription?: string;
readOnly: boolean;
validators: Validators;
annotations: Record<string, string>;
groupAnnotations: Record<string, string>;
autocomplete?:
| "on"
| "off"
| "name"
| "honorific-prefix"
| "given-name"
| "additional-name"
| "family-name"
| "honorific-suffix"
| "nickname"
| "email"
| "username"
| "new-password"
| "current-password"
| "one-time-code"
| "organization-title"
| "organization"
| "street-address"
| "address-line1"
| "address-line2"
| "address-line3"
| "address-level4"
| "address-level3"
| "address-level2"
| "address-level1"
| "country"
| "country-name"
| "postal-code"
| "cc-name"
| "cc-given-name"
| "cc-additional-name"
| "cc-family-name"
| "cc-number"
| "cc-exp"
| "cc-exp-month"
| "cc-exp-year"
| "cc-csc"
| "cc-type"
| "transaction-currency"
| "transaction-amount"
| "language"
| "bday"
| "bday-day"
| "bday-month"
| "bday-year"
| "sex"
| "tel"
| "tel-country-code"
| "tel-national"
| "tel-area-code"
| "tel-local"
| "tel-extension"
| "impp"
| "url"
| "photo";
};
export type Validators = Partial<{
length: Validators.DoIgnoreEmpty & Validators.Range;
double: Validators.DoIgnoreEmpty & Validators.Range;
integer: Validators.DoIgnoreEmpty & Validators.Range;
email: Validators.DoIgnoreEmpty;
"up-immutable-attribute": {};
"up-attribute-required-by-metadata-value": {};
"up-username-has-value": {};
"up-duplicate-username": {};
"up-username-mutation": {};
"up-email-exists-as-username": {};
"up-blank-attribute-value": Validators.ErrorMessage & {
"fail-on-null": boolean;
};
"up-duplicate-email": {};
"local-date": Validators.DoIgnoreEmpty;
pattern: Validators.DoIgnoreEmpty & Validators.ErrorMessage & { pattern: string };
"person-name-prohibited-characters": Validators.DoIgnoreEmpty & Validators.ErrorMessage;
uri: Validators.DoIgnoreEmpty;
"username-prohibited-characters": Validators.DoIgnoreEmpty & Validators.ErrorMessage;
/** Made up validator that only exists in Keycloakify */
_compareToOther: Validators.DoIgnoreEmpty &
Validators.ErrorMessage & {
name: string;
shouldBe: "equal" | "different";
};
options: Validators.Options;
}>;
export declare namespace Validators {
export type DoIgnoreEmpty = {
"ignore.empty.value"?: boolean;
};
export type ErrorMessage = {
"error-message"?: string;
};
export type Range = {
/** "0", "1", "2"... yeah I know, don't tell me */
min?: `${number}`;
max?: `${number}`;
};
export type Options = {
options: string[];
};
}
assert<Equals<KcContextBase["pageId"], PageId>>();

View File

@ -1,108 +0,0 @@
import type { KcContextBase, Attribute } from "./KcContextBase";
import { kcContextMocks, kcContextCommonMock } from "./kcContextMocks";
import type { DeepPartial } from "../tools/DeepPartial";
import { deepAssign } from "../tools/deepAssign";
import { id } from "tsafe/id";
import { exclude } from "tsafe/exclude";
import { assert } from "tsafe/assert";
import type { ExtendsKcContextBase } from "./getKcContextFromWindow";
import { getKcContextFromWindow } from "./getKcContextFromWindow";
import { pathJoin } from "../../bin/tools/pathJoin";
import { pathBasename } from "../tools/pathBasename";
import { mockTestingResourcesCommonPath } from "../../bin/mockTestingResourcesPath";
export function getKcContext<KcContextExtended extends { pageId: string } = never>(params?: {
mockPageId?: ExtendsKcContextBase<KcContextExtended>["pageId"];
mockData?: readonly DeepPartial<ExtendsKcContextBase<KcContextExtended>>[];
}): { kcContext: ExtendsKcContextBase<KcContextExtended> | undefined } {
const { mockPageId, mockData } = params ?? {};
if (mockPageId !== undefined) {
//TODO maybe trow if no mock fo custom page
const kcContextDefaultMock = kcContextMocks.find(({ pageId }) => pageId === mockPageId);
const partialKcContextCustomMock = mockData?.find(({ pageId }) => pageId === mockPageId);
if (kcContextDefaultMock === undefined && partialKcContextCustomMock === undefined) {
console.warn(
[
`WARNING: You declared the non build in page ${mockPageId} but you didn't `,
`provide mock data needed to debug the page outside of Keycloak as you are trying to do now.`,
`Please check the documentation of the getKcContext function`,
].join("\n"),
);
}
const kcContext: any = {};
deepAssign({
"target": kcContext,
"source": kcContextDefaultMock !== undefined ? kcContextDefaultMock : { "pageId": mockPageId, ...kcContextCommonMock },
});
if (partialKcContextCustomMock !== undefined) {
deepAssign({
"target": kcContext,
"source": partialKcContextCustomMock,
});
if (partialKcContextCustomMock.pageId === "register-user-profile.ftl") {
assert(kcContextDefaultMock?.pageId === "register-user-profile.ftl");
const { attributes } = kcContextDefaultMock.profile;
id<KcContextBase.RegisterUserProfile>(kcContext).profile.attributes = [];
id<KcContextBase.RegisterUserProfile>(kcContext).profile.attributesByName = {};
const partialAttributes = [
...((partialKcContextCustomMock as DeepPartial<KcContextBase.RegisterUserProfile>).profile?.attributes ?? []),
].filter(exclude(undefined));
attributes.forEach(attribute => {
const partialAttribute = partialAttributes.find(({ name }) => name === attribute.name);
const augmentedAttribute: Attribute = {} as any;
deepAssign({
"target": augmentedAttribute,
"source": attribute,
});
if (partialAttribute !== undefined) {
partialAttributes.splice(partialAttributes.indexOf(partialAttribute), 1);
deepAssign({
"target": augmentedAttribute,
"source": partialAttribute,
});
}
id<KcContextBase.RegisterUserProfile>(kcContext).profile.attributes.push(augmentedAttribute);
id<KcContextBase.RegisterUserProfile>(kcContext).profile.attributesByName[augmentedAttribute.name] = augmentedAttribute;
});
partialAttributes.forEach(partialAttribute => {
const { name } = partialAttribute;
assert(name !== undefined, "If you define a mock attribute it must have at least a name");
id<KcContextBase.RegisterUserProfile>(kcContext).profile.attributes.push(partialAttribute as any);
id<KcContextBase.RegisterUserProfile>(kcContext).profile.attributesByName[name] = partialAttribute as any;
});
}
}
return { kcContext };
}
const kcContext = getKcContextFromWindow<KcContextExtended>();
if (kcContext !== undefined) {
const { url } = kcContext;
url.resourcesCommonPath = pathJoin(url.resourcesPath, pathBasename(mockTestingResourcesCommonPath));
}
return { kcContext };
}

View File

@ -1,11 +0,0 @@
import type { KcContextBase } from "./KcContextBase";
import type { AndByDiscriminatingKey } from "../tools/AndByDiscriminatingKey";
import { ftlValuesGlobalName } from "../../bin/build-keycloak-theme/ftlValuesGlobalName";
export type ExtendsKcContextBase<KcContextExtended extends { pageId: string }> = [KcContextExtended] extends [never]
? KcContextBase
: AndByDiscriminatingKey<"pageId", KcContextExtended & KcContextBase.Common, KcContextBase>;
export function getKcContextFromWindow<KcContextExtended extends { pageId: string } = never>(): ExtendsKcContextBase<KcContextExtended> | undefined {
return typeof window === "undefined" ? undefined : (window as any)[ftlValuesGlobalName];
}

View File

@ -1,3 +0,0 @@
export type { KcContextBase, Attribute, Validators } from "./KcContextBase";
export type { ExtendsKcContextBase } from "./getKcContextFromWindow";
export { getKcContext } from "./getKcContext";

View File

@ -1 +0,0 @@
export * from "./kcContextMocks";

View File

@ -1,427 +0,0 @@
import "minimal-polyfills/Object.fromEntries";
import type { KcContextBase, Attribute } from "../KcContextBase";
//NOTE: Aside because we want to be able to import them from node
import { mockTestingResourcesCommonPath, mockTestingResourcesPath } from "../../../bin/mockTestingResourcesPath";
import { id } from "tsafe/id";
import { pathJoin } from "../../../bin/tools/pathJoin";
const PUBLIC_URL = process.env["PUBLIC_URL"] ?? "/";
export const kcContextCommonMock: KcContextBase.Common = {
"url": {
"loginAction": "#",
"resourcesPath": pathJoin(PUBLIC_URL, mockTestingResourcesPath),
"resourcesCommonPath": pathJoin(PUBLIC_URL, mockTestingResourcesCommonPath),
"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",
},
"realm": {
"name": "myrealm",
"displayName": "myrealm",
"displayNameHtml": "myrealm",
"internationalizationEnabled": true,
"registrationEmailAsUsername": false,
},
"messagesPerField": {
"printIfExists": (...[, x]) => x,
"existsError": () => true,
"get": key => `Fake error for ${key}`,
"exists": () => true,
},
"locale": {
"supported": [
/* spell-checker: disable */
{
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=de",
"label": "Deutsch",
"languageTag": "de",
},
{
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=no",
"label": "Norsk",
"languageTag": "no",
},
{
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=ru",
"label": "Русский",
"languageTag": "ru",
},
{
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=sv",
"label": "Svenska",
"languageTag": "sv",
},
{
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=pt-BR",
"label": "Português (Brasil)",
"languageTag": "pt-BR",
},
{
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=lt",
"label": "Lietuvių",
"languageTag": "lt",
},
{
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=en",
"label": "English",
"languageTag": "en",
},
{
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=it",
"label": "Italiano",
"languageTag": "it",
},
{
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=fr",
"label": "Français",
"languageTag": "fr",
},
{
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=zh-CN",
"label": "中文简体",
"languageTag": "zh-CN",
},
{
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=es",
"label": "Español",
"languageTag": "es",
},
{
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=cs",
"label": "Čeština",
"languageTag": "cs",
},
{
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=ja",
"label": "日本語",
"languageTag": "ja",
},
{
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=sk",
"label": "Slovenčina",
"languageTag": "sk",
},
{
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=pl",
"label": "Polski",
"languageTag": "pl",
},
{
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=ca",
"label": "Català",
"languageTag": "ca",
},
{
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=nl",
"label": "Nederlands",
"languageTag": "nl",
},
{
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=tr",
"label": "Türkçe",
"languageTag": "tr",
},
/* spell-checker: enable */
],
"currentLanguageTag": "en",
},
"auth": {
"showUsername": false,
"showResetCredentials": false,
"showTryAnotherWayLink": false,
},
"client": {
"clientId": "myApp",
},
"scripts": [],
"message": {
"type": "success",
"summary": "This is a test message",
},
"isAppInitiatedAction": false,
};
const loginUrl = {
...kcContextCommonMock.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",
};
export const kcContextMocks: KcContextBase[] = [
id<KcContextBase.Login>({
...kcContextCommonMock,
"pageId": "login.ftl",
"url": loginUrl,
"realm": {
...kcContextCommonMock.realm,
"loginWithEmailAllowed": true,
"rememberMe": true,
"password": true,
"resetPasswordAllowed": true,
"registrationAllowed": true,
},
"auth": kcContextCommonMock.auth!,
"social": {
"displayInfo": true,
},
"usernameEditDisabled": false,
"login": {
"rememberMe": false,
},
"registrationDisabled": false,
}),
...(() => {
const registerCommon: KcContextBase.RegisterCommon = {
...kcContextCommonMock,
"url": {
...loginUrl,
"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",
},
"scripts": [],
"isAppInitiatedAction": false,
"passwordRequired": true,
"recaptchaRequired": false,
"social": {
"displayInfo": true,
},
};
return [
id<KcContextBase.Register>({
"pageId": "register.ftl",
...registerCommon,
"register": {
"formData": {},
},
}),
id<KcContextBase.RegisterUserProfile>({
"pageId": "register-user-profile.ftl",
...registerCommon,
"profile": {
"context": "REGISTRATION_PROFILE" as const,
...(() => {
const attributes: Attribute[] = [
{
"validators": {
"username-prohibited-characters": {
"ignore.empty.value": true,
},
"up-username-has-value": {},
"length": {
"ignore.empty.value": true,
"min": "3",
"max": "255",
},
"up-duplicate-username": {},
"up-username-mutation": {},
},
"displayName": "${username}",
"annotations": {},
"required": true,
"groupAnnotations": {},
"autocomplete": "username",
"readOnly": false,
"name": "username",
"value": "xxxx",
},
{
"validators": {
"up-email-exists-as-username": {},
"length": {
"max": "255",
"ignore.empty.value": true,
},
"up-blank-attribute-value": {
"error-message": "missingEmailMessage",
"fail-on-null": false,
},
"up-duplicate-email": {},
"email": {
"ignore.empty.value": true,
},
"pattern": {
"ignore.empty.value": true,
"pattern": "gmail\\.com$",
},
},
"displayName": "${email}",
"annotations": {},
"required": true,
"groupAnnotations": {},
"autocomplete": "email",
"readOnly": false,
"name": "email",
},
{
"validators": {
"length": {
"max": "255",
"ignore.empty.value": true,
},
"person-name-prohibited-characters": {
"ignore.empty.value": true,
},
"up-immutable-attribute": {},
"up-attribute-required-by-metadata-value": {},
},
"displayName": "${firstName}",
"annotations": {},
"required": true,
"groupAnnotations": {},
"readOnly": false,
"name": "firstName",
},
{
"validators": {
"length": {
"max": "255",
"ignore.empty.value": true,
},
"person-name-prohibited-characters": {
"ignore.empty.value": true,
},
"up-immutable-attribute": {},
"up-attribute-required-by-metadata-value": {},
},
"displayName": "${lastName}",
"annotations": {},
"required": true,
"groupAnnotations": {},
"readOnly": false,
"name": "lastName",
},
];
return {
attributes,
"attributesByName": Object.fromEntries(attributes.map(attribute => [attribute.name, attribute])) as any,
} as any;
})(),
},
}),
];
})(),
id<KcContextBase.Info>({
...kcContextCommonMock,
"pageId": "info.ftl",
"messageHeader": "<Message header>",
"requiredActions": undefined,
"skipLink": false,
"actionUri": "#",
"client": {
"clientId": "myApp",
"baseUrl": "#",
},
}),
id<KcContextBase.Error>({
...kcContextCommonMock,
"pageId": "error.ftl",
"client": {
"clientId": "myApp",
"baseUrl": "#",
},
"message": {
"type": "error",
"summary": "This is the error message",
},
}),
id<KcContextBase.LoginResetPassword>({
...kcContextCommonMock,
"pageId": "login-reset-password.ftl",
"realm": {
...kcContextCommonMock.realm,
"loginWithEmailAllowed": false,
},
}),
id<KcContextBase.LoginVerifyEmail>({
...kcContextCommonMock,
"pageId": "login-verify-email.ftl",
"user": {
"email": "john.doe@gmail.com",
},
}),
id<KcContextBase.Terms>({
...kcContextCommonMock,
"pageId": "terms.ftl",
}),
id<KcContextBase.LoginOtp>({
...kcContextCommonMock,
"pageId": "login-otp.ftl",
"otpLogin": {
"userOtpCredentials": [
{
"id": "id1",
"userLabel": "label1",
},
{
"id": "id2",
"userLabel": "label2",
},
],
},
}),
id<KcContextBase.LoginUpdatePassword>({
...kcContextCommonMock,
"pageId": "login-update-password.ftl",
"username": "anUsername",
}),
id<KcContextBase.LoginUpdateProfile>({
...kcContextCommonMock,
"pageId": "login-update-profile.ftl",
"user": {
"editUsernameAllowed": true,
"username": "anUsername",
"email": "foo@example.com",
"firstName": "aFirstName",
"lastName": "aLastName",
},
}),
id<KcContextBase.LoginIdpLinkConfirm>({
...kcContextCommonMock,
"pageId": "login-idp-link-confirm.ftl",
"idpAlias": "FranceConnect",
}),
id<KcContextBase.LoginIdpLinkEmail>({
...kcContextCommonMock,
"pageId": "login-idp-link-email.ftl",
"idpAlias": "FranceConnect",
"brokerContext": {
"username": "anUsername",
},
}),
id<KcContextBase.LoginConfigTotp>({
...kcContextCommonMock,
"pageId": "login-config-totp.ftl",
totp: {
totpSecretEncoded: "KVVF G2BY N4YX S6LB IUYT K2LH IFYE 4SBV",
qrUrl: "#",
totpSecretQrCode:
"iVBORw0KGgoAAAANSUhEUgAAAPYAAAD2AQAAAADNaUdlAAACM0lEQVR4Xu3OIZJgOQwDUDFd2UxiurLAVnnbHw4YGDKtSiWOn4Gxf81//7r/+q8b4HfLGBZDK9d85NmNR+sB42sXvOYrN5P1DcgYYFTGfOlbzE8gzwy3euweGizw7cfdl34/GRhlkxjKNV+5AebPXPORX1JuB9x8ZfbyyD2y1krWAKsbMq1HnqQDaLfa77p4+MqvzEGSqvSAD/2IHW2yHaigR9tX3m8dDIYGcNf3f+gDpVBZbZU77zyJ6Rlcy+qoTMG887KAPD9hsh6a1Sv3gJUHGHUAxSMzj7zqDDe7Phmt2eG+8UsMxjRGm816MAO+8VMl1R1jGHOrZB/5Zo/WXAPgxixm9Mo96vDGrM1eOto8c4Ax4wF437mifOXlpiPzCnN7Y9l95NnEMxgMY9AAGA8fucH14Y1aVb6N/cqrmyh0BVht7k1e+bU8LK0Cg5vmVq9c5vHIjOfqxDIfeTraNVTwewa4wVe+SW5N+uP1qACeudUZbqGOfA6VZV750Noq2Xx3kpveV44ZelSV1V7KFHzkWyVrrlUwG0Pl9pWnoy3vsQoME6vKI69i5osVgwWzHT7zjmJtMcNUSVn1oYMd7ZodbgowZl45VG0uVuLPUr1yc79uaQBag/mqR34xhlWyHm1prplHboCWdZ4TeZjsK8+dI+jbz1C5hl65mcpgB5dhcj8+dGO+0Ko68+lD37JDD83dpDLzzK+TrQyaVwGj6pUboGV+7+AyN8An/pf84/7rv/4/1l4OCc/1BYMAAAAASUVORK5CYII=",
manualUrl: "#",
totpSecret: "G4nsI8lQagRMUchH8jEG",
otpCredentials: [],
policy: {
supportedApplications: ["FreeOTP", "Google Authenticator"],
algorithm: "HmacSHA1",
digits: 6,
lookAheadWindow: 1,
type: "totp",
period: 30,
},
},
}),
id<KcContextBase.LogoutConfirm>({
...kcContextCommonMock,
"pageId": "logout-confirm.ftl",
"url": {
...kcContextCommonMock.url,
"logoutConfirmAction": "Continuer?",
},
"client": {
"clientId": "myApp",
"baseUrl": "#",
},
"logoutConfirm": { "code": "123", skipLink: false },
}),
];

View File

@ -1,137 +0,0 @@
//This code was automatically generated by running dist/bin/generate-i18n-messages.js
//PLEASE DO NOT EDIT MANUALLY
/* spell-checker: disable */
const messages = {
"doSave": "Desa",
"doCancel": "Cancel·la",
"doLogOutAllSessions": "Desconnecta de totes les sessions",
"doRemove": "Elimina",
"doAdd": "Afegeix",
"doSignOut": "Desconnectar",
"editAccountHtmlTitle": "Edita compte",
"federatedIdentitiesHtmlTitle": "Identitats federades",
"accountLogHtmlTitle": "Registre del compte",
"changePasswordHtmlTitle": "Canvia contrasenya",
"sessionsHtmlTitle": "Sessions",
"accountManagementTitle": "Gestió de Compte Keycloak",
"authenticatorTitle": "Autenticador",
"applicationsHtmlTitle": "Aplicacions",
"authenticatorCode": "Codi d'un sol ús",
"email": "Email",
"firstName": "Nom",
"givenName": "Nom de pila",
"fullName": "Nom complet",
"lastName": "Cognoms",
"familyName": "Cognom",
"password": "Contrasenya",
"passwordConfirm": "Confirma la contrasenya",
"passwordNew": "Nova contrasenya",
"username": "Usuari",
"address": "Adreça",
"street": "Carrer",
"locality": "Ciutat o Municipi",
"region": "Estat, Província, o Regió",
"postal_code": "Postal code",
"country": "País",
"emailVerified": "Email verificat",
"gssDelegationCredential": "GSS Delegation Credential",
"role_admin": "Administrador",
"role_realm-admin": "Administrador del domini",
"role_create-realm": "Crear domini",
"role_view-realm": "Veure domini",
"role_view-users": "Veure usuaris",
"role_view-applications": "Veure aplicacions",
"role_view-clients": "Veure clients",
"role_view-events": "Veure events",
"role_view-identity-providers": "Veure proveïdors d'identitat",
"role_manage-realm": "Gestionar domini",
"role_manage-users": "Gestinar usuaris",
"role_manage-applications": "Gestionar aplicacions",
"role_manage-identity-providers": "Gestionar proveïdors d'identitat",
"role_manage-clients": "Gestionar clients",
"role_manage-events": "Gestionar events",
"role_view-profile": "Veure perfil",
"role_manage-account": "Gestionar compte",
"role_read-token": "Llegir token",
"role_offline-access": "Accés sense connexió",
"client_account": "Compte",
"client_security-admin-console": "Consola d'Administració de Seguretat",
"client_realm-management": "Gestió de domini",
"client_broker": "Broker",
"requiredFields": "Camps obligatoris",
"allFieldsRequired": "Tots els camps obligatoris",
"backToApplication": "&laquo; Torna a l'aplicació",
"backTo": "Torna a {0}",
"date": "Data",
"event": "Event",
"ip": "IP",
"client": "Client",
"clients": "Clients",
"details": "Detalls",
"started": "Iniciat",
"lastAccess": "Últim accés",
"expires": "Expira",
"applications": "Aplicacions",
"account": "Compte",
"federatedIdentity": "Identitat federada",
"authenticator": "Autenticador",
"sessions": "Sessions",
"log": "Registre",
"application": "Aplicació",
"availablePermissions": "Permisos disponibles",
"grantedPermissions": "Permisos concedits",
"grantedPersonalInfo": "Informació personal concedida",
"additionalGrants": "Permisos addicionals",
"action": "Acció",
"inResource": "a",
"fullAccess": "Accés total",
"offlineToken": "Codi d'autorització offline",
"revoke": "Revocar permís",
"configureAuthenticators": "Autenticadors configurats",
"mobile": "Mòbil",
"totpStep1":
'Instal·la <a href="https://freeotp.github.io/" target="_blank">FreeOTP</a> o Google Authenticator al teu telèfon mòbil. Les dues aplicacions estan disponibles a <a href="https://play.google.com">Google Play</a> i en l\'App Store d\'Apple.',
"totpStep2": "Obre l'aplicació i escaneja el codi o introdueix la clau.",
"totpStep3": "Introdueix el codi únic que et mostra l'aplicació d'autenticació i fes clic a Envia per finalitzar la configuració",
"missingUsernameMessage": "Si us plau indica el teu usuari.",
"missingFirstNameMessage": "Si us plau indica el nom.",
"invalidEmailMessage": "Email no vàlid",
"missingLastNameMessage": "Si us plau indica els teus cognoms.",
"missingEmailMessage": "Si us plau indica l'email.",
"missingPasswordMessage": "Si us plau indica la contrasenya.",
"notMatchPasswordMessage": "Les contrasenyes no coincideixen.",
"missingTotpMessage": "Si us plau indica el teu codi d'autenticació",
"invalidPasswordExistingMessage": "La contrasenya actual no és correcta.",
"invalidPasswordConfirmMessage": "La confirmació de contrasenya no coincideix.",
"invalidTotpMessage": "El código de autenticación no es válido.",
"usernameExistsMessage": "L'usuari ja existeix",
"emailExistsMessage": "L'email ja existeix",
"readOnlyUserMessage": "No pots actualitzar el teu usuari perquè el teu compte és de només lectura.",
"readOnlyPasswordMessage": "No pots actualitzar la contrasenya perquè el teu compte és de només lectura.",
"successTotpMessage": "Aplicació d'autenticació mòbil configurada.",
"successTotpRemovedMessage": "Aplicació d'autenticació mòbil eliminada.",
"successGrantRevokedMessage": "Permís revocat correctament",
"accountUpdatedMessage": "El teu compte s'ha actualitzat.",
"accountPasswordUpdatedMessage": "La contrasenya s'ha actualitzat.",
"missingIdentityProviderMessage": "Proveïdor d'identitat no indicat.",
"invalidFederatedIdentityActionMessage": "Acció no vàlida o no indicada.",
"identityProviderNotFoundMessage": "No s'ha trobat un proveïdor d'identitat.",
"federatedIdentityLinkNotActiveMessage": "Aquesta identitat ja no està activa",
"federatedIdentityRemovingLastProviderMessage": "No pots eliminar l'última identitat federada perquè no tens fixada una contrasenya.",
"identityProviderRedirectErrorMessage": "Error en la redirecció al proveïdor d'identitat",
"identityProviderRemovedMessage": "Proveïdor d'identitat esborrat correctament.",
"accountDisabledMessage": "El compte està desactivada, contacteu amb l'administrador.",
"accountTemporarilyDisabledMessage": "El compte està temporalment desactivat, contacta amb l'administrador o intenta-ho de nou més tard.",
"invalidPasswordMinLengthMessage": "Contrasenya incorrecta: longitud mínima {0}.",
"invalidPasswordMinLowerCaseCharsMessage": "Contrasenya incorrecta: ha de contenir almenys {0} lletres minúscules.",
"invalidPasswordMinDigitsMessage": "Contraseña incorrecta: debe contener al menos {0} caracteres numéricos.",
"invalidPasswordMinUpperCaseCharsMessage": "Contrasenya incorrecta: ha de contenir almenys {0} lletres majúscules.",
"invalidPasswordMinSpecialCharsMessage": "Contrasenya incorrecta: ha de contenir almenys {0} caràcters especials.",
"invalidPasswordNotUsernameMessage": "Contrasenya incorrecta: no pot ser igual al nom d'usuari.",
"invalidPasswordRegexPatternMessage": "Contrasenya incorrecta: no compleix l'expressió regular.",
"invalidPasswordHistoryMessage": "Contrasenya incorrecta: no pot ser igual a cap de les últimes {0} contrasenyes.",
};
export default messages;
/* spell-checker: enable */

View File

@ -1,156 +0,0 @@
//This code was automatically generated by running dist/bin/generate-i18n-messages.js
//PLEASE DO NOT EDIT MANUALLY
/* spell-checker: disable */
const messages = {
"doSave": "Uložit",
"doCancel": "Zrušit",
"doLogOutAllSessions": "Odhlásit všechny relace",
"doRemove": "Odstranit",
"doAdd": "Přidat",
"doSignOut": "Odhlásit se",
"editAccountHtmlTitle": "Upravit účet",
"federatedIdentitiesHtmlTitle": "Propojené identity",
"accountLogHtmlTitle": "Log účtu",
"changePasswordHtmlTitle": "Změnit heslo",
"sessionsHtmlTitle": "Relace",
"accountManagementTitle": "Správa účtů Keycloak",
"authenticatorTitle": "Autentizátor",
"applicationsHtmlTitle": "Aplikace",
"authenticatorCode": "Jednorázový kód",
"email": "E-mail",
"firstName": "První křestní jméno",
"givenName": "Křestní jména",
"fullName": "Celé jméno",
"lastName": "Příjmení",
"familyName": "Rodinné jméno",
"password": "Heslo",
"passwordConfirm": "Nové heslo (znovu)",
"passwordNew": "Nové heslo",
"username": "Uživatelské jméno",
"address": "Adresa",
"street": "Ulice",
"locality": "Město nebo lokalita",
"region": "Kraj",
"postal_code": "PSČ",
"country": "Stát",
"emailVerified": "E-mail ověřen",
"gssDelegationCredential": "GSS delegované oprávnění",
"role_admin": "Správce",
"role_realm-admin": "Správce realmu",
"role_create-realm": "Vytvořit realm",
"role_view-realm": "Zobrazit realm",
"role_view-users": "Zobrazit uživatele",
"role_view-applications": "Zobrazit aplikace",
"role_view-clients": "Zobrazit klienty",
"role_view-events": "Zobrazit události",
"role_view-identity-providers": "Zobrazit poskytovatele identity",
"role_manage-realm": "Spravovat realm",
"role_manage-users": "Spravovat uživatele",
"role_manage-applications": "Spravovat aplikace",
"role_manage-identity-providers": "Spravovat poskytovatele identity",
"role_manage-clients": "Spravovat klienty",
"role_manage-events": "Spravovat události",
"role_view-profile": "Zobrazit profil",
"role_manage-account": "Spravovat účet",
"role_manage-account-links": "Spravovat odkazy na účet",
"role_read-token": "Číst token",
"role_offline-access": "Přístup offline",
"role_uma_authorization": "Získání oprávnění",
"client_account": "Účet",
"client_security-admin-console": "Administrátorská bezpečnostní konzole",
"client_admin-cli": "Administrátorské CLI",
"client_realm-management": "Správa realmů",
"client_broker": "Broker",
"requiredFields": "Požadovaná pole",
"allFieldsRequired": "Všechna pole vyžadovaná",
"backToApplication": "&laquo; Zpět na aplikaci",
"backTo": "Zpět na {0}",
"date": "Datum",
"event": "Událost",
"ip": "IP",
"client": "Klient",
"clients": "Klienti",
"details": "Podrobnosti",
"started": "Zahájeno",
"lastAccess": "Poslední přístup",
"expires": "Vyprší",
"applications": "Aplikace",
"account": "Účet",
"federatedIdentity": "Propojená identita",
"authenticator": "Autentizátor",
"sessions": "Relace",
"log": "Log",
"application": "Aplikace",
"availablePermissions": "Dostupná oprávnění",
"grantedPermissions": "Udělené oprávnění",
"grantedPersonalInfo": "Poskytnuté osobní informace",
"additionalGrants": "Dodatečné oprávnění",
"action": "Akce",
"inResource": "v",
"fullAccess": "Úplný přístup",
"offlineToken": "Offline Token",
"revoke": "Zrušit oprávnění",
"configureAuthenticators": "Konfigurované autentizátory",
"mobile": "Mobilní",
"totpStep1": "Nainstalujte jednu z následujících aplikací",
"totpStep2": "Otevřete aplikaci a naskenujte čárový kód",
"totpStep3": "Zadejte jednorázový kód poskytnutý aplikací a klepnutím na tlačítko Uložit dokončete nastavení.",
"totpManualStep2": "Otevřete aplikaci a zadejte klíč",
"totpManualStep3": "Použijte následující hodnoty konfigurace, pokud aplikace umožňuje jejich nastavení",
"totpUnableToScan": "Nelze skenovat?",
"totpScanBarcode": "Skenovat čárový kód?",
"totp.totp": "Založeno na čase",
"totp.hotp": "Založeno na čítači",
"totpType": "Typ",
"totpAlgorithm": "Algoritmus",
"totpDigits": "Číslice",
"totpInterval": "Interval",
"totpCounter": "Čítač",
"missingUsernameMessage": "Zadejte uživatelské jméno.",
"missingFirstNameMessage": "Zadejte prosím křestní jméno.",
"invalidEmailMessage": "Neplatná e-mailová adresa.",
"missingLastNameMessage": "Zadejte prosím příjmení.",
"missingEmailMessage": "Zadejte prosím e-mail.",
"missingPasswordMessage": "Zadejte prosím heslo.",
"notMatchPasswordMessage": "Hesla se neshodují.",
"missingTotpMessage": "Zadejte prosím kód autentizátoru.",
"invalidPasswordExistingMessage": "Neplatné stávající heslo.",
"invalidPasswordConfirmMessage": "Nová hesla se neshodují.",
"invalidTotpMessage": "Neplatný kód autentizátoru.",
"usernameExistsMessage": "Uživatelské jméno již existuje.",
"emailExistsMessage": "E-mail již existuje.",
"readOnlyUserMessage": "Nemůžete svůj účet aktualizovat, protože je pouze pro čtení.",
"readOnlyUsernameMessage": "Nemůžete aktualizovat své uživatelské jméno, protože je pouze pro čtení.",
"readOnlyPasswordMessage": "Nemůžete aktualizovat své heslo, protože váš účet je jen pro čtení.",
"successTotpMessage": "Ověření pomocí OTP úspěšně konfigurováno.",
"successTotpRemovedMessage": "Ověření pomocí OTP úspěšně odstraněno.",
"successGrantRevokedMessage": "Oprávnění bylo úspěšně zrušeno.",
"accountUpdatedMessage": "Váš účet byl aktualizován.",
"accountPasswordUpdatedMessage": "Vaše heslo bylo aktualizováno.",
"missingIdentityProviderMessage": "Chybějící poskytovatel identity.",
"invalidFederatedIdentityActionMessage": "Neplatná nebo chybějící akce.",
"identityProviderNotFoundMessage": "Poskytovatel identity nenalezen.",
"federatedIdentityLinkNotActiveMessage": "Tato identita již není aktivní.",
"federatedIdentityRemovingLastProviderMessage": "Nemůžete odstranit poslední propojenou identitu, protože nemáte heslo.",
"identityProviderRedirectErrorMessage": "Nepodařilo se přesměrovat na poskytovatele identity.",
"identityProviderRemovedMessage": "Poskytovatel identity byl úspěšně odstraněn.",
"identityProviderAlreadyLinkedMessage": "Propojená identita vrácená uživatelem {0} je již propojena s jiným uživatelem.",
"staleCodeAccountMessage": "Platnost vypršela. Zkuste to ještě jednou.",
"consentDenied": "Souhlas byl zamítnut.",
"accountDisabledMessage": "Účet je zakázán, kontaktujte správce.",
"accountTemporarilyDisabledMessage": "Účet je dočasně zakázán, kontaktujte správce nebo zkuste to později.",
"invalidPasswordMinLengthMessage": "Neplatné heslo: musí obsahovat minimálně {0} malých znaků.",
"invalidPasswordMinLowerCaseCharsMessage": "Neplatné heslo: musí obsahovat minimálně {0} malé znaky.",
"invalidPasswordMinDigitsMessage": "Neplatné heslo: musí obsahovat nejméně {0} číslic.",
"invalidPasswordMinUpperCaseCharsMessage": "Neplatné heslo: musí obsahovat nejméně {0} velkých písmenen.",
"invalidPasswordMinSpecialCharsMessage": "Neplatné heslo: musí obsahovat nejméně {0} speciálních znaků.",
"invalidPasswordNotUsernameMessage": "Neplatné heslo: nesmí být totožné s uživatelským jménem.",
"invalidPasswordRegexPatternMessage": "Neplatné heslo: neshoduje se zadaným regulárním výrazem.",
"invalidPasswordHistoryMessage": "Neplatné heslo: Nesmí se opakovat žádné z posledních {0} hesel.",
"invalidPasswordBlacklistedMessage": "Neplatné heslo: heslo je na černé listině.",
"invalidPasswordGenericMessage": "Neplatné heslo: nové heslo neodpovídá pravidlům hesla.",
};
export default messages;
/* spell-checker: enable */

View File

@ -1,156 +0,0 @@
//This code was automatically generated by running dist/bin/generate-i18n-messages.js
//PLEASE DO NOT EDIT MANUALLY
/* spell-checker: disable */
const messages = {
"doSave": "Speichern",
"doCancel": "Abbrechen",
"doLogOutAllSessions": "Alle Sitzungen abmelden",
"doRemove": "Entfernen",
"doAdd": "Hinzufügen",
"doSignOut": "Abmelden",
"editAccountHtmlTitle": "Benutzerkonto bearbeiten",
"federatedIdentitiesHtmlTitle": "Föderierte Identitäten",
"accountLogHtmlTitle": "Benutzerkonto Log",
"changePasswordHtmlTitle": "Passwort Ändern",
"sessionsHtmlTitle": "Sitzungen",
"accountManagementTitle": "Keycloak Benutzerkontoverwaltung",
"authenticatorTitle": "Mehrfachauthentifizierung",
"applicationsHtmlTitle": "Applikationen",
"authenticatorCode": "One-time Code",
"email": "E-Mail",
"firstName": "Vorname",
"givenName": "Vorname",
"fullName": "Voller Name",
"lastName": "Nachname",
"familyName": "Nachname",
"password": "Passwort",
"passwordConfirm": "Passwort bestätigen",
"passwordNew": "Neues Passwort",
"username": "Benutzername",
"address": "Adresse",
"street": "Straße",
"region": "Staat, Provinz, Region",
"postal_code": "PLZ",
"locality": "Stadt oder Ortschaft",
"country": "Land",
"emailVerified": "E-Mail verifiziert",
"gssDelegationCredential": "GSS delegierte Berechtigung",
"role_admin": "Admin",
"role_realm-admin": "Realm Admin",
"role_create-realm": "Realm erstellen",
"role_view-realm": "Realm ansehen",
"role_view-users": "Benutzer ansehen",
"role_view-applications": "Applikationen ansehen",
"role_view-clients": "Clients ansehen",
"role_view-events": "Events ansehen",
"role_view-identity-providers": "Identity Provider ansehen",
"role_manage-realm": "Realm verwalten",
"role_manage-users": "Benutzer verwalten",
"role_manage-applications": "Applikationen verwalten",
"role_manage-identity-providers": "Identity Provider verwalten",
"role_manage-clients": "Clients verwalten",
"role_manage-events": "Events verwalten",
"role_view-profile": "Profile ansehen",
"role_manage-account": "Profile verwalten",
"role_manage-account-links": "Profil-Links verwalten",
"role_read-token": "Token lesen",
"role_offline-access": "Offline-Zugriff",
"role_uma_authorization": "Berechtigungen einholen",
"client_account": "Clientkonto",
"client_security-admin-console": "Security Adminkonsole",
"client_realm-management": "Realm-Management",
"client_broker": "Broker",
"requiredFields": "Erforderliche Felder",
"allFieldsRequired": "Alle Felder sind erforderlich",
"backToApplication": "&laquo; Zurück zur Applikation",
"backTo": "Zurück zu {0}",
"date": "Datum",
"event": "Ereignis",
"ip": "IP",
"client": "Client",
"clients": "Clients",
"details": "Details",
"started": "Startdatum",
"lastAccess": "Letzter Zugriff",
"expires": "Ablaufdatum",
"applications": "Applikationen",
"account": "Benutzerkonto",
"federatedIdentity": "Föderierte Identität",
"authenticator": "Mehrfachauthentifizierung",
"sessions": "Sitzungen",
"log": "Log",
"application": "Applikation",
"availablePermissions": "verfügbare Berechtigungen",
"grantedPermissions": "gewährte Berechtigungen",
"grantedPersonalInfo": "gewährte persönliche Informationen",
"additionalGrants": "zusätzliche Berechtigungen",
"action": "Aktion",
"inResource": "in",
"fullAccess": "Vollzugriff",
"offlineToken": "Offline-Token",
"revoke": "Berechtigung widerrufen",
"configureAuthenticators": "Mehrfachauthentifizierung konfigurieren",
"mobile": "Mobil",
"totpStep1": "Installieren Sie eine der folgenden Applikationen auf Ihrem Smartphone:",
"totpStep2": "Öffnen Sie die Applikation und scannen Sie den Barcode.",
"totpStep3": "Geben Sie den von der Applikation generierten One-time Code ein und klicken Sie auf Speichern.",
"totpManualStep2": "Öffnen Sie die Applikation und geben Sie den folgenden Schlüssel ein.",
"totpManualStep3": "Verwenden Sie die folgenden Konfigurationswerte, falls Sie diese für die Applikation anpassen können:",
"totpUnableToScan": "Sie können den Barcode nicht scannen?",
"totpScanBarcode": "Barcode scannen?",
"totp.totp": "zeitbasiert (time-based)",
"totp.hotp": "zählerbasiert (counter-based)",
"totpType": "Typ",
"totpAlgorithm": "Algorithmus",
"totpDigits": "Ziffern",
"totpInterval": "Intervall",
"totpCounter": "Zähler",
"missingUsernameMessage": "Bitte geben Sie einen Benutzernamen ein.",
"missingFirstNameMessage": "Bitte geben Sie einen Vornamen ein.",
"invalidEmailMessage": "Ungültige E-Mail Adresse.",
"missingLastNameMessage": "Bitte geben Sie einen Nachnamen ein.",
"missingEmailMessage": "Bitte geben Sie eine E-Mail Adresse ein.",
"missingPasswordMessage": "Bitte geben Sie ein Passwort ein.",
"notMatchPasswordMessage": "Die Passwörter sind nicht identisch.",
"missingTotpMessage": "Bitte geben Sie den One-time Code ein.",
"invalidPasswordExistingMessage": "Das aktuelle Passwort ist ungültig.",
"invalidPasswordConfirmMessage": "Die Passwortbestätigung ist nicht identisch.",
"invalidTotpMessage": "Ungültiger One-time Code.",
"usernameExistsMessage": "Der Benutzername existiert bereits.",
"emailExistsMessage": "Die E-Mail-Adresse existiert bereits.",
"readOnlyUserMessage": "Sie können Ihr Benutzerkonto nicht ändern, da es schreibgeschützt ist.",
"readOnlyUsernameMessage": "Sie können Ihren Benutzernamen nicht ändern, da er schreibgeschützt ist.",
"readOnlyPasswordMessage": "Sie können Ihr Passwort nicht ändern, da es schreibgeschützt ist.",
"successTotpMessage": "Mehrfachauthentifizierung erfolgreich konfiguriert.",
"successTotpRemovedMessage": "Mehrfachauthentifizierung erfolgreich entfernt.",
"successGrantRevokedMessage": "Berechtigung erfolgreich widerrufen.",
"accountUpdatedMessage": "Ihr Benutzerkonto wurde aktualisiert.",
"accountPasswordUpdatedMessage": "Ihr Passwort wurde aktualisiert.",
"missingIdentityProviderMessage": "Identity Provider nicht angegeben.",
"invalidFederatedIdentityActionMessage": "Ungültige oder fehlende Aktion.",
"identityProviderNotFoundMessage": "Angegebener Identity Provider nicht gefunden.",
"federatedIdentityLinkNotActiveMessage": "Diese Identität ist nicht mehr aktiv.",
"federatedIdentityRemovingLastProviderMessage": "Sie können den letzten Eintrag nicht entfernen, da Sie kein Passwort haben.",
"identityProviderRedirectErrorMessage": "Fehler bei der Weiterleitung zum Identity Provider.",
"identityProviderRemovedMessage": "Identity Provider erfolgreich entfernt.",
"identityProviderAlreadyLinkedMessage": "Die föderierte Identität von {0} ist bereits einem anderen Benutzer zugewiesen.",
"staleCodeAccountMessage": "Diese Seite ist nicht mehr gültig, bitte versuchen Sie es noch einmal.",
"consentDenied": "Einverständnis verweigert.",
"accountDisabledMessage": "Ihr Benutzerkonto ist gesperrt, bitte kontaktieren Sie den Admin.",
"accountTemporarilyDisabledMessage":
"Ihr Benutzerkonto ist temporär gesperrt, bitte kontaktieren Sie den Admin oder versuchen Sie es später noch einmal.",
"invalidPasswordMinLengthMessage": "Ungültiges Passwort: Es muss mindestens {0} Zeichen lang sein.",
"invalidPasswordMinLowerCaseCharsMessage": "Ungültiges Passwort: Es muss mindestens {0} Kleinbuchstaben beinhalten.",
"invalidPasswordMinDigitsMessage": "Ungültiges Passwort: Es muss mindestens {0} Zahl(en) beinhalten.",
"invalidPasswordMinUpperCaseCharsMessage": "Ungültiges Passwort: Es muss mindestens {0} Großbuchstaben beinhalten.",
"invalidPasswordMinSpecialCharsMessage": "Ungültiges Passwort: Es muss mindestens {0} Sonderzeichen beinhalten.",
"invalidPasswordNotUsernameMessage": "Ungültiges Passwort: Es darf nicht gleich sein wie der Benutzername.",
"invalidPasswordRegexPatternMessage": "Ungültiges Passwort: Es entspricht nicht dem Regex-Muster.",
"invalidPasswordHistoryMessage": "Ungültiges Passwort: Es darf nicht einem der letzten {0} Passwörter entsprechen.",
"invalidPasswordBlacklistedMessage": "Ungültiges Passwort: Das Passwort steht auf der Blocklist (schwarzen Liste).",
"invalidPasswordGenericMessge": "Ungültiges Passwort: Das neue Passwort verletzt die Passwort-Richtlinien.",
};
export default messages;
/* spell-checker: enable */

View File

@ -1,325 +0,0 @@
//This code was automatically generated by running dist/bin/generate-i18n-messages.js
//PLEASE DO NOT EDIT MANUALLY
/* spell-checker: disable */
const messages = {
"doSave": "Save",
"doCancel": "Cancel",
"doLogOutAllSessions": "Log out all sessions",
"doRemove": "Remove",
"doAdd": "Add",
"doSignOut": "Sign Out",
"doLogIn": "Log In",
"doLink": "Link",
"editAccountHtmlTitle": "Edit Account",
"personalInfoHtmlTitle": "Personal Info",
"federatedIdentitiesHtmlTitle": "Federated Identities",
"accountLogHtmlTitle": "Account Log",
"changePasswordHtmlTitle": "Change Password",
"deviceActivityHtmlTitle": "Device Activity",
"sessionsHtmlTitle": "Sessions",
"accountManagementTitle": "Keycloak Account Management",
"authenticatorTitle": "Authenticator",
"applicationsHtmlTitle": "Applications",
"linkedAccountsHtmlTitle": "Linked Accounts",
"accountManagementWelcomeMessage": "Welcome to Keycloak Account Management",
"personalInfoIntroMessage": "Manage your basic information",
"accountSecurityTitle": "Account Security",
"accountSecurityIntroMessage": "Control your password and account access",
"applicationsIntroMessage": "Track and manage your app permission to access your account",
"resourceIntroMessage": "Share your resources among team members",
"passwordLastUpdateMessage": "Your password was updated at",
"updatePasswordTitle": "Update Password",
"updatePasswordMessageTitle": "Make sure you choose a strong password",
"updatePasswordMessage":
"A strong password contains a mix of numbers, letters, and symbols. It is hard to guess, does not resemble a real word, and is only used for this account.",
"personalSubTitle": "Your Personal Info",
"personalSubMessage": "Manage this basic information: your first name, last name and email",
"authenticatorCode": "One-time code",
"email": "Email",
"firstName": "First name",
"givenName": "Given name",
"fullName": "Full name",
"lastName": "Last name",
"familyName": "Family name",
"password": "Password",
"currentPassword": "Current Password",
"passwordConfirm": "Confirmation",
"passwordNew": "New Password",
"username": "Username",
"address": "Address",
"street": "Street",
"locality": "City or Locality",
"region": "State, Province, or Region",
"postal_code": "Zip or Postal code",
"country": "Country",
"emailVerified": "Email verified",
"gssDelegationCredential": "GSS Delegation Credential",
"profileScopeConsentText": "User profile",
"emailScopeConsentText": "Email address",
"addressScopeConsentText": "Address",
"phoneScopeConsentText": "Phone number",
"offlineAccessScopeConsentText": "Offline Access",
"samlRoleListScopeConsentText": "My Roles",
"rolesScopeConsentText": "User roles",
"role_admin": "Admin",
"role_realm-admin": "Realm Admin",
"role_create-realm": "Create realm",
"role_view-realm": "View realm",
"role_view-users": "View users",
"role_view-applications": "View applications",
"role_view-clients": "View clients",
"role_view-events": "View events",
"role_view-identity-providers": "View identity providers",
"role_view-consent": "View consents",
"role_manage-realm": "Manage realm",
"role_manage-users": "Manage users",
"role_manage-applications": "Manage applications",
"role_manage-identity-providers": "Manage identity providers",
"role_manage-clients": "Manage clients",
"role_manage-events": "Manage events",
"role_view-profile": "View profile",
"role_manage-account": "Manage account",
"role_manage-account-links": "Manage account links",
"role_manage-consent": "Manage consents",
"role_read-token": "Read token",
"role_offline-access": "Offline access",
"role_uma_authorization": "Obtain permissions",
"client_account": "Account",
"client_account-console": "Account Console",
"client_security-admin-console": "Security Admin Console",
"client_admin-cli": "Admin CLI",
"client_realm-management": "Realm Management",
"client_broker": "Broker",
"requiredFields": "Required fields",
"allFieldsRequired": "All fields required",
"backToApplication": "&laquo; Back to application",
"backTo": "Back to {0}",
"date": "Date",
"event": "Event",
"ip": "IP",
"client": "Client",
"clients": "Clients",
"details": "Details",
"started": "Started",
"lastAccess": "Last Access",
"expires": "Expires",
"applications": "Applications",
"account": "Account",
"federatedIdentity": "Federated Identity",
"authenticator": "Authenticator",
"device-activity": "Device Activity",
"sessions": "Sessions",
"log": "Log",
"application": "Application",
"availableRoles": "Available Roles",
"grantedPermissions": "Granted Permissions",
"grantedPersonalInfo": "Granted Personal Info",
"additionalGrants": "Additional Grants",
"action": "Action",
"inResource": "in",
"fullAccess": "Full Access",
"offlineToken": "Offline Token",
"revoke": "Revoke Grant",
"configureAuthenticators": "Configured Authenticators",
"mobile": "Mobile",
"totpStep1": "Install one of the following applications on your mobile:",
"totpStep2": "Open the application and scan the barcode:",
"totpStep3": "Enter the one-time code provided by the application and click Save to finish the setup.",
"totpStep3DeviceName": "Provide a Device Name to help you manage your OTP devices.",
"totpManualStep2": "Open the application and enter the key:",
"totpManualStep3": "Use the following configuration values if the application allows setting them:",
"totpUnableToScan": "Unable to scan?",
"totpScanBarcode": "Scan barcode?",
"totp.totp": "Time-based",
"totp.hotp": "Counter-based",
"totpType": "Type",
"totpAlgorithm": "Algorithm",
"totpDigits": "Digits",
"totpInterval": "Interval",
"totpCounter": "Counter",
"totpDeviceName": "Device Name",
"missingUsernameMessage": "Please specify username.",
"missingFirstNameMessage": "Please specify first name.",
"invalidEmailMessage": "Invalid email address.",
"missingLastNameMessage": "Please specify last name.",
"missingEmailMessage": "Please specify email.",
"missingPasswordMessage": "Please specify password.",
"notMatchPasswordMessage": "Passwords don't match.",
"invalidUserMessage": "Invalid user",
"missingTotpMessage": "Please specify authenticator code.",
"missingTotpDeviceNameMessage": "Please specify device name.",
"invalidPasswordExistingMessage": "Invalid existing password.",
"invalidPasswordConfirmMessage": "Password confirmation doesn't match.",
"invalidTotpMessage": "Invalid authenticator code.",
"usernameExistsMessage": "Username already exists.",
"emailExistsMessage": "Email already exists.",
"readOnlyUserMessage": "You can't update your account as it is read-only.",
"readOnlyUsernameMessage": "You can't update your username as it is read-only.",
"readOnlyPasswordMessage": "You can't update your password as your account is read-only.",
"successTotpMessage": "Mobile authenticator configured.",
"successTotpRemovedMessage": "Mobile authenticator removed.",
"successGrantRevokedMessage": "Grant revoked successfully.",
"accountUpdatedMessage": "Your account has been updated.",
"accountPasswordUpdatedMessage": "Your password has been updated.",
"missingIdentityProviderMessage": "Identity provider not specified.",
"invalidFederatedIdentityActionMessage": "Invalid or missing action.",
"identityProviderNotFoundMessage": "Specified identity provider not found.",
"federatedIdentityLinkNotActiveMessage": "This identity is not active anymore.",
"federatedIdentityRemovingLastProviderMessage": "You can't remove last federated identity as you don't have a password.",
"identityProviderRedirectErrorMessage": "Failed to redirect to identity provider.",
"identityProviderRemovedMessage": "Identity provider removed successfully.",
"identityProviderAlreadyLinkedMessage": "Federated identity returned by {0} is already linked to another user.",
"staleCodeAccountMessage": "The page expired. Please try one more time.",
"consentDenied": "Consent denied.",
"accountDisabledMessage": "Account is disabled, contact your administrator.",
"accountTemporarilyDisabledMessage": "Account is temporarily disabled, contact your administrator or try again later.",
"invalidPasswordMinLengthMessage": "Invalid password: minimum length {0}.",
"invalidPasswordMinLowerCaseCharsMessage": "Invalid password: must contain at least {0} lower case characters.",
"invalidPasswordMinDigitsMessage": "Invalid password: must contain at least {0} numerical digits.",
"invalidPasswordMinUpperCaseCharsMessage": "Invalid password: must contain at least {0} upper case characters.",
"invalidPasswordMinSpecialCharsMessage": "Invalid password: must contain at least {0} special characters.",
"invalidPasswordNotUsernameMessage": "Invalid password: must not be equal to the username.",
"invalidPasswordRegexPatternMessage": "Invalid password: fails to match regex pattern(s).",
"invalidPasswordHistoryMessage": "Invalid password: must not be equal to any of last {0} passwords.",
"invalidPasswordBlacklistedMessage": "Invalid password: password is blacklisted.",
"invalidPasswordGenericMessage": "Invalid password: new password doesn't match password policies.",
"myResources": "My Resources",
"myResourcesSub": "My resources",
"doDeny": "Deny",
"doRevoke": "Revoke",
"doApprove": "Approve",
"doRemoveSharing": "Remove Sharing",
"doRemoveRequest": "Remove Request",
"peopleAccessResource": "People with access to this resource",
"resourceManagedPolicies": "Permissions granting access to this resource",
"resourceNoPermissionsGrantingAccess": "No permissions granting access to this resource",
"anyAction": "Any action",
"description": "Description",
"name": "Name",
"scopes": "Scopes",
"resource": "Resource",
"user": "User",
"peopleSharingThisResource": "People sharing this resource",
"shareWithOthers": "Share with others",
"needMyApproval": "Need my approval",
"requestsWaitingApproval": "Your requests waiting approval",
"icon": "Icon",
"requestor": "Requestor",
"owner": "Owner",
"resourcesSharedWithMe": "Resources shared with me",
"permissionRequestion": "Permission Requestion",
"permission": "Permission",
"shares": "share(s)",
"notBeingShared": "This resource is not being shared.",
"notHaveAnyResource": "You don't have any resources",
"noResourcesSharedWithYou": "There are no resources shared with you",
"havePermissionRequestsWaitingForApproval": "You have {0} permission request(s) waiting for approval.",
"clickHereForDetails": "Click here for details.",
"resourceIsNotBeingShared": "The resource is not being shared",
"locale_ca": "Català",
"locale_cs": "Čeština",
"locale_de": "Deutsch",
"locale_en": "English",
"locale_es": "Español",
"locale_fr": "Français",
"locale_it": "Italian",
"locale_ja": "日本語",
"locale_nl": "Nederlands",
"locale_no": "Norsk",
"locale_lt": "Lietuvių",
"locale_pt-BR": "Português (Brasil)",
"locale_ru": "Русский",
"locale_sk": "Slovenčina",
"locale_sv": "Svenska",
"locale_tr": "Turkish",
"locale_zh-CN": "中文简体",
"applicaitonName": "Name",
"applicationType": "Application Type",
"applicationInUse": "In-use app only",
"clearAllFilter": "Clear all filters",
"activeFilters": "Active filters",
"filterByName": "Filter By Name ...",
"allApps": "All applications",
"internalApps": "Internal applications",
"thirdpartyApps": "Third-Party applications",
"appResults": "Results",
"clientNotFoundMessage": "Client not found.",
"authorizedProvider": "Authorized Provider",
"authorizedProviderMessage": "Authorized Providers linked with your account",
"identityProvider": "Identity Provider",
"identityProviderMessage": "To link your account with identity providers you have configured",
"socialLogin": "Social Login",
"userDefined": "User Defined",
"removeAccess": "Remove Access",
"removeAccessMessage": "You will need to grant access again, if you want to use this app account.",
"authenticatorStatusMessage": "Two-factor authentication is currently",
"authenticatorFinishSetUpTitle": "Your Two-Factor Authentication",
"authenticatorFinishSetUpMessage":
"Each time you sign in to your Keycloak account, you will be asked to provide a two-factor authentication code.",
"authenticatorSubTitle": "Set Up Two-Factor Authentication",
"authenticatorSubMessage": "To enhance the security of your account, enable at least one of the available two-factor authentication methods.",
"authenticatorMobileTitle": "Mobile Authenticator",
"authenticatorMobileMessage": "Use mobile Authenticator to get Verification codes as the two-factor authentication.",
"authenticatorMobileFinishSetUpMessage": "The authenticator has been bound to your phone.",
"authenticatorActionSetup": "Set up",
"authenticatorSMSTitle": "SMS Code",
"authenticatorSMSMessage": "Keycloak will send the Verification code to your phone as the two-factor authentication.",
"authenticatorSMSFinishSetUpMessage": "Text messages are sent to",
"authenticatorDefaultStatus": "Default",
"authenticatorChangePhone": "Change Phone Number",
"authenticatorBackupCodesTitle": "Backup Codes",
"authenticatorBackupCodesMessage": "Get your 8-digit backup codes",
"authenticatorBackupCodesFinishSetUpMessage": "12 backup codes were generated at this time. Each one can be used once.",
"authenticatorMobileSetupTitle": "Mobile Authenticator Setup",
"smscodeIntroMessage": "Enter your phone number and a verification code will be sent to your phone.",
"mobileSetupStep1": "Install an authenticator application on your phone. The applications listed here are supported.",
"mobileSetupStep2": "Open the application and scan the barcode:",
"mobileSetupStep3": "Enter the one-time code provided by the application and click Save to finish the setup.",
"scanBarCode": "Want to scan the barcode?",
"enterBarCode": "Enter the one-time code",
"doCopy": "Copy",
"doFinish": "Finish",
"authenticatorSMSCodeSetupTitle": "SMS Code Setup",
"chooseYourCountry": "Choose your country",
"enterYourPhoneNumber": "Enter your phone number",
"sendVerficationCode": "Send Verification Code",
"enterYourVerficationCode": "Enter your verification code",
"authenticatorBackupCodesSetupTitle": "Backup Codes Setup",
"backupcodesIntroMessage":
"If you lose access to your phone, you can still log into your account through backup codes. Keep them somewhere safe and accessible.",
"realmName": "Realm",
"doDownload": "Download",
"doPrint": "Print",
"backupCodesTips-1": "Each backup code can be used once.",
"backupCodesTips-2": "These codes were generated on",
"generateNewBackupCodes": "Generate New Backup Codes",
"backupCodesTips-3": "When you generate new backup codes, the current codes will not work anymore.",
"backtoAuthenticatorPage": "Back to Authenticator Page",
"resources": "Resources",
"sharedwithMe": "Shared with Me",
"share": "Share",
"sharedwith": "Shared with",
"accessPermissions": "Access Permissions",
"permissionRequests": "Permission Requests",
"approve": "Approve",
"approveAll": "Approve all",
"people": "people",
"perPage": "per page",
"currentPage": "Current Page",
"sharetheResource": "Share the resource",
"group": "Group",
"selectPermission": "Select Permission",
"addPeople": "Add people to share your resource with",
"addTeam": "Add team to share your resource with",
"myPermissions": "My Permissions",
"waitingforApproval": "Waiting for approval",
"anyPermission": "Any Permission",
"openshift.scope.user_info": "User information",
"openshift.scope.user_check-access": "User access information",
"openshift.scope.user_full": "Full Access",
"openshift.scope.list-projects": "List projects",
};
export default messages;
/* spell-checker: enable */

View File

@ -1,137 +0,0 @@
//This code was automatically generated by running dist/bin/generate-i18n-messages.js
//PLEASE DO NOT EDIT MANUALLY
/* spell-checker: disable */
const messages = {
"doSave": "Guardar",
"doCancel": "Cancelar",
"doLogOutAllSessions": "Desconectar de todas las sesiones",
"doRemove": "Eliminar",
"doAdd": "Añadir",
"doSignOut": "Desconectar",
"editAccountHtmlTitle": "Editar cuenta",
"federatedIdentitiesHtmlTitle": "Identidades federadas",
"accountLogHtmlTitle": "Registro de la cuenta",
"changePasswordHtmlTitle": "Cambiar contraseña",
"sessionsHtmlTitle": "Sesiones",
"accountManagementTitle": "Gestión de Cuenta Keycloak",
"authenticatorTitle": "Autenticador",
"applicationsHtmlTitle": "Aplicaciones",
"authenticatorCode": "Código de un solo uso",
"email": "Email",
"firstName": "Nombre",
"givenName": "Nombre de pila",
"fullName": "Nombre completo",
"lastName": "Apellidos",
"familyName": "Apellido",
"password": "Contraseña",
"passwordConfirm": "Confirma la contraseña",
"passwordNew": "Nueva contraseña",
"username": "Usuario",
"address": "Dirección",
"street": "Calle",
"locality": "Ciudad o Municipio",
"region": "Estado, Provincia, o Región",
"postal_code": "Código Postal",
"country": "País",
"emailVerified": "Email verificado",
"gssDelegationCredential": "GSS Delegation Credential",
"role_admin": "Administrador",
"role_realm-admin": "Administrador del dominio",
"role_create-realm": "Crear dominio",
"role_view-realm": "Ver dominio",
"role_view-users": "Ver usuarios",
"role_view-applications": "Ver aplicaciones",
"role_view-clients": "Ver clientes",
"role_view-events": "Ver eventos",
"role_view-identity-providers": "Ver proveedores de identidad",
"role_manage-realm": "Gestionar dominio",
"role_manage-users": "Gestionar usuarios",
"role_manage-applications": "Gestionar aplicaciones",
"role_manage-identity-providers": "Gestionar proveedores de identidad",
"role_manage-clients": "Gestionar clientes",
"role_manage-events": "Gestionar eventos",
"role_view-profile": "Ver perfil",
"role_manage-account": "Gestionar cuenta",
"role_read-token": "Leer token",
"role_offline-access": "Acceso sin conexión",
"client_account": "Cuenta",
"client_security-admin-console": "Consola de Administración de Seguridad",
"client_realm-management": "Gestión de dominio",
"client_broker": "Broker",
"requiredFields": "Campos obligatorios",
"allFieldsRequired": "Todos los campos obligatorios",
"backToApplication": "&laquo; Volver a la aplicación",
"backTo": "Volver a {0}",
"date": "Fecha",
"event": "Evento",
"ip": "IP",
"client": "Cliente",
"clients": "Clientes",
"details": "Detalles",
"started": "Iniciado",
"lastAccess": "Último acceso",
"expires": "Expira",
"applications": "Aplicaciones",
"account": "Cuenta",
"federatedIdentity": "Identidad federada",
"authenticator": "Autenticador",
"sessions": "Sesiones",
"log": "Regisro",
"application": "Aplicación",
"availablePermissions": "Permisos disponibles",
"grantedPermissions": "Permisos concedidos",
"grantedPersonalInfo": "Información personal concedida",
"additionalGrants": "Permisos adicionales",
"action": "Acción",
"inResource": "en",
"fullAccess": "Acceso total",
"offlineToken": "Código de autorización offline",
"revoke": "Revocar permiso",
"configureAuthenticators": "Autenticadores configurados",
"mobile": "Móvil",
"totpStep1":
'Instala <a href="https://freeotp.github.io/" target="_blank">FreeOTP</a> o Google Authenticator en tu teléfono móvil. Ambas aplicaciones están disponibles en <a href="https://play.google.com">Google Play</a> y en la App Store de Apple.',
"totpStep2": "Abre la aplicación y escanea el código o introduce la clave.",
"totpStep3": "Introduce el código único que te muestra la aplicación de autenticación y haz clic en Enviar para finalizar la configuración",
"missingUsernameMessage": "Por favor indica tu usuario.",
"missingFirstNameMessage": "Por favor indica el nombre.",
"invalidEmailMessage": "Email no válido",
"missingLastNameMessage": "Por favor indica tus apellidos.",
"missingEmailMessage": "Por favor indica el email.",
"missingPasswordMessage": "Por favor indica tu contraseña.",
"notMatchPasswordMessage": "Las contraseñas no coinciden.",
"missingTotpMessage": "Por favor indica tu código de autenticación",
"invalidPasswordExistingMessage": "La contraseña actual no es correcta.",
"invalidPasswordConfirmMessage": "La confirmación de contraseña no coincide.",
"invalidTotpMessage": "El código de autenticación no es válido.",
"usernameExistsMessage": "El usuario ya existe",
"emailExistsMessage": "El email ya existe",
"readOnlyUserMessage": "No puedes actualizar tu usuario porque tu cuenta es de solo lectura.",
"readOnlyPasswordMessage": "No puedes actualizar tu contraseña porque tu cuenta es de solo lectura.",
"successTotpMessage": "Aplicación de autenticación móvil configurada.",
"successTotpRemovedMessage": "Aplicación de autenticación móvil eliminada.",
"successGrantRevokedMessage": "Permiso revocado correctamente",
"accountUpdatedMessage": "Tu cuenta se ha actualizado.",
"accountPasswordUpdatedMessage": "Tu contraseña se ha actualizado.",
"missingIdentityProviderMessage": "Proveedor de identidad no indicado.",
"invalidFederatedIdentityActionMessage": "Acción no válida o no indicada.",
"identityProviderNotFoundMessage": "No se encontró un proveedor de identidad.",
"federatedIdentityLinkNotActiveMessage": "Esta identidad ya no está activa",
"federatedIdentityRemovingLastProviderMessage": "No puedes eliminar la última identidad federada porque no tienes fijada una contraseña.",
"identityProviderRedirectErrorMessage": "Error en la redirección al proveedor de identidad",
"identityProviderRemovedMessage": "Proveedor de identidad borrado correctamente.",
"accountDisabledMessage": "La cuenta está desactivada, contacta con el administrador.",
"accountTemporarilyDisabledMessage": "La cuenta está temporalmente desactivada, contacta con el administrador o inténtalo de nuevo más tarde.",
"invalidPasswordMinLengthMessage": "Contraseña incorrecta: longitud mínima {0}.",
"invalidPasswordMinLowerCaseCharsMessage": "Contraseña incorrecta: debe contener al menos {0} letras minúsculas.",
"invalidPasswordMinDigitsMessage": "Contraseña incorrecta: debe contener al menos {0} caracteres numéricos.",
"invalidPasswordMinUpperCaseCharsMessage": "Contraseña incorrecta: debe contener al menos {0} letras mayúsculas.",
"invalidPasswordMinSpecialCharsMessage": "Contraseña incorrecta: debe contener al menos {0} caracteres especiales.",
"invalidPasswordNotUsernameMessage": "Contraseña incorrecta: no puede ser igual al nombre de usuario.",
"invalidPasswordRegexPatternMessage": "Contraseña incorrecta: no cumple la expresión regular.",
"invalidPasswordHistoryMessage": "Contraseña incorrecta: no puede ser igual a ninguna de las últimas {0} contraseñas.",
};
export default messages;
/* spell-checker: enable */

View File

@ -1,150 +0,0 @@
//This code was automatically generated by running dist/bin/generate-i18n-messages.js
//PLEASE DO NOT EDIT MANUALLY
/* spell-checker: disable */
const messages = {
"doSave": "Sauvegarder",
"doCancel": "Annuler",
"doLogOutAllSessions": "Déconnexion de toutes les sessions",
"doRemove": "Supprimer",
"doAdd": "Ajouter",
"doSignOut": "Déconnexion",
"editAccountHtmlTitle": "Édition du compte",
"federatedIdentitiesHtmlTitle": "Identités fédérées",
"accountLogHtmlTitle": "Accès au compte",
"changePasswordHtmlTitle": "Changer de mot de passe",
"sessionsHtmlTitle": "Sessions",
"accountManagementTitle": "Gestion du compte Keycloak",
"authenticatorTitle": "Authentification",
"applicationsHtmlTitle": "Applications",
"authenticatorCode": "Mot de passe unique",
"email": "Courriel",
"firstName": "Prénom",
"givenName": "Prénom",
"fullName": "Nom complet",
"lastName": "Nom",
"familyName": "Nom de famille",
"password": "Mot de passe",
"passwordConfirm": "Confirmation",
"passwordNew": "Nouveau mot de passe",
"username": "Compte",
"address": "Adresse",
"street": "Rue",
"locality": "Ville ou Localité",
"region": "État, Province ou Région",
"postal_code": "Code Postal",
"country": "Pays",
"emailVerified": "Courriel vérifié",
"gssDelegationCredential": "Accréditation de délégation GSS",
"role_admin": "Administrateur",
"role_realm-admin": "Administrateur du domaine",
"role_create-realm": "Créer un domaine",
"role_view-realm": "Voir un domaine",
"role_view-users": "Voir les utilisateurs",
"role_view-applications": "Voir les applications",
"role_view-clients": "Voir les clients",
"role_view-events": "Voir les événements",
"role_view-identity-providers": "Voir les fournisseurs d'identités",
"role_manage-realm": "Gérer le domaine",
"role_manage-users": "Gérer les utilisateurs",
"role_manage-applications": "Gérer les applications",
"role_manage-identity-providers": "Gérer les fournisseurs d'identités",
"role_manage-clients": "Gérer les clients",
"role_manage-events": "Gérer les événements",
"role_view-profile": "Voir le profil",
"role_manage-account": "Gérer le compte",
"role_read-token": "Lire le jeton d'authentification",
"role_offline-access": "Accès hors-ligne",
"client_account": "Compte",
"client_security-admin-console": "Console d'administration de la sécurité",
"client_admin-cli": "Admin CLI",
"client_realm-management": "Gestion du domaine",
"client_broker": "Broker",
"requiredFields": "Champs obligatoires",
"allFieldsRequired": "Tous les champs sont obligatoires",
"backToApplication": "&laquo; Revenir à l'application",
"backTo": "Revenir à {0}",
"date": "Date",
"event": "Evénement",
"ip": "IP",
"client": "Client",
"clients": "Clients",
"details": "Détails",
"started": "Début",
"lastAccess": "Dernier accès",
"expires": "Expiration",
"applications": "Applications",
"account": "Compte",
"federatedIdentity": "Identité fédérée",
"authenticator": "Authentification",
"sessions": "Sessions",
"log": "Connexion",
"application": "Application",
"availablePermissions": "Permissions disponibles",
"grantedPermissions": "Permissions accordées",
"grantedPersonalInfo": "Informations personnelles accordées",
"additionalGrants": "Droits additionnels",
"action": "Action",
"inResource": "dans",
"fullAccess": "Accès complet",
"offlineToken": "Jeton d'authentification hors-ligne",
"revoke": "Révoquer un droit",
"configureAuthenticators": "Authentifications configurées.",
"mobile": "Téléphone mobile",
"totpStep1": "Installez une des applications suivantes sur votre mobile",
"totpStep2": "Ouvrez l'application et scannez le code-barres ou entrez la clef.",
"totpStep3": "Entrez le code à usage unique fourni par l'application et cliquez sur Sauvegarder pour terminer.",
"totpManualStep2": "Ouvrez l'application et entrez la clef",
"totpManualStep3": "Utilisez les valeurs de configuration suivante si l'application les autorise",
"totpUnableToScan": "Impossible de scanner ?",
"totpScanBarcode": "Scanner le code-barres ?",
"totp.totp": "Basé sur le temps",
"totp.hotp": "Basé sur un compteur",
"totpType": "Type",
"totpAlgorithm": "Algorithme",
"totpDigits": "Chiffres",
"totpInterval": "Intervalle",
"totpCounter": "Compteur",
"missingUsernameMessage": "Veuillez entrer votre nom d'utilisateur.",
"missingFirstNameMessage": "Veuillez entrer votre prénom.",
"invalidEmailMessage": "Courriel invalide.",
"missingLastNameMessage": "Veuillez entrer votre nom.",
"missingEmailMessage": "Veuillez entrer votre courriel.",
"missingPasswordMessage": "Veuillez entrer votre mot de passe.",
"notMatchPasswordMessage": "Les mots de passe ne sont pas identiques",
"missingTotpMessage": "Veuillez entrer le code d'authentification.",
"invalidPasswordExistingMessage": "Mot de passe existant invalide.",
"invalidPasswordConfirmMessage": "Le mot de passe de confirmation ne correspond pas.",
"invalidTotpMessage": "Le code d'authentification est invalide.",
"usernameExistsMessage": "Le nom d'utilisateur existe déjà.",
"emailExistsMessage": "Le courriel existe déjà.",
"readOnlyUserMessage": "Vous ne pouvez pas mettre à jour votre compte car il est en lecture seule.",
"readOnlyPasswordMessage": "Vous ne pouvez pas mettre à jour votre mot de passe car votre compte est en lecture seule.",
"successTotpMessage": "L'authentification via téléphone mobile est configurée.",
"successTotpRemovedMessage": "L'authentification via téléphone mobile est supprimée.",
"successGrantRevokedMessage": "Droit révoqué avec succès.",
"accountUpdatedMessage": "Votre compte a été mis à jour.",
"accountPasswordUpdatedMessage": "Votre mot de passe a été mis à jour.",
"missingIdentityProviderMessage": "Le fournisseur d'identité n'est pas spécifié.",
"invalidFederatedIdentityActionMessage": "Action manquante ou invalide.",
"identityProviderNotFoundMessage": "Le fournisseur d'identité spécifié n'est pas trouvé.",
"federatedIdentityLinkNotActiveMessage": "Cette identité n'est plus active dorénavant.",
"federatedIdentityRemovingLastProviderMessage":
"Vous ne pouvez pas supprimer votre dernière fédération d'identité sans avoir de mot de passe spécifié.",
"identityProviderRedirectErrorMessage": "Erreur de redirection vers le fournisseur d'identité.",
"identityProviderRemovedMessage": "Le fournisseur d'identité a été supprimé correctement.",
"identityProviderAlreadyLinkedMessage": "Le fournisseur d'identité retourné par {0} est déjà lié à un autre utilisateur.",
"accountDisabledMessage": "Ce compte est désactivé, veuillez contacter votre administrateur.",
"accountTemporarilyDisabledMessage": "Ce compte est temporairement désactivé, veuillez contacter votre administrateur ou réessayez plus tard.",
"invalidPasswordMinLengthMessage": "Mot de passe invalide: longueur minimale {0}.",
"invalidPasswordMinLowerCaseCharsMessage": "Mot de passe invalide: doit contenir au moins {0} lettre(s) en minuscule.",
"invalidPasswordMinDigitsMessage": "Mot de passe invalide: doit contenir au moins {0} chiffre(s).",
"invalidPasswordMinUpperCaseCharsMessage": "Mot de passe invalide: doit contenir au moins {0} lettre(s) en majuscule.",
"invalidPasswordMinSpecialCharsMessage": "Mot de passe invalide: doit contenir au moins {0} caractère(s) spéciaux.",
"invalidPasswordNotUsernameMessage": "Mot de passe invalide: ne doit pas être identique au nom d'utilisateur.",
"invalidPasswordRegexPatternMessage": "Mot de passe invalide: ne valide pas l'expression rationnelle.",
"invalidPasswordHistoryMessage": "Mot de passe invalide: ne doit pas être égal aux {0} derniers mots de passe.",
};
export default messages;
/* spell-checker: enable */

View File

@ -1,310 +0,0 @@
//This code was automatically generated by running dist/bin/generate-i18n-messages.js
//PLEASE DO NOT EDIT MANUALLY
/* spell-checker: disable */
const messages = {
"doSave": "Salva",
"doCancel": "Annulla",
"doLogOutAllSessions": "Effettua il logout da tutte le sessioni",
"doRemove": "Elimina",
"doAdd": "Aggiungi",
"doSignOut": "Esci",
"doLogIn": "Log In",
"doLink": "Link",
"editAccountHtmlTitle": "Modifica Account",
"personalInfoHtmlTitle": "Informazioni personali",
"federatedIdentitiesHtmlTitle": "Identità federate",
"accountLogHtmlTitle": "Log dell'account",
"changePasswordHtmlTitle": "Cambia password",
"deviceActivityHtmlTitle": "Attività dei dispositivi",
"sessionsHtmlTitle": "Sessioni",
"accountManagementTitle": "Gestione degli account di Keycloak",
"authenticatorTitle": "Autenticatore",
"applicationsHtmlTitle": "Applicazioni",
"linkedAccountsHtmlTitle": "Account collegati",
"accountManagementWelcomeMessage": "Benvenuto nella gestione degli account di Keycloak",
"personalInfoIntroMessage": "Gestisci le tue informazioni di base",
"accountSecurityTitle": "Sicurezza dell'account",
"accountSecurityIntroMessage": "Controlla la tua password e gli accessi dell'account",
"applicationsIntroMessage": "Traccia e gestisci i permessi delle applicazioni nell'accesso al tuo account",
"resourceIntroMessage": "Condividi le tue risorse tra i membri del team",
"passwordLastUpdateMessage": "La tua password è stata aggiornata il",
"updatePasswordTitle": "Aggiornamento password",
"updatePasswordMessageTitle": "Assicurati di scegliere una password robusta",
"updatePasswordMessage":
"Una password robusta contiene un misto di numeri, lettere, e simboli. È difficile da indovinare, non assomiglia a una parola reale, ed è utilizzata solo per questo account.",
"personalSubTitle": "Le tue informazioni personali",
"personalSubMessage": "Gestisce queste informazioni di base: il tuo nome, cognome, e indirizzo email",
"authenticatorCode": "Codice monouso",
"email": "Email",
"firstName": "Nome",
"givenName": "Nome",
"fullName": "Nome completo",
"lastName": "Cognome",
"familyName": "Cognome",
"password": "Password",
"currentPassword": "Password attuale",
"passwordConfirm": "Conferma password",
"passwordNew": "Nuova password",
"username": "Username",
"address": "Indirizzo",
"street": "Via",
"locality": "Città o località",
"region": "Stato, Provincia, o Regione",
"postal_code": "CAP",
"country": "Paese",
"emailVerified": "Email verificata",
"gssDelegationCredential": "Credenziali delega GSS",
"profileScopeConsentText": "Profilo utente",
"emailScopeConsentText": "Indirizzo email",
"addressScopeConsentText": "Indirizzo",
"phoneScopeConsentText": "Numero di telefono",
"offlineAccessScopeConsentText": "Accesso offline",
"samlRoleListScopeConsentText": "I miei ruoli",
"rolesScopeConsentText": "Ruoli utente",
"role_admin": "Admin",
"role_realm-admin": "Realm admin",
"role_create-realm": "Crea realm",
"role_view-realm": "Visualizza realm",
"role_view-users": "Visualizza utenti",
"role_view-applications": "Visualizza applicazioni",
"role_view-clients": "Visualizza client",
"role_view-events": "Visualizza eventi",
"role_view-identity-providers": "Visualizza identity provider",
"role_view-consent": "Visualizza consensi",
"role_manage-realm": "Gestisci realm",
"role_manage-users": "Gestisci utenti",
"role_manage-applications": "Gestisci applicazioni",
"role_manage-identity-providers": "Gestisci identity provider",
"role_manage-clients": "Gestisci client",
"role_manage-events": "Gestisci eventi",
"role_view-profile": "Visualizza profilo",
"role_manage-account": "Gestisci account",
"role_manage-account-links": "Gestisci i link dell'account",
"role_manage-consent": "Gestisci consensi",
"role_read-token": "Leggi token",
"role_offline-access": "Accesso offline",
"role_uma_authorization": "Ottieni permessi",
"client_account": "Account",
"client_account-console": "Console account",
"client_security-admin-console": "Console di amministrazione di sicurezza",
"client_admin-cli": "Admin CLI",
"client_realm-management": "Gestione realm",
"client_broker": "Broker",
"requiredFields": "Campi obbligatori",
"allFieldsRequired": "Tutti campi obbligatori",
"backToApplication": "&laquo; Torna all'applicazione",
"backTo": "Torna a {0}",
"date": "Data",
"event": "Evento",
"ip": "IP",
"client": "Client",
"clients": "Client",
"details": "Dettagli",
"started": "Iniziato",
"lastAccess": "Ultimo accesso",
"expires": "Scade",
"applications": "Applicazioni",
"account": "Account",
"federatedIdentity": "Identità federate",
"authenticator": "Autenticatore",
"device-activity": "Attività dei dispositivi",
"sessions": "Sessioni",
"log": "Log",
"application": "Applicazione",
"availablePermissions": "Autorizzazioni disponibili",
"grantedPermissions": "Autorizzazioni concesse",
"grantedPersonalInfo": "Informazioni personali concesse",
"additionalGrants": "Ulteriori concessioni",
"action": "Azione",
"inResource": "in",
"fullAccess": "Accesso completo",
"offlineToken": "Token offline",
"revoke": "Revoca concessione",
"configureAuthenticators": "Autenticatori configurati",
"mobile": "Dispositivo mobile",
"totpStep1": "Installa una delle seguenti applicazioni sul tuo dispositivo mobile",
"totpStep2": "Apri l'applicazione e scansiona il codice QR",
"totpStep3": "Scrivi il codice monouso fornito dall'applicazione e clicca Salva per completare il setup.",
"totpStep3DeviceName": "Fornisci il nome del dispositivo per aiutarti a gestire i dispositivi di autenticazione.",
"totpManualStep2": "Apri l'applicazione e scrivi la chiave",
"totpManualStep3": "Usa le seguenti impostazioni se l'applicazione lo consente",
"totpUnableToScan": "Non riesci a scansionare il codice QR?",
"totpScanBarcode": "Vuoi scansionare il codice QR?",
"totp.totp": "Basato sull'ora",
"totp.hotp": "Basato sul contatore",
"totpType": "Tipo",
"totpAlgorithm": "Algoritmo",
"totpDigits": "Cifre",
"totpInterval": "Intervallo",
"totpCounter": "Contatore",
"totpDeviceName": "Nome dispositivo",
"missingUsernameMessage": "Inserisci lo username.",
"missingFirstNameMessage": "Inserisci il nome.",
"invalidEmailMessage": "Indirizzo email non valido.",
"missingLastNameMessage": "Inserisci il cognome.",
"missingEmailMessage": "Inserisci l'indirizzo email.",
"missingPasswordMessage": "Inserisci la password.",
"notMatchPasswordMessage": "Le password non coincidono.",
"invalidUserMessage": "Utente non valido",
"missingTotpMessage": "Inserisci il codice di autenticazione.",
"missingTotpDeviceNameMessage": "Inserisci il nome del dispositivo di autenticazione.",
"invalidPasswordExistingMessage": "Password esistente non valida.",
"invalidPasswordConfirmMessage": "La password di conferma non coincide.",
"invalidTotpMessage": "Codice di autenticazione non valido.",
"usernameExistsMessage": "Username già esistente.",
"emailExistsMessage": "Email già esistente.",
"readOnlyUserMessage": "Non puoi aggiornare il tuo account poiché è in modalità sola lettura.",
"readOnlyUsernameMessage": "Non puoi aggiornare il tuo nome utente poiché è in modalità sola lettura.",
"readOnlyPasswordMessage": "Non puoi aggiornare il tuo account poiché è in modalità sola lettura.",
"successTotpMessage": "Autenticatore mobile configurato.",
"successTotpRemovedMessage": "Autenticatore mobile eliminato.",
"successGrantRevokedMessage": "Concessione revocata con successo.",
"accountUpdatedMessage": "Il tuo account è stato aggiornato.",
"accountPasswordUpdatedMessage": "La tua password è stata aggiornata.",
"missingIdentityProviderMessage": "Identity provider non specificato.",
"invalidFederatedIdentityActionMessage": "Azione non valida o mancante.",
"identityProviderNotFoundMessage": "L'identity provider specificato non è stato trovato.",
"federatedIdentityLinkNotActiveMessage": "Questo identity non è più attivo.",
"federatedIdentityRemovingLastProviderMessage": "Non puoi rimuovere l'ultima identità federata poiché non hai più la password.",
"identityProviderRedirectErrorMessage": "Il reindirizzamento all'identity provider è fallito.",
"identityProviderRemovedMessage": "Identity provider eliminato correttamente.",
"identityProviderAlreadyLinkedMessage": "L'identità federata restituita da {0} è già collegata ad un altro utente.",
"staleCodeAccountMessage": "La pagina è scaduta. Prova di nuovo.",
"consentDenied": "Consenso negato.",
"accountDisabledMessage": "Account disabilitato, contatta l'amministratore.",
"accountTemporarilyDisabledMessage": "L'account è temporaneamente disabilitato, contatta l'amministratore o riprova più tardi.",
"invalidPasswordMinLengthMessage": "Password non valida: lunghezza minima {0}.",
"invalidPasswordMinLowerCaseCharsMessage": "Password non valida: deve contenere almeno {0} caratteri minuscoli.",
"invalidPasswordMinDigitsMessage": "Password non valida: deve contenere almeno {0} numeri.",
"invalidPasswordMinUpperCaseCharsMessage": "Password non valida: deve contenere almeno {0} caratteri maiuscoli.",
"invalidPasswordMinSpecialCharsMessage": "Password non valida: deve contenere almeno {0} caratteri speciali.",
"invalidPasswordNotUsernameMessage": "Password non valida: non deve essere uguale allo username.",
"invalidPasswordRegexPatternMessage": "Password non valida: fallito il match con una o più espressioni regolari.",
"invalidPasswordHistoryMessage": "Password non valida: non deve essere uguale a una delle ultime {0} password.",
"invalidPasswordBlacklistedMessage": "Password non valida: la password non è consentita.",
"invalidPasswordGenericMessage": "Password non valida: la nuova password non rispetta le indicazioni previste.",
"myResources": "Le mie risorse",
"myResourcesSub": "Le mie risorse",
"doDeny": "Nega",
"doRevoke": "Revoca",
"doApprove": "Approva",
"doRemoveSharing": "Rimuovi condivisione",
"doRemoveRequest": "Rimuovi richiesta",
"peopleAccessResource": "Persone che hanno accesso a questa risorsa",
"resourceManagedPolicies": "Permessi che danno accesso a questa risorsa",
"resourceNoPermissionsGrantingAccess": "Nessun permesso dà accesso a questa risorsa",
"anyAction": "Qualsiasi azione",
"description": "Descrizione",
"name": "Nome",
"scopes": "Ambito",
"resource": "Risorsa",
"user": "Utente",
"peopleSharingThisResource": "Persone che condividono questa risorsa",
"shareWithOthers": "Condividi con altri",
"needMyApproval": "Richiede la mia approvazione",
"requestsWaitingApproval": "La tua richiesta è in attesa di approvazione",
"icon": "Icona",
"requestor": "Richiedente",
"owner": "Proprietario",
"resourcesSharedWithMe": "Risorse condivise con me",
"permissionRequestion": "Richiesta di permesso",
"permission": "Permesso",
"shares": "condivisioni",
"notBeingShared": "Questa risorsa non è in condivisione.",
"notHaveAnyResource": "Non hai nessuna risorsa",
"noResourcesSharedWithYou": "Non ci sono risorse condivise con te",
"havePermissionRequestsWaitingForApproval": "Hai {0} richiesta(e) di permesso in attesa di approvazione.",
"clickHereForDetails": "Clicca qui per i dettagli.",
"resourceIsNotBeingShared": "La risorsa non è in condivisione",
"locale_it": "Italiano",
"applicaitonName": "Nome",
"applicationType": "Tipo applicazione",
"applicationInUse": "In-use app only",
"clearAllFilter": "Azzera tutti i filtri",
"activeFilters": "Filtri attivi",
"filterByName": "Filtra per nome ...",
"allApps": "Tutte le applicazioni",
"internalApps": "Applicazioni interne",
"thirdpartyApps": "Applicazioni di terze parti",
"appResults": "Risultati",
"clientNotFoundMessage": "Client non trovato.",
"authorizedProvider": "Provider autorizzato",
"authorizedProviderMessage": "Provider autorizzati collegati al tuo account",
"identityProvider": "Identity provider",
"identityProviderMessage": "Collegare il tuo account con gli identity provider che hai configurato",
"socialLogin": "Social Login",
"userDefined": "Definito dall'utente",
"removeAccess": "Rimuovi accesso",
"removeAccessMessage": "Devi concedere di nuovo l'accesso, se vuoi utilizzare l'account di questa applicazione.",
"authenticatorStatusMessage": "L'autenticazione a due fattori è attualmente",
"authenticatorFinishSetUpTitle": "La tua autenticazione a due fattori",
"authenticatorFinishSetUpMessage":
"Ogni volta che effettui l'accesso al tuo account Keycloak, ti verrà richiesto di fornire il tuo codice di autenticazione a due fattori.",
"authenticatorSubTitle": "Imposta l'autenticazione a due fattori",
"authenticatorSubMessage":
"Per incrementare la sicurezza del tuo account, attiva almeno uno dei metodi disponibili per l'autenticazione a due fattori.",
"authenticatorMobileTitle": "Autenticatore mobile",
"authenticatorMobileMessage": "Utilizza l'autenticatore mobile per ottenere i codici di verifica per l'autenticazione a due fattori.",
"authenticatorMobileFinishSetUpMessage": "L'autenticatore è stato collegato al tuo telefono.",
"authenticatorActionSetup": "Set up",
"authenticatorSMSTitle": "Codice SMS",
"authenticatorSMSMessage": "Keycloak invierà il codice di verifica al tuo telefono per l'autenticazione a due fattori.",
"authenticatorSMSFinishSetUpMessage": "I messaggi di testo vengono inviati a",
"authenticatorDefaultStatus": "Default",
"authenticatorChangePhone": "Cambia numero di telefono",
"authenticatorBackupCodesTitle": "Codici di backup",
"authenticatorBackupCodesMessage": "Ottieni i tuoi codici di backup a otto cifre",
"authenticatorBackupCodesFinishSetUpMessage": "Sono stati generati dodici codici di backup. Ognuno può essere usato una sola volta.",
"authenticatorMobileSetupTitle": "Setup autenticatore mobile",
"smscodeIntroMessage": "Inserisci il tuo numero di telefono e ti verrà inviato un codice di verifica.",
"mobileSetupStep1": "Installa un'applicazione di autenticazione sul tuo telefono. Sono supportate le applicazioni qui elencate.",
"mobileSetupStep2": "Apri l'applicazione e scansiona il codice QR:",
"mobileSetupStep3": "Inserisci il codice monouso fornito dall'applicazione e clicca Salva per completare il setup.",
"scanBarCode": "Vuoi scansionare il codice QR?",
"enterBarCode": "Inserisci il codice monouso",
"doCopy": "Copia",
"doFinish": "Termina",
"authenticatorSMSCodeSetupTitle": "Setup codice SMS",
"chooseYourCountry": "Scegli la tua nazione",
"enterYourPhoneNumber": "Inserisci il tuo numero di telefono",
"sendVerficationCode": "Invia il codice di verifica",
"enterYourVerficationCode": "Inserisci il codice di verifica",
"authenticatorBackupCodesSetupTitle": "Setup backup codici",
"backupcodesIntroMessage":
"Se non disponi più del tuo telefono, puoi comunque accedere al tuo account attraverso i codici di backup. Conservali in un posto sicuro e accessibile.",
"realmName": "Realm",
"doDownload": "Download",
"doPrint": "Stampa",
"backupCodesTips-1": "Ogni codice di backup può essere usato una sola volta.",
"backupCodesTips-2": "Questi codici sono stati generati il",
"generateNewBackupCodes": "Genera dei nuovi codici di backup",
"backupCodesTips-3": "Quando generi dei nuovi codici di backup, quelli attuali non funzioneranno più.",
"backtoAuthenticatorPage": "Torna alla pagina dell'autenticatore",
"resources": "Risorse",
"sharedwithMe": "Condiviso con me",
"share": "Condiviso",
"sharedwith": "Condiviso con",
"accessPermissions": "Permessi di accesso",
"permissionRequests": "Richieste di permesso",
"approve": "Approva",
"approveAll": "Approva tutti",
"people": "persone",
"perPage": "per pagina",
"currentPage": "Pagina corrente",
"sharetheResource": "Condividi la risorsa",
"group": "Gruppo",
"selectPermission": "Seleziona permessi",
"addPeople": "Aggiungi persone con le quali condividere la tua risorsa",
"addTeam": "Aggiungi gruppi con i quali condividere la tua risorsa",
"myPermissions": "Miei permessi",
"waitingforApproval": "Attesa dell'approvazione",
"anyPermission": "Qualsiasi permesso",
"openshift.scope.user_info": "Informazioni utente",
"openshift.scope.user_check-access": "Informazioni per l'accesso dell'utente",
"openshift.scope.user_full": "Accesso completo",
"openshift.scope.list-projects": "Elenca progetti",
};
export default messages;
/* spell-checker: enable */

Some files were not shown because too many files have changed in this diff Show More