Compare commits

..

15 Commits

Author SHA1 Message Date
606cf7ad02 Bump version 2024-12-09 05:08:57 +01:00
5225749c7b React 19 compat #741 2024-12-09 05:06:47 +01:00
819e3833ad Bump version 2024-12-08 19:43:00 +01:00
b0ba37fcc4 Smarter appBuild script 2024-12-08 19:42:43 +01:00
f4829b557f Bump version 2024-12-06 00:38:23 +01:00
60a9b5a693 Improve i18n api typing 2024-12-06 00:38:09 +01:00
c323b94a8c Bump version 2024-12-04 00:09:14 +01:00
4bbc0241ec Do not crash when parser can't be inferred 2024-12-04 00:04:49 +01:00
5a7dacfcdd Bump version 2024-12-02 00:41:30 +01:00
7e05e1bf0c Use random port for dev server 2024-12-02 00:41:12 +01:00
1530ca32c8 Bump version 2024-12-01 00:07:28 +01:00
ed054f131a Merge pull request #736 from keycloakify/hmr_in_start_keycloak
Implement hot module replacement for developing Account SPA and Admin UI
2024-12-01 00:01:57 +01:00
ec74ceef4d Implement hot module replacement for developing Account SPA and Admin UI 2024-11-30 23:55:24 +01:00
fd3261cdf1 Bump version 2024-11-25 11:41:36 +01:00
b4b53d2552 Re export wide type def of the kcContext 2024-11-25 11:41:17 +01:00
32 changed files with 527 additions and 134 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -10,7 +10,8 @@ export type ThemeType = (typeof THEME_TYPES)[number];
export const VITE_PLUGIN_SUB_SCRIPTS_ENV_NAMES = { export const VITE_PLUGIN_SUB_SCRIPTS_ENV_NAMES = {
RUN_POST_BUILD_SCRIPT: "KEYCLOAKIFY_RUN_POST_BUILD_SCRIPT", RUN_POST_BUILD_SCRIPT: "KEYCLOAKIFY_RUN_POST_BUILD_SCRIPT",
RESOLVE_VITE_CONFIG: "KEYCLOAKIFY_RESOLVE_VITE_CONFIG" RESOLVE_VITE_CONFIG: "KEYCLOAKIFY_RESOLVE_VITE_CONFIG",
READ_KC_CONTEXT_FROM_URL: "KEYCLOAKIFY_READ_KC_CONTEXT_FROM_URL"
} as const; } as const;
export const BUILD_FOR_KEYCLOAK_MAJOR_VERSION_ENV_NAME = export const BUILD_FOR_KEYCLOAK_MAJOR_VERSION_ENV_NAME =
@ -78,3 +79,5 @@ export const CUSTOM_HANDLER_ENV_NAMES = {
}; };
export const KEYCLOAK_THEME = "keycloak-theme"; export const KEYCLOAK_THEME = "keycloak-theme";
export const KEYCLOAKIFY_SPA_DEV_SERVER_PORT = "KEYCLOAKIFY_SPA_DEV_SERVER_PORT";

View File

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

View File

