Compare commits

..

1 Commits

Author SHA1 Message Date
1006ed70dd Enabling shorter import paths [automatic] 2021-02-21 22:49:03 +00:00
403 changed files with 4620 additions and 44569 deletions

3
.gitattributes vendored
View File

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

View File

@ -1,4 +0,0 @@
# These are supported funding model platforms
github: [garronej]
custom: ['https://www.ringerhq.com/experts/garronej']

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,139 +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@v3
- uses: actions/setup-node@v3
- uses: bahmutov/npm-install@v1
- name: If this step fails run 'yarn 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: ${{ matrix.os }}
needs: test_lint
strategy:
matrix:
node: [ '14', '15' ,'16', '17' ]
os: [ windows-latest, ubuntu-latest ]
name: Test with Node v${{ matrix.node }} on ${{ matrix.os }}
steps:
- name: Tell if project is using npm or yarn
id: step1
uses: garronej/ts-ci@v2.0.2
with:
action_name: tell_if_project_uses_npm_or_yarn
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
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_pre_release: ${{steps.step1.outputs.is_pre_release }}
steps:
- uses: garronej/ts-ci@v2.0.2
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 the main branch
# or if we are on a branch of the repo that has an PR open on main.
if: |
needs.check_if_version_upgraded.outputs.is_upgraded_version == 'true' &&
(
github.event_name == 'push' ||
needs.check_if_version_upgraded.outputs.is_pre_release == '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_pre_release == '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@v3
with:
ref: ${{ github.ref }}
- uses: actions/setup-node@v3
with:
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@1.2.2 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_PRE_RELEASE" = "true" ]; then
EXTRA_ARGS="--tag next"
fi
npm publish $EXTRA_ARGS
env:
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
VERSION: ${{ needs.check_if_version_upgraded.outputs.to_version }}
IS_PRE_RELEASE: ${{ needs.check_if_version_upgraded.outputs.is_pre_release }}

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,10 +0,0 @@
node_modules/
/dist/
/dist_test/
/CHANGELOG.md
/.yarn_home/
/src/test/apps/
/src/tools/types/
/sample_react_project
/build_keycloak/
/src/lib/i18n/generated_messages/

View File

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

30
CHANGELOG.md Normal file
View File

@ -0,0 +1,30 @@
### **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.

196
README.md
View File

@ -1,190 +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>
-
<a href="https://github.com/codegouvfr/keycloak-starter">Starter project</a>
</p>
<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 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>
> 🗣 V6 have been released 🎉
> [It features major improvements](https://github.com/InseeFrLab/keycloakify#600).
> Checkout [the migration guide](https://docs.keycloakify.dev/v5-to-v6).
# REQUIREMENT
## For building the theme:
# Changelog highlights
- `mvn` must be installed
## 6.12
## For development, (testing the theme on a docker container ect ):
Massive improvement in the developer experience:
- `rm`
- `mkdir` )
- `wget`
- `unzip`
- There is now only one starter repo: https://github.com/codegouvfr/keycloakify-starter
- A lot of comments have been added in the code of the starter to make it easier to get started.
- The doc has been updated: https://docs.keycloakify.dev
- A lot of improvements in the type system.
Tested on MacOS
## 6.11.4
# USAGE
- You no longer need to have Maven installed to build the theme. Thanks to @lordvlad, [see PR](https://github.com/InseeFrLab/keycloakify/pull/239).
- Feature new build options: [`bundler`](https://docs.keycloakify.dev/build-options#keycloakify.bundler), [`groupId`](https://docs.keycloakify.dev/build-options#keycloakify.groupid), [`artifactId`](https://docs.keycloakify.dev/build-options#keycloakify.artifactid), [`version`](https://docs.keycloakify.dev/build-options#version).
Theses options can be user to customize the output name of the .jar. You can use environnement variables to overrides the values read in the package.json. Thanks to @lordvlad.
## Build the theme:
`npx build-keycloak-theme`
## 6.10.0
## (Optional/Debug) Download more themes:
- Widows compat (thanks to @lordvlad, [see PR](https://github.com/InseeFrLab/keycloakify/pull/226)). WSL is no longer required 🎉
## 6.8.4
- `@emotion/react` is no longer a peer dependency of Keycloakify.
## 6.8.0
- It is now possible to pass a custom `<Template />` component as a prop to `<KcApp />` and every
individual page (`<Login />`, `<RegisterUserProfile />`, ...) it enables to customize only the header and footer for
example without having to switch to a full-component level customization. [See issue](https://github.com/InseeFrLab/keycloakify/issues/191).
## 6.7.0
- Add support for `webauthn-authenticate.ftl` thanks to [@mstrodl](https://github.com/Mstrodl)'s hacktoberfest [PR](https://github.com/InseeFrLab/keycloakify/pull/185).
## 6.6.0
- Add support for `login-password.ftl` thanks to [@mstrodl](https://github.com/Mstrodl)'s hacktoberfest [PR](https://github.com/InseeFrLab/keycloakify/pull/184).
## 6.5.0
- Add support for `login-username.ftl` thanks to [@mstrodl](https://github.com/Mstrodl)'s hacktoberfest [PR](https://github.com/InseeFrLab/keycloakify/pull/183).
## 6.4.0
- You can now optionally pass a `doFetchDefaultThemeResources: boolean` prop to every page component and the default `<KcApp />`
This enables you to prevent the default CSS and JS that comes with the builtin Keycloak theme to be downloaded.
You'll get [a black slate](https://user-images.githubusercontent.com/6702424/192619083-4baa5df4-4a21-4ec7-8e28-d200d1208299.png).
## 6.0.0
- Bundle size drastically reduced, locals and component dynamically loaded.
- First print much quicker, use of React.lazy() everywhere.
- Real i18n API.
- Actual documentation for build options.
Checkout [the migration guide](https://docs.keycloakify.dev/v5-to-v6)
## 5.8.0
- [React.lazy()](https://reactjs.org/docs/code-splitting.html#reactlazy) support 🎉. [#141](https://github.com/InseeFrLab/keycloakify/issues/141)
## 5.7.0
- Feat `logout-confirm.ftl`. [PR](https://github.com/InseeFrLab/keycloakify/pull/120)
## 5.6.4
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)
## v5.6.0
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", "onyxia") + ":/opt/jboss/keycloak/themes/onyxia: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, "..", "..", "..", "res", path_1.basename(standaloneHaFilePath))));
}
exports.generateDebugFiles = generateDebugFiles;
//# sourceMappingURL=generateDebugFiles.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"generateDebugFiles.js","sourceRoot":"","sources":["../../src/bin/build-keycloak-theme/generateDebugFiles.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,QAAQ,CAAC,6CAA0C;QACtI,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,CACX,WAAQ,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,eAAY,CAAC,oBAAoB,CAAC,CAAC,CACnF,CACJ,CAAC;AAEN,CAAC;AAjED,gDAiEC"}

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,93 @@
"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 __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([
'',
'<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;QACI,EAAE;QACF,SAAS;QACT,wDAA8B,CAC1B,EAAE,kBAAkB,oBAAA,EAAE,CACzB,CAAC,sBAAsB;QACxB,UAAU;QACV,EAAE;QACF,UAAU;QACV,oBAAoB;QACpB,oBAAkB,mBAAmB,MAAG;QACxC,WAAW;QACX,sBAAsB;QACtB,sDAAsD;QACtD,yDAAyD;QACzD,eAAe;QACf,WAAW;QACX,SAAS;QACT,WAAW;QACX,EAAE;KACL,CAAC,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;AAhGD,kEAgGC"}

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,75 @@
"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"));
});
}
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;AAEP,CAAC;AArED,wEAqEC"}

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":""}

1136
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

126
package.json Normal file → Executable file
View File

@ -1,93 +1,75 @@
{
"name": "keycloakify",
"version": "6.12.0",
"description": "Keycloak theme generator for Reacts app",
"name": "keycloak-react-theming",
"version": "0.0.6",
"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",
"pretest": "yarn build:test",
"test": "node dist_test/test/bin && node dist_test/test/lib",
"_format": "prettier '**/*.{ts,tsx,json,md}'",
"format": "yarn _format --write",
"format:check": "yarn _format --list-different",
"generate-messages": "ts-node --skipProject src/scripts/generate-i18n-messages.ts",
"link-in-app": "ts-node --skipProject src/scripts/link-in-app.ts",
"link-in-starter": "yarn link-in-app keycloakify-advanced-starter",
"tsc-watch": "tsc -p src/bin -w & tsc -p src/lib -w "
},
"bin": {
"keycloakify": "dist/bin/keycloakify/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/",
"!src/scripts",
"dist/",
"!dist/tsconfig.tsbuildinfo"
"src/bin/build-keycloak-theme/generateDebugFiles.ts",
"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.d.ts",
"bin/build-keycloak-theme/generateDebugFiles.js",
"bin/build-keycloak-theme/generateDebugFiles.js.map",
"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",
"res/standalone-ha.xml"
],
"keywords": [
"bluehats",
"keycloak",
"react",
"theme",
"FreeMarker",
"ftl",
"login",
"register"
"theme"
],
"homepage": "https://github.com/garronej/keycloakify",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
},
"homepage": "https://github.com/garronej/keycloak-react-theming",
"devDependencies": {
"@babel/core": "^7.0.0",
"@types/memoizee": "^0.4.7",
"@types/minimist": "^1.2.2",
"@types/node": "^18.14.1",
"@types/react": "18.0.9",
"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.9.5",
"ts-node": "^10.9.1",
"scripting-tools": "^0.19.13"
"@types/node": "^10.0.0",
"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.4.13",
"minimal-polyfills": "^2.2.2",
"minimist": "^1.2.6",
"path-browserify": "^1.0.1",
"react-markdown": "^5.0.3",
"rfc4648": "^1.5.2",
"tsafe": "^1.4.3",
"zod": "^3.17.10"
"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": ["tsafe", "evt"],
"enabled": false
},
{
"packagePatterns": ["tsafe", "evt"],
"matchUpdateTypes": ["minor", "patch"],
"automerge": true,
"automergeType": "branch",
"groupName": "garronej_modules_update"
}
]
}

666
res/standalone-ha.xml Normal file
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,71 @@
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", "onyxia")}:/opt/jboss/keycloak/themes/onyxia: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, "..", "..", "..", "res", pathBasename(standaloneHaFilePath)),
)
);
}

View File

@ -0,0 +1,105 @@
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(
[
'',
'<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

@ -0,0 +1,98 @@
import * as url from "url";
import * as fs from "fs";
import { join as pathJoin, dirname as pathDirname } from "path";
export type ParsedPackageJson = {
name: string;
version: string;
homepage?: string;
};
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.${name}`;
return (!homepage ?
fallbackGroupId :
url.parse(homepage).host?.split(".").reverse().join(".") ?? fallbackGroupId
) + ".keycloak";
})();
const artefactId = `${name}-keycloak-theme`;
const pomFileCode = [
`<?xml version="1.0"?>`,
`<project xmlns="http://maven.apache.org/POM/4.0.0"`,
` xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"`,
` xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">`,
` <modelVersion>4.0.0</modelVersion>`,
` <groupId>${groupId}</groupId>`,
` <artifactId>${artefactId}</artifactId>`,
` <version>${version}</version>`,
` <name>${artefactId}</name>`,
` <description />`,
`</project>`
].join("\n");
return { pomFileCode };
})();
fs.writeFileSync(
pathJoin(keycloakThemeBuildingDirPath, "pom.xml"),
Buffer.from(pomFileCode, "utf8")
);
}
{
const themeManifestFilePath = pathJoin(
keycloakThemeBuildingDirPath, "src", "main",
"resources", "META-INF", "keycloak-themes.json"
);
try {
fs.mkdirSync(pathDirname(themeManifestFilePath));
} catch { }
fs.writeFileSync(
themeManifestFilePath,
Buffer.from(
JSON.stringify({
"themes": [
{
"name": name,
"types": ["login"]
}
]
}, null, 2),
"utf8"
)
);
}
}

View File

@ -0,0 +1,83 @@
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": pathJoin(themeDirPath, "resources"),
"srcDirPath": reactAppBuildDirPath,
"transformSourceCodeString": ({ filePath, sourceCode }) => {
if (/\.css?$/i.test(filePath)) {
const { cssGlobalsToDefine, fixedCssCode } = replaceImportFromStaticInCssCode(
{ "cssCode": sourceCode.toString("utf8") }
);
allCssGlobalsToDefine = {
...allCssGlobalsToDefine,
...cssGlobalsToDefine
};
return { "modifiedSourceCode": Buffer.from(fixedCssCode, "utf8") };
}
if (/\.js?$/i.test(filePath)) {
const { fixedJsCode } = replaceImportFromStaticInJsCode({
"jsCode": sourceCode.toString("utf8"),
ftlValuesGlobalName
});
return { "modifiedSourceCode": Buffer.from(fixedJsCode, "utf8") };
}
return { "modifiedSourceCode": sourceCode };
}
});
const { generateFtlFilesCode } = generateFtlFilesCodeFactory({
"cssGlobalsToDefine": allCssGlobalsToDefine,
ftlValuesGlobalName,
"indexHtmlCode": fs.readFileSync(
pathJoin(reactAppBuildDirPath, "index.html")
).toString("utf8")
});
(["login.ftl", "register.ftl"] as const).forEach(pageBasename => {
const { ftlCode } = generateFtlFilesCode({ pageBasename });
fs.writeFileSync(
pathJoin(themeDirPath, pageBasename),
Buffer.from(ftlCode, "utf8")
)
});
}

View File

@ -0,0 +1,39 @@
#!/usr/bin/env node
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) {
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

@ -0,0 +1,91 @@
import * as crypto from "crypto";
export function replaceImportFromStaticInJsCode(
params: {
ftlValuesGlobalName: string;
jsCode: string;
}
): { fixedJsCode: string; } {
const { jsCode, ftlValuesGlobalName } = params;
const fixedJsCode = jsCode!.replace(
/"static\//g,
`window.${ftlValuesGlobalName}.url.resourcesPath.replace(/^\\//,"") + "/" + "static/`
);
return { fixedJsCode };
}
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
);
let fixedCssCode = cssCode;
Object.keys(cssGlobalsToDefine).forEach(
cssVariableName =>
//NOTE: split/join pattern ~ replace all
fixedCssCode =
fixedCssCode.split(cssGlobalsToDefine[cssVariableName])
.join(`var(--${cssVariableName})`)
);
return { fixedCssCode, cssGlobalsToDefine };
}
export function generateCssCodeToDefineGlobals(
params: {
cssGlobalsToDefine: Record<string, string>;
}
): {
cssCodeToPrependInHead: string;
} {
const { cssGlobalsToDefine } = params;
return {
"cssCodeToPrependInHead": [
":root {",
...Object.keys(cssGlobalsToDefine)
.map(cssVariableName => [
`--${cssVariableName}:`,
[
"url(",
"${url.resourcesPath}" +
cssGlobalsToDefine[cssVariableName].match(/^url\(([^)]+)\)$/)![1],
")"
].join("")
].join(" "))
.map(line => ` ${line};`),
"}"
].join("\n")
};
}

View File

