keycloakify 11.4.5 → 11.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/bin/375.index.js +4089 -0
  2. package/bin/{20.index.js → 490.index.js} +378 -56
  3. package/bin/{36.index.js → 503.index.js} +53 -2
  4. package/bin/{450.index.js → 525.index.js} +2 -4085
  5. package/bin/653.index.js +108 -110
  6. package/bin/682.index.js +1987 -0
  7. package/bin/735.index.js +107 -109
  8. package/bin/921.index.js +1 -1
  9. package/bin/main.js +8 -2
  10. package/bin/shared/constants.d.ts +3 -0
  11. package/bin/shared/constants.js +3 -0
  12. package/bin/shared/constants.js.map +1 -1
  13. package/bin/start-keycloak/getSupportedDockerImageTags.d.ts +8 -0
  14. package/bin/start-keycloak/realmConfig/ParsedRealmJson.d.ts +45 -0
  15. package/bin/start-keycloak/realmConfig/defaultConfig/defaultConfig.d.ts +8 -0
  16. package/bin/start-keycloak/realmConfig/defaultConfig/index.d.ts +1 -0
  17. package/bin/start-keycloak/realmConfig/dumpContainerConfig.d.ts +9 -0
  18. package/bin/start-keycloak/realmConfig/index.d.ts +1 -0
  19. package/bin/start-keycloak/realmConfig/prepareRealmConfig.d.ts +15 -0
  20. package/bin/start-keycloak/realmConfig/realmConfig.d.ts +16 -0
  21. package/package.json +31 -14
  22. package/src/bin/shared/constants.ts +6 -0
  23. package/src/bin/start-keycloak/getSupportedDockerImageTags.ts +230 -0
  24. package/src/bin/start-keycloak/keycloakify-logging-1.0.3.jar +0 -0
  25. package/src/bin/start-keycloak/realmConfig/ParsedRealmJson.ts +136 -0
  26. package/src/bin/start-keycloak/realmConfig/defaultConfig/defaultConfig.ts +75 -0
  27. package/src/bin/start-keycloak/realmConfig/defaultConfig/index.ts +1 -0
  28. package/src/bin/start-keycloak/{myrealm-realm-18.json → realmConfig/defaultConfig/realm-kc-18.json} +123 -60
  29. package/src/bin/start-keycloak/{myrealm-realm-19.json → realmConfig/defaultConfig/realm-kc-19.json} +81 -41
  30. package/src/bin/start-keycloak/{myrealm-realm-20.json → realmConfig/defaultConfig/realm-kc-20.json} +83 -42
  31. package/src/bin/start-keycloak/{myrealm-realm-21.json → realmConfig/defaultConfig/realm-kc-21.json} +58 -17
  32. package/src/bin/start-keycloak/{myrealm-realm-23.json → realmConfig/defaultConfig/realm-kc-23.json} +64 -20
  33. package/src/bin/start-keycloak/{myrealm-realm-24.json → realmConfig/defaultConfig/realm-kc-24.json} +63 -19
  34. package/src/bin/start-keycloak/{myrealm-realm-25.json → realmConfig/defaultConfig/realm-kc-25.json} +75 -20
  35. package/src/bin/start-keycloak/{myrealm-realm-26.json → realmConfig/defaultConfig/realm-kc-26.json} +103 -19
  36. package/src/bin/start-keycloak/realmConfig/dumpContainerConfig.ts +194 -0
  37. package/src/bin/start-keycloak/realmConfig/index.ts +1 -0
  38. package/src/bin/start-keycloak/realmConfig/prepareRealmConfig.ts +365 -0
  39. package/src/bin/start-keycloak/realmConfig/realmConfig.ts +159 -0
  40. package/src/bin/start-keycloak/start-keycloak.ts +160 -184
  41. package/src/bin/start-keycloak/startViteDevServer.ts +1 -0
  42. package/vite-plugin/index.js +6 -0
  43. package/bin/392.index.js +0 -740
  44. package/bin/932.index.js +0 -327
