keycloakify 6.4.2 → 6.5.0

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "keycloakify",
3
- "version": "6.4.2",
3
+ "version": "6.5.0",
4
4
  "description": "Keycloak theme generator for Reacts app",
5
5
  "repository": {
6
6
  "type": "git",
@@ -73,6 +73,7 @@
73
73
  "src/lib/components/LoginResetPassword.tsx",
74
74
  "src/lib/components/LoginUpdatePassword.tsx",
75
75
  "src/lib/components/LoginUpdateProfile.tsx",
76
+ "src/lib/components/LoginUsername.tsx",
76
77
  "src/lib/components/LoginVerifyEmail.tsx",
77
78
  "src/lib/components/LogoutConfirm.tsx",
78
79
  "src/lib/components/Register.tsx",
@@ -476,6 +477,9 @@
476
477
  "lib/components/LoginUpdateProfile.d.ts",
477
478
  "lib/components/LoginUpdateProfile.js",
478
479
  "lib/components/LoginUpdateProfile.js.map",
480
+ "lib/components/LoginUsername.d.ts",
481
+ "lib/components/LoginUsername.js",
482
+ "lib/components/LoginUsername.js.map",
479
483
  "lib/components/LoginVerifyEmail.d.ts",
480
484
  "lib/components/LoginVerifyEmail.js",
481
485
  "lib/components/LoginVerifyEmail.js.map",
@@ -1259,6 +1263,7 @@
1259
1263
  "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
1260
1264
  },
1261
1265
  "devDependencies": {
1266
+ "@babel/core": "^7.0.0",
1262
1267
  "@emotion/react": "^11.4.1",
1263
1268
  "@types/memoizee": "^0.4.7",
1264
1269
  "@types/minimist": "^1.2.2",
@@ -1286,7 +1291,7 @@
1286
1291
  "react-markdown": "^5.0.3",
1287
1292
  "scripting-tools": "^0.19.13",
1288
1293
  "tsafe": "^1.1.1",
1289
- "tss-react": "^4.2.0",
1294
+ "tss-react": "^4.3.3",
1290
1295
  "zod": "^3.17.10"
1291
1296
  }
1292
1297
  }
@@ -13,6 +13,7 @@ import { Reflect } from "tsafe/Reflect";
13
13
  // https://github.com/keycloak/keycloak/blob/main/services/src/main/java/org/keycloak/forms/login/freemarker/Templates.java