@ -628,14 +628,16 @@
"id": "d8f14dc4-5f0f-4a1d-8c0b-cfe78ee55cb3", "id": "d8f14dc4-5f0f-4a1d-8c0b-cfe78ee55cb3",
"clientId": "account-console", "clientId": "account-console",
"name": "${client_account-console}", "name": "${client_account-console}",
"description": "",
"rootUrl": "${authBaseUrl}", "rootUrl": "${authBaseUrl}",
"adminUrl": "",
"baseUrl": "/realms/myrealm/account/", "baseUrl": "/realms/myrealm/account/",
"surrogateAuthRequired": false, "surrogateAuthRequired": false,
"enabled": true, "enabled": true,
"alwaysDisplayInConsole": false, "alwaysDisplayInConsole": false,
"clientAuthenticatorType": "client-secret", "clientAuthenticatorType": "client-secret",
"redirectUris": ["/realms/myrealm/account/*"], "redirectUris": ["*"],
"webOrigins": [], "webOrigins": ["*"],
"notBefore": 0, "notBefore": 0,
"bearerOnly": false, "bearerOnly": false,
"consentRequired": false, "consentRequired": false,
@ -647,8 +649,13 @@
"frontchannelLogout": false, "frontchannelLogout": false,
"protocol": "openid-connect", "protocol": "openid-connect",
"attributes": { "attributes": {
"oidc.ciba.grant.enabled": "false",
"backchannel.logout.session.required": "true",
"post.logout.redirect.uris": "+", "post.logout.redirect.uris": "+",
"pkce.code.challenge.method": "S256" "oauth2.device.authorization.grant.enabled": "false",
"display.on.consent.screen": "false",
"pkce.code.challenge.method": "S256",
"backchannel.logout.revoke.offline.tokens": "false"
}, },
"authenticationFlowBindingOverrides": {}, "authenticationFlowBindingOverrides": {},
"fullScopeAllowed": false, "fullScopeAllowed": false,
@ -1574,14 +1581,14 @@
"subComponents": {}, "subComponents": {},
"config": { "config": {
"allowed-protocol-mapper-types": [ "allowed-protocol-mapper-types": [
"oidc-sha256-pairwise-sub-mapper",
"oidc-full-name-mapper", "oidc-full-name-mapper",
"oidc-usermodel-attribute-mapper",
"oidc-address-mapper",
"saml-user-attribute-mapper",
"oidc-usermodel-property-mapper", "oidc-usermodel-property-mapper",
"saml-user-property-mapper",
"saml-role-list-mapper", "saml-role-list-mapper",
"oidc-sha256-pairwise-sub-mapper" "saml-user-attribute-mapper",
"saml-user-property-mapper",
"oidc-usermodel-attribute-mapper",
"oidc-address-mapper"
] ]
} }
}, },
@ -1612,13 +1619,13 @@
"config": { "config": {
"allowed-protocol-mapper-types": [ "allowed-protocol-mapper-types": [
"oidc-sha256-pairwise-sub-mapper", "oidc-sha256-pairwise-sub-mapper",
"oidc-usermodel-property-mapper",
"oidc-address-mapper",
"oidc-usermodel-attribute-mapper",
"oidc-full-name-mapper",
"saml-user-attribute-mapper",
"saml-user-property-mapper", "saml-user-property-mapper",
"saml-role-list-mapper" "oidc-usermodel-attribute-mapper",
"oidc-address-mapper",
"saml-role-list-mapper",
"oidc-usermodel-property-mapper",
"saml-user-attribute-mapper",
"oidc-full-name-mapper"
] ]
} }
}, },
@ -1671,13 +1678,6 @@
"providerId": "rsa-generated", "providerId": "rsa-generated",
"subComponents": {}, "subComponents": {},
"config": { "config": {
"privateKey": [
"MIIEowIBAAKCAQEAsYUWzVfZMd6ywpBmLJYeF1U9Mgd/z3xWvl1Yq76oRPPfpcqQitN+cktWqu0hPerCVSl2ltwXDMrUwFzswG9MiM9hb+BLEld7kYiYkcFNt3lCtmmeRQEae7JwWimzeNV96Qlz0tHY8f9Zh0ffPDsLTN1HGAeRJJhI7mNQm6qCJNMCfVA/O5SWumsIn2XLnSMiQ05AACVHOLUq6rAZ2zCCaYmXTmJkuSOb8e26V303P6l63DSe5HSNXDdI00tjfFFf37q870zhvfsotrjjx0RMijy9Kjj8OZF+pFHpDRaGEi8tpQxZDnCTofTieB/Vp3QP+aTlvAyD3Q1ZnJxGQCLygwIDAQABAoIBABUJ9XMJGNQzamiVwuOWN7ht4UP8ezYvgdEA8NaLUO0PIYVIKyD7l4OwkHPPM9PfRACM2qG0MZp8sCyg4WxIeepy+D979oRqJYUmNRLSipqWlASuItRXIPjiY99uYXdjh2R8Os5pvCD+MZxPX9KHGuaVXmzSJMO7YAAPeYkMHcLYTp/U0c65Ztaaz1zz1FeyvpjkLr9SHiMcIN51zFmhvT1tcRIqy4zidisjrTSUr/KPVxeJtrEfyhTGk3z41yJf5YbeaxaMjJR5x0WXzt1fWVmA/V1bWa2Zlj9d8AxDReA1p7Lpstz34PRoCMj9bmFguI2+RTw6K0D++Jydfxmh8vUCgYEA5Zwk2r3TFO3i3V70LOn6CLzn15yLeuSIJ9p2os70jQOmFMCreLdcUbCaiUe7UV/IIVftbcxhFm9zECXZXX0wubcmHZqyptlbuAn1de4QkLJixXo1A7ZQXBEZk22WN2naXHQF5oK6lh/VSLcZBajTsyvBm5JWXrd8djjG06MugA8CgYEAxexKI5IwcLhpMDV9UPQb/+lDWHVqCT2xwYxnZ85y+5gmrOyyT7mIChz3DFYiaw4CHJWmBkIDBaiDgLEgQk4QXWzYshXawShBHnv1h08bVMMw98Ivec7ZRkV+/ET30YRwC2Uyk4bm4HpwVV5GCFhC4aAvRcCA1CIJk3MwcOwksk0CgYEAqxyaOomMbOR7VQ4WWgJkW26sOHppV8RH06tzDhG9HfnCI2USZHwBSL+b6wKSDiqbMn4cat8M23NjBH2wZ4OMdFqRBS7sRHtnZtfFHYW0wqCuCwzvxTxw1qvHq57Xe6RfHtc4LnjuJELE59PLyfPvEG9jcVS1GREUp+XYBpBtbvECgYAMhWBDU9JAr0noRNoCrw6+Z9Fc3UCyCPcf2XQJOyRHCl8X/XliVchna2GtpB1VTHORv13bc32hdAGtuIbj6vBaGLK0wXEvWw6TkR/9SWHfQOHuKpi6Sf2w1mCsMOjElm5IKkTC1Hvyo4xLukUP7hV9FJcpAH6l7OlSLK1Z13aS2QKBgB6w4gvmVEQruHV5+K60OatuFojr+kxJwmzCb5uKOULUFezT2pA3p3l6IWxGL2XtM+LD0SiZE3KZJUzf+LatYlBU9ek4F1krkVNUTRZpzUa0oADbymCL1chM4oPIs7sISQlFIH2wOSZt6Blvcw0E0wfjd9Gv/LHxcMnlRb1t1sLk"
],
"keyUse": ["SIG"],
"certificate": [
"MIICnTCCAYUCBgGQBsyplzANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdteXJlYWxtMB4XDTI0MDYxMTEwMTQ1NFoXDTM0MDYxMTEwMTYzNFowEjEQMA4GA1UEAwwHbXlyZWFsbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALGFFs1X2THessKQZiyWHhdVPTIHf898Vr5dWKu+qETz36XKkIrTfnJLVqrtIT3qwlUpdpbcFwzK1MBc7MBvTIjPYW/gSxJXe5GImJHBTbd5QrZpnkUBGnuycFops3jVfekJc9LR2PH/WYdH3zw7C0zdRxgHkSSYSO5jUJuqgiTTAn1QPzuUlrprCJ9ly50jIkNOQAAlRzi1KuqwGdswgmmJl05iZLkjm/Htuld9Nz+petw0nuR0jVw3SNNLY3xRX9+6vO9M4b37KLa448dETIo8vSo4/DmRfqRR6Q0WhhIvLaUMWQ5wk6H04ngf1ad0D/mk5bwMg90NWZycRkAi8oMCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAVS+gJshIFX6cmBGI8UaOOI/9+XFb4Gi+DHaHVWVVHTd14MoqNK1bmmyTHbGIZbvK8UqgJ9+FhJX1ejx17d4KBzkZI3tYvPnVacHvaw1CIUMZ1Ini6u+UGUTnIlnQzCG0pcTKjOZXf3ih1B2CKdwyC7XeXyEJHicAIG7XfzYfYd9DYHvA+h6hrXaQcNJMW7WFNbtb3fJhtlv5P1Iw+ZEGdj15ukMI0bg2OEQA0F3jIw6QZpigSAGuai3HOY6OgoPO82d7TyTYlNhuwyutWr9izl6QMc2R7BmRfW9XQj4ICR2VWJiL9nqz+SOyqnjQiOObuw8Vywb8c36R1Ym1aaGjOw=="
],
"priority": ["100"] "priority": ["100"]
} }
}, },
@ -1687,13 +1687,6 @@
"providerId": "rsa-enc-generated", "providerId": "rsa-enc-generated",
"subComponents": {}, "subComponents": {},
"config": { "config": {
"privateKey": [
"MIIEogIBAAKCAQEAkQtefHy82e8d5dVWN00LnGI5YmBOTKh0tgqayVRjqLH6u3NfgJVVIe0tFnxa7Wka/ySHrn1KSsW52czZ4uPXLUo4sXBkQxyyFXeZiWN8H+9WiUQ+0hefZF4es5ZPhY2VpeMK9XAnphC362LFLVycXulkpJcQ+4DjI99To4LLyJmjQvsVaJ7amoVJ5xd62eUv+D7f2+jwuaTwjGE3+MWZADXjVxsUY1qJuGLGKnLkNNxJNMDhvnKYw+aa3Z4V90fQVyjN1Volgw3DdA59o4wrWEy+2xHc6j2ESi8+cM60fWzZU9sp2XkyJoCnV7nmwk7pZkDy3zvAkeOWzrr3OWeR3wIDAQABAoIBACWMcet8R0+L7YuATQ+H7IeRjhV/pQWHXp9541RXem1DlgtM9N5Oynk78z4s90Uavphqlo1/deohgdl2hLmODjh1THPzCqGtHhUcnyzICmwiA58JgdHVt7e9/eiz8uY6HxGQ01dyr3D4RwSyzyTNItYXSayqRwU0+phgykA8LhFCAQM/UkRXDf6UCFKBhDyE7VPBaDv0xyxNb7dKtE7C6Qo5t5D40xCfQ8ni8OcD5RvshQq5xOWcw7igxAhlmXCu1fuO2CDiSiqXLMENs4NlwilQ3caMXAIzUiblaKwCrrK2noBoitx6vuOR2tKmIZSlTyDAG4vLQQtOHk53hBoupGECgYEAx4jSmLM9uUzNwNY1zfs8iNswxbU3YibNe2Q+IFmOQofvTaq1jBBxdPWX5ifIbuTvOAA33pmJRh+BtWzOBBQC7Z4i9mdfvyWB6s8t9nnTnWIY5Hj+hV5gaqae59MjdudsORR887fxzPIeAwwaETfKaZnYpC6zLaE3BXwhIcjlFTcCgYEAuhcKf16JkEYNIwanVHpUXjFxwAThAogHWZAngRokmai67Iulx+rSUhhtOIXtmjj/EaObsrqo5yCKAVZ5EbPTOajdd9RtFzH6q3bRjRdp8o8ZVx4c1vMNaOnLbvK4YzJlKSZN9N7m255Mg+/ea3veKVZsSVHDMnuYmH8GjncjPJkCgYAOIUlQmPjZA3BapJDA2nbJ9kO47IFUiQzqHQotPkpNudSfemRK2+s87htoqA6Qk9PA8nsCX3sSJS8JSwA317bxXs55Bo8IOT6/AxbtKmlq7sR2gX78sNdBFjWQkyoixHasgB/tHmyYJ9kqPBQoffvuiH+H+OqlY5JC6CxseQ6H9wKBgF69Hj4MDjLiRwve9k9+2/b8azHcCgX05PEG/+WtPpbwHQIScnseJKdhAjH1lSqf+9OqHLlYaGcK3Nejg42spEvFmcLI5iUZ78lde3++PNUdX0RH81zHbrtL06MPdSojXPcfJi8VUCjdJY1CEFVeQZOACS8mrh7EZ8KzYM4k/055AoGAYqjBv3WS8ul7kAsjpZKpIw1QZZaTjBSmLpjB6X8InF+Zihjgm80Dd4RMFnMnEawhFBvnpklvyw5Ce6NSwcC137kN3NVpJypykkXuYkimg7OxgJjR7YFdbQWJWlc+1eB81WTHcEOHVI/DmeV2yVJcv6kA2iC+3/JA0VoJxvrRBKc="
],
"keyUse": ["ENC"],
"certificate": [
"MIICnTCCAYUCBgGQBsyq0jANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdteXJlYWxtMB4XDTI0MDYxMTEwMTQ1NFoXDTM0MDYxMTEwMTYzNFowEjEQMA4GA1UEAwwHbXlyZWFsbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJELXnx8vNnvHeXVVjdNC5xiOWJgTkyodLYKmslUY6ix+rtzX4CVVSHtLRZ8Wu1pGv8kh659SkrFudnM2eLj1y1KOLFwZEMcshV3mYljfB/vVolEPtIXn2ReHrOWT4WNlaXjCvVwJ6YQt+tixS1cnF7pZKSXEPuA4yPfU6OCy8iZo0L7FWie2pqFSecXetnlL/g+39vo8Lmk8IxhN/jFmQA141cbFGNaibhixipy5DTcSTTA4b5ymMPmmt2eFfdH0FcozdVaJYMNw3QOfaOMK1hMvtsR3Oo9hEovPnDOtH1s2VPbKdl5MiaAp1e55sJO6WZA8t87wJHjls669zlnkd8CAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAD9wQ+CJ0FRgls3JrUzxwHLgrJ3Yo4+mDFpSe1rh2XYK5FEIWDWSqxaXI3p0cOZq75RZmI2xV8oaiJMUz9WMZkbNe/KtGRzHY1N9AZooicGIsnFu1t++b8taFxxpvKWZgnbOum2PZlfcNiXL0QeMv0wwhfn9zKA9W1DRcqYGbIamoyVlumvbNyIjqXJKwGYIOW6GNt7v3wJl5AJw8qAU/O/DQwWwmzcnFGNRxRxAwI7we8EiQ5JlG0Wi+nyAQn74o3RhNr3zsY0ndmFx9bFV4BBo2AiYGozCDOCCG5HvrmoDbrm//wmGRv0tCwueBzWHL2mhtbZ6sGWmMWfiTJ2HPpg=="
],
"priority": ["100"], "priority": ["100"],
"algorithm": ["RSA-OAEP"] "algorithm": ["RSA-OAEP"]
} }
@ -1704,8 +1697,6 @@
"providerId": "aes-generated", "providerId": "aes-generated",
"subComponents": {}, "subComponents": {},
"config": { "config": {
"kid": ["1c1d0c8a-6f0b-48a9-a66f-488489137d85"],
"secret": ["N4wzheVYYBWxFn9VGWTPQQ"],
"priority": ["100"] "priority": ["100"]
} }
}, },
@ -1715,10 +1706,6 @@
"providerId": "hmac-generated", "providerId": "hmac-generated",
"subComponents": {}, "subComponents": {},
"config": { "config": {
"kid": ["ce43821c-6cfd-4ea9-a29a-a724a37e6955"],
"secret": [
"j_8WeQHYt5R6coay0IOUeu9hGvCoJsgnENSoYm0gDlDx6IHOg-f6p17QIaesNmgrzXtJDRpYMhSjpTMHOnHCHLxwUM4eVg9TcszffndB850Yj3PHPeCc5aoHcpYzWN9NDZZ02nBYA04nfbkdlLXiGlpS3I3e502e4DX3rFtbFZ0"
],
"priority": ["100"], "priority": ["100"],
"algorithm": ["HS512"] "algorithm": ["HS512"]
} }
@ -2388,7 +2375,7 @@
"clientSessionMaxLifespan": "0", "clientSessionMaxLifespan": "0",
"organizationsEnabled": "false" "organizationsEnabled": "false"
}, },
"keycloakVersion": "25.0.0", "keycloakVersion": "25.0.6",
"userManagedAccessAllowed": false, "userManagedAccessAllowed": false,
"organizationsEnabled": false, "organizationsEnabled": false,
"clientProfiles": { "clientProfiles": {

View File

@ -38,6 +38,7 @@
"bruteForceProtected": false, "bruteForceProtected": false,
"permanentLockout": false, "permanentLockout": false,
"maxTemporaryLockouts": 0, "maxTemporaryLockouts": 0,
"bruteForceStrategy": "MULTIPLE",
"maxFailureWaitSeconds": 900, "maxFailureWaitSeconds": 900,
"minimumQuickLoginWaitSeconds": 60, "minimumQuickLoginWaitSeconds": 60,
"waitIncrementSeconds": 60, "waitIncrementSeconds": 60,
@ -604,6 +605,7 @@
"frontchannelLogout": false, "frontchannelLogout": false,
"protocol": "openid-connect", "protocol": "openid-connect",
"attributes": { "attributes": {
"realm_client": "false",
"post.logout.redirect.uris": "+" "post.logout.redirect.uris": "+"
}, },
"authenticationFlowBindingOverrides": {}, "authenticationFlowBindingOverrides": {},
@ -628,14 +630,16 @@
"id": "d8f14dc4-5f0f-4a1d-8c0b-cfe78ee55cb3", "id": "d8f14dc4-5f0f-4a1d-8c0b-cfe78ee55cb3",
"clientId": "account-console", "clientId": "account-console",
"name": "${client_account-console}", "name": "${client_account-console}",
"description": "",
"rootUrl": "${authBaseUrl}", "rootUrl": "${authBaseUrl}",
"adminUrl": "",
"baseUrl": "/realms/myrealm/account/", "baseUrl": "/realms/myrealm/account/",
"surrogateAuthRequired": false, "surrogateAuthRequired": false,
"enabled": true, "enabled": true,
"alwaysDisplayInConsole": false, "alwaysDisplayInConsole": false,
"clientAuthenticatorType": "client-secret", "clientAuthenticatorType": "client-secret",
"redirectUris": ["/realms/myrealm/account/*"], "redirectUris": ["*"],
"webOrigins": [], "webOrigins": ["*"],
"notBefore": 0, "notBefore": 0,
"bearerOnly": false, "bearerOnly": false,
"consentRequired": false, "consentRequired": false,
@ -647,8 +651,14 @@
"frontchannelLogout": false, "frontchannelLogout": false,
"protocol": "openid-connect", "protocol": "openid-connect",
"attributes": { "attributes": {
"realm_client": "false",
"oidc.ciba.grant.enabled": "false",
"backchannel.logout.session.required": "true",
"post.logout.redirect.uris": "+", "post.logout.redirect.uris": "+",
"pkce.code.challenge.method": "S256" "oauth2.device.authorization.grant.enabled": "false",
"display.on.consent.screen": "false",
"pkce.code.challenge.method": "S256",
"backchannel.logout.revoke.offline.tokens": "false"
}, },
"authenticationFlowBindingOverrides": {}, "authenticationFlowBindingOverrides": {},
"fullScopeAllowed": false, "fullScopeAllowed": false,
@ -699,10 +709,12 @@
"frontchannelLogout": false, "frontchannelLogout": false,
"protocol": "openid-connect", "protocol": "openid-connect",
"attributes": { "attributes": {
"realm_client": "false",
"client.use.lightweight.access.token.enabled": "true",
"post.logout.redirect.uris": "+" "post.logout.redirect.uris": "+"
}, },
"authenticationFlowBindingOverrides": {}, "authenticationFlowBindingOverrides": {},
"fullScopeAllowed": false, "fullScopeAllowed": true,
"nodeReRegistrationTimeout": 0, "nodeReRegistrationTimeout": 0,
"defaultClientScopes": [ "defaultClientScopes": [
"web-origins", "web-origins",
@ -740,6 +752,7 @@
"frontchannelLogout": false, "frontchannelLogout": false,
"protocol": "openid-connect", "protocol": "openid-connect",
"attributes": { "attributes": {
"realm_client": "true",
"post.logout.redirect.uris": "+" "post.logout.redirect.uris": "+"
}, },
"authenticationFlowBindingOverrides": {}, "authenticationFlowBindingOverrides": {},
@ -789,6 +802,7 @@
"frontchannelLogout": true, "frontchannelLogout": true,
"protocol": "openid-connect", "protocol": "openid-connect",
"attributes": { "attributes": {
"realm_client": "false",
"oidc.ciba.grant.enabled": "false", "oidc.ciba.grant.enabled": "false",
"backchannel.logout.session.required": "true", "backchannel.logout.session.required": "true",
"login_theme": "keycloakify-starter", "login_theme": "keycloakify-starter",
@ -855,6 +869,7 @@
"frontchannelLogout": false, "frontchannelLogout": false,
"protocol": "openid-connect", "protocol": "openid-connect",
"attributes": { "attributes": {
"realm_client": "true",
"post.logout.redirect.uris": "+" "post.logout.redirect.uris": "+"
}, },
"authenticationFlowBindingOverrides": {}, "authenticationFlowBindingOverrides": {},
@ -898,11 +913,13 @@
"frontchannelLogout": false, "frontchannelLogout": false,
"protocol": "openid-connect", "protocol": "openid-connect",
"attributes": { "attributes": {
"realm_client": "false",
"client.use.lightweight.access.token.enabled": "true",
"post.logout.redirect.uris": "+", "post.logout.redirect.uris": "+",
"pkce.code.challenge.method": "S256" "pkce.code.challenge.method": "S256"
}, },
"authenticationFlowBindingOverrides": {}, "authenticationFlowBindingOverrides": {},
"fullScopeAllowed": false, "fullScopeAllowed": true,
"nodeReRegistrationTimeout": 0, "nodeReRegistrationTimeout": 0,
"protocolMappers": [ "protocolMappers": [
{ {
@ -1574,14 +1591,14 @@
"subComponents": {}, "subComponents": {},
"config": { "config": {
"allowed-protocol-mapper-types": [ "allowed-protocol-mapper-types": [
"oidc-full-name-mapper",
"oidc-usermodel-attribute-mapper",
"oidc-address-mapper",
"saml-user-attribute-mapper", "saml-user-attribute-mapper",
"oidc-usermodel-property-mapper", "oidc-sha256-pairwise-sub-mapper",
"oidc-full-name-mapper",
"saml-user-property-mapper", "saml-user-property-mapper",
"oidc-address-mapper",
"oidc-usermodel-property-mapper",
"saml-role-list-mapper", "saml-role-list-mapper",
"oidc-sha256-pairwise-sub-mapper" "oidc-usermodel-attribute-mapper"
] ]
} }
}, },
@ -1611,14 +1628,14 @@
"subComponents": {}, "subComponents": {},
"config": { "config": {
"allowed-protocol-mapper-types": [ "allowed-protocol-mapper-types": [
"oidc-sha256-pairwise-sub-mapper",
"oidc-usermodel-property-mapper",
"oidc-address-mapper",
"oidc-usermodel-attribute-mapper", "oidc-usermodel-attribute-mapper",
"oidc-full-name-mapper", "oidc-full-name-mapper",
"saml-user-attribute-mapper", "oidc-usermodel-property-mapper",
"oidc-address-mapper",
"saml-user-property-mapper", "saml-user-property-mapper",
"saml-role-list-mapper" "saml-role-list-mapper",
"saml-user-attribute-mapper",
"oidc-sha256-pairwise-sub-mapper"
] ]
} }
}, },
@ -1671,13 +1688,6 @@
"providerId": "rsa-generated", "providerId": "rsa-generated",
"subComponents": {}, "subComponents": {},
"config": { "config": {
"privateKey": [
"MIIEowIBAAKCAQEAsYUWzVfZMd6ywpBmLJYeF1U9Mgd/z3xWvl1Yq76oRPPfpcqQitN+cktWqu0hPerCVSl2ltwXDMrUwFzswG9MiM9hb+BLEld7kYiYkcFNt3lCtmmeRQEae7JwWimzeNV96Qlz0tHY8f9Zh0ffPDsLTN1HGAeRJJhI7mNQm6qCJNMCfVA/O5SWumsIn2XLnSMiQ05AACVHOLUq6rAZ2zCCaYmXTmJkuSOb8e26V303P6l63DSe5HSNXDdI00tjfFFf37q870zhvfsotrjjx0RMijy9Kjj8OZF+pFHpDRaGEi8tpQxZDnCTofTieB/Vp3QP+aTlvAyD3Q1ZnJxGQCLygwIDAQABAoIBABUJ9XMJGNQzamiVwuOWN7ht4UP8ezYvgdEA8NaLUO0PIYVIKyD7l4OwkHPPM9PfRACM2qG0MZp8sCyg4WxIeepy+D979oRqJYUmNRLSipqWlASuItRXIPjiY99uYXdjh2R8Os5pvCD+MZxPX9KHGuaVXmzSJMO7YAAPeYkMHcLYTp/U0c65Ztaaz1zz1FeyvpjkLr9SHiMcIN51zFmhvT1tcRIqy4zidisjrTSUr/KPVxeJtrEfyhTGk3z41yJf5YbeaxaMjJR5x0WXzt1fWVmA/V1bWa2Zlj9d8AxDReA1p7Lpstz34PRoCMj9bmFguI2+RTw6K0D++Jydfxmh8vUCgYEA5Zwk2r3TFO3i3V70LOn6CLzn15yLeuSIJ9p2os70jQOmFMCreLdcUbCaiUe7UV/IIVftbcxhFm9zECXZXX0wubcmHZqyptlbuAn1de4QkLJixXo1A7ZQXBEZk22WN2naXHQF5oK6lh/VSLcZBajTsyvBm5JWXrd8djjG06MugA8CgYEAxexKI5IwcLhpMDV9UPQb/+lDWHVqCT2xwYxnZ85y+5gmrOyyT7mIChz3DFYiaw4CHJWmBkIDBaiDgLEgQk4QXWzYshXawShBHnv1h08bVMMw98Ivec7ZRkV+/ET30YRwC2Uyk4bm4HpwVV5GCFhC4aAvRcCA1CIJk3MwcOwksk0CgYEAqxyaOomMbOR7VQ4WWgJkW26sOHppV8RH06tzDhG9HfnCI2USZHwBSL+b6wKSDiqbMn4cat8M23NjBH2wZ4OMdFqRBS7sRHtnZtfFHYW0wqCuCwzvxTxw1qvHq57Xe6RfHtc4LnjuJELE59PLyfPvEG9jcVS1GREUp+XYBpBtbvECgYAMhWBDU9JAr0noRNoCrw6+Z9Fc3UCyCPcf2XQJOyRHCl8X/XliVchna2GtpB1VTHORv13bc32hdAGtuIbj6vBaGLK0wXEvWw6TkR/9SWHfQOHuKpi6Sf2w1mCsMOjElm5IKkTC1Hvyo4xLukUP7hV9FJcpAH6l7OlSLK1Z13aS2QKBgB6w4gvmVEQruHV5+K60OatuFojr+kxJwmzCb5uKOULUFezT2pA3p3l6IWxGL2XtM+LD0SiZE3KZJUzf+LatYlBU9ek4F1krkVNUTRZpzUa0oADbymCL1chM4oPIs7sISQlFIH2wOSZt6Blvcw0E0wfjd9Gv/LHxcMnlRb1t1sLk"
],
"keyUse": ["SIG"],
"certificate": [
"MIICnTCCAYUCBgGQBsyplzANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdteXJlYWxtMB4XDTI0MDYxMTEwMTQ1NFoXDTM0MDYxMTEwMTYzNFowEjEQMA4GA1UEAwwHbXlyZWFsbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALGFFs1X2THessKQZiyWHhdVPTIHf898Vr5dWKu+qETz36XKkIrTfnJLVqrtIT3qwlUpdpbcFwzK1MBc7MBvTIjPYW/gSxJXe5GImJHBTbd5QrZpnkUBGnuycFops3jVfekJc9LR2PH/WYdH3zw7C0zdRxgHkSSYSO5jUJuqgiTTAn1QPzuUlrprCJ9ly50jIkNOQAAlRzi1KuqwGdswgmmJl05iZLkjm/Htuld9Nz+petw0nuR0jVw3SNNLY3xRX9+6vO9M4b37KLa448dETIo8vSo4/DmRfqRR6Q0WhhIvLaUMWQ5wk6H04ngf1ad0D/mk5bwMg90NWZycRkAi8oMCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAVS+gJshIFX6cmBGI8UaOOI/9+XFb4Gi+DHaHVWVVHTd14MoqNK1bmmyTHbGIZbvK8UqgJ9+FhJX1ejx17d4KBzkZI3tYvPnVacHvaw1CIUMZ1Ini6u+UGUTnIlnQzCG0pcTKjOZXf3ih1B2CKdwyC7XeXyEJHicAIG7XfzYfYd9DYHvA+h6hrXaQcNJMW7WFNbtb3fJhtlv5P1Iw+ZEGdj15ukMI0bg2OEQA0F3jIw6QZpigSAGuai3HOY6OgoPO82d7TyTYlNhuwyutWr9izl6QMc2R7BmRfW9XQj4ICR2VWJiL9nqz+SOyqnjQiOObuw8Vywb8c36R1Ym1aaGjOw=="
],
"priority": ["100"] "priority": ["100"]
} }
}, },
@ -1687,13 +1697,6 @@
"providerId": "rsa-enc-generated", "providerId": "rsa-enc-generated",
"subComponents": {}, "subComponents": {},
"config": { "config": {
"privateKey": [
"MIIEogIBAAKCAQEAkQtefHy82e8d5dVWN00LnGI5YmBOTKh0tgqayVRjqLH6u3NfgJVVIe0tFnxa7Wka/ySHrn1KSsW52czZ4uPXLUo4sXBkQxyyFXeZiWN8H+9WiUQ+0hefZF4es5ZPhY2VpeMK9XAnphC362LFLVycXulkpJcQ+4DjI99To4LLyJmjQvsVaJ7amoVJ5xd62eUv+D7f2+jwuaTwjGE3+MWZADXjVxsUY1qJuGLGKnLkNNxJNMDhvnKYw+aa3Z4V90fQVyjN1Volgw3DdA59o4wrWEy+2xHc6j2ESi8+cM60fWzZU9sp2XkyJoCnV7nmwk7pZkDy3zvAkeOWzrr3OWeR3wIDAQABAoIBACWMcet8R0+L7YuATQ+H7IeRjhV/pQWHXp9541RXem1DlgtM9N5Oynk78z4s90Uavphqlo1/deohgdl2hLmODjh1THPzCqGtHhUcnyzICmwiA58JgdHVt7e9/eiz8uY6HxGQ01dyr3D4RwSyzyTNItYXSayqRwU0+phgykA8LhFCAQM/UkRXDf6UCFKBhDyE7VPBaDv0xyxNb7dKtE7C6Qo5t5D40xCfQ8ni8OcD5RvshQq5xOWcw7igxAhlmXCu1fuO2CDiSiqXLMENs4NlwilQ3caMXAIzUiblaKwCrrK2noBoitx6vuOR2tKmIZSlTyDAG4vLQQtOHk53hBoupGECgYEAx4jSmLM9uUzNwNY1zfs8iNswxbU3YibNe2Q+IFmOQofvTaq1jBBxdPWX5ifIbuTvOAA33pmJRh+BtWzOBBQC7Z4i9mdfvyWB6s8t9nnTnWIY5Hj+hV5gaqae59MjdudsORR887fxzPIeAwwaETfKaZnYpC6zLaE3BXwhIcjlFTcCgYEAuhcKf16JkEYNIwanVHpUXjFxwAThAogHWZAngRokmai67Iulx+rSUhhtOIXtmjj/EaObsrqo5yCKAVZ5EbPTOajdd9RtFzH6q3bRjRdp8o8ZVx4c1vMNaOnLbvK4YzJlKSZN9N7m255Mg+/ea3veKVZsSVHDMnuYmH8GjncjPJkCgYAOIUlQmPjZA3BapJDA2nbJ9kO47IFUiQzqHQotPkpNudSfemRK2+s87htoqA6Qk9PA8nsCX3sSJS8JSwA317bxXs55Bo8IOT6/AxbtKmlq7sR2gX78sNdBFjWQkyoixHasgB/tHmyYJ9kqPBQoffvuiH+H+OqlY5JC6CxseQ6H9wKBgF69Hj4MDjLiRwve9k9+2/b8azHcCgX05PEG/+WtPpbwHQIScnseJKdhAjH1lSqf+9OqHLlYaGcK3Nejg42spEvFmcLI5iUZ78lde3++PNUdX0RH81zHbrtL06MPdSojXPcfJi8VUCjdJY1CEFVeQZOACS8mrh7EZ8KzYM4k/055AoGAYqjBv3WS8ul7kAsjpZKpIw1QZZaTjBSmLpjB6X8InF+Zihjgm80Dd4RMFnMnEawhFBvnpklvyw5Ce6NSwcC137kN3NVpJypykkXuYkimg7OxgJjR7YFdbQWJWlc+1eB81WTHcEOHVI/DmeV2yVJcv6kA2iC+3/JA0VoJxvrRBKc="
],
"keyUse": ["ENC"],
"certificate": [
"MIICnTCCAYUCBgGQBsyq0jANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdteXJlYWxtMB4XDTI0MDYxMTEwMTQ1NFoXDTM0MDYxMTEwMTYzNFowEjEQMA4GA1UEAwwHbXlyZWFsbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJELXnx8vNnvHeXVVjdNC5xiOWJgTkyodLYKmslUY6ix+rtzX4CVVSHtLRZ8Wu1pGv8kh659SkrFudnM2eLj1y1KOLFwZEMcshV3mYljfB/vVolEPtIXn2ReHrOWT4WNlaXjCvVwJ6YQt+tixS1cnF7pZKSXEPuA4yPfU6OCy8iZo0L7FWie2pqFSecXetnlL/g+39vo8Lmk8IxhN/jFmQA141cbFGNaibhixipy5DTcSTTA4b5ymMPmmt2eFfdH0FcozdVaJYMNw3QOfaOMK1hMvtsR3Oo9hEovPnDOtH1s2VPbKdl5MiaAp1e55sJO6WZA8t87wJHjls669zlnkd8CAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAD9wQ+CJ0FRgls3JrUzxwHLgrJ3Yo4+mDFpSe1rh2XYK5FEIWDWSqxaXI3p0cOZq75RZmI2xV8oaiJMUz9WMZkbNe/KtGRzHY1N9AZooicGIsnFu1t++b8taFxxpvKWZgnbOum2PZlfcNiXL0QeMv0wwhfn9zKA9W1DRcqYGbIamoyVlumvbNyIjqXJKwGYIOW6GNt7v3wJl5AJw8qAU/O/DQwWwmzcnFGNRxRxAwI7we8EiQ5JlG0Wi+nyAQn74o3RhNr3zsY0ndmFx9bFV4BBo2AiYGozCDOCCG5HvrmoDbrm//wmGRv0tCwueBzWHL2mhtbZ6sGWmMWfiTJ2HPpg=="
],
"priority": ["100"], "priority": ["100"],
"algorithm": ["RSA-OAEP"] "algorithm": ["RSA-OAEP"]
} }
@ -1704,8 +1707,6 @@
"providerId": "aes-generated", "providerId": "aes-generated",
"subComponents": {}, "subComponents": {},
"config": { "config": {
"kid": ["1c1d0c8a-6f0b-48a9-a66f-488489137d85"],
"secret": ["N4wzheVYYBWxFn9VGWTPQQ"],
"priority": ["100"] "priority": ["100"]
} }
}, },
@ -1715,10 +1716,6 @@
"providerId": "hmac-generated", "providerId": "hmac-generated",
"subComponents": {}, "subComponents": {},
"config": { "config": {
"kid": ["ce43821c-6cfd-4ea9-a29a-a724a37e6955"],
"secret": [
"j_8WeQHYt5R6coay0IOUeu9hGvCoJsgnENSoYm0gDlDx6IHOg-f6p17QIaesNmgrzXtJDRpYMhSjpTMHOnHCHLxwUM4eVg9TcszffndB850Yj3PHPeCc5aoHcpYzWN9NDZZ02nBYA04nfbkdlLXiGlpS3I3e502e4DX3rFtbFZ0"
],
"priority": ["100"], "priority": ["100"],
"algorithm": ["HS512"] "algorithm": ["HS512"]
} }
@ -2388,7 +2385,7 @@
"clientSessionMaxLifespan": "0", "clientSessionMaxLifespan": "0",
"organizationsEnabled": "false" "organizationsEnabled": "false"
}, },
"keycloakVersion": "25.0.0", "keycloakVersion": "26.0.6",
"userManagedAccessAllowed": false, "userManagedAccessAllowed": false,
"organizationsEnabled": false, "organizationsEnabled": false,
"clientProfiles": { "clientProfiles": {

View File

@ -1,7 +1,7 @@
import type { BuildContext } from "../shared/buildContext"; import type { BuildContext } from "../shared/buildContext";
import { exclude } from "tsafe/exclude"; import { exclude } from "tsafe/exclude";
import { promptKeycloakVersion } from "../shared/promptKeycloakVersion"; import { promptKeycloakVersion } from "../shared/promptKeycloakVersion";
import { CONTAINER_NAME } from "../shared/constants"; import { CONTAINER_NAME, KEYCLOAKIFY_SPA_DEV_SERVER_PORT } from "../shared/constants";
import { SemVer } from "../tools/SemVer"; import { SemVer } from "../tools/SemVer";
import { assert, type Equals } from "tsafe/assert"; import { assert, type Equals } from "tsafe/assert";
import * as fs from "fs"; import * as fs from "fs";
@ -27,6 +27,7 @@ import { isInside } from "../tools/isInside";
import { existsAsync } from "../tools/fs.existsAsync"; import { existsAsync } from "../tools/fs.existsAsync";
import { rm } from "../tools/fs.rm"; import { rm } from "../tools/fs.rm";
import { downloadAndExtractArchive } from "../tools/downloadAndExtractArchive"; import { downloadAndExtractArchive } from "../tools/downloadAndExtractArchive";
import { startViteDevServer } from "./startViteDevServer";
export async function command(params: { export async function command(params: {
buildContext: BuildContext; buildContext: BuildContext;
@ -379,6 +380,55 @@ export async function command(params: {
const port = const port =
cliCommandOptions.port ?? buildContext.startKeycloakOptions.port ?? DEFAULT_PORT; cliCommandOptions.port ?? buildContext.startKeycloakOptions.port ?? DEFAULT_PORT;
const doStartDevServer = (() => {
const hasSpaUi =
buildContext.implementedThemeTypes.admin.isImplemented ||
(buildContext.implementedThemeTypes.account.isImplemented &&
buildContext.implementedThemeTypes.account.type === "Single-Page");
if (!hasSpaUi) {
return false;
}
if (buildContext.bundler !== "vite") {
console.log(
chalk.yellow(
[
`WARNING: Since you are using ${buildContext.bundler} instead of Vite,`,
`you'll have to wait serval seconds for the changes you made on your account or admin theme to be reflected in the browser.\n`,
`For a better development experience, consider migrating to Vite.`
].join(" ")
)
);
return false;
}
if (keycloakMajorVersionNumber < 25) {
console.log(
chalk.yellow(
[
`WARNING: Your account or admin theme can't be tested with hot module replacement on Keycloak ${keycloakMajorVersionNumber}.`,
`This mean that you'll have to wait serval seconds for the changes to be reflected in the browser.`,
`For a better development experience, select a more recent version of Keycloak.`
].join("\n")
)
);
return false;
}
return true;
})();
let devServerPort: number | undefined = undefined;
if (doStartDevServer) {
const { port } = await startViteDevServer({ buildContext });
devServerPort = port;
}
const SPACE_PLACEHOLDER = "SPACE_PLACEHOLDER_xKLmdPd"; const SPACE_PLACEHOLDER = "SPACE_PLACEHOLDER_xKLmdPd";
const dockerRunArgs: string[] = [ const dockerRunArgs: string[] = [
@ -386,6 +436,11 @@ export async function command(params: {
`--name${SPACE_PLACEHOLDER}${CONTAINER_NAME}`, `--name${SPACE_PLACEHOLDER}${CONTAINER_NAME}`,
`-e${SPACE_PLACEHOLDER}KEYCLOAK_ADMIN=admin`, `-e${SPACE_PLACEHOLDER}KEYCLOAK_ADMIN=admin`,
`-e${SPACE_PLACEHOLDER}KEYCLOAK_ADMIN_PASSWORD=admin`, `-e${SPACE_PLACEHOLDER}KEYCLOAK_ADMIN_PASSWORD=admin`,
...(devServerPort === undefined
? []
: [
`-e${SPACE_PLACEHOLDER}${KEYCLOAKIFY_SPA_DEV_SERVER_PORT}=${devServerPort}`
]),
...(buildContext.startKeycloakOptions.dockerExtraArgs.length === 0 ...(buildContext.startKeycloakOptions.dockerExtraArgs.length === 0
? [] ? []
: [ : [
@ -592,6 +647,44 @@ export async function command(params: {
} }
) )
.on("all", async (...[, filePath]) => { .on("all", async (...[, filePath]) => {
ignore_account_spa: {
const doImplementAccountSpa =
buildContext.implementedThemeTypes.account.isImplemented &&
buildContext.implementedThemeTypes.account.type === "Single-Page";
if (!doImplementAccountSpa) {
break ignore_account_spa;
}
if (
!isInside({
dirPath: pathJoin(buildContext.themeSrcDirPath, "account"),
filePath
})
) {
break ignore_account_spa;
}
return;
}
ignore_admin: {
if (!buildContext.implementedThemeTypes.admin.isImplemented) {
break ignore_admin;
}
if (
!isInside({
dirPath: pathJoin(buildContext.themeSrcDirPath, "admin"),
filePath
})
) {
break ignore_admin;
}
return;
}
console.log(`Detected changes in ${filePath}`); console.log(`Detected changes in ${filePath}`);
await waitForDebounce(); await waitForDebounce();

View File

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

View File

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

View File

@ -62,7 +62,12 @@ export async function command(params: { buildContext: BuildContext }) {
2 2
)};`, )};`,
``, ``,
`type KcContext =`, `/**`,
` * NOTE: Do not import this type except maybe in your entrypoint. `,
` * If you need to import the KcContext import it either from src/login/KcContext.ts or src/account/KcContext.ts.`,
` * Depending on the theme type you are working on.`,
` */`,
`export type KcContext =`,
hasLoginTheme && ` | import("./login/KcContext").KcContext`, hasLoginTheme && ` | import("./login/KcContext").KcContext`,
hasAccountTheme && ` | import("./account/KcContext").KcContext`, hasAccountTheme && ` | import("./account/KcContext").KcContext`,
hasAdminTheme && ` | import("./admin/KcContext").KcContext`, hasAdminTheme && ` | import("./admin/KcContext").KcContext`,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

View File

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