@ -1,41 +0,0 @@
#!/usr/bin/env node
import { downloadBuiltinKeycloakTheme } from "./download-builtin-keycloak-theme";
import { keycloakThemeEmailDirPath } from "./keycloakify";
import { join as pathJoin, basename as pathBasename } from "path";
import { transformCodebase } from "./tools/transformCodebase";
import { promptKeycloakVersion } from "./promptKeycloakVersion";
import * as fs from "fs";
import { getCliOptions } from "./tools/cliOptions";
import { getLogger } from "./tools/logger";
if (require.main === module) {
(async () => {
const { isSilent } = getCliOptions(process.argv.slice(2));
const logger = getLogger({ isSilent });
if (fs.existsSync(keycloakThemeEmailDirPath)) {
logger.warn(`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,
isSilent
});
transformCodebase({
"srcDirPath": pathJoin(builtinKeycloakThemeTmpDirPath, "base", "email"),
"destDirPath": keycloakThemeEmailDirPath
});
logger.log(`./${pathBasename(keycloakThemeEmailDirPath)} ready to be customized`);
fs.rmSync(builtinKeycloakThemeTmpDirPath, { "recursive": true, "force": true });
})();
}

View File

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

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,207 +0,0 @@
import { z } from "zod";
import { assert } from "tsafe/assert";
import type { Equals } from "tsafe";
import { id } from "tsafe/id";
import { parse as urlParse } from "url";
import { typeGuard } from "tsafe/typeGuard";
import { symToStr } from "tsafe/symToStr";
const bundlers = ["mvn", "keycloakify", "none"] as const;
type Bundler = (typeof bundlers)[number];
type ParsedPackageJson = {
name: string;
version: string;
homepage?: string;
keycloakify?: {
extraPages?: string[];
extraThemeProperties?: string[];
areAppAndKeycloakServerSharingSameDomain?: boolean;
artifactId?: string;
groupId?: string;
bundler?: Bundler;
};
};
const zParsedPackageJson = z.object({
"name": z.string(),
"version": z.string(),
"homepage": z.string().optional(),
"keycloakify": z
.object({
"extraPages": z.array(z.string()).optional(),
"extraThemeProperties": z.array(z.string()).optional(),
"areAppAndKeycloakServerSharingSameDomain": z.boolean().optional(),
"artifactId": z.string().optional(),
"groupId": z.string().optional(),
"bundler": z.enum(bundlers).optional()
})
.optional()
});
assert<Equals<ReturnType<(typeof zParsedPackageJson)["parse"]>, ParsedPackageJson>>();
/** Consolidated build option gathered form CLI arguments and config in package.json */
export type BuildOptions = BuildOptions.Standalone | BuildOptions.ExternalAssets;
export namespace BuildOptions {
export type Common = {
isSilent: boolean;
version: string;
themeName: string;
extraPages?: string[];
extraThemeProperties?: string[];
groupId: string;
artifactId: string;
bundler: Bundler;
};
export type Standalone = Common & {
isStandalone: true;
urlPathname: string | undefined;
};
export type ExternalAssets = ExternalAssets.SameDomain | ExternalAssets.DifferentDomains;
export namespace ExternalAssets {
export type CommonExternalAssets = Common & {
isStandalone: false;
};
export type SameDomain = CommonExternalAssets & {
areAppAndKeycloakServerSharingSameDomain: true;
};
export type DifferentDomains = CommonExternalAssets & {
areAppAndKeycloakServerSharingSameDomain: false;
urlOrigin: string;
urlPathname: string | undefined;
};
}
}
export function readBuildOptions(params: {
packageJson: string;
CNAME: string | undefined;
isExternalAssetsCliParamProvided: boolean;
isSilent: boolean;
}): BuildOptions {
const { packageJson, CNAME, isExternalAssetsCliParamProvided, isSilent } = params;
const parsedPackageJson = zParsedPackageJson.parse(JSON.parse(packageJson));
const url = (() => {
const { homepage } = parsedPackageJson;
let url: URL | undefined = undefined;
if (homepage !== undefined) {
url = new URL(homepage);
}
if (CNAME !== undefined) {
url = new URL(`https://${CNAME.replace(/\s+$/, "")}`);
}
if (url === undefined) {
return undefined;
}
return {
"origin": url.origin,
"pathname": (() => {
const out = url.pathname.replace(/([^/])$/, "$1/");
return out === "/" ? undefined : out;
})()
};
})();
const common: BuildOptions.Common = (() => {
const { name, keycloakify = {}, version, homepage } = parsedPackageJson;
const { extraPages, extraThemeProperties, groupId, artifactId, bundler } = keycloakify ?? {};
const themeName = name
.replace(/^@(.*)/, "$1")
.split("/")
.join("-");
return {
themeName,
"bundler": (() => {
const { KEYCLOAKIFY_BUNDLER } = process.env;
assert(
typeGuard<Bundler | undefined>(
KEYCLOAKIFY_BUNDLER,
[undefined, ...id<readonly string[]>(bundlers)].includes(KEYCLOAKIFY_BUNDLER)
),
`${symToStr({ KEYCLOAKIFY_BUNDLER })} should be one of ${bundlers.join(", ")}`
);
return KEYCLOAKIFY_BUNDLER ?? bundler ?? "keycloakify";
})(),
"artifactId": process.env.KEYCLOAKIFY_ARTIFACT_ID ?? artifactId ?? `${themeName}-keycloak-theme`,
"groupId": (() => {
const fallbackGroupId = `${themeName}.keycloak`;
return (
process.env.KEYCLOAKIFY_GROUP_ID ??
groupId ??
(!homepage
? fallbackGroupId
: urlParse(homepage)
.host?.replace(/:[0-9]+$/, "")
?.split(".")
.reverse()
.join(".") ?? fallbackGroupId) + ".keycloak"
);
})(),
"version": process.env.KEYCLOAKIFY_VERSION ?? version,
extraPages,
extraThemeProperties,
isSilent
};
})();
if (isExternalAssetsCliParamProvided) {
const commonExternalAssets = id<BuildOptions.ExternalAssets.CommonExternalAssets>({
...common,
"isStandalone": false
});
if (parsedPackageJson.keycloakify?.areAppAndKeycloakServerSharingSameDomain) {
return id<BuildOptions.ExternalAssets.SameDomain>({
...commonExternalAssets,
"areAppAndKeycloakServerSharingSameDomain": true
});
} else {
assert(
url !== undefined,
[
"Can't compile in external assets mode if we don't know where",
"the app will be hosted.",
"You should provide a homepage field in the package.json (or create a",
"public/CNAME file.",
"Alternatively, if your app and the Keycloak server are on the same domain, ",
"eg https://example.com is your app and https://example.com/auth is the keycloak",
'admin UI, you can set "keycloakify": { "areAppAndKeycloakServerSharingSameDomain": true }',
"in your package.json"
].join(" ")
);
return id<BuildOptions.ExternalAssets.DifferentDomains>({
...commonExternalAssets,
"areAppAndKeycloakServerSharingSameDomain": false,
"urlOrigin": url.origin,
"urlPathname": url.pathname
});
}
}
return id<BuildOptions.Standalone>({
...common,
"isStandalone": true,
"urlPathname": url?.pathname
});
}

View File

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

View File

@ -1,368 +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;
<#else>
<#list fieldNames as fieldName>
if(fieldName === "${fieldName}" ){
<#attempt>
<#if '${fieldName}' == 'username' || '${fieldName}' == 'password'>
return <#if messagesPerField.existsError('username', 'password')>x<#else>undefined</#if>;
<#else>
return <#if messagesPerField.existsError('${fieldName}')>x<#else>undefined</#if>;
</#if>
<#recover>
</#attempt>
}
</#list>
throw new Error("There is no " + fieldName + " field");
</#if>
},
"existsError": function (fieldName) {
<#if !messagesPerField?? >
return false;
<#else>
<#list fieldNames as fieldName>
if(fieldName === "${fieldName}" ){
<#attempt>
<#if '${fieldName}' == 'username' || '${fieldName}' == 'password'>
return <#if messagesPerField.existsError('username', 'password')>true<#else>false</#if>;
<#else>
return <#if messagesPerField.existsError('${fieldName}')>true<#else>false</#if>;
</#if>
<#recover>
</#attempt>
}
</#list>
throw new Error("There is no " + fieldName + " field");
</#if>
},
"get": function (fieldName) {
<#if !messagesPerField?? >
return '';
<#else>
<#list fieldNames as fieldName>
if(fieldName === "${fieldName}" ){
<#attempt>
<#if '${fieldName}' == 'username' || '${fieldName}' == 'password'>
<#if messagesPerField.existsError('username', 'password')>
return 'Invalid username or password.';
</#if>
<#else>
<#if messagesPerField.existsError('${fieldName}')>
return "${messagesPerField.get('${fieldName}')?no_esc}";
</#if>
</#if>
<#recover>
</#attempt>
}
</#list>
throw new Error("There is no " + fieldName + " field");
</#if>
},
"exists": function (fieldName) {
<#if !messagesPerField?? >
return false;
<#else>
<#list fieldNames as fieldName>
if(fieldName === "${fieldName}" ){
<#attempt>
<#if '${fieldName}' == 'username' || '${fieldName}' == 'password'>
return <#if messagesPerField.exists('username') || messagesPerField.exists('password')>true<#else>false</#if>;
<#else>
return <#if messagesPerField.exists('${fieldName}')>true<#else>false</#if>;
</#if>
<#recover>
</#attempt>
}
</#list>
throw new Error("There is no " + fieldName + " field");
</#if>
}
};
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>
<#if !array_item??>
<#local out_seq += ["null,"]>
<#continue>
</#if>
<#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,190 +0,0 @@
import cheerio from "cheerio";
import { replaceImportsFromStaticInJsCode } from "../replacers/replaceImportsFromStaticInJsCode";
import { generateCssCodeToDefineGlobals } from "../replacers/replaceImportsInCssCode";
import { replaceImportsInInlineCssCode } from "../replacers/replaceImportsInInlineCssCode";
import * as fs from "fs";
import { join as pathJoin } from "path";
import { objectKeys } from "tsafe/objectKeys";
import { ftlValuesGlobalName } from "../ftlValuesGlobalName";
import type { BuildOptions } from "../BuildOptions";
import { assert } from "tsafe/assert";
import { Reflect } from "tsafe/Reflect";
// https://github.com/keycloak/keycloak/blob/main/services/src/main/java/org/keycloak/forms/login/freemarker/Templates.java
export const pageIds = [
"login.ftl",
"login-username.ftl",
"login-password.ftl",
"webauthn-authenticate.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",
"update-user-profile.ftl",
"idp-review-user-profile.ftl"
] as const;
export type BuildOptionsLike = BuildOptionsLike.Standalone | BuildOptionsLike.ExternalAssets;
export namespace BuildOptionsLike {
export type Standalone = {
isStandalone: true;
urlPathname: string | undefined;
};
export type ExternalAssets = ExternalAssets.SameDomain | ExternalAssets.DifferentDomains;
export namespace ExternalAssets {
export type CommonExternalAssets = {
isStandalone: false;
};
export type SameDomain = CommonExternalAssets & {
areAppAndKeycloakServerSharingSameDomain: true;
};
export type DifferentDomains = CommonExternalAssets & {
areAppAndKeycloakServerSharingSameDomain: false;
urlOrigin: string;
urlPathname: string | undefined;
};
}
}
{
const buildOptions = Reflect<BuildOptions>();
assert<typeof buildOptions extends BuildOptionsLike ? true : false>();
}
export type PageId = (typeof pageIds)[number];
export function generateFtlFilesCodeFactory(params: {
indexHtmlCode: string;
//NOTE: Expected to be an empty object if external assets mode is enabled.
cssGlobalsToDefine: Record<string, string>;
buildOptions: BuildOptionsLike;
}) {
const { cssGlobalsToDefine, indexHtmlCode, buildOptions } = params;
const $ = cheerio.load(indexHtmlCode);
fix_imports_statements: {
if (!buildOptions.isStandalone && buildOptions.areAppAndKeycloakServerSharingSameDomain) {
break fix_imports_statements;
}
$("script:not([src])").each((...[, element]) => {
const { fixedJsCode } = replaceImportsFromStaticInJsCode({
"jsCode": $(element).html()!,
buildOptions
});
$(element).text(fixedJsCode);
});
$("style").each((...[, element]) => {
const { fixedCssCode } = replaceImportsInInlineCssCode({
"cssCode": $(element).html()!,
buildOptions
});
$(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,
buildOptions.isStandalone
? href.replace(new RegExp(`^${(buildOptions.urlPathname ?? "/").replace(/\//g, "\\/")}`), "${url.resourcesPath}/build/")
: href.replace(/^\//, `${buildOptions.urlOrigin}/`)
);
})
);
if (Object.keys(cssGlobalsToDefine).length !== 0) {
$("head").prepend(
[
"",
"<style>",
generateCssCodeToDefineGlobals({
cssGlobalsToDefine,
buildOptions
}).cssCodeToPrependInHead,
"</style>",
""
].join("\n")
);
}
}
//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(
[
"<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,87 +0,0 @@
import * as fs from "fs";
import { join as pathJoin, dirname as pathDirname } from "path";
import { assert } from "tsafe/assert";
import { Reflect } from "tsafe/Reflect";
import type { BuildOptions } from "./BuildOptions";
export type BuildOptionsLike = {
themeName: string;
groupId: string;
artifactId?: string;
version: string;
};
{
const buildOptions = Reflect<BuildOptions>();
assert<typeof buildOptions extends BuildOptionsLike ? true : false>();
}
export function generateJavaStackFiles(params: {
keycloakThemeBuildingDirPath: string;
doBundlesEmailTemplate: boolean;
buildOptions: BuildOptionsLike;
}): {
jarFilePath: string;
} {
const {
buildOptions: { groupId, themeName, version, artifactId },
keycloakThemeBuildingDirPath,
doBundlesEmailTemplate
} = params;
{
const { pomFileCode } = (function generatePomFileCode(): {
pomFileCode: string;
} {
const pomFileCode = [
`<?xml version="1.0"?>`,
`<project xmlns="http://maven.apache.org/POM/4.0.0"`,
` xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"`,
` xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">`,
` <modelVersion>4.0.0</modelVersion>`,
` <groupId>${groupId}</groupId>`,
` <artifactId>${artifactId}</artifactId>`,
` <version>${version}</version>`,
` <name>${artifactId}</name>`,
` <description />`,
`</project>`
].join("\n");
return { pomFileCode };
})();
fs.writeFileSync(pathJoin(keycloakThemeBuildingDirPath, "pom.xml"), Buffer.from(pomFileCode, "utf8"));
}
{
const themeManifestFilePath = pathJoin(keycloakThemeBuildingDirPath, "src", "main", "resources", "META-INF", "keycloak-themes.json");
try {
fs.mkdirSync(pathDirname(themeManifestFilePath));
} catch {}
fs.writeFileSync(
themeManifestFilePath,
Buffer.from(
JSON.stringify(
{
"themes": [
{
"name": themeName,
"types": ["login", ...(doBundlesEmailTemplate ? ["email"] : [])]
}
]
},
null,
2
),
"utf8"
)
);
}
return {
"jarFilePath": pathJoin(keycloakThemeBuildingDirPath, "target", `${artifactId}-${version}.jar`)
};
}

View File

@ -1,201 +0,0 @@
import { transformCodebase } from "../tools/transformCodebase";
import * as fs from "fs";
import { join as pathJoin, basename as pathBasename } from "path";
import { replaceImportsFromStaticInJsCode } from "./replacers/replaceImportsFromStaticInJsCode";
import { replaceImportsInCssCode } from "./replacers/replaceImportsInCssCode";
import { generateFtlFilesCodeFactory, pageIds } from "./generateFtl";
import { downloadBuiltinKeycloakTheme } from "../download-builtin-keycloak-theme";
import { mockTestingResourcesCommonPath, mockTestingResourcesPath, mockTestingSubDirOfPublicDirBasename } from "../mockTestingResourcesPath";
import { isInside } from "../tools/isInside";
import type { BuildOptions } from "./BuildOptions";
import { assert } from "tsafe/assert";
import { Reflect } from "tsafe/Reflect";
import { getLogger } from "../tools/logger";
export type BuildOptionsLike = BuildOptionsLike.Standalone | BuildOptionsLike.ExternalAssets;
export namespace BuildOptionsLike {
export type Common = {
themeName: string;
extraPages?: string[];
extraThemeProperties?: string[];
isSilent: boolean;
};
export type Standalone = Common & {
isStandalone: true;
urlPathname: string | undefined;
};
export type ExternalAssets = ExternalAssets.SameDomain | ExternalAssets.DifferentDomains;
export namespace ExternalAssets {
export type CommonExternalAssets = Common & {
isStandalone: false;
};
export type SameDomain = CommonExternalAssets & {
areAppAndKeycloakServerSharingSameDomain: true;
};
export type DifferentDomains = CommonExternalAssets & {
areAppAndKeycloakServerSharingSameDomain: false;
urlOrigin: string;
urlPathname: string | undefined;
};
}
}
{
const buildOptions = Reflect<BuildOptions>();
assert<typeof buildOptions extends BuildOptionsLike ? true : false>();
}
export async function generateKeycloakThemeResources(params: {
reactAppBuildDirPath: string;
keycloakThemeBuildingDirPath: string;
keycloakThemeEmailDirPath: string;
keycloakVersion: string;
buildOptions: BuildOptionsLike;
}): Promise<{ doBundlesEmailTemplate: boolean }> {
const { reactAppBuildDirPath, keycloakThemeBuildingDirPath, keycloakThemeEmailDirPath, keycloakVersion, buildOptions } = params;
const logger = getLogger({ isSilent: buildOptions.isSilent });
const themeDirPath = pathJoin(keycloakThemeBuildingDirPath, "src", "main", "resources", "theme", buildOptions.themeName, "login");
let allCssGlobalsToDefine: Record<string, string> = {};
transformCodebase({
"destDirPath": buildOptions.isStandalone ? pathJoin(themeDirPath, "resources", "build") : reactAppBuildDirPath,
"srcDirPath": reactAppBuildDirPath,
"transformSourceCode": ({ filePath, sourceCode }) => {
//NOTE: Prevent cycles, excludes the folder we generated for debug in public/
if (
buildOptions.isStandalone &&
isInside({
"dirPath": pathJoin(reactAppBuildDirPath, mockTestingSubDirOfPublicDirBasename),
filePath
})
) {
return undefined;
}
if (/\.css?$/i.test(filePath)) {
if (!buildOptions.isStandalone) {
return undefined;
}
const { cssGlobalsToDefine, fixedCssCode } = replaceImportsInCssCode({
"cssCode": sourceCode.toString("utf8")
});
allCssGlobalsToDefine = {
...allCssGlobalsToDefine,
...cssGlobalsToDefine
};
return { "modifiedSourceCode": Buffer.from(fixedCssCode, "utf8") };
}
if (/\.js?$/i.test(filePath)) {
if (!buildOptions.isStandalone && buildOptions.areAppAndKeycloakServerSharingSameDomain) {
return undefined;
}
const { fixedJsCode } = replaceImportsFromStaticInJsCode({
"jsCode": sourceCode.toString("utf8"),
buildOptions
});
return { "modifiedSourceCode": Buffer.from(fixedJsCode, "utf8") };
}
return buildOptions.isStandalone ? { "modifiedSourceCode": sourceCode } : undefined;
}
});
let doBundlesEmailTemplate: boolean;
email: {
if (!fs.existsSync(keycloakThemeEmailDirPath)) {
logger.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")
);
doBundlesEmailTemplate = false;
break email;
}
doBundlesEmailTemplate = true;
transformCodebase({
"srcDirPath": keycloakThemeEmailDirPath,
"destDirPath": pathJoin(themeDirPath, "..", "email")
});
}
const { generateFtlFilesCode } = generateFtlFilesCodeFactory({
"indexHtmlCode": fs.readFileSync(pathJoin(reactAppBuildDirPath, "index.html")).toString("utf8"),
"cssGlobalsToDefine": allCssGlobalsToDefine,
"buildOptions": buildOptions
});
[...pageIds, ...(buildOptions.extraPages ?? [])].forEach(pageId => {
const { ftlCode } = generateFtlFilesCode({ pageId });
fs.mkdirSync(themeDirPath, { "recursive": true });
fs.writeFileSync(pathJoin(themeDirPath, pageId), Buffer.from(ftlCode, "utf8"));
});
{
const tmpDirPath = pathJoin(themeDirPath, "..", "tmp_xxKdLpdIdLd");
await downloadBuiltinKeycloakTheme({
keycloakVersion,
"destDirPath": tmpDirPath,
isSilent: buildOptions.isSilent
});
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);
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(" ")
)
);
fs.writeFileSync(pathJoin(keycloakResourcesWithinPublicDirPath, ".gitignore"), Buffer.from("*", "utf8"));
fs.rmSync(tmpDirPath, { recursive: true, force: true });
}
fs.writeFileSync(
pathJoin(themeDirPath, "theme.properties"),
Buffer.from(["parent=keycloak", ...(buildOptions.extraThemeProperties ?? [])].join("\n\n"), "utf8")
);
return { doBundlesEmailTemplate };
}

