keycloakify 6.4.3 → 6.6.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "keycloakify",
3
- "version": "6.4.3",
3
+ "version": "6.6.1",
4
4
  "description": "Keycloak theme generator for Reacts app",
5
5
  "repository": {
6
6
  "type": "git",
@@ -70,9 +70,11 @@
70
70
  "src/lib/components/LoginIdpLinkEmail.tsx",
71
71
  "src/lib/components/LoginOtp.tsx",
72
72
  "src/lib/components/LoginPageExpired.tsx",
73
+ "src/lib/components/LoginPassword.tsx",
73
74
  "src/lib/components/LoginResetPassword.tsx",
74
75
  "src/lib/components/LoginUpdatePassword.tsx",
75
76
  "src/lib/components/LoginUpdateProfile.tsx",
77
+ "src/lib/components/LoginUsername.tsx",
76
78
  "src/lib/components/LoginVerifyEmail.tsx",
77
79
  "src/lib/components/LogoutConfirm.tsx",
78
80
  "src/lib/components/Register.tsx",
@@ -467,6 +469,9 @@
467
469
  "lib/components/LoginPageExpired.d.ts",
468
470
  "lib/components/LoginPageExpired.js",
469
471
  "lib/components/LoginPageExpired.js.map",
472
+ "lib/components/LoginPassword.d.ts",
473
+ "lib/components/LoginPassword.js",
474
+ "lib/components/LoginPassword.js.map",
470
475
  "lib/components/LoginResetPassword.d.ts",
471
476
  "lib/components/LoginResetPassword.js",
472
477
  "lib/components/LoginResetPassword.js.map",
@@ -476,6 +481,9 @@
476
481
  "lib/components/LoginUpdateProfile.d.ts",
477
482
  "lib/components/LoginUpdateProfile.js",
478
483
  "lib/components/LoginUpdateProfile.js.map",
484
+ "lib/components/LoginUsername.d.ts",
485
+ "lib/components/LoginUsername.js",
486
+ "lib/components/LoginUsername.js.map",
479
487
  "lib/components/LoginVerifyEmail.d.ts",
480
488
  "lib/components/LoginVerifyEmail.js",
481
489
  "lib/components/LoginVerifyEmail.js.map",
@@ -1259,6 +1267,7 @@
1259
1267
  "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
1260
1268
  },
1261
1269
  "devDependencies": {
1270
+ "@babel/core": "^7.0.0",
1262
1271
  "@emotion/react": "^11.4.1",
1263
1272
  "@types/memoizee": "^0.4.7",
1264
1273
  "@types/minimist": "^1.2.2",
@@ -1286,7 +1295,7 @@
1286
1295
  "react-markdown": "^5.0.3",
1287
1296
  "scripting-tools": "^0.19.13",
1288
1297
  "tsafe": "^1.1.1",
1289
- "tss-react": "^4.3.3",
1298
+ "tss-react": "^4.3.4",
1290
1299
  "zod": "^3.17.10"
1291
1300
  }
1292
1301
  }
@@ -13,6 +13,8 @@ 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",
17
+ "login-password.ftl",
16
18
  "register.ftl",
17
19
  "register-user-profile.ftl",
18
20
  "info.ftl",
@@ -13,6 +13,8 @@ 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 LoginPassword = lazy(() => import("./LoginPassword"));
17
+ const LoginUsername = lazy(() => import("./LoginUsername"));
16
18
  const LoginUpdatePassword = lazy(() => import("./LoginUpdatePassword"));
17
19
  const LoginUpdateProfile = lazy(() => import("./LoginUpdateProfile"));
18
20
  const LoginIdpLinkConfirm = lazy(() => import("./LoginIdpLinkConfirm"));