@@ -0,0 +1,194 @@
1
+ import { CONTAINER_NAME } from "../../shared/constants";
2
+ import child_process from "child_process";
3
+ import { join as pathJoin, dirname as pathDirname, basename as pathBasename } from "path";
4
+ import chalk from "chalk";
5
+ import { Deferred } from "evt/tools/Deferred";
6
+ import { assert, is } from "tsafe/assert";
7
+ import type { BuildContext } from "../../shared/buildContext";
8
+ import { type ParsedRealmJson, readRealmJsonFile } from "./ParsedRealmJson";
9
+
10
+ export type BuildContextLike = {
11
+ cacheDirPath: string;
12
+ };
13
+
14
+ assert<BuildContext extends BuildContextLike ? true : false>();
15
+
16
+ export async function dumpContainerConfig(params: {
17
+ realmName: string;
18
+ keycloakMajorVersionNumber: number;
19
+ buildContext: BuildContextLike;
20
+ }): Promise<ParsedRealmJson> {
21
+ const { realmName, keycloakMajorVersionNumber, buildContext } = params;
22
+
23
+ // https://github.com/keycloak/keycloak/issues/33800
24
+ const doesUseLockedH2Database = keycloakMajorVersionNumber >= 25;
25
+
26
+ if (doesUseLockedH2Database) {
27
+ const dCompleted = new Deferred<void>();
28
+
29
+ const cmd = `docker exec ${CONTAINER_NAME} sh -c "cp -rp /opt/keycloak/data/h2 /tmp"`;
30
+
31
+ child_process.exec(cmd, error => {
32
+ if (error !== null) {
33
+ dCompleted.reject(error);
34
+ return;
35
+ }
36
+
37
+ dCompleted.resolve();
38
+ });
39
+
40
+ try {
41
+ await dCompleted.pr;
42
+ } catch (error) {
43
+ assert(is<Error>(error));
44
+
45
+ console.log(chalk.red(`Docker command failed: ${cmd}`));
46
+
47
+ console.log(chalk.red(error.message));
48
+
49
+ throw error;
50
+ }
51
+ }
52
+
53
+ {
54
+ const dCompleted = new Deferred<void>();
55
+
56
+ const child = child_process.spawn(
57
+ "docker",
58
+ [
59
+ ...["exec", CONTAINER_NAME],
60
+ ...["/opt/keycloak/bin/kc.sh", "export"],
61
+ ...["--dir", "/tmp"],
62
+ ...["--realm", realmName],
63
+ ...["--users", "realm_file"],
64
+ ...(!doesUseLockedH2Database
65
+ ? []
66
+ : [
67
+ ...["--db", "dev-file"],
68
+ ...[
69
+ "--db-url",
70
+ "'jdbc:h2:file:/tmp/h2/keycloakdb;NON_KEYWORDS=VALUE'"
71
+ ]
72
+ ])
73
+ ],
74
+ { shell: true }
75
+ );
76
+
77
+ let output = "";
78
+
79
+ const onExit = (code: number | null) => {
80
+ dCompleted.reject(
81
+ new Error(`docker exec kc.sh export command failed with code ${code}`)
82
+ );
83
+ };
84
+
85
+ child.once("exit", onExit);
86
+
87
+ child.stdout.on("data", data => {
88
+ const outputStr = data.toString("utf8");
89
+
90
+ if (outputStr.includes("Export finished successfully")) {
91
+ child.removeListener("exit", onExit);
92
+
93
+ // NOTE: On older Keycloak versions the process keeps running after the export is done.
94
+ const timer = setTimeout(() => {
95
+ child.removeListener("exit", onExit2);
96
+ child.kill();
97
+ dCompleted.resolve();
98
+ }, 1500);
99
+
100
+ const onExit2 = () => {
101
+ clearTimeout(timer);
102
+ dCompleted.resolve();
103
+ };
104
+
105
+ child.once("exit", onExit2);
106
+ }
107
+
108
+ output += outputStr;
109
+ });
110
+
111
+ child.stderr.on("data", data => (output += chalk.red(data.toString("utf8"))));
112
+
113
+ try {
114
+ await dCompleted.pr;
115
+ } catch (error) {
116
+ assert(is<Error>(error));
117
+
118
+ console.log(chalk.red(error.message));
119
+
120
+ console.log(output);
121
+
122
+ throw error;
123
+ }
124
+ }
125
+
126
+ if (doesUseLockedH2Database) {
127
+ const dCompleted = new Deferred<void>();
128
+
129
+ const cmd = `docker exec ${CONTAINER_NAME} sh -c "rm -rf /tmp/h2"`;
130
+
131
+ child_process.exec(cmd, error => {
132
+ if (error !== null) {
133
+ dCompleted.reject(error);
134
+ return;
135
+ }
136
+
137
+ dCompleted.resolve();
138
+ });
139
+
140
+ try {
141
+ await dCompleted.pr;
142
+ } catch (error) {
143
+ assert(is<Error>(error));
144
+
145
+ console.log(chalk.red(`Docker command failed: ${cmd}`));
146
+
147
+ console.log(chalk.red(error.message));
148
+
149
+ throw error;
150
+ }
151
+ }
152
+
153
+ const targetRealmConfigJsonFilePath_tmp = pathJoin(
154
+ buildContext.cacheDirPath,
155
+ "realm.json"
156
+ );
157
+
158
+ {
159
+ const dCompleted = new Deferred<void>();
160
+
161
+ const cmd = `docker cp ${CONTAINER_NAME}:/tmp/${realmName}-realm.json ${pathBasename(targetRealmConfigJsonFilePath_tmp)}`;
162
+
163
+ child_process.exec(
164
+ cmd,
165
+ {
166
+ cwd: pathDirname(targetRealmConfigJsonFilePath_tmp)
167
+ },
168
+ error => {
169
+ if (error !== null) {
170
+ dCompleted.reject(error);
171
+ return;
172
+ }
173
+
174
+ dCompleted.resolve();
175
+ }
176
+ );
177
+
178
+ try {
179
+ await dCompleted.pr;
180
+ } catch (error) {
181
+ assert(is<Error>(error));
182
+
183
+ console.log(chalk.red(`Docker command failed: ${cmd}`));
184
+
185
+ console.log(chalk.red(error.message));
186
+
187
+ throw error;
188
+ }
189
+ }
190
+
191
+ return readRealmJsonFile({
192
+ realmJsonFilePath: targetRealmConfigJsonFilePath_tmp
193
+ });
194
+ }
@@ -0,0 +1 @@
1
+ export * from "./realmConfig";
@@ -0,0 +1,365 @@
1
+ import { assert } from "tsafe/assert";
2
+ import type { ParsedRealmJson } from "./ParsedRealmJson";
3
+ import { getDefaultConfig } from "./defaultConfig";
4
+ import type { BuildContext } from "../../shared/buildContext";
5
+ import { objectKeys } from "tsafe/objectKeys";
6
+ import { TEST_APP_URL } from "../../shared/constants";
7
+ import { sameFactory } from "evt/tools/inDepth/same";
8
+
9
+ export type BuildContextLike = {
10
+ themeNames: BuildContext["themeNames"];
11
+ implementedThemeTypes: BuildContext["implementedThemeTypes"];
12
+ };
13
+
14
+ assert<BuildContext extends BuildContextLike ? true : false>;
15
+
16
+ export function prepareRealmConfig(params: {
17
+ parsedRealmJson: ParsedRealmJson;
18
+ keycloakMajorVersionNumber: number;
19
+ buildContext: BuildContextLike;
20
+ }): {
21
+ realmName: string;
22
+ clientName: string;
23
+ username: string;
24
+ } {
25
+ const { parsedRealmJson, keycloakMajorVersionNumber, buildContext } = params;
26
+
27
+ const { username } = addOrEditTestUser({
28
+ parsedRealmJson,
29
+ keycloakMajorVersionNumber
30
+ });
31
+
32
+ const { clientId } = addOrEditClient({
33
+ parsedRealmJson,
34
+ keycloakMajorVersionNumber
35
+ });
36
+
37
+ editAccountConsoleAndSecurityAdminConsole({ parsedRealmJson });
38
+
39
+ enableCustomThemes({
40
+ parsedRealmJson,
41
+ themeName: buildContext.themeNames[0],
42
+ implementedThemeTypes: buildContext.implementedThemeTypes
43
+ });
44
+
45
+ enable_custom_events_listeners: {
46
+ const name = "keycloakify-logging";
47
+
48
+ if (parsedRealmJson.eventsListeners.includes(name)) {
49
+ break enable_custom_events_listeners;
50
+ }
51
+
52
+ parsedRealmJson.eventsListeners.push(name);
53
+
54
+ parsedRealmJson.eventsListeners.sort();
55
+ }
56
+
57
+ return {
58
+ realmName: parsedRealmJson.realm,
59
+ clientName: clientId,
60
+ username
61
+ };
62
+ }
63
+
64
+ function enableCustomThemes(params: {
65
+ parsedRealmJson: ParsedRealmJson;
66
+ themeName: string;
67
+ implementedThemeTypes: BuildContextLike["implementedThemeTypes"];
68
+ }) {
69
+ const { parsedRealmJson, themeName, implementedThemeTypes } = params;
70
+
71
+ for (const themeType of objectKeys(implementedThemeTypes)) {
72
+ if (!implementedThemeTypes[themeType].isImplemented) {
73
+ continue;
74
+ }
75
+
76
+ parsedRealmJson[`${themeType}Theme` as const] = themeName;
77
+ }
78
+ }
79
+
80
+ function addOrEditTestUser(params: {
81
+ parsedRealmJson: ParsedRealmJson;
82
+ keycloakMajorVersionNumber: number;
83
+ }): { username: string } {
84
+ const { parsedRealmJson, keycloakMajorVersionNumber } = params;
85
+
86
+ const parsedRealmJson_default = getDefaultConfig({ keycloakMajorVersionNumber });
87
+
88
+ const [defaultUser_default] = parsedRealmJson_default.users;
89
+
90
+ assert(defaultUser_default !== undefined);
91
+
92
+ const defaultUser_preexisting = parsedRealmJson.users.find(
93
+ user => user.username === defaultUser_default.username
94
+ );
95
+
96
+ const newUser = structuredClone(
97
+ defaultUser_preexisting ??
98
+ (() => {
99
+ const firstUser = parsedRealmJson.users[0];
100
+
101
+ if (firstUser === undefined) {
102
+ return undefined;
103
+ }
104
+
105
+ const firstUserCopy = structuredClone(firstUser);
106
+
107
+ firstUserCopy.id = defaultUser_default.id;
108
+
109
+ return firstUserCopy;
110
+ })() ??
111
+ defaultUser_default
112
+ );
113
+
114
+ newUser.username = defaultUser_default.username;
115
+ newUser.email = defaultUser_default.email;
116
+
117
+ delete_existing_password_credential_if_any: {
118
+ const i = newUser.credentials.findIndex(
119
+ credential => credential.type === "password"
120
+ );
121
+
122
+ if (i === -1) {
123
+ break delete_existing_password_credential_if_any;
124
+ }
125
+
126
+ newUser.credentials.splice(i, 1);
127
+ }
128
+
129
+ {
130
+ const credential = defaultUser_default.credentials.find(
131
+ credential => credential.type === "password"
132
+ );
133
+
134
+ assert(credential !== undefined);
135
+
136
+ newUser.credentials.push(credential);
137
+ }
138
+
139
+ {
140
+ const nameByClientId = Object.fromEntries(
141
+ parsedRealmJson.clients.map(client => [client.id, client.clientId] as const)
142
+ );
143
+
144
+ const newClientRoles: NonNullable<
145
+ ParsedRealmJson["users"][number]["clientRoles"]
146
+ > = {};
147
+
148
+ for (const clientRole of Object.values(parsedRealmJson.roles.client).flat()) {
149
+ const clientName = nameByClientId[clientRole.containerId];
150
+
151
+ assert(clientName !== undefined);
152
+
153
+ (newClientRoles[clientName] ??= []).push(clientRole.name);
154
+ }
155
+
156
+ const { same: sameSet } = sameFactory({
157
+ takeIntoAccountArraysOrdering: false
158
+ });
159
+
160
+ for (const [clientName, roles] of Object.entries(newClientRoles)) {
161
+ keep_previous_ordering_if_possible: {
162
+ const roles_previous = newUser.clientRoles?.[clientName];
163
+
164
+ if (roles_previous === undefined) {
165
+ break keep_previous_ordering_if_possible;
166
+ }
167
+
168
+ if (!sameSet(roles_previous, roles)) {
169
+ break keep_previous_ordering_if_possible;
170
+ }
171
+
172
+ continue;
173
+ }
174
+
175
+ (newUser.clientRoles ??= {})[clientName] = roles;
176
+ }
177
+ }
178
+
179
+ if (defaultUser_preexisting === undefined) {
180
+ parsedRealmJson.users.push(newUser);
181
+ } else {
182
+ const i = parsedRealmJson.users.indexOf(defaultUser_preexisting);
183
+ assert(i !== -1);
184
+ parsedRealmJson.users[i] = newUser;
185
+ }
186
+
187
+ return { username: newUser.username };
188
+ }
189
+
190
+ function addOrEditClient(params: {
191
+ parsedRealmJson: ParsedRealmJson;
192
+ keycloakMajorVersionNumber: number;
193
+ }): { clientId: string } {
194
+ const { parsedRealmJson, keycloakMajorVersionNumber } = params;
195
+
196
+ const parsedRealmJson_default = getDefaultConfig({ keycloakMajorVersionNumber });
197
+
198
+ const testClient_default = (() => {
199
+ const clients = parsedRealmJson_default.clients.filter(client => {
200
+ return JSON.stringify(client).includes(TEST_APP_URL);
201
+ });
202
+
203
+ assert(clients.length === 1);
204
+
205
+ return clients[0];
206
+ })();
207
+
208
+ const clientIds_builtIn = parsedRealmJson_default.clients
209
+ .map(client => client.clientId)
210
+ .filter(clientId => clientId !== testClient_default.clientId);
211
+
212
+ const testClient_preexisting = (() => {
213
+ const clients = parsedRealmJson.clients
214
+ .filter(client => !clientIds_builtIn.includes(client.clientId))
215
+ .filter(client => client.protocol === "openid-connect");
216
+
217
+ {
218
+ const client = clients.find(
219
+ client => client.clientId === testClient_default.clientId
220
+ );
221
+
222
+ if (client !== undefined) {
223
+ return client;
224
+ }
225
+ }
226
+
227
+ {
228
+ const client = clients.find(
229
+ client =>
230
+ client.redirectUris?.find(redirectUri =>
231
+ redirectUri.startsWith(TEST_APP_URL)
232
+ ) !== undefined
233
+ );
234
+
235
+ if (client !== undefined) {
236
+ return client;
237
+ }
238
+ }
239
+
240
+ const [client] = clients;
241
+
242
+ if (client === undefined) {
243
+ return undefined;
244
+ }
245
+
246
+ return client;
247
+ })();
248
+
249
+ let testClient: typeof testClient_default;
250
+
251
+ if (testClient_preexisting !== undefined) {
252
+ testClient = testClient_preexisting;
253
+ } else {
254
+ testClient = structuredClone(testClient_default);
255
+ delete testClient.protocolMappers;
256
+ parsedRealmJson.clients.push(testClient);
257
+ }
258
+
259
+ testClient.redirectUris = [
260
+ `${TEST_APP_URL}/*`,
261
+ "http://localhost*",
262
+ "http://127.0.0.1*"
263
+ ]
264
+ .sort()
265
+ .reverse();
266
+
267
+ (testClient.attributes ??= {})["post.logout.redirect.uris"] = "+";
268
+
269
+ testClient.webOrigins = ["*"];
270
+
271
+ return { clientId: testClient.clientId };
272
+ }
273
+
274
+ function editAccountConsoleAndSecurityAdminConsole(params: {
275
+ parsedRealmJson: ParsedRealmJson;
276
+ }) {
277
+ const { parsedRealmJson } = params;
278
+
279
+ for (const clientId of ["account-console", "security-admin-console"] as const) {
280
+ const client = parsedRealmJson.clients.find(
281
+ client => client.clientId === clientId
282
+ );
283
+
284
+ assert(client !== undefined);
285
+
286
+ {
287
+ const arr = (client.redirectUris ??= []);
288
+
289
+ for (const value of ["http://localhost*", "http://127.0.0.1*"]) {
290
+ if (!arr.includes(value)) {
291
+ arr.push(value);
292
+ }
293
+ }
294
+
295
+ client.redirectUris?.sort().reverse();
296
+ }
297
+
298
+ (client.attributes ??= {})["post.logout.redirect.uris"] = "+";
299
+
300
+ client.webOrigins = ["*"];
301
+
302
+ admin_specific: {
303
+ if (clientId !== "security-admin-console") {
304
+ break admin_specific;
305
+ }
306
+
307
+ const protocolMapper_preexisting = client.protocolMappers?.find(
308
+ protocolMapper => {
309
+ if (protocolMapper.protocolMapper !== "oidc-hardcoded-claim-mapper") {
310
+ return false;
311
+ }
312
+
313
+ if (protocolMapper.protocol !== "openid-connect") {
314
+ return false;
315
+ }
316
+
317
+ if (protocolMapper.config === undefined) {
318
+ return false;
319
+ }
320
+
321
+ if (protocolMapper.config["claim.name"] !== "allowed-origins") {
322
+ return false;
323
+ }
324
+
325
+ return true;
326
+ }
327
+ );
328
+
329
+ let protocolMapper: NonNullable<typeof protocolMapper_preexisting>;
330
+
331
+ const config = {
332
+ "introspection.token.claim": "true",
333
+ "claim.value": '["*"]',
334
+ "userinfo.token.claim": "true",
335
+ "id.token.claim": "false",
336
+ "lightweight.claim": "false",
337
+ "access.token.claim": "true",
338
+ "claim.name": "allowed-origins",
339
+ "jsonType.label": "JSON",
340
+ "access.tokenResponse.claim": "false"
341
+ };
342
+
343
+ if (protocolMapper_preexisting !== undefined) {
344
+ protocolMapper = protocolMapper_preexisting;
345
+ } else {
346
+ protocolMapper = {
347
+ id: "8fd0d584-7052-4d04-a615-d18a71050873",
348
+ name: "allowed-origins",
349
+ protocol: "openid-connect",
350
+ protocolMapper: "oidc-hardcoded-claim-mapper",
351
+ consentRequired: false,
352
+ config
353
+ };
354
+
355
+ (client.protocolMappers ??= []).push(protocolMapper);
356
+ }
357
+
358
+ assert(protocolMapper.config !== undefined);
359
+
360
+ if (config !== protocolMapper.config) {
361
+ Object.assign(protocolMapper.config, config);
362
+ }
363
+ }
364
+ }
365
+ }