Compare commits

...

58 Commits

Author SHA1 Message Date
94b7d2b85b Bump version 2024-12-21 19:40:07 +01:00
9a4f89e69d Sort out version of keycloak not supported depending on which theme is implemented (start-keycloak cmd) 2024-12-21 19:39:10 +01:00
a5ba03cca0 Reload when .properties files are updated 2024-12-21 19:02:13 +01:00
5203813e7b Rebuild the theme when properties files changes 2024-12-21 15:22:19 +01:00
0e461fd072 Fix bugs in svg assets commenting for mirror files 2024-12-21 14:25:47 +01:00
326411ca5d Correctly generate i18n messages for admin UI 2024-12-21 12:09:29 +01:00
c39c450e90 Support generating eject comments for .svg files 2024-12-20 13:22:15 +01:00
3191954dda Support generating eject comment for .properties file 2024-12-20 13:03:58 +01:00
20c6d2ea86 Bump version 2024-12-19 19:07:24 +01:00
f43544e134 ensure no diff if config hasn't changed 2024-12-19 19:06:54 +01:00
474a863708 Correctly patch the security-admin-console client so that it can be run with HMR 2024-12-18 20:57:42 +01:00
0bacdca8fe Bump version 2024-12-17 18:04:22 +01:00
f023d6bca7 Fixes windows issues #747 2024-12-17 18:04:06 +01:00
150b01f1f3 Bump version 2024-12-17 10:44:38 +01:00
2b2bb20658 #746 2024-12-17 10:44:24 +01:00
70570faed6 Bump version 2024-12-16 18:04:01 +01:00
5d3b7c9a82 Add necessary token claim to access admin in dev mode 2024-12-16 18:04:01 +01:00
95b9b12a3b Try to fix error on windows 2024-12-16 18:04:01 +01:00
0e027055cb Bump version 2024-12-15 19:48:42 +01:00
e47b002535 #744 2024-12-15 19:48:23 +01:00
8dd6dcd1fc Merge pull request #745 from keycloakify/keycloak_config_persistance
Keycloak config persistance
2024-12-15 19:45:52 +01:00
10cfa1cf41 Update default realm configs 2024-12-15 19:45:05 +01:00
3938584aeb Update default realm configs 2024-12-15 18:43:53 +01:00
163b060dc5 Additional teaks 2024-12-15 18:15:36 +01:00
67f8ae41fc Update prepare realm script 2024-12-15 17:42:45 +01:00
b6e9fe2585 Update default realm config for kc 26 2024-12-15 13:28:05 +01:00
5b83bd8fa9 Update dump realm local script 2024-12-15 13:27:49 +01:00
d0f43b6318 Add logging and debug for backup configuration process 2024-12-15 13:11:01 +01:00
df338ed6a0 Improve ordering to minimize diff 2024-12-15 12:28:09 +01:00
295994d02a Use KC_BOOTSTRAP_ADMIN_ in newer keycloak 2024-12-15 11:57:45 +01:00
f9e15f93c4 Fix spelling mistake 2024-12-15 11:49:33 +01:00
2659cf391c Fix schema validation error 2024-12-15 11:47:59 +01:00
76416ddd5b Put persisted realm configs in .keycloakify 2024-12-15 11:45:00 +01:00
8e8a0ccf54 Store https://my-theme.keycloakify.dev as a constant 2024-12-15 11:38:50 +01:00
db0ec954df Fix zod schema error 2024-12-15 11:34:41 +01:00
dc942aa5de Implement cache for fetching available docker images tags 2024-12-15 08:53:54 +01:00
029cfcb591 Fix fetching of keycloak versions 2024-12-14 18:37:54 +01:00
b1b6919395 Assuming latest supported 2024-12-14 14:44:30 +01:00
9185740d35 Keycloak config persistance implemented (to test) 2024-12-14 14:36:11 +01:00
8d59fe7b67 Change structure 2024-12-13 12:16:41 +01:00
92b505dd56 Load custom extention for logging realm change 2024-12-13 12:07:21 +01:00
c0e6661d3d Add function to dump the realm config 2024-12-13 11:31:01 +01:00
0cae2c68d8 Add utils to edit the realm 2024-12-13 09:07:11 +01:00
1e43343529 Update keycloak 26 realm default config (fmt) 2024-12-12 11:19:06 +01:00
0a74dca7c2 Prettier ignore realm default config 2024-12-12 11:16:01 +01:00
a66a373256 Update dump-keycloak-realm internal script https://github.com/keycloak/keycloak/issues/33800 2024-12-10 04:12:56 +01:00
606cf7ad02 Bump version 2024-12-09 05:08:57 +01:00
5225749c7b React 19 compat #741 2024-12-09 05:06:47 +01:00
819e3833ad Bump version 2024-12-08 19:43:00 +01:00
b0ba37fcc4 Smarter appBuild script 2024-12-08 19:42:43 +01:00
f4829b557f Bump version 2024-12-06 00:38:23 +01:00
60a9b5a693 Improve i18n api typing 2024-12-06 00:38:09 +01:00
c323b94a8c Bump version 2024-12-04 00:09:14 +01:00
4bbc0241ec Do not crash when parser can't be inferred 2024-12-04 00:04:49 +01:00
5a7dacfcdd Bump version 2024-12-02 00:41:30 +01:00
7e05e1bf0c Use random port for dev server 2024-12-02 00:41:12 +01:00
1530ca32c8 Bump version 2024-12-01 00:07:28 +01:00
ed054f131a Merge pull request #736 from keycloakify/hmr_in_start_keycloak
Implement hot module replacement for developing Account SPA and Admin UI
2024-12-01 00:01:57 +01:00
59 changed files with 5406 additions and 684 deletions

View File

@ -12,4 +12,5 @@ node_modules/
/sample_react_project/
/sample_custom_react_project/
/keycloakify_starter_test/
/.storybook/static/keycloak-resources/
/.storybook/static/keycloak-resources/
/src/bin/start-keycloak/*.json

View File

@ -1,6 +1,6 @@
{
"name": "keycloakify",
"version": "11.3.32",
"version": "11.6.0",
"description": "Framework to create custom Keycloak UIs",
"repository": {
"type": "git",

View File

@ -0,0 +1,39 @@
import { downloadAndExtractArchive } from "../../src/bin/tools/downloadAndExtractArchive";
import { cacheDirPath } from "../shared/cacheDirPath";
import { getProxyFetchOptions } from "../../src/bin/tools/fetchProxyOptions";
import { getThisCodebaseRootDirPath } from "../../src/bin/tools/getThisCodebaseRootDirPath";
import { existsAsync } from "../../src/bin/tools/fs.existsAsync";
import * as fs from "fs/promises";
import {
KEYCLOAKIFY_LOGGING_VERSION,
KEYCLOAKIFY_LOGIN_JAR_BASENAME
} from "../../src/bin/shared/constants";
import { join as pathJoin } from "path";
export async function downloadKeycloakifyLogging(params: { distDirPath: string }) {
const { distDirPath } = params;
const jarFilePath = pathJoin(
distDirPath,
"src",
"bin",
"start-keycloak",
KEYCLOAKIFY_LOGIN_JAR_BASENAME
);
if (await existsAsync(jarFilePath)) {
return;
}
const { archiveFilePath } = await downloadAndExtractArchive({
cacheDirPath,
fetchOptions: getProxyFetchOptions({
npmConfigGetCwd: getThisCodebaseRootDirPath()
}),
url: `https://github.com/keycloakify/keycloakify-logging/releases/download/${KEYCLOAKIFY_LOGGING_VERSION}/keycloakify-logging-${KEYCLOAKIFY_LOGGING_VERSION}.jar`,
uniqueIdOfOnArchiveFile: "no extraction",
onArchiveFile: async () => {}
});
await fs.cp(archiveFilePath, jarFilePath);
}

View File

@ -7,6 +7,7 @@ import { createAccountV1Dir } from "./createAccountV1Dir";
import chalk from "chalk";
import { run } from "../shared/run";
import { vendorFrontendDependencies } from "./vendorFrontendDependencies";
import { downloadKeycloakifyLogging } from "./downloadKeycloakifyLogging";
(async () => {
console.log(chalk.cyan("Building Keycloakify..."));
@ -148,9 +149,6 @@ import { vendorFrontendDependencies } from "./vendorFrontendDependencies";
fs.cpSync(dirBasename, destDirPath, { recursive: true });
}
await createPublicKeycloakifyDevResourcesDir();
await createAccountV1Dir();
transformCodebase({
srcDirPath: join("stories"),
destDirPath: join("dist", "stories"),
@ -163,6 +161,12 @@ import { vendorFrontendDependencies } from "./vendorFrontendDependencies";
}
});
await createPublicKeycloakifyDevResourcesDir();
await createAccountV1Dir();
await downloadKeycloakifyLogging({
distDirPath: join(process.cwd(), "dist")
});
console.log(
chalk.green(`✓ built in ${((Date.now() - startTime) / 1000).toFixed(2)}s`)
);

View File

@ -67,7 +67,9 @@ export function vendorFrontendDependencies(params: { distDirPath: string }) {
)
);
run(`npx webpack --config ${webpackConfigJsFilePath}`);
run(`npx webpack --config ${pathBasename(webpackConfigJsFilePath)}`, {
cwd: pathDirname(webpackConfigJsFilePath)
});
fs.readdirSync(webpackOutputDirPath)
.filter(fileBasename => !fileBasename.endsWith(".txt"))

View File

@ -1,65 +1,14 @@
import { CONTAINER_NAME } from "../src/bin/shared/constants";
import child_process from "child_process";
import { SemVer } from "../src/bin/tools/SemVer";
import { join as pathJoin, relative as pathRelative } from "path";
import { dumpContainerConfig } from "../src/bin/start-keycloak/realmConfig/dumpContainerConfig";
import { cacheDirPath } from "./shared/cacheDirPath";
import { getThisCodebaseRootDirPath } from "../src/bin/tools/getThisCodebaseRootDirPath";
import { writeRealmJsonFile } from "../src/bin/start-keycloak/realmConfig/ParsedRealmJson";
import { join as pathJoin } from "path";
import chalk from "chalk";
import { Deferred } from "evt/tools/Deferred";
import { assert, is } from "tsafe/assert";
import { run } from "./shared/run";
(async () => {
{
const dCompleted = new Deferred<void>();
const child = child_process.spawn(
"docker",
[
...["exec", CONTAINER_NAME],
...["/opt/keycloak/bin/kc.sh", "export"],
...["--dir", "/tmp"],
...["--realm", "myrealm"],
...["--users", "realm_file"]
],
{ shell: true }
);
let output = "";
const onExit = (code: number | null) => {
dCompleted.reject(new Error(`Exited with code ${code}`));
};
child.on("exit", onExit);
child.stdout.on("data", data => {
const outputStr = data.toString("utf8");
if (outputStr.includes("Export finished successfully")) {
child.removeListener("exit", onExit);
child.kill();
dCompleted.resolve();
}
output += outputStr;
});
child.stderr.on("data", data => (output += chalk.red(data.toString("utf8"))));
try {
await dCompleted.pr;
} catch (error) {
assert(is<Error>(error));
console.log(chalk.red(error.message));
console.log(output);
process.exit(1);
}
}
const keycloakMajorVersionNumber = SemVer.parse(
child_process
.execSync(`docker inspect --format '{{.Config.Image}}' ${CONTAINER_NAME}`)
@ -68,19 +17,29 @@ import { run } from "./shared/run";
.split(":")[1]
).major;
const targetFilePath = pathRelative(
process.cwd(),
pathJoin(
__dirname,
"..",
"src",
"bin",
"start-keycloak",
`myrealm-realm-${keycloakMajorVersionNumber}.json`
)
const parsedRealmJson = await dumpContainerConfig({
buildContext: {
cacheDirPath
},
keycloakMajorVersionNumber,
realmName: "myrealm"
});
const realmJsonFilePath = pathJoin(
getThisCodebaseRootDirPath(),
"src",
"bin",
"start-keycloak",
"realmConfig",
"defaultConfig",
`realm-kc-${keycloakMajorVersionNumber}.json`
);
run(`docker cp ${CONTAINER_NAME}:/tmp/myrealm-realm.json ${targetFilePath}`);
await writeRealmJsonFile({
parsedRealmJson,
realmJsonFilePath,
keycloakMajorVersionNumber
});
console.log(`${chalk.green(`✓ Exported realm to`)} ${chalk.bold(targetFilePath)}`);
console.log(chalk.green(`Realm config dumped to ${realmJsonFilePath}`));
})();

View File

@ -45,7 +45,10 @@ const commonThirdPartyDeps = [
.replace(/"!\.\/dist\//g, '"!./');
modifiedPackageJsonContent = JSON.stringify(
{ ...JSON.parse(modifiedPackageJsonContent), version: "0.0.0" },
{
...JSON.parse(modifiedPackageJsonContent),
version: `0.0.0-rc.${~~(Math.random() * 1000000)}`
},
null,
4
);

View File

@ -1,3 +1,4 @@
import type { JSX } from "keycloakify/tools/JSX";
import { type TemplateProps, type ClassKey } from "keycloakify/account/TemplateProps";
import type { LazyOrNot } from "keycloakify/tools/LazyOrNot";

View File

@ -1,11 +1,9 @@
import { i18nBuilder } from "keycloakify/account";
import type { ThemeName } from "../kc.gen";
const { useI18n, ofTypeI18n } = i18nBuilder
.withThemeName<ThemeName>()
.withExtraLanguages({})
.withCustomTranslations({})
.build();
/** @see: https://docs.keycloakify.dev/i18n */
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { useI18n, ofTypeI18n } = i18nBuilder.withThemeName<ThemeName>().build();
type I18n = typeof ofTypeI18n;

View File