View File

@ -1,65 +0,0 @@
import * as fs from "fs";
import { join as pathJoin } from "path";
import { assert } from "tsafe/assert";
import { Reflect } from "tsafe/Reflect";
import type { BuildOptions } from "./BuildOptions";
export type BuildOptionsLike = {
themeName: string;
};
{
const buildOptions = Reflect<BuildOptions>();
assert<typeof buildOptions extends BuildOptionsLike ? true : false>();
}
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;
keycloakThemeBuildingDirPath: string;
buildOptions: BuildOptionsLike;
}) {
const {
keycloakThemeBuildingDirPath,
keycloakVersion,
buildOptions: { themeName }
} = params;
fs.writeFileSync(
pathJoin(keycloakThemeBuildingDirPath, generateStartKeycloakTestingContainer.basename),
Buffer.from(
[
"#!/usr/bin/env 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 +0,0 @@
#!/usr/bin/env node
export * from "./keycloakify";
import { main } from "./keycloakify";
if (require.main === module) {
main().catch(e => console.error(e));
}

View File

@ -1,139 +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 * as fs from "fs";
import { readBuildOptions } from "./BuildOptions";
import { getLogger } from "../tools/logger";
import { getCliOptions } from "../tools/cliOptions";
import jar from "../tools/jar";
import { assert } from "tsafe/assert";
import type { Equals } from "tsafe";
const reactProjectDirPath = process.cwd();
export const keycloakThemeBuildingDirPath = pathJoin(reactProjectDirPath, "build_keycloak");
export const keycloakThemeEmailDirPath = pathJoin(keycloakThemeBuildingDirPath, "..", "keycloak_email");
export async function main() {
const { isSilent, hasExternalAssets } = getCliOptions(process.argv.slice(2));
const logger = getLogger({ isSilent });
logger.log("🔏 Building the keycloak theme...⌚");
const buildOptions = readBuildOptions({
"packageJson": fs.readFileSync(pathJoin(reactProjectDirPath, "package.json")).toString("utf8"),
"CNAME": (() => {
const cnameFilePath = pathJoin(reactProjectDirPath, "public", "CNAME");
if (!fs.existsSync(cnameFilePath)) {
return undefined;
}
return fs.readFileSync(cnameFilePath).toString("utf8");
})(),
"isExternalAssetsCliParamProvided": hasExternalAssets,
"isSilent": isSilent
});
const { doBundlesEmailTemplate } = await generateKeycloakThemeResources({
keycloakThemeBuildingDirPath,
keycloakThemeEmailDirPath,
"reactAppBuildDirPath": pathJoin(reactProjectDirPath, "build"),
buildOptions,
//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({
keycloakThemeBuildingDirPath,
doBundlesEmailTemplate,
buildOptions
});
switch (buildOptions.bundler) {
case "none":
logger.log("😱 Skipping bundling step, there will be no jar");
break;
case "keycloakify":
logger.log("🫶 Let keycloakify do its thang");
await jar({
"rootPath": pathJoin(keycloakThemeBuildingDirPath, "src", "main", "resources"),
"version": buildOptions.version,
"groupId": buildOptions.groupId,
"artifactId": buildOptions.artifactId,
"targetPath": jarFilePath
});
break;
case "mvn":
logger.log("🫙 Run maven to deliver a jar");
child_process.execSync("mvn package", { "cwd": keycloakThemeBuildingDirPath });
break;
default:
assert<Equals<typeof buildOptions.bundler, never>>(false);
}
// We want, however, to test in a container running the latest Keycloak version
const containerKeycloakVersion = "20.0.1";
generateStartKeycloakTestingContainer({
keycloakThemeBuildingDirPath,
"keycloakVersion": containerKeycloakVersion,
buildOptions
});
logger.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: ${buildOptions.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,86 +0,0 @@
import { ftlValuesGlobalName } from "../ftlValuesGlobalName";
import type { BuildOptions } from "../BuildOptions";
import { assert } from "tsafe/assert";
import { is } from "tsafe/is";
import { Reflect } from "tsafe/Reflect";
export type BuildOptionsLike = BuildOptionsLike.Standalone | BuildOptionsLike.ExternalAssets;
export namespace BuildOptionsLike {
export type Standalone = {
isStandalone: true;
};
export type ExternalAssets = {
isStandalone: false;
urlOrigin: string;
};
}
{
const buildOptions = Reflect<BuildOptions>();
assert(!is<BuildOptions.ExternalAssets.CommonExternalAssets>(buildOptions));
assert<typeof buildOptions extends BuildOptionsLike ? true : false>();
}
export function replaceImportsFromStaticInJsCode(params: { jsCode: string; buildOptions: BuildOptionsLike }): { fixedJsCode: string } {
/*
NOTE:
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.
When urlOrigin isn't defined we can assume the fixedJsCode
will always run in keycloak context.
*/
const { jsCode, buildOptions } = params;
const getReplaceArgs = (language: "js" | "css"): Parameters<typeof String.prototype.replace> => [
new RegExp(`([a-zA-Z_]+)\\.([a-zA-Z]+)=function\\(([a-zA-Z]+)\\){return"static\\/${language}\\/"`, "g"),
(...[, n, u, e]) => `
${n}[(function(){
var pd= Object.getOwnPropertyDescriptor(${n}, "p");
if( pd === undefined || pd.configurable ){
${
buildOptions.isStandalone
? `
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 ? "${buildOptions.urlOrigin}/" : p; },
set: function (value){ p = value;}
});
`
}
}
return "${u}";
})()] = function(${e}) { return "${buildOptions.isStandalone ? "/build/" : ""}static/${language}/"`
];
const fixedJsCode = jsCode
.replace(...getReplaceArgs("js"))
.replace(...getReplaceArgs("css"))
.replace(/([a-zA-Z]+\.[a-zA-Z]+)\+"static\//g, (...[, group]) =>
buildOptions.isStandalone
? `window.${ftlValuesGlobalName}.url.resourcesPath + "/build/static/`
: `("${ftlValuesGlobalName}" in window ? "${buildOptions.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]) =>
buildOptions.isStandalone
? `".chunk.css",${group1} = window.${ftlValuesGlobalName}.url.resourcesPath + "/build/" + ${group3},`
: `".chunk.css",${group1} = ("${ftlValuesGlobalName}" in window ? "${buildOptions.urlOrigin}/" : ${group2}) + ${group3},`
);
return { fixedJsCode };
}

View File

@ -1,64 +0,0 @@
import * as crypto from "crypto";
import type { BuildOptions } from "../BuildOptions";
import { assert } from "tsafe/assert";
import { is } from "tsafe/is";
import { Reflect } from "tsafe/Reflect";
export type BuildOptionsLike = {
urlPathname: string | undefined;
};
{
const buildOptions = Reflect<BuildOptions>();
assert(!is<BuildOptions.ExternalAssets.CommonExternalAssets>(buildOptions));
assert<typeof buildOptions extends BuildOptionsLike ? true : false>();
}
export function replaceImportsInCssCode(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)
);
let fixedCssCode = cssCode;
Object.keys(cssGlobalsToDefine).forEach(
cssVariableName =>
//NOTE: split/join pattern ~ replace all
(fixedCssCode = fixedCssCode.split(cssGlobalsToDefine[cssVariableName]).join(`var(--${cssVariableName})`))
);
return { fixedCssCode, cssGlobalsToDefine };
}
export function generateCssCodeToDefineGlobals(params: { cssGlobalsToDefine: Record<string, string>; buildOptions: BuildOptionsLike }): {
cssCodeToPrependInHead: string;
} {
const { cssGlobalsToDefine, buildOptions } = params;
return {
"cssCodeToPrependInHead": [
":root {",
...Object.keys(cssGlobalsToDefine)
.map(cssVariableName =>
[
`--${cssVariableName}:`,
cssGlobalsToDefine[cssVariableName].replace(
new RegExp(`url\\(${(buildOptions.urlPathname ?? "/").replace(/\//g, "\\/")}`, "g"),
"url(${url.resourcesPath}/build/"
)
].join(" ")
)
.map(line => ` ${line};`),
"}"
].join("\n")
};
}

View File

@ -1,47 +0,0 @@
import type { BuildOptions } from "../BuildOptions";
import { assert } from "tsafe/assert";
import { is } from "tsafe/is";
import { Reflect } from "tsafe/Reflect";
export type BuildOptionsLike = BuildOptionsLike.Standalone | BuildOptionsLike.ExternalAssets;
export namespace BuildOptionsLike {
export type Common = {
urlPathname: string | undefined;
};
export type Standalone = Common & {
isStandalone: true;
};
export type ExternalAssets = Common & {
isStandalone: false;
urlOrigin: string;
};
}
{
const buildOptions = Reflect<BuildOptions>();
assert(!is<BuildOptions.ExternalAssets.CommonExternalAssets>(buildOptions));
assert<typeof buildOptions extends BuildOptionsLike ? true : false>();
}
export function replaceImportsInInlineCssCode(params: { cssCode: string; buildOptions: BuildOptionsLike }): {
fixedCssCode: string;
} {
const { cssCode, buildOptions } = params;
const fixedCssCode = cssCode.replace(
buildOptions.urlPathname === undefined
? /url\(["']?\/([^/][^)"']+)["']?\)/g
: new RegExp(`url\\(["']?${buildOptions.urlPathname}([^)"']+)["']?\\)`, "g"),
(...[, group]) =>
`url(${
buildOptions.isStandalone ? "${url.resourcesPath}/build/" + group : buildOptions.urlOrigin + (buildOptions.urlPathname ?? "/") + group
})`
);
return { fixedCssCode };
}

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,15 +0,0 @@
import parseArgv from "minimist";
export type CliOptions = {
isSilent: boolean;
hasExternalAssets: boolean;
};
export const getCliOptions = (processArgv: string[]): CliOptions => {
const argv = parseArgv(processArgv);
return {
isSilent: typeof argv["silent"] === "boolean" ? argv["silent"] : false,
hasExternalAssets: typeof argv["external-assets"] === "boolean" ? argv["external-assets"] : false
};
};

View File

@ -1,54 +0,0 @@
import { Readable } from "stream";
const crc32tab = [
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
];
/**
*
* @param input either a byte stream, a string or a buffer, you want to have the checksum for
* @returns a promise for a checksum (uint32)
*/
export function crc32(input: Readable | String | Buffer): Promise<number> {
if (typeof input === "string") {
let crc = ~0;
for (let i = 0; i < input.length; i++) crc = (crc >>> 8) ^ crc32tab[(crc ^ input.charCodeAt(i)) & 0xff];
return Promise.resolve((crc ^ -1) >>> 0);
} else if (input instanceof Buffer) {
let crc = ~0;
for (let i = 0; i < input.length; i++) crc = (crc >>> 8) ^ crc32tab[(crc ^ input[i]) & 0xff];
return Promise.resolve((crc ^ -1) >>> 0);
} else if (input instanceof Readable) {
return new Promise<number>((resolve, reject) => {
let crc = ~0;
input.on("end", () => resolve((crc ^ -1) >>> 0));
input.on("error", e => reject(e));
input.on("data", (chunk: Buffer) => {
for (let i = 0; i < chunk.length; i++) crc = (crc >>> 8) ^ crc32tab[(crc ^ chunk[i]) & 0xff];
});
});
} else {
throw new Error("Unsupported input " + typeof input);
}
}

View File

@ -1,61 +0,0 @@
import { PassThrough, Readable, TransformCallback, Writable } from "stream";
import { pipeline } from "stream/promises";
import { deflateRaw as deflateRawCb, createDeflateRaw } from "zlib";
import { promisify } from "util";
import { crc32 } from "./crc32";
import tee from "./tee";
const deflateRaw = promisify(deflateRawCb);
/**
* A stream transformer that records the number of bytes
* passed in its `size` property.
*/
class ByteCounter extends PassThrough {
size: number = 0;
_transform(chunk: any, encoding: BufferEncoding, callback: TransformCallback) {
if ("length" in chunk) this.size += chunk.length;
super._transform(chunk, encoding, callback);
}
}
/**
* @param data buffer containing the data to be compressed
* @returns a buffer containing the compressed/deflated data and the crc32 checksum
* of the source data
*/
export async function deflateBuffer(data: Buffer) {
const [deflated, checksum] = await Promise.all([deflateRaw(data), crc32(data)]);
return { deflated, crc32: checksum };
}
/**
* @param input a byte stream, containing data to be compressed
* @param sink a method that will accept chunks of compressed data; We don't pass
* a writable here, since we don't want the writablestream to be closed after
* a single file
* @returns a promise, which will resolve with the crc32 checksum and the
* compressed size
*/
export async function deflateStream(input: Readable, sink: (chunk: Buffer) => void) {
const deflateWriter = new Writable({
write(chunk, _, callback) {
sink(chunk);
callback();
}
});
// tee the input stream, so we can compress and calc crc32 in parallel
const [rs1, rs2] = tee(input);
const byteCounter = new ByteCounter();
const [_, crc] = await Promise.all([
// pipe input into zip compressor, count the bytes
// returned and pass compressed data to the sink
pipeline(rs1, createDeflateRaw(), byteCounter, deflateWriter),
// calc checksum
crc32(rs2)
]);
return { crc32: crc, compressedSize: byteCounter.size };
}

View File

@ -1,289 +0,0 @@
import { dirname as pathDirname, basename as pathBasename, join as pathJoin } from "path";
import { createReadStream, createWriteStream, unlinkSync } from "fs";
import { stat, mkdir, unlink, readFile, writeFile } from "fs/promises";
import { transformCodebase } from "./transformCodebase";
import { createHash } from "crypto";
import http from "http";
import https from "https";
import { createInflateRaw } from "zlib";
import type { Readable } from "stream";
function hash(s: string) {
return createHash("sha256").update(s).digest("hex");
}
async function maybeReadFile(path: string) {
try {
return await readFile(path, "utf-8");
} catch (error) {
if ((error as Error & { code: string }).code === "ENOENT") return undefined;
throw error;
}
}
async function maybeStat(path: string) {
try {
return await stat(path);
} catch (error) {
if ((error as Error & { code: string }).code === "ENOENT") return undefined;
throw error;
}
}
/**
* Download a file from `url` to `dir`. Will try to avoid downloading existing
* files by using an `{hash(url)}.etag` file. If this file exists, we add an
* etag headear, so server can tell us if file changed and we should re-download
* or if our file is up-to-date.
*
* Warning, this method assumes that the target filename can be extracted from
* url, content-disposition headers are ignored.
*
* If the target directory does not exist, it will be created.
*
* If the target file exists and is out of date, it will be overwritten.
* If the target file exists and there is no etag file, the target file will
* be overwritten.
*
* @param url download url
* @param dir target directory
* @returns promise for the full path of the downloaded file
*/
async function download(url: string, dir: string): Promise<string> {
await mkdir(dir, { recursive: true });
const filename = pathBasename(url);
const filepath = pathJoin(dir, filename);
// If downloaded file exists already and has an `.etag` companion file,
// read the etag from that file. This will avoid re-downloading the file
// if it is up to date.
const exists = await maybeStat(filepath);
const etagFilepath = pathJoin(dir, "_" + hash(url).substring(0, 15) + ".etag");
const etag = !exists ? undefined : await maybeReadFile(etagFilepath);
return new Promise((resolve, reject) => {
// use inner method to allow following redirects
function request(url1: URL) {
const headers: Record<string, string> = {};
if (etag) headers["If-None-Match"] = etag;
(url1.protocol === "https:" ? https : http).get(url1, { headers }, response => {
if (response.statusCode === 301 || response.statusCode === 302) {
// follow redirects
request(new URL(response.headers.location!!));
} else if (response.statusCode === 304) {
// up-to-date, resolve now
resolve(filepath);
} else if (response.statusCode !== 200) {
reject(new Error(`Request to ${url1} returned status ${response.statusCode}.`));
} else {
const fp = createWriteStream(filepath, { autoClose: true });
fp.on("err", e => {
fp.close();
unlinkSync(filepath);
reject(e);
});
fp.on("finish", async () => {
// when targetfile has been written, write etag file so that
// next time around we don't need to re-download
const responseEtag = response.headers.etag;
if (responseEtag) await writeFile(etagFilepath, responseEtag, "utf-8");
resolve(filepath);
});
response.pipe(fp);
}
});
}
request(new URL(url));
});
}
/**
* @typedef
* @type MultiError = Error & { cause: Error[] }
*/
/**
* Extract the archive `zipFile` into the directory `dir`. If `archiveDir` is given,
* only that directory will be extracted, stripping the given path components.
*
* If dir does not exist, it will be created.
*
* If any archive file exists, it will be overwritten.
*
* Will unzip using all available nodejs worker threads.
*
* Will try to clean up extracted files on failure.
*
* If unpacking fails, will either throw an regular error, or
* possibly an `MultiError`, which contains a `cause` field with
* a number of root cause errors.
*
* Warning this method is not optimized for continuous reading of the zip
* archive, but is a trade-off between simplicity and allowing extraction
* of a single directory from the archive.
*
* @param zipFile the file to unzip
* @param dir the target directory
* @param archiveDir if given, unpack only files from this archive directory
* @throws {MultiError} error
* @returns Promise for a list of full file paths pointing to actually extracted files
*/
async function unzip(zipFile: string, dir: string, archiveDir?: string): Promise<string[]> {
await mkdir(dir, { recursive: true });
const promises: Promise<string>[] = [];
// Iterate over all files in the zip, skip files which are not in archiveDir,
// if given.
for await (const record of iterateZipArchive(zipFile)) {
const { path: recordPath, createReadStream: createRecordReadStream } = record;
const filePath = pathJoin(dir, recordPath);
const parent = pathDirname(filePath);
if (archiveDir && !recordPath.startsWith(archiveDir)) continue;
promises.push(
new Promise<string>(async (resolve, reject) => {
await mkdir(parent, { recursive: true });
// Pull the file out of the archive, write it to the target directory
const input = createRecordReadStream();
const output = createWriteStream(filePath);
output.on("error", e => reject(Object.assign(e, { filePath })));
output.on("finish", () => resolve(filePath));
input.pipe(output);
})
);
}
// Wait until _all_ files are either extracted or failed
const results = await Promise.allSettled(promises);
const success = results.filter(r => r.status === "fulfilled").map(r => (r as PromiseFulfilledResult<string>).value);
const failure = results.filter(r => r.status === "rejected").map(r => (r as PromiseRejectedResult).reason);
// If any extraction failed, try to clean up, then throw a MultiError,
// which has a `cause` field, containing a list of root cause errors.
if (failure.length) {
await Promise.all(success.map(path => unlink(path)));
await Promise.all(failure.map(e => e && e.path && unlink(e.path as string)));
const e = new Error("Failed to extract: " + failure.map(e => e.message).join(";"));
(e as any).cause = failure;
throw e;
}
return success;
}
/**
*
* @param file file to read
* @param start first byte to read
* @param end last byte to read
* @returns Promise of a buffer of read bytes
*/
async function readFileChunk(file: string, start: number, end: number): Promise<Buffer> {
const chunks: Buffer[] = [];
return new Promise((resolve, reject) => {
const stream = createReadStream(file, { start, end });
stream.on("error", e => reject(e));
stream.on("end", () => resolve(Buffer.concat(chunks)));
stream.on("data", chunk => chunks.push(chunk as Buffer));
});
}
type ZipRecord = {
path: string;
createReadStream: () => Readable;
compressionMethod: "deflate" | undefined;
};
type ZipRecordGenerator = AsyncGenerator<ZipRecord, void, unknown>;
/**
* Iterate over all records of a zipfile, and yield a ZipRecord.
* Use `record.createReadStream()` to actually read the file.
*
* Warning this method will only work with single-disk zip files.
* Warning this method may fail if the zip archive has an crazy amount
* of files and the central directory is not fully contained within the
* last 65k bytes of the zip file.
*
* @param zipFile
* @returns AsyncGenerator which will yield ZipRecords
*/
async function* iterateZipArchive(zipFile: string): ZipRecordGenerator {
// Need to know zip file size before we can do anything else
const { size } = await stat(zipFile);
const chunkSize = 65_535 + 22 + 1; // max comment size + end header size + wiggle
// Read last ~65k bytes. Zip files have an comment up to 65_535 bytes at the very end,
// before that comes the zip central directory end header.
let chunk = await readFileChunk(zipFile, size - chunkSize, size);
const unread = size - chunk.length;
let i = chunk.length - 4;
let found = false;
// Find central directory end header, reading backwards from the end
while (!found && i-- > 0) if (chunk[i] === 0x50 && chunk.readUInt32LE(i) === 0x06054b50) found = true;
if (!found) throw new Error("Not a zip file");
// This method will fail on a multi-disk zip, so bail early.
if (chunk.readUInt16LE(i + 4) !== 0) throw new Error("Multi-disk zip not supported");
let nFiles = chunk.readUint16LE(i + 10);
// Get the position of the central directory
const directorySize = chunk.readUint32LE(i + 12);
const directoryOffset = chunk.readUint32LE(i + 16);
if (directoryOffset === 0xffff_ffff) throw new Error("zip64 not supported");
if (directoryOffset > size) throw new Error(`Central directory offset ${directoryOffset} is outside file`);
i = directoryOffset - unread;
// If i < 0, it means that the central directory is not contained within `chunk`
if (i < 0) {
chunk = await readFileChunk(zipFile, directoryOffset, directoryOffset + directorySize);
i = 0;
}
// Now iterate the central directory records, yield an `ZipRecord` for every entry
while (nFiles-- > 0) {
// Check for marker bytes
if (chunk.readUInt32LE(i) !== 0x02014b50) throw new Error("No central directory record at position " + (unread + i));
const compressionMethod = ({ 8: "deflate" } as const)[chunk.readUint16LE(i + 10)];
const compressedFileSize = chunk.readUint32LE(i + 20);
const filenameLength = chunk.readUint16LE(i + 28);
const extraLength = chunk.readUint16LE(i + 30);
const commentLength = chunk.readUint16LE(i + 32);
// Start of the actual content byte stream is after the 'local' record header,
// which is 30 bytes long plus filename and extra field
const start = chunk.readUint32LE(i + 42) + 30 + filenameLength + extraLength;
const end = start + compressedFileSize;
const filename = chunk.slice(i + 46, i + 46 + filenameLength).toString("utf-8");
const createRecordReadStream = () => {
const input = createReadStream(zipFile, { start, end });
if (compressionMethod === "deflate") {
const inflate = createInflateRaw();
input.pipe(inflate);
return inflate;
}
return input;
};
if (end > start) yield { path: filename, createReadStream: createRecordReadStream, compressionMethod };
// advance pointer to next central directory entry
i += 46 + filenameLength + extraLength + commentLength;
}
}
export async function downloadAndUnzip({
url,
destDirPath,
pathOfDirToExtractInArchive,
cacheDirPath
}: {
isSilent: boolean;
url: string;
destDirPath: string;
pathOfDirToExtractInArchive?: string;
cacheDirPath: string;
}) {
const downloadHash = hash(JSON.stringify({ url, pathOfDirToExtractInArchive })).substring(0, 15);
const extractDirPath = pathJoin(cacheDirPath, `_${downloadHash}`);
const zipFilepath = await download(url, cacheDirPath);
const zipMtime = (await stat(zipFilepath)).mtimeMs;
const unzipMtime = (await maybeStat(extractDirPath))?.mtimeMs;
if (!unzipMtime || zipMtime > unzipMtime) await unzip(zipFilepath, extractDirPath, pathOfDirToExtractInArchive);
const srcDirPath = pathOfDirToExtractInArchive === undefined ? extractDirPath : pathJoin(extractDirPath, pathOfDirToExtractInArchive);
transformCodebase({ srcDirPath, destDirPath });
}

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,17 +0,0 @@
import { getProjectRoot } from "./getProjectRoot";
import { join as pathJoin } from "path";
import { constants } from "fs";
import { chmod, stat } from "fs/promises";
(async () => {
const { bin } = await import(pathJoin(getProjectRoot(), "package.json"));
const promises = Object.values<string>(bin).map(async scriptPath => {
const fullPath = pathJoin(getProjectRoot(), scriptPath);
const oldMode = (await stat(fullPath)).mode;
const newMode = oldMode | constants.S_IXUSR | constants.S_IXGRP | constants.S_IXOTH;
await chmod(fullPath, newMode);
});
await Promise.all(promises);
})();

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,102 +0,0 @@
import { Readable, Transform } from "stream";
import { dirname, relative, sep } from "path";
import { createWriteStream } from "fs";
import walk from "./walk";
import type { ZipSource } from "./zip";
import zip from "./zip";
import { mkdir } from "fs/promises";
/** Trim leading whitespace from every line */
const trimIndent = (s: string) => s.replace(/(\n)\s+/g, "$1");
type JarArgs = {
rootPath: string;
targetPath: string;
groupId: string;
artifactId: string;
version: string;
};
/**
* Create a jar archive, using the resources found at `rootPath` (a directory) and write the
* archive to `targetPath` (a file). Use `groupId`, `artifactId` and `version` to define
* the contents of the pom.properties file which is going to be added to the archive.
*/
export default async function jar({ groupId, artifactId, version, rootPath, targetPath }: JarArgs) {
const manifest: ZipSource = {
path: "META-INF/MANIFEST.MF",
data: Buffer.from(
trimIndent(
`Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Created-By: Keycloakify
Built-By: unknown
Build-Jdk: 19.0.0`
)
)
};
const pomProps: ZipSource = {
path: `META-INF/maven/${groupId}/${artifactId}/pom.properties`,
data: Buffer.from(
trimIndent(
`# Generated by keycloakify
# ${new Date()}
artifactId=${artifactId}
groupId=${groupId}
version=${version}`
)
)
};
/**
* Convert every path entry to a ZipSource record, and when all records are
* processed, append records for MANIFEST.mf and pom.properties
*/
const pathToRecord = () =>
new Transform({
objectMode: true,
transform: function (fsPath, _, cb) {
const path = relative(rootPath, fsPath).split(sep).join("/");
this.push({ path, fsPath });
cb();
},
final: function () {
this.push(manifest);
this.push(pomProps);
this.push(null);
}
});
await mkdir(dirname(targetPath), { recursive: true });
// Create an async pipeline, wait until everything is fully processed
await new Promise<void>((resolve, reject) => {
// walk all files in `rootPath` recursively
Readable.from(walk(rootPath))
// transform every path into a ZipSource object
.pipe(pathToRecord())
// let the zip lib convert all ZipSource objects into a byte stream
.pipe(zip())
// write that byte stream to targetPath
.pipe(createWriteStream(targetPath, { encoding: "binary" }))
.on("finish", () => resolve())
.on("error", e => reject(e));
});
}
/**
* Standalone usage, call e.g. `ts-node jar.ts dirWithSources some-jar.jar`
*/
if (require.main === module) {
const main = () =>
jar({
rootPath: process.argv[2],
targetPath: process.argv[3],
artifactId: process.env.ARTIFACT_ID ?? "artifact",
groupId: process.env.GROUP_ID ?? "group",
version: process.env.VERSION ?? "1.0.0"
});
main().catch(e => console.error(e));
}

View File

@ -1,27 +0,0 @@
type LoggerOpts = {
force?: boolean;
};
type Logger = {
log: (message: string, opts?: LoggerOpts) => void;
warn: (message: string) => void;
error: (message: string) => void;
};
export const getLogger = ({ isSilent }: { isSilent?: boolean } = {}): Logger => {
return {
log: (message, { force } = {}) => {
if (isSilent && !force) {
return;
}
console.log(message);
},
warn: message => {
console.warn(message);
},
error: message => {
console.error(message);
}
};
};

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,37 +0,0 @@
import { PassThrough, Readable } from "stream";
export default function tee(input: Readable) {
const a = new PassThrough();
const b = new PassThrough();
let aFull = false;
let bFull = false;
a.on("drain", () => {
aFull = false;
if (!aFull && !bFull) input.resume();
});
b.on("drain", () => {
bFull = false;
if (!aFull && !bFull) input.resume();
});
input.on("error", e => {
a.emit("error", e);
b.emit("error", e);
});
input.on("data", chunk => {
aFull = !a.write(chunk);
bFull = !b.write(chunk);
if (aFull || bFull) input.pause();
});
input.on("end", () => {
a.end();
b.end();
});
return [a, b] as const;
}

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,19 +0,0 @@
import { readdir } from "fs/promises";
import { resolve } from "path";
/**
* Asynchronously and recursively walk a directory tree, yielding every file and directory
* found
*
* @param root the starting directory
* @returns AsyncGenerator
*/
export default async function* walk(root: string): AsyncGenerator<string, void, void> {
for (const entry of await readdir(root, { withFileTypes: true })) {
const absolutePath = resolve(root, entry.name);
if (entry.isDirectory()) {
yield absolutePath;
yield* walk(absolutePath);
} else yield absolutePath;
}
}

View File

@ -1,246 +0,0 @@
import { Transform, TransformOptions } from "stream";
import { createReadStream } from "fs";
import { stat } from "fs/promises";
import { Blob } from "buffer";
import { deflateBuffer, deflateStream } from "./deflate";
/**
* Zip source
* @property filename the name of the entry in the archie
* @property path of the source file, if the source is an actual file
* @property data the actual data buffer, if the source is constructed in-memory
*/
export type ZipSource = { path: string } & ({ fsPath: string } | { data: Buffer });
export type ZipRecord = {
path: string;
compression: "deflate" | undefined;
uncompressedSize: number;
compressedSize?: number;
crc32?: number;
offset?: number;
};
/**
* @returns the actual byte size of an string
*/
function utf8size(s: string) {
return new Blob([s]).size;
}
/**
* @param record
* @returns a buffer representing a Zip local header
* @link https://en.wikipedia.org/wiki/ZIP_(file_format)#Local_file_header
*/
function localHeader(record: ZipRecord) {
const { path, compression, uncompressedSize } = record;
const filenameSize = utf8size(path);
const buf = Buffer.alloc(30 + filenameSize);
buf.writeUInt32LE(0x04_03_4b_50, 0); // local header signature
buf.writeUInt16LE(10, 4); // min version
// we write 0x08 because crc and compressed size are unknown at
buf.writeUInt16LE(0x08, 6); // general purpose bit flag
buf.writeUInt16LE(compression ? ({ "deflate": 8 } as const)[compression] : 0, 8);
buf.writeUInt16LE(0, 10); // modified time
buf.writeUInt16LE(0, 12); // modified date
buf.writeUInt32LE(0, 14); // crc unknown
buf.writeUInt32LE(0, 18); // compressed size unknown
buf.writeUInt32LE(uncompressedSize, 22);
buf.writeUInt16LE(filenameSize, 26);
buf.writeUInt16LE(0, 28); // extra field length
buf.write(path, 30, "utf-8");
return buf;
}
/**
* @param record
* @returns a buffer representing a Zip central header
* @link https://en.wikipedia.org/wiki/ZIP_(file_format)#Central_directory_file_header
*/
function centralHeader(record: ZipRecord) {
const { path, compression, crc32, compressedSize, uncompressedSize, offset } = record;
const filenameSize = utf8size(path);
const buf = Buffer.alloc(46 + filenameSize);
const isFile = !path.endsWith("/");
if (typeof offset === "undefined") throw new Error("Illegal argument");
// we don't want to deal with possibly messed up file or directory
// permissions, so we ignore the original permissions
const externalAttr = isFile ? 0x81a40000 : 0x41ed0000;
buf.writeUInt32LE(0x0201_4b50, 0); // central header signature
buf.writeUInt16LE(10, 4); // version
buf.writeUInt16LE(10, 6); // min version
buf.writeUInt16LE(0, 8); // general purpose bit flag
buf.writeUInt16LE(compression ? ({ "deflate": 8 } as const)[compression] : 0, 10);
buf.writeUInt16LE(0, 12); // modified time
buf.writeUInt16LE(0, 14); // modified date
buf.writeUInt32LE(crc32 || 0, 16);
buf.writeUInt32LE(compressedSize || 0, 20);
buf.writeUInt32LE(uncompressedSize, 24);
buf.writeUInt16LE(filenameSize, 28);
buf.writeUInt16LE(0, 30); // extra field length
buf.writeUInt16LE(0, 32); // comment field length
buf.writeUInt16LE(0, 34); // disk number
buf.writeUInt16LE(0, 36); // internal
buf.writeUInt32LE(externalAttr, 38); // external
buf.writeUInt32LE(offset, 42); // offset where file starts
buf.write(path, 46, "utf-8");
return buf;
}
/**
* @returns a buffer representing an Zip End-Of-Central-Directory block
* @link https://en.wikipedia.org/wiki/ZIP_(file_format)#End_of_central_directory_record_(EOCD)
*/
function eocd({ offset, cdSize, nRecords }: { offset: number; cdSize: number; nRecords: number }) {
const buf = Buffer.alloc(22);
buf.writeUint32LE(0x06054b50, 0); // eocd signature
buf.writeUInt16LE(0, 4); // disc number
buf.writeUint16LE(0, 6); // disc where central directory starts
buf.writeUint16LE(nRecords, 8); // records on this disc
buf.writeUInt16LE(nRecords, 10); // records total
buf.writeUInt32LE(cdSize, 12); // byte size of cd
buf.writeUInt32LE(offset, 16); // cd offset
buf.writeUint16LE(0, 20); // comment length
return buf;
}
/**
* @returns a stream Transform, which reads a stream of ZipRecords and
* writes a bytestream
*/
export default function zip() {
/**
* This is called when the input stream of ZipSource items is finished.
* Will write central directory and end-of-central-direcotry blocks.
*/
const final = () => {
// write central directory
let cdSize = 0;
for (const record of records) {
const head = centralHeader(record);
zipTransform.push(head);
cdSize += head.length;
}
// write end-of-central-directory
zipTransform.push(eocd({ offset, cdSize, nRecords: records.length }));
// signal stream end
zipTransform.push(null);
};
/**
* Write a directory entry to the archive
* @param path
*/
const writeDir = async (path: string) => {
const record: ZipRecord = {
path: path + "/",
offset,
compression: undefined,
uncompressedSize: 0
};
const head = localHeader(record);
zipTransform.push(head);
records.push(record);
offset += head.length;
};
/**
* Write a file entry to the archive
* @param archivePath path of the file in archive
* @param fsPath path to file on filesystem
* @param size of the actual, uncompressed, file
*/
const writeFile = async (archivePath: string, fsPath: string, size: number) => {
const record: ZipRecord = {
path: archivePath,
offset,
compression: "deflate",
uncompressedSize: size
};
const head = localHeader(record);
zipTransform.push(head);
const { crc32, compressedSize } = await deflateStream(createReadStream(fsPath), chunk => zipTransform.push(chunk));
record.crc32 = crc32;
record.compressedSize = compressedSize;
records.push(record);
offset += head.length + compressedSize;
};
/**
* Write archive record based on filesystem file or directory
* @param archivePath path of item in archive
* @param fsPath path to item on filesystem
*/
const writeFromPath = async (archivePath: string, fsPath: string) => {
const fileStats = await stat(fsPath);
fileStats.isDirectory() ? await writeDir(archivePath) /**/ : await writeFile(archivePath, fsPath, fileStats.size) /**/;
};
/**
* Write archive record based on data in a buffer
* @param path
* @param data
*/
const writeFromBuffer = async (path: string, data: Buffer) => {
const { deflated, crc32 } = await deflateBuffer(data);
const record: ZipRecord = {
path,
compression: "deflate",
crc32,
uncompressedSize: data.length,
compressedSize: deflated.length,
offset
};
const head = localHeader(record);
zipTransform.push(head);
zipTransform.push(deflated);
records.push(record);
offset += head.length + deflated.length;
};
/**
* Write an archive record
* @param source
*/
const writeRecord = async (source: ZipSource) => {
if ("fsPath" in source) await writeFromPath(source.path, source.fsPath);
else if ("data" in source) await writeFromBuffer(source.path, source.data);
else throw new Error("Illegal argument " + typeof source + " " + JSON.stringify(source));
};
/**
* The actual stream transform function
* @param source
* @param _ encoding, ignored
* @param cb
*/
const transform: TransformOptions["transform"] = async (source: ZipSource, _, cb) => {
await writeRecord(source);
cb();
};
/** offset and records keep local state during processing */
let offset = 0;
const records: ZipRecord[] = [];
const zipTransform = new Transform({
readableObjectMode: false,
writableObjectMode: true,
transform,
final
});
return zipTransform;
}

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,100 +0,0 @@
import React, { lazy, Suspense } from "react";
import { __unsafe_useI18n as useI18n } from "./i18n";
import DefaultTemplate from "./Template";
import type { KcContextBase } from "./getKcContext/KcContextBase";
import type { PageProps } from "./KcProps";
import type { I18nBase } from "./i18n";
import type { SetOptional } from "./tools/SetOptional";
const Login = lazy(() => import("./pages/Login"));
const Register = lazy(() => import("./pages/Register"));
const RegisterUserProfile = lazy(() => import("./pages/RegisterUserProfile"));
const Info = lazy(() => import("./pages/Info"));
const Error = lazy(() => import("./pages/Error"));
const LoginResetPassword = lazy(() => import("./pages/LoginResetPassword"));
const LoginVerifyEmail = lazy(() => import("./pages/LoginVerifyEmail"));
const Terms = lazy(() => import("./pages/Terms"));
const LoginOtp = lazy(() => import("./pages/LoginOtp"));
const LoginPassword = lazy(() => import("./pages/LoginPassword"));
const LoginUsername = lazy(() => import("./pages/LoginUsername"));
const WebauthnAuthenticate = lazy(() => import("./pages/WebauthnAuthenticate"));
const LoginUpdatePassword = lazy(() => import("./pages/LoginUpdatePassword"));
const LoginUpdateProfile = lazy(() => import("./pages/LoginUpdateProfile"));
const LoginIdpLinkConfirm = lazy(() => import("./pages/LoginIdpLinkConfirm"));
const LoginPageExpired = lazy(() => import("./pages/LoginPageExpired"));
const LoginIdpLinkEmail = lazy(() => import("./pages/LoginIdpLinkEmail"));
const LoginConfigTotp = lazy(() => import("./pages/LoginConfigTotp"));
const LogoutConfirm = lazy(() => import("./pages/LogoutConfirm"));
const UpdateUserProfile = lazy(() => import("./pages/UpdateUserProfile"));
const IdpReviewUserProfile = lazy(() => import("./pages/IdpReviewUserProfile"));
export default function KcApp(props_: SetOptional<PageProps<KcContextBase, I18nBase>, "Template">) {
const { kcContext, i18n: userProvidedI18n, Template = DefaultTemplate, ...kcProps } = props_;
const i18n = (function useClosure() {
const i18n = useI18n({
kcContext,
"extraMessages": {},
"doSkip": userProvidedI18n !== undefined
});
return userProvidedI18n ?? i18n;
})();
if (i18n === null) {
return null;
}
const commonProps = { i18n, Template, ...kcProps };
return (
<Suspense>
{(() => {
switch (kcContext.pageId) {
case "login.ftl":
return <Login {...{ kcContext, ...commonProps }} />;
case "register.ftl":
return <Register {...{ kcContext, ...commonProps }} />;
case "register-user-profile.ftl":
return <RegisterUserProfile {...{ kcContext, ...commonProps }} />;
case "info.ftl":
return <Info {...{ kcContext, ...commonProps }} />;
case "error.ftl":
return <Error {...{ kcContext, ...commonProps }} />;
case "login-reset-password.ftl":
return <LoginResetPassword {...{ kcContext, ...commonProps }} />;
case "login-verify-email.ftl":
return <LoginVerifyEmail {...{ kcContext, ...commonProps }} />;
case "terms.ftl":
return <Terms {...{ kcContext, ...commonProps }} />;
case "login-otp.ftl":
return <LoginOtp {...{ kcContext, ...commonProps }} />;
case "login-username.ftl":
return <LoginUsername {...{ kcContext, ...commonProps }} />;
case "login-password.ftl":
return <LoginPassword {...{ kcContext, ...commonProps }} />;
case "webauthn-authenticate.ftl":
return <WebauthnAuthenticate {...{ kcContext, ...commonProps }} />;
case "login-update-password.ftl":
return <LoginUpdatePassword {...{ kcContext, ...commonProps }} />;
case "login-update-profile.ftl":
return <LoginUpdateProfile {...{ kcContext, ...commonProps }} />;
case "login-idp-link-confirm.ftl":
return <LoginIdpLinkConfirm {...{ kcContext, ...commonProps }} />;
case "login-idp-link-email.ftl":
return <LoginIdpLinkEmail {...{ kcContext, ...commonProps }} />;
case "login-page-expired.ftl":
return <LoginPageExpired {...{ kcContext, ...commonProps }} />;
case "login-config-totp.ftl":
return <LoginConfigTotp {...{ kcContext, ...commonProps }} />;
case "logout-confirm.ftl":
return <LogoutConfirm {...{ kcContext, ...commonProps }} />;
case "update-user-profile.ftl":
return <UpdateUserProfile {...{ kcContext, ...commonProps }} />;
case "idp-review-user-profile.ftl":
return <IdpReviewUserProfile {...{ kcContext, ...commonProps }} />;
}
})()}
</Suspense>
);
}

View File

@ -1,239 +0,0 @@
import { allPropertiesValuesToUndefined } from "./tools/allPropertiesValuesToUndefined";
import { assert } from "tsafe/assert";
import type { KcContextBase } from "./getKcContext";
import type { ReactNode } from "react";
import { I18nBase } from "./i18n";
/** 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"
| "kcWebAuthnDefaultIcon"
| "kcFormClass"
| "kcFormGroupErrorClass"
| "kcLabelClass"
| "kcInputClass"
| "kcInputErrorMessageClass"
| "kcInputWrapperClass"
| "kcFormOptionsClass"
| "kcFormButtonsClass"
| "kcFormSettingClass"
| "kcTextareaClass"
| "kcInfoAreaClass"
| "kcFormGroupHeader"
| "kcButtonClass"
| "kcButtonPrimaryClass"
| "kcButtonDefaultClass"
| "kcButtonLargeClass"
| "kcButtonBlockClass"
| "kcInputLargeClass"
| "kcSrOnlyClass"
| "kcSelectAuthListClass"
| "kcSelectAuthListItemClass"
| "kcSelectAuthListItemFillClass"
| "kcSelectAuthListItemInfoClass"
| "kcSelectAuthListItemLeftClass"
| "kcSelectAuthListItemBodyClass"
| "kcSelectAuthListItemDescriptionClass"
| "kcSelectAuthListItemHeadingClass"
| "kcSelectAuthListItemHelpTextClass"
| "kcSelectAuthListItemIconPropertyClass"
| "kcSelectAuthListItemIconClass"
| "kcSelectAuthListItemTitle"
| "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"],
"kcWebAuthnDefaultIcon": ["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"],
"kcSelectAuthListItemFillClass": ["pf-l-split__item", "pf-m-fill"],
"kcSelectAuthListItemIconPropertyClass": ["fa-2x", "select-auth-box-icon-properties"],
"kcSelectAuthListItemIconClass": ["pf-l-split__item", "select-auth-box-icon"],
"kcSelectAuthListItemTitle": ["select-auth-box-paragraph"],
"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;
export type TemplateProps<KcContext extends KcContextBase.Common, I18n extends I18nBase> = {
kcContext: KcContext;
i18n: I18n;
doFetchDefaultThemeResources: boolean;
} & {
displayInfo?: boolean;
displayMessage?: boolean;
displayRequiredFields?: boolean;
displayWide?: boolean;
showAnotherWayIfPresent?: boolean;
headerNode: ReactNode;
showUsernameNode?: ReactNode;
formNode: ReactNode;
infoNode?: ReactNode;
} & KcTemplateProps;
export type PageProps<KcContext, I18n extends I18nBase> = {
kcContext: KcContext;
i18n: I18n;
doFetchDefaultThemeResources?: boolean;
Template: (props: TemplateProps<any, any>) => JSX.Element | null;
} & KcProps;
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,234 +0,0 @@
import React, { useReducer, useEffect } from "react";
import { assert } from "./tools/assert";
import { headInsert } from "./tools/headInsert";
import { pathJoin } from "../bin/tools/pathJoin";
import { clsx } from "./tools/clsx";
import type { TemplateProps } from "./KcProps";
import type { KcContextBase } from "./getKcContext/KcContextBase";
import type { I18nBase } from "./i18n";
export default function Template(props: TemplateProps<KcContextBase.Common, I18nBase>) {
const {
displayInfo = false,
displayMessage = true,
displayRequiredFields = false,
displayWide = false,
showAnotherWayIfPresent = true,
headerNode,
showUsernameNode = null,
formNode,
infoNode = null,
kcContext,
i18n,
doFetchDefaultThemeResources
} = props;
const { msg, changeLocale, labelBySupportedLanguageTag, currentLanguageTag } = i18n;
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 = clsx(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={clsx(props.kcLoginClass)}>
<div id="kc-header" className={clsx(props.kcHeaderClass)}>
<div id="kc-header-wrapper" className={clsx(props.kcHeaderWrapperClass)}>
{msg("loginTitleHtml", realm.displayNameHtml)}
</div>
</div>
<div className={clsx(props.kcFormCardClass, displayWide && props.kcFormCardAccountClass)}>
<header className={clsx(props.kcFormHeaderClass)}>
{realm.internationalizationEnabled && (assert(locale !== undefined), true) && locale.supported.length > 1 && (
<div id="kc-locale">
<div id="kc-locale-wrapper" className={clsx(props.kcLocaleWrapperClass)}>
<div className="kc-dropdown" id="kc-locale-dropdown">
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
<a href="#" id="kc-current-locale-link">
{labelBySupportedLanguageTag[currentLanguageTag]}
</a>
<ul>
{locale.supported.map(({ languageTag }) => (
<li key={languageTag} className="kc-dropdown-item">
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
<a href="#" onClick={() => changeLocale(languageTag)}>
{labelBySupportedLanguageTag[languageTag]}
</a>
</li>
))}
</ul>
</div>
</div>
</div>
)}
{!(auth !== undefined && auth.showUsername && !auth.showResetCredentials) ? (
displayRequiredFields ? (
<div className={clsx(props.kcContentWrapperClass)}>
<div className={clsx(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={clsx(props.kcContentWrapperClass)}>
<div className={clsx(props.kcLabelWrapperClass, "subtitle")}>
<span className="subtitle">
<span className="required">*</span> {msg("requiredFields")}
</span>
</div>
<div className="col-md-10">
{showUsernameNode}
<div className={clsx(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={clsx(props.kcResetFlowIcon)}></i>
<span className="kc-tooltip-text">{msg("restartLoginTooltip")}</span>
</div>
</a>
</div>
</div>
</div>
</div>
) : (
<>
{showUsernameNode}
<div className={clsx(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={clsx(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={clsx("alert", `alert-${message.type}`)}>
{message.type === "success" && <span className={clsx(props.kcFeedbackSuccessIcon)}></span>}
{message.type === "warning" && <span className={clsx(props.kcFeedbackWarningIcon)}></span>}
{message.type === "error" && <span className={clsx(props.kcFeedbackErrorIcon)}></span>}
{message.type === "info" && <span className={clsx(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={clsx(displayWide && props.kcContentWrapperClass)}
>
<div className={clsx(displayWide && [props.kcFormSocialAccountContentClass, props.kcFormSocialAccountClass])}>
<div className={clsx(props.kcFormGroupClass)}>
<input type="hidden" name="tryAnotherWay" value="on" />
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
<a
href="#"
id="try-another-way"
onClick={() => {
document.forms["kc-select-try-another-way-form" as never].submit();
return false;
}}
>
{msg("doTryAnotherWay")}
</a>
</div>
</div>
</form>
)}
{displayInfo && (
<div id="kc-info" className={clsx(props.kcSignUpClass)}>
<div id="kc-info-wrapper" className={clsx(props.kcInfoAreaWrapperClass)}>
{infoNode}
</div>
</div>
)}
</div>
</div>
</div>
</div>
);
}

View File

@ -1,496 +0,0 @@
import type { PageId } from "../../bin/keycloakify/generateFtl";
import { assert } from "tsafe/assert";
import type { Equals } from "tsafe";
import type { MessageKeyBase } from "../i18n";
import type { KcTemplateClassKey } from "../KcProps";
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.LoginUsername
| KcContextBase.WebauthnAuthenticate
| KcContextBase.LoginPassword
| KcContextBase.LoginUpdatePassword
| KcContextBase.LoginUpdateProfile
| KcContextBase.LoginIdpLinkConfirm
| KcContextBase.LoginIdpLinkEmail
| KcContextBase.LoginPageExpired
| KcContextBase.LoginConfigTotp
| KcContextBase.LogoutConfirm
| KcContextBase.UpdateUserProfile
| KcContextBase.IdpReviewUserProfile;
export type WebauthnAuthenticator = {
credentialId: string;
transports: {
iconClass: KcTemplateClassKey;
displayNameProperties: MessageKeyBase[];
};
label: string;
createdAt: string;
};
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 LoginUsername = Common & {
pageId: "login-username.ftl";
url: {
loginResetCredentialsUrl: string;
registrationUrl: string;
};
realm: {
loginWithEmailAllowed: boolean;
rememberMe: boolean;
password: boolean;
resetPasswordAllowed: boolean;
registrationAllowed: boolean;
};
registrationDisabled: boolean;
login: {
username?: string;
rememberMe?: boolean;
};
usernameHidden?: boolean;
social: {
displayInfo: boolean;
providers?: {
loginUrl: string;
alias: string;
providerId: string;
displayName: string;
}[];
};
};
export type LoginPassword = Common & {
pageId: "login-password.ftl";
url: {
loginResetCredentialsUrl: string;
registrationUrl: string;
};
realm: {
resetPasswordAllowed: boolean;
};
auth?: {
showUsername?: boolean;
showResetCredentials?: boolean;
showTryAnotherWayLink?: boolean;
attemptedUsername?: string;
};
social: {
displayInfo: boolean;
};
login: {
password?: string;
};
};
export type WebauthnAuthenticate = Common & {
pageId: "webauthn-authenticate.ftl";
authenticators: {
authenticators: WebauthnAuthenticator[];
};
challenge: string;
// I hate this:
userVerification: UserVerificationRequirement | "not specified";
rpId: string;
createTimeout: string;
isUserIdentified: "true" | "false";
shouldDisplayAuthenticators: boolean;
social: {
displayInfo: boolean;
};
login: {};
};
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 UpdateUserProfile = Common & {
pageId: "update-user-profile.ftl";
profile: {
attributes: Attribute[];
attributesByName: Record<string, Attribute>;
};
};
export type IdpReviewUserProfile = Common & {
pageId: "idp-review-user-profile.ftl";
profile: {
context: "IDP_REVIEW";
attributes: Attribute[];
attributesByName: Record<string, Attribute>;
};
};
}
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,131 +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";
import { symToStr } from "tsafe/symToStr";
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 ?? {};
const realKcContext = getKcContextFromWindow<KcContextExtended>();
if (mockPageId !== undefined && realKcContext === undefined) {
//TODO maybe trow if no mock fo custom page
console.log(
[
`%cKeycloakify: ${symToStr({ mockPageId })} set to ${mockPageId}.`,
`If assets are missing make sure you have built your Keycloak theme at least once.`
].join(" "),
"background: red; color: yellow; font-size: medium"
);
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" ||
partialKcContextCustomMock.pageId === "update-user-profile.ftl" ||
partialKcContextCustomMock.pageId === "idp-review-user-profile.ftl"
) {
assert(
kcContextDefaultMock?.pageId === "register-user-profile.ftl" ||
kcContextDefaultMock?.pageId === "update-user-profile.ftl" ||
kcContextDefaultMock?.pageId === "idp-review-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
.map(partialAttribute => ({ "validators": {}, ...partialAttribute }))
.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 };
}
if (realKcContext === undefined) {
return { "kcContext": undefined };
}
{
const { url } = realKcContext;
url.resourcesCommonPath = pathJoin(url.resourcesPath, pathBasename(mockTestingResourcesCommonPath));
}
return { "kcContext": realKcContext };
}

View File

@ -1,11 +0,0 @@
import type { KcContextBase } from "./KcContextBase";
import type { AndByDiscriminatingKey } from "../tools/AndByDiscriminatingKey";
import { ftlValuesGlobalName } from "../../bin/keycloakify/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,500 +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"] ?? "/";
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"
}
];
const attributesByName = Object.fromEntries(attributes.map(attribute => [attribute.name, attribute])) as any;
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": () => {
console.log("coucou");
return undefined;
},
"existsError": () => false,
"get": key => `Fake error for ${key}`,
"exists": () => false
},
"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,
attributes,
attributesByName
}
})
];
})(),
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.LoginUsername>({
...kcContextCommonMock,
"pageId": "login-username.ftl",
"url": loginUrl,
"realm": {
...kcContextCommonMock.realm,
"loginWithEmailAllowed": true,
"rememberMe": true,
"password": true,
"resetPasswordAllowed": true,
"registrationAllowed": true
},
"social": {
"displayInfo": true
},
"usernameHidden": false,
"login": {
"rememberMe": false
},
"registrationDisabled": false
}),
id<KcContextBase.LoginPassword>({
...kcContextCommonMock,
"pageId": "login-password.ftl",
"url": loginUrl,
"realm": {
...kcContextCommonMock.realm,
"resetPasswordAllowed": true
},
"social": {
"displayInfo": false
},
"login": {}
}),
id<KcContextBase.WebauthnAuthenticate>({
...kcContextCommonMock,
"pageId": "webauthn-authenticate.ftl",
"url": loginUrl,
"authenticators": {
"authenticators": []
},
"realm": {
...kcContextCommonMock.realm
},
"challenge": "",
"userVerification": "not specified",
"rpId": "",
"createTimeout": "0",
"isUserIdentified": "false",
"shouldDisplayAuthenticators": false,
"social": {
"displayInfo": false
},
"login": {}
}),
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 }
}),
id<KcContextBase.UpdateUserProfile>({
...kcContextCommonMock,
"pageId": "update-user-profile.ftl",
"profile": {
attributes,
attributesByName
}
}),
id<KcContextBase.IdpReviewUserProfile>({
...kcContextCommonMock,
"pageId": "idp-review-user-profile.ftl",
"profile": {
context: "IDP_REVIEW",
attributes,
attributesByName
}
})
];

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 */

View File

@ -1,324 +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": "保存",
"doCancel": "キャンセル",
"doLogOutAllSessions": "全セッションからログアウト",
"doRemove": "削除",
"doAdd": "追加",
"doSignOut": "サインアウト",
"doLogIn": "ログイン",
"doLink": "リンク",
"editAccountHtmlTitle": "アカウントの編集",
"personalInfoHtmlTitle": "個人情報",
"federatedIdentitiesHtmlTitle": "連携済みアイデンティティー",
"accountLogHtmlTitle": "アカウントログ",
"changePasswordHtmlTitle": "パスワード変更",
"deviceActivityHtmlTitle": "デバイス・アクティビティー",
"sessionsHtmlTitle": "セッション",
"accountManagementTitle": "Keycloakアカウント管理",
"authenticatorTitle": "オーセンティケーター",
"applicationsHtmlTitle": "アプリケーション",
"linkedAccountsHtmlTitle": "リンクされたアカウント",
"accountManagementWelcomeMessage": "Keycloakアカウント管理へようこそ",
"personalInfoIntroMessage": "基本情報を管理する",
"accountSecurityTitle": "アカウント・セキュリティー",
"accountSecurityIntroMessage": "パスワードとアカウント・アクセスを制御する",
"applicationsIntroMessage": "アカウントへアクセスするためにアプリのパーミッションを追跡して管理する",
"resourceIntroMessage": "チームメンバー間でリソースを共有する",
"passwordLastUpdateMessage": "パスワードは更新されました",
"updatePasswordTitle": "パスワードの更新",
"updatePasswordMessageTitle": "強力なパスワードを選択してください",
"updatePasswordMessage":
"強力なパスワードは、数字、文字、記号を含みます。推測が難しく、実在する言葉に似ておらず、このアカウントだけで使用されています。",
"personalSubTitle": "個人情報",
"personalSubMessage": "この基本情報を管理してください:名、姓、メール",
"authenticatorCode": "ワンタイムコード",
"email": "Eメール",
"firstName": "名",
"givenName": "名",
"fullName": "氏名",
"lastName": "姓",
"familyName": "姓",
"password": "パスワード",
"currentPassword": "現在のパスワード",
"passwordConfirm": "新しいパスワード(確認)",
"passwordNew": "新しいパスワード",
"username": "ユーザー名",
"address": "住所",
"street": "番地",
"locality": "市区町村",
"region": "都道府県",
"postal_code": "郵便番号",
"country": "国",
"emailVerified": "確認済みEメール",
"gssDelegationCredential": "GSS委譲クレデンシャル",
"profileScopeConsentText": "ユーザー・プロファイル",
"emailScopeConsentText": "メールアドレス",
"addressScopeConsentText": "アドレス",
"phoneScopeConsentText": "電話番号",
"offlineAccessScopeConsentText": "オフライン・アクセス",
"samlRoleListScopeConsentText": "ロール",
"rolesScopeConsentText": "ユーザーロール",
"role_admin": "管理者",
"role_realm-admin": "レルム管理者",
"role_create-realm": "レルムの作成",
"role_view-realm": "レルムの参照",
"role_view-users": "ユーザーの参照",
"role_view-applications": "アプリケーションの参照",
"role_view-clients": "クライアントの参照",
"role_view-events": "イベントの参照",
"role_view-identity-providers": "アイデンティティー・プロバイダーの参照",
"role_view-consent": "同意の参照",
"role_manage-realm": "レルムの管理",
"role_manage-users": "ユーザーの管理",
"role_manage-applications": "アプリケーションの管理",
"role_manage-identity-providers": "アイデンティティー・プロバイダーの管理",
"role_manage-clients": "クライアントの管理",
"role_manage-events": "イベントの管理",
"role_view-profile": "プロファイルの参照",
"role_manage-account": "アカウントの管理",
"role_manage-account-links": "アカウントリンクの管理",
"role_manage-consent": "同意の管理",
"role_read-token": "トークンの参照",
"role_offline-access": "オフライン・アクセス",
"role_uma_authorization": "パーミッションの取得",
"client_account": "アカウント",
"client_account-console": "アカウント・コンソール",
"client_security-admin-console": "セキュリティー管理コンソール",
"client_admin-cli": "管理CLI",
"client_realm-management": "レルム管理",
"client_broker": "ブローカー",
"requiredFields": "必須",
"allFieldsRequired": "全ての入力項目が必須",
"backToApplication": "&laquo; アプリケーションに戻る",
"backTo": "{0}に戻る",
"date": "日付",
"event": "イベント",
"ip": "IP",
"client": "クライアント",
"clients": "クライアント",
"details": "詳細",
"started": "開始",
"lastAccess": "最終アクセス",
"expires": "有効期限",
"applications": "アプリケーション",
"account": "アカウント",
"federatedIdentity": "連携済みアイデンティティー",
"authenticator": "オーセンティケーター",
"device-activity": "デバイス・アクティビティー",
"sessions": "セッション",
"log": "ログ",
"application": "アプリケーション",
"availableRoles": "利用可能なロール",
"grantedPermissions": "許可されたパーミッション",
"grantedPersonalInfo": "許可された個人情報",
"additionalGrants": "追加の許可",
"action": "アクション",
"inResource": "in",
"fullAccess": "フルアクセス",
"offlineToken": "オフライン・トークン",
"revoke": "許可の取り消し",
"configureAuthenticators": "設定済みのオーセンティケーター",
"mobile": "モバイル",
"totpStep1": "モバイルに以下のアプリケーションのいずれかをインストールしてください。",
"totpStep2": "アプリケーションを開き、バーコードをスキャンしてください。",
"totpStep3": "アプリケーションで提供されたワンタイムコードを入力して保存をクリックし、セットアップを完了してください。",
"totpStep3DeviceName": "OTPデバイスの管理に役立つようなデバイス名を指定してください。",
"totpManualStep2": "アプリケーションを開き、キーを入力してください。",
"totpManualStep3": "アプリケーションが設定できる場合は、次の設定値を使用してください。",
"totpUnableToScan": "スキャンできませんか?",
"totpScanBarcode": "バーコードをスキャンしますか?",
"totp.totp": "時間ベース",
"totp.hotp": "カウンターベース",
"totpType": "タイプ",
"totpAlgorithm": "アルゴリズム",
"totpDigits": "数字",
"totpInterval": "間隔",
"totpCounter": "カウンター",
"totpDeviceName": "デバイス名",
"missingUsernameMessage": "ユーザー名を入力してください。",
"missingFirstNameMessage": "名を入力してください。",
"invalidEmailMessage": "無効なメールアドレスです。",
"missingLastNameMessage": "姓を入力してください。",
"missingEmailMessage": "Eメールを入力してください。",
"missingPasswordMessage": "パスワードを入力してください。",
"notMatchPasswordMessage": "パスワードが一致していません。",
"invalidUserMessage": "無効なユーザーです。",
"missingTotpMessage": "オーセンティケーター・コードを入力してください。",
"missingTotpDeviceNameMessage": "デバイス名を指定してください。",
"invalidPasswordExistingMessage": "既存のパスワードが不正です。",
"invalidPasswordConfirmMessage": "新しいパスワード(確認)と一致していません。",
"invalidTotpMessage": "無効なオーセンティケーター・コードです。",
"usernameExistsMessage": "既に存在するユーザー名です。",
"emailExistsMessage": "既に存在するEメールです。",
"readOnlyUserMessage": "読み取り専用のため、アカウントを更新することはできません。",
"readOnlyUsernameMessage": "読み取り専用のため、ユーザー名を更新することはできません。",
"readOnlyPasswordMessage": "読み取り専用のため、パスワードを更新することはできません。",
"successTotpMessage": "モバイル・オーセンティケーターが設定されました。",
"successTotpRemovedMessage": "モバイル・オーセンティケーターが削除されました。",
"successGrantRevokedMessage": "許可が正常に取り消しされました。",
"accountUpdatedMessage": "アカウントが更新されました。",
"accountPasswordUpdatedMessage": "パスワードが更新されました。",
"missingIdentityProviderMessage": "アイデンティティー・プロバイダーが指定されていません。",
"invalidFederatedIdentityActionMessage": "無効または存在しないアクションです。",
"identityProviderNotFoundMessage": "指定されたアイデンティティー・プロバイダーが見つかりません。",
"federatedIdentityLinkNotActiveMessage": "このアイデンティティーは有効ではありません。",
"federatedIdentityRemovingLastProviderMessage": "パスワードがないため、最後の連携済みアイデンティティーが削除できません。",
"identityProviderRedirectErrorMessage": "アイデンティティー・プロバイダーへのリダイレクトに失敗しました。",
"identityProviderRemovedMessage": "アイデンティティー・プロバイダーが正常に削除されました。",
"identityProviderAlreadyLinkedMessage": "{0}から返された連携済みアイデンティティーは既に他のユーザーに関連付けされています。",
"staleCodeAccountMessage": "有効期限切れです。再度お試しください。",
"consentDenied": "同意が拒否されました。",
"accountDisabledMessage": "アカウントが無効です。管理者に連絡してください。",
"accountTemporarilyDisabledMessage": "アカウントが一時的に無効です。管理者に連絡するか、しばらく時間をおいてから再度お試しください。",
"invalidPasswordMinLengthMessage": "無効なパスワード: 最小{0}の長さが必要です。",
"invalidPasswordMinLowerCaseCharsMessage": "無効なパスワード: 少なくとも{0}文字の小文字を含む必要があります。",
"invalidPasswordMinDigitsMessage": "無効なパスワード: 少なくとも{0}文字の数字を含む必要があります。",
"invalidPasswordMinUpperCaseCharsMessage": "無効なパスワード:少なくとも{0}文字の大文字を含む必要があります。",
"invalidPasswordMinSpecialCharsMessage": "無効なパスワード: 少なくとも{0}文字の特殊文字を含む必要があります。",
"invalidPasswordNotUsernameMessage": "無効なパスワード: ユーザー名と同じパスワードは禁止されています。",
"invalidPasswordRegexPatternMessage": "無効なパスワード: 正規表現パターンと一致しません。",
"invalidPasswordHistoryMessage": "無効なパスワード: 最近の{0}パスワードのいずれかと同じパスワードは禁止されています。",
"invalidPasswordBlacklistedMessage": "無効なパスワード: パスワードがブラックリストに含まれています。",
"invalidPasswordGenericMessage": "無効なパスワード: 新しいパスワードはパスワード・ポリシーと一致しません。",
"myResources": "マイリソース",
"myResourcesSub": "マイリソース",
"doDeny": "拒否",
"doRevoke": "取り消し",
"doApprove": "承認",
"doRemoveSharing": "共有の削除",
"doRemoveRequest": "要求の削除",
"peopleAccessResource": "このリソースにアクセスできる人",
"resourceManagedPolicies": "このリソースへのアクセスを許可するパーミッション",
"resourceNoPermissionsGrantingAccess": "このリソースへのアクセスを許可する権限はありません",
"anyAction": "任意のアクション",
"description": "説明",
"name": "名前",
"scopes": "スコープ",
"resource": "リソース",
"user": "ユーザー",
"peopleSharingThisResource": "このリソースを共有している人",
"shareWithOthers": "他人と共有",
"needMyApproval": "承認が必要",
"requestsWaitingApproval": "承認待ちの要求",
"icon": "アイコン",
"requestor": "要求者",
"owner": "オーナー",
"resourcesSharedWithMe": "共有しているリソース",
"permissionRequestion": "パーミッションの要求",
"permission": "パーミッション",
"shares": "共有(複数)",
"notBeingShared": "このリソースは共有されていません。",
"notHaveAnyResource": "リソースがありません。",
"noResourcesSharedWithYou": "共有しているリソースはありません",
"havePermissionRequestsWaitingForApproval": "承認を待っている{0}個のパーミッションの要求があります。",
"clickHereForDetails": "詳細はこちらをクリックしてください。",
"resourceIsNotBeingShared": "リソースは共有されていません。",
"locale_ca": "Català",
"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": "名前",
"applicationType": "アプリケーション・タイプ",
"applicationInUse": "使用中のアプリケーションのみ",
"clearAllFilter": "すべてのフィルターをクリア",
"activeFilters": "アクティブなフィルター",
"filterByName": "名前でフィルタリング...",
"allApps": "すべてのアプリケーション",
"internalApps": "内部アプリケーション",
"thirdpartyApps": "サードパーティーのアプリケーション",
"appResults": "結果",
"clientNotFoundMessage": "クライアントが見つかりません。",
"authorizedProvider": "認可済みプロバイダー",
"authorizedProviderMessage": "アカウントにリンクされた認可済みプロバイダー",
"identityProvider": "アイデンティティー・プロバイダー",
"identityProviderMessage": "アカウントと設定したアイデンティティー・プロバイダーをリンクするには",
"socialLogin": "ソーシャル・ログイン",
"userDefined": "ユーザー定義",
"removeAccess": "アクセス権の削除",
"removeAccessMessage": "このアプリ・アカウントを使用する場合は、アクセス権を再度付与する必要があります。",
"authenticatorStatusMessage": "2要素認証は現在",
"authenticatorFinishSetUpTitle": "あなたの2要素認証",
"authenticatorFinishSetUpMessage": "Keycloakアカウントにサインインするたびに、2要素認証コードを入力するように求められます。",
"authenticatorSubTitle": "2要素認証を設定する",
"authenticatorSubMessage": "アカウントのセキュリティーを強化するには、利用可能な2要素認証の方式のうち少なくとも1つを有効にします。",
"authenticatorMobileTitle": "モバイル・オーセンティケーター",
"authenticatorMobileMessage": "モバイル・オーセンティケーターを使用して、2要素認証として確認コードを取得します。",
"authenticatorMobileFinishSetUpMessage": "オーセンティケーターはあなたの携帯電話にバインドされています。",
"authenticatorActionSetup": "セットアップ",
"authenticatorSMSTitle": "SMSコード",
"authenticatorSMSMessage": "Keycloakは、2要素認証として確認コードを携帯電話に送信します。",
"authenticatorSMSFinishSetUpMessage": "テキスト・メッセージが次の電話番号宛に送信されます:",
"authenticatorDefaultStatus": "デフォルト",
"authenticatorChangePhone": "電話番号の変更",
"authenticatorBackupCodesTitle": "バックアップ・コード",
"authenticatorBackupCodesMessage": "8桁のバックアップ・コードの入手",
"authenticatorBackupCodesFinishSetUpMessage": "この時点で12個のバックアップ・コードが生成されました。それぞれ一度だけ使用できます。",
"authenticatorMobileSetupTitle": "モバイル・オーセンティケーターのセットアップ",
"smscodeIntroMessage": "電話番号を入力すると、確認コードがあなたの電話に送信されます。",
"mobileSetupStep1":
"携帯電話にオーセンティケーター・アプリケーションをインストールします。ここにリストされているアプリケーションがサポートされています。",
"mobileSetupStep2": "アプリケーションを開き、バーコードをスキャンしてください。",
"mobileSetupStep3": "アプリケーションから提供されたワンタイムコードを入力し、保存をクリックしてセットアップを終了します。",
"scanBarCode": "バーコードをスキャンしますか?",
"enterBarCode": "ワンタイムコードを入力してください",
"doCopy": "コピー",
"doFinish": "終了",
"authenticatorSMSCodeSetupTitle": "SMSコードのセットアップ",
"chooseYourCountry": "国を選んでください",
"enterYourPhoneNumber": "電話番号を入力してください",
"sendVerficationCode": "確認コードの送信",
"enterYourVerficationCode": "確認コードを入力してください",
"authenticatorBackupCodesSetupTitle": "バックアップ・コードのセットアップ",
"backupcodesIntroMessage":
"携帯電話にアクセスできない場合でも、バックアップ・コードを使用してアカウントにログインできます。どこか安全でアクセス可能な場所に保管してください。",
"realmName": "レルム",
"doDownload": "ダウンロード",
"doPrint": "印刷",
"backupCodesTips-1": "各バックアップ・コードは1回使用できます。",
"backupCodesTips-2": "これらのコードはこの日に生成されました:",
"generateNewBackupCodes": "新しいバックアップ・コードを生成する",
"backupCodesTips-3": "新しいバックアップ・コードを生成すると、現在のコードは機能しなくなります。",
"backtoAuthenticatorPage": "オーセンティケーター・ページに戻る",
"resources": "リソース",
"sharedwithMe": "私と共有",
"share": "共有",
"sharedwith": "共有",
"accessPermissions": "アクセス・パーミッション",
"permissionRequests": "パーミッションの要求",
"approve": "承認",
"approveAll": "すべて承認",
"people": "人",
"perPage": "1ページあたり",
"currentPage": "現在のページ",
"sharetheResource": "リソースの共有",
"group": "グループ",
"selectPermission": "パーミッションを選択",
"addPeople": "あなたのリソースを共有する人を追加",
"addTeam": "あなたのリソースを共有するチームを追加",
"myPermissions": "私のパーミッション",
"waitingforApproval": "承認待ち",
"anyPermission": "任意のパーミッション",
"openshift.scope.user_info": "ユーザー情報",
"openshift.scope.user_check-access": "ユーザーアクセス情報",
"openshift.scope.user_full": "フルアクセス",
"openshift.scope.list-projects": "プロジェクトの一覧表示"
};
export default messages;
/* spell-checker: enable */

View File

@ -1,143 +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": "Saugoti",
"doCancel": "Atšaukti",
"doLogOutAllSessions": "Atjungti visas sesijas",
"doRemove": "Šalinti",
"doAdd": "Pridėti",
"doSignOut": "Atsijungti",
"editAccountHtmlTitle": "Redaguoti paskyrą",
"federatedIdentitiesHtmlTitle": "Susietos paskyros",
"accountLogHtmlTitle": "Paskyros žurnalas",
"changePasswordHtmlTitle": "Keisti slaptažodį",
"sessionsHtmlTitle": "Prisijungimo sesijos",
"accountManagementTitle": "Keycloak Naudotojų Administravimas",
"authenticatorTitle": "Autentifikatorius",
"applicationsHtmlTitle": "Programos",
"authenticatorCode": "Vienkartinis kodas",
"email": "El. paštas",
"firstName": "Vardas",
"givenName": "Pavardė",
"fullName": "Pilnas vardas",
"lastName": "Pavardė",
"familyName": "Pavardė",
"password": "Slaptažodis",
"passwordConfirm": "Pakartotas slaptažodis",
"passwordNew": "Naujas slaptažodis",
"username": "Naudotojo vardas",
"address": "Adresas",
"street": "Gatvė",
"locality": "Miestas arba vietovė",
"region": "Rajonas",
"postal_code": "Pašto kodas",
"country": "Šalis",
"emailVerified": "El. pašto adresas patvirtintas",
"gssDelegationCredential": "GSS prisijungimo duomenų delegavimas",
"role_admin": "Administratorius",
"role_realm-admin": "Srities administravimas",
"role_create-realm": "Kurti sritį",
"role_view-realm": "Peržiūrėti sritį",
"role_view-users": "Peržiūrėti naudotojus",
"role_view-applications": "Peržiūrėti programas",
"role_view-clients": "Peržiūrėti klientines programas",
"role_view-events": "Peržiūrėti įvykių žurnalą",
"role_view-identity-providers": "Peržiūrėti tapatybės teikėjus",
"role_manage-realm": "Valdyti sritis",
"role_manage-users": "Valdyti naudotojus",
"role_manage-applications": "Valdyti programas",
"role_manage-identity-providers": "Valdyti tapatybės teikėjus",
"role_manage-clients": "Valdyti programas",
"role_manage-events": "Valdyti įvykius",
"role_view-profile": "Peržiūrėti paskyrą",
"role_manage-account": "Valdyti paskyrą",
"role_read-token": "Skaityti prieigos rakšą",
"role_offline-access": "Darbas neprisijungus",
"role_uma_authorization": "Įgauti UMA autorizavimo teises",
"client_account": "Paskyra",
"client_security-admin-console": "Saugumo administravimo konsolė",
"client_admin-cli": "Administravimo CLI",
"client_realm-management": "Srities valdymas",
"client_broker": "Tarpininkas",
"requiredFields": "Privalomi laukai",
"allFieldsRequired": "Visi laukai yra privalomi",
"backToApplication": "&laquo; Grįžti į programą",
"backTo": "Atgal į {0}",
"date": "Data",
"event": "Įvykis",
"ip": "IP",
"client": "Klientas",
"clients": "Klientai",
"details": "Detaliau",
"started": "Sukūrimo laikas",
"lastAccess": "Vėliausia prieiga",
"expires": "Galioja iki",
"applications": "Programos",
"account": "Paskyra",
"federatedIdentity": "Susieta tapatybė",
"authenticator": "Autentifikatorius",
"sessions": "Sesijos",
"log": "Įvykiai",
"application": "Programa",
"availablePermissions": "Galimos teisės",
"grantedPermissions": "Įgalintos teisės",
"grantedPersonalInfo": "Įgalinta asmeninė informacija",
"additionalGrants": "Papildomi įgaliojimai",
"action": "Veiksmas",
"inResource": "yra",
"fullAccess": "Pilna prieiga",
"offlineToken": "Režimo neprisijungus raktas (token)",
"revoke": "Atšaukti įgaliojimą",
"configureAuthenticators": "Sukonfigūruotas autentifikatorius",
"mobile": "Mobilus",
"totpStep1":
'Įdiekite <a href="https://freeotp.github.io/" target="_blank">FreeOTP</a> arba Google Authenticator savo įrenginyje. Programėlės prieinamos <a href="https://play.google.com">Google Play</a> ir Apple App Store.',
"totpStep2": "Atidarykite programėlę ir nuskenuokite barkodą arba įveskite kodą.",
"totpStep3": "Įveskite programėlėje sugeneruotą vieną kartą galiojantį kodą ir paspauskite Saugoti norėdami prisijungti.",
"missingUsernameMessage": "Prašome įvesti naudotojo vardą.",
"missingFirstNameMessage": "Prašome įvesti vardą.",
"invalidEmailMessage": "Neteisingas el. pašto adresas.",
"missingLastNameMessage": "Prašome įvesti pavardę.",
"missingEmailMessage": "Prašome įvesti el. pašto adresą.",
"missingPasswordMessage": "Prašome įvesti slaptažodį.",
"notMatchPasswordMessage": "Slaptažodžiai nesutampa.",
"missingTotpMessage": "Prašome įvesti autentifikacijos kodą.",
"invalidPasswordExistingMessage": "Neteisingas dabartinis slaptažodis.",
"invalidPasswordConfirmMessage": "Pakartotas slaptažodis nesutampa.",
"invalidTotpMessage": "Neteisingas autentifikacijos kodas.",
"usernameExistsMessage": "Toks naudotojas jau egzistuoja.",
"emailExistsMessage": "El. pašto adresas jau egzistuoja.",
"readOnlyUserMessage": "Tik skaitymui sukonfigūruotos paskyros duomenų atnaujinti neleidžiama.",
"readOnlyPasswordMessage": "Tik skaitymui sukonfigūruotos paskyros slaptažodžio atnaujinti neleidžiama.",
"successTotpMessage": "Mobilus autentifikatorius sukonfigūruotas.",
"successTotpRemovedMessage": "Mobilus autentifikatorius pašalintas.",
"successGrantRevokedMessage": "Įgalinimas pašalintas sėkmingai.",
"accountUpdatedMessage": "Jūsų paskyros duomenys sėkmingai atnaujinti.",
"accountPasswordUpdatedMessage": "Jūsų paskyros slaptažodis pakeistas.",
"missingIdentityProviderMessage": "Nenurodytas tapatybės teikėjas.",
"invalidFederatedIdentityActionMessage": "Neteisingas arba nežinomas veiksmas.",
"identityProviderNotFoundMessage": "Nurodytas tapatybės teikėjas nerastas.",
"federatedIdentityLinkNotActiveMessage": "Nurodyta susieta tapatybė neaktyvi.",
"federatedIdentityRemovingLastProviderMessage":
"Jūs negalite pašalinti paskutinio tapatybės teikėjo sąsajos, nes Jūs neturite nusistatę paskyros slaptažodžio.",
"identityProviderRedirectErrorMessage": "Klaida nukreipiant į tapatybės teikėjo puslapį.",
"identityProviderRemovedMessage": "Tapatybės teikėjas sėkmingai pašalintas.",
"identityProviderAlreadyLinkedMessage": "Susieta tapatybė iš {0} jau susieta su kita paskyra.",
"staleCodeAccountMessage": "Puslapio galiojimas baigėsi. Bandykite dar kartą.",
"consentDenied": "Prieiga draudžiama.",
"accountDisabledMessage": "Paskyros galiojimas sustabdytas, kreipkitės į administratorių.",
"accountTemporarilyDisabledMessage": "Paskyros galiojimas laikinai sustabdytas. Kreipkitės į administratorių arba pabandykite vėliau.",
"invalidPasswordMinLengthMessage": "Per trumpas slaptažodis: mažiausias ilgis {0}.",
"invalidPasswordMinLowerCaseCharsMessage": "Neteisingas slaptažodis: privaloma įvesti {0} mažąją raidę.",
"invalidPasswordMinDigitsMessage": "Neteisingas slaptažodis: privaloma įvesti {0} skaitmenį.",
"invalidPasswordMinUpperCaseCharsMessage": "Neteisingas slaptažodis: privaloma įvesti {0} didžiąją raidę.",
"invalidPasswordMinSpecialCharsMessage": "Neteisingas slaptažodis: privaloma įvesti {0} specialų simbolį.",
"invalidPasswordNotUsernameMessage": "Neteisingas slaptažodis: slaptažodis negali sutapti su naudotojo vardu.",
"invalidPasswordRegexPatternMessage": "Neteisingas slaptažodis: slaptažodis netenkina regex taisyklės(ių).",
"invalidPasswordHistoryMessage": "Neteisingas slaptažodis: slaptažodis negali sutapti su prieš tai buvusiais {0} slaptažodžiais."
};
export default messages;
/* spell-checker: enable */

