Compare commits

..

52 Commits

Author SHA1 Message Date
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
ec74ceef4d Implement hot module replacement for developing Account SPA and Admin UI 2024-11-30 23:55:24 +01:00
fd3261cdf1 Bump version 2024-11-25 11:41:36 +01:00
b4b53d2552 Re export wide type def of the kcContext 2024-11-25 11:41:17 +01:00
0371d9ea7a Bump version 2024-11-23 08:49:17 +01:00
73031e74ec Make it so it's not required to manually call the copy-keycloak-resources-to-public script even in webpack projects 2024-11-23 08:48:06 +01:00
f71ab4635f Bump version 2024-11-22 06:16:42 +01:00
983db6780a #730 2024-11-22 06:16:15 +01:00
ea22107b9b Bump version 2024-11-21 07:12:53 +01:00
8e4a7fed9e Fix invalid dom nesting errors 2024-11-21 07:12:23 +01:00
30efd8fcf4 Bump version 2024-11-21 06:27:09 +01:00
f4c4e92ca1 #726 2024-11-21 06:26:54 +01:00
cfda99f5b0 Bump version 2024-11-19 03:49:32 +01:00
5063b1c7ab Rename LoginDeviceVerifyUserCode to LoginOauth2DeviceVerifyUserCode (as it should have been) 2024-11-19 03:49:01 +01:00
955b6cac45 Remove noisy stories 2024-11-19 03:22:49 +01:00
1fa3d6133c Remove unused template prop 2024-11-19 03:22:35 +01:00
59 changed files with 2531 additions and 677 deletions

View File

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

View File