@ -11,7 +11,11 @@ import * as fs from "fs";
import { join as pathJoin } from "path";
import type { BuildContext } from "../../shared/buildContext";
import { assert } from "tsafe/assert";
import { type ThemeType, WELL_KNOWN_DIRECTORY_BASE_NAME } from "../../shared/constants";
import {
type ThemeType,
WELL_KNOWN_DIRECTORY_BASE_NAME,
KEYCLOAKIFY_SPA_DEV_SERVER_PORT
} from "../../shared/constants";
import { getThisCodebaseRootDirPath } from "../../tools/getThisCodebaseRootDirPath";
export type BuildContextLike = BuildContextLike_replaceImportsInJsCode &
@ -116,6 +120,7 @@ export function generateFtlFilesCodeFactory(params: {
.replace("{{themeVersion}}", buildContext.themeVersion)
.replace("{{fieldNames}}", fieldNames.map(name => `"${name}"`).join(", "))
.replace("{{RESOURCES_COMMON}}", WELL_KNOWN_DIRECTORY_BASE_NAME.RESOURCES_COMMON)
.replace("{{KEYCLOAKIFY_SPA_DEV_SERVER_PORT}}", KEYCLOAKIFY_SPA_DEV_SERVER_PORT)
.replace(
"{{userDefinedExclusions}}",
buildContext.kcContextExclusionsFtlCode ?? ""

View File

@ -101,7 +101,7 @@ redirect_to_dev_server: {
break redirect_to_dev_server;
}
const devSeverPort = kcContext.properties.KEYCLOAKIFY_SPA_DEV_SERVER_PORT;
const devSeverPort = kcContext.properties.{{KEYCLOAKIFY_SPA_DEV_SERVER_PORT}};
if( !devSeverPort ){
break redirect_to_dev_server;
@ -115,7 +115,7 @@ redirect_to_dev_server: {
console.log(kcContext);
redirectUrl.searchParams.set("kcContext", encodeURIComponent(JSON.stringify(kcContext)) );
redirectUrl.searchParams.set("kcContext", encodeURIComponent(JSON.stringify(kcContext)));
window.location.href = redirectUrl.toString();

View File

@ -40,6 +40,7 @@ import { escapeStringForPropertiesFile } from "../../tools/escapeStringForProper
import * as child_process from "child_process";
import { getThisCodebaseRootDirPath } from "../../tools/getThisCodebaseRootDirPath";
import propertiesParser from "properties-parser";
import { createObjectThatThrowsIfAccessed } from "../../tools/createObjectThatThrowsIfAccessed";
export type BuildContextLike = BuildContextLike_kcContextExclusionsFtlCode &
BuildContextLike_generateMessageProperties & {
@ -256,15 +257,17 @@ export async function generateResources(params: {
writeMessagePropertiesFiles;
}
bring_in_spas_messages: {
bring_in_account_spa_messages: {
if (!isSpa) {
break bring_in_spas_messages;
break bring_in_account_spa_messages;
}
assert(themeType !== "login");
if (themeType !== "account") {
break bring_in_account_spa_messages;
}
const accountUiDirPath = child_process
.execSync(`npm list @keycloakify/keycloak-${themeType}-ui --parseable`, {
.execSync(`npm list @keycloakify/keycloak-account-ui --parseable`, {
cwd: pathDirname(buildContext.packageJsonFilePath)
})
.toString("utf8")
@ -279,7 +282,7 @@ export async function generateResources(params: {
}
const messagesDirPath_dest = pathJoin(
getThemeTypeDirPath({ themeName, themeType }),
getThemeTypeDirPath({ themeName, themeType: "account" }),
"messages"
);
@ -291,7 +294,7 @@ export async function generateResources(params: {
apply_theme_changes: {
const messagesDirPath_theme = pathJoin(
buildContext.themeSrcDirPath,
themeType,
"account",
"messages"
);
@ -339,6 +342,159 @@ export async function generateResources(params: {
);
}
bring_in_admin_messages: {
if (themeType !== "admin") {
break bring_in_admin_messages;
}
const messagesDirPath_theme = pathJoin(
buildContext.themeSrcDirPath,
"admin",
"i18n"
);
assert(
fs.existsSync(messagesDirPath_theme),
`${messagesDirPath_theme} is supposed to exist`
);
const propertiesByLang: Record<
string,
{
base: Buffer;
override: Buffer | undefined;
overrideByThemeName: Record<string, Buffer>;
}
> = {};
fs.readdirSync(messagesDirPath_theme).forEach(basename => {
type ParsedBasename = { lang: string } & (
| {
isOverride: false;
}
| {
isOverride: true;
themeName: string | undefined;
}
);
const parsedBasename = ((): ParsedBasename | undefined => {
const match = basename.match(/^messages_([^.]+)\.properties$/);
if (match === null) {
return undefined;
}
const discriminator = match[1];
const split = discriminator.split("_override");
if (split.length === 1) {
return {
lang: discriminator,
isOverride: false
};
}
assert(split.length === 2);
if (split[1] === "") {
return {
lang: split[0],
isOverride: true,
themeName: undefined
};
}
const match2 = split[1].match(/^_(.+)$/);
assert(match2 !== null);
return {
lang: split[0],
isOverride: true,
themeName: match2[1]
};
})();
if (parsedBasename === undefined) {
return;
}
propertiesByLang[parsedBasename.lang] ??= {
base: createObjectThatThrowsIfAccessed<Buffer>({
debugMessage: `No base ${parsedBasename.lang} translation for admin theme`
}),
override: undefined,
overrideByThemeName: {}
};
const buffer = fs.readFileSync(pathJoin(messagesDirPath_theme, basename));
if (parsedBasename.isOverride === false) {
propertiesByLang[parsedBasename.lang].base = buffer;
return;
}
if (parsedBasename.themeName === undefined) {
propertiesByLang[parsedBasename.lang].override = buffer;
return;
}
propertiesByLang[parsedBasename.lang].overrideByThemeName[
parsedBasename.themeName
] = buffer;
});
writeMessagePropertiesFilesByThemeType.admin = ({
messageDirPath,
themeName
}) => {
if (!fs.existsSync(messageDirPath)) {
fs.mkdirSync(messageDirPath, { recursive: true });
}
Object.entries(propertiesByLang).forEach(
([lang, { base, override, overrideByThemeName }]) => {
(languageTags ??= []).push(lang);
const messages = propertiesParser.parse(base.toString("utf8"));
if (override !== undefined) {
const overrideMessages = propertiesParser.parse(
override.toString("utf8")
);
Object.entries(overrideMessages).forEach(
([key, value]) => (messages[key] = value)
);
}
if (themeName in overrideByThemeName) {
const overrideMessages = propertiesParser.parse(
overrideByThemeName[themeName].toString("utf8")
);
Object.entries(overrideMessages).forEach(
([key, value]) => (messages[key] = value)
);
}
const editor = propertiesParser.createEditor();
Object.entries(messages).forEach(([key, value]) => {
editor.set(key, value);
});
fs.writeFileSync(
pathJoin(messageDirPath, `messages_${lang}.properties`),
Buffer.from(editor.toString(), "utf8")
);
}
);
};
}
keycloak_static_resources: {
if (isSpa) {
break keycloak_static_resources;

View File

@ -5,9 +5,6 @@ import { readThisNpmPackageVersion } from "./tools/readThisNpmPackageVersion";
import * as child_process from "child_process";
import { assertNoPnpmDlx } from "./tools/assertNoPnpmDlx";
import { getBuildContext } from "./shared/buildContext";
import { SemVer } from "./tools/SemVer";
import { assert, is } from "tsafe/assert";
import chalk from "chalk";
type CliCommandOptions = {
projectDirPath: string | undefined;
@ -137,47 +134,11 @@ program
handler: async ({ projectDirPath, keycloakVersion, port, realmJsonFilePath }) => {
const { command } = await import("./start-keycloak");
validate_keycloak_version: {
if (keycloakVersion === undefined) {
break validate_keycloak_version;
}
const isValidVersion = (() => {
if (typeof keycloakVersion === "number") {
return false;
}
try {
SemVer.parse(keycloakVersion);
} catch {
return false;
}
return;
})();
if (isValidVersion) {
break validate_keycloak_version;
}
console.log(
chalk.red(
[
`Invalid Keycloak version: ${keycloakVersion}`,
"It should be a valid semver version example: 26.0.4"
].join(" ")
)
);
process.exit(1);
}
assert(is<string | undefined>(keycloakVersion));
await command({
buildContext: getBuildContext({ projectDirPath }),
cliCommandOptions: {
keycloakVersion,
keycloakVersion:
keycloakVersion === undefined ? undefined : `${keycloakVersion}`,
port,
realmJsonFilePath
}
@ -298,11 +259,12 @@ program
.option({
key: "file",
name: (() => {
const name = "file";
const long = "file";
const short = "f";
optionsKeys.push(name);
optionsKeys.push(long, short);
return name;
return { long, short };
})(),
description: [
"Relative path of the file relative to the directory of your keycloak theme source",

View File

@ -32,38 +32,20 @@ export async function getUiModuleFileSourceCodeReadyToBeCopied(params: {
await fsPr.readFile(pathJoin(uiModuleDirPath, KEYCLOAK_THEME, fileRelativePath))
).toString("utf8");
const toComment = (lines: string[]) => {
for (const ext of [".ts", ".tsx", ".css", ".less", ".sass", ".js", ".jsx"]) {
if (!fileRelativePath.endsWith(ext)) {
continue;
}
return [`/**`, ...lines.map(line => ` * ${line}`), ` */`].join("\n");
}
if (fileRelativePath.endsWith(".html")) {
return [`<!--`, ...lines.map(line => ` ${line}`), `-->`].join("\n");
}
return undefined;
};
const comment = toComment(
isForEjection
sourceCode = addCommentToSourceCode({
sourceCode,
fileRelativePath,
commentLines: isForEjection
? [`This file was ejected from ${uiModuleName} version ${uiModuleVersion}.`]
: [
`WARNING: Before modifying this file run the following command:`,
``,
`$ npx keycloakify eject-file --file ${fileRelativePath.split(pathSep).join("/")}`,
`$ npx keycloakify eject-file --file '${fileRelativePath.split(pathSep).join("/")}'`,
``,
`This file comes from ${uiModuleName} version ${uiModuleVersion}.`,
`This file has been copied over to your repo by your postinstall script: \`npx keycloakify postinstall\``
]
);
if (comment !== undefined) {
sourceCode = [comment, ``, sourceCode].join("\n");
}
});
const destFilePath = pathJoin(buildContext.themeSrcDirPath, fileRelativePath);
@ -80,3 +62,60 @@ export async function getUiModuleFileSourceCodeReadyToBeCopied(params: {
return Buffer.from(sourceCode, "utf8");
}
function addCommentToSourceCode(params: {
sourceCode: string;
fileRelativePath: string;
commentLines: string[];
}): string {
const { sourceCode, fileRelativePath, commentLines } = params;
const toResult = (comment: string) => {
return [comment, ``, sourceCode].join("\n");
};
for (const ext of [".ts", ".tsx", ".css", ".less", ".sass", ".js", ".jsx"]) {
if (!fileRelativePath.endsWith(ext)) {
continue;
}
return toResult(
[`/**`, ...commentLines.map(line => ` * ${line}`), ` */`].join("\n")
);
}
if (fileRelativePath.endsWith(".properties")) {
return toResult(commentLines.map(line => `# ${line}`).join("\n"));
}
if (fileRelativePath.endsWith(".html") || fileRelativePath.endsWith(".svg")) {
const comment = [
`<!--`,
...commentLines.map(
line =>
` ${line.replace("--file", "-f").replace("Before modifying", "Before modifying or replacing")}`
),
`-->`
].join("\n");
if (fileRelativePath.endsWith(".html") && sourceCode.trim().startsWith("<!")) {
const [first, ...rest] = sourceCode.split(">");
const last = rest.join(">");
return [`${first}>`, comment, last].join("\n");
}
if (fileRelativePath.endsWith(".svg") && sourceCode.trim().startsWith("<?")) {
const [first, ...rest] = sourceCode.split("?>");
const last = rest.join("?>");
return [`${first}?>`, comment, last].join("\n");
}
return toResult(comment);
}
return sourceCode;
}

View File

@ -81,3 +81,9 @@ export const CUSTOM_HANDLER_ENV_NAMES = {
export const KEYCLOAK_THEME = "keycloak-theme";
export const KEYCLOAKIFY_SPA_DEV_SERVER_PORT = "KEYCLOAKIFY_SPA_DEV_SERVER_PORT";
export const KEYCLOAKIFY_LOGGING_VERSION = "1.0.3";
export const KEYCLOAKIFY_LOGIN_JAR_BASENAME = `keycloakify-logging-${KEYCLOAKIFY_LOGGING_VERSION}.jar`;
export const TEST_APP_URL = "https://my-theme.keycloakify.dev";

View File

@ -1,18 +1,17 @@
import * as child_process from "child_process";
import { Deferred } from "evt/tools/Deferred";
import { assert } from "tsafe/assert";
import { assert, is, type Equals } from "tsafe/assert";
import { id } from "tsafe/id";
import type { BuildContext } from "../shared/buildContext";
import chalk from "chalk";
import { sep as pathSep, join as pathJoin } from "path";
import { getAbsoluteAndInOsFormatPath } from "../tools/getAbsoluteAndInOsFormatPath";
import * as fs from "fs";
import { dirname as pathDirname, relative as pathRelative } from "path";
import { z } from "zod";
export type BuildContextLike = {
projectDirPath: string;
keycloakifyBuildDirPath: string;
bundler: BuildContext["bundler"];
projectBuildDirPath: string;
packageJsonFilePath: string;
};
@ -23,58 +22,36 @@ export async function appBuild(params: {
}): Promise<{ isAppBuildSuccess: boolean }> {
const { buildContext } = params;
switch (buildContext.bundler) {
case "vite":
return appBuild_vite({ buildContext });
case "webpack":
return appBuild_webpack({ buildContext });
}
}
const { parsedPackageJson } = (() => {
type ParsedPackageJson = {
scripts?: Record<string, string>;
};
async function appBuild_vite(params: {
buildContext: BuildContextLike;
}): Promise<{ isAppBuildSuccess: boolean }> {
const { buildContext } = params;
const zParsedPackageJson = (() => {
type TargetType = ParsedPackageJson;
assert(buildContext.bundler === "vite");
const zTargetType = z.object({
scripts: z.record(z.string()).optional()
});
const dIsSuccess = new Deferred<boolean>();
assert<Equals<z.infer<typeof zTargetType>, TargetType>>();
console.log(chalk.blue("$ npx vite build"));
return id<z.ZodType<TargetType>>(zTargetType);
})();
const parsedPackageJson = JSON.parse(
fs.readFileSync(buildContext.packageJsonFilePath).toString("utf8")
);
const child = child_process.spawn("npx", ["vite", "build"], {
cwd: buildContext.projectDirPath,
shell: true
});
zParsedPackageJson.parse(parsedPackageJson);
child.stdout.on("data", data => {
if (data.toString("utf8").includes("gzip:")) {
return;
}
assert(is<ParsedPackageJson>(parsedPackageJson));
process.stdout.write(data);
});
return { parsedPackageJson };
})();
child.stderr.on("data", data => process.stderr.write(data));
child.on("exit", code => dIsSuccess.resolve(code === 0));
const isSuccess = await dIsSuccess.pr;
return { isAppBuildSuccess: isSuccess };
}
async function appBuild_webpack(params: {
buildContext: BuildContextLike;
}): Promise<{ isAppBuildSuccess: boolean }> {
const { buildContext } = params;
assert(buildContext.bundler === "webpack");
const entries = Object.entries(
(JSON.parse(fs.readFileSync(buildContext.packageJsonFilePath).toString("utf8"))
.scripts ?? {}) as Record<string, string>
).filter(([, scriptCommand]) => scriptCommand.includes("keycloakify build"));
const entries = Object.entries(parsedPackageJson.scripts ?? {}).filter(
([, scriptCommand]) => scriptCommand.includes("keycloakify build")
);
if (entries.length === 0) {
console.log(
@ -127,6 +104,76 @@ async function appBuild_webpack(params: {
process.exit(-1);
}
common_case: {
if (appBuildSubCommands.length !== 1) {
break common_case;
}
const [appBuildSubCommand] = appBuildSubCommands;
const isNpmRunBuild = (() => {
for (const packageManager of ["npm", "yarn", "pnpm", "bun", "deno"]) {
for (const doUseRun of [true, false]) {
if (
`${packageManager}${doUseRun ? " run " : " "}build` ===
appBuildSubCommand
) {
return true;
}
}
}
return false;
})();
if (!isNpmRunBuild) {
break common_case;
}
const { scripts } = parsedPackageJson;
assert(scripts !== undefined);
const buildCmd = scripts.build;
if (buildCmd !== "tsc && vite build") {
break common_case;
}
if (scripts.prebuild !== undefined) {
break common_case;
}
if (scripts.postbuild !== undefined) {
break common_case;
}
const dIsSuccess = new Deferred<boolean>();
console.log(chalk.blue("$ npx vite build"));
const child = child_process.spawn("npx", ["vite", "build"], {
cwd: buildContext.projectDirPath,
shell: true
});
child.stdout.on("data", data => {
if (data.toString("utf8").includes("gzip:")) {
return;
}
process.stdout.write(data);
});
child.stderr.on("data", data => process.stderr.write(data));
child.on("exit", code => dIsSuccess.resolve(code === 0));
const isSuccess = await dIsSuccess.pr;
return { isAppBuildSuccess: isSuccess };
}
let commandCwd = pathDirname(buildContext.packageJsonFilePath);
for (const subCommand of appBuildSubCommands) {

View File

@ -0,0 +1,267 @@
import fetch from "make-fetch-happen";
import type { BuildContext } from "../shared/buildContext";
import { assert, type Equals } from "tsafe/assert";
import { id } from "tsafe/id";
import { z } from "zod";
import { SemVer } from "../tools/SemVer";
import { exclude } from "tsafe/exclude";
import { getSupportedKeycloakMajorVersions } from "./realmConfig/defaultConfig";
import { join as pathJoin, dirname as pathDirname } from "path";
import * as fs from "fs/promises";
import { existsAsync } from "../tools/fs.existsAsync";
import { readThisNpmPackageVersion } from "../tools/readThisNpmPackageVersion";
import type { ReturnType } from "tsafe";
export type BuildContextLike = {
fetchOptions: BuildContext["fetchOptions"];
cacheDirPath: string;
};
assert<BuildContext extends BuildContextLike ? true : false>;
export async function getSupportedDockerImageTags(params: {
buildContext: BuildContextLike;
}): Promise<{
allSupportedTags: string[];
latestMajorTags: string[];
}> {
const { buildContext } = params;
{
const result = await getCachedValue({ cacheDirPath: buildContext.cacheDirPath });
if (result !== undefined) {
return result;
}
}
const tags_queryResponse: string[] = [];
await (async function callee(url: string) {
const r = await fetch(url, buildContext.fetchOptions);
await Promise.all([
(async () => {
tags_queryResponse.push(
...z
.object({
tags: z.array(z.string())
})
.parse(await r.json()).tags
);
})(),
(async () => {
const link = r.headers.get("link");
if (link === null) {
return;
}
const split = link.split(";").map(s => s.trim());
assert(split.length === 2);
assert(split[1] === 'rel="next"');
const match = split[0].match(/^<(.+)>$/);
assert(match !== null);
const nextUrl = new URL(url).origin + match[1];
await callee(nextUrl);
})()
]);
})("https://quay.io/v2/keycloak/keycloak/tags/list");
const supportedKeycloakMajorVersions = getSupportedKeycloakMajorVersions();
const allSupportedTags_withVersion = tags_queryResponse
.map(tag => ({
tag,
version: (() => {
if (tag.includes("-")) {
return undefined;
}
let version: SemVer;
try {
version = SemVer.parse(tag);
} catch {
return undefined;
}
if (tag.split(".").length !== 3) {
return undefined;
}
if (!supportedKeycloakMajorVersions.includes(version.major)) {
return undefined;
}
return version;
})()
}))
.map(({ tag, version }) => (version === undefined ? undefined : { tag, version }))
.filter(exclude(undefined))
.sort(({ version: a }, { version: b }) => SemVer.compare(b, a));
const latestTagByMajor: Record<number, SemVer | undefined> = {};
for (const { version } of allSupportedTags_withVersion) {
const version_current = latestTagByMajor[version.major];
if (
version_current === undefined ||
SemVer.compare(version_current, version) === -1
) {
latestTagByMajor[version.major] = version;
}
}
const latestMajorTags = Object.entries(latestTagByMajor)
.sort(([a], [b]) => parseInt(b) - parseInt(a))
.map(([, version]) => version)
.map(version => {
assert(version !== undefined);
if (!supportedKeycloakMajorVersions.includes(version.major)) {
return undefined;
}
return SemVer.stringify(version);
})
.filter(exclude(undefined));
const allSupportedTags = allSupportedTags_withVersion.map(({ tag }) => tag);
const result = {
latestMajorTags,
allSupportedTags
};
await setCachedValue({ cacheDirPath: buildContext.cacheDirPath, result });
return result;
}
const { getCachedValue, setCachedValue } = (() => {
type Result = ReturnType<typeof getSupportedDockerImageTags>;
const zResult = (() => {
type TargetType = Result;
const zTargetType = z.object({
allSupportedTags: z.array(z.string()),
latestMajorTags: z.array(z.string())
});
type InferredType = z.infer<typeof zTargetType>;
assert<Equals<TargetType, InferredType>>;
return id<z.ZodType<TargetType>>(zTargetType);
})();
type Cache = {
keycloakifyVersion: string;
time: number;
result: Result;
};
const zCache = (() => {
type TargetType = Cache;
const zTargetType = z.object({
keycloakifyVersion: z.string(),
time: z.number(),
result: zResult
});
type InferredType = z.infer<typeof zTargetType>;
assert<Equals<TargetType, InferredType>>;
return id<z.ZodType<TargetType>>(zTargetType);
})();
let inMemoryCachedResult: Cache["result"] | undefined = undefined;
function getCacheFilePath(params: { cacheDirPath: string }) {
const { cacheDirPath } = params;
return pathJoin(cacheDirPath, "supportedDockerImageTags.json");
}
async function getCachedValue(params: { cacheDirPath: string }) {
const { cacheDirPath } = params;
if (inMemoryCachedResult !== undefined) {
return inMemoryCachedResult;
}
const cacheFilePath = getCacheFilePath({ cacheDirPath });
if (!(await existsAsync(cacheFilePath))) {
return undefined;
}
let cache: Cache | undefined;
try {
cache = zCache.parse(JSON.parse(await fs.readFile(cacheFilePath, "utf8")));
} catch {
return undefined;
}
if (cache.keycloakifyVersion !== readThisNpmPackageVersion()) {
return undefined;
}
if (Date.now() - cache.time > 3_600 * 24) {
return undefined;
}
inMemoryCachedResult = cache.result;
return cache.result;
}
async function setCachedValue(params: {
cacheDirPath: string;
result: Cache["result"];
}) {
const { cacheDirPath, result } = params;
inMemoryCachedResult = result;
const cacheFilePath = getCacheFilePath({ cacheDirPath });
{
const dirPath = pathDirname(cacheFilePath);
if (!(await existsAsync(dirPath))) {
await fs.mkdir(dirPath, { recursive: true });
}
}
await fs.writeFile(
cacheFilePath,
JSON.stringify(
zCache.parse({
keycloakifyVersion: readThisNpmPackageVersion(),
time: Date.now(),
result
}),
null,
2
)
);
}
return {
getCachedValue,
setCachedValue
};
})();

View File

@ -0,0 +1,118 @@
import { z } from "zod";
import { assert, type Equals } from "tsafe/assert";
import { id } from "tsafe/id";
export type ParsedRealmJson = {
realm: string;
loginTheme?: string;
accountTheme?: string;
adminTheme?: string;
emailTheme?: string;
eventsListeners: string[];
users: {
id: string;
email: string;
username: string;
credentials: {
type: string /* "password" or something else */;
}[];
clientRoles?: Record<string, string[]>;
}[];
roles: {
client: Record<
string,
{
name: string;
containerId: string; // client id
}[]
>;
};
clients: {
id: string;
clientId: string; // example: realm-management
baseUrl?: string;
redirectUris?: string[];
webOrigins?: string[];
attributes?: {
"post.logout.redirect.uris"?: string;
};
protocol?: string;
protocolMappers?: {
id: string;
name: string;
protocol: string; // "openid-connect" or something else
protocolMapper: string; // "oidc-hardcoded-claim-mapper" or something else
consentRequired: boolean;
config?: Record<string, string>;
}[];
}[];
};
export const zParsedRealmJson = (() => {
type TargetType = ParsedRealmJson;
const zTargetType = z.object({
realm: z.string(),
loginTheme: z.string().optional(),
accountTheme: z.string().optional(),
adminTheme: z.string().optional(),
emailTheme: z.string().optional(),
eventsListeners: z.array(z.string()),
users: z.array(
z.object({
id: z.string(),
email: z.string(),
username: z.string(),
credentials: z.array(
z.object({
type: z.string()
})
),
clientRoles: z.record(z.array(z.string())).optional()
})
),
roles: z.object({
client: z.record(
z.array(
z.object({
name: z.string(),
containerId: z.string()
})
)
)
}),
clients: z.array(
z.object({
id: z.string(),
clientId: z.string(),
baseUrl: z.string().optional(),
redirectUris: z.array(z.string()).optional(),
webOrigins: z.array(z.string()).optional(),
attributes: z
.object({
"post.logout.redirect.uris": z.string().optional()
})
.optional(),
protocol: z.string().optional(),
protocolMappers: z
.array(
z.object({
id: z.string(),
name: z.string(),
protocol: z.string(),
protocolMapper: z.string(),
consentRequired: z.boolean(),
config: z.record(z.string()).optional()
})
)
.optional()
})
)
});
type InferredType = z.infer<typeof zTargetType>;
assert<Equals<TargetType, InferredType>>;
return id<z.ZodType<TargetType>>(zTargetType);
})();

View File

@ -0,0 +1,3 @@
export type { ParsedRealmJson } from "./ParsedRealmJson";
export { readRealmJsonFile } from "./readRealmJsonFile";
export { writeRealmJsonFile } from "./writeRealmJsonFile";

View File

@ -0,0 +1,20 @@
import { assert } from "tsafe/assert";
import { is } from "tsafe/is";
import * as fs from "fs";
import { type ParsedRealmJson, zParsedRealmJson } from "./ParsedRealmJson";
export function readRealmJsonFile(params: {
realmJsonFilePath: string;
}): ParsedRealmJson {
const { realmJsonFilePath } = params;
const parsedRealmJson = JSON.parse(
fs.readFileSync(realmJsonFilePath).toString("utf8")
) as unknown;
zParsedRealmJson.parse(parsedRealmJson);
assert(is<ParsedRealmJson>(parsedRealmJson));
return parsedRealmJson;
}

View File

@ -0,0 +1,29 @@
import * as fsPr from "fs/promises";
import { getIsPrettierAvailable, runPrettier } from "../../../tools/runPrettier";
import { canonicalStringify } from "../../../tools/canonicalStringify";
import type { ParsedRealmJson } from "./ParsedRealmJson";
import { getDefaultConfig } from "../defaultConfig";
export async function writeRealmJsonFile(params: {
realmJsonFilePath: string;
parsedRealmJson: ParsedRealmJson;
keycloakMajorVersionNumber: number;
}): Promise<void> {
const { realmJsonFilePath, parsedRealmJson, keycloakMajorVersionNumber } = params;
let sourceCode = canonicalStringify({
data: parsedRealmJson,
referenceData: getDefaultConfig({
keycloakMajorVersionNumber
})
});
if (await getIsPrettierAvailable()) {
sourceCode = await runPrettier({
sourceCode: sourceCode,
filePath: realmJsonFilePath
});
}
await fsPr.writeFile(realmJsonFilePath, Buffer.from(sourceCode, "utf8"));
}

View File

@ -0,0 +1,74 @@
import { join as pathJoin, dirname as pathDirname } from "path";
import { getThisCodebaseRootDirPath } from "../../../tools/getThisCodebaseRootDirPath";
import * as fs from "fs";
import { exclude } from "tsafe/exclude";
import { assert } from "tsafe/assert";
import { readRealmJsonFile } from "../ParsedRealmJson/readRealmJsonFile";
import type { ParsedRealmJson } from "../ParsedRealmJson/ParsedRealmJson";
function getDefaultRealmJsonFilePath(params: { keycloakMajorVersionNumber: number }) {
const { keycloakMajorVersionNumber } = params;
return pathJoin(
getThisCodebaseRootDirPath(),
"src",
"bin",
"start-keycloak",
"realmConfig",
"defaultConfig",
`realm-kc-${keycloakMajorVersionNumber}.json`
);
}
export const { getSupportedKeycloakMajorVersions } = (() => {
let cache: number[] | undefined = undefined;
function getSupportedKeycloakMajorVersions(): number[] {
if (cache !== undefined) {
return cache;
}
cache = fs
.readdirSync(
pathDirname(
getDefaultRealmJsonFilePath({ keycloakMajorVersionNumber: 0 })
)
)
.map(fileBasename => {
const match = fileBasename.match(/^realm-kc-(\d+)\.json$/);
if (match === null) {
return undefined;
}
const n = parseInt(match[1]);
assert(!isNaN(n));
return n;
})
.filter(exclude(undefined))
.sort((a, b) => b - a);
return cache;
}
return { getSupportedKeycloakMajorVersions };
})();
export function getDefaultConfig(params: {
keycloakMajorVersionNumber: number;
}): ParsedRealmJson {
const { keycloakMajorVersionNumber } = params;
assert(
getSupportedKeycloakMajorVersions().includes(keycloakMajorVersionNumber),
`We do not have a default config for Keycloak ${keycloakMajorVersionNumber}`
);
return readRealmJsonFile({
realmJsonFilePath: getDefaultRealmJsonFilePath({
keycloakMajorVersionNumber
})
});
}

View File

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

View File

@ -73,7 +73,7 @@
"composites": {
"realm": ["offline_access", "uma_authorization"],
"client": {
"account": ["delete-account", "view-profile", "manage-account"]
"account": ["view-profile", "manage-account", "delete-account"]
}
},
"clientRole": false,
@ -398,6 +398,26 @@
"otpPolicyLookAheadWindow": 1,
"otpPolicyPeriod": 30,
"otpSupportedApplications": ["FreeOTP", "Google Authenticator"],
"webAuthnPolicyRpEntityName": "keycloak",
"webAuthnPolicySignatureAlgorithms": ["ES256"],
"webAuthnPolicyRpId": "",
"webAuthnPolicyAttestationConveyancePreference": "not specified",
"webAuthnPolicyAuthenticatorAttachment": "not specified",
"webAuthnPolicyRequireResidentKey": "not specified",
"webAuthnPolicyUserVerificationRequirement": "not specified",
"webAuthnPolicyCreateTimeout": 0,
"webAuthnPolicyAvoidSameAuthenticatorRegister": false,
"webAuthnPolicyAcceptableAaguids": [],
"webAuthnPolicyPasswordlessRpEntityName": "keycloak",
"webAuthnPolicyPasswordlessSignatureAlgorithms": ["ES256"],
"webAuthnPolicyPasswordlessRpId": "",
"webAuthnPolicyPasswordlessAttestationConveyancePreference": "not specified",
"webAuthnPolicyPasswordlessAuthenticatorAttachment": "not specified",
"webAuthnPolicyPasswordlessRequireResidentKey": "not specified",
"webAuthnPolicyPasswordlessUserVerificationRequirement": "not specified",
"webAuthnPolicyPasswordlessCreateTimeout": 0,
"webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister": false,
"webAuthnPolicyPasswordlessAcceptableAaguids": [],
"users": [
{
"id": "00a62e75-bcc1-419a-a292-63ee5d161ed3",
@ -422,30 +442,43 @@
"disableableCredentialTypes": [],
"requiredActions": [],
"realmRoles": ["default-roles-myrealm"],
"clientRoles": {
"realm-management": [
"create-client",
"view-identity-providers",
"manage-realm",
"query-groups",
"manage-clients",
"query-users",
"realm-admin",
"view-authorization",
"view-events",
"view-clients",
"view-realm",
"manage-events",
"query-realms",
"query-clients",
"manage-identity-providers",
"manage-users",
"view-users",
"impersonation",
"manage-authorization"
],
"broker": ["read-token"],
"account": [
"view-profile",
"manage-account-links",
"view-applications",
"manage-consent",
"delete-account",
"manage-account",
"view-consent"
]
},
"notBefore": 0,
"groups": []
}
],
"webAuthnPolicyRpEntityName": "keycloak",
"webAuthnPolicySignatureAlgorithms": ["ES256"],
"webAuthnPolicyRpId": "",
"webAuthnPolicyAttestationConveyancePreference": "not specified",
"webAuthnPolicyAuthenticatorAttachment": "not specified",
"webAuthnPolicyRequireResidentKey": "not specified",
"webAuthnPolicyUserVerificationRequirement": "not specified",
"webAuthnPolicyCreateTimeout": 0,
"webAuthnPolicyAvoidSameAuthenticatorRegister": false,
"webAuthnPolicyAcceptableAaguids": [],
"webAuthnPolicyPasswordlessRpEntityName": "keycloak",
"webAuthnPolicyPasswordlessSignatureAlgorithms": ["ES256"],
"webAuthnPolicyPasswordlessRpId": "",
"webAuthnPolicyPasswordlessAttestationConveyancePreference": "not specified",
"webAuthnPolicyPasswordlessAuthenticatorAttachment": "not specified",
"webAuthnPolicyPasswordlessRequireResidentKey": "not specified",
"webAuthnPolicyPasswordlessUserVerificationRequirement": "not specified",
"webAuthnPolicyPasswordlessCreateTimeout": 0,
"webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister": false,
"webAuthnPolicyPasswordlessAcceptableAaguids": [],
"scopeMappings": [
{
"clientScope": "offline_access",
@ -505,8 +538,12 @@
"enabled": true,
"alwaysDisplayInConsole": false,
"clientAuthenticatorType": "client-secret",
"redirectUris": ["/realms/myrealm/account/*"],
"webOrigins": [],
"redirectUris": [
"http://localhost*",
"http://127.0.0.1*",
"/realms/myrealm/account/*"
],
"webOrigins": ["*"],
"notBefore": 0,
"bearerOnly": false,
"consentRequired": false,
@ -518,6 +555,7 @@
"frontchannelLogout": false,
"protocol": "openid-connect",
"attributes": {
"post.logout.redirect.uris": "+",
"pkce.code.challenge.method": "S256"
},
"authenticationFlowBindingOverrides": {},
@ -636,7 +674,7 @@
"attributes": {
"oidc.ciba.grant.enabled": "false",
"backchannel.logout.session.required": "true",
"login_theme": "keycloakify-starter",
"post.logout.redirect.uris": "+",
"display.on.consent.screen": "false",
"oauth2.device.authorization.grant.enabled": "false",
"backchannel.logout.revoke.offline.tokens": "false"
@ -694,8 +732,12 @@
"enabled": true,
"alwaysDisplayInConsole": false,
"clientAuthenticatorType": "client-secret",
"redirectUris": ["/admin/myrealm/console/*"],
"webOrigins": ["+"],
"redirectUris": [
"http://localhost*",
"http://127.0.0.1*",
"/admin/myrealm/console/*"
],
"webOrigins": ["*"],
"notBefore": 0,
"bearerOnly": false,
"consentRequired": false,
@ -707,12 +749,31 @@
"frontchannelLogout": false,
"protocol": "openid-connect",
"attributes": {
"post.logout.redirect.uris": "+",
"pkce.code.challenge.method": "S256"
},
"authenticationFlowBindingOverrides": {},
"fullScopeAllowed": false,
"nodeReRegistrationTimeout": 0,
"protocolMappers": [
{
"id": "8fd0d584-7052-4d04-a615-d18a71050873",
"name": "allowed-origins",
"protocol": "openid-connect",
"protocolMapper": "oidc-hardcoded-claim-mapper",
"consentRequired": false,
"config": {
"userinfo.token.claim": "true",
"id.token.claim": "false",
"access.token.claim": "true",
"claim.name": "allowed-origins",
"jsonType.label": "JSON",
"access.tokenResponse.claim": "false",
"claim.value": "[\"*\"]",
"introspection.token.claim": "true",
"lightweight.claim": "true"
}
},
{
"id": "7779f8fa-c2fe-4e68-be56-66ee97bf8f13",
"name": "locale",
@ -757,7 +818,8 @@
"consentRequired": false,
"config": {
"id.token.claim": "true",
"access.token.claim": "true"
"access.token.claim": "true",
"userinfo.token.claim": "true"
}
}
]
@ -1205,6 +1267,7 @@
"consentRequired": false,
"config": {
"multivalued": "true",
"userinfo.token.claim": "true",
"user.attribute": "foo",
"id.token.claim": "true",
"access.token.claim": "true",
@ -1271,11 +1334,11 @@
},
"smtpServer": {},
"loginTheme": "keycloakify-starter",
"accountTheme": "keycloakify-starter",
"accountTheme": "",
"adminTheme": "",
"emailTheme": "",
"eventsEnabled": false,
"eventsListeners": ["jboss-logging"],
"eventsListeners": ["keycloakify-logging", "jboss-logging"],
"enabledEventTypes": [],
"adminEventsEnabled": false,
"adminEventsDetailsEnabled": false,
@ -1291,14 +1354,14 @@
"subComponents": {},
"config": {
"allowed-protocol-mapper-types": [
"oidc-full-name-mapper",
"saml-user-property-mapper",
"oidc-sha256-pairwise-sub-mapper",
"oidc-address-mapper",
"oidc-full-name-mapper",
"oidc-usermodel-attribute-mapper",
"saml-user-attribute-mapper",
"oidc-address-mapper",
"oidc-sha256-pairwise-sub-mapper",
"saml-role-list-mapper",
"oidc-usermodel-property-mapper"
"oidc-usermodel-property-mapper",
"saml-role-list-mapper"
]
}
},
@ -1347,14 +1410,14 @@
"subComponents": {},
"config": {
"allowed-protocol-mapper-types": [
"oidc-usermodel-property-mapper",
"oidc-address-mapper",
"oidc-full-name-mapper",
"saml-user-property-mapper",
"saml-user-attribute-mapper",
"oidc-address-mapper",
"saml-role-list-mapper",
"oidc-sha256-pairwise-sub-mapper",
"oidc-usermodel-attribute-mapper",
"saml-role-list-mapper"
"saml-user-attribute-mapper",
"saml-user-property-mapper",
"oidc-usermodel-property-mapper",
"oidc-usermodel-attribute-mapper"
]
}
},
@ -1394,6 +1457,12 @@
"providerId": "rsa-generated",
"subComponents": {},
"config": {
"privateKey": [
"MIIEpAIBAAKCAQEA+VQAcuaRivrzLVI8H/tt8PKbtRznTQKmmxOdLRR37leY/ph7sFnEmZt6K02Rvut7R0dxUFtTdiEHUKxhyM8CADMznGUjDYj/EXQzLfZ3LEwbwmR39zp+fZL/H24UDO03zt23Ov9C8Aly0ufXZ1Ic1c33KW6UtUEK/3M52pU8Y0daWdjx7nBj1eRlzWfVG+BYotTTWEnFJuEoZPFQMiXqeA5ob1zZdXjL5JDuGEiBsYjtiiaKbKL5545+FmEBnoCmWXqGu0qWxI2TzvV2dohxfl5KjNzRoKt40ydraiVk5rtBpoNDpeEApuphbokH5dJVwJ5cvWu1CSTnYPW2jXeG4wIDAQABAoIBAQDHV6AcPbhz8/xlafBkabQXBwHzJi7QZaQrLN1n44uX5jWOqP+LmdoULjjZUmWKzd98t+QjKUFrmzCsEYcE9G1XF5jWHA6Qjc3ReKRKxVm28wrmu0knQ39KizKrQGmLhEYwgRg0dU5heExzz6VrGD2xu8E3QRBocp6GauwAlXz4qcnTPHOl8OBPeDHAc0RUdaL5+jRLgKQzf9nnnKB19imBKP++zwrwFrkOZti2ZPs1I7j/ym27mHUbi8TDI2VepDX4QwjjC5a+v3vTsVAGE+1tUAZtqpxpIP9hiUkLH3ajyvp3typhnmZHklqsSZdwtRcK94WiMzL3TkiY70y8abMhAoGBAP8I4EQRXxcKfBn23eaRw8Cd4PFrOouz4zFbYLrBODsvXfku/jnQOMFD0If4IzT6y0FGgBd+t/yqnFJi98oZOKm3P8w+NZBXTbFLH8rgmsElXyS0+9LVMjVa7+UlqZB1eRZbUeLREp03Fsz1y2rflnoWgUnpDIlyhmJqGhCsJdebAoGBAPpFmJ9P42mUTeDWpCyCxgg0zpp6rlpAP8StqZkcvr7kYjhbWrJfJuxrTXtzTTA1zZ59L9EvEAxuug/gl9BkuZ11Uzg8ZLOr4gSuAJZlAORaxJlcoylmNMYIL1fP/K0dxhdO0eHZOpPVpBmGctgev2HBtWp9ZwzQ3DddKimZfNZZAoGAfNOOWSKbhT6HgXnYIHtl8YgUynUuYaR5ZfYQwTfDWwyTFVzP5+IndUjI71Qff1XlWBy2o0lNqmijPJveJlfz6PWdT01/kBd7GnTnqbgHZtPw3pmKzCW3fm/1DRZDCUbGLpAh4z9rufF1wnnnx3aKQ1VykId1sGySo+bEvTZVC1MCgYAlv6uWk/ksKpdYi2d14z+1aymieVClAj3cD4meM4y9xDrgXz8d2mZHkKO+NBT3aZYbCqzUs3GLPoRH8stTPm4UxuaHe+yAgTN1Gz2xcYih6OLwct2VV/oryH5Dk3Z8Mhp314amtxozxCydQP8/g9vABfS0HDgX4cTlgOLkJWeD+QKBgQDuRtsstQ4Q3yK44himPi1JQMMvbYAqyGgRxWH8G1Kr41DV2sQ4wt9CbYxeh6RwMsE+YYNMkTAw1kksUTugWdcDnYpcSVG7xHLJk8WMti0WTqI/7KlkoRehXXv18WJNEXaCr5mJTtJL9wuQcd8nhkEDrrCZubZiJzX9IDnEqZc4Mg=="
],
"certificate": [
"MIICnTCCAYUCBgGTy58etTANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdteXJlYWxtMB4XDTI0MTIxNTE4Mzg0M1oXDTM0MTIxNTE4NDAyM1owEjEQMA4GA1UEAwwHbXlyZWFsbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPlUAHLmkYr68y1SPB/7bfDym7Uc500CppsTnS0Ud+5XmP6Ye7BZxJmbeitNkb7re0dHcVBbU3YhB1CsYcjPAgAzM5xlIw2I/xF0My32dyxMG8Jkd/c6fn2S/x9uFAztN87dtzr/QvAJctLn12dSHNXN9ylulLVBCv9zOdqVPGNHWlnY8e5wY9XkZc1n1RvgWKLU01hJxSbhKGTxUDIl6ngOaG9c2XV4y+SQ7hhIgbGI7Yomimyi+eeOfhZhAZ6Apll6hrtKlsSNk871dnaIcX5eSozc0aCreNMna2olZOa7QaaDQ6XhAKbqYW6JB+XSVcCeXL1rtQkk52D1to13huMCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAH/nsEi88hFiNPCWYvTB3lERZpeUCbpDzAXQT/4TONmOw8zi7Cd2OlX8BGBFqjh/fESHv+adlzsY1mUdMvpVaYgHr3gYi8sBSrq5TMUfSYaWp4WCD7utiXXGprG08GCdbye1lpyyNnniWp12Bgjao+rtGamL/M1d6+WZTC+XL+H30u4VHURAiFBsAEoX6tlGV8ynhYOr/b8B43jy0/R0JfrzLjwSKEcA6RfKM7ozbZ0QZuQDALULymPIesrV4mvZ2Qwg4YgpAKaki9Sse45yiIhsIY0p5RnuNZRZnCbukyeBzIyDJobEBGhpui/KT2dqXBlRgRuOhCUf7OGCcPVHKNQ=="
],
"priority": ["100"]
}
},
@ -1403,6 +1472,12 @@
"providerId": "rsa-enc-generated",
"subComponents": {},
"config": {
"privateKey": [
"MIIEogIBAAKCAQEAn82AU+InXwYlE8u9lMwhQghZB7oQ71Hg3PdFqS9ICGzw1u1JcENooCsZse55V6nqptdYF1oZA8QrxnhHzCVCGIqFHtXSoPGHVtozO3Fe1cVIVFm1D9TNS3JHe1C8SBQQT4hGItO5cjDyfGdK3x09RkoAcelrzH5uQ78zd0FKHkzbsTMsP2V8V94c35+ViIUjyGhH2T2BpIyGRLignL+6d0wHbw463L1Ewj/J9z8BtNLCH9PaVLWiGQARjlWyL9vtWBig9XXL0Z9tZUuoLihjh4StkXt2lQ++DKxUklsAjyenRAG5d72T2rY8MO5a1Z2ZSt8+s86D5esrAEIFZc9mqwIDAQABAoIBAAmmCcqGzCPDpjd0xMSYMqXfBSkfReh9RBtzXqRhc3L2yO/hMd7yYv3QvGNu56qwWreqJup6CSqeDJqWJpef5EbBDlqXRHltO+O1lwROyxATMlPNes4y5hZZFxHOBSBA/d8fdkSiDf9kDzANuIqSJGH7E93M3zJgq92xTLU1nvkHR/VYJQv+j+Pjye7MWvjIePfhwFeBqEWlWPTlw/080Mpfp8Hhbl6JeKjx2inkSphp43v4wR1Wmp+E2JIHF4P4sVXPPuPf3JDwg5uGOrROw1ziloD3jTI+LnQ+kRm6R2EbqRqqVsehXT7mZy2puQNqVc4vVqWQdxIErMBazYEpZOECgYEA+8PEcDiIPr2PTYZk+/jErRVYwsxyLgDJexPak7onLxLBJRNRnp1Uk6b1LXM6af5qp+Y510kyAe1k+9xkQLx1gW8rMka9rvVsM+1A2ACvF99V23sRw29CVxeFV/zNn83MinYPX5biUl6MkOX2PvWUhdwRGhKByjiYcAeBOsXkz3ECgYEAon2yYXGzph8Vb8Fetv0wFFbjQOixuL02OjVp/nU1XVE8Aw9BJ7uzA6GQ7akPG0HsaUq7AEHP1uUOsJWQTNQ8WYD9LDuDOl/JFqkG+zrmdUdm0mAIYyH1/GBqgaTLvMq78qqosua8BBJojEyoXDz69UBHpu7cwtUgmzRNQSYqgdsCgYASvD3JEBvrd1XLsh2ftqKEMtt5G5e/nqVfuFmCts6lrSKcbLSdNh4OItWJ/VIygxFSz0osoDDNfeoO6Ba5zox8BlbTlfoVpAPaVWSG7n4ZK7CK9bybq5LnQkPVCWYP51O6VhDMz0CmWozhV4ucoc/cqkTHiOsJrm6Bn71ZL1LYsQKBgFNb8qgk4YnGhoPHiuSLbR/yFzGUbqAciXZBMrg0vwS5iPT03XMZytOBDk2uHi7YmgTGLrsKCCrxZaDXiaiwdKliD/+iJEdNHmc+nXNDGzltQOWKGKNqp7wqZllOBqs6wkLSpCrrTec03mejZ/ex3Pj2WgvcnGpjVg/pO/zBLKtjAoGACzGQNEF93fabHQJTsHmb/g+jO2iumjF6ZIWzdFh2KzQABONcoBvy1MJNASFQj3iVy/8kEo4SfmexvMWLBW9igi2z1pHeHY32EuImzuc4xnVDm6dkmDdsO43Ex6CFBx8lM40H4l27mXu+EZRzGClUY8TnmV/FBGmX+LPtOiiwT7s="
],
"certificate": [
"MIICnTCCAYUCBgGTy58fHjANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdteXJlYWxtMB4XDTI0MTIxNTE4Mzg0M1oXDTM0MTIxNTE4NDAyM1owEjEQMA4GA1UEAwwHbXlyZWFsbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJ/NgFPiJ18GJRPLvZTMIUIIWQe6EO9R4Nz3RakvSAhs8NbtSXBDaKArGbHueVep6qbXWBdaGQPEK8Z4R8wlQhiKhR7V0qDxh1baMztxXtXFSFRZtQ/UzUtyR3tQvEgUEE+IRiLTuXIw8nxnSt8dPUZKAHHpa8x+bkO/M3dBSh5M27EzLD9lfFfeHN+flYiFI8hoR9k9gaSMhkS4oJy/undMB28OOty9RMI/yfc/AbTSwh/T2lS1ohkAEY5Vsi/b7VgYoPV1y9GfbWVLqC4oY4eErZF7dpUPvgysVJJbAI8np0QBuXe9k9q2PDDuWtWdmUrfPrPOg+XrKwBCBWXPZqsCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEATwmKBzLiZiUjyB9BWUR4BCXh46DxsiM0BCublewlUFY6FBTn7ea6q3G+X3QP2WM6xa0oAmQz9dq1KChbIoC2WPbceAbwd5XZZfziWsRCv6+xPswtpHPIrsenz8TR4K4P73aeCC+vTVs/y+2tGPEVbnSkcNnOP71hRQGlt0LvjKlEetJSRyYz5depSdJOjl4F3ehpxQtTK/48xUVAytu9ZotJj6AUA7jWFlP5GHgoB+mPk6QTHNWddnc7BQx2FMvg151vxu722ywLh5Dh7WzgFhJNwkX4xpwzhfo0Q1gSygGTdZaJCGj5jfF+KwdiKpN04UxJ8OrRgJqklQgrSVnsgQ=="
],
"priority": ["100"],
"algorithm": ["RSA-OAEP"]
}
@ -1413,6 +1488,8 @@
"providerId": "aes-generated",
"subComponents": {},
"config": {
"kid": ["132fb843-59e9-4f36-ad55-5ce2d3a13fb3"],
"secret": ["ETyyqapnrkUsNXLQ-tBVKw"],
"priority": ["100"]
}
},
@ -1422,6 +1499,10 @@
"providerId": "hmac-generated",
"subComponents": {},
"config": {
"kid": ["5110d380-c930-49d9-b91b-87f338f6170b"],
"secret": [
"uCpQrJvP5OBuTxXfDb4JRL0bCKpXUgfGn5vb8UvL-Sfs_sZ9rtvBmd6vuFWARqyezjJQtpoNlMv7sXgxkN-yxQ"
],
"priority": ["100"],
"algorithm": ["HS256"]
}
@ -1454,7 +1535,7 @@
"defaultLocale": "en",
"authenticationFlows": [
{
"id": "f7f2b89b-43cb-491d-8e7c-f1814024a6da",
"id": "f664efe4-102d-4ec1-bf11-11af67e3f178",
"alias": "Account verification options",
"description": "Method with which to verity the existing account",
"providerId": "basic-flow",
@ -1480,7 +1561,7 @@
]
},
{
"id": "17cdac6f-d2a3-4907-8d44-a42827610b63",
"id": "8a5630c5-eca1-4b6a-8e59-459cb6c84535",
"alias": "Authentication Options",
"description": "Authentication options.",
"providerId": "basic-flow",
@ -1514,7 +1595,7 @@
]
},
{
"id": "53a3e43f-9468-401f-8051-40f982d12f85",
"id": "c1a3eed3-25ce-44ae-93d1-f0b8148a0f8c",
"alias": "Browser - Conditional OTP",
"description": "Flow to determine if the OTP is required for the authentication",
"providerId": "basic-flow",
@ -1540,7 +1621,7 @@
]
},
{
"id": "26286808-3b7b-43df-b32e-af55a37af2e9",
"id": "6eb188ad-1041-44dd-bf8f-37cae0d98bf1",
"alias": "Direct Grant - Conditional OTP",
"description": "Flow to determine if the OTP is required for the authentication",
"providerId": "basic-flow",
@ -1566,7 +1647,7 @@
]
},
{
"id": "8a6a752a-9a9a-4d38-b1f8-edf0a9433490",
"id": "4ee215ac-f4e5-4edb-bf76-65dc9e211543",
"alias": "First broker login - Conditional OTP",
"description": "Flow to determine if the OTP is required for the authentication",
"providerId": "basic-flow",
@ -1592,7 +1673,7 @@
]
},
{
"id": "a6f6804c-4160-4a84-8a1f-c2747a2d3f27",
"id": "5a1eac7e-06a0-46d8-b9ae-1f2c934331f9",
"alias": "Handle Existing Account",
"description": "Handle what to do if there is existing account with same email/username like authenticated identity provider",
"providerId": "basic-flow",
@ -1618,7 +1699,7 @@
]
},
{
"id": "740baa9e-8328-4035-9e1a-8fc1616d1f0f",
"id": "ed165166-4521-4a62-b185-c4b51643cbb1",
"alias": "Reset - Conditional OTP",
"description": "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.",
"providerId": "basic-flow",
@ -1644,7 +1725,7 @@
]
},
{
"id": "e60187a8-3e16-4a0c-9daa-f3a4a1fcfdba",
"id": "4788fb1f-fd81-4f5d-9abe-4199dd641c1e",
"alias": "User creation or linking",
"description": "Flow for the existing/non-existing user alternatives",
"providerId": "basic-flow",
@ -1671,7 +1752,7 @@
]
},
{
"id": "d959d0c2-4004-4633-b280-f80d6423f574",
"id": "d778a70f-f472-4dd3-ac40-cb5612ddc171",
"alias": "Verify Existing Account by Re-authentication",
"description": "Reauthentication of existing account",
"providerId": "basic-flow",
@ -1697,7 +1778,7 @@
]
},
{
"id": "ba02689d-b9e8-4a4b-8fdd-0d1386b198fc",
"id": "9c1ea8ea-7c23-4e60-b02d-1900d9dc4109",
"alias": "browser",
"description": "browser based authentication",
"providerId": "basic-flow",
@ -1739,7 +1820,7 @@
]
},
{
"id": "f09ac92a-e091-4e84-9cd1-cb905ca57b89",
"id": "0ebdf418-d57d-4318-9359-7bd0cb2381f2",
"alias": "clients",
"description": "Base authentication for clients",
"providerId": "client-flow",
@ -1781,7 +1862,7 @@
]
},
{
"id": "aaf72b22-cec4-4714-93d6-f54d5a986ab8",
"id": "5cc89293-c72e-4c5e-b31c-15558588a60d",
"alias": "direct grant",
"description": "OpenID Connect Resource Owner Grant",
"providerId": "basic-flow",
@ -1815,7 +1896,7 @@
]
},
{
"id": "c4a54bb3-f009-4231-a82b-376c2515e07e",
"id": "5ae5a321-ccac-449e-9c19-d6dc22ab8085",
"alias": "docker auth",
"description": "Used by Docker clients to authenticate against the IDP",
"providerId": "basic-flow",
@ -1833,7 +1914,7 @@
]
},
{
"id": "f55ded54-683a-4f5a-a101-9cfbd7b96781",
"id": "7737fdd1-0875-47e6-977b-12561cddfdc3",
"alias": "first broker login",
"description": "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account",
"providerId": "basic-flow",
@ -1860,7 +1941,7 @@
]
},
{
"id": "931d5a82-378f-4533-8c69-2239a4acd047",
"id": "90f975c3-9826-461f-88ca-27c697aff86b",
"alias": "forms",
"description": "Username, password, otp and other auth forms.",
"providerId": "basic-flow",
@ -1886,7 +1967,7 @@
]
},
{
"id": "22b05374-f480-4ca8-aca8-9db8b6dd1729",
"id": "ce2722d5-9f4f-41a2-8f81-e01f7b6cee57",
"alias": "http challenge",
"description": "An authentication flow based on challenge-response HTTP Authentication Schemes",
"providerId": "basic-flow",
@ -1912,7 +1993,7 @@
]
},
{
"id": "c0371832-e4b7-485e-bf23-6babe4c6ac83",
"id": "31b5bfa7-98ad-47a2-b8e6-0669022cd8cb",
"alias": "registration",
"description": "registration flow",
"providerId": "basic-flow",
@ -1931,7 +2012,7 @@
]
},
{
"id": "4d0445da-073e-465e-b25b-af522915c73f",
"id": "bf8a950b-be3b-4e44-8602-64e0bba492eb",
"alias": "registration form",
"description": "registration form",
"providerId": "form-flow",
@ -1973,7 +2054,7 @@
]
},
{
"id": "740d467f-4203-425b-8203-9bfd3eed25ae",
"id": "e3519800-971b-4b1d-b64e-3983ccd02dea",
"alias": "reset credentials",
"description": "Reset credentials for a user if they forgot their password or something",
"providerId": "basic-flow",
@ -2015,7 +2096,7 @@
]
},
{
"id": "cf1a9af9-dadd-4cb9-a26e-fbbba216f8e1",
"id": "9d5a33a2-e777-4beb-95de-b84812f69c56",
"alias": "saml ecp",
"description": "SAML ECP Profile Authentication Flow",
"providerId": "basic-flow",
@ -2035,14 +2116,14 @@
],
"authenticatorConfig": [
{
"id": "4e65eb4b-9f0a-4ab8-98b2-6daf50cd1bf8",
"id": "4901c91d-59bd-4727-b585-8e4e44828d0a",
"alias": "create unique user config",
"config": {
"require.password.update.after.registration": "false"
}
},
{
"id": "5e8dc1c5-1489-4d39-bb75-9c499583b91b",
"id": "5062a078-83a7-4933-b0d5-3f75cc2a5003",
"alias": "review profile config",
"config": {
"update.profile.on.first.login": "missing"
@ -2132,8 +2213,8 @@
"attributes": {
"cibaBackchannelTokenDeliveryMode": "poll",
"cibaAuthRequestedUserHint": "login_hint",
"oauth2DevicePollingInterval": "5",
"clientOfflineSessionMaxLifespan": "0",
"oauth2DevicePollingInterval": "5",
"clientSessionIdleTimeout": "0",
"userProfileEnabled": "true",
"clientOfflineSessionIdleTimeout": "0",

View File

@ -73,7 +73,7 @@
"composites": {
"realm": ["offline_access", "uma_authorization"],
"client": {
"account": ["delete-account", "view-profile", "manage-account"]
"account": ["view-profile", "manage-account", "delete-account"]
}
},
"clientRole": false,
@ -435,13 +435,46 @@
"type": "password",
"userLabel": "My password",
"createdDate": 1716214710762,
"secretData": "{\"value\":\"OaI4sKqQn+NZtS6N/bcqoZ8Q+ucpBby1n4XmzVmioKw=\",\"salt\":\"temixVCSbpA7Genml2KTAw==\",\"additionalParameters\":{}}",
"secretData": "{\"value\":\"QzJjOdXU0L9Pdxdx1V5xUs7BY9beGlmN8NpR2qiWxbkjrQ434Q1GwSiJKekZQ/zrLDtNZ7sAbVu+SS+XIe9Zaw==\",\"salt\":\"x8cABpa0Hk/nJ2BPKdFXTg==\",\"additionalParameters\":{}}",
"credentialData": "{\"hashIterations\":27500,\"algorithm\":\"pbkdf2-sha256\",\"additionalParameters\":{}}"
}
],
"disableableCredentialTypes": [],
"requiredActions": [],
"realmRoles": ["default-roles-myrealm"],
"clientRoles": {
"realm-management": [
"create-client",
"view-identity-providers",
"manage-realm",
"query-groups",
"manage-clients",
"query-users",
"realm-admin",
"view-authorization",
"view-events",
"view-clients",
"view-realm",
"manage-events",
"query-realms",
"query-clients",
"manage-identity-providers",
"manage-users",
"view-users",
"impersonation",
"manage-authorization"
],
"broker": ["read-token"],
"account": [
"view-profile",
"manage-account-links",
"view-applications",
"manage-consent",
"delete-account",
"manage-account",
"view-consent"
]
},
"notBefore": 0,
"groups": []
}
@ -507,8 +540,12 @@
"enabled": true,
"alwaysDisplayInConsole": false,
"clientAuthenticatorType": "client-secret",
"redirectUris": ["/realms/myrealm/account/*"],
"webOrigins": [],
"redirectUris": [
"http://localhost*",
"http://127.0.0.1*",
"/realms/myrealm/account/*"
],
"webOrigins": ["*"],
"notBefore": 0,
"bearerOnly": false,
"consentRequired": false,
@ -643,7 +680,6 @@
"attributes": {
"oidc.ciba.grant.enabled": "false",
"backchannel.logout.session.required": "true",
"login_theme": "keycloakify-starter",
"post.logout.redirect.uris": "+",
"display.on.consent.screen": "false",
"oauth2.device.authorization.grant.enabled": "false",
@ -704,8 +740,12 @@
"enabled": true,
"alwaysDisplayInConsole": false,
"clientAuthenticatorType": "client-secret",
"redirectUris": ["/admin/myrealm/console/*"],
"webOrigins": ["+"],
"redirectUris": [
"http://localhost*",
"http://127.0.0.1*",
"/admin/myrealm/console/*"
],
"webOrigins": ["*"],
"notBefore": 0,
"bearerOnly": false,
"consentRequired": false,
@ -724,6 +764,24 @@
"fullScopeAllowed": false,
"nodeReRegistrationTimeout": 0,
"protocolMappers": [
{
"id": "8fd0d584-7052-4d04-a615-d18a71050873",
"name": "allowed-origins",
"protocol": "openid-connect",
"protocolMapper": "oidc-hardcoded-claim-mapper",
"consentRequired": false,
"config": {
"userinfo.token.claim": "true",
"id.token.claim": "false",
"access.token.claim": "true",
"claim.name": "allowed-origins",
"jsonType.label": "JSON",
"access.tokenResponse.claim": "false",
"claim.value": "[\"*\"]",
"introspection.token.claim": "true",
"lightweight.claim": "true"
}
},
{
"id": "7779f8fa-c2fe-4e68-be56-66ee97bf8f13",
"name": "locale",
@ -1284,11 +1342,11 @@
},
"smtpServer": {},
"loginTheme": "keycloakify-starter",
"accountTheme": "keycloakify-starter",
"accountTheme": "",
"adminTheme": "",
"emailTheme": "",
"eventsEnabled": false,
"eventsListeners": ["jboss-logging"],
"eventsListeners": ["keycloakify-logging", "jboss-logging"],
"enabledEventTypes": [],
"adminEventsEnabled": false,
"adminEventsDetailsEnabled": false,
@ -1304,14 +1362,14 @@
"subComponents": {},
"config": {
"allowed-protocol-mapper-types": [
"saml-user-attribute-mapper",
"oidc-full-name-mapper",
"oidc-sha256-pairwise-sub-mapper",
"oidc-usermodel-property-mapper",
"oidc-address-mapper",
"saml-user-property-mapper",
"oidc-sha256-pairwise-sub-mapper",
"oidc-usermodel-attribute-mapper",
"saml-user-attribute-mapper",
"saml-role-list-mapper"
"saml-role-list-mapper",
"oidc-usermodel-property-mapper"
]
}
},
@ -1361,13 +1419,13 @@
"config": {
"allowed-protocol-mapper-types": [
"saml-user-property-mapper",
"saml-user-attribute-mapper",
"oidc-full-name-mapper",
"oidc-sha256-pairwise-sub-mapper",
"oidc-usermodel-attribute-mapper",
"oidc-full-name-mapper",
"saml-user-attribute-mapper",
"oidc-usermodel-property-mapper",
"oidc-address-mapper",
"saml-role-list-mapper",
"oidc-usermodel-property-mapper"
"saml-role-list-mapper"
]
}
},
@ -1485,7 +1543,7 @@
"defaultLocale": "en",
"authenticationFlows": [
{
"id": "e134634e-f219-4df4-867c-8110688d8e56",
"id": "8ccfe057-5ce6-499b-9fae-3cd89b62bf01",
"alias": "Account verification options",
"description": "Method with which to verity the existing account",
"providerId": "basic-flow",
@ -1511,7 +1569,7 @@
]
},
{
"id": "a611a8eb-9626-4aa4-8b54-ee565ea6e5dc",
"id": "f3b9ab2e-41c2-4e73-876b-e2c275d6d14e",
"alias": "Authentication Options",
"description": "Authentication options.",
"providerId": "basic-flow",
@ -1545,7 +1603,7 @@
]
},
{
"id": "d87cbb31-5c69-45c8-888d-f9649ebbbf97",
"id": "df1329cc-777c-42d8-aa2f-c5d5ddaaf5a4",
"alias": "Browser - Conditional OTP",
"description": "Flow to determine if the OTP is required for the authentication",
"providerId": "basic-flow",
@ -1571,7 +1629,7 @@
]
},
{
"id": "752ba282-a369-4592-92e8-b4287192dbbf",
"id": "f78a4cbc-66ff-4caa-8066-67aff94946f4",
"alias": "Direct Grant - Conditional OTP",
"description": "Flow to determine if the OTP is required for the authentication",
"providerId": "basic-flow",
@ -1597,7 +1655,7 @@
]
},
{
"id": "2349282e-40ff-431a-984d-53911511e3d3",
"id": "4b20995b-5553-45db-86b0-05c3fe14edb1",
"alias": "First broker login - Conditional OTP",
"description": "Flow to determine if the OTP is required for the authentication",
"providerId": "basic-flow",
@ -1623,7 +1681,7 @@
]
},
{
"id": "4ff5463d-26d9-4219-ba85-41464401098f",
"id": "0a7cc6b7-e427-4f72-b44e-a02133241bad",
"alias": "Handle Existing Account",
"description": "Handle what to do if there is existing account with same email/username like authenticated identity provider",
"providerId": "basic-flow",
@ -1649,7 +1707,7 @@
]
},
{
"id": "87bb6c6d-cca8-4832-b5ab-67ecb9454a42",
"id": "e24e73c0-dd51-4fdc-a916-284f11f38487",
"alias": "Reset - Conditional OTP",
"description": "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.",
"providerId": "basic-flow",
@ -1675,7 +1733,7 @@
]
},
{
"id": "1fc3d028-0e0a-43a4-aaf9-ba7f7d60b409",
"id": "37ee5a12-01c2-41b0-aafa-e9c6661ff544",
"alias": "User creation or linking",
"description": "Flow for the existing/non-existing user alternatives",
"providerId": "basic-flow",
@ -1702,7 +1760,7 @@
]
},
{
"id": "036aae59-641f-4799-9124-c7e5034af6c1",
"id": "8902a1a7-c2ee-4648-869f-dd5ef89184fc",
"alias": "Verify Existing Account by Re-authentication",
"description": "Reauthentication of existing account",
"providerId": "basic-flow",
@ -1728,7 +1786,7 @@
]
},
{
"id": "2e8b9f28-93b8-4368-84b0-1a8326daafe0",
"id": "77c78eed-4bcd-4779-b39f-10135be84946",
"alias": "browser",
"description": "browser based authentication",
"providerId": "basic-flow",
@ -1770,7 +1828,7 @@
]
},
{
"id": "0b826105-8493-45ce-87b3-7d917d190b39",
"id": "c6398883-01e6-47a1-bb97-c09f2983155d",
"alias": "clients",
"description": "Base authentication for clients",
"providerId": "client-flow",
@ -1812,7 +1870,7 @@
]
},
{
"id": "bf6d9edd-48d8-4392-bbc8-4b17a6866074",
"id": "78ab5fb8-f35b-4053-b264-94b208000b13",
"alias": "direct grant",
"description": "OpenID Connect Resource Owner Grant",
"providerId": "basic-flow",
@ -1846,7 +1904,7 @@
]
},
{
"id": "97e31722-dd11-42be-aa99-88788fa2dde6",
"id": "959e154b-034e-413d-9b19-211e7d9ba33d",
"alias": "docker auth",
"description": "Used by Docker clients to authenticate against the IDP",
"providerId": "basic-flow",
@ -1864,7 +1922,7 @@
]
},
{
"id": "3f45cf34-231f-4ea1-8e58-d636c451a76b",
"id": "001e253d-bdbd-41e2-81c7-1c7b239feeb1",
"alias": "first broker login",
"description": "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account",
"providerId": "basic-flow",
@ -1891,7 +1949,7 @@
]
},
{
"id": "9bef2f7c-f989-4871-aaa7-18e2cfa73f22",
"id": "45481bb0-18fe-4a26-a77c-35a5afe58436",
"alias": "forms",
"description": "Username, password, otp and other auth forms.",
"providerId": "basic-flow",
@ -1917,7 +1975,7 @@
]
},
{
"id": "0bfaa325-acde-4443-8bd8-1dc2ae759c5f",
"id": "bb47b847-5a55-4c08-909e-9f6f8d8a0636",
"alias": "http challenge",
"description": "An authentication flow based on challenge-response HTTP Authentication Schemes",
"providerId": "basic-flow",
@ -1943,7 +2001,7 @@
]
},
{
"id": "37ddbe8c-abf3-4654-bd6d-ffabbeefbb98",
"id": "77e6e169-05b7-4b89-af00-09cfe1604eed",
"alias": "registration",
"description": "registration flow",
"providerId": "basic-flow",
@ -1962,7 +2020,7 @@
]
},
{
"id": "5d7b4bc9-e93b-40da-aeb6-ba0c38392f1a",
"id": "aef03fe8-1a70-40c3-879f-25588f75c119",
"alias": "registration form",
"description": "registration form",
"providerId": "form-flow",
@ -2004,7 +2062,7 @@
]
},
{
"id": "ee7a56e4-c827-4f24-8b8b-8476050b0b64",
"id": "990abff7-e2ba-4217-984e-8890cbc2b3a9",
"alias": "reset credentials",
"description": "Reset credentials for a user if they forgot their password or something",
"providerId": "basic-flow",
@ -2046,7 +2104,7 @@
]
},
{
"id": "360f0031-4c3b-4272-84ca-2172d430b4bc",
"id": "d9894cf6-2f99-493e-ac47-853f54bfc9c6",
"alias": "saml ecp",
"description": "SAML ECP Profile Authentication Flow",
"providerId": "basic-flow",
@ -2066,14 +2124,14 @@
],
"authenticatorConfig": [
{
"id": "53630acd-a33a-40e3-8786-cf85464c6f9e",
"id": "101ed8ff-4383-4539-aa52-2d1e69698b78",
"alias": "create unique user config",
"config": {
"require.password.update.after.registration": "false"
}
},
{
"id": "c0d2b6a0-caad-4e90-b040-17cacdaf70bb",
"id": "049042a5-3551-4c16-81a1-64d86f5aa1e5",
"alias": "review profile config",
"config": {
"update.profile.on.first.login": "missing"

View File

@ -73,7 +73,7 @@
"composites": {
"realm": ["offline_access", "uma_authorization"],
"client": {
"account": ["delete-account", "view-profile", "manage-account"]
"account": ["view-profile", "manage-account", "delete-account"]
}
},
"clientRole": false,
@ -407,7 +407,7 @@
"otpPolicyLookAheadWindow": 1,
"otpPolicyPeriod": 30,
"otpPolicyCodeReusable": false,
"otpSupportedApplications": ["totpAppGoogleName", "totpAppFreeOTPName"],
"otpSupportedApplications": ["totpAppFreeOTPName", "totpAppGoogleName"],
"webAuthnPolicyRpEntityName": "keycloak",
"webAuthnPolicySignatureAlgorithms": ["ES256"],
"webAuthnPolicyRpId": "",
@ -452,6 +452,40 @@
"disableableCredentialTypes": [],
"requiredActions": [],
"realmRoles": ["default-roles-myrealm"],
"clientRoles": {
"realm-management": [
"create-client",
"view-identity-providers",
"manage-realm",
"query-groups",
"manage-clients",
"query-users",
"realm-admin",
"view-authorization",
"view-events",
"view-clients",
"view-realm",
"manage-events",
"query-realms",
"query-clients",
"manage-identity-providers",
"manage-users",
"view-users",
"impersonation",
"manage-authorization"
],
"broker": ["read-token"],
"account": [
"view-profile",
"manage-account-links",
"view-applications",
"manage-consent",
"delete-account",
"manage-account",
"view-groups",
"view-consent"
]
},
"notBefore": 0,
"groups": []
}
@ -517,8 +551,12 @@
"enabled": true,
"alwaysDisplayInConsole": false,
"clientAuthenticatorType": "client-secret",
"redirectUris": ["/realms/myrealm/account/*"],
"webOrigins": [],
"redirectUris": [
"http://localhost*",
"http://127.0.0.1*",
"/realms/myrealm/account/*"
],
"webOrigins": ["*"],
"notBefore": 0,
"bearerOnly": false,
"consentRequired": false,
@ -653,7 +691,6 @@
"attributes": {
"oidc.ciba.grant.enabled": "false",
"backchannel.logout.session.required": "true",
"login_theme": "keycloakify-starter",
"post.logout.redirect.uris": "+",
"display.on.consent.screen": "false",
"oauth2.device.authorization.grant.enabled": "false",
@ -714,8 +751,12 @@
"enabled": true,
"alwaysDisplayInConsole": false,
"clientAuthenticatorType": "client-secret",
"redirectUris": ["/admin/myrealm/console/*"],
"webOrigins": ["+"],
"redirectUris": [
"http://localhost*",
"http://127.0.0.1*",
"/admin/myrealm/console/*"
],
"webOrigins": ["*"],
"notBefore": 0,
"bearerOnly": false,
"consentRequired": false,
@ -734,6 +775,24 @@
"fullScopeAllowed": false,
"nodeReRegistrationTimeout": 0,
"protocolMappers": [
{
"id": "8fd0d584-7052-4d04-a615-d18a71050873",
"name": "allowed-origins",
"protocol": "openid-connect",
"protocolMapper": "oidc-hardcoded-claim-mapper",
"consentRequired": false,
"config": {
"userinfo.token.claim": "true",
"id.token.claim": "false",
"access.token.claim": "true",
"claim.name": "allowed-origins",
"jsonType.label": "JSON",
"access.tokenResponse.claim": "false",
"claim.value": "[\"*\"]",
"introspection.token.claim": "true",
"lightweight.claim": "true"
}
},
{
"id": "7779f8fa-c2fe-4e68-be56-66ee97bf8f13",
"name": "locale",
@ -1294,11 +1353,11 @@
},
"smtpServer": {},
"loginTheme": "keycloakify-starter",
"accountTheme": "keycloakify-starter",
"accountTheme": "",
"adminTheme": "",
"emailTheme": "",
"eventsEnabled": false,
"eventsListeners": ["jboss-logging"],
"eventsListeners": ["keycloakify-logging", "jboss-logging"],
"enabledEventTypes": [],
"adminEventsEnabled": false,
"adminEventsDetailsEnabled": false,
@ -1314,14 +1373,14 @@
"subComponents": {},
"config": {
"allowed-protocol-mapper-types": [
"saml-user-property-mapper",
"oidc-sha256-pairwise-sub-mapper",
"oidc-usermodel-property-mapper",
"oidc-address-mapper",
"oidc-full-name-mapper",
"oidc-usermodel-attribute-mapper",
"saml-user-attribute-mapper",
"oidc-address-mapper",
"saml-role-list-mapper",
"oidc-full-name-mapper",
"oidc-usermodel-property-mapper"
"saml-user-property-mapper"
]
}
},
@ -1370,14 +1429,14 @@
"subComponents": {},
"config": {
"allowed-protocol-mapper-types": [
"oidc-sha256-pairwise-sub-mapper",
"oidc-address-mapper",
"oidc-full-name-mapper",
"oidc-usermodel-attribute-mapper",
"saml-role-list-mapper",
"saml-user-attribute-mapper",
"oidc-usermodel-attribute-mapper",
"oidc-full-name-mapper",
"saml-user-property-mapper",
"oidc-usermodel-property-mapper"
"oidc-usermodel-property-mapper",
"oidc-address-mapper",
"oidc-sha256-pairwise-sub-mapper",
"saml-user-property-mapper"
]
}
},
@ -1495,7 +1554,7 @@
"defaultLocale": "en",
"authenticationFlows": [
{
"id": "19317acb-fe8e-4c79-82bc-90e159273075",
"id": "30a878f0-57aa-4d20-bab0-6cf1d7317a5c",
"alias": "Account verification options",
"description": "Method with which to verity the existing account",
"providerId": "basic-flow",
@ -1521,7 +1580,7 @@
]
},
{
"id": "122857d2-33da-4086-8acb-cb0e303aaf1b",
"id": "d386affe-d1fe-472a-bee6-54105d0101f5",
"alias": "Authentication Options",
"description": "Authentication options.",
"providerId": "basic-flow",
@ -1555,7 +1614,7 @@
]
},
{
"id": "abf5dd35-4791-4268-a10c-5f4b6a06b84a",
"id": "77b95bc0-bd0c-46b7-8240-3182023e9d50",
"alias": "Browser - Conditional OTP",
"description": "Flow to determine if the OTP is required for the authentication",
"providerId": "basic-flow",
@ -1581,7 +1640,7 @@
]
},
{
"id": "a18daeec-a33c-4a43-b014-10c84ec69b81",
"id": "bc96d3d6-29a1-42af-a63e-bb67a8c6d78f",
"alias": "Direct Grant - Conditional OTP",
"description": "Flow to determine if the OTP is required for the authentication",
"providerId": "basic-flow",
@ -1607,7 +1666,7 @@
]
},
{
"id": "e9f032a7-32f7-457c-becf-011a1a35cc6a",
"id": "7697ca74-5c2b-45ab-9335-e0f6dec59b5c",
"alias": "First broker login - Conditional OTP",
"description": "Flow to determine if the OTP is required for the authentication",
"providerId": "basic-flow",
@ -1633,7 +1692,7 @@
]
},
{
"id": "9db65b7c-98ca-4003-beea-611038831ffe",
"id": "534cb120-f600-4f40-9707-7b781bdbce48",
"alias": "Handle Existing Account",
"description": "Handle what to do if there is existing account with same email/username like authenticated identity provider",
"providerId": "basic-flow",
@ -1659,7 +1718,7 @@
]
},
{
"id": "7bd0854c-d7ae-43d7-a1ae-7b759a34cb1d",
"id": "f884b048-b223-4ed6-ae16-e49a4255131e",
"alias": "Reset - Conditional OTP",
"description": "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.",
"providerId": "basic-flow",
@ -1685,7 +1744,7 @@
]
},
{
"id": "2de1a450-fe98-443a-9c6c-d24d8a7ebcb3",
"id": "61c7966c-ad72-49f5-84dd-376152348092",
"alias": "User creation or linking",
"description": "Flow for the existing/non-existing user alternatives",
"providerId": "basic-flow",
@ -1712,7 +1771,7 @@
]
},
{
"id": "7b3efad5-4b7d-4385-a41c-fecc73afdcc4",
"id": "72412d0f-dd1b-49fe-bb0b-9dad99eb0491",
"alias": "Verify Existing Account by Re-authentication",
"description": "Reauthentication of existing account",
"providerId": "basic-flow",
@ -1738,7 +1797,7 @@
]
},
{
"id": "de93418e-8f28-4099-b15e-ad36ec194796",
"id": "6b76613e-0d39-440d-aab4-98eaffb1e96a",
"alias": "browser",
"description": "browser based authentication",
"providerId": "basic-flow",
@ -1780,7 +1839,7 @@
]
},
{
"id": "0dd3345c-6e82-4c3a-a39a-d49ae1f5c409",
"id": "0ff60395-fa89-41be-ad22-fab339e67c49",
"alias": "clients",
"description": "Base authentication for clients",
"providerId": "client-flow",
@ -1822,7 +1881,7 @@
]
},
{
"id": "87fb4dd0-5326-47a1-b670-982f4872ff89",
"id": "bbb3ece7-7dbf-4aba-80c3-dde4b9cdd0b6",
"alias": "direct grant",
"description": "OpenID Connect Resource Owner Grant",
"providerId": "basic-flow",
@ -1856,7 +1915,7 @@
]
},
{
"id": "344723b3-4ab1-4999-abdd-32398e82327b",
"id": "f5f2c0f6-7dbf-4978-845e-6cacac23aa13",
"alias": "docker auth",
"description": "Used by Docker clients to authenticate against the IDP",
"providerId": "basic-flow",
@ -1874,7 +1933,7 @@
]
},
{
"id": "f3341938-caf9-4c8a-9cd5-eb34609809ab",
"id": "cf463104-19e2-41a8-8a53-d3dd30b75344",
"alias": "first broker login",
"description": "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account",
"providerId": "basic-flow",
@ -1901,7 +1960,7 @@
]
},
{
"id": "ba7b7357-e324-4b71-9bda-f8512a760e02",
"id": "b99b60dc-41ad-487d-be69-a2eefa954a9d",
"alias": "forms",
"description": "Username, password, otp and other auth forms.",
"providerId": "basic-flow",
@ -1927,7 +1986,7 @@
]
},
{
"id": "134971e6-bf63-432c-806e-74ca4fb09963",
"id": "18731296-2c96-4f98-a884-027e629e4f9d",
"alias": "http challenge",
"description": "An authentication flow based on challenge-response HTTP Authentication Schemes",
"providerId": "basic-flow",
@ -1953,7 +2012,7 @@
]
},
{
"id": "6ea9e2cf-5684-4c65-8c07-930d1cbb0b46",
"id": "9a9dce17-5425-4fd5-b3b8-81410e1dbce4",
"alias": "registration",
"description": "registration flow",
"providerId": "basic-flow",
@ -1972,7 +2031,7 @@
]
},
{
"id": "67e3c8c7-1b5e-4119-84a2-e90876293150",
"id": "d0a24e08-cb69-4949-9518-50ae7a96ee49",
"alias": "registration form",
"description": "registration form",
"providerId": "form-flow",
@ -2014,7 +2073,7 @@
]
},
{
"id": "fc6d48ec-a1f1-41b1-9310-54f58861d5aa",
"id": "6a9aa554-afba-487f-9c82-e94c81c15b3b",
"alias": "reset credentials",
"description": "Reset credentials for a user if they forgot their password or something",
"providerId": "basic-flow",
@ -2056,7 +2115,7 @@
]
},
{
"id": "80b1d464-c2ec-4eb1-82e8-32cbede779a8",
"id": "e0361d46-eab4-41a6-bb2e-1dc6a5a6b073",
"alias": "saml ecp",
"description": "SAML ECP Profile Authentication Flow",
"providerId": "basic-flow",
@ -2076,14 +2135,14 @@
],
"authenticatorConfig": [
{
"id": "86b1d5fa-450c-40d8-899c-725861ac39fc",
"id": "053d6017-e54c-418a-abe7-44dd4752eacb",
"alias": "create unique user config",
"config": {
"require.password.update.after.registration": "false"
}
},
{
"id": "ea724f02-029a-493d-b4d3-08972be21cfb",
"id": "8b545cf4-ab9e-4226-b3c0-d7ac773eae2f",
"alias": "review profile config",
"config": {
"update.profile.on.first.login": "missing"

View File

@ -73,7 +73,7 @@
"composites": {
"realm": ["offline_access", "uma_authorization"],
"client": {
"account": ["delete-account", "view-profile", "manage-account"]
"account": ["view-profile", "manage-account", "delete-account"]
}
},
"clientRole": false,
@ -408,9 +408,9 @@
"otpPolicyPeriod": 30,
"otpPolicyCodeReusable": false,
"otpSupportedApplications": [
"totpAppGoogleName",
"totpAppFreeOTPName",
"totpAppMicrosoftAuthenticatorName"
"totpAppMicrosoftAuthenticatorName",
"totpAppGoogleName"
],
"webAuthnPolicyRpEntityName": "keycloak",
"webAuthnPolicySignatureAlgorithms": ["ES256"],
@ -456,6 +456,40 @@
"disableableCredentialTypes": [],
"requiredActions": [],
"realmRoles": ["default-roles-myrealm"],
"clientRoles": {
"realm-management": [
"create-client",
"view-identity-providers",
"manage-realm",
"query-groups",
"manage-clients",
"query-users",
"realm-admin",
"view-authorization",
"view-events",
"view-clients",
"view-realm",
"manage-events",
"query-realms",
"query-clients",
"manage-identity-providers",
"manage-users",
"view-users",
"impersonation",
"manage-authorization"
],
"broker": ["read-token"],
"account": [
"view-profile",
"manage-account-links",
"view-applications",
"manage-consent",
"delete-account",
"manage-account",
"view-groups",
"view-consent"
]
},
"notBefore": 0,
"groups": []
}
@ -521,8 +555,12 @@
"enabled": true,
"alwaysDisplayInConsole": false,
"clientAuthenticatorType": "client-secret",
"redirectUris": ["/realms/myrealm/account/*"],
"webOrigins": [],
"redirectUris": [
"http://localhost*",
"http://127.0.0.1*",
"/realms/myrealm/account/*"
],
"webOrigins": ["*"],
"notBefore": 0,
"bearerOnly": false,
"consentRequired": false,
@ -657,7 +695,6 @@
"attributes": {
"oidc.ciba.grant.enabled": "false",
"backchannel.logout.session.required": "true",
"login_theme": "keycloakify-starter",
"post.logout.redirect.uris": "+",
"display.on.consent.screen": "false",
"oauth2.device.authorization.grant.enabled": "false",
@ -718,8 +755,12 @@
"enabled": true,
"alwaysDisplayInConsole": false,
"clientAuthenticatorType": "client-secret",
"redirectUris": ["/admin/myrealm/console/*"],
"webOrigins": ["+"],
"redirectUris": [
"http://localhost*",
"http://127.0.0.1*",
"/admin/myrealm/console/*"
],
"webOrigins": ["*"],
"notBefore": 0,
"bearerOnly": false,
"consentRequired": false,
@ -738,6 +779,24 @@
"fullScopeAllowed": false,
"nodeReRegistrationTimeout": 0,
"protocolMappers": [
{
"id": "8fd0d584-7052-4d04-a615-d18a71050873",
"name": "allowed-origins",
"protocol": "openid-connect",
"protocolMapper": "oidc-hardcoded-claim-mapper",
"consentRequired": false,
"config": {
"userinfo.token.claim": "true",
"id.token.claim": "false",
"access.token.claim": "true",
"claim.name": "allowed-origins",
"jsonType.label": "JSON",
"access.tokenResponse.claim": "false",
"claim.value": "[\"*\"]",
"introspection.token.claim": "true",
"lightweight.claim": "true"
}
},
{
"id": "7779f8fa-c2fe-4e68-be56-66ee97bf8f13",
"name": "locale",
@ -1298,11 +1357,11 @@
},
"smtpServer": {},
"loginTheme": "keycloakify-starter",
"accountTheme": "keycloakify-starter",
"accountTheme": "",
"adminTheme": "",
"emailTheme": "",
"eventsEnabled": false,
"eventsListeners": ["jboss-logging"],
"eventsListeners": ["keycloakify-logging", "jboss-logging"],
"enabledEventTypes": [],
"adminEventsEnabled": false,
"adminEventsDetailsEnabled": false,
@ -1318,13 +1377,13 @@
"subComponents": {},
"config": {
"allowed-protocol-mapper-types": [
"oidc-usermodel-property-mapper",
"oidc-usermodel-attribute-mapper",
"oidc-full-name-mapper",
"oidc-usermodel-property-mapper",
"oidc-sha256-pairwise-sub-mapper",
"saml-user-property-mapper",
"saml-role-list-mapper",
"oidc-full-name-mapper",
"saml-user-attribute-mapper",
"oidc-sha256-pairwise-sub-mapper",
"oidc-address-mapper"
]
}
@ -1374,14 +1433,14 @@
"subComponents": {},
"config": {
"allowed-protocol-mapper-types": [
"oidc-sha256-pairwise-sub-mapper",
"oidc-address-mapper",
"oidc-full-name-mapper",
"oidc-usermodel-property-mapper",
"oidc-usermodel-attribute-mapper",
"saml-user-attribute-mapper",
"oidc-sha256-pairwise-sub-mapper",
"saml-role-list-mapper",
"saml-user-property-mapper"
"saml-user-attribute-mapper",
"saml-user-property-mapper",
"oidc-usermodel-attribute-mapper",
"oidc-address-mapper",
"oidc-full-name-mapper"
]
}
},

File diff suppressed because it is too large Load Diff

View File

@ -55,7 +55,7 @@
"composites": {
"realm": ["offline_access", "uma_authorization"],
"client": {
"account": ["delete-account", "view-profile", "manage-account"]
"account": ["view-profile", "delete-account", "manage-account"]
}
},
"clientRole": false,
@ -459,6 +459,40 @@
"disableableCredentialTypes": [],
"requiredActions": [],
"realmRoles": ["default-roles-myrealm"],
"clientRoles": {
"realm-management": [
"query-clients",
"manage-identity-providers",
"create-client",
"view-users",
"query-groups",
"view-realm",
"manage-authorization",
"view-authorization",
"query-users",
"impersonation",
"realm-admin",
"manage-users",
"view-identity-providers",
"manage-realm",
"manage-clients",
"query-realms",
"view-events",
"manage-events",
"view-clients"
],
"broker": ["read-token"],
"account": [
"manage-account",
"view-consent",
"view-groups",
"delete-account",
"view-applications",
"manage-account-links",
"view-profile",
"manage-consent"
]
},
"notBefore": 0,
"groups": []
}
@ -505,7 +539,6 @@
"attributes": {
"oidc.ciba.grant.enabled": "false",
"backchannel.logout.session.required": "true",
"login_theme": "keycloakify-starter",
"post.logout.redirect.uris": "+",
"oauth2.device.authorization.grant.enabled": "false",
"display.on.consent.screen": "false",
@ -532,8 +565,12 @@
"enabled": true,
"alwaysDisplayInConsole": false,
"clientAuthenticatorType": "client-secret",
"redirectUris": ["/realms/myrealm/account/*"],
"webOrigins": [],
"redirectUris": [
"http://localhost*",
"http://127.0.0.1*",
"/realms/myrealm/account/*"
],
"webOrigins": ["*"],
"notBefore": 0,
"bearerOnly": false,
"consentRequired": false,
@ -649,7 +686,11 @@
"enabled": true,
"alwaysDisplayInConsole": false,
"clientAuthenticatorType": "client-secret",
"redirectUris": ["https://my-theme.keycloakify.dev/*", "http://localhost*"],
"redirectUris": [
"https://my-theme.keycloakify.dev/*",
"http://localhost*",
"http://127.0.0.1*"
],
"webOrigins": ["*"],
"notBefore": 0,
"bearerOnly": false,
@ -664,8 +705,7 @@
"attributes": {
"oidc.ciba.grant.enabled": "false",
"backchannel.logout.session.required": "true",
"login_theme": "keycloakify-starter",
"post.logout.redirect.uris": "https://my-theme.keycloakify.dev/*",
"post.logout.redirect.uris": "+",
"oauth2.device.authorization.grant.enabled": "false",
"display.on.consent.screen": "false",
"backchannel.logout.revoke.offline.tokens": "false"
@ -725,8 +765,12 @@
"enabled": true,
"alwaysDisplayInConsole": false,
"clientAuthenticatorType": "client-secret",
"redirectUris": ["/admin/myrealm/console/*"],
"webOrigins": ["+"],
"redirectUris": [
"http://localhost*",
"http://127.0.0.1*",
"/admin/myrealm/console/*"
],
"webOrigins": ["*"],
"notBefore": 0,
"bearerOnly": false,
"consentRequired": false,
@ -745,6 +789,24 @@
"fullScopeAllowed": false,
"nodeReRegistrationTimeout": 0,
"protocolMappers": [
{
"id": "8fd0d584-7052-4d04-a615-d18a71050873",
"name": "allowed-origins",
"protocol": "openid-connect",
"protocolMapper": "oidc-hardcoded-claim-mapper",
"consentRequired": false,
"config": {
"introspection.token.claim": "true",
"userinfo.token.claim": "true",
"id.token.claim": "false",
"access.token.claim": "true",
"claim.name": "allowed-origins",
"jsonType.label": "JSON",
"access.tokenResponse.claim": "false",
"claim.value": "[\"*\"]",
"lightweight.claim": "true"
}
},
{
"id": "59cde7ae-2218-4a8e-83af-cad992c3a700",
"name": "locale",
@ -1336,12 +1398,12 @@
"strictTransportSecurity": "max-age=31536000; includeSubDomains"
},
"smtpServer": {},
"loginTheme": "",
"accountTheme": "keycloakify-starter",
"loginTheme": "keycloakify-starter",
"accountTheme": "",
"adminTheme": "",
"emailTheme": "",
"eventsEnabled": false,
"eventsListeners": ["jboss-logging"],
"eventsListeners": ["keycloakify-logging", "jboss-logging"],
"enabledEventTypes": [],
"adminEventsEnabled": false,
"adminEventsDetailsEnabled": false,
@ -1358,13 +1420,13 @@
"config": {
"allowed-protocol-mapper-types": [
"oidc-sha256-pairwise-sub-mapper",
"saml-user-property-mapper",
"oidc-address-mapper",
"oidc-full-name-mapper",
"saml-role-list-mapper",
"oidc-usermodel-attribute-mapper",
"saml-user-attribute-mapper",
"oidc-usermodel-property-mapper"
"oidc-full-name-mapper",
"oidc-usermodel-property-mapper",
"oidc-usermodel-attribute-mapper",
"oidc-address-mapper",
"saml-user-property-mapper",
"saml-role-list-mapper"
]
}
},
@ -1433,14 +1495,14 @@
"subComponents": {},
"config": {
"allowed-protocol-mapper-types": [
"oidc-usermodel-property-mapper",
"saml-role-list-mapper",
"oidc-full-name-mapper",
"oidc-address-mapper",
"saml-user-attribute-mapper",
"oidc-sha256-pairwise-sub-mapper",
"oidc-usermodel-attribute-mapper",
"saml-user-property-mapper",
"oidc-usermodel-property-mapper"
"oidc-usermodel-attribute-mapper",
"saml-user-attribute-mapper",
"oidc-address-mapper",
"oidc-full-name-mapper",
"oidc-sha256-pairwise-sub-mapper"
]
}
}

View File

@ -468,6 +468,40 @@
"disableableCredentialTypes": [],
"requiredActions": [],
"realmRoles": ["default-roles-myrealm"],
"clientRoles": {
"realm-management": [
"manage-clients",
"manage-users",
"view-identity-providers",
"view-users",
"impersonation",
"manage-identity-providers",
"query-users",
"query-realms",
"realm-admin",
"view-events",
"view-realm",
"manage-events",
"manage-authorization",
"manage-realm",
"query-clients",
"query-groups",
"view-clients",
"create-client",
"view-authorization"
],
"broker": ["read-token"],
"account": [
"manage-consent",
"manage-account-links",
"view-applications",
"view-consent",
"manage-account",
"view-profile",
"view-groups",
"delete-account"
]
},
"notBefore": 0,
"groups": []
}
@ -514,7 +548,6 @@
"attributes": {
"oidc.ciba.grant.enabled": "false",
"backchannel.logout.session.required": "true",
"login_theme": "keycloakify-starter",
"post.logout.redirect.uris": "+",
"oauth2.device.authorization.grant.enabled": "false",
"display.on.consent.screen": "false",
@ -541,8 +574,12 @@
"enabled": true,
"alwaysDisplayInConsole": false,
"clientAuthenticatorType": "client-secret",
"redirectUris": ["/realms/myrealm/account/*"],
"webOrigins": [],
"redirectUris": [
"http://localhost*",
"http://127.0.0.1*",
"/realms/myrealm/account/*"
],
"webOrigins": ["*"],
"notBefore": 0,
"bearerOnly": false,
"consentRequired": false,
@ -658,7 +695,11 @@
"enabled": true,
"alwaysDisplayInConsole": false,
"clientAuthenticatorType": "client-secret",
"redirectUris": ["https://my-theme.keycloakify.dev/*", "http://localhost*"],
"redirectUris": [
"https://my-theme.keycloakify.dev/*",
"http://localhost*",
"http://127.0.0.1*"
],
"webOrigins": ["*"],
"notBefore": 0,
"bearerOnly": false,
@ -673,8 +714,7 @@
"attributes": {
"oidc.ciba.grant.enabled": "false",
"backchannel.logout.session.required": "true",
"login_theme": "keycloakify-starter",
"post.logout.redirect.uris": "https://my-theme.keycloakify.dev/*##http://localhost*",
"post.logout.redirect.uris": "+",
"oauth2.device.authorization.grant.enabled": "false",
"display.on.consent.screen": "false",
"backchannel.logout.revoke.offline.tokens": "false"
@ -840,8 +880,12 @@
"enabled": true,
"alwaysDisplayInConsole": false,
"clientAuthenticatorType": "client-secret",
"redirectUris": ["/admin/myrealm/console/*"],
"webOrigins": ["+"],
"redirectUris": [
"http://localhost*",
"http://127.0.0.1*",
"/admin/myrealm/console/*"
],
"webOrigins": ["*"],
"notBefore": 0,
"bearerOnly": false,
"consentRequired": false,
@ -875,6 +919,24 @@
"claim.name": "locale",
"jsonType.label": "String"
}
},
{
"id": "8fd0d584-7052-4d04-a615-d18a71050873",
"name": "allowed-origins",
"protocol": "openid-connect",
"protocolMapper": "oidc-hardcoded-claim-mapper",
"consentRequired": false,
"config": {
"introspection.token.claim": "true",
"userinfo.token.claim": "true",
"id.token.claim": "false",
"access.token.claim": "true",
"claim.name": "allowed-origins",
"jsonType.label": "JSON",
"access.tokenResponse.claim": "false",
"claim.value": "[\"*\"]",
"lightweight.claim": "true"
}
}
],
"defaultClientScopes": ["web-origins", "acr", "profile", "roles", "email"],
@ -1451,12 +1513,12 @@
"strictTransportSecurity": "max-age=31536000; includeSubDomains"
},
"smtpServer": {},
"loginTheme": "keycloak",
"accountTheme": "keycloakify-starter",
"loginTheme": "keycloakify-starter",
"accountTheme": "",
"adminTheme": "",
"emailTheme": "",
"eventsEnabled": false,
"eventsListeners": ["jboss-logging"],
"eventsListeners": ["keycloakify-logging", "jboss-logging"],
"enabledEventTypes": [],
"adminEventsEnabled": false,
"adminEventsDetailsEnabled": false,
@ -1501,14 +1563,14 @@
"subComponents": {},
"config": {
"allowed-protocol-mapper-types": [
"oidc-full-name-mapper",
"saml-role-list-mapper",
"oidc-address-mapper",
"oidc-usermodel-property-mapper",
"oidc-sha256-pairwise-sub-mapper",
"saml-user-attribute-mapper",
"oidc-usermodel-attribute-mapper",
"oidc-full-name-mapper",
"saml-user-property-mapper"
"oidc-address-mapper",
"oidc-usermodel-property-mapper",
"saml-user-property-mapper",
"oidc-sha256-pairwise-sub-mapper"
]
}
},
@ -1541,13 +1603,13 @@
"config": {
"allowed-protocol-mapper-types": [
"oidc-sha256-pairwise-sub-mapper",
"oidc-usermodel-property-mapper",
"oidc-full-name-mapper",
"oidc-address-mapper",
"oidc-usermodel-attribute-mapper",
"saml-user-property-mapper",
"oidc-full-name-mapper",
"oidc-usermodel-property-mapper",
"saml-user-attribute-mapper",
"saml-role-list-mapper",
"saml-user-attribute-mapper"
"saml-user-property-mapper",
"oidc-usermodel-attribute-mapper"
]
}
},