View File

@ -1,143 +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": "Opslaan",
"doCancel": "Annuleer",
"doLogOutAllSessions": "Alle sessies uitloggen",
"doRemove": "Verwijder",
"doAdd": "Voeg toe",
"doSignOut": "Afmelden",
"editAccountHtmlTitle": "Bewerk account",
"federatedIdentitiesHtmlTitle": "Federated Identities",
"accountLogHtmlTitle": "Account log",
"changePasswordHtmlTitle": "Verander wachtwoord",
"sessionsHtmlTitle": "Sessies",
"accountManagementTitle": "Keycloak Accountbeheer",
"authenticatorTitle": "Authenticator",
"applicationsHtmlTitle": "Toepassingen",
"authenticatorCode": "Eenmalige code",
"email": "E-mailadres",
"firstName": "Voornaam",
"givenName": "Voornaam",
"fullName": "Volledige naam",
"lastName": "Achternaam",
"familyName": "Achternaam",
"password": "Wachtwoord",
"passwordConfirm": "Bevestiging",
"passwordNew": "Nieuw Wachtwoord",
"username": "Gebruikersnaam",
"address": "Adres",
"street": "Straat",
"locality": "Stad of plaats",
"region": "Staat, provincie of regio",
"postal_code": "Postcode",
"country": "Land",
"emailVerified": "E-mailadres geverifieerd",
"gssDelegationCredential": "GSS gedelegeerde aanmeldgegevens",
"role_admin": "Beheer",
"role_realm-admin": "Realmbeheer",
"role_create-realm": "Creëer realm",
"role_view-realm": "Bekijk realm",
"role_view-users": "Bekijk gebruikers",
"role_view-applications": "Bekijk toepassingen",
"role_view-clients": "Bekijk clients",
"role_view-events": "Bekijk gebeurtenissen",
"role_view-identity-providers": "Bekijk identity providers",
"role_manage-realm": "Beheer realm",
"role_manage-users": "Beheer gebruikers",
"role_manage-applications": "Beheer toepassingen",
"role_manage-identity-providers": "Beheer identity providers",
"role_manage-clients": "Beheer clients",
"role_manage-events": "Beheer gebeurtenissen",
"role_view-profile": "Bekijk profiel",
"role_manage-account": "Beheer account",
"role_manage-account-links": "Beheer accountkoppelingen",
"role_read-token": "Lees token",
"role_offline-access": "Offline toegang",
"role_uma_authorization": "Verkrijg UMA rechten",
"client_account": "Account",
"client_security-admin-console": "Console Veligheidsbeheer",
"client_admin-cli": "Beheer CLI",
"client_realm-management": "Realmbeheer",
"client_broker": "Broker",
"requiredFields": "Verplichte velden",
"allFieldsRequired": "Alle velden verplicht",
"backToApplication": "&laquo; Terug naar toepassing",
"backTo": "Terug naar {0}",
"date": "Datum",
"event": "Gebeurtenis",
"ip": "IP",
"client": "Client",
"clients": "Clients",
"details": "Details",
"started": "Gestart",
"lastAccess": "Laatste toegang",
"expires": "Vervalt",
"applications": "Toepassingen",
"account": "Account",
"federatedIdentity": "Federated Identity",
"authenticator": "Authenticator",
"sessions": "Sessies",
"log": "Log",
"application": "Toepassing",
"availablePermissions": "Beschikbare rechten",
"grantedPermissions": "Gegunde rechten",
"grantedPersonalInfo": "Gegunde Persoonsgegevens",
"additionalGrants": "Verdere vergunningen",
"action": "Actie",
"inResource": "in",
"fullAccess": "Volledige toegang",
"offlineToken": "Offline Token",
"revoke": "Vergunning intrekken",
"configureAuthenticators": "Ingestelde authenticators",
"mobile": "Mobiel nummer",
"totpStep1": "Installeer een van de onderstaande applicaties op uw mobiele apparaat:",
"totpStep2": "Open de toepassing en scan de QR-code of voer de sleutel in.",
"totpStep3": "Voer de door de toepassing gegeven eenmalige code in en klik op Opslaan om de configuratie af te ronden.",
"missingUsernameMessage": "Gebruikersnaam ontbreekt.",
"missingFirstNameMessage": "Voornaam onbreekt.",
"invalidEmailMessage": "Ongeldig e-mailadres.",
"missingLastNameMessage": "Achternaam ontbreekt.",
"missingEmailMessage": "E-mailadres ontbreekt.",
"missingPasswordMessage": "Wachtwoord ontbreekt.",
"notMatchPasswordMessage": "Wachtwoorden komen niet overeen.",
"missingTotpMessage": "Authenticatiecode ontbreekt.",
"invalidPasswordExistingMessage": "Ongeldig bestaand wachtwoord.",
"invalidPasswordConfirmMessage": "Wachtwoordbevestiging komt niet overeen.",
"invalidTotpMessage": "Ongeldige authenticatiecode.",
"emailExistsMessage": "E-mailadres bestaat reeds.",
"readOnlyUserMessage": "U kunt uw account niet bijwerken aangezien het account alleen-lezen is.",
"readOnlyPasswordMessage": "U kunt uw wachtwoord niet wijzigen omdat uw account alleen-lezen is.",
"successTotpMessage": "Mobiele authenticator geconfigureerd.",
"successTotpRemovedMessage": "Mobiele authenticator verwijderd.",
"successGrantRevokedMessage": "Vergunning succesvol ingetrokken",
"accountUpdatedMessage": "Uw account is gewijzigd.",
"accountPasswordUpdatedMessage": "Uw wachtwoord is gewijzigd.",
"missingIdentityProviderMessage": "Geen identity provider aangegeven.",
"invalidFederatedIdentityActionMessage": "Ongeldige of ontbrekende actie op federated identity.",
"identityProviderNotFoundMessage": "Gespecificeerde identity provider niet gevonden.",
"federatedIdentityLinkNotActiveMessage": "Deze federated identity is niet langer geldig.",
"federatedIdentityRemovingLastProviderMessage":
"U kunt de laatste federated identity provider niet verwijderen aangezien u dan niet langer zou kunnen inloggen.",
"identityProviderRedirectErrorMessage": "Kon niet herverwijzen naar identity provider.",
"identityProviderRemovedMessage": "Identity provider met succes verwijderd.",
"identityProviderAlreadyLinkedMessage": "Door {0} teruggegeven federated identity is al gekoppeld aan een andere gebruiker.",
"staleCodeAccountMessage": "De pagina is verlopen. Probeer het nogmaals.",
"consentDenied": "Toestemming geweigerd",
"accountDisabledMessage": "Account is gedeactiveerd. Contacteer de beheerder.",
"accountTemporarilyDisabledMessage": "Account is tijdelijk deactiveerd, neem contact op met de beheerder of probeer het later opnieuw.",
"invalidPasswordMinLengthMessage": "Ongeldig wachtwoord: de minimale lengte is {0} karakters.",
"invalidPasswordMinLowerCaseCharsMessage": "Ongeldig wachtwoord: het moet minstens {0} kleine letters bevatten.",
"invalidPasswordMinDigitsMessage": "Ongeldig wachtwoord: het moet minstens {0} getallen bevatten.",
"invalidPasswordMinUpperCaseCharsMessage": "Ongeldig wachtwoord: het moet minstens {0} hoofdletters bevatten.",
"invalidPasswordMinSpecialCharsMessage": "Ongeldig wachtwoord: het moet minstens {0} speciale karakters bevatten.",
"invalidPasswordNotUsernameMessage": "Ongeldig wachtwoord: het mag niet overeenkomen met de gebruikersnaam.",
"invalidPasswordRegexPatternMessage": "Ongeldig wachtwoord: het voldoet niet aan het door de beheerder ingestelde patroon.",
"invalidPasswordHistoryMessage": "Ongeldig wachtwoord: het mag niet overeen komen met een van de laatste {0} wachtwoorden.",
"invalidPasswordGenericMessage": "Ongeldig wachtwoord: het nieuwe wachtwoord voldoet niet aan het wachtwoordbeleid."
};
export default messages;
/* spell-checker: enable */

