Compare commits

..

6 Commits

Author SHA1 Message Date
70570faed6 Bump version 2024-12-16 18:04:01 +01:00
5d3b7c9a82 Add necessary token claim to access admin in dev mode 2024-12-16 18:04:01 +01:00
95b9b12a3b Try to fix error on windows 2024-12-16 18:04:01 +01:00
0e027055cb Bump version 2024-12-15 19:48:42 +01:00
e47b002535 #744 2024-12-15 19:48:23 +01:00
8dd6dcd1fc Merge pull request #745 from keycloakify/keycloak_config_persistance
Keycloak config persistance
2024-12-15 19:45:52 +01:00
7 changed files with 197 additions and 42 deletions

View File

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

View File

@ -39,7 +39,14 @@ export type ParsedRealmJson = {
"post.logout.redirect.uris"?: string;
};
protocol?: string;
protocolMappers?: unknown[];
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>;
}[];
}[];
};
@ -89,7 +96,18 @@ const zParsedRealmJson = (() => {
})
.optional(),
protocol: z.string().optional(),
protocolMappers: z.array(z.unknown()).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()
})
)
});

View File

@ -985,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": "false",
"access.token.claim": "true",
"claim.name": "allowed-origins",
"jsonType.label": "JSON",
"access.tokenResponse.claim": "false"
}
}
],
"defaultClientScopes": [
@ -1640,13 +1658,13 @@
"config": {
"allowed-protocol-mapper-types": [
"oidc-usermodel-property-mapper",
"saml-user-attribute-mapper",
"saml-user-property-mapper",
"oidc-full-name-mapper",
"oidc-sha256-pairwise-sub-mapper",
"oidc-address-mapper",
"saml-user-attribute-mapper",
"saml-role-list-mapper",
"oidc-sha256-pairwise-sub-mapper",
"oidc-usermodel-attribute-mapper",
"saml-role-list-mapper"
"oidc-full-name-mapper"
]
}
},
@ -1676,14 +1694,14 @@
"subComponents": {},
"config": {
"allowed-protocol-mapper-types": [
"oidc-sha256-pairwise-sub-mapper",
"saml-user-attribute-mapper",
"oidc-usermodel-property-mapper",
"oidc-full-name-mapper",
"saml-role-list-mapper",
"oidc-sha256-pairwise-sub-mapper",
"saml-user-property-mapper",
"oidc-usermodel-attribute-mapper",
"oidc-address-mapper"
"oidc-usermodel-property-mapper",
"saml-role-list-mapper",
"oidc-address-mapper",
"oidc-usermodel-attribute-mapper"
]
}
},

View File

@ -1,6 +1,6 @@
import { CONTAINER_NAME } from "../../shared/constants";
import child_process from "child_process";
import { join as pathJoin } from "path";
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";
@ -20,16 +20,37 @@ export async function dumpContainerConfig(params: {
}): Promise<ParsedRealmJson> {
const { realmName, keycloakMajorVersionNumber, buildContext } = params;
{
// https://github.com/keycloak/keycloak/issues/33800
const doesUseLockedH2Database = keycloakMajorVersionNumber >= 25;
// https://github.com/keycloak/keycloak/issues/33800
const doesUseLockedH2Database = keycloakMajorVersionNumber >= 25;
if (doesUseLockedH2Database) {
child_process.execSync(
`docker exec ${CONTAINER_NAME} sh -c "cp -rp /opt/keycloak/data/h2 /tmp"`
);
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(
@ -56,7 +77,9 @@ export async function dumpContainerConfig(params: {
let output = "";
const onExit = (code: number | null) => {
dCompleted.reject(new Error(`Exited with code ${code}`));
dCompleted.reject(
new Error(`docker exec kc.sh export command failed with code ${code}`)
);
};
child.once("exit", onExit);
@ -96,25 +119,34 @@ export async function dumpContainerConfig(params: {
console.log(output);
process.exit(1);
throw error;
}
}
if (doesUseLockedH2Database) {
const dCompleted = new Deferred<void>();
if (doesUseLockedH2Database) {
const dCompleted = new Deferred<void>();
child_process.exec(
`docker exec ${CONTAINER_NAME} sh -c "rm -rf /tmp/h2"`,
error => {
if (error !== null) {
dCompleted.reject(error);
return;
}
const cmd = `docker exec ${CONTAINER_NAME} sh -c "rm -rf /tmp/h2"`;
dCompleted.resolve();
}
);
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;
}
}
@ -126,8 +158,13 @@ export async function dumpContainerConfig(params: {
{
const dCompleted = new Deferred<void>();
const cmd = `docker cp ${CONTAINER_NAME}:/tmp/${realmName}-realm.json ${pathBasename(targetRealmConfigJsonFilePath_tmp)}`;
child_process.exec(
`docker cp ${CONTAINER_NAME}:/tmp/${realmName}-realm.json ${targetRealmConfigJsonFilePath_tmp}`,
cmd,
{
cwd: pathDirname(targetRealmConfigJsonFilePath_tmp)
},
error => {
if (error !== null) {
dCompleted.reject(error);
@ -138,7 +175,17 @@ export async function dumpContainerConfig(params: {
}
);
await dCompleted.pr;
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({

View File

@ -276,7 +276,7 @@ function editAccountConsoleAndSecurityAdminConsole(params: {
}) {
const { parsedRealmJson } = params;
for (const clientId of ["account-console", "security-admin-console"]) {
for (const clientId of ["account-console", "security-admin-console"] as const) {
const client = parsedRealmJson.clients.find(
client => client.clientId === clientId
);
@ -298,5 +298,68 @@ function editAccountConsoleAndSecurityAdminConsole(params: {
(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": "false",
"access.token.claim": "true",
"claim.name": "allowed-origins",
"jsonType.label": "JSON",
"access.tokenResponse.claim": "false"
};
if (protocolMapper_preexisting !== undefined) {
protocolMapper = protocolMapper_preexisting;
} else {
protocolMapper = {
id: "8fd0d584-7052-4d04-a615-d18a71050873",
name: "allowed-origins",
protocol: "openid-connect",
protocolMapper: "oidc-hardcoded-claim-mapper",
consentRequired: false,
config
};
(client.protocolMappers ??= []).push(protocolMapper);
}
assert(protocolMapper.config !== undefined);
if (config !== protocolMapper.config) {
Object.assign(protocolMapper.config, config);
}
}
}
}

View File

@ -105,11 +105,19 @@ export async function getRealmConfig(params: {
chalk.grey(`Changes detected to the '${realmName}' config, backing up...`)
);
const parsedRealmJson = await dumpContainerConfig({
buildContext,
realmName,
keycloakMajorVersionNumber
});
let parsedRealmJson: ParsedRealmJson;
try {
parsedRealmJson = await dumpContainerConfig({
buildContext,
realmName,
keycloakMajorVersionNumber
});
} catch (error) {
console.log(chalk.red(`Failed to backup '${realmName}' config:`));
return;
}
await writeRealmJsonFile({ parsedRealmJson });

View File

@ -44,6 +44,7 @@ export function startViteDevServer(params: {
//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) {