View File

@ -538,10 +538,10 @@
"emailVerified": true,
"attributes": {
"additional_emails": ["test.user@protonmail.com", "testuser@hotmail.com"],
"gender": ["prefer_not_to_say"],
"favorite_pet": ["cats"],
"favourite_pet": ["cat"],
"gender": ["prefer_not_to_say"],
"bio": ["Hello I'm Test User and I do not exist."],
"favourite_pet": ["cat"],
"phone_number": ["1111111111"],
"locale": ["en"],
"favorite_media": ["movies", "series"]
@ -562,6 +562,40 @@
"disableableCredentialTypes": [],
"requiredActions": [],
"realmRoles": ["default-roles-myrealm"],
"clientRoles": {
"realm-management": [
"manage-users",
"create-client",
"view-users",
"view-realm",
"query-realms",
"impersonation",
"view-events",
"realm-admin",
"manage-authorization",
"manage-events",
"view-authorization",
"manage-clients",
"query-users",
"query-groups",
"manage-realm",
"query-clients",
"manage-identity-providers",
"view-clients",
"view-identity-providers"
],
"broker": ["read-token"],
"account": [
"delete-account",
"view-applications",
"manage-account",
"view-consent",
"view-groups",
"view-profile",
"manage-account-links",
"manage-consent"
]
},
"notBefore": 0,
"groups": []
}
@ -636,7 +670,7 @@
"enabled": true,
"alwaysDisplayInConsole": false,
"clientAuthenticatorType": "client-secret",
"redirectUris": ["*"],
"redirectUris": ["http://localhost*", "http://127.0.0.1*", "*"],
"webOrigins": ["*"],
"notBefore": 0,
"bearerOnly": false,
@ -798,8 +832,7 @@
"attributes": {
"oidc.ciba.grant.enabled": "false",
"backchannel.logout.session.required": "true",
"login_theme": "keycloakify-starter",
"post.logout.redirect.uris": "https://my-theme.keycloakify.dev/*##http://localhost*##http://127.0.0.1*",
"post.logout.redirect.uris": "+",
"oauth2.device.authorization.grant.enabled": "false",
"display.on.consent.screen": "false",
"backchannel.logout.revoke.offline.tokens": "false"
@ -892,8 +925,12 @@
"enabled": true,
"alwaysDisplayInConsole": false,
"clientAuthenticatorType": "client-secret",
"redirectUris": ["/admin/myrealm/console/*"],
"webOrigins": ["+"],
"redirectUris": [
"http://localhost*",
"http://127.0.0.1*",
"/admin/myrealm/console/*"
],
"webOrigins": ["*"],
"notBefore": 0,
"bearerOnly": false,
"consentRequired": false,
@ -927,6 +964,24 @@
"claim.name": "locale",
"jsonType.label": "String"
}
},
{
"id": "8fd0d584-7052-4d04-a615-d18a71050873",
"name": "allowed-origins",
"protocol": "openid-connect",
"protocolMapper": "oidc-hardcoded-claim-mapper",
"consentRequired": false,
"config": {
"introspection.token.claim": "true",
"userinfo.token.claim": "true",
"id.token.claim": "false",
"access.token.claim": "true",
"claim.name": "allowed-origins",
"jsonType.label": "JSON",
"access.tokenResponse.claim": "false",
"claim.value": "[\"*\"]",
"lightweight.claim": "true"
}
}
],
"defaultClientScopes": [
@ -1551,11 +1606,11 @@
},
"smtpServer": {},
"loginTheme": "keycloakify-starter",
"accountTheme": "keycloakify-starter",
"accountTheme": "",
"adminTheme": "",
"emailTheme": "",
"eventsEnabled": false,
"eventsListeners": ["jboss-logging"],
"eventsListeners": ["keycloakify-logging", "jboss-logging"],
"enabledEventTypes": [],
"adminEventsEnabled": false,
"adminEventsDetailsEnabled": false,
@ -1581,14 +1636,14 @@
"subComponents": {},
"config": {
"allowed-protocol-mapper-types": [
"oidc-sha256-pairwise-sub-mapper",
"oidc-full-name-mapper",
"oidc-usermodel-property-mapper",
"saml-role-list-mapper",
"saml-user-attribute-mapper",
"saml-user-property-mapper",
"oidc-usermodel-attribute-mapper",
"oidc-address-mapper"
"oidc-address-mapper",
"saml-role-list-mapper",
"saml-user-property-mapper",
"oidc-sha256-pairwise-sub-mapper",
"saml-user-attribute-mapper",
"oidc-usermodel-property-mapper",
"oidc-full-name-mapper"
]
}
},
@ -1618,13 +1673,13 @@
"subComponents": {},
"config": {
"allowed-protocol-mapper-types": [
"oidc-address-mapper",
"saml-user-attribute-mapper",
"saml-role-list-mapper",
"oidc-usermodel-property-mapper",
"oidc-sha256-pairwise-sub-mapper",
"saml-user-property-mapper",
"oidc-usermodel-attribute-mapper",
"oidc-address-mapper",
"saml-role-list-mapper",
"oidc-usermodel-property-mapper",
"saml-user-attribute-mapper",
"oidc-full-name-mapper"
]
}
@ -1678,6 +1733,12 @@
"providerId": "rsa-generated",
"subComponents": {},
"config": {
"privateKey": [
"MIIEowIBAAKCAQEAso89qpvLhf9DIcCb2JAbxItRLSIvP/NCZhMdAExTHyrhM5B27ZQ6MZ7dJQbnMu7QJ7yiClsD1XnDN7Wlj07sY2As3lY3v9kjODBeADYlPuN1m7/fXFHX3qfRT+PwVSaAhMykmqvWp86UTg7t7rNjVBnXPPXItmRLIF+jZUMWQduwNznr6Jh54ZdIwEy4hvX1bpNw0nPl4KXiOi2elvg+rk7BhFywGwQ/HUCGkrcq0XS/aNOy1ChmqDbtq817mYpVeteCDe8xP3MPrZ/s2LiEt4Ip1cNo0dY+a4JwOzwL42h3GaR+80iK3pZNo+Mr0KBOY9GXvdV/MvcPHLQ7VujUGQIDAQABAoIBAAHV0OQwmDxUazqiVGe61Bzmcqs5q03SC1K/FmCi/YVikdskvGLaOmk5UQa4+1uDEq7J30onH9ML8+qeFRQek0rn2ZDfxtBpDqsx7LwTUmQtqc8z6buKQs37db5ctnhlk34UmAotQyDz5wMmCkzWWVUWCT02PdMev5qW/mKuIxaCWLHUFiMJaGrYCCwB/Ra8KLcadKgRbytSUth9qILC4krFfmWtzIx1P6nM1pzQ1nydxNnNPJKjoWtLRJ5b701Y5/h2vAAg6Mr+jKe1DPa9QmAqhQudjGbZ31av+0f1/I+XkflpZfokfU+MrAqNYRTYkevRYgc3wakK5mfVYUiMuOECgYEA7fk55O2OJFsR0Vjy4Dx4eSIwgwobvwEuHxlyWn0RC7nFb00eh6OPuc5sHrOk8bK3P367q67sEhxGyBF16nwxgX/T+c8gTC8QRuwNymosA4Je/zJHbKvyzLGOouCP5gYwq/wUmVWzNApVC7LBfxbsqYyivHABc5xgPmTgecY0VWkCgYEAwBXcUKoyq1KZegyNJcTuwuvBXoYVveFGm6QKKKwzojCCKaR3XXtdSon1qYfuKT0MLxgEDyyBks9DgfCodSsTmajX90Yolhyz3ptcOmRURqTRoJhM4g6qA+Ybd3uy8vAz32RdS+4rCTgnMG/5Xpn5B4ojOnhRcnA2TPCJgWz6QzECgYEAhj1FjD75JMb+mRJNB3L1HpfLt8+28RsQUli/ag4M1Il5txxQsYDxbYXk9biuvezrc/Tglqs43cp3nxpCYwClyIA8KjnN5UvTKb601M7pfx1GyzwokEO61f7/ECAO7FnnkMzFLe3rBdsiOFQg1LkwzT/Y+OVR3E6E+A1dlzPYh6kCgYBIP3CwfnO0cMr9Vv8394x+kEIZFYHT+4mdPOP9TFfXZztuAkhLRv1d7eoSq+fuZuHQTM4qDullmMOhei1CdMNYhmNExIS7gWw+DF1yMQ5py9B1ARPZ6v4TnVczZ7l1GtfH7G4TAy/4tcA3vcYjyPIb3d9GPL8VthMWeVqe7ahr4QKBgEwA7ASbs4NxfBsStEGQYQYAeWOoKnTc50FeYz38O4KrOirtTFPNsJcyCiTE0o4cqu/OebSA5irrauV7SEDl/gfH54g3ZWusQbLt2uMnZYtkd2+Ka3T9XM0QfQW/vYl3eJtdQj89TqzLzyP0AgvAyIgeG3RMH8ojqCh3YKY0FTv/"
],
"certificate": [
"MIICnTCCAYUCBgGTy2TGBjANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdteXJlYWxtMB4XDTI0MTIxNTE3MzQ1OVoXDTM0MTIxNTE3MzYzOVowEjEQMA4GA1UEAwwHbXlyZWFsbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALKPPaqby4X/QyHAm9iQG8SLUS0iLz/zQmYTHQBMUx8q4TOQdu2UOjGe3SUG5zLu0Ce8ogpbA9V5wze1pY9O7GNgLN5WN7/ZIzgwXgA2JT7jdZu/31xR196n0U/j8FUmgITMpJqr1qfOlE4O7e6zY1QZ1zz1yLZkSyBfo2VDFkHbsDc56+iYeeGXSMBMuIb19W6TcNJz5eCl4jotnpb4Pq5OwYRcsBsEPx1AhpK3KtF0v2jTstQoZqg27avNe5mKVXrXgg3vMT9zD62f7Ni4hLeCKdXDaNHWPmuCcDs8C+NodxmkfvNIit6WTaPjK9CgTmPRl73VfzL3Dxy0O1bo1BkCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAggzxmYvHqUaCPLxxSidLQMgpu1pTozg3rTq8dcxhcHINI//A/z7qQyDA/QQN5cuSpYvdt2MRWoNop+uRNKqSr3C8aRErbY0j4acl7yG/ghNfQUZ9KxDBxKrd0HLFUibdZobg10+Ih/qXo3Mi2VtkqyZQRl/iy0O3ITgqb7YJUEx5tuEWyGbn+SerFvqZNcmsLziOJefm1n4uqroHgIfmgY6Deh+wZK0DwO3WZ6ThjhMp5GFi1oNeZ9xoExNEXrYp07b2xTQFF57oypc7prf733lqGjPRLfoVJP6qcsjvAlOA7f8TG9sKwGuRsPfadYY9PxmdHxl2k7PHDJeDhA7VdQ=="
],
"priority": ["100"]
}
},
@ -1687,6 +1748,12 @@
"providerId": "rsa-enc-generated",
"subComponents": {},
"config": {
"privateKey": [
"MIIEowIBAAKCAQEAxoEvnv+YHCqUWANGuku5QYscAZyUE0WHSlcAzZ0bQugPow63piQsuxPz0cpPIuLab6adssXUqKEFheT1H0BqtmT9L/7iOKB6MRuInN4aRzzTH9q02TKPkcpSAzAHTGcsJBMMawlbnIdMu5+mevMPxqeVVxvrnKG27S8H3W5jqIkQw8bo646Hr3l5Dxq/jY7slcSXXXe4ZdefeCvnSqea+fy5c+r/r546nX4FTGiklu6KLQaDc9SfGccrZDmljY7DX1kHrmvIdLShcuukTHc0hi2qbgMcUte/7/svSJLUWOZObKxetd4y1OA49v36xrMqGhwGDdwrWf0VuMBN8eHOCQIDAQABAoIBABz/hUXnFRZURWHKxLvKpnBZPTOiZzfzfxfl4tOmq54CtDoVQyXNq2J+6oOPWC/X+ky3hy+1BQ5x9hJrx+qTU04m2EfOe8da8M7DX28kZlauyjF2loG+MvP7ctn4BluWcip+RTZOYn2DfxBPpRcunR409V+JesoMY7fSwtrfA/Gm0PrXgBK7OuE0nxqFFWnsLOc+HxZECS5r0n1MHEBHe774HkqGcK91j8S+QU+/diTnK+N/ClnKWnabMK8bUO5wAUuKwf2deYkGP91pCEJlVnVZyaXshEM+uxTuMRUlq9h1QAIUatvdQwfOKqZ9XvmTVC8b79qLwmezjoDxNCKbaMMCgYEA71WDpMnA2uS2wCJ/MVwzWGSBDjfeKUPRy33BeUfwLGp4Dro+S1sTrLHgi1HGmvmC8ReZrifUlUHUi3ZHauR6vbNsEoSQ3hplO013kj12EfcBpvKYFg1ODCwevb/JtBTWbDG1P+E9DGiF/2u0aicoJoPolNeNVzgO6YK1OI/S/LMCgYEA1FPTqFPulXxcOK12LgYap8typqJ7zu4fByr42010yrKM+LLNA3bT/i/oRkKc7J1ztKSqlVckADWgK4Y27lI4j1tSgTOxFzwxnTZOeF7ZwGSxq9iy9A84nDiW+m6Hj5RDyBjTSoP2Qqv6d5kTUx+pczZvOVTWRlIEnFETbbxOoFMCgYEA0r1etHx+V4AqtxXpH6KLB5s/1DA3a+hu1BrAgLVqcwGxA27VKW9h7J+YE7UHBzELLpVUWfhyhJa5u6+DhUj4Fw/k6o1WLmvZlZVJ4zhBPeJczw8wAcLnZWp4CybUScBLamt+qGgBZGqpCtZgv1QJU5i09FK0/wa6grz4K3zhEGcCgYAlnGe8xIlZr3rCi2+IvYoROQepHtUhlaqnYWRNrI3IrhIsp7eLKoxo1WGmuHwFqepqEFUrORFmfBlQPGkUlDnyovGdc2OmQwJi39DMn7igzPVwBGXGt7+GZLvRxqx6sX/EPSmIZJHFw6MNdm8m5U/l2bmgBTgjormwWug/IwEmgwKBgEouISIuXsjGxeLmhrOXHKXb6IfKglNJeBM6lTQ6MLaVOso7KdelIntwZNtZwMIi3hlwaUb1X1QmztFbnrvnPhWwJR4ZgMEWanRHthtm0SHzg8EHKT40S91oKabsgHk3wpOvq/iWs+k8qWN4HYp6UO603uLMOfxPYJCFxRtg2TsJ"
],
"certificate": [
"MIICnTCCAYUCBgGTy2TG/jANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdteXJlYWxtMB4XDTI0MTIxNTE3MzQ1OVoXDTM0MTIxNTE3MzYzOVowEjEQMA4GA1UEAwwHbXlyZWFsbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMaBL57/mBwqlFgDRrpLuUGLHAGclBNFh0pXAM2dG0LoD6MOt6YkLLsT89HKTyLi2m+mnbLF1KihBYXk9R9AarZk/S/+4jigejEbiJzeGkc80x/atNkyj5HKUgMwB0xnLCQTDGsJW5yHTLufpnrzD8anlVcb65yhtu0vB91uY6iJEMPG6OuOh695eQ8av42O7JXEl113uGXXn3gr50qnmvn8uXPq/6+eOp1+BUxopJbuii0Gg3PUnxnHK2Q5pY2Ow19ZB65ryHS0oXLrpEx3NIYtqm4DHFLXv+/7L0iS1FjmTmysXrXeMtTgOPb9+sazKhocBg3cK1n9FbjATfHhzgkCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAdUIlJ91E0UkFS45AByjFufRnQbAi1smnHkC3WSN39bhcFT7Hgip97qtABODR58zVHSTS0XcMiL4mMObH3Vyz9J3gmwWZnbokAuo9tYeyrhPh/gqXv3LGtGhTpWlUJ7JEJxH7RVI4UZZyG6Y6FR+3zwiZ0j1p3QsZclfcNmacoi/Ano+4TfloOnY4k8yP7G6LWUTJHpcRNWVVozM3RwekYgpJRAtXDoYfm9p2hRQ090e7NvbblSuVQ/FXhUn4g0wz91WdCWlwXZfvNaRjbynPCHejJpszqiyjPkx3aRKTWqer0ZocKNmY8+RO27XIsXmwOYcjdpX2TCFDv6O+VLfNdw=="
],
"priority": ["100"],
"algorithm": ["RSA-OAEP"]
}
@ -1697,6 +1764,8 @@
"providerId": "aes-generated",
"subComponents": {},
"config": {
"kid": ["95db7eb8-b57b-475e-90cd-58841a9388d3"],
"secret": ["dp6bv53YrC2PZuJCxa3aNA"],
"priority": ["100"]
}
},
@ -1706,6 +1775,10 @@
"providerId": "hmac-generated",
"subComponents": {},
"config": {
"kid": ["d0254883-059e-4fdd-bf03-704c76650aab"],
"secret": [
"bcW7E4rcbgSKZIQysWOSuhezRGYs5Kzmp3ZESthdTUMyFivK8RbBAdBE4PhFPk5B9TuByDO2RWvd8F7F5YhGJitf6cfYB1BfDuAk-2iBAtdZA98g7a2h4jpwzh-GIgtoRbGbH9qnquUn52f5qteo34g5WifKE2bWjOELza9FrTo"
],
"priority": ["100"],
"algorithm": ["HS512"]
}