@ -1,6 +1,6 @@
{ {
"name": "keycloakify", "name": "keycloakify",
"version": "11.3.26", "version": "11.4.5",
"description": "Framework to create custom Keycloak UIs", "description": "Framework to create custom Keycloak UIs",
"repository": { "repository": {
"type": "git", "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 chalk from "chalk";
import { run } from "../shared/run"; import { run } from "../shared/run";
import { vendorFrontendDependencies } from "./vendorFrontendDependencies"; import { vendorFrontendDependencies } from "./vendorFrontendDependencies";
import { downloadKeycloakifyLogging } from "./downloadKeycloakifyLogging";
(async () => { (async () => {
console.log(chalk.cyan("Building Keycloakify...")); console.log(chalk.cyan("Building Keycloakify..."));
@ -148,9 +149,6 @@ import { vendorFrontendDependencies } from "./vendorFrontendDependencies";
fs.cpSync(dirBasename, destDirPath, { recursive: true }); fs.cpSync(dirBasename, destDirPath, { recursive: true });
} }
await createPublicKeycloakifyDevResourcesDir();
await createAccountV1Dir();
transformCodebase({ transformCodebase({
srcDirPath: join("stories"), srcDirPath: join("stories"),
destDirPath: join("dist", "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( console.log(
chalk.green(`✓ built in ${((Date.now() - startTime) / 1000).toFixed(2)}s`) chalk.green(`✓ built in ${((Date.now() - startTime) / 1000).toFixed(2)}s`)
); );

View File

@ -1,65 +1,15 @@
import { CONTAINER_NAME } from "../src/bin/shared/constants"; import { CONTAINER_NAME } from "../src/bin/shared/constants";
import child_process from "child_process"; import child_process from "child_process";
import { SemVer } from "../src/bin/tools/SemVer"; 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 { runPrettier } from "../src/bin/tools/runPrettier";
import { getThisCodebaseRootDirPath } from "../src/bin/tools/getThisCodebaseRootDirPath";
import { join as pathJoin } from "path";
import * as fs from "fs";
import chalk from "chalk"; import chalk from "chalk";
import { Deferred } from "evt/tools/Deferred";
import { assert, is } from "tsafe/assert";
import { run } from "./shared/run";
(async () => { (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( const keycloakMajorVersionNumber = SemVer.parse(
child_process child_process
.execSync(`docker inspect --format '{{.Config.Image}}' ${CONTAINER_NAME}`) .execSync(`docker inspect --format '{{.Config.Image}}' ${CONTAINER_NAME}`)
@ -68,19 +18,32 @@ import { run } from "./shared/run";
.split(":")[1] .split(":")[1]
).major; ).major;
const targetFilePath = pathRelative( const parsedRealmJson = await dumpContainerConfig({
process.cwd(), buildContext: {
pathJoin( cacheDirPath
__dirname, },
"..", keycloakMajorVersionNumber,
"src", realmName: "myrealm"
"bin", });
"start-keycloak",
`myrealm-realm-${keycloakMajorVersionNumber}.json` let sourceCode = JSON.stringify(parsedRealmJson, null, 2);
)
const filePath = pathJoin(
getThisCodebaseRootDirPath(),
"src",
"bin",
"start-keycloak",
"realmConfig",
"defaultConfig",
`realm-kc-${keycloakMajorVersionNumber}.json`
); );
run(`docker cp ${CONTAINER_NAME}:/tmp/myrealm-realm.json ${targetFilePath}`); sourceCode = await runPrettier({
sourceCode,
filePath
});
console.log(`${chalk.green(`✓ Exported realm to`)} ${chalk.bold(targetFilePath)}`); fs.writeFileSync(filePath, Buffer.from(sourceCode, "utf8"));
console.log(chalk.green(`Realm config dumped to ${filePath}`));
})(); })();

View File

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

View File

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

View File

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

View File

@ -84,8 +84,47 @@ attributes_to_attributesByName: {
kcContext.profile.attributesByName[attribute.name] = attribute; kcContext.profile.attributesByName[attribute.name] = attribute;
}); });
} }
redirect_to_dev_server: {
switch(kcContext.themeType){
case "login":
break redirect_to_dev_server;
case "account":
if( kcContext.pageId !== "index.ftl" ){
break redirect_to_dev_server;
}
break;
case "admin":
break;
default:
break redirect_to_dev_server;
}
const devSeverPort = kcContext.properties.{{KEYCLOAKIFY_SPA_DEV_SERVER_PORT}};
if( !devSeverPort ){
break redirect_to_dev_server;
}
const redirectUrl = new URL(window.location.href);
redirectUrl.port = devSeverPort;
delete kcContext.msgJSON;
console.log(kcContext);
redirectUrl.searchParams.set("kcContext", encodeURIComponent(JSON.stringify(kcContext)));
window.location.href = redirectUrl.toString();
}
window.kcContext = kcContext; window.kcContext = kcContext;
<#if xKeycloakify.themeType == "login" > <#if xKeycloakify.themeType == "login" >
{ {
const script = document.createElement("script"); const script = document.createElement("script");

View File

@ -20,7 +20,8 @@ import {
LOGIN_THEME_PAGE_IDS, LOGIN_THEME_PAGE_IDS,
ACCOUNT_THEME_PAGE_IDS, ACCOUNT_THEME_PAGE_IDS,
WELL_KNOWN_DIRECTORY_BASE_NAME, WELL_KNOWN_DIRECTORY_BASE_NAME,
THEME_TYPES THEME_TYPES,
KEYCLOAKIFY_SPA_DEV_SERVER_PORT
} from "../../shared/constants"; } from "../../shared/constants";
import { assert, type Equals } from "tsafe/assert"; import { assert, type Equals } from "tsafe/assert";
import { readFieldNameUsage } from "./readFieldNameUsage"; import { readFieldNameUsage } from "./readFieldNameUsage";
@ -379,7 +380,10 @@ export async function generateResources(params: {
? ["deprecatedMode=false"] ? ["deprecatedMode=false"]
: []), : []),
...(buildContext.extraThemeProperties ?? []), ...(buildContext.extraThemeProperties ?? []),
...buildContext.environmentVariables.map( ...[
...buildContext.environmentVariables,
{ name: KEYCLOAKIFY_SPA_DEV_SERVER_PORT, default: "" }
].map(
({ name, default: defaultValue }) => ({ name, default: defaultValue }) =>
`${name}=\${env.${name}:${escapeStringForPropertiesFile(defaultValue)}}` `${name}=\${env.${name}:${escapeStringForPropertiesFile(defaultValue)}}`
), ),

View File

@ -5,6 +5,9 @@ import { readThisNpmPackageVersion } from "./tools/readThisNpmPackageVersion";
import * as child_process from "child_process"; import * as child_process from "child_process";
import { assertNoPnpmDlx } from "./tools/assertNoPnpmDlx"; import { assertNoPnpmDlx } from "./tools/assertNoPnpmDlx";
import { getBuildContext } from "./shared/buildContext"; import { getBuildContext } from "./shared/buildContext";
import { SemVer } from "./tools/SemVer";
import { assert, is } from "tsafe/assert";
import chalk from "chalk";
type CliCommandOptions = { type CliCommandOptions = {
projectDirPath: string | undefined; projectDirPath: string | undefined;
@ -80,7 +83,7 @@ program
program program
.command<{ .command<{
port: number | undefined; port: number | undefined;
keycloakVersion: string | undefined; keycloakVersion: string | number | undefined;
realmJsonFilePath: string | undefined; realmJsonFilePath: string | undefined;
}>({ }>({
name: "start-keycloak", name: "start-keycloak",
@ -134,9 +137,50 @@ program
handler: async ({ projectDirPath, keycloakVersion, port, realmJsonFilePath }) => { handler: async ({ projectDirPath, keycloakVersion, port, realmJsonFilePath }) => {
const { command } = await import("./start-keycloak"); 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({ await command({
buildContext: getBuildContext({ projectDirPath }), buildContext: getBuildContext({ projectDirPath }),
cliCommandOptions: { keycloakVersion, port, realmJsonFilePath } cliCommandOptions: {
keycloakVersion,
port,
realmJsonFilePath
}
}); });
} }
}); });
@ -201,7 +245,7 @@ program
.command({ .command({
name: "copy-keycloak-resources-to-public", name: "copy-keycloak-resources-to-public",
description: description:
"(Webpack/Create-React-App only) Copy Keycloak default theme resources to the public directory." "(Internal) Copy Keycloak default theme resources to the public directory."
}) })
.task({ .task({
skip, skip,

View File

@ -10,7 +10,8 @@ export type ThemeType = (typeof THEME_TYPES)[number];
export const VITE_PLUGIN_SUB_SCRIPTS_ENV_NAMES = { export const VITE_PLUGIN_SUB_SCRIPTS_ENV_NAMES = {
RUN_POST_BUILD_SCRIPT: "KEYCLOAKIFY_RUN_POST_BUILD_SCRIPT", RUN_POST_BUILD_SCRIPT: "KEYCLOAKIFY_RUN_POST_BUILD_SCRIPT",
RESOLVE_VITE_CONFIG: "KEYCLOAKIFY_RESOLVE_VITE_CONFIG" RESOLVE_VITE_CONFIG: "KEYCLOAKIFY_RESOLVE_VITE_CONFIG",
READ_KC_CONTEXT_FROM_URL: "KEYCLOAKIFY_READ_KC_CONTEXT_FROM_URL"
} as const; } as const;
export const BUILD_FOR_KEYCLOAK_MAJOR_VERSION_ENV_NAME = export const BUILD_FOR_KEYCLOAK_MAJOR_VERSION_ENV_NAME =
@ -78,3 +79,11 @@ export const CUSTOM_HANDLER_ENV_NAMES = {
}; };
export const KEYCLOAK_THEME = "keycloak-theme"; 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 * as child_process from "child_process";
import { Deferred } from "evt/tools/Deferred"; 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 type { BuildContext } from "../shared/buildContext";
import chalk from "chalk"; import chalk from "chalk";
import { sep as pathSep, join as pathJoin } from "path"; import { sep as pathSep, join as pathJoin } from "path";
import { getAbsoluteAndInOsFormatPath } from "../tools/getAbsoluteAndInOsFormatPath"; import { getAbsoluteAndInOsFormatPath } from "../tools/getAbsoluteAndInOsFormatPath";
import * as fs from "fs"; import * as fs from "fs";
import { dirname as pathDirname, relative as pathRelative } from "path"; import { dirname as pathDirname, relative as pathRelative } from "path";
import { z } from "zod";
export type BuildContextLike = { export type BuildContextLike = {
projectDirPath: string; projectDirPath: string;
keycloakifyBuildDirPath: string;
bundler: BuildContext["bundler"];
projectBuildDirPath: string;
packageJsonFilePath: string; packageJsonFilePath: string;
}; };
@ -23,58 +22,36 @@ export async function appBuild(params: {
}): Promise<{ isAppBuildSuccess: boolean }> { }): Promise<{ isAppBuildSuccess: boolean }> {
const { buildContext } = params; const { buildContext } = params;
switch (buildContext.bundler) { const { parsedPackageJson } = (() => {
case "vite": type ParsedPackageJson = {
return appBuild_vite({ buildContext }); scripts?: Record<string, string>;
case "webpack": };
return appBuild_webpack({ buildContext });
}
}
async function appBuild_vite(params: { const zParsedPackageJson = (() => {
buildContext: BuildContextLike; type TargetType = ParsedPackageJson;
}): Promise<{ isAppBuildSuccess: boolean }> {
const { buildContext } = params;
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"], { zParsedPackageJson.parse(parsedPackageJson);
cwd: buildContext.projectDirPath,
shell: true
});
child.stdout.on("data", data => { assert(is<ParsedPackageJson>(parsedPackageJson));
if (data.toString("utf8").includes("gzip:")) {
return;
}
process.stdout.write(data); return { parsedPackageJson };
}); })();
child.stderr.on("data", data => process.stderr.write(data)); const entries = Object.entries(parsedPackageJson.scripts ?? {}).filter(
([, scriptCommand]) => scriptCommand.includes("keycloakify build")
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"));
if (entries.length === 0) { if (entries.length === 0) {
console.log( console.log(
@ -127,6 +104,76 @@ async function appBuild_webpack(params: {
process.exit(-1); 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); let commandCwd = pathDirname(buildContext.packageJsonFilePath);
for (const subCommand of appBuildSubCommands) { for (const subCommand of appBuildSubCommands) {

View File

@ -0,0 +1,230 @@
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";
export type BuildContextLike = {
fetchOptions: BuildContext["fetchOptions"];
cacheDirPath: string;
};
assert<BuildContext extends BuildContextLike ? true : false>;
export async function getSupportedDockerImageTags(params: {
buildContext: BuildContextLike;
}) {
const { buildContext } = params;
{
const result = await getCachedValue({ cacheDirPath: buildContext.cacheDirPath });
if (result !== undefined) {
return result;
}
}
const tags: string[] = [];
await (async function callee(url: string) {
const r = await fetch(url, buildContext.fetchOptions);
await Promise.all([
(async () => {
tags.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 arr = tags
.map(tag => ({
tag,
version: (() => {
if (tag.includes("-")) {
return undefined;
}
let version: SemVer;
try {
version = SemVer.parse(tag);
} catch {
return undefined;
}
return version;
})()
}))
.map(({ tag, version }) => (version === undefined ? undefined : { tag, version }))
.filter(exclude(undefined));
const versionByMajor: Record<number, SemVer | undefined> = {};
for (const { version } of arr) {
const version_current = versionByMajor[version.major];
if (
version_current === undefined ||
SemVer.compare(version_current, version) === -1
) {
versionByMajor[version.major] = version;
}
}
const supportedKeycloakMajorVersions = getSupportedKeycloakMajorVersions();
const result = Object.entries(versionByMajor)
.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));
await setCachedValue({ cacheDirPath: buildContext.cacheDirPath, result });
return result;
}
const { getCachedValue, setCachedValue } = (() => {
type Cache = {
keycloakifyVersion: string;
time: number;
result: string[];
};
const zCache = (() => {
type TargetType = Cache;
const zTargetType = z.object({
keycloakifyVersion: z.string(),
time: z.number(),
result: z.array(z.string())
});
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 { is } from "tsafe/is";
import { id } from "tsafe/id";
import * as fs from "fs";
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?: unknown[];
}[];
};
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.unknown()).optional()
})
)
});
type InferredType = z.infer<typeof zTargetType>;
assert<Equals<TargetType, InferredType>>;
return id<z.ZodType<TargetType>>(zTargetType);
})();
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,75 @@
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 { type ParsedRealmJson, readRealmJsonFile } from "../ParsedRealmJson";
export 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": { "composites": {
"realm": ["offline_access", "uma_authorization"], "realm": ["offline_access", "uma_authorization"],
"client": { "client": {
"account": ["delete-account", "view-profile", "manage-account"] "account": ["view-profile", "manage-account", "delete-account"]
} }
}, },
"clientRole": false, "clientRole": false,
@ -398,6 +398,26 @@
"otpPolicyLookAheadWindow": 1, "otpPolicyLookAheadWindow": 1,
"otpPolicyPeriod": 30, "otpPolicyPeriod": 30,
"otpSupportedApplications": ["FreeOTP", "Google Authenticator"], "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": [ "users": [
{ {
"id": "00a62e75-bcc1-419a-a292-63ee5d161ed3", "id": "00a62e75-bcc1-419a-a292-63ee5d161ed3",
@ -422,30 +442,43 @@
"disableableCredentialTypes": [], "disableableCredentialTypes": [],
"requiredActions": [], "requiredActions": [],
"realmRoles": ["default-roles-myrealm"], "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, "notBefore": 0,
"groups": [] "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": [ "scopeMappings": [
{ {
"clientScope": "offline_access", "clientScope": "offline_access",
@ -505,8 +538,12 @@
"enabled": true, "enabled": true,
"alwaysDisplayInConsole": false, "alwaysDisplayInConsole": false,
"clientAuthenticatorType": "client-secret", "clientAuthenticatorType": "client-secret",
"redirectUris": ["/realms/myrealm/account/*"], "redirectUris": [
"webOrigins": [], "http://localhost*",
"http://127.0.0.1*",
"/realms/myrealm/account/*"
],
"webOrigins": ["*"],
"notBefore": 0, "notBefore": 0,
"bearerOnly": false, "bearerOnly": false,
"consentRequired": false, "consentRequired": false,
@ -518,6 +555,7 @@
"frontchannelLogout": false, "frontchannelLogout": false,
"protocol": "openid-connect", "protocol": "openid-connect",
"attributes": { "attributes": {
"post.logout.redirect.uris": "+",
"pkce.code.challenge.method": "S256" "pkce.code.challenge.method": "S256"
}, },
"authenticationFlowBindingOverrides": {}, "authenticationFlowBindingOverrides": {},
@ -636,7 +674,7 @@
"attributes": { "attributes": {
"oidc.ciba.grant.enabled": "false", "oidc.ciba.grant.enabled": "false",
"backchannel.logout.session.required": "true", "backchannel.logout.session.required": "true",
"login_theme": "keycloakify-starter", "post.logout.redirect.uris": "+",
"display.on.consent.screen": "false", "display.on.consent.screen": "false",
"oauth2.device.authorization.grant.enabled": "false", "oauth2.device.authorization.grant.enabled": "false",
"backchannel.logout.revoke.offline.tokens": "false" "backchannel.logout.revoke.offline.tokens": "false"
@ -694,8 +732,12 @@
"enabled": true, "enabled": true,
"alwaysDisplayInConsole": false, "alwaysDisplayInConsole": false,
"clientAuthenticatorType": "client-secret", "clientAuthenticatorType": "client-secret",
"redirectUris": ["/admin/myrealm/console/*"], "redirectUris": [
"webOrigins": ["+"], "http://localhost*",
"http://127.0.0.1*",
"/admin/myrealm/console/*"
],
"webOrigins": ["*"],
"notBefore": 0, "notBefore": 0,
"bearerOnly": false, "bearerOnly": false,
"consentRequired": false, "consentRequired": false,
@ -707,6 +749,7 @@
"frontchannelLogout": false, "frontchannelLogout": false,
"protocol": "openid-connect", "protocol": "openid-connect",
"attributes": { "attributes": {
"post.logout.redirect.uris": "+",
"pkce.code.challenge.method": "S256" "pkce.code.challenge.method": "S256"
}, },
"authenticationFlowBindingOverrides": {}, "authenticationFlowBindingOverrides": {},
@ -757,7 +800,8 @@
"consentRequired": false, "consentRequired": false,
"config": { "config": {
"id.token.claim": "true", "id.token.claim": "true",
"access.token.claim": "true" "access.token.claim": "true",
"userinfo.token.claim": "true"
} }
} }
] ]
@ -1205,6 +1249,7 @@
"consentRequired": false, "consentRequired": false,
"config": { "config": {
"multivalued": "true", "multivalued": "true",
"userinfo.token.claim": "true",
"user.attribute": "foo", "user.attribute": "foo",
"id.token.claim": "true", "id.token.claim": "true",
"access.token.claim": "true", "access.token.claim": "true",
@ -1271,11 +1316,11 @@
}, },
"smtpServer": {}, "smtpServer": {},
"loginTheme": "keycloakify-starter", "loginTheme": "keycloakify-starter",
"accountTheme": "keycloakify-starter", "accountTheme": "",
"adminTheme": "", "adminTheme": "",
"emailTheme": "", "emailTheme": "",
"eventsEnabled": false, "eventsEnabled": false,
"eventsListeners": ["jboss-logging"], "eventsListeners": ["keycloakify-logging", "jboss-logging"],
"enabledEventTypes": [], "enabledEventTypes": [],
"adminEventsEnabled": false, "adminEventsEnabled": false,
"adminEventsDetailsEnabled": false, "adminEventsDetailsEnabled": false,
@ -1291,14 +1336,14 @@
"subComponents": {}, "subComponents": {},
"config": { "config": {
"allowed-protocol-mapper-types": [ "allowed-protocol-mapper-types": [
"saml-user-attribute-mapper",
"oidc-usermodel-property-mapper",
"oidc-full-name-mapper", "oidc-full-name-mapper",
"saml-user-property-mapper", "saml-user-property-mapper",
"oidc-usermodel-attribute-mapper", "oidc-usermodel-attribute-mapper",
"saml-user-attribute-mapper",
"oidc-address-mapper", "oidc-address-mapper",
"oidc-sha256-pairwise-sub-mapper", "oidc-sha256-pairwise-sub-mapper",
"saml-role-list-mapper", "saml-role-list-mapper"
"oidc-usermodel-property-mapper"
] ]
} }
}, },
@ -1347,14 +1392,14 @@
"subComponents": {}, "subComponents": {},
"config": { "config": {
"allowed-protocol-mapper-types": [ "allowed-protocol-mapper-types": [
"oidc-usermodel-property-mapper",
"oidc-address-mapper",
"oidc-full-name-mapper", "oidc-full-name-mapper",
"oidc-usermodel-property-mapper",
"saml-user-property-mapper", "saml-user-property-mapper",
"saml-user-attribute-mapper",
"oidc-sha256-pairwise-sub-mapper",
"oidc-usermodel-attribute-mapper", "oidc-usermodel-attribute-mapper",
"saml-role-list-mapper" "oidc-sha256-pairwise-sub-mapper",
"saml-role-list-mapper",
"oidc-address-mapper",
"saml-user-attribute-mapper"
] ]
} }
}, },
@ -1394,6 +1439,12 @@
"providerId": "rsa-generated", "providerId": "rsa-generated",
"subComponents": {}, "subComponents": {},
"config": { "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"] "priority": ["100"]
} }
}, },
@ -1403,6 +1454,12 @@
"providerId": "rsa-enc-generated", "providerId": "rsa-enc-generated",
"subComponents": {}, "subComponents": {},
"config": { "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"], "priority": ["100"],
"algorithm": ["RSA-OAEP"] "algorithm": ["RSA-OAEP"]
} }
@ -1413,6 +1470,8 @@
"providerId": "aes-generated", "providerId": "aes-generated",
"subComponents": {}, "subComponents": {},
"config": { "config": {
"kid": ["132fb843-59e9-4f36-ad55-5ce2d3a13fb3"],
"secret": ["ETyyqapnrkUsNXLQ-tBVKw"],
"priority": ["100"] "priority": ["100"]
} }
}, },
@ -1422,6 +1481,10 @@
"providerId": "hmac-generated", "providerId": "hmac-generated",
"subComponents": {}, "subComponents": {},
"config": { "config": {
"kid": ["5110d380-c930-49d9-b91b-87f338f6170b"],
"secret": [
"uCpQrJvP5OBuTxXfDb4JRL0bCKpXUgfGn5vb8UvL-Sfs_sZ9rtvBmd6vuFWARqyezjJQtpoNlMv7sXgxkN-yxQ"
],
"priority": ["100"], "priority": ["100"],
"algorithm": ["HS256"] "algorithm": ["HS256"]
} }
@ -1454,7 +1517,7 @@
"defaultLocale": "en", "defaultLocale": "en",
"authenticationFlows": [ "authenticationFlows": [
{ {
"id": "f7f2b89b-43cb-491d-8e7c-f1814024a6da", "id": "223ce532-2038-4f24-a606-2a5c73f7bd65",
"alias": "Account verification options", "alias": "Account verification options",
"description": "Method with which to verity the existing account", "description": "Method with which to verity the existing account",
"providerId": "basic-flow", "providerId": "basic-flow",
@ -1480,7 +1543,7 @@
] ]
}, },
{ {
"id": "17cdac6f-d2a3-4907-8d44-a42827610b63", "id": "57e47732-79cc-4d60-bee7-4f0b8fd44540",
"alias": "Authentication Options", "alias": "Authentication Options",
"description": "Authentication options.", "description": "Authentication options.",
"providerId": "basic-flow", "providerId": "basic-flow",
@ -1514,7 +1577,7 @@
] ]
}, },
{ {
"id": "53a3e43f-9468-401f-8051-40f982d12f85", "id": "c2735d89-60c0-45a4-9b3c-ae5df17df395",
"alias": "Browser - Conditional OTP", "alias": "Browser - Conditional OTP",
"description": "Flow to determine if the OTP is required for the authentication", "description": "Flow to determine if the OTP is required for the authentication",
"providerId": "basic-flow", "providerId": "basic-flow",
@ -1540,7 +1603,7 @@
] ]
}, },
{ {
"id": "26286808-3b7b-43df-b32e-af55a37af2e9", "id": "11a5a507-2b9a-443f-961b-dffd66f4318d",
"alias": "Direct Grant - Conditional OTP", "alias": "Direct Grant - Conditional OTP",
"description": "Flow to determine if the OTP is required for the authentication", "description": "Flow to determine if the OTP is required for the authentication",
"providerId": "basic-flow", "providerId": "basic-flow",
@ -1566,7 +1629,7 @@
] ]
}, },
{ {
"id": "8a6a752a-9a9a-4d38-b1f8-edf0a9433490", "id": "963bd753-6ea7-4d93-ab56-30f9ab59d597",
"alias": "First broker login - Conditional OTP", "alias": "First broker login - Conditional OTP",
"description": "Flow to determine if the OTP is required for the authentication", "description": "Flow to determine if the OTP is required for the authentication",
"providerId": "basic-flow", "providerId": "basic-flow",
@ -1592,7 +1655,7 @@
] ]
}, },
{ {
"id": "a6f6804c-4160-4a84-8a1f-c2747a2d3f27", "id": "1db6a489-a3b4-44c4-b480-1d1e8c123d20",
"alias": "Handle Existing Account", "alias": "Handle Existing Account",
"description": "Handle what to do if there is existing account with same email/username like authenticated identity provider", "description": "Handle what to do if there is existing account with same email/username like authenticated identity provider",
"providerId": "basic-flow", "providerId": "basic-flow",
@ -1618,7 +1681,7 @@
] ]
}, },
{ {
"id": "740baa9e-8328-4035-9e1a-8fc1616d1f0f", "id": "7a38f32d-4f34-450f-8f03-64802d7cb8f1",
"alias": "Reset - Conditional OTP", "alias": "Reset - Conditional OTP",
"description": "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.", "description": "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.",
"providerId": "basic-flow", "providerId": "basic-flow",
@ -1644,7 +1707,7 @@
] ]
}, },
{ {
"id": "e60187a8-3e16-4a0c-9daa-f3a4a1fcfdba", "id": "0df88739-3739-4d70-8893-47c546f19003",
"alias": "User creation or linking", "alias": "User creation or linking",
"description": "Flow for the existing/non-existing user alternatives", "description": "Flow for the existing/non-existing user alternatives",
"providerId": "basic-flow", "providerId": "basic-flow",
@ -1671,7 +1734,7 @@
] ]
}, },
{ {
"id": "d959d0c2-4004-4633-b280-f80d6423f574", "id": "35025424-e291-4c54-8a29-70aadba549ce",
"alias": "Verify Existing Account by Re-authentication", "alias": "Verify Existing Account by Re-authentication",
"description": "Reauthentication of existing account", "description": "Reauthentication of existing account",
"providerId": "basic-flow", "providerId": "basic-flow",
@ -1697,7 +1760,7 @@
] ]
}, },
{ {
"id": "ba02689d-b9e8-4a4b-8fdd-0d1386b198fc", "id": "1813b7f2-c3c2-4b92-8ffc-9ff2d12186c6",
"alias": "browser", "alias": "browser",
"description": "browser based authentication", "description": "browser based authentication",
"providerId": "basic-flow", "providerId": "basic-flow",
@ -1739,7 +1802,7 @@
] ]
}, },
{ {
"id": "f09ac92a-e091-4e84-9cd1-cb905ca57b89", "id": "954283ac-f1c2-40b6-a39f-bf23ff9f3ce8",
"alias": "clients", "alias": "clients",
"description": "Base authentication for clients", "description": "Base authentication for clients",
"providerId": "client-flow", "providerId": "client-flow",
@ -1781,7 +1844,7 @@
] ]
}, },
{ {
"id": "aaf72b22-cec4-4714-93d6-f54d5a986ab8", "id": "52a789ce-2cad-4f0f-93b2-295b7fd519f0",
"alias": "direct grant", "alias": "direct grant",
"description": "OpenID Connect Resource Owner Grant", "description": "OpenID Connect Resource Owner Grant",
"providerId": "basic-flow", "providerId": "basic-flow",
@ -1815,7 +1878,7 @@
] ]
}, },
{ {
"id": "c4a54bb3-f009-4231-a82b-376c2515e07e", "id": "5a6a71e1-9105-45b6-b5f0-52538461357b",
"alias": "docker auth", "alias": "docker auth",
"description": "Used by Docker clients to authenticate against the IDP", "description": "Used by Docker clients to authenticate against the IDP",
"providerId": "basic-flow", "providerId": "basic-flow",
@ -1833,7 +1896,7 @@
] ]
}, },
{ {
"id": "f55ded54-683a-4f5a-a101-9cfbd7b96781", "id": "8392b6e7-bdbf-4d7f-97b6-885761c200db",
"alias": "first broker login", "alias": "first broker login",
"description": "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account", "description": "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account",
"providerId": "basic-flow", "providerId": "basic-flow",
@ -1860,7 +1923,7 @@
] ]
}, },
{ {
"id": "931d5a82-378f-4533-8c69-2239a4acd047", "id": "52136d70-8d08-42ea-b04b-cf40ea2807aa",
"alias": "forms", "alias": "forms",
"description": "Username, password, otp and other auth forms.", "description": "Username, password, otp and other auth forms.",
"providerId": "basic-flow", "providerId": "basic-flow",
@ -1886,7 +1949,7 @@
] ]
}, },
{ {
"id": "22b05374-f480-4ca8-aca8-9db8b6dd1729", "id": "26bbc7e6-ef01-4cdb-9dba-520e2f3f8993",
"alias": "http challenge", "alias": "http challenge",
"description": "An authentication flow based on challenge-response HTTP Authentication Schemes", "description": "An authentication flow based on challenge-response HTTP Authentication Schemes",
"providerId": "basic-flow", "providerId": "basic-flow",
@ -1912,7 +1975,7 @@
] ]
}, },
{ {
"id": "c0371832-e4b7-485e-bf23-6babe4c6ac83", "id": "f0887979-04eb-4033-8f19-0ffd8c8b7f6a",
"alias": "registration", "alias": "registration",
"description": "registration flow", "description": "registration flow",
"providerId": "basic-flow", "providerId": "basic-flow",
@ -1931,7 +1994,7 @@
] ]
}, },
{ {
"id": "4d0445da-073e-465e-b25b-af522915c73f", "id": "a3b7b94b-bfbf-4760-a8c9-7d9cd98d262e",
"alias": "registration form", "alias": "registration form",
"description": "registration form", "description": "registration form",
"providerId": "form-flow", "providerId": "form-flow",
@ -1973,7 +2036,7 @@
] ]
}, },
{ {
"id": "740d467f-4203-425b-8203-9bfd3eed25ae", "id": "dc68a665-2e51-4a22-aaad-bd693ddc77cc",
"alias": "reset credentials", "alias": "reset credentials",
"description": "Reset credentials for a user if they forgot their password or something", "description": "Reset credentials for a user if they forgot their password or something",
"providerId": "basic-flow", "providerId": "basic-flow",
@ -2015,7 +2078,7 @@
] ]
}, },
{ {
"id": "cf1a9af9-dadd-4cb9-a26e-fbbba216f8e1", "id": "ae6b73aa-1318-4ae8-a3d9-d01b5e7d957e",
"alias": "saml ecp", "alias": "saml ecp",
"description": "SAML ECP Profile Authentication Flow", "description": "SAML ECP Profile Authentication Flow",
"providerId": "basic-flow", "providerId": "basic-flow",
@ -2035,14 +2098,14 @@
], ],
"authenticatorConfig": [ "authenticatorConfig": [
{ {
"id": "4e65eb4b-9f0a-4ab8-98b2-6daf50cd1bf8", "id": "0c18de7f-0714-41f4-9a3f-ed4edd53ae9c",
"alias": "create unique user config", "alias": "create unique user config",
"config": { "config": {
"require.password.update.after.registration": "false" "require.password.update.after.registration": "false"
} }
}, },
{ {
"id": "5e8dc1c5-1489-4d39-bb75-9c499583b91b", "id": "65b3c8bb-34a4-4d19-b578-245dc8ff53ea",
"alias": "review profile config", "alias": "review profile config",
"config": { "config": {
"update.profile.on.first.login": "missing" "update.profile.on.first.login": "missing"
@ -2132,8 +2195,8 @@
"attributes": { "attributes": {
"cibaBackchannelTokenDeliveryMode": "poll", "cibaBackchannelTokenDeliveryMode": "poll",
"cibaAuthRequestedUserHint": "login_hint", "cibaAuthRequestedUserHint": "login_hint",
"oauth2DevicePollingInterval": "5",
"clientOfflineSessionMaxLifespan": "0", "clientOfflineSessionMaxLifespan": "0",
"oauth2DevicePollingInterval": "5",
"clientSessionIdleTimeout": "0", "clientSessionIdleTimeout": "0",
"userProfileEnabled": "true", "userProfileEnabled": "true",
"clientOfflineSessionIdleTimeout": "0", "clientOfflineSessionIdleTimeout": "0",

View File

@ -73,7 +73,7 @@
"composites": { "composites": {
"realm": ["offline_access", "uma_authorization"], "realm": ["offline_access", "uma_authorization"],
"client": { "client": {
"account": ["delete-account", "view-profile", "manage-account"] "account": ["view-profile", "manage-account", "delete-account"]
} }
}, },
"clientRole": false, "clientRole": false,
@ -435,13 +435,46 @@
"type": "password", "type": "password",
"userLabel": "My password", "userLabel": "My password",
"createdDate": 1716214710762, "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\":{}}" "credentialData": "{\"hashIterations\":27500,\"algorithm\":\"pbkdf2-sha256\",\"additionalParameters\":{}}"
} }
], ],
"disableableCredentialTypes": [], "disableableCredentialTypes": [],
"requiredActions": [], "requiredActions": [],
"realmRoles": ["default-roles-myrealm"], "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, "notBefore": 0,
"groups": [] "groups": []
} }
@ -507,8 +540,12 @@
"enabled": true, "enabled": true,
"alwaysDisplayInConsole": false, "alwaysDisplayInConsole": false,
"clientAuthenticatorType": "client-secret", "clientAuthenticatorType": "client-secret",
"redirectUris": ["/realms/myrealm/account/*"], "redirectUris": [
"webOrigins": [], "http://localhost*",
"http://127.0.0.1*",
"/realms/myrealm/account/*"
],
"webOrigins": ["*"],
"notBefore": 0, "notBefore": 0,
"bearerOnly": false, "bearerOnly": false,
"consentRequired": false, "consentRequired": false,
@ -643,7 +680,6 @@
"attributes": { "attributes": {
"oidc.ciba.grant.enabled": "false", "oidc.ciba.grant.enabled": "false",
"backchannel.logout.session.required": "true", "backchannel.logout.session.required": "true",
"login_theme": "keycloakify-starter",
"post.logout.redirect.uris": "+", "post.logout.redirect.uris": "+",
"display.on.consent.screen": "false", "display.on.consent.screen": "false",
"oauth2.device.authorization.grant.enabled": "false", "oauth2.device.authorization.grant.enabled": "false",
@ -704,8 +740,12 @@
"enabled": true, "enabled": true,
"alwaysDisplayInConsole": false, "alwaysDisplayInConsole": false,
"clientAuthenticatorType": "client-secret", "clientAuthenticatorType": "client-secret",
"redirectUris": ["/admin/myrealm/console/*"], "redirectUris": [
"webOrigins": ["+"], "http://localhost*",
"http://127.0.0.1*",
"/admin/myrealm/console/*"
],
"webOrigins": ["*"],
"notBefore": 0, "notBefore": 0,
"bearerOnly": false, "bearerOnly": false,
"consentRequired": false, "consentRequired": false,
@ -1284,11 +1324,11 @@
}, },
"smtpServer": {}, "smtpServer": {},
"loginTheme": "keycloakify-starter", "loginTheme": "keycloakify-starter",
"accountTheme": "keycloakify-starter", "accountTheme": "",
"adminTheme": "", "adminTheme": "",
"emailTheme": "", "emailTheme": "",
"eventsEnabled": false, "eventsEnabled": false,
"eventsListeners": ["jboss-logging"], "eventsListeners": ["keycloakify-logging", "jboss-logging"],
"enabledEventTypes": [], "enabledEventTypes": [],
"adminEventsEnabled": false, "adminEventsEnabled": false,
"adminEventsDetailsEnabled": false, "adminEventsDetailsEnabled": false,
@ -1304,14 +1344,14 @@
"subComponents": {}, "subComponents": {},
"config": { "config": {
"allowed-protocol-mapper-types": [ "allowed-protocol-mapper-types": [
"oidc-full-name-mapper",
"oidc-sha256-pairwise-sub-mapper",
"oidc-usermodel-property-mapper",
"oidc-address-mapper",
"saml-user-property-mapper", "saml-user-property-mapper",
"oidc-usermodel-attribute-mapper",
"saml-user-attribute-mapper", "saml-user-attribute-mapper",
"saml-role-list-mapper" "oidc-full-name-mapper",
"oidc-usermodel-property-mapper",
"oidc-usermodel-attribute-mapper",
"oidc-address-mapper",
"saml-role-list-mapper",
"oidc-sha256-pairwise-sub-mapper"
] ]
} }
}, },
@ -1360,14 +1400,14 @@
"subComponents": {}, "subComponents": {},
"config": { "config": {
"allowed-protocol-mapper-types": [ "allowed-protocol-mapper-types": [
"saml-user-property-mapper",
"saml-user-attribute-mapper",
"oidc-full-name-mapper",
"oidc-sha256-pairwise-sub-mapper", "oidc-sha256-pairwise-sub-mapper",
"oidc-usermodel-attribute-mapper", "oidc-usermodel-attribute-mapper",
"oidc-address-mapper", "oidc-usermodel-property-mapper",
"saml-role-list-mapper", "saml-role-list-mapper",
"oidc-usermodel-property-mapper" "oidc-full-name-mapper",
"saml-user-property-mapper",
"oidc-address-mapper",
"saml-user-attribute-mapper"
] ]
} }
}, },
@ -1485,7 +1525,7 @@
"defaultLocale": "en", "defaultLocale": "en",
"authenticationFlows": [ "authenticationFlows": [
{ {
"id": "e134634e-f219-4df4-867c-8110688d8e56", "id": "1f4d4e13-1591-4751-8985-17886a8c98a9",
"alias": "Account verification options", "alias": "Account verification options",
"description": "Method with which to verity the existing account", "description": "Method with which to verity the existing account",
"providerId": "basic-flow", "providerId": "basic-flow",
@ -1511,7 +1551,7 @@
] ]
}, },
{ {
"id": "a611a8eb-9626-4aa4-8b54-ee565ea6e5dc", "id": "126f07c3-1bcb-4a02-bf16-bb44674bf55d",
"alias": "Authentication Options", "alias": "Authentication Options",
"description": "Authentication options.", "description": "Authentication options.",
"providerId": "basic-flow", "providerId": "basic-flow",
@ -1545,7 +1585,7 @@
] ]
}, },
{ {
"id": "d87cbb31-5c69-45c8-888d-f9649ebbbf97", "id": "eb3a08c8-5f99-49b6-b02b-16b62571f273",
"alias": "Browser - Conditional OTP", "alias": "Browser - Conditional OTP",
"description": "Flow to determine if the OTP is required for the authentication", "description": "Flow to determine if the OTP is required for the authentication",
"providerId": "basic-flow", "providerId": "basic-flow",
@ -1571,7 +1611,7 @@
] ]
}, },
{ {
"id": "752ba282-a369-4592-92e8-b4287192dbbf", "id": "3dc19838-5025-4bbb-b569-b574bd5a8d90",
"alias": "Direct Grant - Conditional OTP", "alias": "Direct Grant - Conditional OTP",
"description": "Flow to determine if the OTP is required for the authentication", "description": "Flow to determine if the OTP is required for the authentication",
"providerId": "basic-flow", "providerId": "basic-flow",
@ -1597,7 +1637,7 @@
] ]
}, },
{ {
"id": "2349282e-40ff-431a-984d-53911511e3d3", "id": "70d6fd40-d740-4dae-b0e6-350f8e9d4a1c",
"alias": "First broker login - Conditional OTP", "alias": "First broker login - Conditional OTP",
"description": "Flow to determine if the OTP is required for the authentication", "description": "Flow to determine if the OTP is required for the authentication",
"providerId": "basic-flow", "providerId": "basic-flow",
@ -1623,7 +1663,7 @@
] ]
}, },
{ {
"id": "4ff5463d-26d9-4219-ba85-41464401098f", "id": "6e24dcb3-5818-483c-8e44-883858171901",
"alias": "Handle Existing Account", "alias": "Handle Existing Account",
"description": "Handle what to do if there is existing account with same email/username like authenticated identity provider", "description": "Handle what to do if there is existing account with same email/username like authenticated identity provider",
"providerId": "basic-flow", "providerId": "basic-flow",
@ -1649,7 +1689,7 @@
] ]
}, },
{ {
"id": "87bb6c6d-cca8-4832-b5ab-67ecb9454a42", "id": "ac6254cd-403b-457b-b308-22a2a0e4f99d",
"alias": "Reset - Conditional OTP", "alias": "Reset - Conditional OTP",
"description": "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.", "description": "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.",
"providerId": "basic-flow", "providerId": "basic-flow",
@ -1675,7 +1715,7 @@
] ]
}, },
{ {
"id": "1fc3d028-0e0a-43a4-aaf9-ba7f7d60b409", "id": "485e74e6-9b3e-4b2c-a9b9-927802dc4f06",
"alias": "User creation or linking", "alias": "User creation or linking",
"description": "Flow for the existing/non-existing user alternatives", "description": "Flow for the existing/non-existing user alternatives",
"providerId": "basic-flow", "providerId": "basic-flow",
@ -1702,7 +1742,7 @@
] ]
}, },
{ {
"id": "036aae59-641f-4799-9124-c7e5034af6c1", "id": "ff9bb879-1d6a-4d1c-9836-1e4fab6f8997",
"alias": "Verify Existing Account by Re-authentication", "alias": "Verify Existing Account by Re-authentication",
"description": "Reauthentication of existing account", "description": "Reauthentication of existing account",
"providerId": "basic-flow", "providerId": "basic-flow",
@ -1728,7 +1768,7 @@
] ]
}, },
{ {
"id": "2e8b9f28-93b8-4368-84b0-1a8326daafe0", "id": "af8b2470-d581-401c-9984-762b966ebcc2",
"alias": "browser", "alias": "browser",
"description": "browser based authentication", "description": "browser based authentication",
"providerId": "basic-flow", "providerId": "basic-flow",
@ -1770,7 +1810,7 @@
] ]
}, },
{ {
"id": "0b826105-8493-45ce-87b3-7d917d190b39", "id": "414dbda4-eb3f-4baa-b23a-d3423af1eae6",
"alias": "clients", "alias": "clients",
"description": "Base authentication for clients", "description": "Base authentication for clients",
"providerId": "client-flow", "providerId": "client-flow",
@ -1812,7 +1852,7 @@
] ]
}, },
{ {
"id": "bf6d9edd-48d8-4392-bbc8-4b17a6866074", "id": "1cae0c4b-8dfb-4f5d-a781-e74d0a13c940",
"alias": "direct grant", "alias": "direct grant",
"description": "OpenID Connect Resource Owner Grant", "description": "OpenID Connect Resource Owner Grant",
"providerId": "basic-flow", "providerId": "basic-flow",
@ -1846,7 +1886,7 @@
] ]
}, },
{ {
"id": "97e31722-dd11-42be-aa99-88788fa2dde6", "id": "e798b655-7d85-4b6b-aee7-1448a3e1e0ea",
"alias": "docker auth", "alias": "docker auth",
"description": "Used by Docker clients to authenticate against the IDP", "description": "Used by Docker clients to authenticate against the IDP",
"providerId": "basic-flow", "providerId": "basic-flow",
@ -1864,7 +1904,7 @@
] ]
}, },
{ {
"id": "3f45cf34-231f-4ea1-8e58-d636c451a76b", "id": "eb94b723-1041-426a-87bf-f7b4bd2f485d",
"alias": "first broker login", "alias": "first broker login",
"description": "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account", "description": "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account",
"providerId": "basic-flow", "providerId": "basic-flow",
@ -1891,7 +1931,7 @@
] ]
}, },
{ {
"id": "9bef2f7c-f989-4871-aaa7-18e2cfa73f22", "id": "452d1d5f-7632-44d7-bc89-77ff2b209b3e",
"alias": "forms", "alias": "forms",
"description": "Username, password, otp and other auth forms.", "description": "Username, password, otp and other auth forms.",
"providerId": "basic-flow", "providerId": "basic-flow",
@ -1917,7 +1957,7 @@
] ]
}, },
{ {
"id": "0bfaa325-acde-4443-8bd8-1dc2ae759c5f", "id": "7c1b9e8f-6b57-49d1-a9a7-494862f93c0f",
"alias": "http challenge", "alias": "http challenge",
"description": "An authentication flow based on challenge-response HTTP Authentication Schemes", "description": "An authentication flow based on challenge-response HTTP Authentication Schemes",
"providerId": "basic-flow", "providerId": "basic-flow",
@ -1943,7 +1983,7 @@
] ]
}, },
{ {
"id": "37ddbe8c-abf3-4654-bd6d-ffabbeefbb98", "id": "2b38f34a-1739-499e-bb24-1dff96f32009",
"alias": "registration", "alias": "registration",
"description": "registration flow", "description": "registration flow",
"providerId": "basic-flow", "providerId": "basic-flow",
@ -1962,7 +2002,7 @@
] ]
}, },
{ {
"id": "5d7b4bc9-e93b-40da-aeb6-ba0c38392f1a", "id": "d26ae72b-a933-44dc-9927-1c82757004b2",
"alias": "registration form", "alias": "registration form",
"description": "registration form", "description": "registration form",
"providerId": "form-flow", "providerId": "form-flow",
@ -2004,7 +2044,7 @@
] ]
}, },
{ {
"id": "ee7a56e4-c827-4f24-8b8b-8476050b0b64", "id": "222ee8d6-1892-4768-9ada-720274b6bf9a",
"alias": "reset credentials", "alias": "reset credentials",
"description": "Reset credentials for a user if they forgot their password or something", "description": "Reset credentials for a user if they forgot their password or something",
"providerId": "basic-flow", "providerId": "basic-flow",
@ -2046,7 +2086,7 @@
] ]
}, },
{ {
"id": "360f0031-4c3b-4272-84ca-2172d430b4bc", "id": "e8b4d92c-27c1-4a9b-9b16-7ceb810fa230",
"alias": "saml ecp", "alias": "saml ecp",
"description": "SAML ECP Profile Authentication Flow", "description": "SAML ECP Profile Authentication Flow",
"providerId": "basic-flow", "providerId": "basic-flow",
@ -2066,14 +2106,14 @@
], ],
"authenticatorConfig": [ "authenticatorConfig": [
{ {
"id": "53630acd-a33a-40e3-8786-cf85464c6f9e", "id": "e5847a0b-855d-4d93-85fd-94714be3ed92",
"alias": "create unique user config", "alias": "create unique user config",
"config": { "config": {
"require.password.update.after.registration": "false" "require.password.update.after.registration": "false"
} }
}, },
{ {
"id": "c0d2b6a0-caad-4e90-b040-17cacdaf70bb", "id": "a2a18aa4-bd4c-4c2a-9286-e9d6c64f4812",
"alias": "review profile config", "alias": "review profile config",
"config": { "config": {
"update.profile.on.first.login": "missing" "update.profile.on.first.login": "missing"

View File

@ -73,7 +73,7 @@
"composites": { "composites": {
"realm": ["offline_access", "uma_authorization"], "realm": ["offline_access", "uma_authorization"],
"client": { "client": {
"account": ["delete-account", "view-profile", "manage-account"] "account": ["view-profile", "manage-account", "delete-account"]
} }
}, },
"clientRole": false, "clientRole": false,
@ -407,7 +407,7 @@
"otpPolicyLookAheadWindow": 1, "otpPolicyLookAheadWindow": 1,
"otpPolicyPeriod": 30, "otpPolicyPeriod": 30,
"otpPolicyCodeReusable": false, "otpPolicyCodeReusable": false,
"otpSupportedApplications": ["totpAppGoogleName", "totpAppFreeOTPName"], "otpSupportedApplications": ["totpAppFreeOTPName", "totpAppGoogleName"],
"webAuthnPolicyRpEntityName": "keycloak", "webAuthnPolicyRpEntityName": "keycloak",
"webAuthnPolicySignatureAlgorithms": ["ES256"], "webAuthnPolicySignatureAlgorithms": ["ES256"],
"webAuthnPolicyRpId": "", "webAuthnPolicyRpId": "",
@ -452,6 +452,40 @@
"disableableCredentialTypes": [], "disableableCredentialTypes": [],
"requiredActions": [], "requiredActions": [],
"realmRoles": ["default-roles-myrealm"], "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, "notBefore": 0,
"groups": [] "groups": []
} }
@ -517,8 +551,12 @@
"enabled": true, "enabled": true,
"alwaysDisplayInConsole": false, "alwaysDisplayInConsole": false,
"clientAuthenticatorType": "client-secret", "clientAuthenticatorType": "client-secret",
"redirectUris": ["/realms/myrealm/account/*"], "redirectUris": [
"webOrigins": [], "http://localhost*",
"http://127.0.0.1*",
"/realms/myrealm/account/*"
],
"webOrigins": ["*"],
"notBefore": 0, "notBefore": 0,
"bearerOnly": false, "bearerOnly": false,
"consentRequired": false, "consentRequired": false,
@ -653,7 +691,6 @@
"attributes": { "attributes": {
"oidc.ciba.grant.enabled": "false", "oidc.ciba.grant.enabled": "false",
"backchannel.logout.session.required": "true", "backchannel.logout.session.required": "true",
"login_theme": "keycloakify-starter",
"post.logout.redirect.uris": "+", "post.logout.redirect.uris": "+",
"display.on.consent.screen": "false", "display.on.consent.screen": "false",
"oauth2.device.authorization.grant.enabled": "false", "oauth2.device.authorization.grant.enabled": "false",
@ -714,8 +751,12 @@
"enabled": true, "enabled": true,
"alwaysDisplayInConsole": false, "alwaysDisplayInConsole": false,
"clientAuthenticatorType": "client-secret", "clientAuthenticatorType": "client-secret",
"redirectUris": ["/admin/myrealm/console/*"], "redirectUris": [
"webOrigins": ["+"], "http://localhost*",
"http://127.0.0.1*",
"/admin/myrealm/console/*"
],
"webOrigins": ["*"],
"notBefore": 0, "notBefore": 0,
"bearerOnly": false, "bearerOnly": false,
"consentRequired": false, "consentRequired": false,
@ -1294,11 +1335,11 @@
}, },
"smtpServer": {}, "smtpServer": {},
"loginTheme": "keycloakify-starter", "loginTheme": "keycloakify-starter",
"accountTheme": "keycloakify-starter", "accountTheme": "",
"adminTheme": "", "adminTheme": "",
"emailTheme": "", "emailTheme": "",
"eventsEnabled": false, "eventsEnabled": false,
"eventsListeners": ["jboss-logging"], "eventsListeners": ["keycloakify-logging", "jboss-logging"],
"enabledEventTypes": [], "enabledEventTypes": [],
"adminEventsEnabled": false, "adminEventsEnabled": false,
"adminEventsDetailsEnabled": false, "adminEventsDetailsEnabled": false,
@ -1314,14 +1355,14 @@
"subComponents": {}, "subComponents": {},
"config": { "config": {
"allowed-protocol-mapper-types": [ "allowed-protocol-mapper-types": [
"saml-user-property-mapper",
"oidc-sha256-pairwise-sub-mapper",
"oidc-usermodel-attribute-mapper",
"saml-user-attribute-mapper",
"oidc-address-mapper", "oidc-address-mapper",
"saml-role-list-mapper",
"oidc-full-name-mapper", "oidc-full-name-mapper",
"oidc-usermodel-property-mapper" "saml-role-list-mapper",
"oidc-sha256-pairwise-sub-mapper",
"oidc-usermodel-property-mapper",
"oidc-usermodel-attribute-mapper",
"saml-user-property-mapper",
"saml-user-attribute-mapper"
] ]
} }
}, },
@ -1370,14 +1411,14 @@
"subComponents": {}, "subComponents": {},
"config": { "config": {
"allowed-protocol-mapper-types": [ "allowed-protocol-mapper-types": [
"oidc-sha256-pairwise-sub-mapper",
"oidc-address-mapper",
"saml-role-list-mapper",
"saml-user-attribute-mapper", "saml-user-attribute-mapper",
"oidc-usermodel-attribute-mapper", "saml-role-list-mapper",
"oidc-sha256-pairwise-sub-mapper",
"oidc-full-name-mapper", "oidc-full-name-mapper",
"oidc-usermodel-property-mapper",
"oidc-address-mapper",
"saml-user-property-mapper", "saml-user-property-mapper",
"oidc-usermodel-property-mapper" "oidc-usermodel-attribute-mapper"
] ]
} }
}, },
@ -1495,7 +1536,7 @@
"defaultLocale": "en", "defaultLocale": "en",
"authenticationFlows": [ "authenticationFlows": [
{ {
"id": "19317acb-fe8e-4c79-82bc-90e159273075", "id": "c40791b4-4d59-4df2-bebd-2b71e793704f",
"alias": "Account verification options", "alias": "Account verification options",
"description": "Method with which to verity the existing account", "description": "Method with which to verity the existing account",
"providerId": "basic-flow", "providerId": "basic-flow",
@ -1521,7 +1562,7 @@
] ]
}, },
{ {
"id": "122857d2-33da-4086-8acb-cb0e303aaf1b", "id": "8813b6d1-8b88-4672-b29b-8420ce3f3975",
"alias": "Authentication Options", "alias": "Authentication Options",
"description": "Authentication options.", "description": "Authentication options.",
"providerId": "basic-flow", "providerId": "basic-flow",
@ -1555,7 +1596,7 @@
] ]
}, },
{ {
"id": "abf5dd35-4791-4268-a10c-5f4b6a06b84a", "id": "a9937c40-a1ee-4c57-adf7-ede0a9983953",
"alias": "Browser - Conditional OTP", "alias": "Browser - Conditional OTP",
"description": "Flow to determine if the OTP is required for the authentication", "description": "Flow to determine if the OTP is required for the authentication",
"providerId": "basic-flow", "providerId": "basic-flow",
@ -1581,7 +1622,7 @@
] ]
}, },
{ {
"id": "a18daeec-a33c-4a43-b014-10c84ec69b81", "id": "2d494b5a-eb73-40d0-94d3-a8d8024a7db4",
"alias": "Direct Grant - Conditional OTP", "alias": "Direct Grant - Conditional OTP",
"description": "Flow to determine if the OTP is required for the authentication", "description": "Flow to determine if the OTP is required for the authentication",
"providerId": "basic-flow", "providerId": "basic-flow",
@ -1607,7 +1648,7 @@
] ]
}, },
{ {
"id": "e9f032a7-32f7-457c-becf-011a1a35cc6a", "id": "2e977f5a-8110-412b-b704-3e15164dbb1b",
"alias": "First broker login - Conditional OTP", "alias": "First broker login - Conditional OTP",
"description": "Flow to determine if the OTP is required for the authentication", "description": "Flow to determine if the OTP is required for the authentication",
"providerId": "basic-flow", "providerId": "basic-flow",
@ -1633,7 +1674,7 @@
] ]
}, },
{ {
"id": "9db65b7c-98ca-4003-beea-611038831ffe", "id": "6f171b4b-8723-4e6d-bb1e-6b4293a7bb3f",
"alias": "Handle Existing Account", "alias": "Handle Existing Account",
"description": "Handle what to do if there is existing account with same email/username like authenticated identity provider", "description": "Handle what to do if there is existing account with same email/username like authenticated identity provider",
"providerId": "basic-flow", "providerId": "basic-flow",
@ -1659,7 +1700,7 @@
] ]
}, },
{ {
"id": "7bd0854c-d7ae-43d7-a1ae-7b759a34cb1d", "id": "2dbb7f27-757d-4178-8217-4a24fdb0163c",
"alias": "Reset - Conditional OTP", "alias": "Reset - Conditional OTP",
"description": "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.", "description": "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.",
"providerId": "basic-flow", "providerId": "basic-flow",
@ -1685,7 +1726,7 @@
] ]
}, },
{ {
"id": "2de1a450-fe98-443a-9c6c-d24d8a7ebcb3", "id": "7295aaf7-acf4-4b78-8186-d2415ea4ede0",
"alias": "User creation or linking", "alias": "User creation or linking",
"description": "Flow for the existing/non-existing user alternatives", "description": "Flow for the existing/non-existing user alternatives",
"providerId": "basic-flow", "providerId": "basic-flow",
@ -1712,7 +1753,7 @@
] ]
}, },
{ {
"id": "7b3efad5-4b7d-4385-a41c-fecc73afdcc4", "id": "e0d34d7c-7bbb-4847-8864-fbd97a1f3e89",
"alias": "Verify Existing Account by Re-authentication", "alias": "Verify Existing Account by Re-authentication",
"description": "Reauthentication of existing account", "description": "Reauthentication of existing account",
"providerId": "basic-flow", "providerId": "basic-flow",
@ -1738,7 +1779,7 @@
] ]
}, },
{ {
"id": "de93418e-8f28-4099-b15e-ad36ec194796", "id": "5f3d0fb0-d95e-4841-89d3-a27d0cdbbcb4",
"alias": "browser", "alias": "browser",
"description": "browser based authentication", "description": "browser based authentication",
"providerId": "basic-flow", "providerId": "basic-flow",
@ -1780,7 +1821,7 @@
] ]
}, },
{ {
"id": "0dd3345c-6e82-4c3a-a39a-d49ae1f5c409", "id": "c246380d-af25-4151-ab19-1f1e5b553008",
"alias": "clients", "alias": "clients",
"description": "Base authentication for clients", "description": "Base authentication for clients",
"providerId": "client-flow", "providerId": "client-flow",
@ -1822,7 +1863,7 @@
] ]
}, },
{ {
"id": "87fb4dd0-5326-47a1-b670-982f4872ff89", "id": "abacf398-0f1f-4f28-a310-8d306d588048",
"alias": "direct grant", "alias": "direct grant",
"description": "OpenID Connect Resource Owner Grant", "description": "OpenID Connect Resource Owner Grant",
"providerId": "basic-flow", "providerId": "basic-flow",
@ -1856,7 +1897,7 @@
] ]
}, },
{ {
"id": "344723b3-4ab1-4999-abdd-32398e82327b", "id": "a0f87683-619a-44d4-8b4f-4b053bba2346",
"alias": "docker auth", "alias": "docker auth",
"description": "Used by Docker clients to authenticate against the IDP", "description": "Used by Docker clients to authenticate against the IDP",
"providerId": "basic-flow", "providerId": "basic-flow",
@ -1874,7 +1915,7 @@
] ]
}, },
{ {
"id": "f3341938-caf9-4c8a-9cd5-eb34609809ab", "id": "e8820c7c-22a7-4618-beb7-3e09be72c00c",
"alias": "first broker login", "alias": "first broker login",
"description": "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account", "description": "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account",
"providerId": "basic-flow", "providerId": "basic-flow",
@ -1901,7 +1942,7 @@
] ]
}, },
{ {
"id": "ba7b7357-e324-4b71-9bda-f8512a760e02", "id": "cac00c38-ee44-44c9-b95e-cc755bab36ef",
"alias": "forms", "alias": "forms",
"description": "Username, password, otp and other auth forms.", "description": "Username, password, otp and other auth forms.",
"providerId": "basic-flow", "providerId": "basic-flow",
@ -1927,7 +1968,7 @@
] ]
}, },
{ {
"id": "134971e6-bf63-432c-806e-74ca4fb09963", "id": "688cde36-507e-4a68-afdf-18ec4ad626a7",
"alias": "http challenge", "alias": "http challenge",
"description": "An authentication flow based on challenge-response HTTP Authentication Schemes", "description": "An authentication flow based on challenge-response HTTP Authentication Schemes",
"providerId": "basic-flow", "providerId": "basic-flow",
@ -1953,7 +1994,7 @@
] ]
}, },
{ {
"id": "6ea9e2cf-5684-4c65-8c07-930d1cbb0b46", "id": "e058697c-f450-4f14-ae64-04e9299fa24f",
"alias": "registration", "alias": "registration",
"description": "registration flow", "description": "registration flow",
"providerId": "basic-flow", "providerId": "basic-flow",
@ -1972,7 +2013,7 @@
] ]
}, },
{ {
"id": "67e3c8c7-1b5e-4119-84a2-e90876293150", "id": "ad768088-32c9-4979-90dd-61bf111fd72e",
"alias": "registration form", "alias": "registration form",
"description": "registration form", "description": "registration form",
"providerId": "form-flow", "providerId": "form-flow",
@ -2014,7 +2055,7 @@
] ]
}, },
{ {
"id": "fc6d48ec-a1f1-41b1-9310-54f58861d5aa", "id": "47d4b090-f965-4588-b5bc-029ccb59876f",
"alias": "reset credentials", "alias": "reset credentials",
"description": "Reset credentials for a user if they forgot their password or something", "description": "Reset credentials for a user if they forgot their password or something",
"providerId": "basic-flow", "providerId": "basic-flow",
@ -2056,7 +2097,7 @@
] ]
}, },
{ {
"id": "80b1d464-c2ec-4eb1-82e8-32cbede779a8", "id": "1f68feec-7f99-4c49-afe6-45d46684ca21",
"alias": "saml ecp", "alias": "saml ecp",
"description": "SAML ECP Profile Authentication Flow", "description": "SAML ECP Profile Authentication Flow",
"providerId": "basic-flow", "providerId": "basic-flow",
@ -2076,14 +2117,14 @@
], ],
"authenticatorConfig": [ "authenticatorConfig": [
{ {
"id": "86b1d5fa-450c-40d8-899c-725861ac39fc", "id": "bd7365c7-842b-4bc6-a4ca-498cf025c210",
"alias": "create unique user config", "alias": "create unique user config",
"config": { "config": {
"require.password.update.after.registration": "false" "require.password.update.after.registration": "false"
} }
}, },
{ {
"id": "ea724f02-029a-493d-b4d3-08972be21cfb", "id": "b929192d-f650-4a09-9701-3d3216547552",
"alias": "review profile config", "alias": "review profile config",
"config": { "config": {
"update.profile.on.first.login": "missing" "update.profile.on.first.login": "missing"

View File

@ -73,7 +73,7 @@
"composites": { "composites": {
"realm": ["offline_access", "uma_authorization"], "realm": ["offline_access", "uma_authorization"],
"client": { "client": {
"account": ["delete-account", "view-profile", "manage-account"] "account": ["view-profile", "manage-account", "delete-account"]
} }
}, },
"clientRole": false, "clientRole": false,
@ -456,6 +456,40 @@
"disableableCredentialTypes": [], "disableableCredentialTypes": [],
"requiredActions": [], "requiredActions": [],
"realmRoles": ["default-roles-myrealm"], "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, "notBefore": 0,
"groups": [] "groups": []
} }
@ -521,8 +555,12 @@
"enabled": true, "enabled": true,
"alwaysDisplayInConsole": false, "alwaysDisplayInConsole": false,
"clientAuthenticatorType": "client-secret", "clientAuthenticatorType": "client-secret",
"redirectUris": ["/realms/myrealm/account/*"], "redirectUris": [
"webOrigins": [], "http://localhost*",
"http://127.0.0.1*",
"/realms/myrealm/account/*"
],
"webOrigins": ["*"],
"notBefore": 0, "notBefore": 0,
"bearerOnly": false, "bearerOnly": false,
"consentRequired": false, "consentRequired": false,
@ -657,7 +695,6 @@
"attributes": { "attributes": {
"oidc.ciba.grant.enabled": "false", "oidc.ciba.grant.enabled": "false",
"backchannel.logout.session.required": "true", "backchannel.logout.session.required": "true",
"login_theme": "keycloakify-starter",
"post.logout.redirect.uris": "+", "post.logout.redirect.uris": "+",
"display.on.consent.screen": "false", "display.on.consent.screen": "false",
"oauth2.device.authorization.grant.enabled": "false", "oauth2.device.authorization.grant.enabled": "false",
@ -718,8 +755,12 @@
"enabled": true, "enabled": true,
"alwaysDisplayInConsole": false, "alwaysDisplayInConsole": false,
"clientAuthenticatorType": "client-secret", "clientAuthenticatorType": "client-secret",
"redirectUris": ["/admin/myrealm/console/*"], "redirectUris": [
"webOrigins": ["+"], "http://localhost*",
"http://127.0.0.1*",
"/admin/myrealm/console/*"
],
"webOrigins": ["*"],
"notBefore": 0, "notBefore": 0,
"bearerOnly": false, "bearerOnly": false,
"consentRequired": false, "consentRequired": false,
@ -1298,11 +1339,11 @@
}, },
"smtpServer": {}, "smtpServer": {},
"loginTheme": "keycloakify-starter", "loginTheme": "keycloakify-starter",
"accountTheme": "keycloakify-starter", "accountTheme": "",
"adminTheme": "", "adminTheme": "",
"emailTheme": "", "emailTheme": "",
"eventsEnabled": false, "eventsEnabled": false,
"eventsListeners": ["jboss-logging"], "eventsListeners": ["keycloakify-logging", "jboss-logging"],
"enabledEventTypes": [], "enabledEventTypes": [],
"adminEventsEnabled": false, "adminEventsEnabled": false,
"adminEventsDetailsEnabled": false, "adminEventsDetailsEnabled": false,
@ -1318,13 +1359,13 @@
"subComponents": {}, "subComponents": {},
"config": { "config": {
"allowed-protocol-mapper-types": [ "allowed-protocol-mapper-types": [
"oidc-usermodel-property-mapper", "saml-user-attribute-mapper",
"saml-user-property-mapper",
"oidc-sha256-pairwise-sub-mapper",
"saml-role-list-mapper",
"oidc-usermodel-attribute-mapper", "oidc-usermodel-attribute-mapper",
"oidc-full-name-mapper", "oidc-full-name-mapper",
"saml-user-property-mapper", "oidc-usermodel-property-mapper",
"saml-role-list-mapper",
"saml-user-attribute-mapper",
"oidc-sha256-pairwise-sub-mapper",
"oidc-address-mapper" "oidc-address-mapper"
] ]
} }
@ -1374,14 +1415,14 @@
"subComponents": {}, "subComponents": {},
"config": { "config": {
"allowed-protocol-mapper-types": [ "allowed-protocol-mapper-types": [
"oidc-sha256-pairwise-sub-mapper",
"oidc-address-mapper", "oidc-address-mapper",
"oidc-full-name-mapper",
"oidc-usermodel-property-mapper", "oidc-usermodel-property-mapper",
"oidc-usermodel-attribute-mapper", "oidc-usermodel-attribute-mapper",
"saml-user-attribute-mapper", "oidc-full-name-mapper",
"oidc-sha256-pairwise-sub-mapper",
"saml-user-property-mapper",
"saml-role-list-mapper", "saml-role-list-mapper",
"saml-user-property-mapper" "saml-user-attribute-mapper"
] ]
} }
}, },

View File

@ -55,7 +55,7 @@
"composites": { "composites": {
"realm": ["offline_access", "uma_authorization"], "realm": ["offline_access", "uma_authorization"],
"client": { "client": {
"account": ["delete-account", "view-profile", "manage-account"] "account": ["view-profile", "delete-account", "manage-account"]
} }
}, },
"clientRole": false, "clientRole": false,
@ -459,6 +459,40 @@
"disableableCredentialTypes": [], "disableableCredentialTypes": [],
"requiredActions": [], "requiredActions": [],
"realmRoles": ["default-roles-myrealm"], "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, "notBefore": 0,
"groups": [] "groups": []
} }
@ -505,7 +539,6 @@
"attributes": { "attributes": {
"oidc.ciba.grant.enabled": "false", "oidc.ciba.grant.enabled": "false",
"backchannel.logout.session.required": "true", "backchannel.logout.session.required": "true",
"login_theme": "keycloakify-starter",
"post.logout.redirect.uris": "+", "post.logout.redirect.uris": "+",
"oauth2.device.authorization.grant.enabled": "false", "oauth2.device.authorization.grant.enabled": "false",
"display.on.consent.screen": "false", "display.on.consent.screen": "false",
@ -532,8 +565,12 @@
"enabled": true, "enabled": true,
"alwaysDisplayInConsole": false, "alwaysDisplayInConsole": false,
"clientAuthenticatorType": "client-secret", "clientAuthenticatorType": "client-secret",
"redirectUris": ["/realms/myrealm/account/*"], "redirectUris": [
"webOrigins": [], "http://localhost*",
"http://127.0.0.1*",
"/realms/myrealm/account/*"
],
"webOrigins": ["*"],
"notBefore": 0, "notBefore": 0,
"bearerOnly": false, "bearerOnly": false,
"consentRequired": false, "consentRequired": false,
@ -649,7 +686,11 @@
"enabled": true, "enabled": true,
"alwaysDisplayInConsole": false, "alwaysDisplayInConsole": false,
"clientAuthenticatorType": "client-secret", "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": ["*"], "webOrigins": ["*"],
"notBefore": 0, "notBefore": 0,
"bearerOnly": false, "bearerOnly": false,
@ -664,8 +705,7 @@
"attributes": { "attributes": {
"oidc.ciba.grant.enabled": "false", "oidc.ciba.grant.enabled": "false",
"backchannel.logout.session.required": "true", "backchannel.logout.session.required": "true",
"login_theme": "keycloakify-starter", "post.logout.redirect.uris": "+",
"post.logout.redirect.uris": "https://my-theme.keycloakify.dev/*",
"oauth2.device.authorization.grant.enabled": "false", "oauth2.device.authorization.grant.enabled": "false",
"display.on.consent.screen": "false", "display.on.consent.screen": "false",
"backchannel.logout.revoke.offline.tokens": "false" "backchannel.logout.revoke.offline.tokens": "false"
@ -725,8 +765,12 @@
"enabled": true, "enabled": true,
"alwaysDisplayInConsole": false, "alwaysDisplayInConsole": false,
"clientAuthenticatorType": "client-secret", "clientAuthenticatorType": "client-secret",
"redirectUris": ["/admin/myrealm/console/*"], "redirectUris": [
"webOrigins": ["+"], "http://localhost*",
"http://127.0.0.1*",
"/admin/myrealm/console/*"
],
"webOrigins": ["*"],
"notBefore": 0, "notBefore": 0,
"bearerOnly": false, "bearerOnly": false,
"consentRequired": false, "consentRequired": false,
@ -1336,12 +1380,12 @@
"strictTransportSecurity": "max-age=31536000; includeSubDomains" "strictTransportSecurity": "max-age=31536000; includeSubDomains"
}, },
"smtpServer": {}, "smtpServer": {},
"loginTheme": "", "loginTheme": "keycloakify-starter",
"accountTheme": "keycloakify-starter", "accountTheme": "",
"adminTheme": "", "adminTheme": "",
"emailTheme": "", "emailTheme": "",
"eventsEnabled": false, "eventsEnabled": false,
"eventsListeners": ["jboss-logging"], "eventsListeners": ["keycloakify-logging", "jboss-logging"],
"enabledEventTypes": [], "enabledEventTypes": [],
"adminEventsEnabled": false, "adminEventsEnabled": false,
"adminEventsDetailsEnabled": false, "adminEventsDetailsEnabled": false,
@ -1357,13 +1401,13 @@
"subComponents": {}, "subComponents": {},
"config": { "config": {
"allowed-protocol-mapper-types": [ "allowed-protocol-mapper-types": [
"oidc-sha256-pairwise-sub-mapper",
"saml-user-property-mapper",
"oidc-address-mapper",
"oidc-full-name-mapper",
"saml-role-list-mapper", "saml-role-list-mapper",
"oidc-sha256-pairwise-sub-mapper",
"oidc-usermodel-attribute-mapper", "oidc-usermodel-attribute-mapper",
"saml-user-attribute-mapper", "saml-user-attribute-mapper",
"oidc-full-name-mapper",
"oidc-address-mapper",
"saml-user-property-mapper",
"oidc-usermodel-property-mapper" "oidc-usermodel-property-mapper"
] ]
} }
@ -1433,13 +1477,13 @@
"subComponents": {}, "subComponents": {},
"config": { "config": {
"allowed-protocol-mapper-types": [ "allowed-protocol-mapper-types": [
"saml-role-list-mapper",
"oidc-full-name-mapper",
"oidc-address-mapper",
"saml-user-attribute-mapper", "saml-user-attribute-mapper",
"oidc-sha256-pairwise-sub-mapper", "saml-role-list-mapper",
"oidc-usermodel-attribute-mapper", "oidc-usermodel-attribute-mapper",
"oidc-address-mapper",
"saml-user-property-mapper", "saml-user-property-mapper",
"oidc-full-name-mapper",
"oidc-sha256-pairwise-sub-mapper",
"oidc-usermodel-property-mapper" "oidc-usermodel-property-mapper"
] ]
} }

View File

@ -468,6 +468,40 @@
"disableableCredentialTypes": [], "disableableCredentialTypes": [],
"requiredActions": [], "requiredActions": [],
"realmRoles": ["default-roles-myrealm"], "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, "notBefore": 0,
"groups": [] "groups": []
} }
@ -514,7 +548,6 @@
"attributes": { "attributes": {
"oidc.ciba.grant.enabled": "false", "oidc.ciba.grant.enabled": "false",
"backchannel.logout.session.required": "true", "backchannel.logout.session.required": "true",
"login_theme": "keycloakify-starter",
"post.logout.redirect.uris": "+", "post.logout.redirect.uris": "+",
"oauth2.device.authorization.grant.enabled": "false", "oauth2.device.authorization.grant.enabled": "false",
"display.on.consent.screen": "false", "display.on.consent.screen": "false",
@ -541,8 +574,12 @@
"enabled": true, "enabled": true,
"alwaysDisplayInConsole": false, "alwaysDisplayInConsole": false,
"clientAuthenticatorType": "client-secret", "clientAuthenticatorType": "client-secret",
"redirectUris": ["/realms/myrealm/account/*"], "redirectUris": [
"webOrigins": [], "http://localhost*",
"http://127.0.0.1*",
"/realms/myrealm/account/*"
],
"webOrigins": ["*"],
"notBefore": 0, "notBefore": 0,
"bearerOnly": false, "bearerOnly": false,
"consentRequired": false, "consentRequired": false,
@ -658,7 +695,11 @@
"enabled": true, "enabled": true,
"alwaysDisplayInConsole": false, "alwaysDisplayInConsole": false,
"clientAuthenticatorType": "client-secret", "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": ["*"], "webOrigins": ["*"],
"notBefore": 0, "notBefore": 0,
"bearerOnly": false, "bearerOnly": false,
@ -673,8 +714,7 @@
"attributes": { "attributes": {
"oidc.ciba.grant.enabled": "false", "oidc.ciba.grant.enabled": "false",
"backchannel.logout.session.required": "true", "backchannel.logout.session.required": "true",
"login_theme": "keycloakify-starter", "post.logout.redirect.uris": "+",
"post.logout.redirect.uris": "https://my-theme.keycloakify.dev/*##http://localhost*",
"oauth2.device.authorization.grant.enabled": "false", "oauth2.device.authorization.grant.enabled": "false",
"display.on.consent.screen": "false", "display.on.consent.screen": "false",
"backchannel.logout.revoke.offline.tokens": "false" "backchannel.logout.revoke.offline.tokens": "false"
@ -840,8 +880,12 @@
"enabled": true, "enabled": true,
"alwaysDisplayInConsole": false, "alwaysDisplayInConsole": false,
"clientAuthenticatorType": "client-secret", "clientAuthenticatorType": "client-secret",
"redirectUris": ["/admin/myrealm/console/*"], "redirectUris": [
"webOrigins": ["+"], "http://localhost*",
"http://127.0.0.1*",
"/admin/myrealm/console/*"
],
"webOrigins": ["*"],
"notBefore": 0, "notBefore": 0,
"bearerOnly": false, "bearerOnly": false,
"consentRequired": false, "consentRequired": false,
@ -1451,12 +1495,12 @@
"strictTransportSecurity": "max-age=31536000; includeSubDomains" "strictTransportSecurity": "max-age=31536000; includeSubDomains"
}, },
"smtpServer": {}, "smtpServer": {},
"loginTheme": "keycloak", "loginTheme": "keycloakify-starter",
"accountTheme": "keycloakify-starter", "accountTheme": "",
"adminTheme": "", "adminTheme": "",
"emailTheme": "", "emailTheme": "",
"eventsEnabled": false, "eventsEnabled": false,
"eventsListeners": ["jboss-logging"], "eventsListeners": ["keycloakify-logging", "jboss-logging"],
"enabledEventTypes": [], "enabledEventTypes": [],
"adminEventsEnabled": false, "adminEventsEnabled": false,
"adminEventsDetailsEnabled": false, "adminEventsDetailsEnabled": false,
@ -1504,11 +1548,11 @@
"saml-role-list-mapper", "saml-role-list-mapper",
"oidc-address-mapper", "oidc-address-mapper",
"oidc-usermodel-property-mapper", "oidc-usermodel-property-mapper",
"oidc-sha256-pairwise-sub-mapper",
"saml-user-attribute-mapper", "saml-user-attribute-mapper",
"saml-user-property-mapper",
"oidc-sha256-pairwise-sub-mapper",
"oidc-usermodel-attribute-mapper", "oidc-usermodel-attribute-mapper",
"oidc-full-name-mapper", "oidc-full-name-mapper"
"saml-user-property-mapper"
] ]
} }
}, },
@ -1540,14 +1584,14 @@
"subComponents": {}, "subComponents": {},
"config": { "config": {
"allowed-protocol-mapper-types": [ "allowed-protocol-mapper-types": [
"oidc-sha256-pairwise-sub-mapper",
"oidc-usermodel-property-mapper",
"oidc-full-name-mapper", "oidc-full-name-mapper",
"oidc-usermodel-property-mapper",
"saml-user-attribute-mapper",
"oidc-sha256-pairwise-sub-mapper",
"saml-role-list-mapper",
"oidc-address-mapper", "oidc-address-mapper",
"oidc-usermodel-attribute-mapper", "oidc-usermodel-attribute-mapper",
"saml-user-property-mapper", "saml-user-property-mapper"
"saml-role-list-mapper",
"saml-user-attribute-mapper"
] ]
} }
}, },

