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/README.md +8 -0
- package/bin/keycloakify/generateFtl/generateFtl.d.ts +1 -1
- package/bin/keycloakify/generateFtl/generateFtl.js +2 -0
- package/bin/keycloakify/generateFtl/generateFtl.js.map +1 -1
- package/bin/tsconfig.tsbuildinfo +1 -1
- package/lib/components/KcApp.js +6 -0
- package/lib/components/KcApp.js.map +1 -1
- package/lib/components/LoginPassword.d.ts +10 -0
- package/lib/components/LoginPassword.js +44 -0
- package/lib/components/LoginPassword.js.map +1 -0
- package/lib/components/LoginUsername.d.ts +10 -0
- package/lib/components/LoginUsername.js +73 -0
- package/lib/components/LoginUsername.js.map +1 -0
- package/lib/getKcContext/KcContextBase.d.ts +52 -1
- package/lib/getKcContext/KcContextBase.js.map +1 -1
- package/lib/getKcContext/kcContextMocks/kcContextMocks.js +8 -0
- package/lib/getKcContext/kcContextMocks/kcContextMocks.js.map +1 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +11 -2
- package/src/bin/keycloakify/generateFtl/generateFtl.ts +2 -0
- package/src/lib/components/KcApp.tsx +6 -0
- package/src/lib/components/LoginPassword.tsx +96 -0
- package/src/lib/components/LoginUsername.tsx +168 -0
- package/src/lib/getKcContext/KcContextBase.ts +55 -0
- package/src/lib/getKcContext/kcContextMocks/kcContextMocks.ts +34 -0
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "keycloakify",
|
3
|
-
"version": "6.
|
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.
|
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",
|