Compare commits

...

13 Commits

Author SHA1 Message Date
1530ca32c8 Bump version 2024-12-01 00:07:28 +01:00
ed054f131a Merge pull request #736 from keycloakify/hmr_in_start_keycloak
Implement hot module replacement for developing Account SPA and Admin UI
2024-12-01 00:01:57 +01:00
ec74ceef4d Implement hot module replacement for developing Account SPA and Admin UI 2024-11-30 23:55:24 +01:00
fd3261cdf1 Bump version 2024-11-25 11:41:36 +01:00
b4b53d2552 Re export wide type def of the kcContext 2024-11-25 11:41:17 +01:00
0371d9ea7a Bump version 2024-11-23 08:49:17 +01:00
73031e74ec Make it so it's not required to manually call the copy-keycloak-resources-to-public script even in webpack projects 2024-11-23 08:48:06 +01:00
f71ab4635f Bump version 2024-11-22 06:16:42 +01:00
983db6780a #730 2024-11-22 06:16:15 +01:00
ea22107b9b Bump version 2024-11-21 07:12:53 +01:00
8e4a7fed9e Fix invalid dom nesting errors 2024-11-21 07:12:23 +01:00
30efd8fcf4 Bump version 2024-11-21 06:27:09 +01:00
f4c4e92ca1 #726 2024-11-21 06:26:54 +01:00
15 changed files with 450 additions and 109 deletions

View File

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

View File