View File

@ -538,10 +538,10 @@
"emailVerified": true, "emailVerified": true,
"attributes": { "attributes": {
"additional_emails": ["test.user@protonmail.com", "testuser@hotmail.com"], "additional_emails": ["test.user@protonmail.com", "testuser@hotmail.com"],
"gender": ["prefer_not_to_say"],
"favorite_pet": ["cats"], "favorite_pet": ["cats"],
"favourite_pet": ["cat"], "gender": ["prefer_not_to_say"],
"bio": ["Hello I'm Test User and I do not exist."], "bio": ["Hello I'm Test User and I do not exist."],
"favourite_pet": ["cat"],
"phone_number": ["1111111111"], "phone_number": ["1111111111"],
"locale": ["en"], "locale": ["en"],
"favorite_media": ["movies", "series"] "favorite_media": ["movies", "series"]
@ -562,6 +562,40 @@
"disableableCredentialTypes": [], "disableableCredentialTypes": [],
"requiredActions": [], "requiredActions": [],
"realmRoles": ["default-roles-myrealm"], "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, "notBefore": 0,
"groups": [] "groups": []
} }
@ -628,14 +662,16 @@
"id": "d8f14dc4-5f0f-4a1d-8c0b-cfe78ee55cb3", "id": "d8f14dc4-5f0f-4a1d-8c0b-cfe78ee55cb3",
"clientId": "account-console", "clientId": "account-console",
"name": "${client_account-console}", "name": "${client_account-console}",
"description": "",
"rootUrl": "${authBaseUrl}", "rootUrl": "${authBaseUrl}",
"adminUrl": "",
"baseUrl": "/realms/myrealm/account/", "baseUrl": "/realms/myrealm/account/",
"surrogateAuthRequired": false, "surrogateAuthRequired": false,
"enabled": true, "enabled": true,
"alwaysDisplayInConsole": false, "alwaysDisplayInConsole": false,
"clientAuthenticatorType": "client-secret", "clientAuthenticatorType": "client-secret",
"redirectUris": ["/realms/myrealm/account/*"], "redirectUris": ["http://localhost*", "http://127.0.0.1*", "*"],
"webOrigins": [], "webOrigins": ["*"],
"notBefore": 0, "notBefore": 0,
"bearerOnly": false, "bearerOnly": false,
"consentRequired": false, "consentRequired": false,
@ -647,8 +683,13 @@
"frontchannelLogout": false, "frontchannelLogout": false,
"protocol": "openid-connect", "protocol": "openid-connect",
"attributes": { "attributes": {
"oidc.ciba.grant.enabled": "false",
"backchannel.logout.session.required": "true",
"post.logout.redirect.uris": "+", "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": {}, "authenticationFlowBindingOverrides": {},
"fullScopeAllowed": false, "fullScopeAllowed": false,
@ -791,8 +832,7 @@
"attributes": { "attributes": {
"oidc.ciba.grant.enabled": "false", "oidc.ciba.grant.enabled": "false",
"backchannel.logout.session.required": "true", "backchannel.logout.session.required": "true",
"login_theme": "keycloakify-starter", "post.logout.redirect.uris": "+",
"post.logout.redirect.uris": "https://my-theme.keycloakify.dev/*##http://localhost*##http://127.0.0.1*",
"oauth2.device.authorization.grant.enabled": "false", "oauth2.device.authorization.grant.enabled": "false",
"display.on.consent.screen": "false", "display.on.consent.screen": "false",
"backchannel.logout.revoke.offline.tokens": "false" "backchannel.logout.revoke.offline.tokens": "false"
@ -885,8 +925,12 @@
"enabled": true, "enabled": true,
"alwaysDisplayInConsole": false, "alwaysDisplayInConsole": false,
"clientAuthenticatorType": "client-secret", "clientAuthenticatorType": "client-secret",
"redirectUris": ["/admin/myrealm/console/*"], "redirectUris": [
"webOrigins": ["+"], "http://localhost*",
"http://127.0.0.1*",
"/admin/myrealm/console/*"
],
"webOrigins": ["*"],
"notBefore": 0, "notBefore": 0,
"bearerOnly": false, "bearerOnly": false,
"consentRequired": false, "consentRequired": false,
@ -1544,11 +1588,11 @@
}, },
"smtpServer": {}, "smtpServer": {},
"loginTheme": "keycloakify-starter", "loginTheme": "keycloakify-starter",
"accountTheme": "keycloakify-starter", "accountTheme": "",
"adminTheme": "", "adminTheme": "",
"emailTheme": "", "emailTheme": "",
"eventsEnabled": false, "eventsEnabled": false,
"eventsListeners": ["jboss-logging"], "eventsListeners": ["keycloakify-logging", "jboss-logging"],
"enabledEventTypes": [], "enabledEventTypes": [],
"adminEventsEnabled": false, "adminEventsEnabled": false,
"adminEventsDetailsEnabled": false, "adminEventsDetailsEnabled": false,
@ -1574,14 +1618,14 @@
"subComponents": {}, "subComponents": {},
"config": { "config": {
"allowed-protocol-mapper-types": [ "allowed-protocol-mapper-types": [
"saml-role-list-mapper",
"oidc-full-name-mapper", "oidc-full-name-mapper",
"saml-user-property-mapper",
"saml-user-attribute-mapper",
"oidc-usermodel-attribute-mapper", "oidc-usermodel-attribute-mapper",
"oidc-address-mapper", "oidc-address-mapper",
"saml-user-attribute-mapper", "oidc-sha256-pairwise-sub-mapper",
"oidc-usermodel-property-mapper", "oidc-usermodel-property-mapper"
"saml-user-property-mapper",
"saml-role-list-mapper",
"oidc-sha256-pairwise-sub-mapper"
] ]
} }
}, },
@ -1611,14 +1655,14 @@
"subComponents": {}, "subComponents": {},
"config": { "config": {
"allowed-protocol-mapper-types": [ "allowed-protocol-mapper-types": [
"oidc-sha256-pairwise-sub-mapper",
"oidc-usermodel-property-mapper",
"oidc-address-mapper", "oidc-address-mapper",
"oidc-usermodel-attribute-mapper",
"oidc-full-name-mapper",
"saml-user-attribute-mapper", "saml-user-attribute-mapper",
"oidc-full-name-mapper",
"saml-role-list-mapper",
"oidc-sha256-pairwise-sub-mapper",
"oidc-usermodel-attribute-mapper",
"saml-user-property-mapper", "saml-user-property-mapper",
"saml-role-list-mapper" "oidc-usermodel-property-mapper"
] ]
} }
}, },
@ -1672,11 +1716,10 @@
"subComponents": {}, "subComponents": {},
"config": { "config": {
"privateKey": [ "privateKey": [
"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"
], ],
"keyUse": ["SIG"],
"certificate": [ "certificate": [
"MIICnTCCAYUCBgGQBsyplzANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdteXJlYWxtMB4XDTI0MDYxMTEwMTQ1NFoXDTM0MDYxMTEwMTYzNFowEjEQMA4GA1UEAwwHbXlyZWFsbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALGFFs1X2THessKQZiyWHhdVPTIHf898Vr5dWKu+qETz36XKkIrTfnJLVqrtIT3qwlUpdpbcFwzK1MBc7MBvTIjPYW/gSxJXe5GImJHBTbd5QrZpnkUBGnuycFops3jVfekJc9LR2PH/WYdH3zw7C0zdRxgHkSSYSO5jUJuqgiTTAn1QPzuUlrprCJ9ly50jIkNOQAAlRzi1KuqwGdswgmmJl05iZLkjm/Htuld9Nz+petw0nuR0jVw3SNNLY3xRX9+6vO9M4b37KLa448dETIo8vSo4/DmRfqRR6Q0WhhIvLaUMWQ5wk6H04ngf1ad0D/mk5bwMg90NWZycRkAi8oMCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAVS+gJshIFX6cmBGI8UaOOI/9+XFb4Gi+DHaHVWVVHTd14MoqNK1bmmyTHbGIZbvK8UqgJ9+FhJX1ejx17d4KBzkZI3tYvPnVacHvaw1CIUMZ1Ini6u+UGUTnIlnQzCG0pcTKjOZXf3ih1B2CKdwyC7XeXyEJHicAIG7XfzYfYd9DYHvA+h6hrXaQcNJMW7WFNbtb3fJhtlv5P1Iw+ZEGdj15ukMI0bg2OEQA0F3jIw6QZpigSAGuai3HOY6OgoPO82d7TyTYlNhuwyutWr9izl6QMc2R7BmRfW9XQj4ICR2VWJiL9nqz+SOyqnjQiOObuw8Vywb8c36R1Ym1aaGjOw==" "MIICnTCCAYUCBgGTy2TGBjANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdteXJlYWxtMB4XDTI0MTIxNTE3MzQ1OVoXDTM0MTIxNTE3MzYzOVowEjEQMA4GA1UEAwwHbXlyZWFsbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALKPPaqby4X/QyHAm9iQG8SLUS0iLz/zQmYTHQBMUx8q4TOQdu2UOjGe3SUG5zLu0Ce8ogpbA9V5wze1pY9O7GNgLN5WN7/ZIzgwXgA2JT7jdZu/31xR196n0U/j8FUmgITMpJqr1qfOlE4O7e6zY1QZ1zz1yLZkSyBfo2VDFkHbsDc56+iYeeGXSMBMuIb19W6TcNJz5eCl4jotnpb4Pq5OwYRcsBsEPx1AhpK3KtF0v2jTstQoZqg27avNe5mKVXrXgg3vMT9zD62f7Ni4hLeCKdXDaNHWPmuCcDs8C+NodxmkfvNIit6WTaPjK9CgTmPRl73VfzL3Dxy0O1bo1BkCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAggzxmYvHqUaCPLxxSidLQMgpu1pTozg3rTq8dcxhcHINI//A/z7qQyDA/QQN5cuSpYvdt2MRWoNop+uRNKqSr3C8aRErbY0j4acl7yG/ghNfQUZ9KxDBxKrd0HLFUibdZobg10+Ih/qXo3Mi2VtkqyZQRl/iy0O3ITgqb7YJUEx5tuEWyGbn+SerFvqZNcmsLziOJefm1n4uqroHgIfmgY6Deh+wZK0DwO3WZ6ThjhMp5GFi1oNeZ9xoExNEXrYp07b2xTQFF57oypc7prf733lqGjPRLfoVJP6qcsjvAlOA7f8TG9sKwGuRsPfadYY9PxmdHxl2k7PHDJeDhA7VdQ=="
], ],
"priority": ["100"] "priority": ["100"]
} }
@ -1688,11 +1731,10 @@
"subComponents": {}, "subComponents": {},
"config": { "config": {
"privateKey": [ "privateKey": [
"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"
], ],
"keyUse": ["ENC"],
"certificate": [ "certificate": [
"MIICnTCCAYUCBgGQBsyq0jANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdteXJlYWxtMB4XDTI0MDYxMTEwMTQ1NFoXDTM0MDYxMTEwMTYzNFowEjEQMA4GA1UEAwwHbXlyZWFsbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJELXnx8vNnvHeXVVjdNC5xiOWJgTkyodLYKmslUY6ix+rtzX4CVVSHtLRZ8Wu1pGv8kh659SkrFudnM2eLj1y1KOLFwZEMcshV3mYljfB/vVolEPtIXn2ReHrOWT4WNlaXjCvVwJ6YQt+tixS1cnF7pZKSXEPuA4yPfU6OCy8iZo0L7FWie2pqFSecXetnlL/g+39vo8Lmk8IxhN/jFmQA141cbFGNaibhixipy5DTcSTTA4b5ymMPmmt2eFfdH0FcozdVaJYMNw3QOfaOMK1hMvtsR3Oo9hEovPnDOtH1s2VPbKdl5MiaAp1e55sJO6WZA8t87wJHjls669zlnkd8CAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAD9wQ+CJ0FRgls3JrUzxwHLgrJ3Yo4+mDFpSe1rh2XYK5FEIWDWSqxaXI3p0cOZq75RZmI2xV8oaiJMUz9WMZkbNe/KtGRzHY1N9AZooicGIsnFu1t++b8taFxxpvKWZgnbOum2PZlfcNiXL0QeMv0wwhfn9zKA9W1DRcqYGbIamoyVlumvbNyIjqXJKwGYIOW6GNt7v3wJl5AJw8qAU/O/DQwWwmzcnFGNRxRxAwI7we8EiQ5JlG0Wi+nyAQn74o3RhNr3zsY0ndmFx9bFV4BBo2AiYGozCDOCCG5HvrmoDbrm//wmGRv0tCwueBzWHL2mhtbZ6sGWmMWfiTJ2HPpg==" "MIICnTCCAYUCBgGTy2TG/jANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdteXJlYWxtMB4XDTI0MTIxNTE3MzQ1OVoXDTM0MTIxNTE3MzYzOVowEjEQMA4GA1UEAwwHbXlyZWFsbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMaBL57/mBwqlFgDRrpLuUGLHAGclBNFh0pXAM2dG0LoD6MOt6YkLLsT89HKTyLi2m+mnbLF1KihBYXk9R9AarZk/S/+4jigejEbiJzeGkc80x/atNkyj5HKUgMwB0xnLCQTDGsJW5yHTLufpnrzD8anlVcb65yhtu0vB91uY6iJEMPG6OuOh695eQ8av42O7JXEl113uGXXn3gr50qnmvn8uXPq/6+eOp1+BUxopJbuii0Gg3PUnxnHK2Q5pY2Ow19ZB65ryHS0oXLrpEx3NIYtqm4DHFLXv+/7L0iS1FjmTmysXrXeMtTgOPb9+sazKhocBg3cK1n9FbjATfHhzgkCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAdUIlJ91E0UkFS45AByjFufRnQbAi1smnHkC3WSN39bhcFT7Hgip97qtABODR58zVHSTS0XcMiL4mMObH3Vyz9J3gmwWZnbokAuo9tYeyrhPh/gqXv3LGtGhTpWlUJ7JEJxH7RVI4UZZyG6Y6FR+3zwiZ0j1p3QsZclfcNmacoi/Ano+4TfloOnY4k8yP7G6LWUTJHpcRNWVVozM3RwekYgpJRAtXDoYfm9p2hRQ090e7NvbblSuVQ/FXhUn4g0wz91WdCWlwXZfvNaRjbynPCHejJpszqiyjPkx3aRKTWqer0ZocKNmY8+RO27XIsXmwOYcjdpX2TCFDv6O+VLfNdw=="
], ],
"priority": ["100"], "priority": ["100"],
"algorithm": ["RSA-OAEP"] "algorithm": ["RSA-OAEP"]
@ -1704,8 +1746,8 @@
"providerId": "aes-generated", "providerId": "aes-generated",
"subComponents": {}, "subComponents": {},
"config": { "config": {
"kid": ["1c1d0c8a-6f0b-48a9-a66f-488489137d85"], "kid": ["95db7eb8-b57b-475e-90cd-58841a9388d3"],
"secret": ["N4wzheVYYBWxFn9VGWTPQQ"], "secret": ["dp6bv53YrC2PZuJCxa3aNA"],
"priority": ["100"] "priority": ["100"]
} }
}, },
@ -1715,9 +1757,9 @@
"providerId": "hmac-generated", "providerId": "hmac-generated",
"subComponents": {}, "subComponents": {},
"config": { "config": {
"kid": ["ce43821c-6cfd-4ea9-a29a-a724a37e6955"], "kid": ["d0254883-059e-4fdd-bf03-704c76650aab"],
"secret": [ "secret": [
"j_8WeQHYt5R6coay0IOUeu9hGvCoJsgnENSoYm0gDlDx6IHOg-f6p17QIaesNmgrzXtJDRpYMhSjpTMHOnHCHLxwUM4eVg9TcszffndB850Yj3PHPeCc5aoHcpYzWN9NDZZ02nBYA04nfbkdlLXiGlpS3I3e502e4DX3rFtbFZ0" "bcW7E4rcbgSKZIQysWOSuhezRGYs5Kzmp3ZESthdTUMyFivK8RbBAdBE4PhFPk5B9TuByDO2RWvd8F7F5YhGJitf6cfYB1BfDuAk-2iBAtdZA98g7a2h4jpwzh-GIgtoRbGbH9qnquUn52f5qteo34g5WifKE2bWjOELza9FrTo"
], ],
"priority": ["100"], "priority": ["100"],
"algorithm": ["HS512"] "algorithm": ["HS512"]
@ -2388,7 +2430,7 @@
"clientSessionMaxLifespan": "0", "clientSessionMaxLifespan": "0",
"organizationsEnabled": "false" "organizationsEnabled": "false"
}, },
"keycloakVersion": "25.0.0", "keycloakVersion": "25.0.6",
"userManagedAccessAllowed": false, "userManagedAccessAllowed": false,
"organizationsEnabled": false, "organizationsEnabled": false,
"clientProfiles": { "clientProfiles": {

View File

@ -38,6 +38,7 @@
"bruteForceProtected": false, "bruteForceProtected": false,
"permanentLockout": false, "permanentLockout": false,
"maxTemporaryLockouts": 0, "maxTemporaryLockouts": 0,
"bruteForceStrategy": "MULTIPLE",
"maxFailureWaitSeconds": 900, "maxFailureWaitSeconds": 900,
"minimumQuickLoginWaitSeconds": 60, "minimumQuickLoginWaitSeconds": 60,
"waitIncrementSeconds": 60, "waitIncrementSeconds": 60,
@ -562,6 +563,40 @@
"disableableCredentialTypes": [], "disableableCredentialTypes": [],
"requiredActions": [], "requiredActions": [],
"realmRoles": ["default-roles-myrealm"], "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, "notBefore": 0,
"groups": [] "groups": []
} }
@ -604,6 +639,7 @@
"frontchannelLogout": false, "frontchannelLogout": false,
"protocol": "openid-connect", "protocol": "openid-connect",
"attributes": { "attributes": {
"realm_client": "false",
"post.logout.redirect.uris": "+" "post.logout.redirect.uris": "+"
}, },
"authenticationFlowBindingOverrides": {}, "authenticationFlowBindingOverrides": {},
@ -628,14 +664,20 @@
"id": "d8f14dc4-5f0f-4a1d-8c0b-cfe78ee55cb3", "id": "d8f14dc4-5f0f-4a1d-8c0b-cfe78ee55cb3",
"clientId": "account-console", "clientId": "account-console",
"name": "${client_account-console}", "name": "${client_account-console}",
"description": "",
"rootUrl": "${authBaseUrl}", "rootUrl": "${authBaseUrl}",
"adminUrl": "",
"baseUrl": "/realms/myrealm/account/", "baseUrl": "/realms/myrealm/account/",
"surrogateAuthRequired": false, "surrogateAuthRequired": false,
"enabled": true, "enabled": true,
"alwaysDisplayInConsole": false, "alwaysDisplayInConsole": false,
"clientAuthenticatorType": "client-secret", "clientAuthenticatorType": "client-secret",
"redirectUris": ["/realms/myrealm/account/*"], "redirectUris": [
"webOrigins": [], "http://localhost*",
"http://127.0.0.1*",
"/realms/myrealm/account/*"
],
"webOrigins": ["*"],
"notBefore": 0, "notBefore": 0,
"bearerOnly": false, "bearerOnly": false,
"consentRequired": false, "consentRequired": false,
@ -647,8 +689,14 @@
"frontchannelLogout": false, "frontchannelLogout": false,
"protocol": "openid-connect", "protocol": "openid-connect",
"attributes": { "attributes": {
"realm_client": "false",
"oidc.ciba.grant.enabled": "false",
"backchannel.logout.session.required": "true",
"post.logout.redirect.uris": "+", "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": {}, "authenticationFlowBindingOverrides": {},
"fullScopeAllowed": false, "fullScopeAllowed": false,
@ -699,10 +747,12 @@
"frontchannelLogout": false, "frontchannelLogout": false,
"protocol": "openid-connect", "protocol": "openid-connect",
"attributes": { "attributes": {
"realm_client": "false",
"client.use.lightweight.access.token.enabled": "true",
"post.logout.redirect.uris": "+" "post.logout.redirect.uris": "+"
}, },
"authenticationFlowBindingOverrides": {}, "authenticationFlowBindingOverrides": {},
"fullScopeAllowed": false, "fullScopeAllowed": true,
"nodeReRegistrationTimeout": 0, "nodeReRegistrationTimeout": 0,
"defaultClientScopes": [ "defaultClientScopes": [
"web-origins", "web-origins",
@ -740,6 +790,7 @@
"frontchannelLogout": false, "frontchannelLogout": false,
"protocol": "openid-connect", "protocol": "openid-connect",
"attributes": { "attributes": {
"realm_client": "true",
"post.logout.redirect.uris": "+" "post.logout.redirect.uris": "+"
}, },
"authenticationFlowBindingOverrides": {}, "authenticationFlowBindingOverrides": {},
@ -789,10 +840,10 @@
"frontchannelLogout": true, "frontchannelLogout": true,
"protocol": "openid-connect", "protocol": "openid-connect",
"attributes": { "attributes": {
"realm_client": "false",
"oidc.ciba.grant.enabled": "false", "oidc.ciba.grant.enabled": "false",
"backchannel.logout.session.required": "true", "backchannel.logout.session.required": "true",
"login_theme": "keycloakify-starter", "post.logout.redirect.uris": "+",
"post.logout.redirect.uris": "https://my-theme.keycloakify.dev/*##http://localhost*##http://127.0.0.1*",
"oauth2.device.authorization.grant.enabled": "false", "oauth2.device.authorization.grant.enabled": "false",
"display.on.consent.screen": "false", "display.on.consent.screen": "false",
"backchannel.logout.revoke.offline.tokens": "false" "backchannel.logout.revoke.offline.tokens": "false"
@ -855,6 +906,7 @@
"frontchannelLogout": false, "frontchannelLogout": false,
"protocol": "openid-connect", "protocol": "openid-connect",
"attributes": { "attributes": {
"realm_client": "true",
"post.logout.redirect.uris": "+" "post.logout.redirect.uris": "+"
}, },
"authenticationFlowBindingOverrides": {}, "authenticationFlowBindingOverrides": {},
@ -879,14 +931,20 @@
"id": "fce8a109-6f32-4814-9a20-2ff2435d2da6", "id": "fce8a109-6f32-4814-9a20-2ff2435d2da6",
"clientId": "security-admin-console", "clientId": "security-admin-console",
"name": "${client_security-admin-console}", "name": "${client_security-admin-console}",
"description": "",
"rootUrl": "${authAdminUrl}", "rootUrl": "${authAdminUrl}",
"adminUrl": "",
"baseUrl": "/admin/myrealm/console/", "baseUrl": "/admin/myrealm/console/",
"surrogateAuthRequired": false, "surrogateAuthRequired": false,
"enabled": true, "enabled": true,
"alwaysDisplayInConsole": false, "alwaysDisplayInConsole": false,
"clientAuthenticatorType": "client-secret", "clientAuthenticatorType": "client-secret",
"redirectUris": ["/admin/myrealm/console/*"], "redirectUris": [
"webOrigins": ["+"], "http://localhost*",
"http://127.0.0.1*",
"/admin/myrealm/console/*"
],
"webOrigins": ["*"],
"notBefore": 0, "notBefore": 0,
"bearerOnly": false, "bearerOnly": false,
"consentRequired": false, "consentRequired": false,
@ -898,11 +956,18 @@
"frontchannelLogout": false, "frontchannelLogout": false,
"protocol": "openid-connect", "protocol": "openid-connect",
"attributes": { "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": "+", "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": {}, "authenticationFlowBindingOverrides": {},
"fullScopeAllowed": false, "fullScopeAllowed": true,
"nodeReRegistrationTimeout": 0, "nodeReRegistrationTimeout": 0,
"protocolMappers": [ "protocolMappers": [
{ {
@ -1544,11 +1609,11 @@
}, },
"smtpServer": {}, "smtpServer": {},
"loginTheme": "keycloakify-starter", "loginTheme": "keycloakify-starter",
"accountTheme": "keycloakify-starter", "accountTheme": "",
"adminTheme": "", "adminTheme": "",
"emailTheme": "", "emailTheme": "",
"eventsEnabled": false, "eventsEnabled": false,
"eventsListeners": ["jboss-logging"], "eventsListeners": ["keycloakify-logging", "jboss-logging"],
"enabledEventTypes": [], "enabledEventTypes": [],
"adminEventsEnabled": false, "adminEventsEnabled": false,
"adminEventsDetailsEnabled": false, "adminEventsDetailsEnabled": false,
@ -1574,14 +1639,14 @@
"subComponents": {}, "subComponents": {},
"config": { "config": {
"allowed-protocol-mapper-types": [ "allowed-protocol-mapper-types": [
"oidc-full-name-mapper",
"oidc-usermodel-attribute-mapper",
"oidc-address-mapper",
"saml-user-attribute-mapper",
"oidc-usermodel-property-mapper", "oidc-usermodel-property-mapper",
"saml-user-attribute-mapper",
"saml-user-property-mapper", "saml-user-property-mapper",
"saml-role-list-mapper", "oidc-full-name-mapper",
"oidc-sha256-pairwise-sub-mapper" "oidc-sha256-pairwise-sub-mapper",
"oidc-address-mapper",
"oidc-usermodel-attribute-mapper",
"saml-role-list-mapper"
] ]
} }
}, },
@ -1612,13 +1677,13 @@
"config": { "config": {
"allowed-protocol-mapper-types": [ "allowed-protocol-mapper-types": [
"oidc-sha256-pairwise-sub-mapper", "oidc-sha256-pairwise-sub-mapper",
"oidc-usermodel-property-mapper",
"oidc-address-mapper",
"oidc-usermodel-attribute-mapper",
"oidc-full-name-mapper",
"saml-user-attribute-mapper", "saml-user-attribute-mapper",
"oidc-usermodel-property-mapper",
"oidc-full-name-mapper",
"saml-role-list-mapper",
"saml-user-property-mapper", "saml-user-property-mapper",
"saml-role-list-mapper" "oidc-usermodel-attribute-mapper",
"oidc-address-mapper"
] ]
} }
}, },
@ -1672,11 +1737,10 @@
"subComponents": {}, "subComponents": {},
"config": { "config": {
"privateKey": [ "privateKey": [
"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"
], ],
"keyUse": ["SIG"],
"certificate": [ "certificate": [
"MIICnTCCAYUCBgGQBsyplzANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdteXJlYWxtMB4XDTI0MDYxMTEwMTQ1NFoXDTM0MDYxMTEwMTYzNFowEjEQMA4GA1UEAwwHbXlyZWFsbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALGFFs1X2THessKQZiyWHhdVPTIHf898Vr5dWKu+qETz36XKkIrTfnJLVqrtIT3qwlUpdpbcFwzK1MBc7MBvTIjPYW/gSxJXe5GImJHBTbd5QrZpnkUBGnuycFops3jVfekJc9LR2PH/WYdH3zw7C0zdRxgHkSSYSO5jUJuqgiTTAn1QPzuUlrprCJ9ly50jIkNOQAAlRzi1KuqwGdswgmmJl05iZLkjm/Htuld9Nz+petw0nuR0jVw3SNNLY3xRX9+6vO9M4b37KLa448dETIo8vSo4/DmRfqRR6Q0WhhIvLaUMWQ5wk6H04ngf1ad0D/mk5bwMg90NWZycRkAi8oMCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAVS+gJshIFX6cmBGI8UaOOI/9+XFb4Gi+DHaHVWVVHTd14MoqNK1bmmyTHbGIZbvK8UqgJ9+FhJX1ejx17d4KBzkZI3tYvPnVacHvaw1CIUMZ1Ini6u+UGUTnIlnQzCG0pcTKjOZXf3ih1B2CKdwyC7XeXyEJHicAIG7XfzYfYd9DYHvA+h6hrXaQcNJMW7WFNbtb3fJhtlv5P1Iw+ZEGdj15ukMI0bg2OEQA0F3jIw6QZpigSAGuai3HOY6OgoPO82d7TyTYlNhuwyutWr9izl6QMc2R7BmRfW9XQj4ICR2VWJiL9nqz+SOyqnjQiOObuw8Vywb8c36R1Ym1aaGjOw==" "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"] "priority": ["100"]
} }
@ -1688,11 +1752,10 @@
"subComponents": {}, "subComponents": {},
"config": { "config": {
"privateKey": [ "privateKey": [
"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"
], ],
"keyUse": ["ENC"],
"certificate": [ "certificate": [
"MIICnTCCAYUCBgGQBsyq0jANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdteXJlYWxtMB4XDTI0MDYxMTEwMTQ1NFoXDTM0MDYxMTEwMTYzNFowEjEQMA4GA1UEAwwHbXlyZWFsbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJELXnx8vNnvHeXVVjdNC5xiOWJgTkyodLYKmslUY6ix+rtzX4CVVSHtLRZ8Wu1pGv8kh659SkrFudnM2eLj1y1KOLFwZEMcshV3mYljfB/vVolEPtIXn2ReHrOWT4WNlaXjCvVwJ6YQt+tixS1cnF7pZKSXEPuA4yPfU6OCy8iZo0L7FWie2pqFSecXetnlL/g+39vo8Lmk8IxhN/jFmQA141cbFGNaibhixipy5DTcSTTA4b5ymMPmmt2eFfdH0FcozdVaJYMNw3QOfaOMK1hMvtsR3Oo9hEovPnDOtH1s2VPbKdl5MiaAp1e55sJO6WZA8t87wJHjls669zlnkd8CAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAD9wQ+CJ0FRgls3JrUzxwHLgrJ3Yo4+mDFpSe1rh2XYK5FEIWDWSqxaXI3p0cOZq75RZmI2xV8oaiJMUz9WMZkbNe/KtGRzHY1N9AZooicGIsnFu1t++b8taFxxpvKWZgnbOum2PZlfcNiXL0QeMv0wwhfn9zKA9W1DRcqYGbIamoyVlumvbNyIjqXJKwGYIOW6GNt7v3wJl5AJw8qAU/O/DQwWwmzcnFGNRxRxAwI7we8EiQ5JlG0Wi+nyAQn74o3RhNr3zsY0ndmFx9bFV4BBo2AiYGozCDOCCG5HvrmoDbrm//wmGRv0tCwueBzWHL2mhtbZ6sGWmMWfiTJ2HPpg==" "MIICnTCCAYUCBgGTulJDCDANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdteXJlYWxtMB4XDTI0MTIxMjEwMDExM1oXDTM0MTIxMjEwMDI1M1owEjEQMA4GA1UEAwwHbXlyZWFsbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALp4C+KLC8j/GxOjEio/GTCUxvFgYdytv8QeQSWAmHOz9QiDkWSPaP7ykMdUJz/gl5CCp50dpMyMwtK506pSDpGmX4Zkp+dIxubzQUvaj0/llO1Ht6isbliRcHGhVOUhCWsCR6pwzyvqZLZJMZLnXjB8+omij7PsCnG360Sevt2txakEHRxEIOCI1VPKQWZmoNXOyR2sojOejk+cuo5hOSb2OtPefYj62T10kzFN4TaIlcHciukMHSvv9MKzuvNVYSD/rtqXvdp0PobIDEkkCXoC8dVLBBMlPO7Mm5Rvqd8wCkkvpLdp74lf2tnGIojBNC3ZUN5ZyrEOLdWOsmQYktkCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAPhPdLFcXdQT4k06oXB06ZSJ8AkZNXLvQFWCHXI34OmrS2yTse+dLqrqehnC3kPwxElVmawoUVc1sbsk7fUnspfM+Xw20PaABZu4MO2m5TB98f1hEkezP9fSqgPeuWJgTL8ZW5kkZyiD3IaZoqyxzYXaFxKHhU455g+k2+DO+N6FreVKcYz12Q5EMaxZ6U1neZAo3vicNxM3/TA5V8sPK8+oKvon7v5OyjpOH0goJo9v/klKeUk36h4u2h1S67IhVSU7tfzVFYrpns1JhrwGZ2xavVqEoqX8zFp3GKz3yVXkwHRHlrzYkZoGn21rm5boXIP3wEB7yXZbXWTiUko/IFw=="
], ],
"priority": ["100"], "priority": ["100"],
"algorithm": ["RSA-OAEP"] "algorithm": ["RSA-OAEP"]
@ -1704,8 +1767,8 @@
"providerId": "aes-generated", "providerId": "aes-generated",
"subComponents": {}, "subComponents": {},
"config": { "config": {
"kid": ["1c1d0c8a-6f0b-48a9-a66f-488489137d85"], "kid": ["c36222c6-6a43-4d32-9d44-d5d355e5cabd"],
"secret": ["N4wzheVYYBWxFn9VGWTPQQ"], "secret": ["rzL4qUQ7wTEkZDbgt595VA"],
"priority": ["100"] "priority": ["100"]
} }
}, },
@ -1715,9 +1778,9 @@
"providerId": "hmac-generated", "providerId": "hmac-generated",
"subComponents": {}, "subComponents": {},
"config": { "config": {
"kid": ["ce43821c-6cfd-4ea9-a29a-a724a37e6955"], "kid": ["06532a54-c310-41c1-829c-58776ce2ab4a"],
"secret": [ "secret": [
"j_8WeQHYt5R6coay0IOUeu9hGvCoJsgnENSoYm0gDlDx6IHOg-f6p17QIaesNmgrzXtJDRpYMhSjpTMHOnHCHLxwUM4eVg9TcszffndB850Yj3PHPeCc5aoHcpYzWN9NDZZ02nBYA04nfbkdlLXiGlpS3I3e502e4DX3rFtbFZ0" "9v1ZjFhEFH6UpY6ncFkaCbqJYHMyI4tA0cvx4GuQ5KtMXYbimitSSVDqxIKwa-gBC_8bY2O4FQfpmp1Qn1-L4fFmPFfIF3ZKsO16263BwpADo_FNSBTte8Le4gJLylqFULdsn3ye17FHyq5Jjms_OTt3opzcDLNduCuK22GBBsU"
], ],
"priority": ["100"], "priority": ["100"],
"algorithm": ["HS512"] "algorithm": ["HS512"]
@ -2388,7 +2451,7 @@
"clientSessionMaxLifespan": "0", "clientSessionMaxLifespan": "0",
"organizationsEnabled": "false" "organizationsEnabled": "false"
}, },
"keycloakVersion": "25.0.0", "keycloakVersion": "26.0.7",
"userManagedAccessAllowed": false, "userManagedAccessAllowed": false,
"organizationsEnabled": false, "organizationsEnabled": false,
"clientProfiles": { "clientProfiles": {

View File

@ -0,0 +1,147 @@
import { CONTAINER_NAME } from "../../shared/constants";
import child_process from "child_process";
import { join as pathJoin } 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) {
child_process.execSync(
`docker exec ${CONTAINER_NAME} sh -c "cp -rp /opt/keycloak/data/h2 /tmp"`
);
}
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(`Exited 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);
process.exit(1);
}
if (doesUseLockedH2Database) {
const dCompleted = new Deferred<void>();
child_process.exec(
`docker exec ${CONTAINER_NAME} sh -c "rm -rf /tmp/h2"`,
error => {
if (error !== null) {
dCompleted.reject(error);
return;
}
dCompleted.resolve();
}
);
await dCompleted.pr;
}
}
const targetRealmConfigJsonFilePath_tmp = pathJoin(
buildContext.cacheDirPath,
"realm.json"
);
{
const dCompleted = new Deferred<void>();
child_process.exec(
`docker cp ${CONTAINER_NAME}:/tmp/${realmName}-realm.json ${targetRealmConfigJsonFilePath_tmp}`,
error => {
if (error !== null) {
dCompleted.reject(error);
return;
}
dCompleted.resolve();
}
);
await dCompleted.pr;
}
return readRealmJsonFile({
realmJsonFilePath: targetRealmConfigJsonFilePath_tmp
});
}

View File

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

View File

@ -0,0 +1,302 @@
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"]) {
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 = ["*"];
}
}

View File

@ -0,0 +1,151 @@
import type { BuildContext } from "../../shared/buildContext";
import { assert } from "tsafe/assert";
import { runPrettier, getIsPrettierAvailable } from "../../tools/runPrettier";
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, 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 });
}
}
const writeRealmJsonFile = async (params: { parsedRealmJson: ParsedRealmJson }) => {
const { parsedRealmJson } = params;
let sourceCode = JSON.stringify(parsedRealmJson, null, 2);
if (await getIsPrettierAvailable()) {
sourceCode = await runPrettier({
sourceCode,
filePath: realmJsonFilePath
});
}
fs.writeFileSync(realmJsonFilePath, sourceCode);
};
await writeRealmJsonFile({ parsedRealmJson });
const { onRealmConfigChange } = (() => {
const run = runExclusive.build(async () => {
const start = Date.now();
console.log(
chalk.grey(`Changes detected to the '${realmName}' config, backing up...`)
);
const parsedRealmJson = await dumpContainerConfig({
buildContext,
realmName,
keycloakMajorVersionNumber
});
await writeRealmJsonFile({ parsedRealmJson });
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 type { BuildContext } from "../shared/buildContext";
import { exclude } from "tsafe/exclude"; import { exclude } from "tsafe/exclude";
import { promptKeycloakVersion } from "../shared/promptKeycloakVersion"; import {
import { CONTAINER_NAME } from "../shared/constants"; CONTAINER_NAME,
KEYCLOAKIFY_SPA_DEV_SERVER_PORT,
KEYCLOAKIFY_LOGIN_JAR_BASENAME,
TEST_APP_URL
} from "../shared/constants";
import { SemVer } from "../tools/SemVer"; import { SemVer } from "../tools/SemVer";
import { assert, type Equals } from "tsafe/assert"; import { assert, type Equals } from "tsafe/assert";
import * as fs from "fs"; import * as fs from "fs";
@ -9,8 +13,7 @@ import {
join as pathJoin, join as pathJoin,
relative as pathRelative, relative as pathRelative,
sep as pathSep, sep as pathSep,
basename as pathBasename, basename as pathBasename
dirname as pathDirname
} from "path"; } from "path";
import * as child_process from "child_process"; import * as child_process from "child_process";
import chalk from "chalk"; import chalk from "chalk";
@ -27,6 +30,10 @@ import { isInside } from "../tools/isInside";
import { existsAsync } from "../tools/fs.existsAsync"; import { existsAsync } from "../tools/fs.existsAsync";
import { rm } from "../tools/fs.rm"; import { rm } from "../tools/fs.rm";
import { downloadAndExtractArchive } from "../tools/downloadAndExtractArchive"; 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: { export async function command(params: {
buildContext: BuildContext; buildContext: BuildContext;
@ -90,9 +97,32 @@ export async function command(params: {
const { cliCommandOptions, buildContext } = params; const { cliCommandOptions, buildContext } = params;
const availableTags = await getSupportedDockerImageTags({
buildContext
});
const { dockerImageTag } = await (async () => { const { dockerImageTag } = await (async () => {
if (cliCommandOptions.keycloakVersion !== undefined) { if (cliCommandOptions.keycloakVersion !== undefined) {
return { dockerImageTag: cliCommandOptions.keycloakVersion }; const cliCommandOptions_keycloakVersion = cliCommandOptions.keycloakVersion;
const tag = availableTags.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) { if (buildContext.startKeycloakOptions.dockerImage !== undefined) {
@ -107,50 +137,84 @@ export async function command(params: {
"On which version of Keycloak do you want to test your theme?" "On which version of Keycloak do you want to test your theme?"
), ),
chalk.gray( 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") ].join("\n")
); );
const { keycloakVersion } = await promptKeycloakVersion({ const { value: tag } = await cliSelect<string>({
startingFromMajor: 18, values: availableTags
excludeMajorVersions: [22], }).catch(() => {
doOmitPatch: true, process.exit(-1);
buildContext
}); });
console.log(`${keycloakVersion}`); console.log(`${tag}`);
return { dockerImageTag: keycloakVersion }; return { dockerImageTag: tag };
})(); })();
const keycloakMajorVersionNumber = (() => { const keycloakMajorVersionNumber = (() => {
if (buildContext.startKeycloakOptions.dockerImage === undefined) { const [wrap] = getSupportedKeycloakMajorVersions()
return SemVer.parse(dockerImageTag).major;
}
const { tag } = buildContext.startKeycloakOptions.dockerImage;
const [wrap] = [18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28]
.map(majorVersionNumber => ({ .map(majorVersionNumber => ({
majorVersionNumber, majorVersionNumber,
index: tag.indexOf(`${majorVersionNumber}`) index: dockerImageTag.indexOf(`${majorVersionNumber}`)
})) }))
.filter(({ index }) => index !== -1) .filter(({ index }) => index !== -1)
.sort((a, b) => a.index - b.index); .sort((a, b) => a.index - b.index);
if (wrap === undefined) { if (wrap === undefined) {
console.warn( try {
chalk.yellow( const version = SemVer.parse(dockerImageTag);
`Could not determine the major Keycloak version number from the docker image tag ${tag}. Assuming 25`
) console.error(
); chalk.yellow(
return 25; `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; 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({ const { isAppBuildSuccess } = await appBuild({
buildContext buildContext
@ -188,154 +252,48 @@ export async function command(params: {
assert(jarFilePath !== undefined); assert(jarFilePath !== undefined);
const extensionJarFilePaths = await Promise.all( const extensionJarFilePaths = [
buildContext.startKeycloakOptions.extensionJars.map(async extensionJar => { ...(keycloakMajorVersionNumber <= 20
switch (extensionJar.type) { ? (console.log(
case "path": { chalk.yellow(
assert( "WARNING: With older version of keycloak your changes to the realm configuration are not persisted"
await existsAsync(extensionJar.path), )
`${extensionJar.path} does not exist` ),
); [])
return extensionJar.path; : [
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": { assert<Equals<typeof extensionJar, never>>(false);
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");
}
async function extractThemeResourcesFromJar() { async function extractThemeResourcesFromJar() {
await extractArchive({ await extractArchive({
@ -375,17 +333,76 @@ export async function command(params: {
}); });
} catch {} } catch {}
const DEFAULT_PORT = 8080; const port = cliCommandOptions.port ?? buildContext.startKeycloakOptions.port ?? 8080;
const port =
cliCommandOptions.port ?? buildContext.startKeycloakOptions.port ?? DEFAULT_PORT; const doStartDevServer = (() => {
const hasSpaUi =
buildContext.implementedThemeTypes.admin.isImplemented ||
(buildContext.implementedThemeTypes.account.isImplemented &&
buildContext.implementedThemeTypes.account.type === "Single-Page");
if (!hasSpaUi) {
return false;
}
if (buildContext.bundler !== "vite") {
console.log(
chalk.yellow(
[
`WARNING: Since you are using ${buildContext.bundler} instead of Vite,`,
`you'll have to wait serval seconds for the changes you made on your account or admin theme to be reflected in the browser.\n`,
`For a better development experience, consider migrating to Vite.`
].join(" ")
)
);
return false;
}
if (keycloakMajorVersionNumber < 25) {
console.log(
chalk.yellow(
[
`WARNING: Your account or admin theme can't be tested with hot module replacement on Keycloak ${keycloakMajorVersionNumber}.`,
`This mean that you'll have to wait serval seconds for the changes to be reflected in the browser.`,
`For a better development experience, select a more recent version of Keycloak.`
].join("\n")
)
);
return false;
}
return true;
})();
let devServerPort: number | undefined = undefined;
if (doStartDevServer) {
const { port } = await startViteDevServer({ buildContext });
devServerPort = port;
}
const SPACE_PLACEHOLDER = "SPACE_PLACEHOLDER_xKLmdPd"; const SPACE_PLACEHOLDER = "SPACE_PLACEHOLDER_xKLmdPd";
const dockerRunArgs: string[] = [ const dockerRunArgs: string[] = [
`-p${SPACE_PLACEHOLDER}${port}:8080`, `-p${SPACE_PLACEHOLDER}${port}:8080`,
`--name${SPACE_PLACEHOLDER}${CONTAINER_NAME}`, `--name${SPACE_PLACEHOLDER}${CONTAINER_NAME}`,
`-e${SPACE_PLACEHOLDER}KEYCLOAK_ADMIN=admin`, ...(keycloakMajorVersionNumber >= 26
`-e${SPACE_PLACEHOLDER}KEYCLOAK_ADMIN_PASSWORD=admin`, ? [
`-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
? []
: [
`-e${SPACE_PLACEHOLDER}${KEYCLOAKIFY_SPA_DEV_SERVER_PORT}=${devServerPort}`
]),
...(buildContext.startKeycloakOptions.dockerExtraArgs.length === 0 ...(buildContext.startKeycloakOptions.dockerExtraArgs.length === 0
? [] ? []
: [ : [
@ -396,7 +413,7 @@ export async function command(params: {
...(realmJsonFilePath === undefined ...(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`, `-v${SPACE_PLACEHOLDER}"${jarFilePath_cacheDir}":/opt/keycloak/providers/keycloak-theme.jar`,
...extensionJarFilePaths.map( ...extensionJarFilePaths.map(
@ -471,7 +488,14 @@ export async function command(params: {
{ shell: true } { 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)); child.stderr.on("data", data => process.stderr.write(data));
@ -518,9 +542,9 @@ export async function command(params: {
`${chalk.green("Your theme is accessible at:")}`, `${chalk.green("Your theme is accessible at:")}`,
`${chalk.green("➜")} ${chalk.cyan.bold( `${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}`); url.searchParams.set("port", `${port}`);
} }
if (kcHttpRelativePath !== undefined) { if (kcHttpRelativePath !== undefined) {
@ -529,13 +553,20 @@ export async function command(params: {
kcHttpRelativePath kcHttpRelativePath
); );
} }
if (realmName !== "myrealm") {
url.searchParams.set("realm", realmName);
}
if (clientName !== "myclient") {
url.searchParams.set("client", clientName);
}
return url.href; return url.href;
})() })()
)}`, )}`,
"", "",
"You can login with the following credentials:", "You can login with the following credentials:",
`- username: ${chalk.cyan.bold("testuser")}`, `- username: ${chalk.cyan.bold(username)}`,
`- password: ${chalk.cyan.bold("password123")}`, `- password: ${chalk.cyan.bold("password123")}`,
"", "",
`Watching for changes in ${chalk.bold( `Watching for changes in ${chalk.bold(
@ -592,6 +623,44 @@ export async function command(params: {
} }
) )
.on("all", async (...[, filePath]) => { .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;
}
if (
!isInside({
dirPath: pathJoin(buildContext.themeSrcDirPath, "account"),
filePath
})
) {
break ignore_account_spa;
}
return;
}
ignore_admin: {
if (!buildContext.implementedThemeTypes.admin.isImplemented) {
break ignore_admin;
}
if (
!isInside({
dirPath: pathJoin(buildContext.themeSrcDirPath, "admin"),
filePath
})
) {
break ignore_admin;
}
return;
}
console.log(`Detected changes in ${filePath}`); console.log(`Detected changes in ${filePath}`);
await waitForDebounce(); await waitForDebounce();

View File

@ -0,0 +1,66 @@
import * as child_process from "child_process";
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;
};
assert<BuildContext extends BuildContextLike ? true : false>();
export function startViteDevServer(params: {
buildContext: BuildContextLike;
}): Promise<{ port: number }> {
const { buildContext } = params;
console.log(chalk.blue(`$ npx vite dev`));
const child = child_process.spawn("npx", ["vite", "dev"], {
cwd: buildContext.projectDirPath,
env: {
...process.env,
[VITE_PLUGIN_SUB_SCRIPTS_ENV_NAMES.READ_KC_CONTEXT_FROM_URL]: "true"
},
shell: true
});
child.stdout.on("data", data => {
if (!data.toString("utf8").includes("[vite] hmr")) {
return;
}
process.stdout.write(data);
});
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")
.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

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

View File

@ -9,6 +9,16 @@ import { getIsPrettierAvailable, runPrettier } from "./tools/runPrettier";
export async function command(params: { buildContext: BuildContext }) { export async function command(params: { buildContext: BuildContext }) {
const { buildContext } = params; const { buildContext } = params;
run_copy_assets_to_public: {
if (buildContext.bundler !== "webpack") {
break run_copy_assets_to_public;
}
const { command } = await import("./copy-keycloak-resources-to-public");
await command({ buildContext });
}
const { hasBeenHandled } = maybeDelegateCommandToCustomHandler({ const { hasBeenHandled } = maybeDelegateCommandToCustomHandler({
commandName: "update-kc-gen", commandName: "update-kc-gen",
buildContext buildContext
@ -52,7 +62,12 @@ export async function command(params: { buildContext: BuildContext }) {
2 2
)};`, )};`,
``, ``,
`type KcContext =`, `/**`,
` * NOTE: Do not import this type except maybe in your entrypoint. `,
` * If you need to import the KcContext import it either from src/login/KcContext.ts or src/account/KcContext.ts.`,
` * Depending on the theme type you are working on.`,
` */`,
`export type KcContext =`,
hasLoginTheme && ` | import("./login/KcContext").KcContext`, hasLoginTheme && ` | import("./login/KcContext").KcContext`,
hasAccountTheme && ` | import("./account/KcContext").KcContext`, hasAccountTheme && ` | import("./account/KcContext").KcContext`,
hasAdminTheme && ` | import("./admin/KcContext").KcContext`, hasAdminTheme && ` | import("./admin/KcContext").KcContext`,

View File

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

View File

@ -33,7 +33,7 @@ export type KcContext =
| KcContext.LoginResetPassword | KcContext.LoginResetPassword
| KcContext.LoginVerifyEmail | KcContext.LoginVerifyEmail
| KcContext.Terms | KcContext.Terms
| KcContext.LoginDeviceVerifyUserCode | KcContext.LoginOauth2DeviceVerifyUserCode
| KcContext.LoginOauthGrant | KcContext.LoginOauthGrant
| KcContext.LoginOtp | KcContext.LoginOtp
| KcContext.LoginUsername | KcContext.LoginUsername
@ -277,7 +277,7 @@ export declare namespace KcContext {
__localizationRealmOverridesTermsText?: string; __localizationRealmOverridesTermsText?: string;
}; };
export type LoginDeviceVerifyUserCode = Common & { export type LoginOauth2DeviceVerifyUserCode = Common & {
pageId: "login-oauth2-device-verify-user-code.ftl"; pageId: "login-oauth2-device-verify-user-code.ftl";
url: { url: {
oauth2DeviceVerificationAction: string; oauth2DeviceVerificationAction: string;

View File

@ -290,7 +290,7 @@ export const kcContextMocks = [
...kcContextCommonMock, ...kcContextCommonMock,
pageId: "terms.ftl" pageId: "terms.ftl"
}), }),
id<KcContext.LoginDeviceVerifyUserCode>({ id<KcContext.LoginOauth2DeviceVerifyUserCode>({
...kcContextCommonMock, ...kcContextCommonMock,
pageId: "login-oauth2-device-verify-user-code.ftl", pageId: "login-oauth2-device-verify-user-code.ftl",
url: loginUrl url: loginUrl

View File

@ -11,7 +11,6 @@ export type TemplateProps<KcContext, I18n> = {
displayInfo?: boolean; displayInfo?: boolean;
displayMessage?: boolean; displayMessage?: boolean;
displayRequiredFields?: boolean; displayRequiredFields?: boolean;
showAnotherWayIfPresent?: boolean;
headerNode: ReactNode; headerNode: ReactNode;
socialProvidersNode?: ReactNode; socialProvidersNode?: ReactNode;
infoNode?: ReactNode; infoNode?: ReactNode;

View File

@ -1,3 +1,4 @@
import type { JSX } from "keycloakify/tools/JSX";
import { useEffect, useReducer, Fragment } from "react"; import { useEffect, useReducer, Fragment } from "react";
import { assert } from "keycloakify/tools/assert"; import { assert } from "keycloakify/tools/assert";
import type { KcClsx } from "keycloakify/login/lib/kcClsx"; 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 FormAction, type FormFieldError } from "keycloakify/login/lib/useUserProfileForm";
import type { KcClsx } from "keycloakify/login/lib/kcClsx"; import type { KcClsx } from "keycloakify/login/lib/kcClsx";
import type { Attribute } from "keycloakify/login/KcContext"; import type { Attribute } from "keycloakify/login/KcContext";

View File

@ -1,5 +1,6 @@
import "keycloakify/tools/Object.fromEntries"; import "keycloakify/tools/Object.fromEntries";
import { assert, is } from "tsafe/assert"; import { assert, is } from "tsafe/assert";
import { extractLastParenthesisContent } from "keycloakify/tools/extractLastParenthesisContent";
import messages_defaultSet_fallbackLanguage from "../messages_defaultSet/en"; import messages_defaultSet_fallbackLanguage from "../messages_defaultSet/en";
import { fetchMessages_defaultSet } from "../messages_defaultSet"; import { fetchMessages_defaultSet } from "../messages_defaultSet";
import type { KcContext } from "../../KcContext"; import type { KcContext } from "../../KcContext";
@ -168,12 +169,10 @@ export function createGetI18n<
break from_server; break from_server;
} }
// cspell: disable-next-line const lastParenthesisContent = extractLastParenthesisContent(supportedEntry.label);
// from "Espagnol (Español)" we want to extract "Español"
const match = supportedEntry.label.match(/[^(]+\(([^)]+)\)/);
if (match !== null) { if (lastParenthesisContent !== undefined) {
return match[1]; return lastParenthesisContent;
} }
return supportedEntry.label; return supportedEntry.label;

View File

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

View File

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

View File

@ -1,3 +1,4 @@
import type { JSX } from "keycloakify/tools/JSX";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { kcSanitize } from "keycloakify/lib/kcSanitize"; import { kcSanitize } from "keycloakify/lib/kcSanitize";
import { createGetI18n, type KcContextLike } from "../noJsx/getI18n"; import { createGetI18n, type KcContextLike } from "../noJsx/getI18n";
@ -47,11 +48,25 @@ export function createUseI18n<
function renderHtmlString(params: { htmlString: string; msgKey: string }): JSX.Element { function renderHtmlString(params: { htmlString: string; msgKey: string }): JSX.Element {
const { htmlString, msgKey } = params; const { htmlString, msgKey } = params;
const htmlString_sanitized = kcSanitize(htmlString);
const Element = (() => {
if (htmlString_sanitized.includes("<") && htmlString_sanitized.includes(">")) {
for (const tagName of ["div", "section", "article", "ul", "ol"]) {
if (htmlString_sanitized.includes(`<${tagName}`)) {
return "div";
}
}
}
return "span";
})();
return ( return (
<div <Element
data-kc-msg={msgKey} data-kc-msg={msgKey}
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: kcSanitize(htmlString) __html: htmlString_sanitized
}} }}
/> />
); );
@ -83,7 +98,7 @@ export function createUseI18n<
})(); })();
add_style: { add_style: {
const attributeName = "data-kc-i18n"; const attributeName = "data-kc-msg";
// Check if already exists in head // Check if already exists in head
if (document.querySelector(`style[${attributeName}]`) !== null) { if (document.querySelector(`style[${attributeName}]`) !== null) {
@ -92,7 +107,7 @@ export function createUseI18n<
const styleElement = document.createElement("style"); const styleElement = document.createElement("style");
styleElement.attributes.setNamedItem(document.createAttribute(attributeName)); styleElement.attributes.setNamedItem(document.createAttribute(attributeName));
styleElement.textContent = `[data-kc-msg] { display: inline-block; }`; styleElement.textContent = `div[${attributeName}] { display: inline-block; }`;
document.head.prepend(styleElement); document.head.prepend(styleElement);
} }

View File

@ -1,3 +1,4 @@
import type { JSX } from "keycloakify/tools/JSX";
import * as reactlessApi from "./getUserProfileApi/index"; import * as reactlessApi from "./getUserProfileApi/index";
import type { PasswordPolicies, Attribute, Validators } from "keycloakify/login/KcContext"; import type { PasswordPolicies, Attribute, Validators } from "keycloakify/login/KcContext";
import { useEffect, useState, useMemo, Fragment } from "react"; 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 { useState } from "react";
import type { LazyOrNot } from "keycloakify/tools/LazyOrNot"; import type { LazyOrNot } from "keycloakify/tools/LazyOrNot";
import { getKcClsx } from "keycloakify/login/lib/kcClsx"; 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 { useState, useEffect, useReducer } from "react";
import { kcSanitize } from "keycloakify/lib/kcSanitize"; import { kcSanitize } from "keycloakify/lib/kcSanitize";
import { assert } from "keycloakify/tools/assert"; import { assert } from "keycloakify/tools/assert";

View File

@ -52,28 +52,26 @@ export default function LoginConfigTotp(props: PageProps<Extract<KcContext, { pa
</li> </li>
<li> <li>
<p>{msg("loginTotpManualStep3")}</p> <p>{msg("loginTotpManualStep3")}</p>
<p> <ul>
<ul> <li id="kc-totp-type">
<li id="kc-totp-type"> {msg("loginTotpType")}: {msg(`loginTotp.${totp.policy.type}`)}
{msg("loginTotpType")}: {msg(`loginTotp.${totp.policy.type}`)} </li>
<li id="kc-totp-algorithm">
{msg("loginTotpAlgorithm")}: {totp.policy.getAlgorithmKey()}
</li>
<li id="kc-totp-digits">
{msg("loginTotpDigits")}: {totp.policy.digits}
</li>
{totp.policy.type === "totp" ? (
<li id="kc-totp-period">
{msg("loginTotpInterval")}: {totp.policy.period}
</li> </li>
<li id="kc-totp-algorithm"> ) : (
{msg("loginTotpAlgorithm")}: {totp.policy.getAlgorithmKey()} <li id="kc-totp-counter">
{msg("loginTotpCounter")}: {totp.policy.initialCounter}
</li> </li>
<li id="kc-totp-digits"> )}
{msg("loginTotpDigits")}: {totp.policy.digits} </ul>
</li>
{totp.policy.type === "totp" ? (
<li id="kc-totp-period">
{msg("loginTotpInterval")}: {totp.policy.period}
</li>
) : (
<li id="kc-totp-counter">
{msg("loginTotpCounter")}: {totp.policy.initialCounter}
</li>
)}
</ul>
</p>
</li> </li>
</> </>
) : ( ) : (

View File

@ -1,3 +1,4 @@
import type { JSX } from "keycloakify/tools/JSX";
import { useState, useEffect, useReducer } from "react"; import { useState, useEffect, useReducer } from "react";
import { kcSanitize } from "keycloakify/lib/kcSanitize"; import { kcSanitize } from "keycloakify/lib/kcSanitize";
import { clsx } from "keycloakify/tools/clsx"; 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 { useEffect, useReducer } from "react";
import { kcSanitize } from "keycloakify/lib/kcSanitize"; import { kcSanitize } from "keycloakify/lib/kcSanitize";
import { assert } from "keycloakify/tools/assert"; import { assert } from "keycloakify/tools/assert";

View File

@ -1,3 +1,4 @@
import type { JSX } from "keycloakify/tools/JSX";
import { useState } from "react"; import { useState } from "react";
import type { LazyOrNot } from "keycloakify/tools/LazyOrNot"; import type { LazyOrNot } from "keycloakify/tools/LazyOrNot";
import { getKcClsx } from "keycloakify/login/lib/kcClsx"; 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 TemplateProps, type ClassKey } from "keycloakify/login/TemplateProps";
import type { LazyOrNot } from "keycloakify/tools/LazyOrNot"; 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 { useState } from "react";
import type { LazyOrNot } from "keycloakify/tools/LazyOrNot"; import type { LazyOrNot } from "keycloakify/tools/LazyOrNot";
import { kcSanitize } from "keycloakify/lib/kcSanitize"; import { kcSanitize } from "keycloakify/lib/kcSanitize";

View File

@ -1,3 +1,4 @@
import type { JSX } from "keycloakify/tools/JSX";
import { useState } from "react"; import { useState } from "react";
import type { LazyOrNot } from "keycloakify/tools/LazyOrNot"; import type { LazyOrNot } from "keycloakify/tools/LazyOrNot";
import { getKcClsx, type KcClsx } from "keycloakify/login/lib/kcClsx"; 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

@ -0,0 +1,43 @@
/**
* "Hello (world)" => "world"
* "Hello (world) (foo)" => "foo"
* "Hello (world (foo))" => "world (foo)"
*/
export function extractLastParenthesisContent(str: string): string | undefined {
const chars: string[] = [];
for (const char of str) {
chars.push(char);
}
const extractedChars: string[] = [];
let openingCount = 0;
loop_through_char: for (let i = chars.length - 1; i >= 0; i--) {
const char = chars[i];
if (i === chars.length - 1) {
if (char !== ")") {
return undefined;
}
continue;
}
switch (char) {
case ")":
openingCount++;
break;
case "(":
if (openingCount === 0) {
return extractedChars.join("");
}
openingCount--;
break;
}
extractedChars.unshift(char);
}
return undefined;
}

View File

@ -212,6 +212,73 @@ export function keycloakify(params: keycloakify.Params) {
force: true force: true
} }
); );
},
transformIndexHtml: html => {
const doReadKcContextFromUrl =
process.env.NODE_ENV === "development" &&
process.env[
VITE_PLUGIN_SUB_SCRIPTS_ENV_NAMES.READ_KC_CONTEXT_FROM_URL
] === "true";
if (!doReadKcContextFromUrl) {
return html;
}
const scriptContent = `
(()=>{
const kcContext = (()=>{
const paramName= "kcContext";
read_from_url_case: {
const url = new URL(window.location.href);
const paramValue = url.searchParams.get(paramName);
if( paramValue === null ){
break read_from_url_case;
}
url.searchParams.delete(paramName);
window.history.replaceState({}, "", url);
const kcContext = JSON.parse(decodeURIComponent(paramValue));
sessionStorage.setItem(paramName, JSON.stringify(kcContext));
return kcContext;
}
read_from_session_storage_case: {
const paramValue = sessionStorage.getItem(paramName);
if( paramValue === null ){
break read_from_session_storage_case;
}
return JSON.parse(paramValue);
}
return undefined;
})();
if( kcContext === undefined ){
return;
}
window.kcContext = kcContext;
})();
`;
return html.replace(/<head>/, `<head><script>${scriptContent}</script>`);
} }
} satisfies Plugin; } satisfies Plugin;

View File

@ -17,6 +17,7 @@ export const Default: Story = {
render: () => ( render: () => (
<KcPageStory <KcPageStory
kcContext={{ kcContext={{
messageHeader: "Message header",
message: { message: {
summary: "Server info message" summary: "Server info message"
} }
@ -29,6 +30,7 @@ export const WithLinkBack: Story = {
render: () => ( render: () => (
<KcPageStory <KcPageStory
kcContext={{ kcContext={{
messageHeader: "Message header",
message: { message: {
summary: "Server message" summary: "Server message"
}, },
@ -42,6 +44,7 @@ export const WithRequiredActions: Story = {
render: () => ( render: () => (
<KcPageStory <KcPageStory
kcContext={{ kcContext={{
messageHeader: "Message header",
message: { message: {
summary: "Required actions: " summary: "Required actions: "
}, },
@ -55,42 +58,3 @@ export const WithRequiredActions: Story = {
/> />
) )
}; };
export const WithPageRedirect: Story = {
render: () => (
<KcPageStory
kcContext={{
message: { summary: "You will be redirected shortly." },
pageRedirectUri: "https://example.com"
}}
/>
)
};
export const WithoutClientBaseUrl: Story = {
render: () => (
<KcPageStory
kcContext={{
message: { summary: "No client base URL defined." },
client: { baseUrl: undefined }
}}
/>
)
};
export const WithMessageHeader: Story = {
render: () => (
<KcPageStory
kcContext={{
messageHeader: "Important Notice",
message: { summary: "This is an important message." }
}}
/>
)
};
export const WithAdvancedMessage: Story = {
render: () => (
<KcPageStory
kcContext={{
message: { summary: "Please take note of this <strong>important</strong> information." }
}}
/>
)
};

View File

@ -1,18 +0,0 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { createKcPageStory } from "../KcPageStory";
const { KcPageStory } = createKcPageStory({ pageId: "login-device-verify-user-code.ftl" });
const meta = {
title: "login/login-device-verify-user-code.ftl",
component: KcPageStory
} satisfies Meta<typeof KcPageStory>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
render: () => <KcPageStory />
};

View File

@ -62,3 +62,65 @@ type I18n = typeof ofTypeI18n;
assert<Equals<typeof node, JSX.Element>>; 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"
}
});