Compare commits
58 Commits
hmr_in_sta
...
v11.6.0
Author | SHA1 | Date | |
---|---|---|---|
94b7d2b85b | |||
9a4f89e69d | |||
a5ba03cca0 | |||
5203813e7b | |||
0e461fd072 | |||
326411ca5d | |||
c39c450e90 | |||
3191954dda | |||
20c6d2ea86 | |||
f43544e134 | |||
474a863708 | |||
0bacdca8fe | |||
f023d6bca7 | |||
150b01f1f3 | |||
2b2bb20658 | |||
70570faed6 | |||
5d3b7c9a82 | |||
95b9b12a3b | |||
0e027055cb | |||
e47b002535 | |||
8dd6dcd1fc | |||
10cfa1cf41 | |||
3938584aeb | |||
163b060dc5 | |||
67f8ae41fc | |||
b6e9fe2585 | |||
5b83bd8fa9 | |||
d0f43b6318 | |||
df338ed6a0 | |||
295994d02a | |||
f9e15f93c4 | |||
2659cf391c | |||
76416ddd5b | |||
8e8a0ccf54 | |||
db0ec954df | |||
dc942aa5de | |||
029cfcb591 | |||
b1b6919395 | |||
9185740d35 | |||
8d59fe7b67 | |||
92b505dd56 | |||
c0e6661d3d | |||
0cae2c68d8 | |||
1e43343529 | |||
0a74dca7c2 | |||
a66a373256 | |||
606cf7ad02 | |||
5225749c7b | |||
819e3833ad | |||
b0ba37fcc4 | |||
f4829b557f | |||
60a9b5a693 | |||
c323b94a8c | |||
4bbc0241ec | |||
5a7dacfcdd | |||
7e05e1bf0c | |||
1530ca32c8 | |||
ed054f131a |
@ -12,4 +12,5 @@ node_modules/
|
||||
/sample_react_project/
|
||||
/sample_custom_react_project/
|
||||
/keycloakify_starter_test/
|
||||
/.storybook/static/keycloak-resources/
|
||||
/.storybook/static/keycloak-resources/
|
||||
/src/bin/start-keycloak/*.json
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "keycloakify",
|
||||
"version": "11.3.32",
|
||||
"version": "11.6.0",
|
||||
"description": "Framework to create custom Keycloak UIs",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
39
scripts/build/downloadKeycloakifyLogging.ts
Normal file
39
scripts/build/downloadKeycloakifyLogging.ts
Normal 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);
|
||||
}
|
@ -7,6 +7,7 @@ import { createAccountV1Dir } from "./createAccountV1Dir";
|
||||
import chalk from "chalk";
|
||||
import { run } from "../shared/run";
|
||||
import { vendorFrontendDependencies } from "./vendorFrontendDependencies";
|
||||
import { downloadKeycloakifyLogging } from "./downloadKeycloakifyLogging";
|
||||
|
||||
(async () => {
|
||||
console.log(chalk.cyan("Building Keycloakify..."));
|
||||
@ -148,9 +149,6 @@ import { vendorFrontendDependencies } from "./vendorFrontendDependencies";
|
||||
fs.cpSync(dirBasename, destDirPath, { recursive: true });
|
||||
}
|
||||
|
||||
await createPublicKeycloakifyDevResourcesDir();
|
||||
await createAccountV1Dir();
|
||||
|
||||
transformCodebase({
|
||||
srcDirPath: join("stories"),
|
||||
destDirPath: join("dist", "stories"),
|
||||
@ -163,6 +161,12 @@ import { vendorFrontendDependencies } from "./vendorFrontendDependencies";
|
||||
}
|
||||
});
|
||||
|
||||
await createPublicKeycloakifyDevResourcesDir();
|
||||
await createAccountV1Dir();
|
||||
await downloadKeycloakifyLogging({
|
||||
distDirPath: join(process.cwd(), "dist")
|
||||
});
|
||||
|
||||
console.log(
|
||||
chalk.green(`✓ built in ${((Date.now() - startTime) / 1000).toFixed(2)}s`)
|
||||
);
|
||||
|
@ -67,7 +67,9 @@ export function vendorFrontendDependencies(params: { distDirPath: string }) {
|
||||
)
|
||||
);
|
||||
|
||||
run(`npx webpack --config ${webpackConfigJsFilePath}`);
|
||||
run(`npx webpack --config ${pathBasename(webpackConfigJsFilePath)}`, {
|
||||
cwd: pathDirname(webpackConfigJsFilePath)
|
||||
});
|
||||
|
||||
fs.readdirSync(webpackOutputDirPath)
|
||||
.filter(fileBasename => !fileBasename.endsWith(".txt"))
|
||||
|
@ -1,65 +1,14 @@
|
||||
import { CONTAINER_NAME } from "../src/bin/shared/constants";
|
||||
import child_process from "child_process";
|
||||
import { SemVer } from "../src/bin/tools/SemVer";
|
||||
import { join as pathJoin, relative as pathRelative } from "path";
|
||||
import { dumpContainerConfig } from "../src/bin/start-keycloak/realmConfig/dumpContainerConfig";
|
||||
import { cacheDirPath } from "./shared/cacheDirPath";
|
||||
import { getThisCodebaseRootDirPath } from "../src/bin/tools/getThisCodebaseRootDirPath";
|
||||
import { writeRealmJsonFile } from "../src/bin/start-keycloak/realmConfig/ParsedRealmJson";
|
||||
import { join as pathJoin } from "path";
|
||||
import chalk from "chalk";
|
||||
import { Deferred } from "evt/tools/Deferred";
|
||||
import { assert, is } from "tsafe/assert";
|
||||
import { run } from "./shared/run";
|
||||
|
||||
(async () => {
|
||||
{
|
||||
const dCompleted = new Deferred<void>();
|
||||
|
||||
const child = child_process.spawn(
|
||||
"docker",
|
||||
[
|
||||
...["exec", CONTAINER_NAME],
|
||||
...["/opt/keycloak/bin/kc.sh", "export"],
|
||||
...["--dir", "/tmp"],
|
||||
...["--realm", "myrealm"],
|
||||
...["--users", "realm_file"]
|
||||
],
|
||||
{ shell: true }
|
||||
);
|
||||
|
||||
let output = "";
|
||||
|
||||
const onExit = (code: number | null) => {
|
||||
dCompleted.reject(new Error(`Exited with code ${code}`));
|
||||
};
|
||||
|
||||
child.on("exit", onExit);
|
||||
|
||||
child.stdout.on("data", data => {
|
||||
const outputStr = data.toString("utf8");
|
||||
|
||||
if (outputStr.includes("Export finished successfully")) {
|
||||
child.removeListener("exit", onExit);
|
||||
|
||||
child.kill();
|
||||
|
||||
dCompleted.resolve();
|
||||
}
|
||||
|
||||
output += outputStr;
|
||||
});
|
||||
|
||||
child.stderr.on("data", data => (output += chalk.red(data.toString("utf8"))));
|
||||
|
||||
try {
|
||||
await dCompleted.pr;
|
||||
} catch (error) {
|
||||
assert(is<Error>(error));
|
||||
|
||||
console.log(chalk.red(error.message));
|
||||
|
||||
console.log(output);
|
||||
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
const keycloakMajorVersionNumber = SemVer.parse(
|
||||
child_process
|
||||
.execSync(`docker inspect --format '{{.Config.Image}}' ${CONTAINER_NAME}`)
|
||||
@ -68,19 +17,29 @@ import { run } from "./shared/run";
|
||||
.split(":")[1]
|
||||
).major;
|
||||
|
||||
const targetFilePath = pathRelative(
|
||||
process.cwd(),
|
||||
pathJoin(
|
||||
__dirname,
|
||||
"..",
|
||||
"src",
|
||||
"bin",
|
||||
"start-keycloak",
|
||||
`myrealm-realm-${keycloakMajorVersionNumber}.json`
|
||||
)
|
||||
const parsedRealmJson = await dumpContainerConfig({
|
||||
buildContext: {
|
||||
cacheDirPath
|
||||
},
|
||||
keycloakMajorVersionNumber,
|
||||
realmName: "myrealm"
|
||||
});
|
||||
|
||||
const realmJsonFilePath = pathJoin(
|
||||
getThisCodebaseRootDirPath(),
|
||||
"src",
|
||||
"bin",
|
||||
"start-keycloak",
|
||||
"realmConfig",
|
||||
"defaultConfig",
|
||||
`realm-kc-${keycloakMajorVersionNumber}.json`
|
||||
);
|
||||
|
||||
run(`docker cp ${CONTAINER_NAME}:/tmp/myrealm-realm.json ${targetFilePath}`);
|
||||
await writeRealmJsonFile({
|
||||
parsedRealmJson,
|
||||
realmJsonFilePath,
|
||||
keycloakMajorVersionNumber
|
||||
});
|
||||
|
||||
console.log(`${chalk.green(`✓ Exported realm to`)} ${chalk.bold(targetFilePath)}`);
|
||||
console.log(chalk.green(`Realm config dumped to ${realmJsonFilePath}`));
|
||||
})();
|
||||
|
@ -45,7 +45,10 @@ const commonThirdPartyDeps = [
|
||||
.replace(/"!\.\/dist\//g, '"!./');
|
||||
|
||||
modifiedPackageJsonContent = JSON.stringify(
|
||||
{ ...JSON.parse(modifiedPackageJsonContent), version: "0.0.0" },
|
||||
{
|
||||
...JSON.parse(modifiedPackageJsonContent),
|
||||
version: `0.0.0-rc.${~~(Math.random() * 1000000)}`
|
||||
},
|
||||
null,
|
||||
4
|
||||
);
|
||||
|
@ -1,3 +1,4 @@
|
||||
import type { JSX } from "keycloakify/tools/JSX";
|
||||
import { type TemplateProps, type ClassKey } from "keycloakify/account/TemplateProps";
|
||||
import type { LazyOrNot } from "keycloakify/tools/LazyOrNot";
|
||||
|
||||
|
@ -1,11 +1,9 @@
|
||||
import { i18nBuilder } from "keycloakify/account";
|
||||
import type { ThemeName } from "../kc.gen";
|
||||
|
||||
const { useI18n, ofTypeI18n } = i18nBuilder
|
||||
.withThemeName<ThemeName>()
|
||||
.withExtraLanguages({})
|
||||
.withCustomTranslations({})
|
||||
.build();
|
||||
/** @see: https://docs.keycloakify.dev/i18n */
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { useI18n, ofTypeI18n } = i18nBuilder.withThemeName<ThemeName>().build();
|
||||
|
||||
type I18n = typeof ofTypeI18n;
|
||||
|
||||
|
@ -11,7 +11,11 @@ import * as fs from "fs";
|
||||
import { join as pathJoin } from "path";
|
||||
import type { BuildContext } from "../../shared/buildContext";
|
||||
import { assert } from "tsafe/assert";
|
||||
import { type ThemeType, WELL_KNOWN_DIRECTORY_BASE_NAME } from "../../shared/constants";
|
||||
import {
|
||||
type ThemeType,
|
||||
WELL_KNOWN_DIRECTORY_BASE_NAME,
|
||||
KEYCLOAKIFY_SPA_DEV_SERVER_PORT
|
||||
} from "../../shared/constants";
|
||||
import { getThisCodebaseRootDirPath } from "../../tools/getThisCodebaseRootDirPath";
|
||||
|
||||
export type BuildContextLike = BuildContextLike_replaceImportsInJsCode &
|
||||
@ -116,6 +120,7 @@ export function generateFtlFilesCodeFactory(params: {
|
||||
.replace("{{themeVersion}}", buildContext.themeVersion)
|
||||
.replace("{{fieldNames}}", fieldNames.map(name => `"${name}"`).join(", "))
|
||||
.replace("{{RESOURCES_COMMON}}", WELL_KNOWN_DIRECTORY_BASE_NAME.RESOURCES_COMMON)
|
||||
.replace("{{KEYCLOAKIFY_SPA_DEV_SERVER_PORT}}", KEYCLOAKIFY_SPA_DEV_SERVER_PORT)
|
||||
.replace(
|
||||
"{{userDefinedExclusions}}",
|
||||
buildContext.kcContextExclusionsFtlCode ?? ""
|
||||
|
@ -101,7 +101,7 @@ redirect_to_dev_server: {
|
||||
break redirect_to_dev_server;
|
||||
}
|
||||
|
||||
const devSeverPort = kcContext.properties.KEYCLOAKIFY_SPA_DEV_SERVER_PORT;
|
||||
const devSeverPort = kcContext.properties.{{KEYCLOAKIFY_SPA_DEV_SERVER_PORT}};
|
||||
|
||||
if( !devSeverPort ){
|
||||
break redirect_to_dev_server;
|
||||
@ -115,7 +115,7 @@ redirect_to_dev_server: {
|
||||
|
||||
console.log(kcContext);
|
||||
|
||||
redirectUrl.searchParams.set("kcContext", encodeURIComponent(JSON.stringify(kcContext)) );
|
||||
redirectUrl.searchParams.set("kcContext", encodeURIComponent(JSON.stringify(kcContext)));
|
||||
|
||||
window.location.href = redirectUrl.toString();
|
||||
|
||||
|
@ -40,6 +40,7 @@ import { escapeStringForPropertiesFile } from "../../tools/escapeStringForProper
|
||||
import * as child_process from "child_process";
|
||||
import { getThisCodebaseRootDirPath } from "../../tools/getThisCodebaseRootDirPath";
|
||||
import propertiesParser from "properties-parser";
|
||||
import { createObjectThatThrowsIfAccessed } from "../../tools/createObjectThatThrowsIfAccessed";
|
||||
|
||||
export type BuildContextLike = BuildContextLike_kcContextExclusionsFtlCode &
|
||||
BuildContextLike_generateMessageProperties & {
|
||||
@ -256,15 +257,17 @@ export async function generateResources(params: {
|
||||
writeMessagePropertiesFiles;
|
||||
}
|
||||
|
||||
bring_in_spas_messages: {
|
||||
bring_in_account_spa_messages: {
|
||||
if (!isSpa) {
|
||||
break bring_in_spas_messages;
|
||||
break bring_in_account_spa_messages;
|
||||
}
|
||||
|
||||
assert(themeType !== "login");
|
||||
if (themeType !== "account") {
|
||||
break bring_in_account_spa_messages;
|
||||
}
|
||||
|
||||
const accountUiDirPath = child_process
|
||||
.execSync(`npm list @keycloakify/keycloak-${themeType}-ui --parseable`, {
|
||||
.execSync(`npm list @keycloakify/keycloak-account-ui --parseable`, {
|
||||
cwd: pathDirname(buildContext.packageJsonFilePath)
|
||||
})
|
||||
.toString("utf8")
|
||||
@ -279,7 +282,7 @@ export async function generateResources(params: {
|
||||
}
|
||||
|
||||
const messagesDirPath_dest = pathJoin(
|
||||
getThemeTypeDirPath({ themeName, themeType }),
|
||||
getThemeTypeDirPath({ themeName, themeType: "account" }),
|
||||
"messages"
|
||||
);
|
||||
|
||||
@ -291,7 +294,7 @@ export async function generateResources(params: {
|
||||
apply_theme_changes: {
|
||||
const messagesDirPath_theme = pathJoin(
|
||||
buildContext.themeSrcDirPath,
|
||||
themeType,
|
||||
"account",
|
||||
"messages"
|
||||
);
|
||||
|
||||
@ -339,6 +342,159 @@ export async function generateResources(params: {
|
||||
);
|
||||
}
|
||||
|
||||
bring_in_admin_messages: {
|
||||
if (themeType !== "admin") {
|
||||
break bring_in_admin_messages;
|
||||
}
|
||||
|
||||
const messagesDirPath_theme = pathJoin(
|
||||
buildContext.themeSrcDirPath,
|
||||
"admin",
|
||||
"i18n"
|
||||
);
|
||||
|
||||
assert(
|
||||
fs.existsSync(messagesDirPath_theme),
|
||||
`${messagesDirPath_theme} is supposed to exist`
|
||||
);
|
||||
|
||||
const propertiesByLang: Record<
|
||||
string,
|
||||
{
|
||||
base: Buffer;
|
||||
override: Buffer | undefined;
|
||||
overrideByThemeName: Record<string, Buffer>;
|
||||
}
|
||||
> = {};
|
||||
|
||||
fs.readdirSync(messagesDirPath_theme).forEach(basename => {
|
||||
type ParsedBasename = { lang: string } & (
|
||||
| {
|
||||
isOverride: false;
|
||||
}
|
||||
| {
|
||||
isOverride: true;
|
||||
themeName: string | undefined;
|
||||
}
|
||||
);
|
||||
|
||||
const parsedBasename = ((): ParsedBasename | undefined => {
|
||||
const match = basename.match(/^messages_([^.]+)\.properties$/);
|
||||
|
||||
if (match === null) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const discriminator = match[1];
|
||||
|
||||
const split = discriminator.split("_override");
|
||||
|
||||
if (split.length === 1) {
|
||||
return {
|
||||
lang: discriminator,
|
||||
isOverride: false
|
||||
};
|
||||
}
|
||||
|
||||
assert(split.length === 2);
|
||||
|
||||
if (split[1] === "") {
|
||||
return {
|
||||
lang: split[0],
|
||||
isOverride: true,
|
||||
themeName: undefined
|
||||
};
|
||||
}
|
||||
|
||||
const match2 = split[1].match(/^_(.+)$/);
|
||||
|
||||
assert(match2 !== null);
|
||||
|
||||
return {
|
||||
lang: split[0],
|
||||
isOverride: true,
|
||||
themeName: match2[1]
|
||||
};
|
||||
})();
|
||||
|
||||
if (parsedBasename === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
propertiesByLang[parsedBasename.lang] ??= {
|
||||
base: createObjectThatThrowsIfAccessed<Buffer>({
|
||||
debugMessage: `No base ${parsedBasename.lang} translation for admin theme`
|
||||
}),
|
||||
override: undefined,
|
||||
overrideByThemeName: {}
|
||||
};
|
||||
|
||||
const buffer = fs.readFileSync(pathJoin(messagesDirPath_theme, basename));
|
||||
|
||||
if (parsedBasename.isOverride === false) {
|
||||
propertiesByLang[parsedBasename.lang].base = buffer;
|
||||
return;
|
||||
}
|
||||
|
||||
if (parsedBasename.themeName === undefined) {
|
||||
propertiesByLang[parsedBasename.lang].override = buffer;
|
||||
return;
|
||||
}
|
||||
|
||||
propertiesByLang[parsedBasename.lang].overrideByThemeName[
|
||||
parsedBasename.themeName
|
||||
] = buffer;
|
||||
});
|
||||
|
||||
writeMessagePropertiesFilesByThemeType.admin = ({
|
||||
messageDirPath,
|
||||
themeName
|
||||
}) => {
|
||||
if (!fs.existsSync(messageDirPath)) {
|
||||
fs.mkdirSync(messageDirPath, { recursive: true });
|
||||
}
|
||||
|
||||
Object.entries(propertiesByLang).forEach(
|
||||
([lang, { base, override, overrideByThemeName }]) => {
|
||||
(languageTags ??= []).push(lang);
|
||||
|
||||
const messages = propertiesParser.parse(base.toString("utf8"));
|
||||
|
||||
if (override !== undefined) {
|
||||
const overrideMessages = propertiesParser.parse(
|
||||
override.toString("utf8")
|
||||
);
|
||||
|
||||
Object.entries(overrideMessages).forEach(
|
||||
([key, value]) => (messages[key] = value)
|
||||
);
|
||||
}
|
||||
|
||||
if (themeName in overrideByThemeName) {
|
||||
const overrideMessages = propertiesParser.parse(
|
||||
overrideByThemeName[themeName].toString("utf8")
|
||||
);
|
||||
|
||||
Object.entries(overrideMessages).forEach(
|
||||
([key, value]) => (messages[key] = value)
|
||||
);
|
||||
}
|
||||
|
||||
const editor = propertiesParser.createEditor();
|
||||
|
||||
Object.entries(messages).forEach(([key, value]) => {
|
||||
editor.set(key, value);
|
||||
});
|
||||
|
||||
fs.writeFileSync(
|
||||
pathJoin(messageDirPath, `messages_${lang}.properties`),
|
||||
Buffer.from(editor.toString(), "utf8")
|
||||
);
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
keycloak_static_resources: {
|
||||
if (isSpa) {
|
||||
break keycloak_static_resources;
|
||||
|
@ -5,9 +5,6 @@ import { readThisNpmPackageVersion } from "./tools/readThisNpmPackageVersion";
|
||||
import * as child_process from "child_process";
|
||||
import { assertNoPnpmDlx } from "./tools/assertNoPnpmDlx";
|
||||
import { getBuildContext } from "./shared/buildContext";
|
||||
import { SemVer } from "./tools/SemVer";
|
||||
import { assert, is } from "tsafe/assert";
|
||||
import chalk from "chalk";
|
||||
|
||||
type CliCommandOptions = {
|
||||
projectDirPath: string | undefined;
|
||||
@ -137,47 +134,11 @@ program
|
||||
handler: async ({ projectDirPath, keycloakVersion, port, realmJsonFilePath }) => {
|
||||
const { command } = await import("./start-keycloak");
|
||||
|
||||
validate_keycloak_version: {
|
||||
if (keycloakVersion === undefined) {
|
||||
break validate_keycloak_version;
|
||||
}
|
||||
|
||||
const isValidVersion = (() => {
|
||||
if (typeof keycloakVersion === "number") {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
SemVer.parse(keycloakVersion);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
|
||||
return;
|
||||
})();
|
||||
|
||||
if (isValidVersion) {
|
||||
break validate_keycloak_version;
|
||||
}
|
||||
|
||||
console.log(
|
||||
chalk.red(
|
||||
[
|
||||
`Invalid Keycloak version: ${keycloakVersion}`,
|
||||
"It should be a valid semver version example: 26.0.4"
|
||||
].join(" ")
|
||||
)
|
||||
);
|
||||
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
assert(is<string | undefined>(keycloakVersion));
|
||||
|
||||
await command({
|
||||
buildContext: getBuildContext({ projectDirPath }),
|
||||
cliCommandOptions: {
|
||||
keycloakVersion,
|
||||
keycloakVersion:
|
||||
keycloakVersion === undefined ? undefined : `${keycloakVersion}`,
|
||||
port,
|
||||
realmJsonFilePath
|
||||
}
|
||||
@ -298,11 +259,12 @@ program
|
||||
.option({
|
||||
key: "file",
|
||||
name: (() => {
|
||||
const name = "file";
|
||||
const long = "file";
|
||||
const short = "f";
|
||||
|
||||
optionsKeys.push(name);
|
||||
optionsKeys.push(long, short);
|
||||
|
||||
return name;
|
||||
return { long, short };
|
||||
})(),
|
||||
description: [
|
||||
"Relative path of the file relative to the directory of your keycloak theme source",
|
||||
|
@ -32,38 +32,20 @@ export async function getUiModuleFileSourceCodeReadyToBeCopied(params: {
|
||||
await fsPr.readFile(pathJoin(uiModuleDirPath, KEYCLOAK_THEME, fileRelativePath))
|
||||
).toString("utf8");
|
||||
|
||||
const toComment = (lines: string[]) => {
|
||||
for (const ext of [".ts", ".tsx", ".css", ".less", ".sass", ".js", ".jsx"]) {
|
||||
if (!fileRelativePath.endsWith(ext)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return [`/**`, ...lines.map(line => ` * ${line}`), ` */`].join("\n");
|
||||
}
|
||||
|
||||
if (fileRelativePath.endsWith(".html")) {
|
||||
return [`<!--`, ...lines.map(line => ` ${line}`), `-->`].join("\n");
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const comment = toComment(
|
||||
isForEjection
|
||||
sourceCode = addCommentToSourceCode({
|
||||
sourceCode,
|
||||
fileRelativePath,
|
||||
commentLines: isForEjection
|
||||
? [`This file was ejected from ${uiModuleName} version ${uiModuleVersion}.`]
|
||||
: [
|
||||
`WARNING: Before modifying this file run the following command:`,
|
||||
``,
|
||||
`$ npx keycloakify eject-file --file ${fileRelativePath.split(pathSep).join("/")}`,
|
||||
`$ npx keycloakify eject-file --file '${fileRelativePath.split(pathSep).join("/")}'`,
|
||||
``,
|
||||
`This file comes from ${uiModuleName} version ${uiModuleVersion}.`,
|
||||
`This file has been copied over to your repo by your postinstall script: \`npx keycloakify postinstall\``
|
||||
]
|
||||
);
|
||||
|
||||
if (comment !== undefined) {
|
||||
sourceCode = [comment, ``, sourceCode].join("\n");
|
||||
}
|
||||
});
|
||||
|
||||
const destFilePath = pathJoin(buildContext.themeSrcDirPath, fileRelativePath);
|
||||
|
||||
@ -80,3 +62,60 @@ export async function getUiModuleFileSourceCodeReadyToBeCopied(params: {
|
||||
|
||||
return Buffer.from(sourceCode, "utf8");
|
||||
}
|
||||
|
||||
function addCommentToSourceCode(params: {
|
||||
sourceCode: string;
|
||||
fileRelativePath: string;
|
||||
commentLines: string[];
|
||||
}): string {
|
||||
const { sourceCode, fileRelativePath, commentLines } = params;
|
||||
|
||||
const toResult = (comment: string) => {
|
||||
return [comment, ``, sourceCode].join("\n");
|
||||
};
|
||||
|
||||
for (const ext of [".ts", ".tsx", ".css", ".less", ".sass", ".js", ".jsx"]) {
|
||||
if (!fileRelativePath.endsWith(ext)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return toResult(
|
||||
[`/**`, ...commentLines.map(line => ` * ${line}`), ` */`].join("\n")
|
||||
);
|
||||
}
|
||||
|
||||
if (fileRelativePath.endsWith(".properties")) {
|
||||
return toResult(commentLines.map(line => `# ${line}`).join("\n"));
|
||||
}
|
||||
|
||||
if (fileRelativePath.endsWith(".html") || fileRelativePath.endsWith(".svg")) {
|
||||
const comment = [
|
||||
`<!--`,
|
||||
...commentLines.map(
|
||||
line =>
|
||||
` ${line.replace("--file", "-f").replace("Before modifying", "Before modifying or replacing")}`
|
||||
),
|
||||
`-->`
|
||||
].join("\n");
|
||||
|
||||
if (fileRelativePath.endsWith(".html") && sourceCode.trim().startsWith("<!")) {
|
||||
const [first, ...rest] = sourceCode.split(">");
|
||||
|
||||
const last = rest.join(">");
|
||||
|
||||
return [`${first}>`, comment, last].join("\n");
|
||||
}
|
||||
|
||||
if (fileRelativePath.endsWith(".svg") && sourceCode.trim().startsWith("<?")) {
|
||||
const [first, ...rest] = sourceCode.split("?>");
|
||||
|
||||
const last = rest.join("?>");
|
||||
|
||||
return [`${first}?>`, comment, last].join("\n");
|
||||
}
|
||||
|
||||
return toResult(comment);
|
||||
}
|
||||
|
||||
return sourceCode;
|
||||
}
|
||||
|
@ -81,3 +81,9 @@ export const CUSTOM_HANDLER_ENV_NAMES = {
|
||||
export const KEYCLOAK_THEME = "keycloak-theme";
|
||||
|
||||
export const KEYCLOAKIFY_SPA_DEV_SERVER_PORT = "KEYCLOAKIFY_SPA_DEV_SERVER_PORT";
|
||||
|
||||
export const KEYCLOAKIFY_LOGGING_VERSION = "1.0.3";
|
||||
|
||||
export const KEYCLOAKIFY_LOGIN_JAR_BASENAME = `keycloakify-logging-${KEYCLOAKIFY_LOGGING_VERSION}.jar`;
|
||||
|
||||
export const TEST_APP_URL = "https://my-theme.keycloakify.dev";
|
||||
|
@ -1,18 +1,17 @@
|
||||
import * as child_process from "child_process";
|
||||
import { Deferred } from "evt/tools/Deferred";
|
||||
import { assert } from "tsafe/assert";
|
||||
import { assert, is, type Equals } from "tsafe/assert";
|
||||
import { id } from "tsafe/id";
|
||||
import type { BuildContext } from "../shared/buildContext";
|
||||
import chalk from "chalk";
|
||||
import { sep as pathSep, join as pathJoin } from "path";
|
||||
import { getAbsoluteAndInOsFormatPath } from "../tools/getAbsoluteAndInOsFormatPath";
|
||||
import * as fs from "fs";
|
||||
import { dirname as pathDirname, relative as pathRelative } from "path";
|
||||
import { z } from "zod";
|
||||
|
||||
export type BuildContextLike = {
|
||||
projectDirPath: string;
|
||||
keycloakifyBuildDirPath: string;
|
||||
bundler: BuildContext["bundler"];
|
||||
projectBuildDirPath: string;
|
||||
packageJsonFilePath: string;
|
||||
};
|
||||
|
||||
@ -23,58 +22,36 @@ export async function appBuild(params: {
|
||||
}): Promise<{ isAppBuildSuccess: boolean }> {
|
||||
const { buildContext } = params;
|
||||
|
||||
switch (buildContext.bundler) {
|
||||
case "vite":
|
||||
return appBuild_vite({ buildContext });
|
||||
case "webpack":
|
||||
return appBuild_webpack({ buildContext });
|
||||
}
|
||||
}
|
||||
const { parsedPackageJson } = (() => {
|
||||
type ParsedPackageJson = {
|
||||
scripts?: Record<string, string>;
|
||||
};
|
||||
|
||||
async function appBuild_vite(params: {
|
||||
buildContext: BuildContextLike;
|
||||
}): Promise<{ isAppBuildSuccess: boolean }> {
|
||||
const { buildContext } = params;
|
||||
const zParsedPackageJson = (() => {
|
||||
type TargetType = ParsedPackageJson;
|
||||
|
||||
assert(buildContext.bundler === "vite");
|
||||
const zTargetType = z.object({
|
||||
scripts: z.record(z.string()).optional()
|
||||
});
|
||||
|
||||
const dIsSuccess = new Deferred<boolean>();
|
||||
assert<Equals<z.infer<typeof zTargetType>, TargetType>>();
|
||||
|
||||
console.log(chalk.blue("$ npx vite build"));
|
||||
return id<z.ZodType<TargetType>>(zTargetType);
|
||||
})();
|
||||
const parsedPackageJson = JSON.parse(
|
||||
fs.readFileSync(buildContext.packageJsonFilePath).toString("utf8")
|
||||
);
|
||||
|
||||
const child = child_process.spawn("npx", ["vite", "build"], {
|
||||
cwd: buildContext.projectDirPath,
|
||||
shell: true
|
||||
});
|
||||
zParsedPackageJson.parse(parsedPackageJson);
|
||||
|
||||
child.stdout.on("data", data => {
|
||||
if (data.toString("utf8").includes("gzip:")) {
|
||||
return;
|
||||
}
|
||||
assert(is<ParsedPackageJson>(parsedPackageJson));
|
||||
|
||||
process.stdout.write(data);
|
||||
});
|
||||
return { parsedPackageJson };
|
||||
})();
|
||||
|
||||
child.stderr.on("data", data => process.stderr.write(data));
|
||||
|
||||
child.on("exit", code => dIsSuccess.resolve(code === 0));
|
||||
|
||||
const isSuccess = await dIsSuccess.pr;
|
||||
|
||||
return { isAppBuildSuccess: isSuccess };
|
||||
}
|
||||
|
||||
async function appBuild_webpack(params: {
|
||||
buildContext: BuildContextLike;
|
||||
}): Promise<{ isAppBuildSuccess: boolean }> {
|
||||
const { buildContext } = params;
|
||||
|
||||
assert(buildContext.bundler === "webpack");
|
||||
|
||||
const entries = Object.entries(
|
||||
(JSON.parse(fs.readFileSync(buildContext.packageJsonFilePath).toString("utf8"))
|
||||
.scripts ?? {}) as Record<string, string>
|
||||
).filter(([, scriptCommand]) => scriptCommand.includes("keycloakify build"));
|
||||
const entries = Object.entries(parsedPackageJson.scripts ?? {}).filter(
|
||||
([, scriptCommand]) => scriptCommand.includes("keycloakify build")
|
||||
);
|
||||
|
||||
if (entries.length === 0) {
|
||||
console.log(
|
||||
@ -127,6 +104,76 @@ async function appBuild_webpack(params: {
|
||||
process.exit(-1);
|
||||
}
|
||||
|
||||
common_case: {
|
||||
if (appBuildSubCommands.length !== 1) {
|
||||
break common_case;
|
||||
}
|
||||
|
||||
const [appBuildSubCommand] = appBuildSubCommands;
|
||||
|
||||
const isNpmRunBuild = (() => {
|
||||
for (const packageManager of ["npm", "yarn", "pnpm", "bun", "deno"]) {
|
||||
for (const doUseRun of [true, false]) {
|
||||
if (
|
||||
`${packageManager}${doUseRun ? " run " : " "}build` ===
|
||||
appBuildSubCommand
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
})();
|
||||
|
||||
if (!isNpmRunBuild) {
|
||||
break common_case;
|
||||
}
|
||||
|
||||
const { scripts } = parsedPackageJson;
|
||||
|
||||
assert(scripts !== undefined);
|
||||
|
||||
const buildCmd = scripts.build;
|
||||
|
||||
if (buildCmd !== "tsc && vite build") {
|
||||
break common_case;
|
||||
}
|
||||
|
||||
if (scripts.prebuild !== undefined) {
|
||||
break common_case;
|
||||
}
|
||||
|
||||
if (scripts.postbuild !== undefined) {
|
||||
break common_case;
|
||||
}
|
||||
|
||||
const dIsSuccess = new Deferred<boolean>();
|
||||
|
||||
console.log(chalk.blue("$ npx vite build"));
|
||||
|
||||
const child = child_process.spawn("npx", ["vite", "build"], {
|
||||
cwd: buildContext.projectDirPath,
|
||||
shell: true
|
||||
});
|
||||
|
||||
child.stdout.on("data", data => {
|
||||
if (data.toString("utf8").includes("gzip:")) {
|
||||
return;
|
||||
}
|
||||
|
||||
process.stdout.write(data);
|
||||
});
|
||||
|
||||
child.stderr.on("data", data => process.stderr.write(data));
|
||||
|
||||
child.on("exit", code => dIsSuccess.resolve(code === 0));
|
||||
|
||||
const isSuccess = await dIsSuccess.pr;
|
||||
|
||||
return { isAppBuildSuccess: isSuccess };
|
||||
}
|
||||
|
||||
let commandCwd = pathDirname(buildContext.packageJsonFilePath);
|
||||
|
||||
for (const subCommand of appBuildSubCommands) {
|
||||
|
267
src/bin/start-keycloak/getSupportedDockerImageTags.ts
Normal file
267
src/bin/start-keycloak/getSupportedDockerImageTags.ts
Normal file
@ -0,0 +1,267 @@
|
||||
import fetch from "make-fetch-happen";
|
||||
import type { BuildContext } from "../shared/buildContext";
|
||||
import { assert, type Equals } from "tsafe/assert";
|
||||
import { id } from "tsafe/id";
|
||||
import { z } from "zod";
|
||||
import { SemVer } from "../tools/SemVer";
|
||||
import { exclude } from "tsafe/exclude";
|
||||
import { getSupportedKeycloakMajorVersions } from "./realmConfig/defaultConfig";
|
||||
import { join as pathJoin, dirname as pathDirname } from "path";
|
||||
import * as fs from "fs/promises";
|
||||
import { existsAsync } from "../tools/fs.existsAsync";
|
||||
import { readThisNpmPackageVersion } from "../tools/readThisNpmPackageVersion";
|
||||
import type { ReturnType } from "tsafe";
|
||||
|
||||
export type BuildContextLike = {
|
||||
fetchOptions: BuildContext["fetchOptions"];
|
||||
cacheDirPath: string;
|
||||
};
|
||||
|
||||
assert<BuildContext extends BuildContextLike ? true : false>;
|
||||
|
||||
export async function getSupportedDockerImageTags(params: {
|
||||
buildContext: BuildContextLike;
|
||||
}): Promise<{
|
||||
allSupportedTags: string[];
|
||||
latestMajorTags: string[];
|
||||
}> {
|
||||
const { buildContext } = params;
|
||||
|
||||
{
|
||||
const result = await getCachedValue({ cacheDirPath: buildContext.cacheDirPath });
|
||||
|
||||
if (result !== undefined) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
const tags_queryResponse: string[] = [];
|
||||
|
||||
await (async function callee(url: string) {
|
||||
const r = await fetch(url, buildContext.fetchOptions);
|
||||
|
||||
await Promise.all([
|
||||
(async () => {
|
||||
tags_queryResponse.push(
|
||||
...z
|
||||
.object({
|
||||
tags: z.array(z.string())
|
||||
})
|
||||
.parse(await r.json()).tags
|
||||
);
|
||||
})(),
|
||||
(async () => {
|
||||
const link = r.headers.get("link");
|
||||
|
||||
if (link === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const split = link.split(";").map(s => s.trim());
|
||||
|
||||
assert(split.length === 2);
|
||||
|
||||
assert(split[1] === 'rel="next"');
|
||||
|
||||
const match = split[0].match(/^<(.+)>$/);
|
||||
|
||||
assert(match !== null);
|
||||
|
||||
const nextUrl = new URL(url).origin + match[1];
|
||||
|
||||
await callee(nextUrl);
|
||||
})()
|
||||
]);
|
||||
})("https://quay.io/v2/keycloak/keycloak/tags/list");
|
||||
|
||||
const supportedKeycloakMajorVersions = getSupportedKeycloakMajorVersions();
|
||||
|
||||
const allSupportedTags_withVersion = tags_queryResponse
|
||||
.map(tag => ({
|
||||
tag,
|
||||
version: (() => {
|
||||
if (tag.includes("-")) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let version: SemVer;
|
||||
|
||||
try {
|
||||
version = SemVer.parse(tag);
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (tag.split(".").length !== 3) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!supportedKeycloakMajorVersions.includes(version.major)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return version;
|
||||
})()
|
||||
}))
|
||||
.map(({ tag, version }) => (version === undefined ? undefined : { tag, version }))
|
||||
.filter(exclude(undefined))
|
||||
.sort(({ version: a }, { version: b }) => SemVer.compare(b, a));
|
||||
|
||||
const latestTagByMajor: Record<number, SemVer | undefined> = {};
|
||||
|
||||
for (const { version } of allSupportedTags_withVersion) {
|
||||
const version_current = latestTagByMajor[version.major];
|
||||
|
||||
if (
|
||||
version_current === undefined ||
|
||||
SemVer.compare(version_current, version) === -1
|
||||
) {
|
||||
latestTagByMajor[version.major] = version;
|
||||
}
|
||||
}
|
||||
|
||||
const latestMajorTags = Object.entries(latestTagByMajor)
|
||||
.sort(([a], [b]) => parseInt(b) - parseInt(a))
|
||||
.map(([, version]) => version)
|
||||
.map(version => {
|
||||
assert(version !== undefined);
|
||||
|
||||
if (!supportedKeycloakMajorVersions.includes(version.major)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return SemVer.stringify(version);
|
||||
})
|
||||
.filter(exclude(undefined));
|
||||
|
||||
const allSupportedTags = allSupportedTags_withVersion.map(({ tag }) => tag);
|
||||
|
||||
const result = {
|
||||
latestMajorTags,
|
||||
allSupportedTags
|
||||
};
|
||||
|
||||
await setCachedValue({ cacheDirPath: buildContext.cacheDirPath, result });
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
const { getCachedValue, setCachedValue } = (() => {
|
||||
type Result = ReturnType<typeof getSupportedDockerImageTags>;
|
||||
|
||||
const zResult = (() => {
|
||||
type TargetType = Result;
|
||||
|
||||
const zTargetType = z.object({
|
||||
allSupportedTags: z.array(z.string()),
|
||||
latestMajorTags: z.array(z.string())
|
||||
});
|
||||
|
||||
type InferredType = z.infer<typeof zTargetType>;
|
||||
|
||||
assert<Equals<TargetType, InferredType>>;
|
||||
|
||||
return id<z.ZodType<TargetType>>(zTargetType);
|
||||
})();
|
||||
|
||||
type Cache = {
|
||||
keycloakifyVersion: string;
|
||||
time: number;
|
||||
result: Result;
|
||||
};
|
||||
|
||||
const zCache = (() => {
|
||||
type TargetType = Cache;
|
||||
|
||||
const zTargetType = z.object({
|
||||
keycloakifyVersion: z.string(),
|
||||
time: z.number(),
|
||||
result: zResult
|
||||
});
|
||||
|
||||
type InferredType = z.infer<typeof zTargetType>;
|
||||
|
||||
assert<Equals<TargetType, InferredType>>;
|
||||
|
||||
return id<z.ZodType<TargetType>>(zTargetType);
|
||||
})();
|
||||
|
||||
let inMemoryCachedResult: Cache["result"] | undefined = undefined;
|
||||
|
||||
function getCacheFilePath(params: { cacheDirPath: string }) {
|
||||
const { cacheDirPath } = params;
|
||||
|
||||
return pathJoin(cacheDirPath, "supportedDockerImageTags.json");
|
||||
}
|
||||
|
||||
async function getCachedValue(params: { cacheDirPath: string }) {
|
||||
const { cacheDirPath } = params;
|
||||
|
||||
if (inMemoryCachedResult !== undefined) {
|
||||
return inMemoryCachedResult;
|
||||
}
|
||||
|
||||
const cacheFilePath = getCacheFilePath({ cacheDirPath });
|
||||
|
||||
if (!(await existsAsync(cacheFilePath))) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let cache: Cache | undefined;
|
||||
|
||||
try {
|
||||
cache = zCache.parse(JSON.parse(await fs.readFile(cacheFilePath, "utf8")));
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (cache.keycloakifyVersion !== readThisNpmPackageVersion()) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (Date.now() - cache.time > 3_600 * 24) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
inMemoryCachedResult = cache.result;
|
||||
|
||||
return cache.result;
|
||||
}
|
||||
|
||||
async function setCachedValue(params: {
|
||||
cacheDirPath: string;
|
||||
result: Cache["result"];
|
||||
}) {
|
||||
const { cacheDirPath, result } = params;
|
||||
|
||||
inMemoryCachedResult = result;
|
||||
|
||||
const cacheFilePath = getCacheFilePath({ cacheDirPath });
|
||||
|
||||
{
|
||||
const dirPath = pathDirname(cacheFilePath);
|
||||
|
||||
if (!(await existsAsync(dirPath))) {
|
||||
await fs.mkdir(dirPath, { recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
await fs.writeFile(
|
||||
cacheFilePath,
|
||||
JSON.stringify(
|
||||
zCache.parse({
|
||||
keycloakifyVersion: readThisNpmPackageVersion(),
|
||||
time: Date.now(),
|
||||
result
|
||||
}),
|
||||
null,
|
||||
2
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
getCachedValue,
|
||||
setCachedValue
|
||||
};
|
||||
})();
|
@ -0,0 +1,118 @@
|
||||
import { z } from "zod";
|
||||
import { assert, type Equals } from "tsafe/assert";
|
||||
import { id } from "tsafe/id";
|
||||
|
||||
export type ParsedRealmJson = {
|
||||
realm: string;
|
||||
loginTheme?: string;
|
||||
accountTheme?: string;
|
||||
adminTheme?: string;
|
||||
emailTheme?: string;
|
||||
eventsListeners: string[];
|
||||
users: {
|
||||
id: string;
|
||||
email: string;
|
||||
username: string;
|
||||
credentials: {
|
||||
type: string /* "password" or something else */;
|
||||
}[];
|
||||
clientRoles?: Record<string, string[]>;
|
||||
}[];
|
||||
roles: {
|
||||
client: Record<
|
||||
string,
|
||||
{
|
||||
name: string;
|
||||
containerId: string; // client id
|
||||
}[]
|
||||
>;
|
||||
};
|
||||
clients: {
|
||||
id: string;
|
||||
clientId: string; // example: realm-management
|
||||
baseUrl?: string;
|
||||
redirectUris?: string[];
|
||||
webOrigins?: string[];
|
||||
attributes?: {
|
||||
"post.logout.redirect.uris"?: string;
|
||||
};
|
||||
protocol?: string;
|
||||
protocolMappers?: {
|
||||
id: string;
|
||||
name: string;
|
||||
protocol: string; // "openid-connect" or something else
|
||||
protocolMapper: string; // "oidc-hardcoded-claim-mapper" or something else
|
||||
consentRequired: boolean;
|
||||
config?: Record<string, string>;
|
||||
}[];
|
||||
}[];
|
||||
};
|
||||
|
||||
export const zParsedRealmJson = (() => {
|
||||
type TargetType = ParsedRealmJson;
|
||||
|
||||
const zTargetType = z.object({
|
||||
realm: z.string(),
|
||||
loginTheme: z.string().optional(),
|
||||
accountTheme: z.string().optional(),
|
||||
adminTheme: z.string().optional(),
|
||||
emailTheme: z.string().optional(),
|
||||
eventsListeners: z.array(z.string()),
|
||||
users: z.array(
|
||||
z.object({
|
||||
id: z.string(),
|
||||
email: z.string(),
|
||||
username: z.string(),
|
||||
credentials: z.array(
|
||||
z.object({
|
||||
type: z.string()
|
||||
})
|
||||
),
|
||||
clientRoles: z.record(z.array(z.string())).optional()
|
||||
})
|
||||
),
|
||||
roles: z.object({
|
||||
client: z.record(
|
||||
z.array(
|
||||
z.object({
|
||||
name: z.string(),
|
||||
containerId: z.string()
|
||||
})
|
||||
)
|
||||
)
|
||||
}),
|
||||
clients: z.array(
|
||||
z.object({
|
||||
id: z.string(),
|
||||
clientId: z.string(),
|
||||
baseUrl: z.string().optional(),
|
||||
redirectUris: z.array(z.string()).optional(),
|
||||
webOrigins: z.array(z.string()).optional(),
|
||||
attributes: z
|
||||
.object({
|
||||
"post.logout.redirect.uris": z.string().optional()
|
||||
})
|
||||
.optional(),
|
||||
protocol: z.string().optional(),
|
||||
protocolMappers: z
|
||||
.array(
|
||||
z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
protocol: z.string(),
|
||||
protocolMapper: z.string(),
|
||||
consentRequired: z.boolean(),
|
||||
config: z.record(z.string()).optional()
|
||||
})
|
||||
)
|
||||
.optional()
|
||||
})
|
||||
)
|
||||
});
|
||||
|
||||
type InferredType = z.infer<typeof zTargetType>;
|
||||
|
||||
assert<Equals<TargetType, InferredType>>;
|
||||
|
||||
return id<z.ZodType<TargetType>>(zTargetType);
|
||||
})();
|
@ -0,0 +1,3 @@
|
||||
export type { ParsedRealmJson } from "./ParsedRealmJson";
|
||||
export { readRealmJsonFile } from "./readRealmJsonFile";
|
||||
export { writeRealmJsonFile } from "./writeRealmJsonFile";
|
@ -0,0 +1,20 @@
|
||||
import { assert } from "tsafe/assert";
|
||||
import { is } from "tsafe/is";
|
||||
import * as fs from "fs";
|
||||
import { type ParsedRealmJson, zParsedRealmJson } from "./ParsedRealmJson";
|
||||
|
||||
export function readRealmJsonFile(params: {
|
||||
realmJsonFilePath: string;
|
||||
}): ParsedRealmJson {
|
||||
const { realmJsonFilePath } = params;
|
||||
|
||||
const parsedRealmJson = JSON.parse(
|
||||
fs.readFileSync(realmJsonFilePath).toString("utf8")
|
||||
) as unknown;
|
||||
|
||||
zParsedRealmJson.parse(parsedRealmJson);
|
||||
|
||||
assert(is<ParsedRealmJson>(parsedRealmJson));
|
||||
|
||||
return parsedRealmJson;
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
import * as fsPr from "fs/promises";
|
||||
import { getIsPrettierAvailable, runPrettier } from "../../../tools/runPrettier";
|
||||
import { canonicalStringify } from "../../../tools/canonicalStringify";
|
||||
import type { ParsedRealmJson } from "./ParsedRealmJson";
|
||||
import { getDefaultConfig } from "../defaultConfig";
|
||||
|
||||
export async function writeRealmJsonFile(params: {
|
||||
realmJsonFilePath: string;
|
||||
parsedRealmJson: ParsedRealmJson;
|
||||
keycloakMajorVersionNumber: number;
|
||||
}): Promise<void> {
|
||||
const { realmJsonFilePath, parsedRealmJson, keycloakMajorVersionNumber } = params;
|
||||
|
||||
let sourceCode = canonicalStringify({
|
||||
data: parsedRealmJson,
|
||||
referenceData: getDefaultConfig({
|
||||
keycloakMajorVersionNumber
|
||||
})
|
||||
});
|
||||
|
||||
if (await getIsPrettierAvailable()) {
|
||||
sourceCode = await runPrettier({
|
||||
sourceCode: sourceCode,
|
||||
filePath: realmJsonFilePath
|
||||
});
|
||||
}
|
||||
|
||||
await fsPr.writeFile(realmJsonFilePath, Buffer.from(sourceCode, "utf8"));
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
import { join as pathJoin, dirname as pathDirname } from "path";
|
||||
import { getThisCodebaseRootDirPath } from "../../../tools/getThisCodebaseRootDirPath";
|
||||
import * as fs from "fs";
|
||||
import { exclude } from "tsafe/exclude";
|
||||
import { assert } from "tsafe/assert";
|
||||
import { readRealmJsonFile } from "../ParsedRealmJson/readRealmJsonFile";
|
||||
import type { ParsedRealmJson } from "../ParsedRealmJson/ParsedRealmJson";
|
||||
|
||||
function getDefaultRealmJsonFilePath(params: { keycloakMajorVersionNumber: number }) {
|
||||
const { keycloakMajorVersionNumber } = params;
|
||||
|
||||
return pathJoin(
|
||||
getThisCodebaseRootDirPath(),
|
||||
"src",
|
||||
"bin",
|
||||
"start-keycloak",
|
||||
"realmConfig",
|
||||
"defaultConfig",
|
||||
`realm-kc-${keycloakMajorVersionNumber}.json`
|
||||
);
|
||||
}
|
||||
|
||||
export const { getSupportedKeycloakMajorVersions } = (() => {
|
||||
let cache: number[] | undefined = undefined;
|
||||
|
||||
function getSupportedKeycloakMajorVersions(): number[] {
|
||||
if (cache !== undefined) {
|
||||
return cache;
|
||||
}
|
||||
|
||||
cache = fs
|
||||
.readdirSync(
|
||||
pathDirname(
|
||||
getDefaultRealmJsonFilePath({ keycloakMajorVersionNumber: 0 })
|
||||
)
|
||||
)
|
||||
.map(fileBasename => {
|
||||
const match = fileBasename.match(/^realm-kc-(\d+)\.json$/);
|
||||
|
||||
if (match === null) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const n = parseInt(match[1]);
|
||||
|
||||
assert(!isNaN(n));
|
||||
|
||||
return n;
|
||||
})
|
||||
.filter(exclude(undefined))
|
||||
.sort((a, b) => b - a);
|
||||
|
||||
return cache;
|
||||
}
|
||||
|
||||
return { getSupportedKeycloakMajorVersions };
|
||||
})();
|
||||
|
||||
export function getDefaultConfig(params: {
|
||||
keycloakMajorVersionNumber: number;
|
||||
}): ParsedRealmJson {
|
||||
const { keycloakMajorVersionNumber } = params;
|
||||
|
||||
assert(
|
||||
getSupportedKeycloakMajorVersions().includes(keycloakMajorVersionNumber),
|
||||
`We do not have a default config for Keycloak ${keycloakMajorVersionNumber}`
|
||||
);
|
||||
|
||||
return readRealmJsonFile({
|
||||
realmJsonFilePath: getDefaultRealmJsonFilePath({
|
||||
keycloakMajorVersionNumber
|
||||
})
|
||||
});
|
||||
}
|
@ -0,0 +1 @@
|
||||
export * from "./defaultConfig";
|
@ -73,7 +73,7 @@
|
||||
"composites": {
|
||||
"realm": ["offline_access", "uma_authorization"],
|
||||
"client": {
|
||||
"account": ["delete-account", "view-profile", "manage-account"]
|
||||
"account": ["view-profile", "manage-account", "delete-account"]
|
||||
}
|
||||
},
|
||||
"clientRole": false,
|
||||
@ -398,6 +398,26 @@
|
||||
"otpPolicyLookAheadWindow": 1,
|
||||
"otpPolicyPeriod": 30,
|
||||
"otpSupportedApplications": ["FreeOTP", "Google Authenticator"],
|
||||
"webAuthnPolicyRpEntityName": "keycloak",
|
||||
"webAuthnPolicySignatureAlgorithms": ["ES256"],
|
||||
"webAuthnPolicyRpId": "",
|
||||
"webAuthnPolicyAttestationConveyancePreference": "not specified",
|
||||
"webAuthnPolicyAuthenticatorAttachment": "not specified",
|
||||
"webAuthnPolicyRequireResidentKey": "not specified",
|
||||
"webAuthnPolicyUserVerificationRequirement": "not specified",
|
||||
"webAuthnPolicyCreateTimeout": 0,
|
||||
"webAuthnPolicyAvoidSameAuthenticatorRegister": false,
|
||||
"webAuthnPolicyAcceptableAaguids": [],
|
||||
"webAuthnPolicyPasswordlessRpEntityName": "keycloak",
|
||||
"webAuthnPolicyPasswordlessSignatureAlgorithms": ["ES256"],
|
||||
"webAuthnPolicyPasswordlessRpId": "",
|
||||
"webAuthnPolicyPasswordlessAttestationConveyancePreference": "not specified",
|
||||
"webAuthnPolicyPasswordlessAuthenticatorAttachment": "not specified",
|
||||
"webAuthnPolicyPasswordlessRequireResidentKey": "not specified",
|
||||
"webAuthnPolicyPasswordlessUserVerificationRequirement": "not specified",
|
||||
"webAuthnPolicyPasswordlessCreateTimeout": 0,
|
||||
"webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister": false,
|
||||
"webAuthnPolicyPasswordlessAcceptableAaguids": [],
|
||||
"users": [
|
||||
{
|
||||
"id": "00a62e75-bcc1-419a-a292-63ee5d161ed3",
|
||||
@ -422,30 +442,43 @@
|
||||
"disableableCredentialTypes": [],
|
||||
"requiredActions": [],
|
||||
"realmRoles": ["default-roles-myrealm"],
|
||||
"clientRoles": {
|
||||
"realm-management": [
|
||||
"create-client",
|
||||
"view-identity-providers",
|
||||
"manage-realm",
|
||||
"query-groups",
|
||||
"manage-clients",
|
||||
"query-users",
|
||||
"realm-admin",
|
||||
"view-authorization",
|
||||
"view-events",
|
||||
"view-clients",
|
||||
"view-realm",
|
||||
"manage-events",
|
||||
"query-realms",
|
||||
"query-clients",
|
||||
"manage-identity-providers",
|
||||
"manage-users",
|
||||
"view-users",
|
||||
"impersonation",
|
||||
"manage-authorization"
|
||||
],
|
||||
"broker": ["read-token"],
|
||||
"account": [
|
||||
"view-profile",
|
||||
"manage-account-links",
|
||||
"view-applications",
|
||||
"manage-consent",
|
||||
"delete-account",
|
||||
"manage-account",
|
||||
"view-consent"
|
||||
]
|
||||
},
|
||||
"notBefore": 0,
|
||||
"groups": []
|
||||
}
|
||||
],
|
||||
"webAuthnPolicyRpEntityName": "keycloak",
|
||||
"webAuthnPolicySignatureAlgorithms": ["ES256"],
|
||||
"webAuthnPolicyRpId": "",
|
||||
"webAuthnPolicyAttestationConveyancePreference": "not specified",
|
||||
"webAuthnPolicyAuthenticatorAttachment": "not specified",
|
||||
"webAuthnPolicyRequireResidentKey": "not specified",
|
||||
"webAuthnPolicyUserVerificationRequirement": "not specified",
|
||||
"webAuthnPolicyCreateTimeout": 0,
|
||||
"webAuthnPolicyAvoidSameAuthenticatorRegister": false,
|
||||
"webAuthnPolicyAcceptableAaguids": [],
|
||||
"webAuthnPolicyPasswordlessRpEntityName": "keycloak",
|
||||
"webAuthnPolicyPasswordlessSignatureAlgorithms": ["ES256"],
|
||||
"webAuthnPolicyPasswordlessRpId": "",
|
||||
"webAuthnPolicyPasswordlessAttestationConveyancePreference": "not specified",
|
||||
"webAuthnPolicyPasswordlessAuthenticatorAttachment": "not specified",
|
||||
"webAuthnPolicyPasswordlessRequireResidentKey": "not specified",
|
||||
"webAuthnPolicyPasswordlessUserVerificationRequirement": "not specified",
|
||||
"webAuthnPolicyPasswordlessCreateTimeout": 0,
|
||||
"webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister": false,
|
||||
"webAuthnPolicyPasswordlessAcceptableAaguids": [],
|
||||
"scopeMappings": [
|
||||
{
|
||||
"clientScope": "offline_access",
|
||||
@ -505,8 +538,12 @@
|
||||
"enabled": true,
|
||||
"alwaysDisplayInConsole": false,
|
||||
"clientAuthenticatorType": "client-secret",
|
||||
"redirectUris": ["/realms/myrealm/account/*"],
|
||||
"webOrigins": [],
|
||||
"redirectUris": [
|
||||
"http://localhost*",
|
||||
"http://127.0.0.1*",
|
||||
"/realms/myrealm/account/*"
|
||||
],
|
||||
"webOrigins": ["*"],
|
||||
"notBefore": 0,
|
||||
"bearerOnly": false,
|
||||
"consentRequired": false,
|
||||
@ -518,6 +555,7 @@
|
||||
"frontchannelLogout": false,
|
||||
"protocol": "openid-connect",
|
||||
"attributes": {
|
||||
"post.logout.redirect.uris": "+",
|
||||
"pkce.code.challenge.method": "S256"
|
||||
},
|
||||
"authenticationFlowBindingOverrides": {},
|
||||
@ -636,7 +674,7 @@
|
||||
"attributes": {
|
||||
"oidc.ciba.grant.enabled": "false",
|
||||
"backchannel.logout.session.required": "true",
|
||||
"login_theme": "keycloakify-starter",
|
||||
"post.logout.redirect.uris": "+",
|
||||
"display.on.consent.screen": "false",
|
||||
"oauth2.device.authorization.grant.enabled": "false",
|
||||
"backchannel.logout.revoke.offline.tokens": "false"
|
||||
@ -694,8 +732,12 @@
|
||||
"enabled": true,
|
||||
"alwaysDisplayInConsole": false,
|
||||
"clientAuthenticatorType": "client-secret",
|
||||
"redirectUris": ["/admin/myrealm/console/*"],
|
||||
"webOrigins": ["+"],
|
||||
"redirectUris": [
|
||||
"http://localhost*",
|
||||
"http://127.0.0.1*",
|
||||
"/admin/myrealm/console/*"
|
||||
],
|
||||
"webOrigins": ["*"],
|
||||
"notBefore": 0,
|
||||
"bearerOnly": false,
|
||||
"consentRequired": false,
|
||||
@ -707,12 +749,31 @@
|
||||
"frontchannelLogout": false,
|
||||
"protocol": "openid-connect",
|
||||
"attributes": {
|
||||
"post.logout.redirect.uris": "+",
|
||||
"pkce.code.challenge.method": "S256"
|
||||
},
|
||||
"authenticationFlowBindingOverrides": {},
|
||||
"fullScopeAllowed": false,
|
||||
"nodeReRegistrationTimeout": 0,
|
||||
"protocolMappers": [
|
||||
{
|
||||
"id": "8fd0d584-7052-4d04-a615-d18a71050873",
|
||||
"name": "allowed-origins",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-hardcoded-claim-mapper",
|
||||
"consentRequired": false,
|
||||
"config": {
|
||||
"userinfo.token.claim": "true",
|
||||
"id.token.claim": "false",
|
||||
"access.token.claim": "true",
|
||||
"claim.name": "allowed-origins",
|
||||
"jsonType.label": "JSON",
|
||||
"access.tokenResponse.claim": "false",
|
||||
"claim.value": "[\"*\"]",
|
||||
"introspection.token.claim": "true",
|
||||
"lightweight.claim": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "7779f8fa-c2fe-4e68-be56-66ee97bf8f13",
|
||||
"name": "locale",
|
||||
@ -757,7 +818,8 @@
|
||||
"consentRequired": false,
|
||||
"config": {
|
||||
"id.token.claim": "true",
|
||||
"access.token.claim": "true"
|
||||
"access.token.claim": "true",
|
||||
"userinfo.token.claim": "true"
|
||||
}
|
||||
}
|
||||
]
|
||||
@ -1205,6 +1267,7 @@
|
||||
"consentRequired": false,
|
||||
"config": {
|
||||
"multivalued": "true",
|
||||
"userinfo.token.claim": "true",
|
||||
"user.attribute": "foo",
|
||||
"id.token.claim": "true",
|
||||
"access.token.claim": "true",
|
||||
@ -1271,11 +1334,11 @@
|
||||
},
|
||||
"smtpServer": {},
|
||||
"loginTheme": "keycloakify-starter",
|
||||
"accountTheme": "keycloakify-starter",
|
||||
"accountTheme": "",
|
||||
"adminTheme": "",
|
||||
"emailTheme": "",
|
||||
"eventsEnabled": false,
|
||||
"eventsListeners": ["jboss-logging"],
|
||||
"eventsListeners": ["keycloakify-logging", "jboss-logging"],
|
||||
"enabledEventTypes": [],
|
||||
"adminEventsEnabled": false,
|
||||
"adminEventsDetailsEnabled": false,
|
||||
@ -1291,14 +1354,14 @@
|
||||
"subComponents": {},
|
||||
"config": {
|
||||
"allowed-protocol-mapper-types": [
|
||||
"oidc-full-name-mapper",
|
||||
"saml-user-property-mapper",
|
||||
"oidc-sha256-pairwise-sub-mapper",
|
||||
"oidc-address-mapper",
|
||||
"oidc-full-name-mapper",
|
||||
"oidc-usermodel-attribute-mapper",
|
||||
"saml-user-attribute-mapper",
|
||||
"oidc-address-mapper",
|
||||
"oidc-sha256-pairwise-sub-mapper",
|
||||
"saml-role-list-mapper",
|
||||
"oidc-usermodel-property-mapper"
|
||||
"oidc-usermodel-property-mapper",
|
||||
"saml-role-list-mapper"
|
||||
]
|
||||
}
|
||||
},
|
||||
@ -1347,14 +1410,14 @@
|
||||
"subComponents": {},
|
||||
"config": {
|
||||
"allowed-protocol-mapper-types": [
|
||||
"oidc-usermodel-property-mapper",
|
||||
"oidc-address-mapper",
|
||||
"oidc-full-name-mapper",
|
||||
"saml-user-property-mapper",
|
||||
"saml-user-attribute-mapper",
|
||||
"oidc-address-mapper",
|
||||
"saml-role-list-mapper",
|
||||
"oidc-sha256-pairwise-sub-mapper",
|
||||
"oidc-usermodel-attribute-mapper",
|
||||
"saml-role-list-mapper"
|
||||
"saml-user-attribute-mapper",
|
||||
"saml-user-property-mapper",
|
||||
"oidc-usermodel-property-mapper",
|
||||
"oidc-usermodel-attribute-mapper"
|
||||
]
|
||||
}
|
||||
},
|
||||
@ -1394,6 +1457,12 @@
|
||||
"providerId": "rsa-generated",
|
||||
"subComponents": {},
|
||||
"config": {
|
||||
"privateKey": [
|
||||
"MIIEpAIBAAKCAQEA+VQAcuaRivrzLVI8H/tt8PKbtRznTQKmmxOdLRR37leY/ph7sFnEmZt6K02Rvut7R0dxUFtTdiEHUKxhyM8CADMznGUjDYj/EXQzLfZ3LEwbwmR39zp+fZL/H24UDO03zt23Ov9C8Aly0ufXZ1Ic1c33KW6UtUEK/3M52pU8Y0daWdjx7nBj1eRlzWfVG+BYotTTWEnFJuEoZPFQMiXqeA5ob1zZdXjL5JDuGEiBsYjtiiaKbKL5545+FmEBnoCmWXqGu0qWxI2TzvV2dohxfl5KjNzRoKt40ydraiVk5rtBpoNDpeEApuphbokH5dJVwJ5cvWu1CSTnYPW2jXeG4wIDAQABAoIBAQDHV6AcPbhz8/xlafBkabQXBwHzJi7QZaQrLN1n44uX5jWOqP+LmdoULjjZUmWKzd98t+QjKUFrmzCsEYcE9G1XF5jWHA6Qjc3ReKRKxVm28wrmu0knQ39KizKrQGmLhEYwgRg0dU5heExzz6VrGD2xu8E3QRBocp6GauwAlXz4qcnTPHOl8OBPeDHAc0RUdaL5+jRLgKQzf9nnnKB19imBKP++zwrwFrkOZti2ZPs1I7j/ym27mHUbi8TDI2VepDX4QwjjC5a+v3vTsVAGE+1tUAZtqpxpIP9hiUkLH3ajyvp3typhnmZHklqsSZdwtRcK94WiMzL3TkiY70y8abMhAoGBAP8I4EQRXxcKfBn23eaRw8Cd4PFrOouz4zFbYLrBODsvXfku/jnQOMFD0If4IzT6y0FGgBd+t/yqnFJi98oZOKm3P8w+NZBXTbFLH8rgmsElXyS0+9LVMjVa7+UlqZB1eRZbUeLREp03Fsz1y2rflnoWgUnpDIlyhmJqGhCsJdebAoGBAPpFmJ9P42mUTeDWpCyCxgg0zpp6rlpAP8StqZkcvr7kYjhbWrJfJuxrTXtzTTA1zZ59L9EvEAxuug/gl9BkuZ11Uzg8ZLOr4gSuAJZlAORaxJlcoylmNMYIL1fP/K0dxhdO0eHZOpPVpBmGctgev2HBtWp9ZwzQ3DddKimZfNZZAoGAfNOOWSKbhT6HgXnYIHtl8YgUynUuYaR5ZfYQwTfDWwyTFVzP5+IndUjI71Qff1XlWBy2o0lNqmijPJveJlfz6PWdT01/kBd7GnTnqbgHZtPw3pmKzCW3fm/1DRZDCUbGLpAh4z9rufF1wnnnx3aKQ1VykId1sGySo+bEvTZVC1MCgYAlv6uWk/ksKpdYi2d14z+1aymieVClAj3cD4meM4y9xDrgXz8d2mZHkKO+NBT3aZYbCqzUs3GLPoRH8stTPm4UxuaHe+yAgTN1Gz2xcYih6OLwct2VV/oryH5Dk3Z8Mhp314amtxozxCydQP8/g9vABfS0HDgX4cTlgOLkJWeD+QKBgQDuRtsstQ4Q3yK44himPi1JQMMvbYAqyGgRxWH8G1Kr41DV2sQ4wt9CbYxeh6RwMsE+YYNMkTAw1kksUTugWdcDnYpcSVG7xHLJk8WMti0WTqI/7KlkoRehXXv18WJNEXaCr5mJTtJL9wuQcd8nhkEDrrCZubZiJzX9IDnEqZc4Mg=="
|
||||
],
|
||||
"certificate": [
|
||||
"MIICnTCCAYUCBgGTy58etTANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdteXJlYWxtMB4XDTI0MTIxNTE4Mzg0M1oXDTM0MTIxNTE4NDAyM1owEjEQMA4GA1UEAwwHbXlyZWFsbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPlUAHLmkYr68y1SPB/7bfDym7Uc500CppsTnS0Ud+5XmP6Ye7BZxJmbeitNkb7re0dHcVBbU3YhB1CsYcjPAgAzM5xlIw2I/xF0My32dyxMG8Jkd/c6fn2S/x9uFAztN87dtzr/QvAJctLn12dSHNXN9ylulLVBCv9zOdqVPGNHWlnY8e5wY9XkZc1n1RvgWKLU01hJxSbhKGTxUDIl6ngOaG9c2XV4y+SQ7hhIgbGI7Yomimyi+eeOfhZhAZ6Apll6hrtKlsSNk871dnaIcX5eSozc0aCreNMna2olZOa7QaaDQ6XhAKbqYW6JB+XSVcCeXL1rtQkk52D1to13huMCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAH/nsEi88hFiNPCWYvTB3lERZpeUCbpDzAXQT/4TONmOw8zi7Cd2OlX8BGBFqjh/fESHv+adlzsY1mUdMvpVaYgHr3gYi8sBSrq5TMUfSYaWp4WCD7utiXXGprG08GCdbye1lpyyNnniWp12Bgjao+rtGamL/M1d6+WZTC+XL+H30u4VHURAiFBsAEoX6tlGV8ynhYOr/b8B43jy0/R0JfrzLjwSKEcA6RfKM7ozbZ0QZuQDALULymPIesrV4mvZ2Qwg4YgpAKaki9Sse45yiIhsIY0p5RnuNZRZnCbukyeBzIyDJobEBGhpui/KT2dqXBlRgRuOhCUf7OGCcPVHKNQ=="
|
||||
],
|
||||
"priority": ["100"]
|
||||
}
|
||||
},
|
||||
@ -1403,6 +1472,12 @@
|
||||
"providerId": "rsa-enc-generated",
|
||||
"subComponents": {},
|
||||
"config": {
|
||||
"privateKey": [
|
||||
"MIIEogIBAAKCAQEAn82AU+InXwYlE8u9lMwhQghZB7oQ71Hg3PdFqS9ICGzw1u1JcENooCsZse55V6nqptdYF1oZA8QrxnhHzCVCGIqFHtXSoPGHVtozO3Fe1cVIVFm1D9TNS3JHe1C8SBQQT4hGItO5cjDyfGdK3x09RkoAcelrzH5uQ78zd0FKHkzbsTMsP2V8V94c35+ViIUjyGhH2T2BpIyGRLignL+6d0wHbw463L1Ewj/J9z8BtNLCH9PaVLWiGQARjlWyL9vtWBig9XXL0Z9tZUuoLihjh4StkXt2lQ++DKxUklsAjyenRAG5d72T2rY8MO5a1Z2ZSt8+s86D5esrAEIFZc9mqwIDAQABAoIBAAmmCcqGzCPDpjd0xMSYMqXfBSkfReh9RBtzXqRhc3L2yO/hMd7yYv3QvGNu56qwWreqJup6CSqeDJqWJpef5EbBDlqXRHltO+O1lwROyxATMlPNes4y5hZZFxHOBSBA/d8fdkSiDf9kDzANuIqSJGH7E93M3zJgq92xTLU1nvkHR/VYJQv+j+Pjye7MWvjIePfhwFeBqEWlWPTlw/080Mpfp8Hhbl6JeKjx2inkSphp43v4wR1Wmp+E2JIHF4P4sVXPPuPf3JDwg5uGOrROw1ziloD3jTI+LnQ+kRm6R2EbqRqqVsehXT7mZy2puQNqVc4vVqWQdxIErMBazYEpZOECgYEA+8PEcDiIPr2PTYZk+/jErRVYwsxyLgDJexPak7onLxLBJRNRnp1Uk6b1LXM6af5qp+Y510kyAe1k+9xkQLx1gW8rMka9rvVsM+1A2ACvF99V23sRw29CVxeFV/zNn83MinYPX5biUl6MkOX2PvWUhdwRGhKByjiYcAeBOsXkz3ECgYEAon2yYXGzph8Vb8Fetv0wFFbjQOixuL02OjVp/nU1XVE8Aw9BJ7uzA6GQ7akPG0HsaUq7AEHP1uUOsJWQTNQ8WYD9LDuDOl/JFqkG+zrmdUdm0mAIYyH1/GBqgaTLvMq78qqosua8BBJojEyoXDz69UBHpu7cwtUgmzRNQSYqgdsCgYASvD3JEBvrd1XLsh2ftqKEMtt5G5e/nqVfuFmCts6lrSKcbLSdNh4OItWJ/VIygxFSz0osoDDNfeoO6Ba5zox8BlbTlfoVpAPaVWSG7n4ZK7CK9bybq5LnQkPVCWYP51O6VhDMz0CmWozhV4ucoc/cqkTHiOsJrm6Bn71ZL1LYsQKBgFNb8qgk4YnGhoPHiuSLbR/yFzGUbqAciXZBMrg0vwS5iPT03XMZytOBDk2uHi7YmgTGLrsKCCrxZaDXiaiwdKliD/+iJEdNHmc+nXNDGzltQOWKGKNqp7wqZllOBqs6wkLSpCrrTec03mejZ/ex3Pj2WgvcnGpjVg/pO/zBLKtjAoGACzGQNEF93fabHQJTsHmb/g+jO2iumjF6ZIWzdFh2KzQABONcoBvy1MJNASFQj3iVy/8kEo4SfmexvMWLBW9igi2z1pHeHY32EuImzuc4xnVDm6dkmDdsO43Ex6CFBx8lM40H4l27mXu+EZRzGClUY8TnmV/FBGmX+LPtOiiwT7s="
|
||||
],
|
||||
"certificate": [
|
||||
"MIICnTCCAYUCBgGTy58fHjANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdteXJlYWxtMB4XDTI0MTIxNTE4Mzg0M1oXDTM0MTIxNTE4NDAyM1owEjEQMA4GA1UEAwwHbXlyZWFsbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJ/NgFPiJ18GJRPLvZTMIUIIWQe6EO9R4Nz3RakvSAhs8NbtSXBDaKArGbHueVep6qbXWBdaGQPEK8Z4R8wlQhiKhR7V0qDxh1baMztxXtXFSFRZtQ/UzUtyR3tQvEgUEE+IRiLTuXIw8nxnSt8dPUZKAHHpa8x+bkO/M3dBSh5M27EzLD9lfFfeHN+flYiFI8hoR9k9gaSMhkS4oJy/undMB28OOty9RMI/yfc/AbTSwh/T2lS1ohkAEY5Vsi/b7VgYoPV1y9GfbWVLqC4oY4eErZF7dpUPvgysVJJbAI8np0QBuXe9k9q2PDDuWtWdmUrfPrPOg+XrKwBCBWXPZqsCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEATwmKBzLiZiUjyB9BWUR4BCXh46DxsiM0BCublewlUFY6FBTn7ea6q3G+X3QP2WM6xa0oAmQz9dq1KChbIoC2WPbceAbwd5XZZfziWsRCv6+xPswtpHPIrsenz8TR4K4P73aeCC+vTVs/y+2tGPEVbnSkcNnOP71hRQGlt0LvjKlEetJSRyYz5depSdJOjl4F3ehpxQtTK/48xUVAytu9ZotJj6AUA7jWFlP5GHgoB+mPk6QTHNWddnc7BQx2FMvg151vxu722ywLh5Dh7WzgFhJNwkX4xpwzhfo0Q1gSygGTdZaJCGj5jfF+KwdiKpN04UxJ8OrRgJqklQgrSVnsgQ=="
|
||||
],
|
||||
"priority": ["100"],
|
||||
"algorithm": ["RSA-OAEP"]
|
||||
}
|
||||
@ -1413,6 +1488,8 @@
|
||||
"providerId": "aes-generated",
|
||||
"subComponents": {},
|
||||
"config": {
|
||||
"kid": ["132fb843-59e9-4f36-ad55-5ce2d3a13fb3"],
|
||||
"secret": ["ETyyqapnrkUsNXLQ-tBVKw"],
|
||||
"priority": ["100"]
|
||||
}
|
||||
},
|
||||
@ -1422,6 +1499,10 @@
|
||||
"providerId": "hmac-generated",
|
||||
"subComponents": {},
|
||||
"config": {
|
||||
"kid": ["5110d380-c930-49d9-b91b-87f338f6170b"],
|
||||
"secret": [
|
||||
"uCpQrJvP5OBuTxXfDb4JRL0bCKpXUgfGn5vb8UvL-Sfs_sZ9rtvBmd6vuFWARqyezjJQtpoNlMv7sXgxkN-yxQ"
|
||||
],
|
||||
"priority": ["100"],
|
||||
"algorithm": ["HS256"]
|
||||
}
|
||||
@ -1454,7 +1535,7 @@
|
||||
"defaultLocale": "en",
|
||||
"authenticationFlows": [
|
||||
{
|
||||
"id": "f7f2b89b-43cb-491d-8e7c-f1814024a6da",
|
||||
"id": "f664efe4-102d-4ec1-bf11-11af67e3f178",
|
||||
"alias": "Account verification options",
|
||||
"description": "Method with which to verity the existing account",
|
||||
"providerId": "basic-flow",
|
||||
@ -1480,7 +1561,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "17cdac6f-d2a3-4907-8d44-a42827610b63",
|
||||
"id": "8a5630c5-eca1-4b6a-8e59-459cb6c84535",
|
||||
"alias": "Authentication Options",
|
||||
"description": "Authentication options.",
|
||||
"providerId": "basic-flow",
|
||||
@ -1514,7 +1595,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "53a3e43f-9468-401f-8051-40f982d12f85",
|
||||
"id": "c1a3eed3-25ce-44ae-93d1-f0b8148a0f8c",
|
||||
"alias": "Browser - Conditional OTP",
|
||||
"description": "Flow to determine if the OTP is required for the authentication",
|
||||
"providerId": "basic-flow",
|
||||
@ -1540,7 +1621,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "26286808-3b7b-43df-b32e-af55a37af2e9",
|
||||
"id": "6eb188ad-1041-44dd-bf8f-37cae0d98bf1",
|
||||
"alias": "Direct Grant - Conditional OTP",
|
||||
"description": "Flow to determine if the OTP is required for the authentication",
|
||||
"providerId": "basic-flow",
|
||||
@ -1566,7 +1647,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "8a6a752a-9a9a-4d38-b1f8-edf0a9433490",
|
||||
"id": "4ee215ac-f4e5-4edb-bf76-65dc9e211543",
|
||||
"alias": "First broker login - Conditional OTP",
|
||||
"description": "Flow to determine if the OTP is required for the authentication",
|
||||
"providerId": "basic-flow",
|
||||
@ -1592,7 +1673,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "a6f6804c-4160-4a84-8a1f-c2747a2d3f27",
|
||||
"id": "5a1eac7e-06a0-46d8-b9ae-1f2c934331f9",
|
||||
"alias": "Handle Existing Account",
|
||||
"description": "Handle what to do if there is existing account with same email/username like authenticated identity provider",
|
||||
"providerId": "basic-flow",
|
||||
@ -1618,7 +1699,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "740baa9e-8328-4035-9e1a-8fc1616d1f0f",
|
||||
"id": "ed165166-4521-4a62-b185-c4b51643cbb1",
|
||||
"alias": "Reset - Conditional OTP",
|
||||
"description": "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.",
|
||||
"providerId": "basic-flow",
|
||||
@ -1644,7 +1725,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "e60187a8-3e16-4a0c-9daa-f3a4a1fcfdba",
|
||||
"id": "4788fb1f-fd81-4f5d-9abe-4199dd641c1e",
|
||||
"alias": "User creation or linking",
|
||||
"description": "Flow for the existing/non-existing user alternatives",
|
||||
"providerId": "basic-flow",
|
||||
@ -1671,7 +1752,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "d959d0c2-4004-4633-b280-f80d6423f574",
|
||||
"id": "d778a70f-f472-4dd3-ac40-cb5612ddc171",
|
||||
"alias": "Verify Existing Account by Re-authentication",
|
||||
"description": "Reauthentication of existing account",
|
||||
"providerId": "basic-flow",
|
||||
@ -1697,7 +1778,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "ba02689d-b9e8-4a4b-8fdd-0d1386b198fc",
|
||||
"id": "9c1ea8ea-7c23-4e60-b02d-1900d9dc4109",
|
||||
"alias": "browser",
|
||||
"description": "browser based authentication",
|
||||
"providerId": "basic-flow",
|
||||
@ -1739,7 +1820,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "f09ac92a-e091-4e84-9cd1-cb905ca57b89",
|
||||
"id": "0ebdf418-d57d-4318-9359-7bd0cb2381f2",
|
||||
"alias": "clients",
|
||||
"description": "Base authentication for clients",
|
||||
"providerId": "client-flow",
|
||||
@ -1781,7 +1862,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "aaf72b22-cec4-4714-93d6-f54d5a986ab8",
|
||||
"id": "5cc89293-c72e-4c5e-b31c-15558588a60d",
|
||||
"alias": "direct grant",
|
||||
"description": "OpenID Connect Resource Owner Grant",
|
||||
"providerId": "basic-flow",
|
||||
@ -1815,7 +1896,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "c4a54bb3-f009-4231-a82b-376c2515e07e",
|
||||
"id": "5ae5a321-ccac-449e-9c19-d6dc22ab8085",
|
||||
"alias": "docker auth",
|
||||
"description": "Used by Docker clients to authenticate against the IDP",
|
||||
"providerId": "basic-flow",
|
||||
@ -1833,7 +1914,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "f55ded54-683a-4f5a-a101-9cfbd7b96781",
|
||||
"id": "7737fdd1-0875-47e6-977b-12561cddfdc3",
|
||||
"alias": "first broker login",
|
||||
"description": "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account",
|
||||
"providerId": "basic-flow",
|
||||
@ -1860,7 +1941,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "931d5a82-378f-4533-8c69-2239a4acd047",
|
||||
"id": "90f975c3-9826-461f-88ca-27c697aff86b",
|
||||
"alias": "forms",
|
||||
"description": "Username, password, otp and other auth forms.",
|
||||
"providerId": "basic-flow",
|
||||
@ -1886,7 +1967,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "22b05374-f480-4ca8-aca8-9db8b6dd1729",
|
||||
"id": "ce2722d5-9f4f-41a2-8f81-e01f7b6cee57",
|
||||
"alias": "http challenge",
|
||||
"description": "An authentication flow based on challenge-response HTTP Authentication Schemes",
|
||||
"providerId": "basic-flow",
|
||||
@ -1912,7 +1993,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "c0371832-e4b7-485e-bf23-6babe4c6ac83",
|
||||
"id": "31b5bfa7-98ad-47a2-b8e6-0669022cd8cb",
|
||||
"alias": "registration",
|
||||
"description": "registration flow",
|
||||
"providerId": "basic-flow",
|
||||
@ -1931,7 +2012,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "4d0445da-073e-465e-b25b-af522915c73f",
|
||||
"id": "bf8a950b-be3b-4e44-8602-64e0bba492eb",
|
||||
"alias": "registration form",
|
||||
"description": "registration form",
|
||||
"providerId": "form-flow",
|
||||
@ -1973,7 +2054,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "740d467f-4203-425b-8203-9bfd3eed25ae",
|
||||
"id": "e3519800-971b-4b1d-b64e-3983ccd02dea",
|
||||
"alias": "reset credentials",
|
||||
"description": "Reset credentials for a user if they forgot their password or something",
|
||||
"providerId": "basic-flow",
|
||||
@ -2015,7 +2096,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "cf1a9af9-dadd-4cb9-a26e-fbbba216f8e1",
|
||||
"id": "9d5a33a2-e777-4beb-95de-b84812f69c56",
|
||||
"alias": "saml ecp",
|
||||
"description": "SAML ECP Profile Authentication Flow",
|
||||
"providerId": "basic-flow",
|
||||
@ -2035,14 +2116,14 @@
|
||||
],
|
||||
"authenticatorConfig": [
|
||||
{
|
||||
"id": "4e65eb4b-9f0a-4ab8-98b2-6daf50cd1bf8",
|
||||
"id": "4901c91d-59bd-4727-b585-8e4e44828d0a",
|
||||
"alias": "create unique user config",
|
||||
"config": {
|
||||
"require.password.update.after.registration": "false"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "5e8dc1c5-1489-4d39-bb75-9c499583b91b",
|
||||
"id": "5062a078-83a7-4933-b0d5-3f75cc2a5003",
|
||||
"alias": "review profile config",
|
||||
"config": {
|
||||
"update.profile.on.first.login": "missing"
|
||||
@ -2132,8 +2213,8 @@
|
||||
"attributes": {
|
||||
"cibaBackchannelTokenDeliveryMode": "poll",
|
||||
"cibaAuthRequestedUserHint": "login_hint",
|
||||
"oauth2DevicePollingInterval": "5",
|
||||
"clientOfflineSessionMaxLifespan": "0",
|
||||
"oauth2DevicePollingInterval": "5",
|
||||
"clientSessionIdleTimeout": "0",
|
||||
"userProfileEnabled": "true",
|
||||
"clientOfflineSessionIdleTimeout": "0",
|
@ -73,7 +73,7 @@
|
||||
"composites": {
|
||||
"realm": ["offline_access", "uma_authorization"],
|
||||
"client": {
|
||||
"account": ["delete-account", "view-profile", "manage-account"]
|
||||
"account": ["view-profile", "manage-account", "delete-account"]
|
||||
}
|
||||
},
|
||||
"clientRole": false,
|
||||
@ -435,13 +435,46 @@
|
||||
"type": "password",
|
||||
"userLabel": "My password",
|
||||
"createdDate": 1716214710762,
|
||||
"secretData": "{\"value\":\"OaI4sKqQn+NZtS6N/bcqoZ8Q+ucpBby1n4XmzVmioKw=\",\"salt\":\"temixVCSbpA7Genml2KTAw==\",\"additionalParameters\":{}}",
|
||||
"secretData": "{\"value\":\"QzJjOdXU0L9Pdxdx1V5xUs7BY9beGlmN8NpR2qiWxbkjrQ434Q1GwSiJKekZQ/zrLDtNZ7sAbVu+SS+XIe9Zaw==\",\"salt\":\"x8cABpa0Hk/nJ2BPKdFXTg==\",\"additionalParameters\":{}}",
|
||||
"credentialData": "{\"hashIterations\":27500,\"algorithm\":\"pbkdf2-sha256\",\"additionalParameters\":{}}"
|
||||
}
|
||||
],
|
||||
"disableableCredentialTypes": [],
|
||||
"requiredActions": [],
|
||||
"realmRoles": ["default-roles-myrealm"],
|
||||
"clientRoles": {
|
||||
"realm-management": [
|
||||
"create-client",
|
||||
"view-identity-providers",
|
||||
"manage-realm",
|
||||
"query-groups",
|
||||
"manage-clients",
|
||||
"query-users",
|
||||
"realm-admin",
|
||||
"view-authorization",
|
||||
"view-events",
|
||||
"view-clients",
|
||||
"view-realm",
|
||||
"manage-events",
|
||||
"query-realms",
|
||||
"query-clients",
|
||||
"manage-identity-providers",
|
||||
"manage-users",
|
||||
"view-users",
|
||||
"impersonation",
|
||||
"manage-authorization"
|
||||
],
|
||||
"broker": ["read-token"],
|
||||
"account": [
|
||||
"view-profile",
|
||||
"manage-account-links",
|
||||
"view-applications",
|
||||
"manage-consent",
|
||||
"delete-account",
|
||||
"manage-account",
|
||||
"view-consent"
|
||||
]
|
||||
},
|
||||
"notBefore": 0,
|
||||
"groups": []
|
||||
}
|
||||
@ -507,8 +540,12 @@
|
||||
"enabled": true,
|
||||
"alwaysDisplayInConsole": false,
|
||||
"clientAuthenticatorType": "client-secret",
|
||||
"redirectUris": ["/realms/myrealm/account/*"],
|
||||
"webOrigins": [],
|
||||
"redirectUris": [
|
||||
"http://localhost*",
|
||||
"http://127.0.0.1*",
|
||||
"/realms/myrealm/account/*"
|
||||
],
|
||||
"webOrigins": ["*"],
|
||||
"notBefore": 0,
|
||||
"bearerOnly": false,
|
||||
"consentRequired": false,
|
||||
@ -643,7 +680,6 @@
|
||||
"attributes": {
|
||||
"oidc.ciba.grant.enabled": "false",
|
||||
"backchannel.logout.session.required": "true",
|
||||
"login_theme": "keycloakify-starter",
|
||||
"post.logout.redirect.uris": "+",
|
||||
"display.on.consent.screen": "false",
|
||||
"oauth2.device.authorization.grant.enabled": "false",
|
||||
@ -704,8 +740,12 @@
|
||||
"enabled": true,
|
||||
"alwaysDisplayInConsole": false,
|
||||
"clientAuthenticatorType": "client-secret",
|
||||
"redirectUris": ["/admin/myrealm/console/*"],
|
||||
"webOrigins": ["+"],
|
||||
"redirectUris": [
|
||||
"http://localhost*",
|
||||
"http://127.0.0.1*",
|
||||
"/admin/myrealm/console/*"
|
||||
],
|
||||
"webOrigins": ["*"],
|
||||
"notBefore": 0,
|
||||
"bearerOnly": false,
|
||||
"consentRequired": false,
|
||||
@ -724,6 +764,24 @@
|
||||
"fullScopeAllowed": false,
|
||||
"nodeReRegistrationTimeout": 0,
|
||||
"protocolMappers": [
|
||||
{
|
||||
"id": "8fd0d584-7052-4d04-a615-d18a71050873",
|
||||
"name": "allowed-origins",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-hardcoded-claim-mapper",
|
||||
"consentRequired": false,
|
||||
"config": {
|
||||
"userinfo.token.claim": "true",
|
||||
"id.token.claim": "false",
|
||||
"access.token.claim": "true",
|
||||
"claim.name": "allowed-origins",
|
||||
"jsonType.label": "JSON",
|
||||
"access.tokenResponse.claim": "false",
|
||||
"claim.value": "[\"*\"]",
|
||||
"introspection.token.claim": "true",
|
||||
"lightweight.claim": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "7779f8fa-c2fe-4e68-be56-66ee97bf8f13",
|
||||
"name": "locale",
|
||||
@ -1284,11 +1342,11 @@
|
||||
},
|
||||
"smtpServer": {},
|
||||
"loginTheme": "keycloakify-starter",
|
||||
"accountTheme": "keycloakify-starter",
|
||||
"accountTheme": "",
|
||||
"adminTheme": "",
|
||||
"emailTheme": "",
|
||||
"eventsEnabled": false,
|
||||
"eventsListeners": ["jboss-logging"],
|
||||
"eventsListeners": ["keycloakify-logging", "jboss-logging"],
|
||||
"enabledEventTypes": [],
|
||||
"adminEventsEnabled": false,
|
||||
"adminEventsDetailsEnabled": false,
|
||||
@ -1304,14 +1362,14 @@
|
||||
"subComponents": {},
|
||||
"config": {
|
||||
"allowed-protocol-mapper-types": [
|
||||
"saml-user-attribute-mapper",
|
||||
"oidc-full-name-mapper",
|
||||
"oidc-sha256-pairwise-sub-mapper",
|
||||
"oidc-usermodel-property-mapper",
|
||||
"oidc-address-mapper",
|
||||
"saml-user-property-mapper",
|
||||
"oidc-sha256-pairwise-sub-mapper",
|
||||
"oidc-usermodel-attribute-mapper",
|
||||
"saml-user-attribute-mapper",
|
||||
"saml-role-list-mapper"
|
||||
"saml-role-list-mapper",
|
||||
"oidc-usermodel-property-mapper"
|
||||
]
|
||||
}
|
||||
},
|
||||
@ -1361,13 +1419,13 @@
|
||||
"config": {
|
||||
"allowed-protocol-mapper-types": [
|
||||
"saml-user-property-mapper",
|
||||
"saml-user-attribute-mapper",
|
||||
"oidc-full-name-mapper",
|
||||
"oidc-sha256-pairwise-sub-mapper",
|
||||
"oidc-usermodel-attribute-mapper",
|
||||
"oidc-full-name-mapper",
|
||||
"saml-user-attribute-mapper",
|
||||
"oidc-usermodel-property-mapper",
|
||||
"oidc-address-mapper",
|
||||
"saml-role-list-mapper",
|
||||
"oidc-usermodel-property-mapper"
|
||||
"saml-role-list-mapper"
|
||||
]
|
||||
}
|
||||
},
|
||||
@ -1485,7 +1543,7 @@
|
||||
"defaultLocale": "en",
|
||||
"authenticationFlows": [
|
||||
{
|
||||
"id": "e134634e-f219-4df4-867c-8110688d8e56",
|
||||
"id": "8ccfe057-5ce6-499b-9fae-3cd89b62bf01",
|
||||
"alias": "Account verification options",
|
||||
"description": "Method with which to verity the existing account",
|
||||
"providerId": "basic-flow",
|
||||
@ -1511,7 +1569,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "a611a8eb-9626-4aa4-8b54-ee565ea6e5dc",
|
||||
"id": "f3b9ab2e-41c2-4e73-876b-e2c275d6d14e",
|
||||
"alias": "Authentication Options",
|
||||
"description": "Authentication options.",
|
||||
"providerId": "basic-flow",
|
||||
@ -1545,7 +1603,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "d87cbb31-5c69-45c8-888d-f9649ebbbf97",
|
||||
"id": "df1329cc-777c-42d8-aa2f-c5d5ddaaf5a4",
|
||||
"alias": "Browser - Conditional OTP",
|
||||
"description": "Flow to determine if the OTP is required for the authentication",
|
||||
"providerId": "basic-flow",
|
||||
@ -1571,7 +1629,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "752ba282-a369-4592-92e8-b4287192dbbf",
|
||||
"id": "f78a4cbc-66ff-4caa-8066-67aff94946f4",
|
||||
"alias": "Direct Grant - Conditional OTP",
|
||||
"description": "Flow to determine if the OTP is required for the authentication",
|
||||
"providerId": "basic-flow",
|
||||
@ -1597,7 +1655,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "2349282e-40ff-431a-984d-53911511e3d3",
|
||||
"id": "4b20995b-5553-45db-86b0-05c3fe14edb1",
|
||||
"alias": "First broker login - Conditional OTP",
|
||||
"description": "Flow to determine if the OTP is required for the authentication",
|
||||
"providerId": "basic-flow",
|
||||
@ -1623,7 +1681,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "4ff5463d-26d9-4219-ba85-41464401098f",
|
||||
"id": "0a7cc6b7-e427-4f72-b44e-a02133241bad",
|
||||
"alias": "Handle Existing Account",
|
||||
"description": "Handle what to do if there is existing account with same email/username like authenticated identity provider",
|
||||
"providerId": "basic-flow",
|
||||
@ -1649,7 +1707,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "87bb6c6d-cca8-4832-b5ab-67ecb9454a42",
|
||||
"id": "e24e73c0-dd51-4fdc-a916-284f11f38487",
|
||||
"alias": "Reset - Conditional OTP",
|
||||
"description": "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.",
|
||||
"providerId": "basic-flow",
|
||||
@ -1675,7 +1733,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "1fc3d028-0e0a-43a4-aaf9-ba7f7d60b409",
|
||||
"id": "37ee5a12-01c2-41b0-aafa-e9c6661ff544",
|
||||
"alias": "User creation or linking",
|
||||
"description": "Flow for the existing/non-existing user alternatives",
|
||||
"providerId": "basic-flow",
|
||||
@ -1702,7 +1760,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "036aae59-641f-4799-9124-c7e5034af6c1",
|
||||
"id": "8902a1a7-c2ee-4648-869f-dd5ef89184fc",
|
||||
"alias": "Verify Existing Account by Re-authentication",
|
||||
"description": "Reauthentication of existing account",
|
||||
"providerId": "basic-flow",
|
||||
@ -1728,7 +1786,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "2e8b9f28-93b8-4368-84b0-1a8326daafe0",
|
||||
"id": "77c78eed-4bcd-4779-b39f-10135be84946",
|
||||
"alias": "browser",
|
||||
"description": "browser based authentication",
|
||||
"providerId": "basic-flow",
|
||||
@ -1770,7 +1828,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "0b826105-8493-45ce-87b3-7d917d190b39",
|
||||
"id": "c6398883-01e6-47a1-bb97-c09f2983155d",
|
||||
"alias": "clients",
|
||||
"description": "Base authentication for clients",
|
||||
"providerId": "client-flow",
|
||||
@ -1812,7 +1870,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "bf6d9edd-48d8-4392-bbc8-4b17a6866074",
|
||||
"id": "78ab5fb8-f35b-4053-b264-94b208000b13",
|
||||
"alias": "direct grant",
|
||||
"description": "OpenID Connect Resource Owner Grant",
|
||||
"providerId": "basic-flow",
|
||||
@ -1846,7 +1904,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "97e31722-dd11-42be-aa99-88788fa2dde6",
|
||||
"id": "959e154b-034e-413d-9b19-211e7d9ba33d",
|
||||
"alias": "docker auth",
|
||||
"description": "Used by Docker clients to authenticate against the IDP",
|
||||
"providerId": "basic-flow",
|
||||
@ -1864,7 +1922,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "3f45cf34-231f-4ea1-8e58-d636c451a76b",
|
||||
"id": "001e253d-bdbd-41e2-81c7-1c7b239feeb1",
|
||||
"alias": "first broker login",
|
||||
"description": "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account",
|
||||
"providerId": "basic-flow",
|
||||
@ -1891,7 +1949,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "9bef2f7c-f989-4871-aaa7-18e2cfa73f22",
|
||||
"id": "45481bb0-18fe-4a26-a77c-35a5afe58436",
|
||||
"alias": "forms",
|
||||
"description": "Username, password, otp and other auth forms.",
|
||||
"providerId": "basic-flow",
|
||||
@ -1917,7 +1975,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "0bfaa325-acde-4443-8bd8-1dc2ae759c5f",
|
||||
"id": "bb47b847-5a55-4c08-909e-9f6f8d8a0636",
|
||||
"alias": "http challenge",
|
||||
"description": "An authentication flow based on challenge-response HTTP Authentication Schemes",
|
||||
"providerId": "basic-flow",
|
||||
@ -1943,7 +2001,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "37ddbe8c-abf3-4654-bd6d-ffabbeefbb98",
|
||||
"id": "77e6e169-05b7-4b89-af00-09cfe1604eed",
|
||||
"alias": "registration",
|
||||
"description": "registration flow",
|
||||
"providerId": "basic-flow",
|
||||
@ -1962,7 +2020,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "5d7b4bc9-e93b-40da-aeb6-ba0c38392f1a",
|
||||
"id": "aef03fe8-1a70-40c3-879f-25588f75c119",
|
||||
"alias": "registration form",
|
||||
"description": "registration form",
|
||||
"providerId": "form-flow",
|
||||
@ -2004,7 +2062,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "ee7a56e4-c827-4f24-8b8b-8476050b0b64",
|
||||
"id": "990abff7-e2ba-4217-984e-8890cbc2b3a9",
|
||||
"alias": "reset credentials",
|
||||
"description": "Reset credentials for a user if they forgot their password or something",
|
||||
"providerId": "basic-flow",
|
||||
@ -2046,7 +2104,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "360f0031-4c3b-4272-84ca-2172d430b4bc",
|
||||
"id": "d9894cf6-2f99-493e-ac47-853f54bfc9c6",
|
||||
"alias": "saml ecp",
|
||||
"description": "SAML ECP Profile Authentication Flow",
|
||||
"providerId": "basic-flow",
|
||||
@ -2066,14 +2124,14 @@
|
||||
],
|
||||
"authenticatorConfig": [
|
||||
{
|
||||
"id": "53630acd-a33a-40e3-8786-cf85464c6f9e",
|
||||
"id": "101ed8ff-4383-4539-aa52-2d1e69698b78",
|
||||
"alias": "create unique user config",
|
||||
"config": {
|
||||
"require.password.update.after.registration": "false"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "c0d2b6a0-caad-4e90-b040-17cacdaf70bb",
|
||||
"id": "049042a5-3551-4c16-81a1-64d86f5aa1e5",
|
||||
"alias": "review profile config",
|
||||
"config": {
|
||||
"update.profile.on.first.login": "missing"
|
@ -73,7 +73,7 @@
|
||||
"composites": {
|
||||
"realm": ["offline_access", "uma_authorization"],
|
||||
"client": {
|
||||
"account": ["delete-account", "view-profile", "manage-account"]
|
||||
"account": ["view-profile", "manage-account", "delete-account"]
|
||||
}
|
||||
},
|
||||
"clientRole": false,
|
||||
@ -407,7 +407,7 @@
|
||||
"otpPolicyLookAheadWindow": 1,
|
||||
"otpPolicyPeriod": 30,
|
||||
"otpPolicyCodeReusable": false,
|
||||
"otpSupportedApplications": ["totpAppGoogleName", "totpAppFreeOTPName"],
|
||||
"otpSupportedApplications": ["totpAppFreeOTPName", "totpAppGoogleName"],
|
||||
"webAuthnPolicyRpEntityName": "keycloak",
|
||||
"webAuthnPolicySignatureAlgorithms": ["ES256"],
|
||||
"webAuthnPolicyRpId": "",
|
||||
@ -452,6 +452,40 @@
|
||||
"disableableCredentialTypes": [],
|
||||
"requiredActions": [],
|
||||
"realmRoles": ["default-roles-myrealm"],
|
||||
"clientRoles": {
|
||||
"realm-management": [
|
||||
"create-client",
|
||||
"view-identity-providers",
|
||||
"manage-realm",
|
||||
"query-groups",
|
||||
"manage-clients",
|
||||
"query-users",
|
||||
"realm-admin",
|
||||
"view-authorization",
|
||||
"view-events",
|
||||
"view-clients",
|
||||
"view-realm",
|
||||
"manage-events",
|
||||
"query-realms",
|
||||
"query-clients",
|
||||
"manage-identity-providers",
|
||||
"manage-users",
|
||||
"view-users",
|
||||
"impersonation",
|
||||
"manage-authorization"
|
||||
],
|
||||
"broker": ["read-token"],
|
||||
"account": [
|
||||
"view-profile",
|
||||
"manage-account-links",
|
||||
"view-applications",
|
||||
"manage-consent",
|
||||
"delete-account",
|
||||
"manage-account",
|
||||
"view-groups",
|
||||
"view-consent"
|
||||
]
|
||||
},
|
||||
"notBefore": 0,
|
||||
"groups": []
|
||||
}
|
||||
@ -517,8 +551,12 @@
|
||||
"enabled": true,
|
||||
"alwaysDisplayInConsole": false,
|
||||
"clientAuthenticatorType": "client-secret",
|
||||
"redirectUris": ["/realms/myrealm/account/*"],
|
||||
"webOrigins": [],
|
||||
"redirectUris": [
|
||||
"http://localhost*",
|
||||
"http://127.0.0.1*",
|
||||
"/realms/myrealm/account/*"
|
||||
],
|
||||
"webOrigins": ["*"],
|
||||
"notBefore": 0,
|
||||
"bearerOnly": false,
|
||||
"consentRequired": false,
|
||||
@ -653,7 +691,6 @@
|
||||
"attributes": {
|
||||
"oidc.ciba.grant.enabled": "false",
|
||||
"backchannel.logout.session.required": "true",
|
||||
"login_theme": "keycloakify-starter",
|
||||
"post.logout.redirect.uris": "+",
|
||||
"display.on.consent.screen": "false",
|
||||
"oauth2.device.authorization.grant.enabled": "false",
|
||||
@ -714,8 +751,12 @@
|
||||
"enabled": true,
|
||||
"alwaysDisplayInConsole": false,
|
||||
"clientAuthenticatorType": "client-secret",
|
||||
"redirectUris": ["/admin/myrealm/console/*"],
|
||||
"webOrigins": ["+"],
|
||||
"redirectUris": [
|
||||
"http://localhost*",
|
||||
"http://127.0.0.1*",
|
||||
"/admin/myrealm/console/*"
|
||||
],
|
||||
"webOrigins": ["*"],
|
||||
"notBefore": 0,
|
||||
"bearerOnly": false,
|
||||
"consentRequired": false,
|
||||
@ -734,6 +775,24 @@
|
||||
"fullScopeAllowed": false,
|
||||
"nodeReRegistrationTimeout": 0,
|
||||
"protocolMappers": [
|
||||
{
|
||||
"id": "8fd0d584-7052-4d04-a615-d18a71050873",
|
||||
"name": "allowed-origins",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-hardcoded-claim-mapper",
|
||||
"consentRequired": false,
|
||||
"config": {
|
||||
"userinfo.token.claim": "true",
|
||||
"id.token.claim": "false",
|
||||
"access.token.claim": "true",
|
||||
"claim.name": "allowed-origins",
|
||||
"jsonType.label": "JSON",
|
||||
"access.tokenResponse.claim": "false",
|
||||
"claim.value": "[\"*\"]",
|
||||
"introspection.token.claim": "true",
|
||||
"lightweight.claim": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "7779f8fa-c2fe-4e68-be56-66ee97bf8f13",
|
||||
"name": "locale",
|
||||
@ -1294,11 +1353,11 @@
|
||||
},
|
||||
"smtpServer": {},
|
||||
"loginTheme": "keycloakify-starter",
|
||||
"accountTheme": "keycloakify-starter",
|
||||
"accountTheme": "",
|
||||
"adminTheme": "",
|
||||
"emailTheme": "",
|
||||
"eventsEnabled": false,
|
||||
"eventsListeners": ["jboss-logging"],
|
||||
"eventsListeners": ["keycloakify-logging", "jboss-logging"],
|
||||
"enabledEventTypes": [],
|
||||
"adminEventsEnabled": false,
|
||||
"adminEventsDetailsEnabled": false,
|
||||
@ -1314,14 +1373,14 @@
|
||||
"subComponents": {},
|
||||
"config": {
|
||||
"allowed-protocol-mapper-types": [
|
||||
"saml-user-property-mapper",
|
||||
"oidc-sha256-pairwise-sub-mapper",
|
||||
"oidc-usermodel-property-mapper",
|
||||
"oidc-address-mapper",
|
||||
"oidc-full-name-mapper",
|
||||
"oidc-usermodel-attribute-mapper",
|
||||
"saml-user-attribute-mapper",
|
||||
"oidc-address-mapper",
|
||||
"saml-role-list-mapper",
|
||||
"oidc-full-name-mapper",
|
||||
"oidc-usermodel-property-mapper"
|
||||
"saml-user-property-mapper"
|
||||
]
|
||||
}
|
||||
},
|
||||
@ -1370,14 +1429,14 @@
|
||||
"subComponents": {},
|
||||
"config": {
|
||||
"allowed-protocol-mapper-types": [
|
||||
"oidc-sha256-pairwise-sub-mapper",
|
||||
"oidc-address-mapper",
|
||||
"oidc-full-name-mapper",
|
||||
"oidc-usermodel-attribute-mapper",
|
||||
"saml-role-list-mapper",
|
||||
"saml-user-attribute-mapper",
|
||||
"oidc-usermodel-attribute-mapper",
|
||||
"oidc-full-name-mapper",
|
||||
"saml-user-property-mapper",
|
||||
"oidc-usermodel-property-mapper"
|
||||
"oidc-usermodel-property-mapper",
|
||||
"oidc-address-mapper",
|
||||
"oidc-sha256-pairwise-sub-mapper",
|
||||
"saml-user-property-mapper"
|
||||
]
|
||||
}
|
||||
},
|
||||
@ -1495,7 +1554,7 @@
|
||||
"defaultLocale": "en",
|
||||
"authenticationFlows": [
|
||||
{
|
||||
"id": "19317acb-fe8e-4c79-82bc-90e159273075",
|
||||
"id": "30a878f0-57aa-4d20-bab0-6cf1d7317a5c",
|
||||
"alias": "Account verification options",
|
||||
"description": "Method with which to verity the existing account",
|
||||
"providerId": "basic-flow",
|
||||
@ -1521,7 +1580,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "122857d2-33da-4086-8acb-cb0e303aaf1b",
|
||||
"id": "d386affe-d1fe-472a-bee6-54105d0101f5",
|
||||
"alias": "Authentication Options",
|
||||
"description": "Authentication options.",
|
||||
"providerId": "basic-flow",
|
||||
@ -1555,7 +1614,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "abf5dd35-4791-4268-a10c-5f4b6a06b84a",
|
||||
"id": "77b95bc0-bd0c-46b7-8240-3182023e9d50",
|
||||
"alias": "Browser - Conditional OTP",
|
||||
"description": "Flow to determine if the OTP is required for the authentication",
|
||||
"providerId": "basic-flow",
|
||||
@ -1581,7 +1640,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "a18daeec-a33c-4a43-b014-10c84ec69b81",
|
||||
"id": "bc96d3d6-29a1-42af-a63e-bb67a8c6d78f",
|
||||
"alias": "Direct Grant - Conditional OTP",
|
||||
"description": "Flow to determine if the OTP is required for the authentication",
|
||||
"providerId": "basic-flow",
|
||||
@ -1607,7 +1666,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "e9f032a7-32f7-457c-becf-011a1a35cc6a",
|
||||
"id": "7697ca74-5c2b-45ab-9335-e0f6dec59b5c",
|
||||
"alias": "First broker login - Conditional OTP",
|
||||
"description": "Flow to determine if the OTP is required for the authentication",
|
||||
"providerId": "basic-flow",
|
||||
@ -1633,7 +1692,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "9db65b7c-98ca-4003-beea-611038831ffe",
|
||||
"id": "534cb120-f600-4f40-9707-7b781bdbce48",
|
||||
"alias": "Handle Existing Account",
|
||||
"description": "Handle what to do if there is existing account with same email/username like authenticated identity provider",
|
||||
"providerId": "basic-flow",
|
||||
@ -1659,7 +1718,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "7bd0854c-d7ae-43d7-a1ae-7b759a34cb1d",
|
||||
"id": "f884b048-b223-4ed6-ae16-e49a4255131e",
|
||||
"alias": "Reset - Conditional OTP",
|
||||
"description": "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.",
|
||||
"providerId": "basic-flow",
|
||||
@ -1685,7 +1744,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "2de1a450-fe98-443a-9c6c-d24d8a7ebcb3",
|
||||
"id": "61c7966c-ad72-49f5-84dd-376152348092",
|
||||
"alias": "User creation or linking",
|
||||
"description": "Flow for the existing/non-existing user alternatives",
|
||||
"providerId": "basic-flow",
|
||||
@ -1712,7 +1771,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "7b3efad5-4b7d-4385-a41c-fecc73afdcc4",
|
||||
"id": "72412d0f-dd1b-49fe-bb0b-9dad99eb0491",
|
||||
"alias": "Verify Existing Account by Re-authentication",
|
||||
"description": "Reauthentication of existing account",
|
||||
"providerId": "basic-flow",
|
||||
@ -1738,7 +1797,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "de93418e-8f28-4099-b15e-ad36ec194796",
|
||||
"id": "6b76613e-0d39-440d-aab4-98eaffb1e96a",
|
||||
"alias": "browser",
|
||||
"description": "browser based authentication",
|
||||
"providerId": "basic-flow",
|
||||
@ -1780,7 +1839,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "0dd3345c-6e82-4c3a-a39a-d49ae1f5c409",
|
||||
"id": "0ff60395-fa89-41be-ad22-fab339e67c49",
|
||||
"alias": "clients",
|
||||
"description": "Base authentication for clients",
|
||||
"providerId": "client-flow",
|
||||
@ -1822,7 +1881,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "87fb4dd0-5326-47a1-b670-982f4872ff89",
|
||||
"id": "bbb3ece7-7dbf-4aba-80c3-dde4b9cdd0b6",
|
||||
"alias": "direct grant",
|
||||
"description": "OpenID Connect Resource Owner Grant",
|
||||
"providerId": "basic-flow",
|
||||
@ -1856,7 +1915,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "344723b3-4ab1-4999-abdd-32398e82327b",
|
||||
"id": "f5f2c0f6-7dbf-4978-845e-6cacac23aa13",
|
||||
"alias": "docker auth",
|
||||
"description": "Used by Docker clients to authenticate against the IDP",
|
||||
"providerId": "basic-flow",
|
||||
@ -1874,7 +1933,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "f3341938-caf9-4c8a-9cd5-eb34609809ab",
|
||||
"id": "cf463104-19e2-41a8-8a53-d3dd30b75344",
|
||||
"alias": "first broker login",
|
||||
"description": "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account",
|
||||
"providerId": "basic-flow",
|
||||
@ -1901,7 +1960,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "ba7b7357-e324-4b71-9bda-f8512a760e02",
|
||||
"id": "b99b60dc-41ad-487d-be69-a2eefa954a9d",
|
||||
"alias": "forms",
|
||||
"description": "Username, password, otp and other auth forms.",
|
||||
"providerId": "basic-flow",
|
||||
@ -1927,7 +1986,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "134971e6-bf63-432c-806e-74ca4fb09963",
|
||||
"id": "18731296-2c96-4f98-a884-027e629e4f9d",
|
||||
"alias": "http challenge",
|
||||
"description": "An authentication flow based on challenge-response HTTP Authentication Schemes",
|
||||
"providerId": "basic-flow",
|
||||
@ -1953,7 +2012,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "6ea9e2cf-5684-4c65-8c07-930d1cbb0b46",
|
||||
"id": "9a9dce17-5425-4fd5-b3b8-81410e1dbce4",
|
||||
"alias": "registration",
|
||||
"description": "registration flow",
|
||||
"providerId": "basic-flow",
|
||||
@ -1972,7 +2031,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "67e3c8c7-1b5e-4119-84a2-e90876293150",
|
||||
"id": "d0a24e08-cb69-4949-9518-50ae7a96ee49",
|
||||
"alias": "registration form",
|
||||
"description": "registration form",
|
||||
"providerId": "form-flow",
|
||||
@ -2014,7 +2073,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "fc6d48ec-a1f1-41b1-9310-54f58861d5aa",
|
||||
"id": "6a9aa554-afba-487f-9c82-e94c81c15b3b",
|
||||
"alias": "reset credentials",
|
||||
"description": "Reset credentials for a user if they forgot their password or something",
|
||||
"providerId": "basic-flow",
|
||||
@ -2056,7 +2115,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "80b1d464-c2ec-4eb1-82e8-32cbede779a8",
|
||||
"id": "e0361d46-eab4-41a6-bb2e-1dc6a5a6b073",
|
||||
"alias": "saml ecp",
|
||||
"description": "SAML ECP Profile Authentication Flow",
|
||||
"providerId": "basic-flow",
|
||||
@ -2076,14 +2135,14 @@
|
||||
],
|
||||
"authenticatorConfig": [
|
||||
{
|
||||
"id": "86b1d5fa-450c-40d8-899c-725861ac39fc",
|
||||
"id": "053d6017-e54c-418a-abe7-44dd4752eacb",
|
||||
"alias": "create unique user config",
|
||||
"config": {
|
||||
"require.password.update.after.registration": "false"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "ea724f02-029a-493d-b4d3-08972be21cfb",
|
||||
"id": "8b545cf4-ab9e-4226-b3c0-d7ac773eae2f",
|
||||
"alias": "review profile config",
|
||||
"config": {
|
||||
"update.profile.on.first.login": "missing"
|
@ -73,7 +73,7 @@
|
||||
"composites": {
|
||||
"realm": ["offline_access", "uma_authorization"],
|
||||
"client": {
|
||||
"account": ["delete-account", "view-profile", "manage-account"]
|
||||
"account": ["view-profile", "manage-account", "delete-account"]
|
||||
}
|
||||
},
|
||||
"clientRole": false,
|
||||
@ -408,9 +408,9 @@
|
||||
"otpPolicyPeriod": 30,
|
||||
"otpPolicyCodeReusable": false,
|
||||
"otpSupportedApplications": [
|
||||
"totpAppGoogleName",
|
||||
"totpAppFreeOTPName",
|
||||
"totpAppMicrosoftAuthenticatorName"
|
||||
"totpAppMicrosoftAuthenticatorName",
|
||||
"totpAppGoogleName"
|
||||
],
|
||||
"webAuthnPolicyRpEntityName": "keycloak",
|
||||
"webAuthnPolicySignatureAlgorithms": ["ES256"],
|
||||
@ -456,6 +456,40 @@
|
||||
"disableableCredentialTypes": [],
|
||||
"requiredActions": [],
|
||||
"realmRoles": ["default-roles-myrealm"],
|
||||
"clientRoles": {
|
||||
"realm-management": [
|
||||
"create-client",
|
||||
"view-identity-providers",
|
||||
"manage-realm",
|
||||
"query-groups",
|
||||
"manage-clients",
|
||||
"query-users",
|
||||
"realm-admin",
|
||||
"view-authorization",
|
||||
"view-events",
|
||||
"view-clients",
|
||||
"view-realm",
|
||||
"manage-events",
|
||||
"query-realms",
|
||||
"query-clients",
|
||||
"manage-identity-providers",
|
||||
"manage-users",
|
||||
"view-users",
|
||||
"impersonation",
|
||||
"manage-authorization"
|
||||
],
|
||||
"broker": ["read-token"],
|
||||
"account": [
|
||||
"view-profile",
|
||||
"manage-account-links",
|
||||
"view-applications",
|
||||
"manage-consent",
|
||||
"delete-account",
|
||||
"manage-account",
|
||||
"view-groups",
|
||||
"view-consent"
|
||||
]
|
||||
},
|
||||
"notBefore": 0,
|
||||
"groups": []
|
||||
}
|
||||
@ -521,8 +555,12 @@
|
||||
"enabled": true,
|
||||
"alwaysDisplayInConsole": false,
|
||||
"clientAuthenticatorType": "client-secret",
|
||||
"redirectUris": ["/realms/myrealm/account/*"],
|
||||
"webOrigins": [],
|
||||
"redirectUris": [
|
||||
"http://localhost*",
|
||||
"http://127.0.0.1*",
|
||||
"/realms/myrealm/account/*"
|
||||
],
|
||||
"webOrigins": ["*"],
|
||||
"notBefore": 0,
|
||||
"bearerOnly": false,
|
||||
"consentRequired": false,
|
||||
@ -657,7 +695,6 @@
|
||||
"attributes": {
|
||||
"oidc.ciba.grant.enabled": "false",
|
||||
"backchannel.logout.session.required": "true",
|
||||
"login_theme": "keycloakify-starter",
|
||||
"post.logout.redirect.uris": "+",
|
||||
"display.on.consent.screen": "false",
|
||||
"oauth2.device.authorization.grant.enabled": "false",
|
||||
@ -718,8 +755,12 @@
|
||||
"enabled": true,
|
||||
"alwaysDisplayInConsole": false,
|
||||
"clientAuthenticatorType": "client-secret",
|
||||
"redirectUris": ["/admin/myrealm/console/*"],
|
||||
"webOrigins": ["+"],
|
||||
"redirectUris": [
|
||||
"http://localhost*",
|
||||
"http://127.0.0.1*",
|
||||
"/admin/myrealm/console/*"
|
||||
],
|
||||
"webOrigins": ["*"],
|
||||
"notBefore": 0,
|
||||
"bearerOnly": false,
|
||||
"consentRequired": false,
|
||||
@ -738,6 +779,24 @@
|
||||
"fullScopeAllowed": false,
|
||||
"nodeReRegistrationTimeout": 0,
|
||||
"protocolMappers": [
|
||||
{
|
||||
"id": "8fd0d584-7052-4d04-a615-d18a71050873",
|
||||
"name": "allowed-origins",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-hardcoded-claim-mapper",
|
||||
"consentRequired": false,
|
||||
"config": {
|
||||
"userinfo.token.claim": "true",
|
||||
"id.token.claim": "false",
|
||||
"access.token.claim": "true",
|
||||
"claim.name": "allowed-origins",
|
||||
"jsonType.label": "JSON",
|
||||
"access.tokenResponse.claim": "false",
|
||||
"claim.value": "[\"*\"]",
|
||||
"introspection.token.claim": "true",
|
||||
"lightweight.claim": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "7779f8fa-c2fe-4e68-be56-66ee97bf8f13",
|
||||
"name": "locale",
|
||||
@ -1298,11 +1357,11 @@
|
||||
},
|
||||
"smtpServer": {},
|
||||
"loginTheme": "keycloakify-starter",
|
||||
"accountTheme": "keycloakify-starter",
|
||||
"accountTheme": "",
|
||||
"adminTheme": "",
|
||||
"emailTheme": "",
|
||||
"eventsEnabled": false,
|
||||
"eventsListeners": ["jboss-logging"],
|
||||
"eventsListeners": ["keycloakify-logging", "jboss-logging"],
|
||||
"enabledEventTypes": [],
|
||||
"adminEventsEnabled": false,
|
||||
"adminEventsDetailsEnabled": false,
|
||||
@ -1318,13 +1377,13 @@
|
||||
"subComponents": {},
|
||||
"config": {
|
||||
"allowed-protocol-mapper-types": [
|
||||
"oidc-usermodel-property-mapper",
|
||||
"oidc-usermodel-attribute-mapper",
|
||||
"oidc-full-name-mapper",
|
||||
"oidc-usermodel-property-mapper",
|
||||
"oidc-sha256-pairwise-sub-mapper",
|
||||
"saml-user-property-mapper",
|
||||
"saml-role-list-mapper",
|
||||
"oidc-full-name-mapper",
|
||||
"saml-user-attribute-mapper",
|
||||
"oidc-sha256-pairwise-sub-mapper",
|
||||
"oidc-address-mapper"
|
||||
]
|
||||
}
|
||||
@ -1374,14 +1433,14 @@
|
||||
"subComponents": {},
|
||||
"config": {
|
||||
"allowed-protocol-mapper-types": [
|
||||
"oidc-sha256-pairwise-sub-mapper",
|
||||
"oidc-address-mapper",
|
||||
"oidc-full-name-mapper",
|
||||
"oidc-usermodel-property-mapper",
|
||||
"oidc-usermodel-attribute-mapper",
|
||||
"saml-user-attribute-mapper",
|
||||
"oidc-sha256-pairwise-sub-mapper",
|
||||
"saml-role-list-mapper",
|
||||
"saml-user-property-mapper"
|
||||
"saml-user-attribute-mapper",
|
||||
"saml-user-property-mapper",
|
||||
"oidc-usermodel-attribute-mapper",
|
||||
"oidc-address-mapper",
|
||||
"oidc-full-name-mapper"
|
||||
]
|
||||
}
|
||||
},
|
2201
src/bin/start-keycloak/realmConfig/defaultConfig/realm-kc-22.json
Normal file
2201
src/bin/start-keycloak/realmConfig/defaultConfig/realm-kc-22.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -55,7 +55,7 @@
|
||||
"composites": {
|
||||
"realm": ["offline_access", "uma_authorization"],
|
||||
"client": {
|
||||
"account": ["delete-account", "view-profile", "manage-account"]
|
||||
"account": ["view-profile", "delete-account", "manage-account"]
|
||||
}
|
||||
},
|
||||
"clientRole": false,
|
||||
@ -459,6 +459,40 @@
|
||||
"disableableCredentialTypes": [],
|
||||
"requiredActions": [],
|
||||
"realmRoles": ["default-roles-myrealm"],
|
||||
"clientRoles": {
|
||||
"realm-management": [
|
||||
"query-clients",
|
||||
"manage-identity-providers",
|
||||
"create-client",
|
||||
"view-users",
|
||||
"query-groups",
|
||||
"view-realm",
|
||||
"manage-authorization",
|
||||
"view-authorization",
|
||||
"query-users",
|
||||
"impersonation",
|
||||
"realm-admin",
|
||||
"manage-users",
|
||||
"view-identity-providers",
|
||||
"manage-realm",
|
||||
"manage-clients",
|
||||
"query-realms",
|
||||
"view-events",
|
||||
"manage-events",
|
||||
"view-clients"
|
||||
],
|
||||
"broker": ["read-token"],
|
||||
"account": [
|
||||
"manage-account",
|
||||
"view-consent",
|
||||
"view-groups",
|
||||
"delete-account",
|
||||
"view-applications",
|
||||
"manage-account-links",
|
||||
"view-profile",
|
||||
"manage-consent"
|
||||
]
|
||||
},
|
||||
"notBefore": 0,
|
||||
"groups": []
|
||||
}
|
||||
@ -505,7 +539,6 @@
|
||||
"attributes": {
|
||||
"oidc.ciba.grant.enabled": "false",
|
||||
"backchannel.logout.session.required": "true",
|
||||
"login_theme": "keycloakify-starter",
|
||||
"post.logout.redirect.uris": "+",
|
||||
"oauth2.device.authorization.grant.enabled": "false",
|
||||
"display.on.consent.screen": "false",
|
||||
@ -532,8 +565,12 @@
|
||||
"enabled": true,
|
||||
"alwaysDisplayInConsole": false,
|
||||
"clientAuthenticatorType": "client-secret",
|
||||
"redirectUris": ["/realms/myrealm/account/*"],
|
||||
"webOrigins": [],
|
||||
"redirectUris": [
|
||||
"http://localhost*",
|
||||
"http://127.0.0.1*",
|
||||
"/realms/myrealm/account/*"
|
||||
],
|
||||
"webOrigins": ["*"],
|
||||
"notBefore": 0,
|
||||
"bearerOnly": false,
|
||||
"consentRequired": false,
|
||||
@ -649,7 +686,11 @@
|
||||
"enabled": true,
|
||||
"alwaysDisplayInConsole": false,
|
||||
"clientAuthenticatorType": "client-secret",
|
||||
"redirectUris": ["https://my-theme.keycloakify.dev/*", "http://localhost*"],
|
||||
"redirectUris": [
|
||||
"https://my-theme.keycloakify.dev/*",
|
||||
"http://localhost*",
|
||||
"http://127.0.0.1*"
|
||||
],
|
||||
"webOrigins": ["*"],
|
||||
"notBefore": 0,
|
||||
"bearerOnly": false,
|
||||
@ -664,8 +705,7 @@
|
||||
"attributes": {
|
||||
"oidc.ciba.grant.enabled": "false",
|
||||
"backchannel.logout.session.required": "true",
|
||||
"login_theme": "keycloakify-starter",
|
||||
"post.logout.redirect.uris": "https://my-theme.keycloakify.dev/*",
|
||||
"post.logout.redirect.uris": "+",
|
||||
"oauth2.device.authorization.grant.enabled": "false",
|
||||
"display.on.consent.screen": "false",
|
||||
"backchannel.logout.revoke.offline.tokens": "false"
|
||||
@ -725,8 +765,12 @@
|
||||
"enabled": true,
|
||||
"alwaysDisplayInConsole": false,
|
||||
"clientAuthenticatorType": "client-secret",
|
||||
"redirectUris": ["/admin/myrealm/console/*"],
|
||||
"webOrigins": ["+"],
|
||||
"redirectUris": [
|
||||
"http://localhost*",
|
||||
"http://127.0.0.1*",
|
||||
"/admin/myrealm/console/*"
|
||||
],
|
||||
"webOrigins": ["*"],
|
||||
"notBefore": 0,
|
||||
"bearerOnly": false,
|
||||
"consentRequired": false,
|
||||
@ -745,6 +789,24 @@
|
||||
"fullScopeAllowed": false,
|
||||
"nodeReRegistrationTimeout": 0,
|
||||
"protocolMappers": [
|
||||
{
|
||||
"id": "8fd0d584-7052-4d04-a615-d18a71050873",
|
||||
"name": "allowed-origins",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-hardcoded-claim-mapper",
|
||||
"consentRequired": false,
|
||||
"config": {
|
||||
"introspection.token.claim": "true",
|
||||
"userinfo.token.claim": "true",
|
||||
"id.token.claim": "false",
|
||||
"access.token.claim": "true",
|
||||
"claim.name": "allowed-origins",
|
||||
"jsonType.label": "JSON",
|
||||
"access.tokenResponse.claim": "false",
|
||||
"claim.value": "[\"*\"]",
|
||||
"lightweight.claim": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "59cde7ae-2218-4a8e-83af-cad992c3a700",
|
||||
"name": "locale",
|
||||
@ -1336,12 +1398,12 @@
|
||||
"strictTransportSecurity": "max-age=31536000; includeSubDomains"
|
||||
},
|
||||
"smtpServer": {},
|
||||
"loginTheme": "",
|
||||
"accountTheme": "keycloakify-starter",
|
||||
"loginTheme": "keycloakify-starter",
|
||||
"accountTheme": "",
|
||||
"adminTheme": "",
|
||||
"emailTheme": "",
|
||||
"eventsEnabled": false,
|
||||
"eventsListeners": ["jboss-logging"],
|
||||
"eventsListeners": ["keycloakify-logging", "jboss-logging"],
|
||||
"enabledEventTypes": [],
|
||||
"adminEventsEnabled": false,
|
||||
"adminEventsDetailsEnabled": false,
|
||||
@ -1358,13 +1420,13 @@
|
||||
"config": {
|
||||
"allowed-protocol-mapper-types": [
|
||||
"oidc-sha256-pairwise-sub-mapper",
|
||||
"saml-user-property-mapper",
|
||||
"oidc-address-mapper",
|
||||
"oidc-full-name-mapper",
|
||||
"saml-role-list-mapper",
|
||||
"oidc-usermodel-attribute-mapper",
|
||||
"saml-user-attribute-mapper",
|
||||
"oidc-usermodel-property-mapper"
|
||||
"oidc-full-name-mapper",
|
||||
"oidc-usermodel-property-mapper",
|
||||
"oidc-usermodel-attribute-mapper",
|
||||
"oidc-address-mapper",
|
||||
"saml-user-property-mapper",
|
||||
"saml-role-list-mapper"
|
||||
]
|
||||
}
|
||||
},
|
||||
@ -1433,14 +1495,14 @@
|
||||
"subComponents": {},
|
||||
"config": {
|
||||
"allowed-protocol-mapper-types": [
|
||||
"oidc-usermodel-property-mapper",
|
||||
"saml-role-list-mapper",
|
||||
"oidc-full-name-mapper",
|
||||
"oidc-address-mapper",
|
||||
"saml-user-attribute-mapper",
|
||||
"oidc-sha256-pairwise-sub-mapper",
|
||||
"oidc-usermodel-attribute-mapper",
|
||||
"saml-user-property-mapper",
|
||||
"oidc-usermodel-property-mapper"
|
||||
"oidc-usermodel-attribute-mapper",
|
||||
"saml-user-attribute-mapper",
|
||||
"oidc-address-mapper",
|
||||
"oidc-full-name-mapper",
|
||||
"oidc-sha256-pairwise-sub-mapper"
|
||||
]
|
||||
}
|
||||
}
|
@ -468,6 +468,40 @@
|
||||
"disableableCredentialTypes": [],
|
||||
"requiredActions": [],
|
||||
"realmRoles": ["default-roles-myrealm"],
|
||||
"clientRoles": {
|
||||
"realm-management": [
|
||||
"manage-clients",
|
||||
"manage-users",
|
||||
"view-identity-providers",
|
||||
"view-users",
|
||||
"impersonation",
|
||||
"manage-identity-providers",
|
||||
"query-users",
|
||||
"query-realms",
|
||||
"realm-admin",
|
||||
"view-events",
|
||||
"view-realm",
|
||||
"manage-events",
|
||||
"manage-authorization",
|
||||
"manage-realm",
|
||||
"query-clients",
|
||||
"query-groups",
|
||||
"view-clients",
|
||||
"create-client",
|
||||
"view-authorization"
|
||||
],
|
||||
"broker": ["read-token"],
|
||||
"account": [
|
||||
"manage-consent",
|
||||
"manage-account-links",
|
||||
"view-applications",
|
||||
"view-consent",
|
||||
"manage-account",
|
||||
"view-profile",
|
||||
"view-groups",
|
||||
"delete-account"
|
||||
]
|
||||
},
|
||||
"notBefore": 0,
|
||||
"groups": []
|
||||
}
|
||||
@ -514,7 +548,6 @@
|
||||
"attributes": {
|
||||
"oidc.ciba.grant.enabled": "false",
|
||||
"backchannel.logout.session.required": "true",
|
||||
"login_theme": "keycloakify-starter",
|
||||
"post.logout.redirect.uris": "+",
|
||||
"oauth2.device.authorization.grant.enabled": "false",
|
||||
"display.on.consent.screen": "false",
|
||||
@ -541,8 +574,12 @@
|
||||
"enabled": true,
|
||||
"alwaysDisplayInConsole": false,
|
||||
"clientAuthenticatorType": "client-secret",
|
||||
"redirectUris": ["/realms/myrealm/account/*"],
|
||||
"webOrigins": [],
|
||||
"redirectUris": [
|
||||
"http://localhost*",
|
||||
"http://127.0.0.1*",
|
||||
"/realms/myrealm/account/*"
|
||||
],
|
||||
"webOrigins": ["*"],
|
||||
"notBefore": 0,
|
||||
"bearerOnly": false,
|
||||
"consentRequired": false,
|
||||
@ -658,7 +695,11 @@
|
||||
"enabled": true,
|
||||
"alwaysDisplayInConsole": false,
|
||||
"clientAuthenticatorType": "client-secret",
|
||||
"redirectUris": ["https://my-theme.keycloakify.dev/*", "http://localhost*"],
|
||||
"redirectUris": [
|
||||
"https://my-theme.keycloakify.dev/*",
|
||||
"http://localhost*",
|
||||
"http://127.0.0.1*"
|
||||
],
|
||||
"webOrigins": ["*"],
|
||||
"notBefore": 0,
|
||||
"bearerOnly": false,
|
||||
@ -673,8 +714,7 @@
|
||||
"attributes": {
|
||||
"oidc.ciba.grant.enabled": "false",
|
||||
"backchannel.logout.session.required": "true",
|
||||
"login_theme": "keycloakify-starter",
|
||||
"post.logout.redirect.uris": "https://my-theme.keycloakify.dev/*##http://localhost*",
|
||||
"post.logout.redirect.uris": "+",
|
||||
"oauth2.device.authorization.grant.enabled": "false",
|
||||
"display.on.consent.screen": "false",
|
||||
"backchannel.logout.revoke.offline.tokens": "false"
|
||||
@ -840,8 +880,12 @@
|
||||
"enabled": true,
|
||||
"alwaysDisplayInConsole": false,
|
||||
"clientAuthenticatorType": "client-secret",
|
||||
"redirectUris": ["/admin/myrealm/console/*"],
|
||||
"webOrigins": ["+"],
|
||||
"redirectUris": [
|
||||
"http://localhost*",
|
||||
"http://127.0.0.1*",
|
||||
"/admin/myrealm/console/*"
|
||||
],
|
||||
"webOrigins": ["*"],
|
||||
"notBefore": 0,
|
||||
"bearerOnly": false,
|
||||
"consentRequired": false,
|
||||
@ -875,6 +919,24 @@
|
||||
"claim.name": "locale",
|
||||
"jsonType.label": "String"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "8fd0d584-7052-4d04-a615-d18a71050873",
|
||||
"name": "allowed-origins",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-hardcoded-claim-mapper",
|
||||
"consentRequired": false,
|
||||
"config": {
|
||||
"introspection.token.claim": "true",
|
||||
"userinfo.token.claim": "true",
|
||||
"id.token.claim": "false",
|
||||
"access.token.claim": "true",
|
||||
"claim.name": "allowed-origins",
|
||||
"jsonType.label": "JSON",
|
||||
"access.tokenResponse.claim": "false",
|
||||
"claim.value": "[\"*\"]",
|
||||
"lightweight.claim": "true"
|
||||
}
|
||||
}
|
||||
],
|
||||
"defaultClientScopes": ["web-origins", "acr", "profile", "roles", "email"],
|
||||
@ -1451,12 +1513,12 @@
|
||||
"strictTransportSecurity": "max-age=31536000; includeSubDomains"
|
||||
},
|
||||
"smtpServer": {},
|
||||
"loginTheme": "keycloak",
|
||||
"accountTheme": "keycloakify-starter",
|
||||
"loginTheme": "keycloakify-starter",
|
||||
"accountTheme": "",
|
||||
"adminTheme": "",
|
||||
"emailTheme": "",
|
||||
"eventsEnabled": false,
|
||||
"eventsListeners": ["jboss-logging"],
|
||||
"eventsListeners": ["keycloakify-logging", "jboss-logging"],
|
||||
"enabledEventTypes": [],
|
||||
"adminEventsEnabled": false,
|
||||
"adminEventsDetailsEnabled": false,
|
||||
@ -1501,14 +1563,14 @@
|
||||
"subComponents": {},
|
||||
"config": {
|
||||
"allowed-protocol-mapper-types": [
|
||||
"oidc-full-name-mapper",
|
||||
"saml-role-list-mapper",
|
||||
"oidc-address-mapper",
|
||||
"oidc-usermodel-property-mapper",
|
||||
"oidc-sha256-pairwise-sub-mapper",
|
||||
"saml-user-attribute-mapper",
|
||||
"oidc-usermodel-attribute-mapper",
|
||||
"oidc-full-name-mapper",
|
||||
"saml-user-property-mapper"
|
||||
"oidc-address-mapper",
|
||||
"oidc-usermodel-property-mapper",
|
||||
"saml-user-property-mapper",
|
||||
"oidc-sha256-pairwise-sub-mapper"
|
||||
]
|
||||
}
|
||||
},
|
||||
@ -1541,13 +1603,13 @@
|
||||
"config": {
|
||||
"allowed-protocol-mapper-types": [
|
||||
"oidc-sha256-pairwise-sub-mapper",
|
||||
"oidc-usermodel-property-mapper",
|
||||
"oidc-full-name-mapper",
|
||||
"oidc-address-mapper",
|
||||
"oidc-usermodel-attribute-mapper",
|
||||
"saml-user-property-mapper",
|
||||
"oidc-full-name-mapper",
|
||||
"oidc-usermodel-property-mapper",
|
||||
"saml-user-attribute-mapper",
|
||||
"saml-role-list-mapper",
|
||||
"saml-user-attribute-mapper"
|
||||
"saml-user-property-mapper",
|
||||
"oidc-usermodel-attribute-mapper"
|
||||
]
|
||||
}
|
||||
},
|
@ -538,10 +538,10 @@
|
||||
"emailVerified": true,
|
||||
"attributes": {
|
||||
"additional_emails": ["test.user@protonmail.com", "testuser@hotmail.com"],
|
||||
"gender": ["prefer_not_to_say"],
|
||||
"favorite_pet": ["cats"],
|
||||
"favourite_pet": ["cat"],
|
||||
"gender": ["prefer_not_to_say"],
|
||||
"bio": ["Hello I'm Test User and I do not exist."],
|
||||
"favourite_pet": ["cat"],
|
||||
"phone_number": ["1111111111"],
|
||||
"locale": ["en"],
|
||||
"favorite_media": ["movies", "series"]
|
||||
@ -562,6 +562,40 @@
|
||||
"disableableCredentialTypes": [],
|
||||
"requiredActions": [],
|
||||
"realmRoles": ["default-roles-myrealm"],
|
||||
"clientRoles": {
|
||||
"realm-management": [
|
||||
"manage-users",
|
||||
"create-client",
|
||||
"view-users",
|
||||
"view-realm",
|
||||
"query-realms",
|
||||
"impersonation",
|
||||
"view-events",
|
||||
"realm-admin",
|
||||
"manage-authorization",
|
||||
"manage-events",
|
||||
"view-authorization",
|
||||
"manage-clients",
|
||||
"query-users",
|
||||
"query-groups",
|
||||
"manage-realm",
|
||||
"query-clients",
|
||||
"manage-identity-providers",
|
||||
"view-clients",
|
||||
"view-identity-providers"
|
||||
],
|
||||
"broker": ["read-token"],
|
||||
"account": [
|
||||
"delete-account",
|
||||
"view-applications",
|
||||
"manage-account",
|
||||
"view-consent",
|
||||
"view-groups",
|
||||
"view-profile",
|
||||
"manage-account-links",
|
||||
"manage-consent"
|
||||
]
|
||||
},
|
||||
"notBefore": 0,
|
||||
"groups": []
|
||||
}
|
||||
@ -636,7 +670,7 @@
|
||||
"enabled": true,
|
||||
"alwaysDisplayInConsole": false,
|
||||
"clientAuthenticatorType": "client-secret",
|
||||
"redirectUris": ["*"],
|
||||
"redirectUris": ["http://localhost*", "http://127.0.0.1*", "*"],
|
||||
"webOrigins": ["*"],
|
||||
"notBefore": 0,
|
||||
"bearerOnly": false,
|
||||
@ -798,8 +832,7 @@
|
||||
"attributes": {
|
||||
"oidc.ciba.grant.enabled": "false",
|
||||
"backchannel.logout.session.required": "true",
|
||||
"login_theme": "keycloakify-starter",
|
||||
"post.logout.redirect.uris": "https://my-theme.keycloakify.dev/*##http://localhost*##http://127.0.0.1*",
|
||||
"post.logout.redirect.uris": "+",
|
||||
"oauth2.device.authorization.grant.enabled": "false",
|
||||
"display.on.consent.screen": "false",
|
||||
"backchannel.logout.revoke.offline.tokens": "false"
|
||||
@ -892,8 +925,12 @@
|
||||
"enabled": true,
|
||||
"alwaysDisplayInConsole": false,
|
||||
"clientAuthenticatorType": "client-secret",
|
||||
"redirectUris": ["/admin/myrealm/console/*"],
|
||||
"webOrigins": ["+"],
|
||||
"redirectUris": [
|
||||
"http://localhost*",
|
||||
"http://127.0.0.1*",
|
||||
"/admin/myrealm/console/*"
|
||||
],
|
||||
"webOrigins": ["*"],
|
||||
"notBefore": 0,
|
||||
"bearerOnly": false,
|
||||
"consentRequired": false,
|
||||
@ -927,6 +964,24 @@
|
||||
"claim.name": "locale",
|
||||
"jsonType.label": "String"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "8fd0d584-7052-4d04-a615-d18a71050873",
|
||||
"name": "allowed-origins",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-hardcoded-claim-mapper",
|
||||
"consentRequired": false,
|
||||
"config": {
|
||||
"introspection.token.claim": "true",
|
||||
"userinfo.token.claim": "true",
|
||||
"id.token.claim": "false",
|
||||
"access.token.claim": "true",
|
||||
"claim.name": "allowed-origins",
|
||||
"jsonType.label": "JSON",
|
||||
"access.tokenResponse.claim": "false",
|
||||
"claim.value": "[\"*\"]",
|
||||
"lightweight.claim": "true"
|
||||
}
|
||||
}
|
||||
],
|
||||
"defaultClientScopes": [
|
||||
@ -1551,11 +1606,11 @@
|
||||
},
|
||||
"smtpServer": {},
|
||||
"loginTheme": "keycloakify-starter",
|
||||
"accountTheme": "keycloakify-starter",
|
||||
"accountTheme": "",
|
||||
"adminTheme": "",
|
||||
"emailTheme": "",
|
||||
"eventsEnabled": false,
|
||||
"eventsListeners": ["jboss-logging"],
|
||||
"eventsListeners": ["keycloakify-logging", "jboss-logging"],
|
||||
"enabledEventTypes": [],
|
||||
"adminEventsEnabled": false,
|
||||
"adminEventsDetailsEnabled": false,
|
||||
@ -1581,14 +1636,14 @@
|
||||
"subComponents": {},
|
||||
"config": {
|
||||
"allowed-protocol-mapper-types": [
|
||||
"oidc-sha256-pairwise-sub-mapper",
|
||||
"oidc-full-name-mapper",
|
||||
"oidc-usermodel-property-mapper",
|
||||
"saml-role-list-mapper",
|
||||
"saml-user-attribute-mapper",
|
||||
"saml-user-property-mapper",
|
||||
"oidc-usermodel-attribute-mapper",
|
||||
"oidc-address-mapper"
|
||||
"oidc-address-mapper",
|
||||
"saml-role-list-mapper",
|
||||
"saml-user-property-mapper",
|
||||
"oidc-sha256-pairwise-sub-mapper",
|
||||
"saml-user-attribute-mapper",
|
||||
"oidc-usermodel-property-mapper",
|
||||
"oidc-full-name-mapper"
|
||||
]
|
||||
}
|
||||
},
|
||||
@ -1618,13 +1673,13 @@
|
||||
"subComponents": {},
|
||||
"config": {
|
||||
"allowed-protocol-mapper-types": [
|
||||
"oidc-address-mapper",
|
||||
"saml-user-attribute-mapper",
|
||||
"saml-role-list-mapper",
|
||||
"oidc-usermodel-property-mapper",
|
||||
"oidc-sha256-pairwise-sub-mapper",
|
||||
"saml-user-property-mapper",
|
||||
"oidc-usermodel-attribute-mapper",
|
||||
"oidc-address-mapper",
|
||||
"saml-role-list-mapper",
|
||||
"oidc-usermodel-property-mapper",
|
||||
"saml-user-attribute-mapper",
|
||||
"oidc-full-name-mapper"
|
||||
]
|
||||
}
|
||||
@ -1678,6 +1733,12 @@
|
||||
"providerId": "rsa-generated",
|
||||
"subComponents": {},
|
||||
"config": {
|
||||
"privateKey": [
|
||||
"MIIEowIBAAKCAQEAso89qpvLhf9DIcCb2JAbxItRLSIvP/NCZhMdAExTHyrhM5B27ZQ6MZ7dJQbnMu7QJ7yiClsD1XnDN7Wlj07sY2As3lY3v9kjODBeADYlPuN1m7/fXFHX3qfRT+PwVSaAhMykmqvWp86UTg7t7rNjVBnXPPXItmRLIF+jZUMWQduwNznr6Jh54ZdIwEy4hvX1bpNw0nPl4KXiOi2elvg+rk7BhFywGwQ/HUCGkrcq0XS/aNOy1ChmqDbtq817mYpVeteCDe8xP3MPrZ/s2LiEt4Ip1cNo0dY+a4JwOzwL42h3GaR+80iK3pZNo+Mr0KBOY9GXvdV/MvcPHLQ7VujUGQIDAQABAoIBAAHV0OQwmDxUazqiVGe61Bzmcqs5q03SC1K/FmCi/YVikdskvGLaOmk5UQa4+1uDEq7J30onH9ML8+qeFRQek0rn2ZDfxtBpDqsx7LwTUmQtqc8z6buKQs37db5ctnhlk34UmAotQyDz5wMmCkzWWVUWCT02PdMev5qW/mKuIxaCWLHUFiMJaGrYCCwB/Ra8KLcadKgRbytSUth9qILC4krFfmWtzIx1P6nM1pzQ1nydxNnNPJKjoWtLRJ5b701Y5/h2vAAg6Mr+jKe1DPa9QmAqhQudjGbZ31av+0f1/I+XkflpZfokfU+MrAqNYRTYkevRYgc3wakK5mfVYUiMuOECgYEA7fk55O2OJFsR0Vjy4Dx4eSIwgwobvwEuHxlyWn0RC7nFb00eh6OPuc5sHrOk8bK3P367q67sEhxGyBF16nwxgX/T+c8gTC8QRuwNymosA4Je/zJHbKvyzLGOouCP5gYwq/wUmVWzNApVC7LBfxbsqYyivHABc5xgPmTgecY0VWkCgYEAwBXcUKoyq1KZegyNJcTuwuvBXoYVveFGm6QKKKwzojCCKaR3XXtdSon1qYfuKT0MLxgEDyyBks9DgfCodSsTmajX90Yolhyz3ptcOmRURqTRoJhM4g6qA+Ybd3uy8vAz32RdS+4rCTgnMG/5Xpn5B4ojOnhRcnA2TPCJgWz6QzECgYEAhj1FjD75JMb+mRJNB3L1HpfLt8+28RsQUli/ag4M1Il5txxQsYDxbYXk9biuvezrc/Tglqs43cp3nxpCYwClyIA8KjnN5UvTKb601M7pfx1GyzwokEO61f7/ECAO7FnnkMzFLe3rBdsiOFQg1LkwzT/Y+OVR3E6E+A1dlzPYh6kCgYBIP3CwfnO0cMr9Vv8394x+kEIZFYHT+4mdPOP9TFfXZztuAkhLRv1d7eoSq+fuZuHQTM4qDullmMOhei1CdMNYhmNExIS7gWw+DF1yMQ5py9B1ARPZ6v4TnVczZ7l1GtfH7G4TAy/4tcA3vcYjyPIb3d9GPL8VthMWeVqe7ahr4QKBgEwA7ASbs4NxfBsStEGQYQYAeWOoKnTc50FeYz38O4KrOirtTFPNsJcyCiTE0o4cqu/OebSA5irrauV7SEDl/gfH54g3ZWusQbLt2uMnZYtkd2+Ka3T9XM0QfQW/vYl3eJtdQj89TqzLzyP0AgvAyIgeG3RMH8ojqCh3YKY0FTv/"
|
||||
],
|
||||
"certificate": [
|
||||
"MIICnTCCAYUCBgGTy2TGBjANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdteXJlYWxtMB4XDTI0MTIxNTE3MzQ1OVoXDTM0MTIxNTE3MzYzOVowEjEQMA4GA1UEAwwHbXlyZWFsbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALKPPaqby4X/QyHAm9iQG8SLUS0iLz/zQmYTHQBMUx8q4TOQdu2UOjGe3SUG5zLu0Ce8ogpbA9V5wze1pY9O7GNgLN5WN7/ZIzgwXgA2JT7jdZu/31xR196n0U/j8FUmgITMpJqr1qfOlE4O7e6zY1QZ1zz1yLZkSyBfo2VDFkHbsDc56+iYeeGXSMBMuIb19W6TcNJz5eCl4jotnpb4Pq5OwYRcsBsEPx1AhpK3KtF0v2jTstQoZqg27avNe5mKVXrXgg3vMT9zD62f7Ni4hLeCKdXDaNHWPmuCcDs8C+NodxmkfvNIit6WTaPjK9CgTmPRl73VfzL3Dxy0O1bo1BkCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAggzxmYvHqUaCPLxxSidLQMgpu1pTozg3rTq8dcxhcHINI//A/z7qQyDA/QQN5cuSpYvdt2MRWoNop+uRNKqSr3C8aRErbY0j4acl7yG/ghNfQUZ9KxDBxKrd0HLFUibdZobg10+Ih/qXo3Mi2VtkqyZQRl/iy0O3ITgqb7YJUEx5tuEWyGbn+SerFvqZNcmsLziOJefm1n4uqroHgIfmgY6Deh+wZK0DwO3WZ6ThjhMp5GFi1oNeZ9xoExNEXrYp07b2xTQFF57oypc7prf733lqGjPRLfoVJP6qcsjvAlOA7f8TG9sKwGuRsPfadYY9PxmdHxl2k7PHDJeDhA7VdQ=="
|
||||
],
|
||||
"priority": ["100"]
|
||||
}
|
||||
},
|
||||
@ -1687,6 +1748,12 @@
|
||||
"providerId": "rsa-enc-generated",
|
||||
"subComponents": {},
|
||||
"config": {
|
||||
"privateKey": [
|
||||
"MIIEowIBAAKCAQEAxoEvnv+YHCqUWANGuku5QYscAZyUE0WHSlcAzZ0bQugPow63piQsuxPz0cpPIuLab6adssXUqKEFheT1H0BqtmT9L/7iOKB6MRuInN4aRzzTH9q02TKPkcpSAzAHTGcsJBMMawlbnIdMu5+mevMPxqeVVxvrnKG27S8H3W5jqIkQw8bo646Hr3l5Dxq/jY7slcSXXXe4ZdefeCvnSqea+fy5c+r/r546nX4FTGiklu6KLQaDc9SfGccrZDmljY7DX1kHrmvIdLShcuukTHc0hi2qbgMcUte/7/svSJLUWOZObKxetd4y1OA49v36xrMqGhwGDdwrWf0VuMBN8eHOCQIDAQABAoIBABz/hUXnFRZURWHKxLvKpnBZPTOiZzfzfxfl4tOmq54CtDoVQyXNq2J+6oOPWC/X+ky3hy+1BQ5x9hJrx+qTU04m2EfOe8da8M7DX28kZlauyjF2loG+MvP7ctn4BluWcip+RTZOYn2DfxBPpRcunR409V+JesoMY7fSwtrfA/Gm0PrXgBK7OuE0nxqFFWnsLOc+HxZECS5r0n1MHEBHe774HkqGcK91j8S+QU+/diTnK+N/ClnKWnabMK8bUO5wAUuKwf2deYkGP91pCEJlVnVZyaXshEM+uxTuMRUlq9h1QAIUatvdQwfOKqZ9XvmTVC8b79qLwmezjoDxNCKbaMMCgYEA71WDpMnA2uS2wCJ/MVwzWGSBDjfeKUPRy33BeUfwLGp4Dro+S1sTrLHgi1HGmvmC8ReZrifUlUHUi3ZHauR6vbNsEoSQ3hplO013kj12EfcBpvKYFg1ODCwevb/JtBTWbDG1P+E9DGiF/2u0aicoJoPolNeNVzgO6YK1OI/S/LMCgYEA1FPTqFPulXxcOK12LgYap8typqJ7zu4fByr42010yrKM+LLNA3bT/i/oRkKc7J1ztKSqlVckADWgK4Y27lI4j1tSgTOxFzwxnTZOeF7ZwGSxq9iy9A84nDiW+m6Hj5RDyBjTSoP2Qqv6d5kTUx+pczZvOVTWRlIEnFETbbxOoFMCgYEA0r1etHx+V4AqtxXpH6KLB5s/1DA3a+hu1BrAgLVqcwGxA27VKW9h7J+YE7UHBzELLpVUWfhyhJa5u6+DhUj4Fw/k6o1WLmvZlZVJ4zhBPeJczw8wAcLnZWp4CybUScBLamt+qGgBZGqpCtZgv1QJU5i09FK0/wa6grz4K3zhEGcCgYAlnGe8xIlZr3rCi2+IvYoROQepHtUhlaqnYWRNrI3IrhIsp7eLKoxo1WGmuHwFqepqEFUrORFmfBlQPGkUlDnyovGdc2OmQwJi39DMn7igzPVwBGXGt7+GZLvRxqx6sX/EPSmIZJHFw6MNdm8m5U/l2bmgBTgjormwWug/IwEmgwKBgEouISIuXsjGxeLmhrOXHKXb6IfKglNJeBM6lTQ6MLaVOso7KdelIntwZNtZwMIi3hlwaUb1X1QmztFbnrvnPhWwJR4ZgMEWanRHthtm0SHzg8EHKT40S91oKabsgHk3wpOvq/iWs+k8qWN4HYp6UO603uLMOfxPYJCFxRtg2TsJ"
|
||||
],
|
||||
"certificate": [
|
||||
"MIICnTCCAYUCBgGTy2TG/jANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdteXJlYWxtMB4XDTI0MTIxNTE3MzQ1OVoXDTM0MTIxNTE3MzYzOVowEjEQMA4GA1UEAwwHbXlyZWFsbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMaBL57/mBwqlFgDRrpLuUGLHAGclBNFh0pXAM2dG0LoD6MOt6YkLLsT89HKTyLi2m+mnbLF1KihBYXk9R9AarZk/S/+4jigejEbiJzeGkc80x/atNkyj5HKUgMwB0xnLCQTDGsJW5yHTLufpnrzD8anlVcb65yhtu0vB91uY6iJEMPG6OuOh695eQ8av42O7JXEl113uGXXn3gr50qnmvn8uXPq/6+eOp1+BUxopJbuii0Gg3PUnxnHK2Q5pY2Ow19ZB65ryHS0oXLrpEx3NIYtqm4DHFLXv+/7L0iS1FjmTmysXrXeMtTgOPb9+sazKhocBg3cK1n9FbjATfHhzgkCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAdUIlJ91E0UkFS45AByjFufRnQbAi1smnHkC3WSN39bhcFT7Hgip97qtABODR58zVHSTS0XcMiL4mMObH3Vyz9J3gmwWZnbokAuo9tYeyrhPh/gqXv3LGtGhTpWlUJ7JEJxH7RVI4UZZyG6Y6FR+3zwiZ0j1p3QsZclfcNmacoi/Ano+4TfloOnY4k8yP7G6LWUTJHpcRNWVVozM3RwekYgpJRAtXDoYfm9p2hRQ090e7NvbblSuVQ/FXhUn4g0wz91WdCWlwXZfvNaRjbynPCHejJpszqiyjPkx3aRKTWqer0ZocKNmY8+RO27XIsXmwOYcjdpX2TCFDv6O+VLfNdw=="
|
||||
],
|
||||
"priority": ["100"],
|
||||
"algorithm": ["RSA-OAEP"]
|
||||
}
|
||||
@ -1697,6 +1764,8 @@
|
||||
"providerId": "aes-generated",
|
||||
"subComponents": {},
|
||||
"config": {
|
||||
"kid": ["95db7eb8-b57b-475e-90cd-58841a9388d3"],
|
||||
"secret": ["dp6bv53YrC2PZuJCxa3aNA"],
|
||||
"priority": ["100"]
|
||||
}
|
||||
},
|
||||
@ -1706,6 +1775,10 @@
|
||||
"providerId": "hmac-generated",
|
||||
"subComponents": {},
|
||||
"config": {
|
||||
"kid": ["d0254883-059e-4fdd-bf03-704c76650aab"],
|
||||
"secret": [
|
||||
"bcW7E4rcbgSKZIQysWOSuhezRGYs5Kzmp3ZESthdTUMyFivK8RbBAdBE4PhFPk5B9TuByDO2RWvd8F7F5YhGJitf6cfYB1BfDuAk-2iBAtdZA98g7a2h4jpwzh-GIgtoRbGbH9qnquUn52f5qteo34g5WifKE2bWjOELza9FrTo"
|
||||
],
|
||||
"priority": ["100"],
|
||||
"algorithm": ["HS512"]
|
||||
}
|
@ -563,6 +563,40 @@
|
||||
"disableableCredentialTypes": [],
|
||||
"requiredActions": [],
|
||||
"realmRoles": ["default-roles-myrealm"],
|
||||
"clientRoles": {
|
||||
"realm-management": [
|
||||
"manage-users",
|
||||
"create-client",
|
||||
"view-users",
|
||||
"view-realm",
|
||||
"query-realms",
|
||||
"impersonation",
|
||||
"view-events",
|
||||
"realm-admin",
|
||||
"manage-authorization",
|
||||
"view-authorization",
|
||||
"manage-events",
|
||||
"manage-clients",
|
||||
"query-users",
|
||||
"query-groups",
|
||||
"manage-realm",
|
||||
"query-clients",
|
||||
"manage-identity-providers",
|
||||
"view-identity-providers",
|
||||
"view-clients"
|
||||
],
|
||||
"broker": ["read-token"],
|
||||
"account": [
|
||||
"delete-account",
|
||||
"view-applications",
|
||||
"manage-account",
|
||||
"view-consent",
|
||||
"view-groups",
|
||||
"view-profile",
|
||||
"manage-account-links",
|
||||
"manage-consent"
|
||||
]
|
||||
},
|
||||
"notBefore": 0,
|
||||
"groups": []
|
||||
}
|
||||
@ -638,7 +672,11 @@
|
||||
"enabled": true,
|
||||
"alwaysDisplayInConsole": false,
|
||||
"clientAuthenticatorType": "client-secret",
|
||||
"redirectUris": ["*"],
|
||||
"redirectUris": [
|
||||
"http://localhost*",
|
||||
"http://127.0.0.1*",
|
||||
"/realms/myrealm/account/*"
|
||||
],
|
||||
"webOrigins": ["*"],
|
||||
"notBefore": 0,
|
||||
"bearerOnly": false,
|
||||
@ -805,8 +843,7 @@
|
||||
"realm_client": "false",
|
||||
"oidc.ciba.grant.enabled": "false",
|
||||
"backchannel.logout.session.required": "true",
|
||||
"login_theme": "keycloakify-starter",
|
||||
"post.logout.redirect.uris": "https://my-theme.keycloakify.dev/*##http://localhost*##http://127.0.0.1*",
|
||||
"post.logout.redirect.uris": "+",
|
||||
"oauth2.device.authorization.grant.enabled": "false",
|
||||
"display.on.consent.screen": "false",
|
||||
"backchannel.logout.revoke.offline.tokens": "false"
|
||||
@ -894,14 +931,20 @@
|
||||
"id": "fce8a109-6f32-4814-9a20-2ff2435d2da6",
|
||||
"clientId": "security-admin-console",
|
||||
"name": "${client_security-admin-console}",
|
||||
"description": "",
|
||||
"rootUrl": "${authAdminUrl}",
|
||||
"adminUrl": "",
|
||||
"baseUrl": "/admin/myrealm/console/",
|
||||
"surrogateAuthRequired": false,
|
||||
"enabled": true,
|
||||
"alwaysDisplayInConsole": false,
|
||||
"clientAuthenticatorType": "client-secret",
|
||||
"redirectUris": ["/admin/myrealm/console/*"],
|
||||
"webOrigins": ["+"],
|
||||
"redirectUris": [
|
||||
"http://localhost*",
|
||||
"http://127.0.0.1*",
|
||||
"/admin/myrealm/console/*"
|
||||
],
|
||||
"webOrigins": ["*"],
|
||||
"notBefore": 0,
|
||||
"bearerOnly": false,
|
||||
"consentRequired": false,
|
||||
@ -914,9 +957,14 @@
|
||||
"protocol": "openid-connect",
|
||||
"attributes": {
|
||||
"realm_client": "false",
|
||||
"oidc.ciba.grant.enabled": "false",
|
||||
"client.use.lightweight.access.token.enabled": "true",
|
||||
"backchannel.logout.session.required": "true",
|
||||
"post.logout.redirect.uris": "+",
|
||||
"pkce.code.challenge.method": "S256"
|
||||
"oauth2.device.authorization.grant.enabled": "false",
|
||||
"display.on.consent.screen": "false",
|
||||
"pkce.code.challenge.method": "S256",
|
||||
"backchannel.logout.revoke.offline.tokens": "false"
|
||||
},
|
||||
"authenticationFlowBindingOverrides": {},
|
||||
"fullScopeAllowed": true,
|
||||
@ -937,6 +985,24 @@
|
||||
"claim.name": "locale",
|
||||
"jsonType.label": "String"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "8fd0d584-7052-4d04-a615-d18a71050873",
|
||||
"name": "allowed-origins",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-hardcoded-claim-mapper",
|
||||
"consentRequired": false,
|
||||
"config": {
|
||||
"introspection.token.claim": "true",
|
||||
"claim.value": "[\"*\"]",
|
||||
"userinfo.token.claim": "true",
|
||||
"id.token.claim": "false",
|
||||
"lightweight.claim": "true",
|
||||
"access.token.claim": "true",
|
||||
"claim.name": "allowed-origins",
|
||||
"jsonType.label": "JSON",
|
||||
"access.tokenResponse.claim": "false"
|
||||
}
|
||||
}
|
||||
],
|
||||
"defaultClientScopes": [
|
||||
@ -1561,11 +1627,11 @@
|
||||
},
|
||||
"smtpServer": {},
|
||||
"loginTheme": "keycloakify-starter",
|
||||
"accountTheme": "keycloakify-starter",
|
||||
"adminTheme": "",
|
||||
"accountTheme": "",
|
||||
"adminTheme": "keycloakify-starter",
|
||||
"emailTheme": "",
|
||||
"eventsEnabled": false,
|
||||
"eventsListeners": ["jboss-logging"],
|
||||
"eventsListeners": ["keycloakify-logging", "jboss-logging"],
|
||||
"enabledEventTypes": [],
|
||||
"adminEventsEnabled": false,
|
||||
"adminEventsDetailsEnabled": false,
|
||||
@ -1591,14 +1657,14 @@
|
||||
"subComponents": {},
|
||||
"config": {
|
||||
"allowed-protocol-mapper-types": [
|
||||
"saml-user-attribute-mapper",
|
||||
"oidc-sha256-pairwise-sub-mapper",
|
||||
"oidc-full-name-mapper",
|
||||
"saml-user-property-mapper",
|
||||
"oidc-address-mapper",
|
||||
"saml-user-attribute-mapper",
|
||||
"oidc-usermodel-property-mapper",
|
||||
"oidc-usermodel-attribute-mapper",
|
||||
"saml-user-property-mapper",
|
||||
"oidc-sha256-pairwise-sub-mapper",
|
||||
"saml-role-list-mapper",
|
||||
"oidc-usermodel-attribute-mapper"
|
||||
"oidc-full-name-mapper"
|
||||
]
|
||||
}
|
||||
},
|
||||
@ -1629,13 +1695,13 @@
|
||||
"config": {
|
||||
"allowed-protocol-mapper-types": [
|
||||
"oidc-usermodel-attribute-mapper",
|
||||
"oidc-full-name-mapper",
|
||||
"oidc-usermodel-property-mapper",
|
||||
"oidc-address-mapper",
|
||||
"saml-user-property-mapper",
|
||||
"oidc-sha256-pairwise-sub-mapper",
|
||||
"saml-role-list-mapper",
|
||||
"saml-user-attribute-mapper",
|
||||
"oidc-sha256-pairwise-sub-mapper"
|
||||
"oidc-address-mapper",
|
||||
"oidc-full-name-mapper",
|
||||
"saml-user-property-mapper",
|
||||
"oidc-usermodel-property-mapper",
|
||||
"saml-user-attribute-mapper"
|
||||
]
|
||||
}
|
||||
},
|
||||
@ -1688,6 +1754,12 @@
|
||||
"providerId": "rsa-generated",
|
||||
"subComponents": {},
|
||||
"config": {
|
||||
"privateKey": [
|
||||
"MIIEoQIBAAKCAQEAxTFMvRiNiQjY9zajvLsah6Vy4pn8U7smsnBcHS9SkLJ1j9O8+90B90tIZk4IqEE4gdJA/mbbeUnou1vWuc0k69diQMFelzdIaDqJaFFeOS+J1DoApjThjGIz7FIgmGi6qoN8xnrPVD/6oMYAuxTvQaJH7mENiIG0198dvaufV1mFPg+krTsh7Womo2CJeZmNuAXv7RDQYxwPYDCFZLbppez48D7+2D+1V6Stk6Xwz8IDQZvljxDF6W2P9rhPWV1C5tcJpC/9RPyGDo+ke8UN3fM6X7YOgpbMztVrg8J0aTqPXZ7dt6QFUqVOufo+5wYL2jCafpYNV8cmaGlY+Q3d5QIDAQABAoH/DIPcaZaJTLG4FeUKGOaT40nesEiINRY99aeIkp+hdGj1EgTEn49TyLENGnhrrdbIvOJDeD6Z6dbpJBDvfFevxa589EnVKaGaaW5U91FDyVYH2YPU411dAeOp0z1xwxXzlJqX3h42ZJnvLAp/2l1Xo64vGCoTJtYlppAvpe2MjANxPNObAc65Phdi/sConAlwMeBylWXJ574uryFrJ64W/sUuIUMSunGGz0db4Y1hfkX9U2YnxB3DdXCBH09jQJyKDSj6feNXR87+1KhqcFMd5DUiGSAOqRBzuBMsDf1QDJd8A/DDlK7e/PA1Yk/Dii4hsf+LCeOdmhlifuyROqJBAoGBAOEm4gLvaBWwnUhmr4sW8xywIhGGbU+MX6vm/KkGtScres7pPhmfy6ARUzCxxyBqIE+nhCRNBpOEPhP7dv8naJhZZ4fRvNzuXpUMT2X3bc5yNzdhaOxBJl95YQbrYUHhjcIw2kdXnIkpdbB/RqmY0F5BUTYECrd0tKWbjuL5RIRNAoGBAOA1wTXrYyVorouxV+mGNb62Py+utHJQKSa5cxF9nbbwWJd+FdreiBOJddjATmH8ovKjueQFVqK7koDveOb+pgRY2bpT88/NW8UF6a2wMiI0p6pxrR+hgzas480YiOCWr6XlsprqsSKBbEu4W97GicleZ6P5Iso/gBr9aHj9EWv5AoGAYhRzHj42RESUr4Zz8A5GR3f+z02U7rNCtfrAk80lOvP44ou+jqEKrib961d2XAt/GdPqf3nCZJ6WAFRp6Qq8yKkhrYvTTxbTwvAC4nNftTASF6DqeQiEc9DHUKFW08Ey5KYtYCitOx8BcqpvGNBF7NldTD+Ef5hqXT4fh4Z4r30CgYEAy2OYGMymTRowNKK06C+Kc62plhy6rnRPUESswLIeLwTKqOqE8t4pvOdWk0CoGjVusAOcLuA03jyfwvz5xTo96fWb1W4w31IgLJOXjqsmX2c6reCfNvFyMVgW8keOa4XmYu0C34uFEpMrZWkhVe7usVBFXjczuxptoI4+hnqzoikCgYBICBVR9Z7n2LvmWH19/Nnns8dsMn5peL7H6Mey76Lo9RMEMp4qhiJTqVZzWgxEyVjr0KFCHmdmwkTOm6A1yYmkqqXDdiJ9v4J4fXe0lRAoUoYPTOWynrCyd6uqq+3zlzTKW8jY9luywHq6msn07D636PvveeZ93DNCcO8Whw36rQ=="
|
||||
],
|
||||
"certificate": [
|
||||
"MIICnTCCAYUCBgGTulJBzTANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdteXJlYWxtMB4XDTI0MTIxMjEwMDExM1oXDTM0MTIxMjEwMDI1M1owEjEQMA4GA1UEAwwHbXlyZWFsbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMUxTL0YjYkI2Pc2o7y7GoelcuKZ/FO7JrJwXB0vUpCydY/TvPvdAfdLSGZOCKhBOIHSQP5m23lJ6Ltb1rnNJOvXYkDBXpc3SGg6iWhRXjkvidQ6AKY04YxiM+xSIJhouqqDfMZ6z1Q/+qDGALsU70GiR+5hDYiBtNffHb2rn1dZhT4PpK07Ie1qJqNgiXmZjbgF7+0Q0GMcD2AwhWS26aXs+PA+/tg/tVekrZOl8M/CA0Gb5Y8Qxeltj/a4T1ldQubXCaQv/UT8hg6PpHvFDd3zOl+2DoKWzM7Va4PCdGk6j12e3bekBVKlTrn6PucGC9owmn6WDVfHJmhpWPkN3eUCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEATZXyOluloTj6Q/Mv0JjstfdvPQbzGFzWtULB1ttOJqQVL+IJoF8V79HIvfP9U5OYaOdYk9dDurQcd2hXvEtX+zQlLYGniRfJlFI7d+m6MDXa7/g1r+OmcvaiXX7O3ol7eJdymPKS79+PSWFsHk0JjfgRJ11jajOscYPoQ+IvxXgwuy6v7VHigsLnGnmmo+KWiKO6Cna6eilm6/awYXaoym4ky9S4T5+WaJwd/tH/n5VY77zyXaXfANd1hU/+4Ux/eaGVnoMAM4ud2emd4qCN2tQQ3HusIVl+5V+S8Uq1y54mBpXv6CAODDGDJeFa+cGPJUSLdv/ZT2F8yfDlDc4J6g=="
|
||||
],
|
||||
"priority": ["100"]
|
||||
}
|
||||
},
|
||||
@ -1697,6 +1769,12 @@
|
||||
"providerId": "rsa-enc-generated",
|
||||
"subComponents": {},
|
||||
"config": {
|
||||
"privateKey": [
|
||||
"MIIEogIBAAKCAQEAungL4osLyP8bE6MSKj8ZMJTG8WBh3K2/xB5BJYCYc7P1CIORZI9o/vKQx1QnP+CXkIKnnR2kzIzC0rnTqlIOkaZfhmSn50jG5vNBS9qPT+WU7Ue3qKxuWJFwcaFU5SEJawJHqnDPK+pktkkxkudeMHz6iaKPs+wKcbfrRJ6+3a3FqQQdHEQg4IjVU8pBZmag1c7JHayiM56OT5y6jmE5JvY60959iPrZPXSTMU3hNoiVwdyK6QwdK+/0wrO681VhIP+u2pe92nQ+hsgMSSQJegLx1UsEEyU87syblG+p3zAKSS+kt2nviV/a2cYiiME0LdlQ3lnKsQ4t1Y6yZBiS2QIDAQABAoIBABhozI18TC+kjWPVrfQPzHlakGxahJUBvZ+rojWJjutefE4AAxFZ4JG3KRKexoCLIuwM3monzkHkj0BMiRO7qCKS1+Bc3snc8gSbhUmrs6Tu1b7162nOIKfBainFx7oyx+vVIZKDL+t8xHBERpQHa4IHajiIKi2QUZGvVMHn0e5srkPK0eSMjb5Z5j61aFb8InQzs7tczr99ke4VavOPT1gmRWGnbTavUbw/zIQ9sxAuMiD2v0nrGlOLZrMhaqzsT6PjIWVCSZrWex1pin9gA4XwGZ39E7+zFWgg+2OX0dEvehVDluAQR0K4PBUknuL1LFFW8dpvCrUSTmGGQOSVuB0CgYEA+bQjbjTNiMTEfoxx/WvVDgtLRL/x9RVyeYTPia2TGNBwpEcU64lLMOwUt5X/QuGXayPr0EGAxMA8kwq/E8Wj2t9+SuqkGK9SIwvghi2fOh0KWghuQbKYMogG5hsJAI8+/mBIOJJ8pyh0RX58vaTlYctbThO22aVahhZQ2weaW58CgYEAvyu4vIe44/7F19Hjh2BW+9lHsHA2zwHvC5T1kFaEdBYEwGsLMW6leCsiEMfpc2Uq3k9+buZgVpTE5APs9cSJX1aUXEG5QHQmYDxAAMiTyvpj0o2cKbDi1A5QZCRo23lC+uDyR7g2zLDJuHek0uyCtd83hbgyxIVFUnfvI9EmfocCgYBtpcZxHEqspgrKrw1XBMTXl+oDVG4A+tv7tHAVutx+5vivim8LRox3/RLT0s/2JG2DJJDmL/1FaEyxHOTu37il4cHpT8Oi+0mMDikXgm0K7bmf81fHDY97kPPGk1SOpFg7BzhvbxPBqyfzZCmOdRwsp0l+rXV7ePqZKq9ynpIPbQKBgFO/LZC5zE9k/vrK4egeVjzCNNugbQJGkJf8S49Nt3y7YJ2Cx0aCeE6qZqP/T8/Tk/IL1RF0LuP/DDnvVlFcJen0Hc5EpIkN2Pnzqv4s4EHdavmEO9MvwE6xbppQMPdkqekJvlmY47jMAbKkBzq3jZNrFAGqbeMVlwbHr6V7LGflAoGANFbzOnUMJwUfIdoI9uEG2QOTAcBb7vzt9MurO67wiTexOYadOSlcV1lQX3RKR9mCFJwy4kud0TN0gD++Ggl10eNB6f8JOF95e5+tWrtz88xZ5EalBOMfh+ATdKq8Q9MBSWZvO9bizhW1dhZZds/QmHgEItdwsTKDAq1PEiXhD0c="
|
||||
],
|
||||
"certificate": [
|
||||
"MIICnTCCAYUCBgGTulJDCDANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdteXJlYWxtMB4XDTI0MTIxMjEwMDExM1oXDTM0MTIxMjEwMDI1M1owEjEQMA4GA1UEAwwHbXlyZWFsbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALp4C+KLC8j/GxOjEio/GTCUxvFgYdytv8QeQSWAmHOz9QiDkWSPaP7ykMdUJz/gl5CCp50dpMyMwtK506pSDpGmX4Zkp+dIxubzQUvaj0/llO1Ht6isbliRcHGhVOUhCWsCR6pwzyvqZLZJMZLnXjB8+omij7PsCnG360Sevt2txakEHRxEIOCI1VPKQWZmoNXOyR2sojOejk+cuo5hOSb2OtPefYj62T10kzFN4TaIlcHciukMHSvv9MKzuvNVYSD/rtqXvdp0PobIDEkkCXoC8dVLBBMlPO7Mm5Rvqd8wCkkvpLdp74lf2tnGIojBNC3ZUN5ZyrEOLdWOsmQYktkCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAPhPdLFcXdQT4k06oXB06ZSJ8AkZNXLvQFWCHXI34OmrS2yTse+dLqrqehnC3kPwxElVmawoUVc1sbsk7fUnspfM+Xw20PaABZu4MO2m5TB98f1hEkezP9fSqgPeuWJgTL8ZW5kkZyiD3IaZoqyxzYXaFxKHhU455g+k2+DO+N6FreVKcYz12Q5EMaxZ6U1neZAo3vicNxM3/TA5V8sPK8+oKvon7v5OyjpOH0goJo9v/klKeUk36h4u2h1S67IhVSU7tfzVFYrpns1JhrwGZ2xavVqEoqX8zFp3GKz3yVXkwHRHlrzYkZoGn21rm5boXIP3wEB7yXZbXWTiUko/IFw=="
|
||||
],
|
||||
"priority": ["100"],
|
||||
"algorithm": ["RSA-OAEP"]
|
||||
}
|
||||
@ -1707,6 +1785,8 @@
|
||||
"providerId": "aes-generated",
|
||||
"subComponents": {},
|
||||
"config": {
|
||||
"kid": ["c36222c6-6a43-4d32-9d44-d5d355e5cabd"],
|
||||
"secret": ["rzL4qUQ7wTEkZDbgt595VA"],
|
||||
"priority": ["100"]
|
||||
}
|
||||
},
|
||||
@ -1716,6 +1796,10 @@
|
||||
"providerId": "hmac-generated",
|
||||
"subComponents": {},
|
||||
"config": {
|
||||
"kid": ["06532a54-c310-41c1-829c-58776ce2ab4a"],
|
||||
"secret": [
|
||||
"9v1ZjFhEFH6UpY6ncFkaCbqJYHMyI4tA0cvx4GuQ5KtMXYbimitSSVDqxIKwa-gBC_8bY2O4FQfpmp1Qn1-L4fFmPFfIF3ZKsO16263BwpADo_FNSBTte8Le4gJLylqFULdsn3ye17FHyq5Jjms_OTt3opzcDLNduCuK22GBBsU"
|
||||
],
|
||||
"priority": ["100"],
|
||||
"algorithm": ["HS512"]
|
||||
}
|
||||
@ -2385,7 +2469,7 @@
|
||||
"clientSessionMaxLifespan": "0",
|
||||
"organizationsEnabled": "false"
|
||||
},
|
||||
"keycloakVersion": "26.0.6",
|
||||
"keycloakVersion": "26.0.7",
|
||||
"userManagedAccessAllowed": false,
|
||||
"organizationsEnabled": false,
|
||||
"clientProfiles": {
|
194
src/bin/start-keycloak/realmConfig/dumpContainerConfig.ts
Normal file
194
src/bin/start-keycloak/realmConfig/dumpContainerConfig.ts
Normal file
@ -0,0 +1,194 @@
|
||||
import { CONTAINER_NAME } from "../../shared/constants";
|
||||
import child_process from "child_process";
|
||||
import { join as pathJoin, dirname as pathDirname, basename as pathBasename } from "path";
|
||||
import chalk from "chalk";
|
||||
import { Deferred } from "evt/tools/Deferred";
|
||||
import { assert, is } from "tsafe/assert";
|
||||
import type { BuildContext } from "../../shared/buildContext";
|
||||
import { type ParsedRealmJson, readRealmJsonFile } from "./ParsedRealmJson";
|
||||
|
||||
export type BuildContextLike = {
|
||||
cacheDirPath: string;
|
||||
};
|
||||
|
||||
assert<BuildContext extends BuildContextLike ? true : false>();
|
||||
|
||||
export async function dumpContainerConfig(params: {
|
||||
realmName: string;
|
||||
keycloakMajorVersionNumber: number;
|
||||
buildContext: BuildContextLike;
|
||||
}): Promise<ParsedRealmJson> {
|
||||
const { realmName, keycloakMajorVersionNumber, buildContext } = params;
|
||||
|
||||
// https://github.com/keycloak/keycloak/issues/33800
|
||||
const doesUseLockedH2Database = keycloakMajorVersionNumber >= 25;
|
||||
|
||||
if (doesUseLockedH2Database) {
|
||||
const dCompleted = new Deferred<void>();
|
||||
|
||||
const cmd = `docker exec ${CONTAINER_NAME} sh -c "cp -rp /opt/keycloak/data/h2 /tmp"`;
|
||||
|
||||
child_process.exec(cmd, error => {
|
||||
if (error !== null) {
|
||||
dCompleted.reject(error);
|
||||
return;
|
||||
}
|
||||
|
||||
dCompleted.resolve();
|
||||
});
|
||||
|
||||
try {
|
||||
await dCompleted.pr;
|
||||
} catch (error) {
|
||||
assert(is<Error>(error));
|
||||
|
||||
console.log(chalk.red(`Docker command failed: ${cmd}`));
|
||||
|
||||
console.log(chalk.red(error.message));
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
const dCompleted = new Deferred<void>();
|
||||
|
||||
const child = child_process.spawn(
|
||||
"docker",
|
||||
[
|
||||
...["exec", CONTAINER_NAME],
|
||||
...["/opt/keycloak/bin/kc.sh", "export"],
|
||||
...["--dir", "/tmp"],
|
||||
...["--realm", realmName],
|
||||
...["--users", "realm_file"],
|
||||
...(!doesUseLockedH2Database
|
||||
? []
|
||||
: [
|
||||
...["--db", "dev-file"],
|
||||
...[
|
||||
"--db-url",
|
||||
'"jdbc:h2:file:/tmp/h2/keycloakdb;NON_KEYWORDS=VALUE"'
|
||||
]
|
||||
])
|
||||
],
|
||||
{ shell: true }
|
||||
);
|
||||
|
||||
let output = "";
|
||||
|
||||
const onExit = (code: number | null) => {
|
||||
dCompleted.reject(
|
||||
new Error(`docker exec kc.sh export command failed with code ${code}`)
|
||||
);
|
||||
};
|
||||
|
||||
child.once("exit", onExit);
|
||||
|
||||
child.stdout.on("data", data => {
|
||||
const outputStr = data.toString("utf8");
|
||||
|
||||
if (outputStr.includes("Export finished successfully")) {
|
||||
child.removeListener("exit", onExit);
|
||||
|
||||
// NOTE: On older Keycloak versions the process keeps running after the export is done.
|
||||
const timer = setTimeout(() => {
|
||||
child.removeListener("exit", onExit2);
|
||||
child.kill();
|
||||
dCompleted.resolve();
|
||||
}, 1500);
|
||||
|
||||
const onExit2 = () => {
|
||||
clearTimeout(timer);
|
||||
dCompleted.resolve();
|
||||
};
|
||||
|
||||
child.once("exit", onExit2);
|
||||
}
|
||||
|
||||
output += outputStr;
|
||||
});
|
||||
|
||||
child.stderr.on("data", data => (output += chalk.red(data.toString("utf8"))));
|
||||
|
||||
try {
|
||||
await dCompleted.pr;
|
||||
} catch (error) {
|
||||
assert(is<Error>(error));
|
||||
|
||||
console.log(chalk.red(error.message));
|
||||
|
||||
console.log(output);
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
if (doesUseLockedH2Database) {
|
||||
const dCompleted = new Deferred<void>();
|
||||
|
||||
const cmd = `docker exec ${CONTAINER_NAME} sh -c "rm -rf /tmp/h2"`;
|
||||
|
||||
child_process.exec(cmd, error => {
|
||||
if (error !== null) {
|
||||
dCompleted.reject(error);
|
||||
return;
|
||||
}
|
||||
|
||||
dCompleted.resolve();
|
||||
});
|
||||
|
||||
try {
|
||||
await dCompleted.pr;
|
||||
} catch (error) {
|
||||
assert(is<Error>(error));
|
||||
|
||||
console.log(chalk.red(`Docker command failed: ${cmd}`));
|
||||
|
||||
console.log(chalk.red(error.message));
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
const targetRealmConfigJsonFilePath_tmp = pathJoin(
|
||||
buildContext.cacheDirPath,
|
||||
"realm.json"
|
||||
);
|
||||
|
||||
{
|
||||
const dCompleted = new Deferred<void>();
|
||||
|
||||
const cmd = `docker cp ${CONTAINER_NAME}:/tmp/${realmName}-realm.json ${pathBasename(targetRealmConfigJsonFilePath_tmp)}`;
|
||||
|
||||
child_process.exec(
|
||||
cmd,
|
||||
{
|
||||
cwd: pathDirname(targetRealmConfigJsonFilePath_tmp)
|
||||
},
|
||||
error => {
|
||||
if (error !== null) {
|
||||
dCompleted.reject(error);
|
||||
return;
|
||||
}
|
||||
|
||||
dCompleted.resolve();
|
||||
}
|
||||
);
|
||||
|
||||
try {
|
||||
await dCompleted.pr;
|
||||
} catch (error) {
|
||||
assert(is<Error>(error));
|
||||
|
||||
console.log(chalk.red(`Docker command failed: ${cmd}`));
|
||||
|
||||
console.log(chalk.red(error.message));
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
return readRealmJsonFile({
|
||||
realmJsonFilePath: targetRealmConfigJsonFilePath_tmp
|
||||
});
|
||||
}
|
1
src/bin/start-keycloak/realmConfig/index.ts
Normal file
1
src/bin/start-keycloak/realmConfig/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./realmConfig";
|
365
src/bin/start-keycloak/realmConfig/prepareRealmConfig.ts
Normal file
365
src/bin/start-keycloak/realmConfig/prepareRealmConfig.ts
Normal file
@ -0,0 +1,365 @@
|
||||
import { assert } from "tsafe/assert";
|
||||
import type { ParsedRealmJson } from "./ParsedRealmJson";
|
||||
import { getDefaultConfig } from "./defaultConfig";
|
||||
import type { BuildContext } from "../../shared/buildContext";
|
||||
import { objectKeys } from "tsafe/objectKeys";
|
||||
import { TEST_APP_URL } from "../../shared/constants";
|
||||
import { sameFactory } from "evt/tools/inDepth/same";
|
||||
|
||||
export type BuildContextLike = {
|
||||
themeNames: BuildContext["themeNames"];
|
||||
implementedThemeTypes: BuildContext["implementedThemeTypes"];
|
||||
};
|
||||
|
||||
assert<BuildContext extends BuildContextLike ? true : false>;
|
||||
|
||||
export function prepareRealmConfig(params: {
|
||||
parsedRealmJson: ParsedRealmJson;
|
||||
keycloakMajorVersionNumber: number;
|
||||
buildContext: BuildContextLike;
|
||||
}): {
|
||||
realmName: string;
|
||||
clientName: string;
|
||||
username: string;
|
||||
} {
|
||||
const { parsedRealmJson, keycloakMajorVersionNumber, buildContext } = params;
|
||||
|
||||
const { username } = addOrEditTestUser({
|
||||
parsedRealmJson,
|
||||
keycloakMajorVersionNumber
|
||||
});
|
||||
|
||||
const { clientId } = addOrEditClient({
|
||||
parsedRealmJson,
|
||||
keycloakMajorVersionNumber
|
||||
});
|
||||
|
||||
editAccountConsoleAndSecurityAdminConsole({ parsedRealmJson });
|
||||
|
||||
enableCustomThemes({
|
||||
parsedRealmJson,
|
||||
themeName: buildContext.themeNames[0],
|
||||
implementedThemeTypes: buildContext.implementedThemeTypes
|
||||
});
|
||||
|
||||
enable_custom_events_listeners: {
|
||||
const name = "keycloakify-logging";
|
||||
|
||||
if (parsedRealmJson.eventsListeners.includes(name)) {
|
||||
break enable_custom_events_listeners;
|
||||
}
|
||||
|
||||
parsedRealmJson.eventsListeners.push(name);
|
||||
|
||||
parsedRealmJson.eventsListeners.sort();
|
||||
}
|
||||
|
||||
return {
|
||||
realmName: parsedRealmJson.realm,
|
||||
clientName: clientId,
|
||||
username
|
||||
};
|
||||
}
|
||||
|
||||
function enableCustomThemes(params: {
|
||||
parsedRealmJson: ParsedRealmJson;
|
||||
themeName: string;
|
||||
implementedThemeTypes: BuildContextLike["implementedThemeTypes"];
|
||||
}) {
|
||||
const { parsedRealmJson, themeName, implementedThemeTypes } = params;
|
||||
|
||||
for (const themeType of objectKeys(implementedThemeTypes)) {
|
||||
if (!implementedThemeTypes[themeType].isImplemented) {
|
||||
continue;
|
||||
}
|
||||
|
||||
parsedRealmJson[`${themeType}Theme` as const] = themeName;
|
||||
}
|
||||
}
|
||||
|
||||
function addOrEditTestUser(params: {
|
||||
parsedRealmJson: ParsedRealmJson;
|
||||
keycloakMajorVersionNumber: number;
|
||||
}): { username: string } {
|
||||
const { parsedRealmJson, keycloakMajorVersionNumber } = params;
|
||||
|
||||
const parsedRealmJson_default = getDefaultConfig({ keycloakMajorVersionNumber });
|
||||
|
||||
const [defaultUser_default] = parsedRealmJson_default.users;
|
||||
|
||||
assert(defaultUser_default !== undefined);
|
||||
|
||||
const defaultUser_preexisting = parsedRealmJson.users.find(
|
||||
user => user.username === defaultUser_default.username
|
||||
);
|
||||
|
||||
const newUser = structuredClone(
|
||||
defaultUser_preexisting ??
|
||||
(() => {
|
||||
const firstUser = parsedRealmJson.users[0];
|
||||
|
||||
if (firstUser === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const firstUserCopy = structuredClone(firstUser);
|
||||
|
||||
firstUserCopy.id = defaultUser_default.id;
|
||||
|
||||
return firstUserCopy;
|
||||
})() ??
|
||||
defaultUser_default
|
||||
);
|
||||
|
||||
newUser.username = defaultUser_default.username;
|
||||
newUser.email = defaultUser_default.email;
|
||||
|
||||
delete_existing_password_credential_if_any: {
|
||||
const i = newUser.credentials.findIndex(
|
||||
credential => credential.type === "password"
|
||||
);
|
||||
|
||||
if (i === -1) {
|
||||
break delete_existing_password_credential_if_any;
|
||||
}
|
||||
|
||||
newUser.credentials.splice(i, 1);
|
||||
}
|
||||
|
||||
{
|
||||
const credential = defaultUser_default.credentials.find(
|
||||
credential => credential.type === "password"
|
||||
);
|
||||
|
||||
assert(credential !== undefined);
|
||||
|
||||
newUser.credentials.push(credential);
|
||||
}
|
||||
|
||||
{
|
||||
const nameByClientId = Object.fromEntries(
|
||||
parsedRealmJson.clients.map(client => [client.id, client.clientId] as const)
|
||||
);
|
||||
|
||||
const newClientRoles: NonNullable<
|
||||
ParsedRealmJson["users"][number]["clientRoles"]
|
||||
> = {};
|
||||
|
||||
for (const clientRole of Object.values(parsedRealmJson.roles.client).flat()) {
|
||||
const clientName = nameByClientId[clientRole.containerId];
|
||||
|
||||
assert(clientName !== undefined);
|
||||
|
||||
(newClientRoles[clientName] ??= []).push(clientRole.name);
|
||||
}
|
||||
|
||||
const { same: sameSet } = sameFactory({
|
||||
takeIntoAccountArraysOrdering: false
|
||||
});
|
||||
|
||||
for (const [clientName, roles] of Object.entries(newClientRoles)) {
|
||||
keep_previous_ordering_if_possible: {
|
||||
const roles_previous = newUser.clientRoles?.[clientName];
|
||||
|
||||
if (roles_previous === undefined) {
|
||||
break keep_previous_ordering_if_possible;
|
||||
}
|
||||
|
||||
if (!sameSet(roles_previous, roles)) {
|
||||
break keep_previous_ordering_if_possible;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
(newUser.clientRoles ??= {})[clientName] = roles;
|
||||
}
|
||||
}
|
||||
|
||||
if (defaultUser_preexisting === undefined) {
|
||||
parsedRealmJson.users.push(newUser);
|
||||
} else {
|
||||
const i = parsedRealmJson.users.indexOf(defaultUser_preexisting);
|
||||
assert(i !== -1);
|
||||
parsedRealmJson.users[i] = newUser;
|
||||
}
|
||||
|
||||
return { username: newUser.username };
|
||||
}
|
||||
|
||||
function addOrEditClient(params: {
|
||||
parsedRealmJson: ParsedRealmJson;
|
||||
keycloakMajorVersionNumber: number;
|
||||
}): { clientId: string } {
|
||||
const { parsedRealmJson, keycloakMajorVersionNumber } = params;
|
||||
|
||||
const parsedRealmJson_default = getDefaultConfig({ keycloakMajorVersionNumber });
|
||||
|
||||
const testClient_default = (() => {
|
||||
const clients = parsedRealmJson_default.clients.filter(client => {
|
||||
return JSON.stringify(client).includes(TEST_APP_URL);
|
||||
});
|
||||
|
||||
assert(clients.length === 1);
|
||||
|
||||
return clients[0];
|
||||
})();
|
||||
|
||||
const clientIds_builtIn = parsedRealmJson_default.clients
|
||||
.map(client => client.clientId)
|
||||
.filter(clientId => clientId !== testClient_default.clientId);
|
||||
|
||||
const testClient_preexisting = (() => {
|
||||
const clients = parsedRealmJson.clients
|
||||
.filter(client => !clientIds_builtIn.includes(client.clientId))
|
||||
.filter(client => client.protocol === "openid-connect");
|
||||
|
||||
{
|
||||
const client = clients.find(
|
||||
client => client.clientId === testClient_default.clientId
|
||||
);
|
||||
|
||||
if (client !== undefined) {
|
||||
return client;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
const client = clients.find(
|
||||
client =>
|
||||
client.redirectUris?.find(redirectUri =>
|
||||
redirectUri.startsWith(TEST_APP_URL)
|
||||
) !== undefined
|
||||
);
|
||||
|
||||
if (client !== undefined) {
|
||||
return client;
|
||||
}
|
||||
}
|
||||
|
||||
const [client] = clients;
|
||||
|
||||
if (client === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return client;
|
||||
})();
|
||||
|
||||
let testClient: typeof testClient_default;
|
||||
|
||||
if (testClient_preexisting !== undefined) {
|
||||
testClient = testClient_preexisting;
|
||||
} else {
|
||||
testClient = structuredClone(testClient_default);
|
||||
delete testClient.protocolMappers;
|
||||
parsedRealmJson.clients.push(testClient);
|
||||
}
|
||||
|
||||
testClient.redirectUris = [
|
||||
`${TEST_APP_URL}/*`,
|
||||
"http://localhost*",
|
||||
"http://127.0.0.1*"
|
||||
]
|
||||
.sort()
|
||||
.reverse();
|
||||
|
||||
(testClient.attributes ??= {})["post.logout.redirect.uris"] = "+";
|
||||
|
||||
testClient.webOrigins = ["*"];
|
||||
|
||||
return { clientId: testClient.clientId };
|
||||
}
|
||||
|
||||
function editAccountConsoleAndSecurityAdminConsole(params: {
|
||||
parsedRealmJson: ParsedRealmJson;
|
||||
}) {
|
||||
const { parsedRealmJson } = params;
|
||||
|
||||
for (const clientId of ["account-console", "security-admin-console"] as const) {
|
||||
const client = parsedRealmJson.clients.find(
|
||||
client => client.clientId === clientId
|
||||
);
|
||||
|
||||
assert(client !== undefined);
|
||||
|
||||
{
|
||||
const arr = (client.redirectUris ??= []);
|
||||
|
||||
for (const value of ["http://localhost*", "http://127.0.0.1*"]) {
|
||||
if (!arr.includes(value)) {
|
||||
arr.push(value);
|
||||
}
|
||||
}
|
||||
|
||||
client.redirectUris?.sort().reverse();
|
||||
}
|
||||
|
||||
(client.attributes ??= {})["post.logout.redirect.uris"] = "+";
|
||||
|
||||
client.webOrigins = ["*"];
|
||||
|
||||
admin_specific: {
|
||||
if (clientId !== "security-admin-console") {
|
||||
break admin_specific;
|
||||
}
|
||||
|
||||
const protocolMapper_preexisting = client.protocolMappers?.find(
|
||||
protocolMapper => {
|
||||
if (protocolMapper.protocolMapper !== "oidc-hardcoded-claim-mapper") {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (protocolMapper.protocol !== "openid-connect") {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (protocolMapper.config === undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (protocolMapper.config["claim.name"] !== "allowed-origins") {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
let protocolMapper: NonNullable<typeof protocolMapper_preexisting>;
|
||||
|
||||
const config = {
|
||||
"introspection.token.claim": "true",
|
||||
"claim.value": '["*"]',
|
||||
"userinfo.token.claim": "true",
|
||||
"id.token.claim": "false",
|
||||
"lightweight.claim": "true",
|
||||
"access.token.claim": "true",
|
||||
"claim.name": "allowed-origins",
|
||||
"jsonType.label": "JSON",
|
||||
"access.tokenResponse.claim": "false"
|
||||
};
|
||||
|
||||
if (protocolMapper_preexisting !== undefined) {
|
||||
protocolMapper = protocolMapper_preexisting;
|
||||
} else {
|
||||
protocolMapper = {
|
||||
id: "8fd0d584-7052-4d04-a615-d18a71050873",
|
||||
name: "allowed-origins",
|
||||
protocol: "openid-connect",
|
||||
protocolMapper: "oidc-hardcoded-claim-mapper",
|
||||
consentRequired: false,
|
||||
config
|
||||
};
|
||||
|
||||
(client.protocolMappers ??= []).push(protocolMapper);
|
||||
}
|
||||
|
||||
assert(protocolMapper.config !== undefined);
|
||||
|
||||
if (config !== protocolMapper.config) {
|
||||
Object.assign(protocolMapper.config, config);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
155
src/bin/start-keycloak/realmConfig/realmConfig.ts
Normal file
155
src/bin/start-keycloak/realmConfig/realmConfig.ts
Normal file
@ -0,0 +1,155 @@
|
||||
import type { BuildContext } from "../../shared/buildContext";
|
||||
import { assert } from "tsafe/assert";
|
||||
import { getDefaultConfig } from "./defaultConfig";
|
||||
import {
|
||||
prepareRealmConfig,
|
||||
type BuildContextLike as BuildContextLike_prepareRealmConfig
|
||||
} from "./prepareRealmConfig";
|
||||
import * as fs from "fs";
|
||||
import {
|
||||
join as pathJoin,
|
||||
dirname as pathDirname,
|
||||
relative as pathRelative,
|
||||
sep as pathSep
|
||||
} from "path";
|
||||
import { existsAsync } from "../../tools/fs.existsAsync";
|
||||
import {
|
||||
readRealmJsonFile,
|
||||
writeRealmJsonFile,
|
||||
type ParsedRealmJson
|
||||
} from "./ParsedRealmJson";
|
||||
import {
|
||||
dumpContainerConfig,
|
||||
type BuildContextLike as BuildContextLike_dumpContainerConfig
|
||||
} from "./dumpContainerConfig";
|
||||
import * as runExclusive from "run-exclusive";
|
||||
import { waitForDebounceFactory } from "powerhooks/tools/waitForDebounce";
|
||||
import chalk from "chalk";
|
||||
|
||||
export type BuildContextLike = BuildContextLike_dumpContainerConfig &
|
||||
BuildContextLike_prepareRealmConfig & {
|
||||
projectDirPath: string;
|
||||
};
|
||||
|
||||
assert<BuildContext extends BuildContextLike ? true : false>;
|
||||
|
||||
export async function getRealmConfig(params: {
|
||||
keycloakMajorVersionNumber: number;
|
||||
realmJsonFilePath_userProvided: string | undefined;
|
||||
buildContext: BuildContextLike;
|
||||
}): Promise<{
|
||||
realmJsonFilePath: string;
|
||||
clientName: string;
|
||||
realmName: string;
|
||||
username: string;
|
||||
onRealmConfigChange: () => Promise<void>;
|
||||
}> {
|
||||
const { keycloakMajorVersionNumber, realmJsonFilePath_userProvided, buildContext } =
|
||||
params;
|
||||
|
||||
const realmJsonFilePath = pathJoin(
|
||||
buildContext.projectDirPath,
|
||||
".keycloakify",
|
||||
`realm-kc-${keycloakMajorVersionNumber}.json`
|
||||
);
|
||||
|
||||
const parsedRealmJson = await (async () => {
|
||||
if (realmJsonFilePath_userProvided !== undefined) {
|
||||
return readRealmJsonFile({
|
||||
realmJsonFilePath: realmJsonFilePath_userProvided
|
||||
});
|
||||
}
|
||||
|
||||
if (await existsAsync(realmJsonFilePath)) {
|
||||
return readRealmJsonFile({
|
||||
realmJsonFilePath
|
||||
});
|
||||
}
|
||||
|
||||
return getDefaultConfig({ keycloakMajorVersionNumber });
|
||||
})();
|
||||
|
||||
const { clientName, realmName, username } = prepareRealmConfig({
|
||||
parsedRealmJson,
|
||||
buildContext,
|
||||
keycloakMajorVersionNumber
|
||||
});
|
||||
|
||||
{
|
||||
const dirPath = pathDirname(realmJsonFilePath);
|
||||
|
||||
if (!(await existsAsync(dirPath))) {
|
||||
fs.mkdirSync(dirPath, { recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
await writeRealmJsonFile({
|
||||
realmJsonFilePath,
|
||||
parsedRealmJson,
|
||||
keycloakMajorVersionNumber
|
||||
});
|
||||
|
||||
const { onRealmConfigChange } = (() => {
|
||||
const run = runExclusive.build(async () => {
|
||||
const start = Date.now();
|
||||
|
||||
console.log(
|
||||
chalk.grey(`Changes detected to the '${realmName}' config, backing up...`)
|
||||
);
|
||||
|
||||
let parsedRealmJson: ParsedRealmJson;
|
||||
|
||||
try {
|
||||
parsedRealmJson = await dumpContainerConfig({
|
||||
buildContext,
|
||||
realmName,
|
||||
keycloakMajorVersionNumber
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(chalk.red(`Failed to backup '${realmName}' config:`));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await writeRealmJsonFile({
|
||||
realmJsonFilePath,
|
||||
parsedRealmJson,
|
||||
keycloakMajorVersionNumber
|
||||
});
|
||||
|
||||
console.log(
|
||||
[
|
||||
chalk.grey(
|
||||
`Save changed to \`.${pathSep}${pathRelative(buildContext.projectDirPath, realmJsonFilePath)}\``
|
||||
),
|
||||
chalk.grey(
|
||||
`Next time you'll be running \`keycloakify start-keycloak\`, the realm '${realmName}' will be restored to this state.`
|
||||
),
|
||||
chalk.green(
|
||||
`✓ '${realmName}' config backed up completed in ${Date.now() - start}ms`
|
||||
)
|
||||
].join("\n")
|
||||
);
|
||||
});
|
||||
|
||||
const { waitForDebounce } = waitForDebounceFactory({
|
||||
delay: 1_000
|
||||
});
|
||||
|
||||
async function onRealmConfigChange() {
|
||||
await waitForDebounce();
|
||||
|
||||
run();
|
||||
}
|
||||
|
||||
return { onRealmConfigChange };
|
||||
})();
|
||||
|
||||
return {
|
||||
realmJsonFilePath,
|
||||
clientName,
|
||||
realmName,
|
||||
username,
|
||||
onRealmConfigChange
|
||||
};
|
||||
}
|
@ -1,7 +1,11 @@
|
||||
import type { BuildContext } from "../shared/buildContext";
|
||||
import { exclude } from "tsafe/exclude";
|
||||
import { promptKeycloakVersion } from "../shared/promptKeycloakVersion";
|
||||
import { CONTAINER_NAME, KEYCLOAKIFY_SPA_DEV_SERVER_PORT } from "../shared/constants";
|
||||
import {
|
||||
CONTAINER_NAME,
|
||||
KEYCLOAKIFY_SPA_DEV_SERVER_PORT,
|
||||
KEYCLOAKIFY_LOGIN_JAR_BASENAME,
|
||||
TEST_APP_URL
|
||||
} from "../shared/constants";
|
||||
import { SemVer } from "../tools/SemVer";
|
||||
import { assert, type Equals } from "tsafe/assert";
|
||||
import * as fs from "fs";
|
||||
@ -9,8 +13,7 @@ import {
|
||||
join as pathJoin,
|
||||
relative as pathRelative,
|
||||
sep as pathSep,
|
||||
basename as pathBasename,
|
||||
dirname as pathDirname
|
||||
basename as pathBasename
|
||||
} from "path";
|
||||
import * as child_process from "child_process";
|
||||
import chalk from "chalk";
|
||||
@ -28,6 +31,9 @@ import { existsAsync } from "../tools/fs.existsAsync";
|
||||
import { rm } from "../tools/fs.rm";
|
||||
import { downloadAndExtractArchive } from "../tools/downloadAndExtractArchive";
|
||||
import { startViteDevServer } from "./startViteDevServer";
|
||||
import { getSupportedKeycloakMajorVersions } from "./realmConfig/defaultConfig";
|
||||
import { getSupportedDockerImageTags } from "./getSupportedDockerImageTags";
|
||||
import { getRealmConfig } from "./realmConfig";
|
||||
|
||||
export async function command(params: {
|
||||
buildContext: BuildContext;
|
||||
@ -91,9 +97,32 @@ export async function command(params: {
|
||||
|
||||
const { cliCommandOptions, buildContext } = params;
|
||||
|
||||
const { allSupportedTags, latestMajorTags } = await getSupportedDockerImageTags({
|
||||
buildContext
|
||||
});
|
||||
|
||||
const { dockerImageTag } = await (async () => {
|
||||
if (cliCommandOptions.keycloakVersion !== undefined) {
|
||||
return { dockerImageTag: cliCommandOptions.keycloakVersion };
|
||||
const cliCommandOptions_keycloakVersion = cliCommandOptions.keycloakVersion;
|
||||
|
||||
const tag = allSupportedTags.find(tag =>
|
||||
tag.startsWith(cliCommandOptions_keycloakVersion)
|
||||
);
|
||||
|
||||
if (tag === undefined) {
|
||||
console.log(
|
||||
chalk.red(
|
||||
[
|
||||
`We could not find a Keycloak Docker image for ${cliCommandOptions_keycloakVersion}`,
|
||||
`Example of valid values: --keycloak-version 26, --keycloak-version 26.0.7`
|
||||
].join("\n")
|
||||
)
|
||||
);
|
||||
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
return { dockerImageTag: tag };
|
||||
}
|
||||
|
||||
if (buildContext.startKeycloakOptions.dockerImage !== undefined) {
|
||||
@ -108,50 +137,165 @@ export async function command(params: {
|
||||
"On which version of Keycloak do you want to test your theme?"
|
||||
),
|
||||
chalk.gray(
|
||||
"You can also explicitly provide the version with `npx keycloakify start-keycloak --keycloak-version 25.0.2` (or any other version)"
|
||||
"You can also explicitly provide the version with `npx keycloakify start-keycloak --keycloak-version 26` (or any other version)"
|
||||
)
|
||||
].join("\n")
|
||||
);
|
||||
|
||||
const { keycloakVersion } = await promptKeycloakVersion({
|
||||
startingFromMajor: 18,
|
||||
excludeMajorVersions: [22],
|
||||
doOmitPatch: true,
|
||||
buildContext
|
||||
});
|
||||
const tag_userSelected = await (async () => {
|
||||
let tag: string;
|
||||
|
||||
console.log(`→ ${keycloakVersion}`);
|
||||
let latestMajorTags_copy = [...latestMajorTags];
|
||||
|
||||
return { dockerImageTag: keycloakVersion };
|
||||
while (true) {
|
||||
const { value } = await cliSelect<string>({
|
||||
values: latestMajorTags_copy
|
||||
}).catch(() => {
|
||||
process.exit(-1);
|
||||
});
|
||||
|
||||
tag = value;
|
||||
|
||||
{
|
||||
const doImplementAccountMpa =
|
||||
buildContext.implementedThemeTypes.account.isImplemented &&
|
||||
buildContext.implementedThemeTypes.account.type === "Multi-Page";
|
||||
|
||||
if (doImplementAccountMpa && tag.startsWith("22.")) {
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
`You are implementing a Multi-Page Account theme. Keycloak 22 is not supported, select another version`
|
||||
)
|
||||
);
|
||||
latestMajorTags_copy = latestMajorTags_copy.filter(
|
||||
tag => !tag.startsWith("22.")
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
const readMajor = (tag: string) => {
|
||||
const major = parseInt(tag.split(".")[0]);
|
||||
assert(!isNaN(major));
|
||||
return major;
|
||||
};
|
||||
|
||||
{
|
||||
const major = readMajor(tag);
|
||||
|
||||
const doImplementAdminTheme =
|
||||
buildContext.implementedThemeTypes.admin.isImplemented;
|
||||
|
||||
const getIsSupported = (major: number) => major >= 23;
|
||||
|
||||
if (doImplementAdminTheme && !getIsSupported(major)) {
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
`You are implementing an Admin theme. Only Keycloak 23 and later are supported, select another version`
|
||||
)
|
||||
);
|
||||
latestMajorTags_copy = latestMajorTags_copy.filter(tag =>
|
||||
getIsSupported(readMajor(tag))
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
const doImplementAccountSpa =
|
||||
buildContext.implementedThemeTypes.account.isImplemented &&
|
||||
buildContext.implementedThemeTypes.account.type === "Single-Page";
|
||||
|
||||
const major = readMajor(tag);
|
||||
|
||||
const getIsSupported = (major: number) => major >= 19;
|
||||
|
||||
if (doImplementAccountSpa && !getIsSupported(major)) {
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
`You are implementing a Single-Page Account theme. Only Keycloak 19 and later are supported, select another version`
|
||||
)
|
||||
);
|
||||
latestMajorTags_copy = latestMajorTags_copy.filter(tag =>
|
||||
getIsSupported(readMajor(tag))
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return tag;
|
||||
})();
|
||||
|
||||
console.log(`→ ${tag_userSelected}`);
|
||||
|
||||
return { dockerImageTag: tag_userSelected };
|
||||
})();
|
||||
|
||||
const keycloakMajorVersionNumber = (() => {
|
||||
if (buildContext.startKeycloakOptions.dockerImage === undefined) {
|
||||
return SemVer.parse(dockerImageTag).major;
|
||||
}
|
||||
|
||||
const { tag } = buildContext.startKeycloakOptions.dockerImage;
|
||||
|
||||
const [wrap] = [18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28]
|
||||
const [wrap] = getSupportedKeycloakMajorVersions()
|
||||
.map(majorVersionNumber => ({
|
||||
majorVersionNumber,
|
||||
index: tag.indexOf(`${majorVersionNumber}`)
|
||||
index: dockerImageTag.indexOf(`${majorVersionNumber}`)
|
||||
}))
|
||||
.filter(({ index }) => index !== -1)
|
||||
.sort((a, b) => a.index - b.index);
|
||||
|
||||
if (wrap === undefined) {
|
||||
console.warn(
|
||||
chalk.yellow(
|
||||
`Could not determine the major Keycloak version number from the docker image tag ${tag}. Assuming 25`
|
||||
)
|
||||
);
|
||||
return 25;
|
||||
try {
|
||||
const version = SemVer.parse(dockerImageTag);
|
||||
|
||||
console.error(
|
||||
chalk.yellow(
|
||||
`Keycloak version ${version.major} is not supported, supported versions are ${getSupportedKeycloakMajorVersions().join(", ")}`
|
||||
)
|
||||
);
|
||||
|
||||
process.exit(1);
|
||||
} catch {
|
||||
// NOTE: Latest version
|
||||
const [n] = getSupportedKeycloakMajorVersions();
|
||||
|
||||
console.warn(
|
||||
chalk.yellow(
|
||||
`Could not determine the major Keycloak version number from the docker image tag ${dockerImageTag}. Assuming ${n}`
|
||||
)
|
||||
);
|
||||
return n;
|
||||
}
|
||||
}
|
||||
|
||||
return wrap.majorVersionNumber;
|
||||
})();
|
||||
|
||||
const { clientName, onRealmConfigChange, realmJsonFilePath, realmName, username } =
|
||||
await getRealmConfig({
|
||||
keycloakMajorVersionNumber,
|
||||
realmJsonFilePath_userProvided: await (async () => {
|
||||
if (cliCommandOptions.realmJsonFilePath !== undefined) {
|
||||
return getAbsoluteAndInOsFormatPath({
|
||||
pathIsh: cliCommandOptions.realmJsonFilePath,
|
||||
cwd: process.cwd()
|
||||
});
|
||||
}
|
||||
|
||||
if (buildContext.startKeycloakOptions.realmJsonFilePath !== undefined) {
|
||||
assert(
|
||||
await existsAsync(
|
||||
buildContext.startKeycloakOptions.realmJsonFilePath
|
||||
),
|
||||
`${pathRelative(process.cwd(), buildContext.startKeycloakOptions.realmJsonFilePath)} does not exist`
|
||||
);
|
||||
return buildContext.startKeycloakOptions.realmJsonFilePath;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
})(),
|
||||
buildContext
|
||||
});
|
||||
|
||||
{
|
||||
const { isAppBuildSuccess } = await appBuild({
|
||||
buildContext
|
||||
@ -189,154 +333,48 @@ export async function command(params: {
|
||||
|
||||
assert(jarFilePath !== undefined);
|
||||
|
||||
const extensionJarFilePaths = await Promise.all(
|
||||
buildContext.startKeycloakOptions.extensionJars.map(async extensionJar => {
|
||||
switch (extensionJar.type) {
|
||||
case "path": {
|
||||
assert(
|
||||
await existsAsync(extensionJar.path),
|
||||
`${extensionJar.path} does not exist`
|
||||
);
|
||||
return extensionJar.path;
|
||||
const extensionJarFilePaths = [
|
||||
...(keycloakMajorVersionNumber <= 20
|
||||
? (console.log(
|
||||
chalk.yellow(
|
||||
"WARNING: With older version of keycloak your changes to the realm configuration are not persisted"
|
||||
)
|
||||
),
|
||||
[])
|
||||
: [
|
||||
pathJoin(
|
||||
getThisCodebaseRootDirPath(),
|
||||
"src",
|
||||
"bin",
|
||||
"start-keycloak",
|
||||
KEYCLOAKIFY_LOGIN_JAR_BASENAME
|
||||
)
|
||||
]),
|
||||
...(await Promise.all(
|
||||
buildContext.startKeycloakOptions.extensionJars.map(async extensionJar => {
|
||||
switch (extensionJar.type) {
|
||||
case "path": {
|
||||
assert(
|
||||
await existsAsync(extensionJar.path),
|
||||
`${extensionJar.path} does not exist`
|
||||
);
|
||||
return extensionJar.path;
|
||||
}
|
||||
case "url": {
|
||||
const { archiveFilePath } = await downloadAndExtractArchive({
|
||||
cacheDirPath: buildContext.cacheDirPath,
|
||||
fetchOptions: buildContext.fetchOptions,
|
||||
url: extensionJar.url,
|
||||
uniqueIdOfOnArchiveFile: "no extraction",
|
||||
onArchiveFile: async () => {}
|
||||
});
|
||||
return archiveFilePath;
|
||||
}
|
||||
}
|
||||
case "url": {
|
||||
const { archiveFilePath } = await downloadAndExtractArchive({
|
||||
cacheDirPath: buildContext.cacheDirPath,
|
||||
fetchOptions: buildContext.fetchOptions,
|
||||
url: extensionJar.url,
|
||||
uniqueIdOfOnArchiveFile: "no extraction",
|
||||
onArchiveFile: async () => {}
|
||||
});
|
||||
return archiveFilePath;
|
||||
}
|
||||
}
|
||||
assert<Equals<typeof extensionJar, never>>(false);
|
||||
})
|
||||
);
|
||||
|
||||
const getRealmJsonFilePath_defaultForKeycloakMajor = (
|
||||
keycloakMajorVersionNumber: number
|
||||
) =>
|
||||
pathJoin(
|
||||
getThisCodebaseRootDirPath(),
|
||||
"src",
|
||||
"bin",
|
||||
"start-keycloak",
|
||||
`myrealm-realm-${keycloakMajorVersionNumber}.json`
|
||||
);
|
||||
|
||||
const realmJsonFilePath = await (async () => {
|
||||
if (cliCommandOptions.realmJsonFilePath !== undefined) {
|
||||
if (cliCommandOptions.realmJsonFilePath === "none") {
|
||||
return undefined;
|
||||
}
|
||||
return getAbsoluteAndInOsFormatPath({
|
||||
pathIsh: cliCommandOptions.realmJsonFilePath,
|
||||
cwd: process.cwd()
|
||||
});
|
||||
}
|
||||
|
||||
if (buildContext.startKeycloakOptions.realmJsonFilePath !== undefined) {
|
||||
assert(
|
||||
await existsAsync(buildContext.startKeycloakOptions.realmJsonFilePath),
|
||||
`${pathRelative(process.cwd(), buildContext.startKeycloakOptions.realmJsonFilePath)} does not exist`
|
||||
);
|
||||
return buildContext.startKeycloakOptions.realmJsonFilePath;
|
||||
}
|
||||
|
||||
const internalFilePath = await (async () => {
|
||||
const defaultFilePath = getRealmJsonFilePath_defaultForKeycloakMajor(
|
||||
keycloakMajorVersionNumber
|
||||
);
|
||||
|
||||
if (fs.existsSync(defaultFilePath)) {
|
||||
return defaultFilePath;
|
||||
}
|
||||
|
||||
console.log(
|
||||
`${chalk.yellow(
|
||||
`Keycloakify do not have a realm configuration for Keycloak ${keycloakMajorVersionNumber} yet.`
|
||||
)}`
|
||||
);
|
||||
|
||||
console.log(chalk.cyan("Select what configuration to use:"));
|
||||
|
||||
const dirPath = pathDirname(defaultFilePath);
|
||||
|
||||
const { value } = await cliSelect<string>({
|
||||
values: [
|
||||
...fs
|
||||
.readdirSync(dirPath)
|
||||
.filter(fileBasename => fileBasename.endsWith(".json")),
|
||||
"none"
|
||||
]
|
||||
}).catch(() => {
|
||||
process.exit(-1);
|
||||
});
|
||||
|
||||
if (value === "none") {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return pathJoin(dirPath, value);
|
||||
})();
|
||||
|
||||
if (internalFilePath === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const filePath = pathJoin(
|
||||
buildContext.cacheDirPath,
|
||||
pathBasename(internalFilePath)
|
||||
);
|
||||
|
||||
fs.writeFileSync(
|
||||
filePath,
|
||||
Buffer.from(
|
||||
fs
|
||||
.readFileSync(internalFilePath)
|
||||
.toString("utf8")
|
||||
.replace(/keycloakify\-starter/g, buildContext.themeNames[0])
|
||||
),
|
||||
"utf8"
|
||||
);
|
||||
|
||||
return filePath;
|
||||
})();
|
||||
|
||||
add_test_user_if_missing: {
|
||||
if (realmJsonFilePath === undefined) {
|
||||
break add_test_user_if_missing;
|
||||
}
|
||||
|
||||
const realm: Record<string, unknown> = JSON.parse(
|
||||
fs.readFileSync(realmJsonFilePath).toString("utf8")
|
||||
);
|
||||
|
||||
if (realm.users !== undefined) {
|
||||
break add_test_user_if_missing;
|
||||
}
|
||||
|
||||
const realmJsonFilePath_internal = (() => {
|
||||
const filePath = getRealmJsonFilePath_defaultForKeycloakMajor(
|
||||
keycloakMajorVersionNumber
|
||||
);
|
||||
|
||||
if (!fs.existsSync(filePath)) {
|
||||
return getRealmJsonFilePath_defaultForKeycloakMajor(25);
|
||||
}
|
||||
|
||||
return filePath;
|
||||
})();
|
||||
|
||||
const users = JSON.parse(
|
||||
fs.readFileSync(realmJsonFilePath_internal).toString("utf8")
|
||||
).users;
|
||||
|
||||
realm.users = users;
|
||||
|
||||
fs.writeFileSync(realmJsonFilePath, JSON.stringify(realm, null, 2), "utf8");
|
||||
}
|
||||
assert<Equals<typeof extensionJar, never>>(false);
|
||||
})
|
||||
))
|
||||
];
|
||||
|
||||
async function extractThemeResourcesFromJar() {
|
||||
await extractArchive({
|
||||
@ -376,18 +414,16 @@ export async function command(params: {
|
||||
});
|
||||
} catch {}
|
||||
|
||||
const DEFAULT_PORT = 8080;
|
||||
const port =
|
||||
cliCommandOptions.port ?? buildContext.startKeycloakOptions.port ?? DEFAULT_PORT;
|
||||
const port = cliCommandOptions.port ?? buildContext.startKeycloakOptions.port ?? 8080;
|
||||
|
||||
const devServerPort = (() => {
|
||||
const doStartDevServer = (() => {
|
||||
const hasSpaUi =
|
||||
buildContext.implementedThemeTypes.admin.isImplemented ||
|
||||
(buildContext.implementedThemeTypes.account.isImplemented &&
|
||||
buildContext.implementedThemeTypes.account.type === "Single-Page");
|
||||
|
||||
if (!hasSpaUi) {
|
||||
return undefined;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (buildContext.bundler !== "vite") {
|
||||
@ -401,7 +437,7 @@ export async function command(params: {
|
||||
)
|
||||
);
|
||||
|
||||
return undefined;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (keycloakMajorVersionNumber < 25) {
|
||||
@ -415,17 +451,18 @@ export async function command(params: {
|
||||
)
|
||||
);
|
||||
|
||||
return undefined;
|
||||
return false;
|
||||
}
|
||||
|
||||
return port + 1;
|
||||
return true;
|
||||
})();
|
||||
|
||||
if (devServerPort !== undefined) {
|
||||
startViteDevServer({
|
||||
buildContext,
|
||||
port: devServerPort
|
||||
});
|
||||
let devServerPort: number | undefined = undefined;
|
||||
|
||||
if (doStartDevServer) {
|
||||
const { port } = await startViteDevServer({ buildContext });
|
||||
|
||||
devServerPort = port;
|
||||
}
|
||||
|
||||
const SPACE_PLACEHOLDER = "SPACE_PLACEHOLDER_xKLmdPd";
|
||||
@ -433,8 +470,15 @@ export async function command(params: {
|
||||
const dockerRunArgs: string[] = [
|
||||
`-p${SPACE_PLACEHOLDER}${port}:8080`,
|
||||
`--name${SPACE_PLACEHOLDER}${CONTAINER_NAME}`,
|
||||
`-e${SPACE_PLACEHOLDER}KEYCLOAK_ADMIN=admin`,
|
||||
`-e${SPACE_PLACEHOLDER}KEYCLOAK_ADMIN_PASSWORD=admin`,
|
||||
...(keycloakMajorVersionNumber >= 26
|
||||
? [
|
||||
`-e${SPACE_PLACEHOLDER}KC_BOOTSTRAP_ADMIN_USERNAME=admin`,
|
||||
`-e${SPACE_PLACEHOLDER}KC_BOOTSTRAP_ADMIN_PASSWORD=admin`
|
||||
]
|
||||
: [
|
||||
`-e${SPACE_PLACEHOLDER}KEYCLOAK_ADMIN=admin`,
|
||||
`-e${SPACE_PLACEHOLDER}KEYCLOAK_ADMIN_PASSWORD=admin`
|
||||
]),
|
||||
...(devServerPort === undefined
|
||||
? []
|
||||
: [
|
||||
@ -450,7 +494,7 @@ export async function command(params: {
|
||||
...(realmJsonFilePath === undefined
|
||||
? []
|
||||
: [
|
||||
`-v${SPACE_PLACEHOLDER}"${realmJsonFilePath}":/opt/keycloak/data/import/myrealm-realm.json`
|
||||
`-v${SPACE_PLACEHOLDER}"${realmJsonFilePath}":/opt/keycloak/data/import/${realmName}-realm.json`
|
||||
]),
|
||||
`-v${SPACE_PLACEHOLDER}"${jarFilePath_cacheDir}":/opt/keycloak/providers/keycloak-theme.jar`,
|
||||
...extensionJarFilePaths.map(
|
||||
@ -525,7 +569,14 @@ export async function command(params: {
|
||||
{ shell: true }
|
||||
);
|
||||
|
||||
child.stdout.on("data", data => process.stdout.write(data));
|
||||
child.stdout.on("data", async data => {
|
||||
if (data.toString("utf8").includes("keycloakify-logging: REALM_CONFIG_CHANGED")) {
|
||||
await onRealmConfigChange();
|
||||
return;
|
||||
}
|
||||
|
||||
process.stdout.write(data);
|
||||
});
|
||||
|
||||
child.stderr.on("data", data => process.stderr.write(data));
|
||||
|
||||
@ -572,9 +623,9 @@ export async function command(params: {
|
||||
`${chalk.green("Your theme is accessible at:")}`,
|
||||
`${chalk.green("➜")} ${chalk.cyan.bold(
|
||||
(() => {
|
||||
const url = new URL("https://my-theme.keycloakify.dev");
|
||||
const url = new URL(TEST_APP_URL);
|
||||
|
||||
if (port !== DEFAULT_PORT) {
|
||||
if (port !== 8080) {
|
||||
url.searchParams.set("port", `${port}`);
|
||||
}
|
||||
if (kcHttpRelativePath !== undefined) {
|
||||
@ -583,13 +634,20 @@ export async function command(params: {
|
||||
kcHttpRelativePath
|
||||
);
|
||||
}
|
||||
if (realmName !== "myrealm") {
|
||||
url.searchParams.set("realm", realmName);
|
||||
}
|
||||
|
||||
if (clientName !== "myclient") {
|
||||
url.searchParams.set("client", clientName);
|
||||
}
|
||||
|
||||
return url.href;
|
||||
})()
|
||||
)}`,
|
||||
"",
|
||||
"You can login with the following credentials:",
|
||||
`- username: ${chalk.cyan.bold("testuser")}`,
|
||||
`- username: ${chalk.cyan.bold(username)}`,
|
||||
`- password: ${chalk.cyan.bold("password123")}`,
|
||||
"",
|
||||
`Watching for changes in ${chalk.bold(
|
||||
@ -646,42 +704,56 @@ export async function command(params: {
|
||||
}
|
||||
)
|
||||
.on("all", async (...[, filePath]) => {
|
||||
ignore_account_spa: {
|
||||
const doImplementAccountSpa =
|
||||
buildContext.implementedThemeTypes.account.isImplemented &&
|
||||
buildContext.implementedThemeTypes.account.type === "Single-Page";
|
||||
|
||||
if (!doImplementAccountSpa) {
|
||||
break ignore_account_spa;
|
||||
ignore_path_covered_by_hmr: {
|
||||
if (filePath.endsWith(".properties")) {
|
||||
break ignore_path_covered_by_hmr;
|
||||
}
|
||||
|
||||
if (
|
||||
!isInside({
|
||||
dirPath: pathJoin(buildContext.themeSrcDirPath, "account"),
|
||||
filePath
|
||||
})
|
||||
) {
|
||||
break ignore_account_spa;
|
||||
if (!doStartDevServer) {
|
||||
break ignore_path_covered_by_hmr;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
ignore_account_spa: {
|
||||
const doImplementAccountSpa =
|
||||
buildContext.implementedThemeTypes.account.isImplemented &&
|
||||
buildContext.implementedThemeTypes.account.type ===
|
||||
"Single-Page";
|
||||
|
||||
ignore_admin: {
|
||||
if (!buildContext.implementedThemeTypes.admin.isImplemented) {
|
||||
break ignore_admin;
|
||||
if (!doImplementAccountSpa) {
|
||||
break ignore_account_spa;
|
||||
}
|
||||
|
||||
if (
|
||||
!isInside({
|
||||
dirPath: pathJoin(
|
||||
buildContext.themeSrcDirPath,
|
||||
"account"
|
||||
),
|
||||
filePath
|
||||
})
|
||||
) {
|
||||
break ignore_account_spa;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
!isInside({
|
||||
dirPath: pathJoin(buildContext.themeSrcDirPath, "admin"),
|
||||
filePath
|
||||
})
|
||||
) {
|
||||
break ignore_admin;
|
||||
}
|
||||
ignore_admin: {
|
||||
if (!buildContext.implementedThemeTypes.admin.isImplemented) {
|
||||
break ignore_admin;
|
||||
}
|
||||
|
||||
return;
|
||||
if (
|
||||
!isInside({
|
||||
dirPath: pathJoin(buildContext.themeSrcDirPath, "admin"),
|
||||
filePath
|
||||
})
|
||||
) {
|
||||
break ignore_admin;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`Detected changes in ${filePath}`);
|
||||
|
@ -3,6 +3,7 @@ import { assert } from "tsafe/assert";
|
||||
import type { BuildContext } from "../shared/buildContext";
|
||||
import chalk from "chalk";
|
||||
import { VITE_PLUGIN_SUB_SCRIPTS_ENV_NAMES } from "../shared/constants";
|
||||
import { Deferred } from "evt/tools/Deferred";
|
||||
|
||||
export type BuildContextLike = {
|
||||
projectDirPath: string;
|
||||
@ -12,13 +13,12 @@ assert<BuildContext extends BuildContextLike ? true : false>();
|
||||
|
||||
export function startViteDevServer(params: {
|
||||
buildContext: BuildContextLike;
|
||||
port: number;
|
||||
}): void {
|
||||
const { buildContext, port } = params;
|
||||
}): Promise<{ port: number }> {
|
||||
const { buildContext } = params;
|
||||
|
||||
console.log(chalk.blue(`$ npx vite dev --port ${port}`));
|
||||
console.log(chalk.blue(`$ npx vite dev`));
|
||||
|
||||
const child = child_process.spawn("npx", ["vite", "dev", "--port", `${port}`], {
|
||||
const child = child_process.spawn("npx", ["vite", "dev"], {
|
||||
cwd: buildContext.projectDirPath,
|
||||
env: {
|
||||
...process.env,
|
||||
@ -36,4 +36,32 @@ export function startViteDevServer(params: {
|
||||
});
|
||||
|
||||
child.stderr.on("data", data => process.stderr.write(data));
|
||||
|
||||
const dPort = new Deferred<number>();
|
||||
|
||||
{
|
||||
const onData = (data: Buffer) => {
|
||||
//Local: http://localhost:8083/
|
||||
const match = data
|
||||
.toString("utf8")
|
||||
.replace(/\x1b[[0-9;]*m/g, "")
|
||||
.match(/Local:\s*http:\/\/(?:localhost|127\.0\.0\.1):(\d+)\//);
|
||||
|
||||
if (match === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
child.stdout.off("data", onData);
|
||||
|
||||
const port = parseInt(match[1]);
|
||||
|
||||
assert(!isNaN(port));
|
||||
|
||||
dPort.resolve(port);
|
||||
};
|
||||
|
||||
child.stdout.on("data", onData);
|
||||
}
|
||||
|
||||
return dPort.pr.then(port => ({ port }));
|
||||
}
|
||||
|
99
src/bin/tools/Stringifyable.ts
Normal file
99
src/bin/tools/Stringifyable.ts
Normal file
@ -0,0 +1,99 @@
|
||||
import { z } from "zod";
|
||||
import { same } from "evt/tools/inDepth/same";
|
||||
import { assert, type Equals } from "tsafe/assert";
|
||||
import { id } from "tsafe/id";
|
||||
|
||||
export type Stringifyable =
|
||||
| StringifyableAtomic
|
||||
| StringifyableObject
|
||||
| StringifyableArray;
|
||||
|
||||
export type StringifyableAtomic = string | number | boolean | null;
|
||||
|
||||
// NOTE: Use Record<string, Stringifyable>
|
||||
interface StringifyableObject {
|
||||
[key: string]: Stringifyable;
|
||||
}
|
||||
|
||||
// NOTE: Use Stringifyable[]
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
||||
interface StringifyableArray extends Array<Stringifyable> {}
|
||||
|
||||
export const zStringifyableAtomic = (() => {
|
||||
type TargetType = StringifyableAtomic;
|
||||
|
||||
const zTargetType = z.union([z.string(), z.number(), z.boolean(), z.null()]);
|
||||
|
||||
assert<Equals<z.infer<typeof zTargetType>, TargetType>>();
|
||||
|
||||
return id<z.ZodType<TargetType>>(zTargetType);
|
||||
})();
|
||||
|
||||
export const zStringifyable: z.ZodType<Stringifyable> = z
|
||||
.any()
|
||||
.superRefine((val, ctx) => {
|
||||
const isStringifyable = same(JSON.parse(JSON.stringify(val)), val);
|
||||
if (!isStringifyable) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "Not stringifyable"
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export function getIsAtomic(
|
||||
stringifyable: Stringifyable
|
||||
): stringifyable is StringifyableAtomic {
|
||||
return (
|
||||
["string", "number", "boolean"].includes(typeof stringifyable) ||
|
||||
stringifyable === null
|
||||
);
|
||||
}
|
||||
|
||||
export const { getValueAtPath } = (() => {
|
||||
function getValueAtPath_rec(
|
||||
stringifyable: Stringifyable,
|
||||
path: (string | number)[]
|
||||
): Stringifyable | undefined {
|
||||
if (path.length === 0) {
|
||||
return stringifyable;
|
||||
}
|
||||
|
||||
if (getIsAtomic(stringifyable)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const [first, ...rest] = path;
|
||||
|
||||
let dereferenced: Stringifyable | undefined;
|
||||
|
||||
if (stringifyable instanceof Array) {
|
||||
if (typeof first !== "number") {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
dereferenced = stringifyable[first];
|
||||
} else {
|
||||
if (typeof first !== "string") {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
dereferenced = stringifyable[first];
|
||||
}
|
||||
|
||||
if (dereferenced === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return getValueAtPath_rec(dereferenced, rest);
|
||||
}
|
||||
|
||||
function getValueAtPath(
|
||||
stringifyableObjectOrArray: Record<string, Stringifyable> | Stringifyable[],
|
||||
path: (string | number)[]
|
||||
): Stringifyable | undefined {
|
||||
return getValueAtPath_rec(stringifyableObjectOrArray, path);
|
||||
}
|
||||
|
||||
return { getValueAtPath };
|
||||
})();
|
164
src/bin/tools/canonicalStringify.ts
Normal file
164
src/bin/tools/canonicalStringify.ts
Normal file
@ -0,0 +1,164 @@
|
||||
import { getIsAtomic, getValueAtPath, type Stringifyable } from "./Stringifyable";
|
||||
|
||||
export function canonicalStringify(params: {
|
||||
data: Record<string, Stringifyable> | Stringifyable[];
|
||||
referenceData: Record<string, Stringifyable> | Stringifyable[];
|
||||
}): string {
|
||||
const { data, referenceData } = params;
|
||||
|
||||
return JSON.stringify(
|
||||
makeDeterministicCopy({
|
||||
path: [],
|
||||
data,
|
||||
getCanonicalKeys: path => {
|
||||
const referenceValue = (() => {
|
||||
const path_patched: (string | number)[] = [];
|
||||
|
||||
for (let i = 0; i < path.length; i++) {
|
||||
let value_i = getValueAtPath(referenceData, [
|
||||
...path_patched,
|
||||
path[i]
|
||||
]);
|
||||
|
||||
if (value_i !== undefined) {
|
||||
path_patched.push(path[i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (typeof path[i] !== "number") {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
value_i = getValueAtPath(referenceData, [...path_patched, 0]);
|
||||
|
||||
if (value_i !== undefined) {
|
||||
path_patched.push(0);
|
||||
continue;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return getValueAtPath(referenceData, path_patched);
|
||||
})();
|
||||
|
||||
if (referenceValue === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (getIsAtomic(referenceValue)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (referenceValue instanceof Array) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return Object.keys(referenceValue);
|
||||
}
|
||||
}),
|
||||
null,
|
||||
2
|
||||
);
|
||||
}
|
||||
|
||||
function makeDeterministicCopy(params: {
|
||||
path: (string | number)[];
|
||||
data: Stringifyable;
|
||||
getCanonicalKeys: (path: (string | number)[]) => string[] | undefined;
|
||||
}): Stringifyable {
|
||||
const { path, data, getCanonicalKeys } = params;
|
||||
|
||||
if (getIsAtomic(data)) {
|
||||
return data;
|
||||
}
|
||||
|
||||
if (data instanceof Array) {
|
||||
return makeDeterministicCopy_array({
|
||||
path,
|
||||
data,
|
||||
getCanonicalKeys
|
||||
});
|
||||
}
|
||||
|
||||
return makeDeterministicCopy_record({
|
||||
path,
|
||||
data,
|
||||
getCanonicalKeys
|
||||
});
|
||||
}
|
||||
|
||||
function makeDeterministicCopy_record(params: {
|
||||
path: (string | number)[];
|
||||
data: Record<string, Stringifyable>;
|
||||
getCanonicalKeys: (path: (string | number)[]) => string[] | undefined;
|
||||
}): Record<string, Stringifyable> {
|
||||
const { path, data, getCanonicalKeys } = params;
|
||||
|
||||
const keysOfAtomicValues: string[] = [];
|
||||
const keysOfNonAtomicValues: string[] = [];
|
||||
|
||||
for (const [key, value] of Object.entries(data)) {
|
||||
if (getIsAtomic(value)) {
|
||||
keysOfAtomicValues.push(key);
|
||||
} else {
|
||||
keysOfNonAtomicValues.push(key);
|
||||
}
|
||||
}
|
||||
|
||||
keysOfAtomicValues.sort();
|
||||
keysOfNonAtomicValues.sort();
|
||||
|
||||
const keys = [...keysOfAtomicValues, ...keysOfNonAtomicValues];
|
||||
|
||||
reorder_according_to_canonical: {
|
||||
const canonicalKeys = getCanonicalKeys(path);
|
||||
|
||||
if (canonicalKeys === undefined) {
|
||||
break reorder_according_to_canonical;
|
||||
}
|
||||
|
||||
const keys_toPrepend: string[] = [];
|
||||
|
||||
for (const key of canonicalKeys) {
|
||||
const indexOfKey = keys.indexOf(key);
|
||||
|
||||
if (indexOfKey === -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
keys.splice(indexOfKey, 1);
|
||||
keys_toPrepend.push(key);
|
||||
}
|
||||
|
||||
keys.unshift(...keys_toPrepend);
|
||||
}
|
||||
|
||||
const result: Record<string, Stringifyable> = {};
|
||||
|
||||
for (const key of keys) {
|
||||
result[key] = makeDeterministicCopy({
|
||||
path: [...path, key],
|
||||
data: data[key],
|
||||
getCanonicalKeys
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function makeDeterministicCopy_array(params: {
|
||||
path: (string | number)[];
|
||||
data: Stringifyable[];
|
||||
getCanonicalKeys: (path: (string | number)[]) => string[] | undefined;
|
||||
}): Stringifyable[] {
|
||||
const { path, data, getCanonicalKeys } = params;
|
||||
|
||||
return [...data].map((entry, i) =>
|
||||
makeDeterministicCopy({
|
||||
path: [...path, i],
|
||||
data: entry,
|
||||
getCanonicalKeys
|
||||
})
|
||||
);
|
||||
}
|
90
src/bin/tools/createObjectThatThrowsIfAccessed.ts
Normal file
90
src/bin/tools/createObjectThatThrowsIfAccessed.ts
Normal file
@ -0,0 +1,90 @@
|
||||
const keyIsTrapped = "isTrapped_zSskDe9d";
|
||||
|
||||
export class AccessError extends Error {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
Object.setPrototypeOf(this, new.target.prototype);
|
||||
}
|
||||
}
|
||||
|
||||
export function createObjectThatThrowsIfAccessed<T extends object>(params?: {
|
||||
debugMessage?: string;
|
||||
isPropertyWhitelisted?: (prop: string | number | symbol) => boolean;
|
||||
}): T {
|
||||
const { debugMessage = "", isPropertyWhitelisted = () => false } = params ?? {};
|
||||
|
||||
const get: NonNullable<ProxyHandler<T>["get"]> = (...args) => {
|
||||
const [, prop] = args;
|
||||
|
||||
if (isPropertyWhitelisted(prop)) {
|
||||
return Reflect.get(...args);
|
||||
}
|
||||
|
||||
if (prop === keyIsTrapped) {
|
||||
return true;
|
||||
}
|
||||
|
||||
throw new AccessError(`Cannot access ${String(prop)} yet ${debugMessage}`);
|
||||
};
|
||||
|
||||
const trappedObject = new Proxy<T>({} as any, {
|
||||
get,
|
||||
set: get
|
||||
});
|
||||
|
||||
return trappedObject;
|
||||
}
|
||||
|
||||
export function createObjectThatThrowsIfAccessedFactory(params: {
|
||||
isPropertyWhitelisted?: (prop: string | number | symbol) => boolean;
|
||||
}) {
|
||||
const { isPropertyWhitelisted } = params;
|
||||
|
||||
return {
|
||||
createObjectThatThrowsIfAccessed: <T extends object>(params?: {
|
||||
debugMessage?: string;
|
||||
}) => {
|
||||
const { debugMessage } = params ?? {};
|
||||
|
||||
return createObjectThatThrowsIfAccessed<T>({
|
||||
debugMessage,
|
||||
isPropertyWhitelisted
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function isObjectThatThrowIfAccessed(obj: object) {
|
||||
return (obj as any)[keyIsTrapped] === true;
|
||||
}
|
||||
|
||||
export const THROW_IF_ACCESSED = {
|
||||
__brand: "THROW_IF_ACCESSED"
|
||||
};
|
||||
|
||||
export function createObjectWithSomePropertiesThatThrowIfAccessed<
|
||||
T extends Record<string, unknown>
|
||||
>(obj: { [K in keyof T]: T[K] | typeof THROW_IF_ACCESSED }, debugMessage?: string): T {
|
||||
return Object.defineProperties(
|
||||
obj,
|
||||
Object.fromEntries(
|
||||
Object.entries(obj)
|
||||
.filter(([, value]) => value === THROW_IF_ACCESSED)
|
||||
.map(([key]) => {
|
||||
const getAndSet = () => {
|
||||
throw new AccessError(
|
||||
`Cannot access ${key} yet ${debugMessage ?? ""}`
|
||||
);
|
||||
};
|
||||
|
||||
const pd = {
|
||||
get: getAndSet,
|
||||
set: getAndSet,
|
||||
enumerable: true
|
||||
};
|
||||
|
||||
return [key, pd];
|
||||
})
|
||||
)
|
||||
) as any;
|
||||
}
|
@ -101,7 +101,7 @@ export async function runPrettier(params: {
|
||||
resolveConfig: true
|
||||
});
|
||||
|
||||
if (ignored) {
|
||||
if (ignored || inferredParser === null) {
|
||||
return sourceCode;
|
||||
}
|
||||
|
||||
@ -110,7 +110,7 @@ export async function runPrettier(params: {
|
||||
formattedSourceCode = await prettier.format(sourceCode, {
|
||||
...config,
|
||||
filePath,
|
||||
parser: inferredParser ?? undefined
|
||||
parser: inferredParser
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(
|
||||
|
@ -1,3 +1,4 @@
|
||||
import type { JSX } from "keycloakify/tools/JSX";
|
||||
import { lazy, Suspense } from "react";
|
||||
import { assert, type Equals } from "tsafe/assert";
|
||||
import type { LazyOrNot } from "keycloakify/tools/LazyOrNot";
|
||||
|
@ -1,3 +1,4 @@
|
||||
import type { JSX } from "keycloakify/tools/JSX";
|
||||
import { useEffect, useReducer, Fragment } from "react";
|
||||
import { assert } from "keycloakify/tools/assert";
|
||||
import type { KcClsx } from "keycloakify/login/lib/kcClsx";
|
||||
|
@ -1,3 +1,4 @@
|
||||
import type { JSX } from "keycloakify/tools/JSX";
|
||||
import { type FormAction, type FormFieldError } from "keycloakify/login/lib/useUserProfileForm";
|
||||
import type { KcClsx } from "keycloakify/login/lib/kcClsx";
|
||||
import type { Attribute } from "keycloakify/login/KcContext";
|
||||
|
@ -1,3 +1,4 @@
|
||||
import type { JSX } from "keycloakify/tools/JSX";
|
||||
import type { GenericI18n_noJsx } from "../noJsx/GenericI18n_noJsx";
|
||||
import { assert, type Equals } from "tsafe/assert";
|
||||
|
||||
|
@ -46,7 +46,7 @@ export type I18nBuilder<
|
||||
}>
|
||||
) => I18nBuilder<
|
||||
ThemeName,
|
||||
MessageKey_themeDefined,
|
||||
string extends MessageKey_themeDefined ? never : MessageKey_themeDefined,
|
||||
LanguageTag_notInDefaultSet,
|
||||
ExcludedMethod | "withCustomTranslations"
|
||||
>;
|
||||
|
@ -1,3 +1,4 @@
|
||||
import type { JSX } from "keycloakify/tools/JSX";
|
||||
import { useEffect, useState } from "react";
|
||||
import { kcSanitize } from "keycloakify/lib/kcSanitize";
|
||||
import { createGetI18n, type KcContextLike } from "../noJsx/getI18n";
|
||||
|
@ -1,3 +1,4 @@
|
||||
import type { JSX } from "keycloakify/tools/JSX";
|
||||
import * as reactlessApi from "./getUserProfileApi/index";
|
||||
import type { PasswordPolicies, Attribute, Validators } from "keycloakify/login/KcContext";
|
||||
import { useEffect, useState, useMemo, Fragment } from "react";
|
||||
|
@ -1,3 +1,4 @@
|
||||
import type { JSX } from "keycloakify/tools/JSX";
|
||||
import { useState } from "react";
|
||||
import type { LazyOrNot } from "keycloakify/tools/LazyOrNot";
|
||||
import { getKcClsx } from "keycloakify/login/lib/kcClsx";
|
||||
|
@ -1,3 +1,4 @@
|
||||
import type { JSX } from "keycloakify/tools/JSX";
|
||||
import { useState, useEffect, useReducer } from "react";
|
||||
import { kcSanitize } from "keycloakify/lib/kcSanitize";
|
||||
import { assert } from "keycloakify/tools/assert";
|
||||
|
@ -1,3 +1,4 @@
|
||||
import type { JSX } from "keycloakify/tools/JSX";
|
||||
import { useState, useEffect, useReducer } from "react";
|
||||
import { kcSanitize } from "keycloakify/lib/kcSanitize";
|
||||
import { clsx } from "keycloakify/tools/clsx";
|
||||
|
@ -1,3 +1,4 @@
|
||||
import type { JSX } from "keycloakify/tools/JSX";
|
||||
import { useEffect, useReducer } from "react";
|
||||
import { kcSanitize } from "keycloakify/lib/kcSanitize";
|
||||
import { assert } from "keycloakify/tools/assert";
|
||||
|
@ -1,3 +1,4 @@
|
||||
import type { JSX } from "keycloakify/tools/JSX";
|
||||
import { useState } from "react";
|
||||
import type { LazyOrNot } from "keycloakify/tools/LazyOrNot";
|
||||
import { getKcClsx } from "keycloakify/login/lib/kcClsx";
|
||||
|
@ -1,3 +1,4 @@
|
||||
import type { JSX } from "keycloakify/tools/JSX";
|
||||
import { type TemplateProps, type ClassKey } from "keycloakify/login/TemplateProps";
|
||||
import type { LazyOrNot } from "keycloakify/tools/LazyOrNot";
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
import type { JSX } from "keycloakify/tools/JSX";
|
||||
import { useState } from "react";
|
||||
import type { LazyOrNot } from "keycloakify/tools/LazyOrNot";
|
||||
import { kcSanitize } from "keycloakify/lib/kcSanitize";
|
||||
|
@ -1,3 +1,4 @@
|
||||
import type { JSX } from "keycloakify/tools/JSX";
|
||||
import { useState } from "react";
|
||||
import type { LazyOrNot } from "keycloakify/tools/LazyOrNot";
|
||||
import { getKcClsx, type KcClsx } from "keycloakify/login/lib/kcClsx";
|
||||
|
5
src/tools/JSX.ts
Normal file
5
src/tools/JSX.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import type { ReactElement } from "react";
|
||||
|
||||
export namespace JSX {
|
||||
export interface Element extends ReactElement<any, any> {}
|
||||
}
|
@ -62,3 +62,65 @@ type I18n = typeof ofTypeI18n;
|
||||
|
||||
assert<Equals<typeof node, JSX.Element>>;
|
||||
}
|
||||
|
||||
{
|
||||
const i18n = Reflect<I18n>();
|
||||
|
||||
i18n.msg("passwordConfirm");
|
||||
}
|
||||
|
||||
{
|
||||
const i18n = Reflect<I18n>();
|
||||
|
||||
// @ts-expect-error
|
||||
i18n.msg("iDoNotExist");
|
||||
}
|
||||
|
||||
{
|
||||
const { ofTypeI18n } = i18nBuilder
|
||||
.withThemeName<"keycloakify-starter">()
|
||||
.withCustomTranslations({})
|
||||
.build();
|
||||
|
||||
type I18n = typeof ofTypeI18n;
|
||||
|
||||
{
|
||||
const i18n = Reflect<I18n>();
|
||||
|
||||
// @ts-expect-error
|
||||
const node = i18n.msg("iDoNotExist");
|
||||
|
||||
assert<Equals<typeof node, JSX.Element>>;
|
||||
}
|
||||
}
|
||||
|
||||
i18nBuilder.withThemeName<"my-theme-1" | "my-theme-2">().withCustomTranslations({
|
||||
en: {
|
||||
myCustomKey1: "my-custom-key-1-en",
|
||||
// @ts-expect-error
|
||||
myCustomKey2: {
|
||||
"my-theme-1": "my-theme-1-en"
|
||||
//"my-theme-2": "my-theme-2-en"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
i18nBuilder
|
||||
.withThemeName<"my-theme-1" | "my-theme-2">()
|
||||
.withExtraLanguages({
|
||||
he: {
|
||||
label: "עברית",
|
||||
getMessages: () => import("./he")
|
||||
}
|
||||
})
|
||||
.withCustomTranslations({
|
||||
en: {
|
||||
myCustomKey1: "my-custom-key-1-en",
|
||||
myCustomKey2: "my-custom-key-2-en"
|
||||
},
|
||||
// @ts-expect-error
|
||||
he: {
|
||||
myCustomKey1: "my-custom-key-1-he"
|
||||
//myCustomKey2: "my-custom-key-2-he"
|
||||
}
|
||||
});
|
||||
|
Reference in New Issue
Block a user