View File

@ -563,6 +563,40 @@
"disableableCredentialTypes": [],
"requiredActions": [],
"realmRoles": ["default-roles-myrealm"],
"clientRoles": {
"realm-management": [
"manage-users",
"create-client",
"view-users",
"view-realm",
"query-realms",
"impersonation",
"view-events",
"realm-admin",
"manage-authorization",
"view-authorization",
"manage-events",
"manage-clients",
"query-users",
"query-groups",
"manage-realm",
"query-clients",
"manage-identity-providers",
"view-identity-providers",
"view-clients"
],
"broker": ["read-token"],
"account": [
"delete-account",
"view-applications",
"manage-account",
"view-consent",
"view-groups",
"view-profile",
"manage-account-links",
"manage-consent"
]
},
"notBefore": 0,
"groups": []
}
@ -638,7 +672,11 @@
"enabled": true,
"alwaysDisplayInConsole": false,
"clientAuthenticatorType": "client-secret",
"redirectUris": ["*"],
"redirectUris": [
"http://localhost*",
"http://127.0.0.1*",
"/realms/myrealm/account/*"
],
"webOrigins": ["*"],
"notBefore": 0,
"bearerOnly": false,
@ -805,8 +843,7 @@
"realm_client": "false",
"oidc.ciba.grant.enabled": "false",
"backchannel.logout.session.required": "true",
"login_theme": "keycloakify-starter",
"post.logout.redirect.uris": "https://my-theme.keycloakify.dev/*##http://localhost*##http://127.0.0.1*",
"post.logout.redirect.uris": "+",
"oauth2.device.authorization.grant.enabled": "false",
"display.on.consent.screen": "false",
"backchannel.logout.revoke.offline.tokens": "false"
@ -894,14 +931,20 @@
"id": "fce8a109-6f32-4814-9a20-2ff2435d2da6",
"clientId": "security-admin-console",
"name": "${client_security-admin-console}",
"description": "",
"rootUrl": "${authAdminUrl}",
"adminUrl": "",
"baseUrl": "/admin/myrealm/console/",
"surrogateAuthRequired": false,
"enabled": true,
"alwaysDisplayInConsole": false,
"clientAuthenticatorType": "client-secret",
"redirectUris": ["/admin/myrealm/console/*"],
"webOrigins": ["+"],
"redirectUris": [
"http://localhost*",
"http://127.0.0.1*",
"/admin/myrealm/console/*"
],
"webOrigins": ["*"],
"notBefore": 0,
"bearerOnly": false,
"consentRequired": false,
@ -914,9 +957,14 @@
"protocol": "openid-connect",
"attributes": {
"realm_client": "false",
"oidc.ciba.grant.enabled": "false",
"client.use.lightweight.access.token.enabled": "true",
"backchannel.logout.session.required": "true",
"post.logout.redirect.uris": "+",
"pkce.code.challenge.method": "S256"
"oauth2.device.authorization.grant.enabled": "false",
"display.on.consent.screen": "false",
"pkce.code.challenge.method": "S256",
"backchannel.logout.revoke.offline.tokens": "false"
},
"authenticationFlowBindingOverrides": {},
"fullScopeAllowed": true,
@ -937,6 +985,24 @@
"claim.name": "locale",
"jsonType.label": "String"
}
},
{
"id": "8fd0d584-7052-4d04-a615-d18a71050873",
"name": "allowed-origins",
"protocol": "openid-connect",
"protocolMapper": "oidc-hardcoded-claim-mapper",
"consentRequired": false,
"config": {
"introspection.token.claim": "true",
"claim.value": "[\"*\"]",
"userinfo.token.claim": "true",
"id.token.claim": "false",
"lightweight.claim": "true",
"access.token.claim": "true",
"claim.name": "allowed-origins",
"jsonType.label": "JSON",
"access.tokenResponse.claim": "false"
}
}
],
"defaultClientScopes": [
@ -1561,11 +1627,11 @@
},
"smtpServer": {},
"loginTheme": "keycloakify-starter",
"accountTheme": "keycloakify-starter",
"adminTheme": "",
"accountTheme": "",
"adminTheme": "keycloakify-starter",
"emailTheme": "",
"eventsEnabled": false,
"eventsListeners": ["jboss-logging"],
"eventsListeners": ["keycloakify-logging", "jboss-logging"],
"enabledEventTypes": [],
"adminEventsEnabled": false,
"adminEventsDetailsEnabled": false,
@ -1591,14 +1657,14 @@
"subComponents": {},
"config": {
"allowed-protocol-mapper-types": [
"saml-user-attribute-mapper",
"oidc-sha256-pairwise-sub-mapper",
"oidc-full-name-mapper",
"saml-user-property-mapper",
"oidc-address-mapper",
"saml-user-attribute-mapper",
"oidc-usermodel-property-mapper",
"oidc-usermodel-attribute-mapper",
"saml-user-property-mapper",
"oidc-sha256-pairwise-sub-mapper",
"saml-role-list-mapper",
"oidc-usermodel-attribute-mapper"
"oidc-full-name-mapper"
]
}
},
@ -1629,13 +1695,13 @@
"config": {
"allowed-protocol-mapper-types": [
"oidc-usermodel-attribute-mapper",
"oidc-full-name-mapper",
"oidc-usermodel-property-mapper",
"oidc-address-mapper",
"saml-user-property-mapper",
"oidc-sha256-pairwise-sub-mapper",
"saml-role-list-mapper",
"saml-user-attribute-mapper",
"oidc-sha256-pairwise-sub-mapper"
"oidc-address-mapper",
"oidc-full-name-mapper",
"saml-user-property-mapper",
"oidc-usermodel-property-mapper",
"saml-user-attribute-mapper"
]
}
},
@ -1688,6 +1754,12 @@
"providerId": "rsa-generated",
"subComponents": {},
"config": {
"privateKey": [
"MIIEoQIBAAKCAQEAxTFMvRiNiQjY9zajvLsah6Vy4pn8U7smsnBcHS9SkLJ1j9O8+90B90tIZk4IqEE4gdJA/mbbeUnou1vWuc0k69diQMFelzdIaDqJaFFeOS+J1DoApjThjGIz7FIgmGi6qoN8xnrPVD/6oMYAuxTvQaJH7mENiIG0198dvaufV1mFPg+krTsh7Womo2CJeZmNuAXv7RDQYxwPYDCFZLbppez48D7+2D+1V6Stk6Xwz8IDQZvljxDF6W2P9rhPWV1C5tcJpC/9RPyGDo+ke8UN3fM6X7YOgpbMztVrg8J0aTqPXZ7dt6QFUqVOufo+5wYL2jCafpYNV8cmaGlY+Q3d5QIDAQABAoH/DIPcaZaJTLG4FeUKGOaT40nesEiINRY99aeIkp+hdGj1EgTEn49TyLENGnhrrdbIvOJDeD6Z6dbpJBDvfFevxa589EnVKaGaaW5U91FDyVYH2YPU411dAeOp0z1xwxXzlJqX3h42ZJnvLAp/2l1Xo64vGCoTJtYlppAvpe2MjANxPNObAc65Phdi/sConAlwMeBylWXJ574uryFrJ64W/sUuIUMSunGGz0db4Y1hfkX9U2YnxB3DdXCBH09jQJyKDSj6feNXR87+1KhqcFMd5DUiGSAOqRBzuBMsDf1QDJd8A/DDlK7e/PA1Yk/Dii4hsf+LCeOdmhlifuyROqJBAoGBAOEm4gLvaBWwnUhmr4sW8xywIhGGbU+MX6vm/KkGtScres7pPhmfy6ARUzCxxyBqIE+nhCRNBpOEPhP7dv8naJhZZ4fRvNzuXpUMT2X3bc5yNzdhaOxBJl95YQbrYUHhjcIw2kdXnIkpdbB/RqmY0F5BUTYECrd0tKWbjuL5RIRNAoGBAOA1wTXrYyVorouxV+mGNb62Py+utHJQKSa5cxF9nbbwWJd+FdreiBOJddjATmH8ovKjueQFVqK7koDveOb+pgRY2bpT88/NW8UF6a2wMiI0p6pxrR+hgzas480YiOCWr6XlsprqsSKBbEu4W97GicleZ6P5Iso/gBr9aHj9EWv5AoGAYhRzHj42RESUr4Zz8A5GR3f+z02U7rNCtfrAk80lOvP44ou+jqEKrib961d2XAt/GdPqf3nCZJ6WAFRp6Qq8yKkhrYvTTxbTwvAC4nNftTASF6DqeQiEc9DHUKFW08Ey5KYtYCitOx8BcqpvGNBF7NldTD+Ef5hqXT4fh4Z4r30CgYEAy2OYGMymTRowNKK06C+Kc62plhy6rnRPUESswLIeLwTKqOqE8t4pvOdWk0CoGjVusAOcLuA03jyfwvz5xTo96fWb1W4w31IgLJOXjqsmX2c6reCfNvFyMVgW8keOa4XmYu0C34uFEpMrZWkhVe7usVBFXjczuxptoI4+hnqzoikCgYBICBVR9Z7n2LvmWH19/Nnns8dsMn5peL7H6Mey76Lo9RMEMp4qhiJTqVZzWgxEyVjr0KFCHmdmwkTOm6A1yYmkqqXDdiJ9v4J4fXe0lRAoUoYPTOWynrCyd6uqq+3zlzTKW8jY9luywHq6msn07D636PvveeZ93DNCcO8Whw36rQ=="
],
"certificate": [
"MIICnTCCAYUCBgGTulJBzTANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdteXJlYWxtMB4XDTI0MTIxMjEwMDExM1oXDTM0MTIxMjEwMDI1M1owEjEQMA4GA1UEAwwHbXlyZWFsbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMUxTL0YjYkI2Pc2o7y7GoelcuKZ/FO7JrJwXB0vUpCydY/TvPvdAfdLSGZOCKhBOIHSQP5m23lJ6Ltb1rnNJOvXYkDBXpc3SGg6iWhRXjkvidQ6AKY04YxiM+xSIJhouqqDfMZ6z1Q/+qDGALsU70GiR+5hDYiBtNffHb2rn1dZhT4PpK07Ie1qJqNgiXmZjbgF7+0Q0GMcD2AwhWS26aXs+PA+/tg/tVekrZOl8M/CA0Gb5Y8Qxeltj/a4T1ldQubXCaQv/UT8hg6PpHvFDd3zOl+2DoKWzM7Va4PCdGk6j12e3bekBVKlTrn6PucGC9owmn6WDVfHJmhpWPkN3eUCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEATZXyOluloTj6Q/Mv0JjstfdvPQbzGFzWtULB1ttOJqQVL+IJoF8V79HIvfP9U5OYaOdYk9dDurQcd2hXvEtX+zQlLYGniRfJlFI7d+m6MDXa7/g1r+OmcvaiXX7O3ol7eJdymPKS79+PSWFsHk0JjfgRJ11jajOscYPoQ+IvxXgwuy6v7VHigsLnGnmmo+KWiKO6Cna6eilm6/awYXaoym4ky9S4T5+WaJwd/tH/n5VY77zyXaXfANd1hU/+4Ux/eaGVnoMAM4ud2emd4qCN2tQQ3HusIVl+5V+S8Uq1y54mBpXv6CAODDGDJeFa+cGPJUSLdv/ZT2F8yfDlDc4J6g=="
],
"priority": ["100"]
}
},
@ -1697,6 +1769,12 @@
"providerId": "rsa-enc-generated",
"subComponents": {},
"config": {
"privateKey": [
"MIIEogIBAAKCAQEAungL4osLyP8bE6MSKj8ZMJTG8WBh3K2/xB5BJYCYc7P1CIORZI9o/vKQx1QnP+CXkIKnnR2kzIzC0rnTqlIOkaZfhmSn50jG5vNBS9qPT+WU7Ue3qKxuWJFwcaFU5SEJawJHqnDPK+pktkkxkudeMHz6iaKPs+wKcbfrRJ6+3a3FqQQdHEQg4IjVU8pBZmag1c7JHayiM56OT5y6jmE5JvY60959iPrZPXSTMU3hNoiVwdyK6QwdK+/0wrO681VhIP+u2pe92nQ+hsgMSSQJegLx1UsEEyU87syblG+p3zAKSS+kt2nviV/a2cYiiME0LdlQ3lnKsQ4t1Y6yZBiS2QIDAQABAoIBABhozI18TC+kjWPVrfQPzHlakGxahJUBvZ+rojWJjutefE4AAxFZ4JG3KRKexoCLIuwM3monzkHkj0BMiRO7qCKS1+Bc3snc8gSbhUmrs6Tu1b7162nOIKfBainFx7oyx+vVIZKDL+t8xHBERpQHa4IHajiIKi2QUZGvVMHn0e5srkPK0eSMjb5Z5j61aFb8InQzs7tczr99ke4VavOPT1gmRWGnbTavUbw/zIQ9sxAuMiD2v0nrGlOLZrMhaqzsT6PjIWVCSZrWex1pin9gA4XwGZ39E7+zFWgg+2OX0dEvehVDluAQR0K4PBUknuL1LFFW8dpvCrUSTmGGQOSVuB0CgYEA+bQjbjTNiMTEfoxx/WvVDgtLRL/x9RVyeYTPia2TGNBwpEcU64lLMOwUt5X/QuGXayPr0EGAxMA8kwq/E8Wj2t9+SuqkGK9SIwvghi2fOh0KWghuQbKYMogG5hsJAI8+/mBIOJJ8pyh0RX58vaTlYctbThO22aVahhZQ2weaW58CgYEAvyu4vIe44/7F19Hjh2BW+9lHsHA2zwHvC5T1kFaEdBYEwGsLMW6leCsiEMfpc2Uq3k9+buZgVpTE5APs9cSJX1aUXEG5QHQmYDxAAMiTyvpj0o2cKbDi1A5QZCRo23lC+uDyR7g2zLDJuHek0uyCtd83hbgyxIVFUnfvI9EmfocCgYBtpcZxHEqspgrKrw1XBMTXl+oDVG4A+tv7tHAVutx+5vivim8LRox3/RLT0s/2JG2DJJDmL/1FaEyxHOTu37il4cHpT8Oi+0mMDikXgm0K7bmf81fHDY97kPPGk1SOpFg7BzhvbxPBqyfzZCmOdRwsp0l+rXV7ePqZKq9ynpIPbQKBgFO/LZC5zE9k/vrK4egeVjzCNNugbQJGkJf8S49Nt3y7YJ2Cx0aCeE6qZqP/T8/Tk/IL1RF0LuP/DDnvVlFcJen0Hc5EpIkN2Pnzqv4s4EHdavmEO9MvwE6xbppQMPdkqekJvlmY47jMAbKkBzq3jZNrFAGqbeMVlwbHr6V7LGflAoGANFbzOnUMJwUfIdoI9uEG2QOTAcBb7vzt9MurO67wiTexOYadOSlcV1lQX3RKR9mCFJwy4kud0TN0gD++Ggl10eNB6f8JOF95e5+tWrtz88xZ5EalBOMfh+ATdKq8Q9MBSWZvO9bizhW1dhZZds/QmHgEItdwsTKDAq1PEiXhD0c="
],
"certificate": [
"MIICnTCCAYUCBgGTulJDCDANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdteXJlYWxtMB4XDTI0MTIxMjEwMDExM1oXDTM0MTIxMjEwMDI1M1owEjEQMA4GA1UEAwwHbXlyZWFsbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALp4C+KLC8j/GxOjEio/GTCUxvFgYdytv8QeQSWAmHOz9QiDkWSPaP7ykMdUJz/gl5CCp50dpMyMwtK506pSDpGmX4Zkp+dIxubzQUvaj0/llO1Ht6isbliRcHGhVOUhCWsCR6pwzyvqZLZJMZLnXjB8+omij7PsCnG360Sevt2txakEHRxEIOCI1VPKQWZmoNXOyR2sojOejk+cuo5hOSb2OtPefYj62T10kzFN4TaIlcHciukMHSvv9MKzuvNVYSD/rtqXvdp0PobIDEkkCXoC8dVLBBMlPO7Mm5Rvqd8wCkkvpLdp74lf2tnGIojBNC3ZUN5ZyrEOLdWOsmQYktkCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAPhPdLFcXdQT4k06oXB06ZSJ8AkZNXLvQFWCHXI34OmrS2yTse+dLqrqehnC3kPwxElVmawoUVc1sbsk7fUnspfM+Xw20PaABZu4MO2m5TB98f1hEkezP9fSqgPeuWJgTL8ZW5kkZyiD3IaZoqyxzYXaFxKHhU455g+k2+DO+N6FreVKcYz12Q5EMaxZ6U1neZAo3vicNxM3/TA5V8sPK8+oKvon7v5OyjpOH0goJo9v/klKeUk36h4u2h1S67IhVSU7tfzVFYrpns1JhrwGZ2xavVqEoqX8zFp3GKz3yVXkwHRHlrzYkZoGn21rm5boXIP3wEB7yXZbXWTiUko/IFw=="
],
"priority": ["100"],
"algorithm": ["RSA-OAEP"]
}
@ -1707,6 +1785,8 @@
"providerId": "aes-generated",
"subComponents": {},
"config": {
"kid": ["c36222c6-6a43-4d32-9d44-d5d355e5cabd"],
"secret": ["rzL4qUQ7wTEkZDbgt595VA"],
"priority": ["100"]
}
},
@ -1716,6 +1796,10 @@
"providerId": "hmac-generated",
"subComponents": {},
"config": {
"kid": ["06532a54-c310-41c1-829c-58776ce2ab4a"],
"secret": [
"9v1ZjFhEFH6UpY6ncFkaCbqJYHMyI4tA0cvx4GuQ5KtMXYbimitSSVDqxIKwa-gBC_8bY2O4FQfpmp1Qn1-L4fFmPFfIF3ZKsO16263BwpADo_FNSBTte8Le4gJLylqFULdsn3ye17FHyq5Jjms_OTt3opzcDLNduCuK22GBBsU"
],
"priority": ["100"],
"algorithm": ["HS512"]
}
@ -2385,7 +2469,7 @@
"clientSessionMaxLifespan": "0",
"organizationsEnabled": "false"
},
"keycloakVersion": "26.0.6",
"keycloakVersion": "26.0.7",
"userManagedAccessAllowed": false,
"organizationsEnabled": false,
"clientProfiles": {

View File

@ -0,0 +1,194 @@
import { CONTAINER_NAME } from "../../shared/constants";
import child_process from "child_process";
import { join as pathJoin, dirname as pathDirname, basename as pathBasename } from "path";
import chalk from "chalk";
import { Deferred } from "evt/tools/Deferred";
import { assert, is } from "tsafe/assert";
import type { BuildContext } from "../../shared/buildContext";
import { type ParsedRealmJson, readRealmJsonFile } from "./ParsedRealmJson";
export type BuildContextLike = {
cacheDirPath: string;
};
assert<BuildContext extends BuildContextLike ? true : false>();
export async function dumpContainerConfig(params: {
realmName: string;
keycloakMajorVersionNumber: number;
buildContext: BuildContextLike;
}): Promise<ParsedRealmJson> {
const { realmName, keycloakMajorVersionNumber, buildContext } = params;
// https://github.com/keycloak/keycloak/issues/33800
const doesUseLockedH2Database = keycloakMajorVersionNumber >= 25;
if (doesUseLockedH2Database) {
const dCompleted = new Deferred<void>();
const cmd = `docker exec ${CONTAINER_NAME} sh -c "cp -rp /opt/keycloak/data/h2 /tmp"`;
child_process.exec(cmd, error => {
if (error !== null) {
dCompleted.reject(error);
return;
}
dCompleted.resolve();
});
try {
await dCompleted.pr;
} catch (error) {
assert(is<Error>(error));
console.log(chalk.red(`Docker command failed: ${cmd}`));
console.log(chalk.red(error.message));
throw error;
}
}
{
const dCompleted = new Deferred<void>();
const child = child_process.spawn(
"docker",
[
...["exec", CONTAINER_NAME],
...["/opt/keycloak/bin/kc.sh", "export"],
...["--dir", "/tmp"],
...["--realm", realmName],
...["--users", "realm_file"],
...(!doesUseLockedH2Database
? []
: [
...["--db", "dev-file"],
...[
"--db-url",
'"jdbc:h2:file:/tmp/h2/keycloakdb;NON_KEYWORDS=VALUE"'
]
])
],
{ shell: true }
);
let output = "";
const onExit = (code: number | null) => {
dCompleted.reject(
new Error(`docker exec kc.sh export command failed with code ${code}`)
);
};
child.once("exit", onExit);
child.stdout.on("data", data => {
const outputStr = data.toString("utf8");
if (outputStr.includes("Export finished successfully")) {
child.removeListener("exit", onExit);
// NOTE: On older Keycloak versions the process keeps running after the export is done.
const timer = setTimeout(() => {
child.removeListener("exit", onExit2);
child.kill();
dCompleted.resolve();
}, 1500);
const onExit2 = () => {
clearTimeout(timer);
dCompleted.resolve();
};
child.once("exit", onExit2);
}
output += outputStr;
});
child.stderr.on("data", data => (output += chalk.red(data.toString("utf8"))));
try {
await dCompleted.pr;
} catch (error) {
assert(is<Error>(error));
console.log(chalk.red(error.message));
console.log(output);
throw error;
}
}
if (doesUseLockedH2Database) {
const dCompleted = new Deferred<void>();
const cmd = `docker exec ${CONTAINER_NAME} sh -c "rm -rf /tmp/h2"`;
child_process.exec(cmd, error => {
if (error !== null) {
dCompleted.reject(error);
return;
}
dCompleted.resolve();
});
try {
await dCompleted.pr;
} catch (error) {
assert(is<Error>(error));
console.log(chalk.red(`Docker command failed: ${cmd}`));
console.log(chalk.red(error.message));
throw error;
}
}
const targetRealmConfigJsonFilePath_tmp = pathJoin(
buildContext.cacheDirPath,
"realm.json"
);
{
const dCompleted = new Deferred<void>();
const cmd = `docker cp ${CONTAINER_NAME}:/tmp/${realmName}-realm.json ${pathBasename(targetRealmConfigJsonFilePath_tmp)}`;
child_process.exec(
cmd,
{
cwd: pathDirname(targetRealmConfigJsonFilePath_tmp)
},
error => {
if (error !== null) {
dCompleted.reject(error);
return;
}
dCompleted.resolve();
}
);
try {
await dCompleted.pr;
} catch (error) {
assert(is<Error>(error));
console.log(chalk.red(`Docker command failed: ${cmd}`));
console.log(chalk.red(error.message));
throw error;
}
}
return readRealmJsonFile({
realmJsonFilePath: targetRealmConfigJsonFilePath_tmp
});
}

View File

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

View File

@ -0,0 +1,365 @@
import { assert } from "tsafe/assert";
import type { ParsedRealmJson } from "./ParsedRealmJson";
import { getDefaultConfig } from "./defaultConfig";
import type { BuildContext } from "../../shared/buildContext";
import { objectKeys } from "tsafe/objectKeys";
import { TEST_APP_URL } from "../../shared/constants";
import { sameFactory } from "evt/tools/inDepth/same";
export type BuildContextLike = {
themeNames: BuildContext["themeNames"];
implementedThemeTypes: BuildContext["implementedThemeTypes"];
};
assert<BuildContext extends BuildContextLike ? true : false>;
export function prepareRealmConfig(params: {
parsedRealmJson: ParsedRealmJson;
keycloakMajorVersionNumber: number;
buildContext: BuildContextLike;
}): {
realmName: string;
clientName: string;
username: string;
} {
const { parsedRealmJson, keycloakMajorVersionNumber, buildContext } = params;
const { username } = addOrEditTestUser({
parsedRealmJson,
keycloakMajorVersionNumber
});
const { clientId } = addOrEditClient({
parsedRealmJson,
keycloakMajorVersionNumber
});
editAccountConsoleAndSecurityAdminConsole({ parsedRealmJson });
enableCustomThemes({
parsedRealmJson,
themeName: buildContext.themeNames[0],
implementedThemeTypes: buildContext.implementedThemeTypes
});
enable_custom_events_listeners: {
const name = "keycloakify-logging";
if (parsedRealmJson.eventsListeners.includes(name)) {
break enable_custom_events_listeners;
}
parsedRealmJson.eventsListeners.push(name);
parsedRealmJson.eventsListeners.sort();
}
return {
realmName: parsedRealmJson.realm,
clientName: clientId,
username
};
}
function enableCustomThemes(params: {
parsedRealmJson: ParsedRealmJson;
themeName: string;
implementedThemeTypes: BuildContextLike["implementedThemeTypes"];
}) {
const { parsedRealmJson, themeName, implementedThemeTypes } = params;
for (const themeType of objectKeys(implementedThemeTypes)) {
if (!implementedThemeTypes[themeType].isImplemented) {
continue;
}
parsedRealmJson[`${themeType}Theme` as const] = themeName;
}
}
function addOrEditTestUser(params: {
parsedRealmJson: ParsedRealmJson;
keycloakMajorVersionNumber: number;
}): { username: string } {
const { parsedRealmJson, keycloakMajorVersionNumber } = params;
const parsedRealmJson_default = getDefaultConfig({ keycloakMajorVersionNumber });
const [defaultUser_default] = parsedRealmJson_default.users;
assert(defaultUser_default !== undefined);
const defaultUser_preexisting = parsedRealmJson.users.find(
user => user.username === defaultUser_default.username
);
const newUser = structuredClone(
defaultUser_preexisting ??
(() => {
const firstUser = parsedRealmJson.users[0];
if (firstUser === undefined) {
return undefined;
}
const firstUserCopy = structuredClone(firstUser);
firstUserCopy.id = defaultUser_default.id;
return firstUserCopy;
})() ??
defaultUser_default
);
newUser.username = defaultUser_default.username;
newUser.email = defaultUser_default.email;
delete_existing_password_credential_if_any: {
const i = newUser.credentials.findIndex(
credential => credential.type === "password"
);
if (i === -1) {
break delete_existing_password_credential_if_any;
}
newUser.credentials.splice(i, 1);
}
{
const credential = defaultUser_default.credentials.find(
credential => credential.type === "password"
);
assert(credential !== undefined);
newUser.credentials.push(credential);
}
{
const nameByClientId = Object.fromEntries(
parsedRealmJson.clients.map(client => [client.id, client.clientId] as const)
);
const newClientRoles: NonNullable<
ParsedRealmJson["users"][number]["clientRoles"]
> = {};
for (const clientRole of Object.values(parsedRealmJson.roles.client).flat()) {
const clientName = nameByClientId[clientRole.containerId];
assert(clientName !== undefined);
(newClientRoles[clientName] ??= []).push(clientRole.name);
}
const { same: sameSet } = sameFactory({
takeIntoAccountArraysOrdering: false
});
for (const [clientName, roles] of Object.entries(newClientRoles)) {
keep_previous_ordering_if_possible: {
const roles_previous = newUser.clientRoles?.[clientName];
if (roles_previous === undefined) {
break keep_previous_ordering_if_possible;
}
if (!sameSet(roles_previous, roles)) {
break keep_previous_ordering_if_possible;
}
continue;
}
(newUser.clientRoles ??= {})[clientName] = roles;
}
}
if (defaultUser_preexisting === undefined) {
parsedRealmJson.users.push(newUser);
} else {
const i = parsedRealmJson.users.indexOf(defaultUser_preexisting);
assert(i !== -1);
parsedRealmJson.users[i] = newUser;
}
return { username: newUser.username };
}
function addOrEditClient(params: {
parsedRealmJson: ParsedRealmJson;
keycloakMajorVersionNumber: number;
}): { clientId: string } {
const { parsedRealmJson, keycloakMajorVersionNumber } = params;
const parsedRealmJson_default = getDefaultConfig({ keycloakMajorVersionNumber });
const testClient_default = (() => {
const clients = parsedRealmJson_default.clients.filter(client => {
return JSON.stringify(client).includes(TEST_APP_URL);
});
assert(clients.length === 1);
return clients[0];
})();
const clientIds_builtIn = parsedRealmJson_default.clients
.map(client => client.clientId)
.filter(clientId => clientId !== testClient_default.clientId);
const testClient_preexisting = (() => {
const clients = parsedRealmJson.clients
.filter(client => !clientIds_builtIn.includes(client.clientId))
.filter(client => client.protocol === "openid-connect");
{
const client = clients.find(
client => client.clientId === testClient_default.clientId
);
if (client !== undefined) {
return client;
}
}
{
const client = clients.find(
client =>
client.redirectUris?.find(redirectUri =>
redirectUri.startsWith(TEST_APP_URL)
) !== undefined
);
if (client !== undefined) {
return client;
}
}
const [client] = clients;
if (client === undefined) {
return undefined;
}
return client;
})();
let testClient: typeof testClient_default;
if (testClient_preexisting !== undefined) {
testClient = testClient_preexisting;
} else {
testClient = structuredClone(testClient_default);
delete testClient.protocolMappers;
parsedRealmJson.clients.push(testClient);
}
testClient.redirectUris = [
`${TEST_APP_URL}/*`,
"http://localhost*",
"http://127.0.0.1*"
]
.sort()
.reverse();
(testClient.attributes ??= {})["post.logout.redirect.uris"] = "+";
testClient.webOrigins = ["*"];
return { clientId: testClient.clientId };
}
function editAccountConsoleAndSecurityAdminConsole(params: {
parsedRealmJson: ParsedRealmJson;
}) {
const { parsedRealmJson } = params;
for (const clientId of ["account-console", "security-admin-console"] as const) {
const client = parsedRealmJson.clients.find(
client => client.clientId === clientId
);
assert(client !== undefined);
{
const arr = (client.redirectUris ??= []);
for (const value of ["http://localhost*", "http://127.0.0.1*"]) {
if (!arr.includes(value)) {
arr.push(value);
}
}
client.redirectUris?.sort().reverse();
}
(client.attributes ??= {})["post.logout.redirect.uris"] = "+";
client.webOrigins = ["*"];
admin_specific: {
if (clientId !== "security-admin-console") {
break admin_specific;
}
const protocolMapper_preexisting = client.protocolMappers?.find(
protocolMapper => {
if (protocolMapper.protocolMapper !== "oidc-hardcoded-claim-mapper") {
return false;
}
if (protocolMapper.protocol !== "openid-connect") {
return false;
}
if (protocolMapper.config === undefined) {
return false;
}
if (protocolMapper.config["claim.name"] !== "allowed-origins") {
return false;
}
return true;
}
);
let protocolMapper: NonNullable<typeof protocolMapper_preexisting>;
const config = {
"introspection.token.claim": "true",
"claim.value": '["*"]',
"userinfo.token.claim": "true",
"id.token.claim": "false",
"lightweight.claim": "true",
"access.token.claim": "true",
"claim.name": "allowed-origins",
"jsonType.label": "JSON",
"access.tokenResponse.claim": "false"
};
if (protocolMapper_preexisting !== undefined) {
protocolMapper = protocolMapper_preexisting;
} else {
protocolMapper = {
id: "8fd0d584-7052-4d04-a615-d18a71050873",
name: "allowed-origins",
protocol: "openid-connect",
protocolMapper: "oidc-hardcoded-claim-mapper",
consentRequired: false,
config
};
(client.protocolMappers ??= []).push(protocolMapper);
}
assert(protocolMapper.config !== undefined);
if (config !== protocolMapper.config) {
Object.assign(protocolMapper.config, config);
}
}
}
}

View File

@ -0,0 +1,155 @@
import type { BuildContext } from "../../shared/buildContext";
import { assert } from "tsafe/assert";
import { getDefaultConfig } from "./defaultConfig";
import {
prepareRealmConfig,
type BuildContextLike as BuildContextLike_prepareRealmConfig
} from "./prepareRealmConfig";
import * as fs from "fs";
import {
join as pathJoin,
dirname as pathDirname,
relative as pathRelative,
sep as pathSep
} from "path";
import { existsAsync } from "../../tools/fs.existsAsync";
import {
readRealmJsonFile,
writeRealmJsonFile,
type ParsedRealmJson
} from "./ParsedRealmJson";
import {
dumpContainerConfig,
type BuildContextLike as BuildContextLike_dumpContainerConfig
} from "./dumpContainerConfig";
import * as runExclusive from "run-exclusive";
import { waitForDebounceFactory } from "powerhooks/tools/waitForDebounce";
import chalk from "chalk";
export type BuildContextLike = BuildContextLike_dumpContainerConfig &
BuildContextLike_prepareRealmConfig & {
projectDirPath: string;
};
assert<BuildContext extends BuildContextLike ? true : false>;
export async function getRealmConfig(params: {
keycloakMajorVersionNumber: number;
realmJsonFilePath_userProvided: string | undefined;
buildContext: BuildContextLike;
}): Promise<{
realmJsonFilePath: string;
clientName: string;
realmName: string;
username: string;
onRealmConfigChange: () => Promise<void>;
}> {
const { keycloakMajorVersionNumber, realmJsonFilePath_userProvided, buildContext } =
params;
const realmJsonFilePath = pathJoin(
buildContext.projectDirPath,
".keycloakify",
`realm-kc-${keycloakMajorVersionNumber}.json`
);
const parsedRealmJson = await (async () => {
if (realmJsonFilePath_userProvided !== undefined) {
return readRealmJsonFile({
realmJsonFilePath: realmJsonFilePath_userProvided
});
}
if (await existsAsync(realmJsonFilePath)) {
return readRealmJsonFile({
realmJsonFilePath
});
}
return getDefaultConfig({ keycloakMajorVersionNumber });
})();
const { clientName, realmName, username } = prepareRealmConfig({
parsedRealmJson,
buildContext,
keycloakMajorVersionNumber
});
{
const dirPath = pathDirname(realmJsonFilePath);
if (!(await existsAsync(dirPath))) {
fs.mkdirSync(dirPath, { recursive: true });
}
}
await writeRealmJsonFile({
realmJsonFilePath,
parsedRealmJson,
keycloakMajorVersionNumber
});
const { onRealmConfigChange } = (() => {
const run = runExclusive.build(async () => {
const start = Date.now();
console.log(
chalk.grey(`Changes detected to the '${realmName}' config, backing up...`)
);
let parsedRealmJson: ParsedRealmJson;
try {
parsedRealmJson = await dumpContainerConfig({
buildContext,
realmName,
keycloakMajorVersionNumber
});
} catch (error) {
console.log(chalk.red(`Failed to backup '${realmName}' config:`));
return;
}
await writeRealmJsonFile({
realmJsonFilePath,
parsedRealmJson,
keycloakMajorVersionNumber
});
console.log(
[
chalk.grey(
`Save changed to \`.${pathSep}${pathRelative(buildContext.projectDirPath, realmJsonFilePath)}\``
),
chalk.grey(
`Next time you'll be running \`keycloakify start-keycloak\`, the realm '${realmName}' will be restored to this state.`
),
chalk.green(
`✓ '${realmName}' config backed up completed in ${Date.now() - start}ms`
)
].join("\n")
);
});
const { waitForDebounce } = waitForDebounceFactory({
delay: 1_000
});
async function onRealmConfigChange() {
await waitForDebounce();
run();
}
return { onRealmConfigChange };
})();
return {
realmJsonFilePath,
clientName,
realmName,
username,
onRealmConfigChange
};
}

View File

@ -1,7 +1,11 @@
import type { BuildContext } from "../shared/buildContext";
import { exclude } from "tsafe/exclude";
import { promptKeycloakVersion } from "../shared/promptKeycloakVersion";
import { CONTAINER_NAME, KEYCLOAKIFY_SPA_DEV_SERVER_PORT } from "../shared/constants";
import {
CONTAINER_NAME,
KEYCLOAKIFY_SPA_DEV_SERVER_PORT,
KEYCLOAKIFY_LOGIN_JAR_BASENAME,
TEST_APP_URL
} from "../shared/constants";
import { SemVer } from "../tools/SemVer";
import { assert, type Equals } from "tsafe/assert";
import * as fs from "fs";
@ -9,8 +13,7 @@ import {
join as pathJoin,
relative as pathRelative,
sep as pathSep,
basename as pathBasename,
dirname as pathDirname
basename as pathBasename
} from "path";
import * as child_process from "child_process";
import chalk from "chalk";
@ -28,6 +31,9 @@ import { existsAsync } from "../tools/fs.existsAsync";
import { rm } from "../tools/fs.rm";
import { downloadAndExtractArchive } from "../tools/downloadAndExtractArchive";
import { startViteDevServer } from "./startViteDevServer";
import { getSupportedKeycloakMajorVersions } from "./realmConfig/defaultConfig";
import { getSupportedDockerImageTags } from "./getSupportedDockerImageTags";
import { getRealmConfig } from "./realmConfig";
export async function command(params: {
buildContext: BuildContext;
@ -91,9 +97,32 @@ export async function command(params: {
const { cliCommandOptions, buildContext } = params;
const { allSupportedTags, latestMajorTags } = await getSupportedDockerImageTags({
buildContext
});
const { dockerImageTag } = await (async () => {
if (cliCommandOptions.keycloakVersion !== undefined) {
return { dockerImageTag: cliCommandOptions.keycloakVersion };
const cliCommandOptions_keycloakVersion = cliCommandOptions.keycloakVersion;
const tag = allSupportedTags.find(tag =>
tag.startsWith(cliCommandOptions_keycloakVersion)
);
if (tag === undefined) {
console.log(
chalk.red(
[
`We could not find a Keycloak Docker image for ${cliCommandOptions_keycloakVersion}`,
`Example of valid values: --keycloak-version 26, --keycloak-version 26.0.7`
].join("\n")
)
);
process.exit(1);
}
return { dockerImageTag: tag };
}
if (buildContext.startKeycloakOptions.dockerImage !== undefined) {
@ -108,50 +137,165 @@ export async function command(params: {
"On which version of Keycloak do you want to test your theme?"
),
chalk.gray(
"You can also explicitly provide the version with `npx keycloakify start-keycloak --keycloak-version 25.0.2` (or any other version)"
"You can also explicitly provide the version with `npx keycloakify start-keycloak --keycloak-version 26` (or any other version)"
)
].join("\n")
);
const { keycloakVersion } = await promptKeycloakVersion({
startingFromMajor: 18,
excludeMajorVersions: [22],
doOmitPatch: true,
buildContext
});
const tag_userSelected = await (async () => {
let tag: string;
console.log(`${keycloakVersion}`);
let latestMajorTags_copy = [...latestMajorTags];
return { dockerImageTag: keycloakVersion };
while (true) {
const { value } = await cliSelect<string>({
values: latestMajorTags_copy
}).catch(() => {
process.exit(-1);
});
tag = value;
{
const doImplementAccountMpa =
buildContext.implementedThemeTypes.account.isImplemented &&
buildContext.implementedThemeTypes.account.type === "Multi-Page";
if (doImplementAccountMpa && tag.startsWith("22.")) {
console.log(
chalk.yellow(
`You are implementing a Multi-Page Account theme. Keycloak 22 is not supported, select another version`
)
);
latestMajorTags_copy = latestMajorTags_copy.filter(
tag => !tag.startsWith("22.")
);
continue;
}
}
const readMajor = (tag: string) => {
const major = parseInt(tag.split(".")[0]);
assert(!isNaN(major));
return major;
};
{
const major = readMajor(tag);
const doImplementAdminTheme =
buildContext.implementedThemeTypes.admin.isImplemented;
const getIsSupported = (major: number) => major >= 23;
if (doImplementAdminTheme && !getIsSupported(major)) {
console.log(
chalk.yellow(
`You are implementing an Admin theme. Only Keycloak 23 and later are supported, select another version`
)
);
latestMajorTags_copy = latestMajorTags_copy.filter(tag =>
getIsSupported(readMajor(tag))
);
continue;
}
}
{
const doImplementAccountSpa =
buildContext.implementedThemeTypes.account.isImplemented &&
buildContext.implementedThemeTypes.account.type === "Single-Page";
const major = readMajor(tag);
const getIsSupported = (major: number) => major >= 19;
if (doImplementAccountSpa && !getIsSupported(major)) {
console.log(
chalk.yellow(
`You are implementing a Single-Page Account theme. Only Keycloak 19 and later are supported, select another version`
)
);
latestMajorTags_copy = latestMajorTags_copy.filter(tag =>
getIsSupported(readMajor(tag))
);
continue;
}
}
break;
}
return tag;
})();
console.log(`${tag_userSelected}`);
return { dockerImageTag: tag_userSelected };
})();
const keycloakMajorVersionNumber = (() => {
if (buildContext.startKeycloakOptions.dockerImage === undefined) {
return SemVer.parse(dockerImageTag).major;
}
const { tag } = buildContext.startKeycloakOptions.dockerImage;
const [wrap] = [18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28]
const [wrap] = getSupportedKeycloakMajorVersions()
.map(majorVersionNumber => ({
majorVersionNumber,
index: tag.indexOf(`${majorVersionNumber}`)
index: dockerImageTag.indexOf(`${majorVersionNumber}`)
}))
.filter(({ index }) => index !== -1)
.sort((a, b) => a.index - b.index);
if (wrap === undefined) {
console.warn(
chalk.yellow(
`Could not determine the major Keycloak version number from the docker image tag ${tag}. Assuming 25`
)
);
return 25;
try {
const version = SemVer.parse(dockerImageTag);
console.error(
chalk.yellow(
`Keycloak version ${version.major} is not supported, supported versions are ${getSupportedKeycloakMajorVersions().join(", ")}`
)
);
process.exit(1);
} catch {
// NOTE: Latest version
const [n] = getSupportedKeycloakMajorVersions();
console.warn(
chalk.yellow(
`Could not determine the major Keycloak version number from the docker image tag ${dockerImageTag}. Assuming ${n}`
)
);
return n;
}
}
return wrap.majorVersionNumber;
})();
const { clientName, onRealmConfigChange, realmJsonFilePath, realmName, username } =
await getRealmConfig({
keycloakMajorVersionNumber,
realmJsonFilePath_userProvided: await (async () => {
if (cliCommandOptions.realmJsonFilePath !== undefined) {
return getAbsoluteAndInOsFormatPath({
pathIsh: cliCommandOptions.realmJsonFilePath,
cwd: process.cwd()
});
}
if (buildContext.startKeycloakOptions.realmJsonFilePath !== undefined) {
assert(
await existsAsync(
buildContext.startKeycloakOptions.realmJsonFilePath
),
`${pathRelative(process.cwd(), buildContext.startKeycloakOptions.realmJsonFilePath)} does not exist`
);
return buildContext.startKeycloakOptions.realmJsonFilePath;
}
return undefined;
})(),
buildContext
});
{
const { isAppBuildSuccess } = await appBuild({
buildContext
@ -189,154 +333,48 @@ export async function command(params: {
assert(jarFilePath !== undefined);
const extensionJarFilePaths = await Promise.all(
buildContext.startKeycloakOptions.extensionJars.map(async extensionJar => {
switch (extensionJar.type) {
case "path": {
assert(
await existsAsync(extensionJar.path),
`${extensionJar.path} does not exist`
);
return extensionJar.path;
const extensionJarFilePaths = [
...(keycloakMajorVersionNumber <= 20
? (console.log(
chalk.yellow(
"WARNING: With older version of keycloak your changes to the realm configuration are not persisted"
)
),
[])
: [
pathJoin(
getThisCodebaseRootDirPath(),
"src",
"bin",
"start-keycloak",
KEYCLOAKIFY_LOGIN_JAR_BASENAME
)
]),
...(await Promise.all(
buildContext.startKeycloakOptions.extensionJars.map(async extensionJar => {
switch (extensionJar.type) {
case "path": {
assert(
await existsAsync(extensionJar.path),
`${extensionJar.path} does not exist`
);
return extensionJar.path;
}
case "url": {
const { archiveFilePath } = await downloadAndExtractArchive({
cacheDirPath: buildContext.cacheDirPath,
fetchOptions: buildContext.fetchOptions,
url: extensionJar.url,
uniqueIdOfOnArchiveFile: "no extraction",
onArchiveFile: async () => {}
});
return archiveFilePath;
}
}
case "url": {
const { archiveFilePath } = await downloadAndExtractArchive({
cacheDirPath: buildContext.cacheDirPath,
fetchOptions: buildContext.fetchOptions,
url: extensionJar.url,
uniqueIdOfOnArchiveFile: "no extraction",
onArchiveFile: async () => {}
});
return archiveFilePath;
}
}
assert<Equals<typeof extensionJar, never>>(false);
})
);
const getRealmJsonFilePath_defaultForKeycloakMajor = (
keycloakMajorVersionNumber: number
) =>
pathJoin(
getThisCodebaseRootDirPath(),
"src",
"bin",
"start-keycloak",
`myrealm-realm-${keycloakMajorVersionNumber}.json`
);
const realmJsonFilePath = await (async () => {
if (cliCommandOptions.realmJsonFilePath !== undefined) {
if (cliCommandOptions.realmJsonFilePath === "none") {
return undefined;
}
return getAbsoluteAndInOsFormatPath({
pathIsh: cliCommandOptions.realmJsonFilePath,
cwd: process.cwd()
});
}
if (buildContext.startKeycloakOptions.realmJsonFilePath !== undefined) {
assert(
await existsAsync(buildContext.startKeycloakOptions.realmJsonFilePath),
`${pathRelative(process.cwd(), buildContext.startKeycloakOptions.realmJsonFilePath)} does not exist`
);
return buildContext.startKeycloakOptions.realmJsonFilePath;
}
const internalFilePath = await (async () => {
const defaultFilePath = getRealmJsonFilePath_defaultForKeycloakMajor(
keycloakMajorVersionNumber
);
if (fs.existsSync(defaultFilePath)) {
return defaultFilePath;
}
console.log(
`${chalk.yellow(
`Keycloakify do not have a realm configuration for Keycloak ${keycloakMajorVersionNumber} yet.`
)}`
);
console.log(chalk.cyan("Select what configuration to use:"));
const dirPath = pathDirname(defaultFilePath);
const { value } = await cliSelect<string>({
values: [
...fs
.readdirSync(dirPath)
.filter(fileBasename => fileBasename.endsWith(".json")),
"none"
]
}).catch(() => {
process.exit(-1);
});
if (value === "none") {
return undefined;
}
return pathJoin(dirPath, value);
})();
if (internalFilePath === undefined) {
return undefined;
}
const filePath = pathJoin(
buildContext.cacheDirPath,
pathBasename(internalFilePath)
);
fs.writeFileSync(
filePath,
Buffer.from(
fs
.readFileSync(internalFilePath)
.toString("utf8")
.replace(/keycloakify\-starter/g, buildContext.themeNames[0])
),
"utf8"
);
return filePath;
})();
add_test_user_if_missing: {
if (realmJsonFilePath === undefined) {
break add_test_user_if_missing;
}
const realm: Record<string, unknown> = JSON.parse(
fs.readFileSync(realmJsonFilePath).toString("utf8")
);
if (realm.users !== undefined) {
break add_test_user_if_missing;
}
const realmJsonFilePath_internal = (() => {
const filePath = getRealmJsonFilePath_defaultForKeycloakMajor(
keycloakMajorVersionNumber
);
if (!fs.existsSync(filePath)) {
return getRealmJsonFilePath_defaultForKeycloakMajor(25);
}
return filePath;
})();
const users = JSON.parse(
fs.readFileSync(realmJsonFilePath_internal).toString("utf8")
).users;
realm.users = users;
fs.writeFileSync(realmJsonFilePath, JSON.stringify(realm, null, 2), "utf8");
}
assert<Equals<typeof extensionJar, never>>(false);
})
))
];
async function extractThemeResourcesFromJar() {
await extractArchive({
@ -376,18 +414,16 @@ export async function command(params: {
});
} catch {}
const DEFAULT_PORT = 8080;
const port =
cliCommandOptions.port ?? buildContext.startKeycloakOptions.port ?? DEFAULT_PORT;
const port = cliCommandOptions.port ?? buildContext.startKeycloakOptions.port ?? 8080;
const devServerPort = (() => {
const doStartDevServer = (() => {
const hasSpaUi =
buildContext.implementedThemeTypes.admin.isImplemented ||
(buildContext.implementedThemeTypes.account.isImplemented &&
buildContext.implementedThemeTypes.account.type === "Single-Page");
if (!hasSpaUi) {
return undefined;
return false;
}
if (buildContext.bundler !== "vite") {
@ -401,7 +437,7 @@ export async function command(params: {
)
);
return undefined;
return false;
}
if (keycloakMajorVersionNumber < 25) {
@ -415,17 +451,18 @@ export async function command(params: {
)
);
return undefined;
return false;
}
return port + 1;
return true;
})();
if (devServerPort !== undefined) {
startViteDevServer({
buildContext,
port: devServerPort
});
let devServerPort: number | undefined = undefined;
if (doStartDevServer) {
const { port } = await startViteDevServer({ buildContext });
devServerPort = port;
}
const SPACE_PLACEHOLDER = "SPACE_PLACEHOLDER_xKLmdPd";
@ -433,8 +470,15 @@ export async function command(params: {
const dockerRunArgs: string[] = [
`-p${SPACE_PLACEHOLDER}${port}:8080`,
`--name${SPACE_PLACEHOLDER}${CONTAINER_NAME}`,
`-e${SPACE_PLACEHOLDER}KEYCLOAK_ADMIN=admin`,
`-e${SPACE_PLACEHOLDER}KEYCLOAK_ADMIN_PASSWORD=admin`,
...(keycloakMajorVersionNumber >= 26
? [
`-e${SPACE_PLACEHOLDER}KC_BOOTSTRAP_ADMIN_USERNAME=admin`,
`-e${SPACE_PLACEHOLDER}KC_BOOTSTRAP_ADMIN_PASSWORD=admin`
]
: [
`-e${SPACE_PLACEHOLDER}KEYCLOAK_ADMIN=admin`,
`-e${SPACE_PLACEHOLDER}KEYCLOAK_ADMIN_PASSWORD=admin`
]),
...(devServerPort === undefined
? []
: [
@ -450,7 +494,7 @@ export async function command(params: {
...(realmJsonFilePath === undefined
? []
: [
`-v${SPACE_PLACEHOLDER}"${realmJsonFilePath}":/opt/keycloak/data/import/myrealm-realm.json`
`-v${SPACE_PLACEHOLDER}"${realmJsonFilePath}":/opt/keycloak/data/import/${realmName}-realm.json`
]),
`-v${SPACE_PLACEHOLDER}"${jarFilePath_cacheDir}":/opt/keycloak/providers/keycloak-theme.jar`,
...extensionJarFilePaths.map(
@ -525,7 +569,14 @@ export async function command(params: {
{ shell: true }
);
child.stdout.on("data", data => process.stdout.write(data));
child.stdout.on("data", async data => {
if (data.toString("utf8").includes("keycloakify-logging: REALM_CONFIG_CHANGED")) {
await onRealmConfigChange();
return;
}
process.stdout.write(data);
});
child.stderr.on("data", data => process.stderr.write(data));
@ -572,9 +623,9 @@ export async function command(params: {
`${chalk.green("Your theme is accessible at:")}`,
`${chalk.green("➜")} ${chalk.cyan.bold(
(() => {
const url = new URL("https://my-theme.keycloakify.dev");
const url = new URL(TEST_APP_URL);
if (port !== DEFAULT_PORT) {
if (port !== 8080) {
url.searchParams.set("port", `${port}`);
}
if (kcHttpRelativePath !== undefined) {
@ -583,13 +634,20 @@ export async function command(params: {
kcHttpRelativePath
);
}
if (realmName !== "myrealm") {
url.searchParams.set("realm", realmName);
}
if (clientName !== "myclient") {
url.searchParams.set("client", clientName);
}
return url.href;
})()
)}`,
"",
"You can login with the following credentials:",
`- username: ${chalk.cyan.bold("testuser")}`,
`- username: ${chalk.cyan.bold(username)}`,
`- password: ${chalk.cyan.bold("password123")}`,
"",
`Watching for changes in ${chalk.bold(
@ -646,42 +704,56 @@ export async function command(params: {
}
)
.on("all", async (...[, filePath]) => {
ignore_account_spa: {
const doImplementAccountSpa =
buildContext.implementedThemeTypes.account.isImplemented &&
buildContext.implementedThemeTypes.account.type === "Single-Page";
if (!doImplementAccountSpa) {
break ignore_account_spa;
ignore_path_covered_by_hmr: {
if (filePath.endsWith(".properties")) {
break ignore_path_covered_by_hmr;
}
if (
!isInside({
dirPath: pathJoin(buildContext.themeSrcDirPath, "account"),
filePath
})
) {
break ignore_account_spa;
if (!doStartDevServer) {
break ignore_path_covered_by_hmr;
}
return;
}
ignore_account_spa: {
const doImplementAccountSpa =
buildContext.implementedThemeTypes.account.isImplemented &&
buildContext.implementedThemeTypes.account.type ===
"Single-Page";
ignore_admin: {
if (!buildContext.implementedThemeTypes.admin.isImplemented) {
break ignore_admin;
if (!doImplementAccountSpa) {
break ignore_account_spa;
}
if (
!isInside({
dirPath: pathJoin(
buildContext.themeSrcDirPath,
"account"
),
filePath
})
) {
break ignore_account_spa;
}
return;
}
if (
!isInside({
dirPath: pathJoin(buildContext.themeSrcDirPath, "admin"),
filePath
})
) {
break ignore_admin;
}
ignore_admin: {
if (!buildContext.implementedThemeTypes.admin.isImplemented) {
break ignore_admin;
}
return;
if (
!isInside({
dirPath: pathJoin(buildContext.themeSrcDirPath, "admin"),
filePath
})
) {
break ignore_admin;
}
return;
}
}
console.log(`Detected changes in ${filePath}`);

View File

@ -3,6 +3,7 @@ import { assert } from "tsafe/assert";
import type { BuildContext } from "../shared/buildContext";
import chalk from "chalk";
import { VITE_PLUGIN_SUB_SCRIPTS_ENV_NAMES } from "../shared/constants";
import { Deferred } from "evt/tools/Deferred";
export type BuildContextLike = {
projectDirPath: string;
@ -12,13 +13,12 @@ assert<BuildContext extends BuildContextLike ? true : false>();
export function startViteDevServer(params: {
buildContext: BuildContextLike;
port: number;
}): void {
const { buildContext, port } = params;
}): Promise<{ port: number }> {
const { buildContext } = params;
console.log(chalk.blue(`$ npx vite dev --port ${port}`));
console.log(chalk.blue(`$ npx vite dev`));
const child = child_process.spawn("npx", ["vite", "dev", "--port", `${port}`], {
const child = child_process.spawn("npx", ["vite", "dev"], {
cwd: buildContext.projectDirPath,
env: {
...process.env,
@ -36,4 +36,32 @@ export function startViteDevServer(params: {
});
child.stderr.on("data", data => process.stderr.write(data));
const dPort = new Deferred<number>();
{
const onData = (data: Buffer) => {
//Local: http://localhost:8083/
const match = data
.toString("utf8")
.replace(/\x1b[[0-9;]*m/g, "")
.match(/Local:\s*http:\/\/(?:localhost|127\.0\.0\.1):(\d+)\//);
if (match === null) {
return;
}
child.stdout.off("data", onData);
const port = parseInt(match[1]);
assert(!isNaN(port));
dPort.resolve(port);
};
child.stdout.on("data", onData);
}
return dPort.pr.then(port => ({ port }));
}

View File

@ -0,0 +1,99 @@
import { z } from "zod";
import { same } from "evt/tools/inDepth/same";
import { assert, type Equals } from "tsafe/assert";
import { id } from "tsafe/id";
export type Stringifyable =
| StringifyableAtomic
| StringifyableObject
| StringifyableArray;
export type StringifyableAtomic = string | number | boolean | null;
// NOTE: Use Record<string, Stringifyable>
interface StringifyableObject {
[key: string]: Stringifyable;
}
// NOTE: Use Stringifyable[]
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
interface StringifyableArray extends Array<Stringifyable> {}
export const zStringifyableAtomic = (() => {
type TargetType = StringifyableAtomic;
const zTargetType = z.union([z.string(), z.number(), z.boolean(), z.null()]);
assert<Equals<z.infer<typeof zTargetType>, TargetType>>();
return id<z.ZodType<TargetType>>(zTargetType);
})();
export const zStringifyable: z.ZodType<Stringifyable> = z
.any()
.superRefine((val, ctx) => {
const isStringifyable = same(JSON.parse(JSON.stringify(val)), val);
if (!isStringifyable) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Not stringifyable"
});
}
});
export function getIsAtomic(
stringifyable: Stringifyable
): stringifyable is StringifyableAtomic {
return (
["string", "number", "boolean"].includes(typeof stringifyable) ||
stringifyable === null
);
}
export const { getValueAtPath } = (() => {
function getValueAtPath_rec(
stringifyable: Stringifyable,
path: (string | number)[]
): Stringifyable | undefined {
if (path.length === 0) {
return stringifyable;
}
if (getIsAtomic(stringifyable)) {
return undefined;
}
const [first, ...rest] = path;
let dereferenced: Stringifyable | undefined;
if (stringifyable instanceof Array) {
if (typeof first !== "number") {
return undefined;
}
dereferenced = stringifyable[first];
} else {
if (typeof first !== "string") {
return undefined;
}
dereferenced = stringifyable[first];
}
if (dereferenced === undefined) {
return undefined;
}
return getValueAtPath_rec(dereferenced, rest);
}
function getValueAtPath(
stringifyableObjectOrArray: Record<string, Stringifyable> | Stringifyable[],
path: (string | number)[]
): Stringifyable | undefined {
return getValueAtPath_rec(stringifyableObjectOrArray, path);
}
return { getValueAtPath };
})();

View File

@ -0,0 +1,164 @@
import { getIsAtomic, getValueAtPath, type Stringifyable } from "./Stringifyable";
export function canonicalStringify(params: {
data: Record<string, Stringifyable> | Stringifyable[];
referenceData: Record<string, Stringifyable> | Stringifyable[];
}): string {
const { data, referenceData } = params;
return JSON.stringify(
makeDeterministicCopy({
path: [],
data,
getCanonicalKeys: path => {
const referenceValue = (() => {
const path_patched: (string | number)[] = [];
for (let i = 0; i < path.length; i++) {
let value_i = getValueAtPath(referenceData, [
...path_patched,
path[i]
]);
if (value_i !== undefined) {
path_patched.push(path[i]);
continue;
}
if (typeof path[i] !== "number") {
return undefined;
}
value_i = getValueAtPath(referenceData, [...path_patched, 0]);
if (value_i !== undefined) {
path_patched.push(0);
continue;
}
return undefined;
}
return getValueAtPath(referenceData, path_patched);
})();
if (referenceValue === undefined) {
return undefined;
}
if (getIsAtomic(referenceValue)) {
return undefined;
}
if (referenceValue instanceof Array) {
return undefined;
}
return Object.keys(referenceValue);
}
}),
null,
2
);
}
function makeDeterministicCopy(params: {
path: (string | number)[];
data: Stringifyable;
getCanonicalKeys: (path: (string | number)[]) => string[] | undefined;
}): Stringifyable {
const { path, data, getCanonicalKeys } = params;
if (getIsAtomic(data)) {
return data;
}
if (data instanceof Array) {
return makeDeterministicCopy_array({
path,
data,
getCanonicalKeys
});
}
return makeDeterministicCopy_record({
path,
data,
getCanonicalKeys
});
}
function makeDeterministicCopy_record(params: {
path: (string | number)[];
data: Record<string, Stringifyable>;
getCanonicalKeys: (path: (string | number)[]) => string[] | undefined;
}): Record<string, Stringifyable> {
const { path, data, getCanonicalKeys } = params;
const keysOfAtomicValues: string[] = [];
const keysOfNonAtomicValues: string[] = [];
for (const [key, value] of Object.entries(data)) {
if (getIsAtomic(value)) {
keysOfAtomicValues.push(key);
} else {
keysOfNonAtomicValues.push(key);
}
}
keysOfAtomicValues.sort();
keysOfNonAtomicValues.sort();
const keys = [...keysOfAtomicValues, ...keysOfNonAtomicValues];
reorder_according_to_canonical: {
const canonicalKeys = getCanonicalKeys(path);
if (canonicalKeys === undefined) {
break reorder_according_to_canonical;
}
const keys_toPrepend: string[] = [];
for (const key of canonicalKeys) {
const indexOfKey = keys.indexOf(key);
if (indexOfKey === -1) {
continue;
}
keys.splice(indexOfKey, 1);
keys_toPrepend.push(key);
}
keys.unshift(...keys_toPrepend);
}
const result: Record<string, Stringifyable> = {};
for (const key of keys) {
result[key] = makeDeterministicCopy({
path: [...path, key],
data: data[key],
getCanonicalKeys
});
}
return result;
}
function makeDeterministicCopy_array(params: {
path: (string | number)[];
data: Stringifyable[];
getCanonicalKeys: (path: (string | number)[]) => string[] | undefined;
}): Stringifyable[] {
const { path, data, getCanonicalKeys } = params;
return [...data].map((entry, i) =>
makeDeterministicCopy({
path: [...path, i],
data: entry,
getCanonicalKeys
})
);
}

View File

@ -0,0 +1,90 @@
const keyIsTrapped = "isTrapped_zSskDe9d";
export class AccessError extends Error {
constructor(message: string) {
super(message);
Object.setPrototypeOf(this, new.target.prototype);
}
}
export function createObjectThatThrowsIfAccessed<T extends object>(params?: {
debugMessage?: string;
isPropertyWhitelisted?: (prop: string | number | symbol) => boolean;
}): T {
const { debugMessage = "", isPropertyWhitelisted = () => false } = params ?? {};
const get: NonNullable<ProxyHandler<T>["get"]> = (...args) => {
const [, prop] = args;
if (isPropertyWhitelisted(prop)) {
return Reflect.get(...args);
}
if (prop === keyIsTrapped) {
return true;
}
throw new AccessError(`Cannot access ${String(prop)} yet ${debugMessage}`);
};
const trappedObject = new Proxy<T>({} as any, {
get,
set: get
});
return trappedObject;
}
export function createObjectThatThrowsIfAccessedFactory(params: {
isPropertyWhitelisted?: (prop: string | number | symbol) => boolean;
}) {
const { isPropertyWhitelisted } = params;
return {
createObjectThatThrowsIfAccessed: <T extends object>(params?: {
debugMessage?: string;
}) => {
const { debugMessage } = params ?? {};
return createObjectThatThrowsIfAccessed<T>({
debugMessage,
isPropertyWhitelisted
});
}
};
}
export function isObjectThatThrowIfAccessed(obj: object) {
return (obj as any)[keyIsTrapped] === true;
}
export const THROW_IF_ACCESSED = {
__brand: "THROW_IF_ACCESSED"
};
export function createObjectWithSomePropertiesThatThrowIfAccessed<
T extends Record<string, unknown>
>(obj: { [K in keyof T]: T[K] | typeof THROW_IF_ACCESSED }, debugMessage?: string): T {
return Object.defineProperties(
obj,
Object.fromEntries(
Object.entries(obj)
.filter(([, value]) => value === THROW_IF_ACCESSED)
.map(([key]) => {
const getAndSet = () => {
throw new AccessError(
`Cannot access ${key} yet ${debugMessage ?? ""}`
);
};
const pd = {
get: getAndSet,
set: getAndSet,
enumerable: true
};
return [key, pd];
})
)
) as any;
}

View File

@ -101,7 +101,7 @@ export async function runPrettier(params: {
resolveConfig: true
});
if (ignored) {
if (ignored || inferredParser === null) {
return sourceCode;
}
@ -110,7 +110,7 @@ export async function runPrettier(params: {
formattedSourceCode = await prettier.format(sourceCode, {
...config,
filePath,
parser: inferredParser ?? undefined
parser: inferredParser
});
} catch (error) {
console.log(

View File

@ -1,3 +1,4 @@
import type { JSX } from "keycloakify/tools/JSX";
import { lazy, Suspense } from "react";
import { assert, type Equals } from "tsafe/assert";
import type { LazyOrNot } from "keycloakify/tools/LazyOrNot";

View File

@ -1,3 +1,4 @@
import type { JSX } from "keycloakify/tools/JSX";
import { useEffect, useReducer, Fragment } from "react";
import { assert } from "keycloakify/tools/assert";
import type { KcClsx } from "keycloakify/login/lib/kcClsx";

View File

@ -1,3 +1,4 @@
import type { JSX } from "keycloakify/tools/JSX";
import { type FormAction, type FormFieldError } from "keycloakify/login/lib/useUserProfileForm";
import type { KcClsx } from "keycloakify/login/lib/kcClsx";
import type { Attribute } from "keycloakify/login/KcContext";

View File

@ -1,3 +1,4 @@
import type { JSX } from "keycloakify/tools/JSX";
import type { GenericI18n_noJsx } from "../noJsx/GenericI18n_noJsx";
import { assert, type Equals } from "tsafe/assert";

View File

@ -46,7 +46,7 @@ export type I18nBuilder<
}>
) => I18nBuilder<
ThemeName,
MessageKey_themeDefined,
string extends MessageKey_themeDefined ? never : MessageKey_themeDefined,
LanguageTag_notInDefaultSet,
ExcludedMethod | "withCustomTranslations"
>;

View File

@ -1,3 +1,4 @@
import type { JSX } from "keycloakify/tools/JSX";
import { useEffect, useState } from "react";
import { kcSanitize } from "keycloakify/lib/kcSanitize";
import { createGetI18n, type KcContextLike } from "../noJsx/getI18n";

View File

@ -1,3 +1,4 @@
import type { JSX } from "keycloakify/tools/JSX";
import * as reactlessApi from "./getUserProfileApi/index";
import type { PasswordPolicies, Attribute, Validators } from "keycloakify/login/KcContext";
import { useEffect, useState, useMemo, Fragment } from "react";

View File

@ -1,3 +1,4 @@
import type { JSX } from "keycloakify/tools/JSX";
import { useState } from "react";
import type { LazyOrNot } from "keycloakify/tools/LazyOrNot";
import { getKcClsx } from "keycloakify/login/lib/kcClsx";

View File

@ -1,3 +1,4 @@
import type { JSX } from "keycloakify/tools/JSX";
import { useState, useEffect, useReducer } from "react";
import { kcSanitize } from "keycloakify/lib/kcSanitize";
import { assert } from "keycloakify/tools/assert";

View File

@ -1,3 +1,4 @@
import type { JSX } from "keycloakify/tools/JSX";
import { useState, useEffect, useReducer } from "react";
import { kcSanitize } from "keycloakify/lib/kcSanitize";
import { clsx } from "keycloakify/tools/clsx";

View File

@ -1,3 +1,4 @@
import type { JSX } from "keycloakify/tools/JSX";
import { useEffect, useReducer } from "react";
import { kcSanitize } from "keycloakify/lib/kcSanitize";
import { assert } from "keycloakify/tools/assert";

View File

@ -1,3 +1,4 @@
import type { JSX } from "keycloakify/tools/JSX";
import { useState } from "react";
import type { LazyOrNot } from "keycloakify/tools/LazyOrNot";
import { getKcClsx } from "keycloakify/login/lib/kcClsx";

View File

@ -1,3 +1,4 @@
import type { JSX } from "keycloakify/tools/JSX";
import { type TemplateProps, type ClassKey } from "keycloakify/login/TemplateProps";
import type { LazyOrNot } from "keycloakify/tools/LazyOrNot";

View File

@ -1,3 +1,4 @@
import type { JSX } from "keycloakify/tools/JSX";
import { useState } from "react";
import type { LazyOrNot } from "keycloakify/tools/LazyOrNot";
import { kcSanitize } from "keycloakify/lib/kcSanitize";

View File

@ -1,3 +1,4 @@
import type { JSX } from "keycloakify/tools/JSX";
import { useState } from "react";
import type { LazyOrNot } from "keycloakify/tools/LazyOrNot";
import { getKcClsx, type KcClsx } from "keycloakify/login/lib/kcClsx";

5
src/tools/JSX.ts Normal file
View File

@ -0,0 +1,5 @@
import type { ReactElement } from "react";
export namespace JSX {
export interface Element extends ReactElement<any, any> {}
}

View File

@ -62,3 +62,65 @@ type I18n = typeof ofTypeI18n;
assert<Equals<typeof node, JSX.Element>>;
}
{
const i18n = Reflect<I18n>();
i18n.msg("passwordConfirm");
}
{
const i18n = Reflect<I18n>();
// @ts-expect-error
i18n.msg("iDoNotExist");
}
{
const { ofTypeI18n } = i18nBuilder
.withThemeName<"keycloakify-starter">()
.withCustomTranslations({})
.build();
type I18n = typeof ofTypeI18n;
{
const i18n = Reflect<I18n>();
// @ts-expect-error
const node = i18n.msg("iDoNotExist");
assert<Equals<typeof node, JSX.Element>>;
}
}
i18nBuilder.withThemeName<"my-theme-1" | "my-theme-2">().withCustomTranslations({
en: {
myCustomKey1: "my-custom-key-1-en",
// @ts-expect-error
myCustomKey2: {
"my-theme-1": "my-theme-1-en"
//"my-theme-2": "my-theme-2-en"
}
}
});
i18nBuilder
.withThemeName<"my-theme-1" | "my-theme-2">()
.withExtraLanguages({
he: {
label: "עברית",
getMessages: () => import("./he")
}
})
.withCustomTranslations({
en: {
myCustomKey1: "my-custom-key-1-en",
myCustomKey2: "my-custom-key-2-en"
},
// @ts-expect-error
he: {
myCustomKey1: "my-custom-key-1-he"
//myCustomKey2: "my-custom-key-2-he"
}
});