@ -84,8 +84,47 @@ attributes_to_attributesByName: {
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;
<#if xKeycloakify.themeType == "login" >
{
const script = document.createElement("script");

View File

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

View File

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

View File

@ -10,7 +10,8 @@ export type ThemeType = (typeof THEME_TYPES)[number];
export const VITE_PLUGIN_SUB_SCRIPTS_ENV_NAMES = {
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;
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 KEYCLOAKIFY_SPA_DEV_SERVER_PORT = "KEYCLOAKIFY_SPA_DEV_SERVER_PORT";

View File

@ -628,14 +628,16 @@
"id": "d8f14dc4-5f0f-4a1d-8c0b-cfe78ee55cb3",
"clientId": "account-console",
"name": "${client_account-console}",
"description": "",
"rootUrl": "${authBaseUrl}",
"adminUrl": "",
"baseUrl": "/realms/myrealm/account/",
"surrogateAuthRequired": false,
"enabled": true,
"alwaysDisplayInConsole": false,
"clientAuthenticatorType": "client-secret",
"redirectUris": ["/realms/myrealm/account/*"],
"webOrigins": [],
"redirectUris": ["*"],
"webOrigins": ["*"],
"notBefore": 0,
"bearerOnly": false,
"consentRequired": false,
@ -647,8 +649,13 @@
"frontchannelLogout": false,
"protocol": "openid-connect",
"attributes": {
"oidc.ciba.grant.enabled": "false",
"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": false,
@ -1574,14 +1581,14 @@
"subComponents": {},
"config": {
"allowed-protocol-mapper-types": [
"oidc-sha256-pairwise-sub-mapper",
"oidc-full-name-mapper",
"oidc-usermodel-attribute-mapper",
"oidc-address-mapper",
"saml-user-attribute-mapper",
"oidc-usermodel-property-mapper",
"saml-user-property-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": {
"allowed-protocol-mapper-types": [
"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-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",
"subComponents": {},
"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"]
}
},
@ -1687,13 +1687,6 @@
"providerId": "rsa-enc-generated",
"subComponents": {},
"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"],
"algorithm": ["RSA-OAEP"]
}
@ -1704,8 +1697,6 @@
"providerId": "aes-generated",
"subComponents": {},
"config": {
"kid": ["1c1d0c8a-6f0b-48a9-a66f-488489137d85"],
"secret": ["N4wzheVYYBWxFn9VGWTPQQ"],
"priority": ["100"]
}
},
@ -1715,10 +1706,6 @@
"providerId": "hmac-generated",
"subComponents": {},
"config": {
"kid": ["ce43821c-6cfd-4ea9-a29a-a724a37e6955"],
"secret": [
"j_8WeQHYt5R6coay0IOUeu9hGvCoJsgnENSoYm0gDlDx6IHOg-f6p17QIaesNmgrzXtJDRpYMhSjpTMHOnHCHLxwUM4eVg9TcszffndB850Yj3PHPeCc5aoHcpYzWN9NDZZ02nBYA04nfbkdlLXiGlpS3I3e502e4DX3rFtbFZ0"
],
"priority": ["100"],
"algorithm": ["HS512"]
}
@ -2388,7 +2375,7 @@
"clientSessionMaxLifespan": "0",
"organizationsEnabled": "false"
},
"keycloakVersion": "25.0.0",
"keycloakVersion": "25.0.6",
"userManagedAccessAllowed": false,
"organizationsEnabled": false,
"clientProfiles": {

View File

@ -38,6 +38,7 @@
"bruteForceProtected": false,
"permanentLockout": false,
"maxTemporaryLockouts": 0,
"bruteForceStrategy": "MULTIPLE",
"maxFailureWaitSeconds": 900,
"minimumQuickLoginWaitSeconds": 60,
"waitIncrementSeconds": 60,
@ -604,6 +605,7 @@
"frontchannelLogout": false,
"protocol": "openid-connect",
"attributes": {
"realm_client": "false",
"post.logout.redirect.uris": "+"
},
"authenticationFlowBindingOverrides": {},
@ -628,14 +630,16 @@
"id": "d8f14dc4-5f0f-4a1d-8c0b-cfe78ee55cb3",
"clientId": "account-console",
"name": "${client_account-console}",
"description": "",
"rootUrl": "${authBaseUrl}",
"adminUrl": "",
"baseUrl": "/realms/myrealm/account/",
"surrogateAuthRequired": false,
"enabled": true,
"alwaysDisplayInConsole": false,
"clientAuthenticatorType": "client-secret",
"redirectUris": ["/realms/myrealm/account/*"],
"webOrigins": [],
"redirectUris": ["*"],
"webOrigins": ["*"],
"notBefore": 0,
"bearerOnly": false,
"consentRequired": false,
@ -647,8 +651,14 @@
"frontchannelLogout": false,
"protocol": "openid-connect",
"attributes": {
"realm_client": "false",
"oidc.ciba.grant.enabled": "false",
"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": false,
@ -699,10 +709,12 @@
"frontchannelLogout": false,
"protocol": "openid-connect",
"attributes": {
"realm_client": "false",
"client.use.lightweight.access.token.enabled": "true",
"post.logout.redirect.uris": "+"
},
"authenticationFlowBindingOverrides": {},
"fullScopeAllowed": false,
"fullScopeAllowed": true,
"nodeReRegistrationTimeout": 0,
"defaultClientScopes": [
"web-origins",
@ -740,6 +752,7 @@
"frontchannelLogout": false,
"protocol": "openid-connect",
"attributes": {
"realm_client": "true",
"post.logout.redirect.uris": "+"
},
"authenticationFlowBindingOverrides": {},
@ -789,6 +802,7 @@
"frontchannelLogout": true,
"protocol": "openid-connect",
"attributes": {
"realm_client": "false",
"oidc.ciba.grant.enabled": "false",
"backchannel.logout.session.required": "true",
"login_theme": "keycloakify-starter",
@ -855,6 +869,7 @@
"frontchannelLogout": false,
"protocol": "openid-connect",
"attributes": {
"realm_client": "true",
"post.logout.redirect.uris": "+"
},
"authenticationFlowBindingOverrides": {},
@ -898,11 +913,13 @@
"frontchannelLogout": false,
"protocol": "openid-connect",
"attributes": {
"realm_client": "false",
"client.use.lightweight.access.token.enabled": "true",
"post.logout.redirect.uris": "+",
"pkce.code.challenge.method": "S256"
},
"authenticationFlowBindingOverrides": {},
"fullScopeAllowed": false,
"fullScopeAllowed": true,
"nodeReRegistrationTimeout": 0,
"protocolMappers": [
{
@ -1574,14 +1591,14 @@
"subComponents": {},
"config": {
"allowed-protocol-mapper-types": [
"oidc-full-name-mapper",
"oidc-usermodel-attribute-mapper",
"oidc-address-mapper",
"saml-user-attribute-mapper",
"oidc-usermodel-property-mapper",
"oidc-sha256-pairwise-sub-mapper",
"oidc-full-name-mapper",
"saml-user-property-mapper",
"oidc-address-mapper",
"oidc-usermodel-property-mapper",
"saml-role-list-mapper",
"oidc-sha256-pairwise-sub-mapper"
"oidc-usermodel-attribute-mapper"
]
}
},
@ -1611,14 +1628,14 @@
"subComponents": {},
"config": {
"allowed-protocol-mapper-types": [
"oidc-sha256-pairwise-sub-mapper",
"oidc-usermodel-property-mapper",
"oidc-address-mapper",
"oidc-usermodel-attribute-mapper",
"oidc-full-name-mapper",
"saml-user-attribute-mapper",
"oidc-usermodel-property-mapper",
"oidc-address-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",
"subComponents": {},
"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"]
}
},
@ -1687,13 +1697,6 @@
"providerId": "rsa-enc-generated",
"subComponents": {},
"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"],
"algorithm": ["RSA-OAEP"]
}
@ -1704,8 +1707,6 @@
"providerId": "aes-generated",
"subComponents": {},
"config": {
"kid": ["1c1d0c8a-6f0b-48a9-a66f-488489137d85"],
"secret": ["N4wzheVYYBWxFn9VGWTPQQ"],
"priority": ["100"]
}
},
@ -1715,10 +1716,6 @@
"providerId": "hmac-generated",
"subComponents": {},
"config": {
"kid": ["ce43821c-6cfd-4ea9-a29a-a724a37e6955"],
"secret": [
"j_8WeQHYt5R6coay0IOUeu9hGvCoJsgnENSoYm0gDlDx6IHOg-f6p17QIaesNmgrzXtJDRpYMhSjpTMHOnHCHLxwUM4eVg9TcszffndB850Yj3PHPeCc5aoHcpYzWN9NDZZ02nBYA04nfbkdlLXiGlpS3I3e502e4DX3rFtbFZ0"
],
"priority": ["100"],
"algorithm": ["HS512"]
}
@ -2388,7 +2385,7 @@
"clientSessionMaxLifespan": "0",
"organizationsEnabled": "false"
},
"keycloakVersion": "25.0.0",
"keycloakVersion": "26.0.6",
"userManagedAccessAllowed": false,
"organizationsEnabled": false,
"clientProfiles": {

View File

@ -1,7 +1,7 @@
import type { BuildContext } from "../shared/buildContext";
import { exclude } from "tsafe/exclude";
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 { assert, type Equals } from "tsafe/assert";
import * as fs from "fs";
@ -27,6 +27,7 @@ import { isInside } from "../tools/isInside";
import { existsAsync } from "../tools/fs.existsAsync";
import { rm } from "../tools/fs.rm";
import { downloadAndExtractArchive } from "../tools/downloadAndExtractArchive";
import { startViteDevServer } from "./startViteDevServer";
export async function command(params: {
buildContext: BuildContext;
@ -379,6 +380,54 @@ export async function command(params: {
const port =
cliCommandOptions.port ?? buildContext.startKeycloakOptions.port ?? DEFAULT_PORT;
const devServerPort = (() => {
const hasSpaUi =
buildContext.implementedThemeTypes.admin.isImplemented ||
(buildContext.implementedThemeTypes.account.isImplemented &&
buildContext.implementedThemeTypes.account.type === "Single-Page");
if (!hasSpaUi) {
return undefined;
}
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 undefined;
}
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 undefined;
}
return port + 1;
})();
if (devServerPort !== undefined) {
startViteDevServer({
buildContext,
port: devServerPort
});
}
const SPACE_PLACEHOLDER = "SPACE_PLACEHOLDER_xKLmdPd";
const dockerRunArgs: string[] = [
@ -386,6 +435,11 @@ export async function command(params: {
`--name${SPACE_PLACEHOLDER}${CONTAINER_NAME}`,
`-e${SPACE_PLACEHOLDER}KEYCLOAK_ADMIN=admin`,
`-e${SPACE_PLACEHOLDER}KEYCLOAK_ADMIN_PASSWORD=admin`,
...(devServerPort === undefined
? []
: [
`-e${SPACE_PLACEHOLDER}${KEYCLOAKIFY_SPA_DEV_SERVER_PORT}=${devServerPort}`
]),
...(buildContext.startKeycloakOptions.dockerExtraArgs.length === 0
? []
: [
@ -592,6 +646,44 @@ 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;
}
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}`);
await waitForDebounce();

View File

@ -0,0 +1,39 @@
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";
export type BuildContextLike = {
projectDirPath: string;
};
assert<BuildContext extends BuildContextLike ? true : false>();
export function startViteDevServer(params: {
buildContext: BuildContextLike;
port: number;
}): void {
const { buildContext, port } = params;
console.log(chalk.blue(`$ npx vite dev --port ${port}`));
const child = child_process.spawn("npx", ["vite", "dev", "--port", `${port}`], {
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));
}

View File

@ -9,6 +9,16 @@ import { getIsPrettierAvailable, runPrettier } from "./tools/runPrettier";
export async function command(params: { buildContext: BuildContext }) {
const { buildContext } = params;
run_copy_assets_to_public: {
if (buildContext.bundler !== "webpack") {
break run_copy_assets_to_public;
}
const { command } = await import("./copy-keycloak-resources-to-public");
await command({ buildContext });
}
const { hasBeenHandled } = maybeDelegateCommandToCustomHandler({
commandName: "update-kc-gen",
buildContext
@ -52,7 +62,12 @@ export async function command(params: { buildContext: BuildContext }) {
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`,
hasAccountTheme && ` | import("./account/KcContext").KcContext`,
hasAdminTheme && ` | import("./admin/KcContext").KcContext`,

View File

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

View File

@ -47,11 +47,25 @@ export function createUseI18n<
function renderHtmlString(params: { htmlString: string; msgKey: string }): JSX.Element {
const { htmlString, msgKey } = params;
const htmlString_sanitized = kcSanitize(htmlString);
const Element = (() => {
if (htmlString_sanitized.includes("<") && htmlString_sanitized.includes(">")) {
for (const tagName of ["div", "section", "article", "ul", "ol"]) {
if (htmlString_sanitized.includes(`<${tagName}`)) {
return "div";
}
}
}
return "span";
})();
return (
<div
<Element
data-kc-msg={msgKey}
dangerouslySetInnerHTML={{
__html: kcSanitize(htmlString)
__html: htmlString_sanitized
}}
/>
);
@ -83,7 +97,7 @@ export function createUseI18n<
})();
add_style: {
const attributeName = "data-kc-i18n";
const attributeName = "data-kc-msg";
// Check if already exists in head
if (document.querySelector(`style[${attributeName}]`) !== null) {
@ -92,7 +106,7 @@ export function createUseI18n<
const styleElement = document.createElement("style");
styleElement.attributes.setNamedItem(document.createAttribute(attributeName));
styleElement.textContent = `[data-kc-msg] { display: inline-block; }`;
styleElement.textContent = `div[${attributeName}] { display: inline-block; }`;
document.head.prepend(styleElement);
}

View File

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

View File

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

View File

@ -212,6 +212,73 @@ export function keycloakify(params: keycloakify.Params) {
force: true
}
);
},
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;