@@ -67,6 +69,10 @@ const KcApp = memo(
67
69
  return <Terms {...{ kcContext, ...props }} />;
68
70
  case "login-otp.ftl":
69
71
  return <LoginOtp {...{ kcContext, ...props }} />;
72
+ case "login-username.ftl":
73
+ return <LoginUsername {...{ kcContext, ...props }} />;
74
+ case "login-password.ftl":
75
+ return <LoginPassword {...{ kcContext, ...props }} />;
70
76
  case "login-update-password.ftl":
71
77
  return <LoginUpdatePassword {...{ kcContext, ...props }} />;
72
78
  case "login-update-profile.ftl":
@@ -0,0 +1,96 @@
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 LoginPassword = memo(
11
+ ({
12
+ kcContext,
13
+ i18n,
14
+ doFetchDefaultThemeResources = true,
15
+ ...props
16
+ }: { kcContext: KcContextBase.LoginPassword; i18n: I18n; doFetchDefaultThemeResources?: boolean } & KcProps) => {
17
+ const { realm, url, login } = 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
+ formElement.submit();
33
+ });
34
+
35
+ return (
36
+ <Template
37
+ {...{ kcContext, i18n, doFetchDefaultThemeResources, ...props }}
38
+ headerNode={msg("doLogIn")}
39
+ formNode={
40
+ <div id="kc-form">
41
+ <div id="kc-form-wrapper">
42
+ <form id="kc-form-login" onSubmit={onSubmit} action={url.loginAction} method="post">
43
+ <div className={cx(props.kcFormGroupClass)}>
44
+ <hr />
45
+ <label htmlFor="password" className={cx(props.kcLabelClass)}>
46
+ {msg("password")}
47
+ </label>
48
+ <input
49
+ tabIndex={2}
50
+ id="password"
51
+ className={cx(props.kcInputClass)}
52
+ name="password"
53
+ type="password"
54
+ autoFocus={true}
55
+ autoComplete="on"
56
+ defaultValue={login.password ?? ""}
57
+ />
58
+ </div>
59
+ <div className={cx(props.kcFormGroupClass, props.kcFormSettingClass)}>
60
+ <div id="kc-form-options" />
61
+ <div className={cx(props.kcFormOptionsWrapperClass)}>
62
+ {realm.resetPasswordAllowed && (
63
+ <span>
64
+ <a tabIndex={5} href={url.loginResetCredentialsUrl}>
65
+ {msg("doForgotPassword")}
66
+ </a>
67
+ </span>
68
+ )}
69
+ </div>
70
+ </div>
71
+ <div id="kc-form-buttons" className={cx(props.kcFormGroupClass)}>
72
+ <input
73
+ tabIndex={4}
74
+ className={cx(
75
+ props.kcButtonClass,
76
+ props.kcButtonPrimaryClass,
77
+ props.kcButtonBlockClass,
78
+ props.kcButtonLargeClass
79
+ )}
80
+ name="login"
81
+ id="kc-login"
82
+ type="submit"
83
+ value={msgStr("doLogIn")}
84
+ disabled={isLoginButtonDisabled}
85
+ />
86
+ </div>
87
+ </form>
88
+ </div>
89
+ </div>
90
+ }
91
+ />
92
+ );
93
+ }
94
+ );
95
+
96
+ export default LoginPassword;
@@ -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,8 @@ export type KcContextBase =
19
19
  | KcContextBase.LoginVerifyEmail
20
20
  | KcContextBase.Terms
21
21
  | KcContextBase.LoginOtp
22
+ | KcContextBase.LoginUsername
23
+ | KcContextBase.LoginPassword
22
24
  | KcContextBase.LoginUpdatePassword
23
25
  | KcContextBase.LoginUpdateProfile
24
26
  | KcContextBase.LoginIdpLinkConfirm
@@ -198,6 +200,59 @@ export declare namespace KcContextBase {
198
200
  };
199
201
  };
200
202
 
203
+ export type LoginUsername = Common & {
204
+ pageId: "login-username.ftl";
205
+ url: {
206
+ loginResetCredentialsUrl: string;
207
+ registrationUrl: string;
208
+ };
209
+ realm: {
210
+ loginWithEmailAllowed: boolean;
211
+ rememberMe: boolean;
212
+ password: boolean;
213
+ resetPasswordAllowed: boolean;
214
+ registrationAllowed: boolean;
215
+ };
216
+ registrationDisabled: boolean;
217
+ login: {
218
+ username?: string;
219
+ rememberMe?: boolean;
220
+ };
221
+ usernameHidden?: boolean;
222
+ social: {
223
+ displayInfo: boolean;
224
+ providers?: {
225
+ loginUrl: string;
226
+ alias: string;
227
+ providerId: string;
228
+ displayName: string;
229
+ }[];
230
+ };
231
+ };
232
+
233
+ export type LoginPassword = Common & {
234
+ pageId: "login-password.ftl";
235
+ url: {
236
+ loginResetCredentialsUrl: string;
237
+ registrationUrl: string;
238
+ };
239
+ realm: {
240
+ resetPasswordAllowed: boolean;
241
+ };
242
+ auth?: {
243
+ showUsername?: boolean;
244
+ showResetCredentials?: boolean;
245
+ showTryAnotherWayLink?: boolean;
246
+ attemptedUsername?: string;
247
+ };
248
+ social: {
249
+ displayInfo: boolean;
250
+ };
251
+ login: {
252
+ password?: string;
253
+ };
254
+ };
255
+
201
256
  export type LoginUpdatePassword = Common & {
202
257
  pageId: "login-update-password.ftl";
203
258
  username: string;
@@ -359,6 +359,40 @@ 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
+ }),
383
+ id<KcContextBase.LoginPassword>({
384
+ ...kcContextCommonMock,
385
+ "pageId": "login-password.ftl",
386
+ "url": loginUrl,
387
+ "realm": {
388
+ ...kcContextCommonMock.realm,
389
+ "resetPasswordAllowed": true
390
+ },
391
+ "social": {
392
+ "displayInfo": false
393
+ },
394
+ "login": {}
395
+ }),
362
396
  id<KcContextBase.LoginUpdatePassword>({
363
397
  ...kcContextCommonMock,
364
398
  "pageId": "login-update-password.ftl",