14
14
  export const pageIds = [
15
15
  "login.ftl",
16
+ "login-username.ftl",
16
17
  "register.ftl",
17
18
  "register-user-profile.ftl",
18
19
  "info.ftl",
@@ -13,6 +13,7 @@ const LoginResetPassword = lazy(() => import("./LoginResetPassword"));
13
13
  const LoginVerifyEmail = lazy(() => import("./LoginVerifyEmail"));
14
14
  const Terms = lazy(() => import("./Terms"));
15
15
  const LoginOtp = lazy(() => import("./LoginOtp"));
16
+ const LoginUsername = lazy(() => import("./LoginUsername"));
16
17
  const LoginUpdatePassword = lazy(() => import("./LoginUpdatePassword"));
17
18
  const LoginUpdateProfile = lazy(() => import("./LoginUpdateProfile"));
18
19
  const LoginIdpLinkConfirm = lazy(() => import("./LoginIdpLinkConfirm"));
@@ -67,6 +68,8 @@ const KcApp = memo(
67
68
  return <Terms {...{ kcContext, ...props }} />;
68
69
  case "login-otp.ftl":
69
70
  return <LoginOtp {...{ kcContext, ...props }} />;
71
+ case "login-username.ftl":
72
+ return <LoginUsername {...{ kcContext, ...props }} />;
70
73
  case "login-update-password.ftl":
71
74
  return <LoginUpdatePassword {...{ kcContext, ...props }} />;
72
75
  case "login-update-profile.ftl":
@@ -0,0 +1,168 @@
1
+ import React, { useState, memo } from "react";
2
+ import Template from "./Template";
3
+ import type { KcProps } from "./KcProps";
4
+ import type { KcContextBase } from "../getKcContext/KcContextBase";
5
+ import { useCssAndCx } from "../tools/useCssAndCx";
6
+ import { useConstCallback } from "powerhooks/useConstCallback";
7
+ import type { FormEventHandler } from "react";
8
+ import type { I18n } from "../i18n";
9
+
10
+ const LoginUsername = memo(
11
+ ({
12
+ kcContext,
13
+ i18n,
14
+ doFetchDefaultThemeResources = true,
15
+ ...props
16
+ }: { kcContext: KcContextBase.LoginUsername; i18n: I18n; doFetchDefaultThemeResources?: boolean } & KcProps) => {
17
+ const { social, realm, url, usernameHidden, login, registrationDisabled } = kcContext;
18
+
19
+ const { msg, msgStr } = i18n;
20
+
21
+ const { cx } = useCssAndCx();
22
+
23
+ const [isLoginButtonDisabled, setIsLoginButtonDisabled] = useState(false);
24
+
25
+ const onSubmit = useConstCallback<FormEventHandler<HTMLFormElement>>(e => {
26
+ e.preventDefault();
27
+
28
+ setIsLoginButtonDisabled(true);
29
+
30
+ const formElement = e.target as HTMLFormElement;
31
+
32
+ //NOTE: Even if we login with email Keycloak expect username and password in
33
+ //the POST request.
34
+ formElement.querySelector("input[name='email']")?.setAttribute("name", "username");
35
+
36
+ formElement.submit();
37
+ });
38
+
39
+ return (
40
+ <Template
41
+ {...{ kcContext, i18n, doFetchDefaultThemeResources, ...props }}
42
+ displayInfo={social.displayInfo}
43
+ displayWide={realm.password && social.providers !== undefined}
44
+ headerNode={msg("doLogIn")}
45
+ formNode={
46
+ <div id="kc-form" className={cx(realm.password && social.providers !== undefined && props.kcContentWrapperClass)}>
47
+ <div
48
+ id="kc-form-wrapper"
49
+ className={cx(
50
+ realm.password && social.providers && [props.kcFormSocialAccountContentClass, props.kcFormSocialAccountClass]
51
+ )}
52
+ >
53
+ {realm.password && (
54
+ <form id="kc-form-login" onSubmit={onSubmit} action={url.loginAction} method="post">
55
+ <div className={cx(props.kcFormGroupClass)}>
56
+ {!usernameHidden &&
57
+ (() => {
58
+ const label = !realm.loginWithEmailAllowed
59
+ ? "username"
60
+ : realm.registrationEmailAsUsername
61
+ ? "email"
62
+ : "usernameOrEmail";
63
+
64
+ const autoCompleteHelper: typeof label = label === "usernameOrEmail" ? "username" : label;
65
+
66
+ return (
67
+ <>
68
+ <label htmlFor={autoCompleteHelper} className={cx(props.kcLabelClass)}>
69
+ {msg(label)}
70
+ </label>
71
+ <input
72
+ tabIndex={1}
73
+ id={autoCompleteHelper}
74
+ className={cx(props.kcInputClass)}
75
+ //NOTE: This is used by Google Chrome auto fill so we use it to tell
76
+ //the browser how to pre fill the form but before submit we put it back
77
+ //to username because it is what keycloak expects.
78
+ name={autoCompleteHelper}
79
+ defaultValue={login.username ?? ""}
80
+ type="text"
81
+ autoFocus={true}
82
+ autoComplete="off"
83
+ />
84
+ </>
85
+ );
86
+ })()}
87
+ </div>
88
+ <div className={cx(props.kcFormGroupClass, props.kcFormSettingClass)}>
89
+ <div id="kc-form-options">
90
+ {realm.rememberMe && !usernameHidden && (
91
+ <div className="checkbox">
92
+ <label>
93
+ <input
94
+ tabIndex={3}
95
+ id="rememberMe"
96
+ name="rememberMe"
97
+ type="checkbox"
98
+ {...(login.rememberMe
99
+ ? {
100
+ "checked": true
101
+ }
102
+ : {})}
103
+ />
104
+ {msg("rememberMe")}
105
+ </label>
106
+ </div>
107
+ )}
108
+ </div>
109
+ </div>
110
+ <div id="kc-form-buttons" className={cx(props.kcFormGroupClass)}>
111
+ <input
112
+ tabIndex={4}
113
+ className={cx(
114
+ props.kcButtonClass,
115
+ props.kcButtonPrimaryClass,
116
+ props.kcButtonBlockClass,
117
+ props.kcButtonLargeClass
118
+ )}
119
+ name="login"
120
+ id="kc-login"
121
+ type="submit"
122
+ value={msgStr("doLogIn")}
123
+ disabled={isLoginButtonDisabled}
124
+ />
125
+ </div>
126
+ </form>
127
+ )}
128
+ </div>
129
+ {realm.password && social.providers !== undefined && (
130
+ <div id="kc-social-providers" className={cx(props.kcFormSocialAccountContentClass, props.kcFormSocialAccountClass)}>
131
+ <ul
132
+ className={cx(
133
+ props.kcFormSocialAccountListClass,
134
+ social.providers.length > 4 && props.kcFormSocialAccountDoubleListClass
135
+ )}
136
+ >
137
+ {social.providers.map(p => (
138
+ <li key={p.providerId} className={cx(props.kcFormSocialAccountListLinkClass)}>
139
+ <a href={p.loginUrl} id={`zocial-${p.alias}`} className={cx("zocial", p.providerId)}>
140
+ <span>{p.displayName}</span>
141
+ </a>
142
+ </li>
143
+ ))}
144
+ </ul>
145
+ </div>
146
+ )}
147
+ </div>
148
+ }
149
+ infoNode={
150
+ realm.password &&
151
+ realm.registrationAllowed &&
152
+ !registrationDisabled && (
153
+ <div id="kc-registration">
154
+ <span>
155
+ {msg("noAccount")}
156
+ <a tabIndex={6} href={url.registrationUrl}>
157
+ {msg("doRegister")}
158
+ </a>
159
+ </span>
160
+ </div>
161
+ )
162
+ }
163
+ />
164
+ );
165
+ }
166
+ );
167
+
168
+ export default LoginUsername;
@@ -19,6 +19,7 @@ export type KcContextBase =
19
19
  | KcContextBase.LoginVerifyEmail
20
20
  | KcContextBase.Terms
21
21
  | KcContextBase.LoginOtp
22
+ | KcContextBase.LoginUsername
22
23
  | KcContextBase.LoginUpdatePassword
23
24
  | KcContextBase.LoginUpdateProfile
24
25
  | KcContextBase.LoginIdpLinkConfirm
@@ -198,6 +199,36 @@ export declare namespace KcContextBase {
198
199
  };
199
200
  };
200
201
 
202
+ export type LoginUsername = Common & {
203
+ pageId: "login-username.ftl";
204
+ url: {
205
+ loginResetCredentialsUrl: string;
206
+ registrationUrl: string;
207
+ };
208
+ realm: {
209
+ loginWithEmailAllowed: boolean;
210
+ rememberMe: boolean;
211
+ password: boolean;
212
+ resetPasswordAllowed: boolean;
213
+ registrationAllowed: boolean;
214
+ };
215
+ registrationDisabled: boolean;
216
+ login: {
217
+ username?: string;
218
+ rememberMe?: boolean;
219
+ };
220
+ usernameHidden?: boolean;
221
+ social: {
222
+ displayInfo: boolean;
223
+ providers?: {
224
+ loginUrl: string;
225
+ alias: string;
226
+ providerId: string;
227
+ displayName: string;
228
+ }[];
229
+ };
230
+ };
231
+
201
232
  export type LoginUpdatePassword = Common & {
202
233
  pageId: "login-update-password.ftl";
203
234
  username: string;
@@ -359,6 +359,27 @@ export const kcContextMocks: KcContextBase[] = [
359
359
  ]
360
360
  }
361
361
  }),
362
+ id<KcContextBase.LoginUsername>({
363
+ ...kcContextCommonMock,
364
+ "pageId": "login-username.ftl",
365
+ "url": loginUrl,
366
+ "realm": {
367
+ ...kcContextCommonMock.realm,
368
+ "loginWithEmailAllowed": true,
369
+ "rememberMe": true,
370
+ "password": true,
371
+ "resetPasswordAllowed": true,
372
+ "registrationAllowed": true
373
+ },
374
+ "social": {
375
+ "displayInfo": true
376
+ },
377
+ "usernameHidden": false,
378
+ "login": {
379
+ "rememberMe": false
380
+ },
381
+ "registrationDisabled": false
382
+ }),
362
383
  id<KcContextBase.LoginUpdatePassword>({
363
384
  ...kcContextCommonMock,
364
385
  "pageId": "login-update-password.ftl",