keycloakify 10.0.0-rc.48 → 10.0.0-rc.49

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.
@@ -159,7 +159,8 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
159
159
  );
160
160
 
161
161
  const { keycloakVersion } = await promptKeycloakVersion({
162
- startingFromMajor: 17,
162
+ startingFromMajor: 18,
163
+ excludeMajorVersions: [22],
163
164
  cacheDirPath: buildContext.cacheDirPath
164
165
  });
165
166
 
@@ -231,6 +232,10 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
231
232
 
232
233
  const realmJsonFilePath = await (async () => {
233
234
  if (cliCommandOptions.realmJsonFilePath !== undefined) {
235
+ if (cliCommandOptions.realmJsonFilePath === "none") {
236
+ return undefined;
237
+ }
238
+
234
239
  console.log(
235
240
  chalk.green(
236
241
  `Using realm json file: ${cliCommandOptions.realmJsonFilePath}`
@@ -312,6 +317,10 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
312
317
 
313
318
  await extractThemeResourcesFromJar();
314
319
 
320
+ const jarFilePath_cacheDir = pathJoin(buildContext.cacheDirPath, jarFileBasename);
321
+
322
+ fs.copyFileSync(jarFilePath, jarFilePath_cacheDir);
323
+
315
324
  try {
316
325
  child_process.execSync(`docker rm --force ${containerName}`, {
317
326
  stdio: "ignore"
@@ -332,7 +341,10 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
332
341
  "-v",
333
342
  `${realmJsonFilePath}:/opt/keycloak/data/import/myrealm-realm.json`
334
343
  ]),
335
- ...["-v", `${jarFilePath}:/opt/keycloak/providers/keycloak-theme.jar`],
344
+ ...[
345
+ "-v",
346
+ `${jarFilePath_cacheDir}:/opt/keycloak/providers/keycloak-theme.jar`
347
+ ],
336
348
  ...(keycloakMajorVersionNumber <= 20
337
349
  ? ["-e", "JAVA_OPTS=-Dkeycloak.profile=preview"]
338
350
  : []),
@@ -130,168 +130,137 @@ export function useUserProfileForm(params: ParamsOfUseUserProfileForm): ReturnTy
130
130
  const initialState = useMemo((): internal.State => {
131
131
  // NOTE: We don't use te kcContext.profile.attributes directly because
132
132
  // they don't includes the password and password confirm fields and we want to add them.
133
- // Also, we want to polyfill the attributes for older Keycloak version before User Profile was introduced.
134
- // Finally we want to patch the changes made by Keycloak on the attributes format so we have an homogeneous
135
- // attributes format to work with.
136
- const syntheticAttributes = (() => {
137
- const syntheticAttributes: Attribute[] = [];
138
-
139
- const attributes = (() => {
140
- retrocompat_patch: {
141
- if (
142
- "profile" in kcContext &&
143
- "attributesByName" in kcContext.profile &&
144
- Object.keys(kcContext.profile.attributesByName).length !== 0
145
- ) {
146
- break retrocompat_patch;
147
- }
148
-
149
- if ("register" in kcContext && kcContext.register instanceof Object && "formData" in kcContext.register) {
150
- //NOTE: Handle legacy register.ftl page
151
- return (["firstName", "lastName", "email", "username"] as const)
152
- .filter(name => (name !== "username" ? true : !kcContext.realm.registrationEmailAsUsername))
153
- .map(name =>
154
- id<Attribute>({
155
- name: name,
156
- displayName: id<`\${${MessageKey}}`>(`\${${name}}`),
157
- required: true,
158
- value: (kcContext.register as any).formData[name] ?? "",
159
- html5DataAnnotations: {},
160
- readOnly: false,
161
- validators: {},
162
- annotations: {},
163
- autocomplete: (() => {
164
- switch (name) {
165
- case "email":
166
- return "email";
167
- case "username":
168
- return "username";
169
- default:
170
- return undefined;
171
- }
172
- })()
173
- })
174
- );
175
- }
133
+ // We also want to apply some retro-compatibility and consistency patches.
134
+ const attributes: Attribute[] = (() => {
135
+ mock_user_profile_attributes_for_older_keycloak_versions: {
136
+ if (
137
+ "profile" in kcContext &&
138
+ "attributesByName" in kcContext.profile &&
139
+ Object.keys(kcContext.profile.attributesByName).length !== 0
140
+ ) {
141
+ break mock_user_profile_attributes_for_older_keycloak_versions;
142
+ }
176
143
 
177
- if ("user" in kcContext && kcContext.user instanceof Object) {
178
- //NOTE: Handle legacy login-update-profile.ftl
179
- return (["username", "email", "firstName", "lastName"] as const)
180
- .filter(name => (name !== "username" ? true : (kcContext.user as any).editUsernameAllowed))
181
- .map(name =>
182
- id<Attribute>({
183
- name: name,
184
- displayName: id<`\${${MessageKey}}`>(`\${${name}}`),
185
- required: true,
186
- value: (kcContext as any).user[name] ?? "",
187
- html5DataAnnotations: {},
188
- readOnly: false,
189
- validators: {},
190
- annotations: {},
191
- autocomplete: (() => {
192
- switch (name) {
193
- case "email":
194
- return "email";
195
- case "username":
196
- return "username";
197
- default:
198
- return undefined;
199
- }
200
- })()
201
- })
202
- );
203
- }
144
+ if ("register" in kcContext && kcContext.register instanceof Object && "formData" in kcContext.register) {
145
+ //NOTE: Handle legacy register.ftl page
146
+ return (["firstName", "lastName", "email", "username"] as const)
147
+ .filter(name => (name !== "username" ? true : !kcContext.realm.registrationEmailAsUsername))
148
+ .map(name =>
149
+ id<Attribute>({
150
+ name: name,
151
+ displayName: id<`\${${MessageKey}}`>(`\${${name}}`),
152
+ required: true,
153
+ value: (kcContext.register as any).formData[name] ?? "",
154
+ html5DataAnnotations: {},
155
+ readOnly: false,
156
+ validators: {},
157
+ annotations: {},
158
+ autocomplete: (() => {
159
+ switch (name) {
160
+ case "email":
161
+ return "email";
162
+ case "username":
163
+ return "username";
164
+ default:
165
+ return undefined;
166
+ }
167
+ })()
168
+ })
169
+ );
170
+ }
204
171
 
205
- if ("email" in kcContext && kcContext.email instanceof Object) {
206
- //NOTE: Handle legacy update-email.ftl
207
- return [
172
+ if ("user" in kcContext && kcContext.user instanceof Object) {
173
+ //NOTE: Handle legacy login-update-profile.ftl
174
+ return (["username", "email", "firstName", "lastName"] as const)
175
+ .filter(name => (name !== "username" ? true : (kcContext.user as any).editUsernameAllowed))
176
+ .map(name =>
208
177
  id<Attribute>({
209
- name: "email",
210
- displayName: id<`\${${MessageKey}}`>(`\${email}`),
178
+ name: name,
179
+ displayName: id<`\${${MessageKey}}`>(`\${${name}}`),
211
180
  required: true,
212
- value: (kcContext.email as any).value ?? "",
181
+ value: (kcContext as any).user[name] ?? "",
213
182
  html5DataAnnotations: {},
214
183
  readOnly: false,
215
184
  validators: {},
216
185
  annotations: {},
217
- autocomplete: "email"
186
+ autocomplete: (() => {
187
+ switch (name) {
188
+ case "email":
189
+ return "email";
190
+ case "username":
191
+ return "username";
192
+ default:
193
+ return undefined;
194
+ }
195
+ })()
218
196
  })
219
- ];
220
- }
197
+ );
198
+ }
221
199
 
222
- assert(false, "Unable to mock user profile from the current kcContext");
200
+ if ("email" in kcContext && kcContext.email instanceof Object) {
201
+ //NOTE: Handle legacy update-email.ftl
202
+ return [
203
+ id<Attribute>({
204
+ name: "email",
205
+ displayName: id<`\${${MessageKey}}`>(`\${email}`),
206
+ required: true,
207
+ value: (kcContext.email as any).value ?? "",
208
+ html5DataAnnotations: {},
209
+ readOnly: false,
210
+ validators: {},
211
+ annotations: {},
212
+ autocomplete: "email"
213
+ })
214
+ ];
223
215
  }
224
216
 
225
- return Object.values(kcContext.profile.attributesByName).map(attribute_pre_group_patch => {
226
- if (typeof attribute_pre_group_patch.group === "string" && attribute_pre_group_patch.group !== "") {
227
- const { group, groupDisplayHeader, groupDisplayDescription, groupAnnotations, ...rest } =
228
- attribute_pre_group_patch as Attribute & {
229
- group: string;
230
- groupDisplayHeader?: string;
231
- groupDisplayDescription?: string;
232
- groupAnnotations: Record<string, string>;
233
- };
234
-
235
- return id<Attribute>({
236
- ...rest,
237
- group: {
238
- name: group,
239
- displayHeader: groupDisplayHeader,
240
- displayDescription: groupDisplayDescription,
241
- html5DataAnnotations: {}
242
- }
243
- });
244
- }
217
+ assert(false, "Unable to mock user profile from the current kcContext");
218
+ }
245
219
 
246
- return attribute_pre_group_patch;
247
- });
248
- })();
220
+ return Object.values(kcContext.profile.attributesByName).map(structuredCloneButFunctions);
221
+ })();
249
222
 
250
- for (const attribute of attributes) {
251
- syntheticAttributes.push(structuredCloneButFunctions(attribute));
223
+ // Retro-compatibility and consistency patches
224
+ attributes.forEach(attribute => {
225
+ patch_legacy_group: {
226
+ if (typeof attribute.group !== "string") {
227
+ break patch_legacy_group;
228
+ }
252
229
 
253
- add_password_and_password_confirm: {
254
- if (!kcContext.passwordRequired) {
255
- break add_password_and_password_confirm;
256
- }
230
+ const { group, groupDisplayHeader, groupDisplayDescription /*, groupAnnotations*/ } = attribute as Attribute & {
231
+ group: string;
232
+ groupDisplayHeader?: string;
233
+ groupDisplayDescription?: string;
234
+ groupAnnotations: Record<string, string>;
235
+ };
257
236
 
258
- if (attribute.name !== (kcContext.realm.registrationEmailAsUsername ? "email" : "username")) {
259
- // NOTE: We want to add password and password-confirm after the field that identifies the user.
260
- // It's either email or username.
261
- break add_password_and_password_confirm;
262
- }
237
+ delete attribute.group;
238
+ // @ts-expect-error
239
+ delete attribute.groupDisplayHeader;
240
+ // @ts-expect-error
241
+ delete attribute.groupDisplayDescription;
242
+ // @ts-expect-error
243
+ delete attribute.groupAnnotations;
263
244
 
264
- syntheticAttributes.push(
265
- {
266
- name: "password",
267
- displayName: id<`\${${MessageKey}}`>("${password}"),
268
- required: true,
269
- readOnly: false,
270
- validators: {},
271
- annotations: {},
272
- autocomplete: "new-password",
273
- html5DataAnnotations: {},
274
- // NOTE: Compat with Keycloak version prior to 24
275
- ...({ groupAnnotations: {} } as {})
276
- },
277
- {
278
- name: "password-confirm",
279
- displayName: id<`\${${MessageKey}}`>("${passwordConfirm}"),
280
- required: true,
281
- readOnly: false,
282
- validators: {},
283
- annotations: {},
284
- html5DataAnnotations: {},
285
- autocomplete: "new-password",
286
- // NOTE: Compat with Keycloak version prior to 24
287
- ...({ groupAnnotations: {} } as {})
288
- }
289
- );
245
+ if (group === "") {
246
+ break patch_legacy_group;
290
247
  }
248
+
249
+ attribute.group = {
250
+ name: group,
251
+ displayHeader: groupDisplayHeader,
252
+ displayDescription: groupDisplayDescription,
253
+ html5DataAnnotations: {}
254
+ };
255
+ }
256
+
257
+ // Attributes with options rendered by default as select inputs
258
+ if (attribute.validators.options !== undefined && attribute.annotations.inputType === undefined) {
259
+ attribute.annotations.inputType = "select";
291
260
  }
292
261
 
293
- // NOTE: Consistency patch
294
- syntheticAttributes.forEach(attribute => {
262
+ // Consistency patch on values/value property
263
+ {
295
264
  if (getIsMultivaluedSingleField({ attribute })) {
296
265
  attribute.multivalued = true;
297
266
  }
@@ -303,65 +272,98 @@ export function useUserProfileForm(params: ParamsOfUseUserProfileForm): ReturnTy
303
272
  attribute.value ??= attribute.values?.[0];
304
273
  delete attribute.values;
305
274
  }
306
- });
275
+ }
276
+ });
307
277
 
308
- return syntheticAttributes;
309
- })();
278
+ add_password_and_password_confirm: {
279
+ if (!kcContext.passwordRequired) {
280
+ break add_password_and_password_confirm;
281
+ }
310
282
 
311
- const initialFormFieldState = (() => {
312
- const out: {
313
- attribute: Attribute;
314
- valueOrValues: string | string[];
315
- }[] = [];
283
+ attributes.forEach((attribute, i) => {
284
+ if (attribute.name !== (kcContext.realm.registrationEmailAsUsername ? "email" : "username")) {
285
+ // NOTE: We want to add password and password-confirm after the field that identifies the user.
286
+ // It's either email or username.
287
+ return;
288
+ }
316
289
 
317
- for (const attribute of syntheticAttributes) {
318
- handle_multi_valued_attribute: {
319
- if (!attribute.multivalued) {
320
- break handle_multi_valued_attribute;
290
+ attributes.splice(
291
+ i + 1,
292
+ 0,
293
+ {
294
+ name: "password",
295
+ displayName: id<`\${${MessageKey}}`>("${password}"),
296
+ required: true,
297
+ readOnly: false,
298
+ validators: {},
299
+ annotations: {},
300
+ autocomplete: "new-password",
301
+ html5DataAnnotations: {}
302
+ },
303
+ {
304
+ name: "password-confirm",
305
+ displayName: id<`\${${MessageKey}}`>("${passwordConfirm}"),
306
+ required: true,
307
+ readOnly: false,
308
+ validators: {},
309
+ annotations: {},
310
+ html5DataAnnotations: {},
311
+ autocomplete: "new-password"
321
312
  }
313
+ );
314
+ });
315
+ }
322
316
 
323
- const values = attribute.values?.length ? attribute.values : [""];
317
+ const initialFormFieldState: {
318
+ attribute: Attribute;
319
+ valueOrValues: string | string[];
320
+ }[] = [];
324
321
 
325
- apply_validator_min_range: {
326
- if (getIsMultivaluedSingleField({ attribute })) {
327
- break apply_validator_min_range;
328
- }
322
+ for (const attribute of attributes) {
323
+ handle_multi_valued_attribute: {
324
+ if (!attribute.multivalued) {
325
+ break handle_multi_valued_attribute;
326
+ }
329
327
 
330
- const validator = attribute.validators.multivalued;
328
+ const values = attribute.values?.length ? attribute.values : [""];
331
329
 
332
- if (validator === undefined) {
333
- break apply_validator_min_range;
334
- }
330
+ apply_validator_min_range: {
331
+ if (getIsMultivaluedSingleField({ attribute })) {
332
+ break apply_validator_min_range;
333
+ }
335
334
 
336
- const { min: minStr } = validator;
335
+ const validator = attribute.validators.multivalued;
337
336
 
338
- if (!minStr) {
339
- break apply_validator_min_range;
340
- }
337
+ if (validator === undefined) {
338
+ break apply_validator_min_range;
339
+ }
341
340
 
342
- const min = parseInt(`${minStr}`);
341
+ const { min: minStr } = validator;
343
342
 
344
- for (let index = values.length; index < min; index++) {
345
- values.push("");
346
- }
343
+ if (!minStr) {
344
+ break apply_validator_min_range;
347
345
  }
348
346
 
349
- out.push({
350
- attribute,
351
- valueOrValues: values
352
- });
347
+ const min = parseInt(`${minStr}`);
353
348
 
354
- continue;
349
+ for (let index = values.length; index < min; index++) {
350
+ values.push("");
351
+ }
355
352
  }
356
353
 
357
- out.push({
354
+ initialFormFieldState.push({
358
355
  attribute,
359
- valueOrValues: attribute.value ?? ""
356
+ valueOrValues: values
360
357
  });
358
+
359
+ continue;
361
360
  }
362
361
 
363
- return out;
364
- })();
362
+ initialFormFieldState.push({
363
+ attribute,
364
+ valueOrValues: attribute.value ?? ""
365
+ });
366
+ }
365
367
 
366
368
  const initialState: internal.State = {
367
369
  formFieldStates: initialFormFieldState.map(({ attribute, valueOrValues }) => ({
@@ -1,4 +1,4 @@
1
- export const formatNumber = (input: string, format: string): string => {
1
+ export const formatNumber = (input: string, format: string) => {
2
2
  if (!input) {
3
3
  return "";
4
4
  }
@@ -20,7 +20,8 @@ export const formatNumber = (input: string, format: string): string => {
20
20
  let rawValue = input.replace(/\D+/g, "");
21
21
 
22
22
  // make sure the value is a number
23
- if (`${parseInt(rawValue)}` !== rawValue) {
23
+ // @ts-expect-error: It's Keycloak's code, we trust it.
24
+ if (parseInt(rawValue) != rawValue) {
24
25
  return "";
25
26
  }
26
27
 
@@ -12,7 +12,8 @@ export const formatNumber = (input, format) => {
12
12
  // keep only digits
13
13
  let rawValue = input.replace(/\D+/g, "");
14
14
  // make sure the value is a number
15
- if (`${parseInt(rawValue)}` !== rawValue) {
15
+ // @ts-expect-error: It's Keycloak's code, we trust it.
16
+ if (parseInt(rawValue) != rawValue) {
16
17
  return "";
17
18
  }
18
19
  // make sure the number of digits does not exceed the maximum size
@@ -1 +1 @@
1
- {"version":3,"file":"formatNumber.js","sourceRoot":"","sources":["../src/tools/formatNumber.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,KAAa,EAAE,MAAc,EAAU,EAAE;IAClE,IAAI,CAAC,KAAK,EAAE;QACR,OAAO,EAAE,CAAC;KACb;IAED,4EAA4E;IAC5E,MAAM,YAAY,GAAG,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IAE5C,IAAI,CAAC,YAAY,EAAE;QACf,OAAO,EAAE,CAAC;KACb;IAED,0FAA0F;IAC1F,MAAM,OAAO,GAAG,YAAY,CAAC,MAAM,CAC/B,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC,KAAK,GAAG,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,EACnE,CAAC,CACJ,CAAC;IAEF,mBAAmB;IACnB,IAAI,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAEzC,kCAAkC;IAClC,IAAI,GAAG,QAAQ,CAAC,QAAQ,CAAC,EAAE,KAAK,QAAQ,EAAE;QACtC,OAAO,EAAE,CAAC;KACb;IAED,kEAAkE;IAClE,IAAI,QAAQ,CAAC,MAAM,GAAG,OAAO,EAAE;QAC3B,QAAQ,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;KAC7C;IAED,kEAAkE;IAClE,MAAM,SAAS,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC,MAAM,GAAG,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IAEhF,mFAAmF;IACnF,IAAI,MAAM,GAAG,IAAI,MAAM,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAElD,oDAAoD;IACpD,IAAI,CAAC,MAAM,EAAE;QACT,OAAO,KAAK,CAAC;KAChB;IAED,IAAI,MAAM,GAAG,MAAM,CAAC;IAEpB,oEAAoE;IACpE,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,YAAY,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;QAC1C,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;KAC3D;IAED,OAAO,MAAM,CAAC;AAClB,CAAC,CAAC"}
1
+ {"version":3,"file":"formatNumber.js","sourceRoot":"","sources":["../src/tools/formatNumber.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,KAAa,EAAE,MAAc,EAAE,EAAE;IAC1D,IAAI,CAAC,KAAK,EAAE;QACR,OAAO,EAAE,CAAC;KACb;IAED,4EAA4E;IAC5E,MAAM,YAAY,GAAG,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IAE5C,IAAI,CAAC,YAAY,EAAE;QACf,OAAO,EAAE,CAAC;KACb;IAED,0FAA0F;IAC1F,MAAM,OAAO,GAAG,YAAY,CAAC,MAAM,CAC/B,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC,KAAK,GAAG,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,EACnE,CAAC,CACJ,CAAC;IAEF,mBAAmB;IACnB,IAAI,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAEzC,kCAAkC;IAClC,uDAAuD;IACvD,IAAI,QAAQ,CAAC,QAAQ,CAAC,IAAI,QAAQ,EAAE;QAChC,OAAO,EAAE,CAAC;KACb;IAED,kEAAkE;IAClE,IAAI,QAAQ,CAAC,MAAM,GAAG,OAAO,EAAE;QAC3B,QAAQ,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;KAC7C;IAED,kEAAkE;IAClE,MAAM,SAAS,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC,MAAM,GAAG,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IAEhF,mFAAmF;IACnF,IAAI,MAAM,GAAG,IAAI,MAAM,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAElD,oDAAoD;IACpD,IAAI,CAAC,MAAM,EAAE;QACT,OAAO,KAAK,CAAC;KAChB;IAED,IAAI,MAAM,GAAG,MAAM,CAAC;IAEpB,oEAAoE;IACpE,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,YAAY,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;QAC1C,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;KAC3D;IAED,OAAO,MAAM,CAAC;AAClB,CAAC,CAAC"}