View File

@ -1,153 +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": "Lagre",
"doCancel": "Avbryt",
"doLogOutAllSessions": "Logg ut av alle sesjoner",
"doRemove": "Fjern",
"doAdd": "Legg til",
"doSignOut": "Logg ut",
"editAccountHtmlTitle": "Rediger konto",
"federatedIdentitiesHtmlTitle": "Federerte identiteter",
"accountLogHtmlTitle": "Kontologg",
"changePasswordHtmlTitle": "Endre passord",
"sessionsHtmlTitle": "Sesjoner",
"accountManagementTitle": "Keycloak kontoadministrasjon",
"authenticatorTitle": "Autentikator",
"applicationsHtmlTitle": "Applikasjoner",
"authenticatorCode": "Engangskode",
"email": "E-post",
"firstName": "Fornavn",
"givenName": "Fornavn",
"fullName": "Fullt navn",
"lastName": "Etternavn",
"familyName": "Etternavn",
"password": "Passord",
"passwordConfirm": "Bekreftelse",
"passwordNew": "Nytt passord",
"username": "Brukernavn",
"address": "Adresse",
"street": "Gate-/veinavn + husnummer",
"locality": "By",
"region": "Fylke",
"postal_code": "Postnummer",
"country": "Land",
"emailVerified": "E-post bekreftet",
"gssDelegationCredential": "GSS legitimasjonsdelegering",
"role_admin": "Administrator",
"role_realm-admin": "Administrator for sikkerhetsdomene",
"role_create-realm": "Opprette sikkerhetsdomene",
"role_view-realm": "Se sikkerhetsdomene",
"role_view-users": "Se brukere",
"role_view-applications": "Se applikasjoner",
"role_view-clients": "Se klienter",
"role_view-events": "Se hendelser",
"role_view-identity-providers": "Se identitetsleverandører",
"role_manage-realm": "Administrere sikkerhetsdomene",
"role_manage-users": "Administrere brukere",
"role_manage-applications": "Administrere applikasjoner",
"role_manage-identity-providers": "Administrere identitetsleverandører",
"role_manage-clients": "Administrere klienter",
"role_manage-events": "Administrere hendelser",
"role_view-profile": "Se profil",
"role_manage-account": "Administrere konto",
"role_read-token": "Lese token",
"role_offline-access": "Frakoblet tilgang",
"role_uma_authorization": "Skaffe tillatelser",
"client_account": "Konto",
"client_security-admin-console": "Sikkerhetsadministrasjonskonsoll",
"client_admin-cli": "Kommandolinje-grensesnitt for administrator",
"client_realm-management": "Sikkerhetsdomene-administrasjon",
"client_broker": "Broker",
"requiredFields": "Obligatoriske felt",
"allFieldsRequired": "Alle felt må fylles ut",
"backToApplication": "&laquo; Tilbake til applikasjonen",
"backTo": "Tilbake til {0}",
"date": "Dato",
"event": "Hendelse",
"ip": "IP",
"client": "Klient",
"clients": "Klienter",
"details": "Detaljer",
"started": "Startet",
"lastAccess": "Sist benyttet",
"expires": "Utløper",
"applications": "Applikasjoner",
"account": "Konto",
"federatedIdentity": "Federert identitet",
"authenticator": "Autentikator",
"sessions": "Sesjoner",
"log": "Logg",
"application": "Applikasjon",
"availablePermissions": "Tilgjengelige rettigheter",
"grantedPermissions": "Innvilgede rettigheter",
"grantedPersonalInfo": "Innvilget personlig informasjon",
"additionalGrants": "Ekstra rettigheter",
"action": "Handling",
"inResource": "i",
"fullAccess": "Full tilgang",
"offlineToken": "Offline token",
"revoke": "Opphev rettighet",
"configureAuthenticators": "Konfigurerte autentikatorer",
"mobile": "Mobiltelefon",
"totpStep1": "Installer ett av følgende programmer på mobilen din.",
"totpStep2": "Åpne applikasjonen og skann strekkoden eller skriv inn koden.",
"totpStep3": "Skriv inn engangskoden gitt av applikasjonen og klikk Lagre for å fullføre.",
"missingUsernameMessage": "Vennligst oppgi brukernavn.",
"missingFirstNameMessage": "Vennligst oppgi fornavn.",
"invalidEmailMessage": "Ugyldig e-postadresse.",
"missingLastNameMessage": "Vennligst oppgi etternavn.",
"missingEmailMessage": "Vennligst oppgi e-postadresse.",
"missingPasswordMessage": "Vennligst oppgi passord.",
"notMatchPasswordMessage": "Passordene er ikke like.",
"missingTotpMessage": "Vennligst oppgi engangskode.",
"invalidPasswordExistingMessage": "Ugyldig eksisterende passord.",
"invalidPasswordConfirmMessage": "Passordene er ikke like.",
"invalidTotpMessage": "Ugyldig engangskode.",
"usernameExistsMessage": "Brukernavnet finnes allerede.",
"emailExistsMessage": "E-postadressen finnes allerede.",
"readOnlyUserMessage": "Du kan ikke oppdatere kontoen din ettersom den er skrivebeskyttet.",
"readOnlyPasswordMessage": "Du kan ikke oppdatere passordet ditt ettersom kontoen din er skrivebeskyttet.",
"successTotpMessage": "Autentikator for mobiltelefon er konfigurert.",
"successTotpRemovedMessage": "Autentikator for mobiltelefon er fjernet.",
"successGrantRevokedMessage": "Vellykket oppheving av rettighet.",
"accountUpdatedMessage": "Kontoen din har blitt oppdatert.",
"accountPasswordUpdatedMessage": "Ditt passord har blitt oppdatert.",
"missingIdentityProviderMessage": "Identitetsleverandør er ikke spesifisert.",
"invalidFederatedIdentityActionMessage": "Ugyldig eller manglende handling.",
"identityProviderNotFoundMessage": "Spesifisert identitetsleverandør ikke funnet.",
"federatedIdentityLinkNotActiveMessage": "Denne identiteten er ikke lenger aktiv.",
"federatedIdentityRemovingLastProviderMessage": "Du kan ikke fjerne siste federerte identitet ettersom du ikke har et passord.",
"identityProviderRedirectErrorMessage": "Redirect til identitetsleverandør feilet.",
"identityProviderRemovedMessage": "Fjerning av identitetsleverandør var vellykket.",
"identityProviderAlreadyLinkedMessage": "Federert identitet returnert av {0} er allerede koblet til en annen bruker.",
"staleCodeAccountMessage": "Siden har utløpt. Vennligst prøv en gang til.",
"consentDenied": "Samtykke avslått.",
"accountDisabledMessage": "Konto er deaktivert, kontakt administrator.",
"accountTemporarilyDisabledMessage": "Konto er midlertidig deaktivert, kontakt administrator eller prøv igjen senere.",
"invalidPasswordMinLengthMessage": "Ugyldig passord: minimum lengde {0}.",
"invalidPasswordMinLowerCaseCharsMessage": "Ugyldig passord: må inneholde minimum {0} små bokstaver.",
"invalidPasswordMinDigitsMessage": "Ugyldig passord: må inneholde minimum {0} sifre.",
"invalidPasswordMinUpperCaseCharsMessage": "Ugyldig passord: må inneholde minimum {0} store bokstaver.",
"invalidPasswordMinSpecialCharsMessage": "Ugyldig passord: må inneholde minimum {0} spesialtegn.",
"invalidPasswordNotUsernameMessage": "Ugyldig passord: kan ikke være likt brukernavn.",
"invalidPasswordRegexPatternMessage": "Ugyldig passord: tilfredsstiller ikke kravene for passord-mønster.",
"invalidPasswordHistoryMessage": "Ugyldig passord: kan ikke være likt noen av de {0} foregående passordene.",
"locale_ca": "Català",
"locale_de": "Deutsch",
"locale_en": "English",
"locale_es": "Español",
"locale_fr": "Français",
"locale_it": "Italian",
"locale_ja": "日本語",
"locale_no": "Norsk",
"locale_nl": "Nederlands",
"locale_pt-BR": "Português (Brasil)",
"locale_ru": "Русский",
"locale_zh-CN": "中文简体"
};
export default messages;
/* spell-checker: enable */

View File

@ -1,8 +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 = {};
export default messages;
/* spell-checker: enable */

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