#191: Enable to only customize the Template

This commit is contained in:
garronej
2022-10-13 11:58:31 +02:00
parent 3c96d2ea42
commit 688455d0aa
22 changed files with 1856 additions and 1779 deletions

View File

@ -1,40 +1,43 @@
import React, { memo } from "react"; import React, { memo } from "react";
import Template from "./Template"; import DefaultTemplate from "./Template";
import type { TemplateProps } from "./Template";
import type { KcProps } from "./KcProps"; import type { KcProps } from "./KcProps";
import type { KcContextBase } from "../getKcContext/KcContextBase"; import type { KcContextBase } from "../getKcContext/KcContextBase";
import type { I18n } from "../i18n"; import type { I18n } from "../i18n";
const Error = memo( export type ErrorProps = KcProps & {
({ kcContext: KcContextBase.Error;
kcContext, i18n: I18n;
i18n, doFetchDefaultThemeResources?: boolean;
doFetchDefaultThemeResources = true, Template?: (props: TemplateProps) => JSX.Element | null;
...props };
}: { kcContext: KcContextBase.Error; i18n: I18n; doFetchDefaultThemeResources?: boolean } & KcProps) => {
const { message, client } = kcContext;
const { msg } = i18n; const Error = memo((props: ErrorProps) => {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template = DefaultTemplate, ...kcProps } = props;
return ( const { message, client } = kcContext;
<Template
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...props }} const { msg } = i18n;
displayMessage={false}
headerNode={msg("errorTitle")} return (
formNode={ <Template
<div id="kc-error-message"> {...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
<p className="instruction">{message.summary}</p> displayMessage={false}
{client !== undefined && client.baseUrl !== undefined && ( headerNode={msg("errorTitle")}
<p> formNode={
<a id="backToApplication" href={client.baseUrl}> <div id="kc-error-message">
{msg("backToApplication")} <p className="instruction">{message.summary}</p>
</a> {client !== undefined && client.baseUrl !== undefined && (
</p> <p>
)} <a id="backToApplication" href={client.baseUrl}>
</div> {msg("backToApplication")}
} </a>
/> </p>
); )}
} </div>
); }
/>
);
});
export default Error; export default Error;

View File

@ -1,57 +1,60 @@
import React, { useState, memo } from "react"; import React, { useState, memo } from "react";
import Template from "./Template"; import DefaultTemplate from "./Template";
import type { TemplateProps } from "./Template";
import type { KcProps } from "./KcProps"; import type { KcProps } from "./KcProps";
import type { KcContextBase } from "../getKcContext/KcContextBase"; import type { KcContextBase } from "../getKcContext/KcContextBase";
import { useCssAndCx } from "../tools/useCssAndCx"; import { useCssAndCx } from "../tools/useCssAndCx";
import type { I18n } from "../i18n"; import type { I18n } from "../i18n";
import { UserProfileFormFields } from "./shared/UserProfileCommons"; import { UserProfileFormFields } from "./shared/UserProfileCommons";
const IdpReviewUserProfile = memo( export type IdpReviewUserProfileProps = KcProps & {
({ kcContext: KcContextBase.IdpReviewUserProfile;
kcContext, i18n: I18n;
i18n, doFetchDefaultThemeResources?: boolean;
doFetchDefaultThemeResources = true, Template?: (props: TemplateProps) => JSX.Element | null;
...props };
}: { kcContext: KcContextBase.IdpReviewUserProfile; i18n: I18n; doFetchDefaultThemeResources?: boolean } & KcProps) => {
const { cx } = useCssAndCx();
const { msg, msgStr } = i18n; const IdpReviewUserProfile = memo((props: IdpReviewUserProfileProps) => {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template = DefaultTemplate, ...kcProps } = props;
const { url } = kcContext; const { cx } = useCssAndCx();
const [isFomSubmittable, setIsFomSubmittable] = useState(false); const { msg, msgStr } = i18n;
return ( const { url } = kcContext;
<Template
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...props }}
headerNode={msg("loginIdpReviewProfileTitle")}
formNode={
<form id="kc-idp-review-profile-form" className={cx(props.kcFormClass)} action={url.loginAction} method="post">
<UserProfileFormFields kcContext={kcContext} onIsFormSubmittableValueChange={setIsFomSubmittable} i18n={i18n} {...props} />
<div className={cx(props.kcFormGroupClass)}> const [isFomSubmittable, setIsFomSubmittable] = useState(false);
<div id="kc-form-options" className={cx(props.kcFormOptionsClass)}>
<div className={cx(props.kcFormOptionsWrapperClass)} /> return (
</div> <Template
<div id="kc-form-buttons" className={cx(props.kcFormButtonsClass)}> {...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
<input headerNode={msg("loginIdpReviewProfileTitle")}
className={cx( formNode={
props.kcButtonClass, <form id="kc-idp-review-profile-form" className={cx(kcProps.kcFormClass)} action={url.loginAction} method="post">
props.kcButtonPrimaryClass, <UserProfileFormFields kcContext={kcContext} onIsFormSubmittableValueChange={setIsFomSubmittable} i18n={i18n} {...kcProps} />
props.kcButtonBlockClass,
props.kcButtonLargeClass <div className={cx(kcProps.kcFormGroupClass)}>
)} <div id="kc-form-options" className={cx(kcProps.kcFormOptionsClass)}>
type="submit" <div className={cx(kcProps.kcFormOptionsWrapperClass)} />
value={msgStr("doSubmit")}
disabled={!isFomSubmittable}
/>
</div>
</div> </div>
</form> <div id="kc-form-buttons" className={cx(kcProps.kcFormButtonsClass)}>
} <input
/> className={cx(
); kcProps.kcButtonClass,
} kcProps.kcButtonPrimaryClass,
); kcProps.kcButtonBlockClass,
kcProps.kcButtonLargeClass
)}
type="submit"
value={msgStr("doSubmit")}
disabled={!isFomSubmittable}
/>
</div>
</div>
</form>
}
/>
);
});
export default IdpReviewUserProfile; export default IdpReviewUserProfile;

View File

@ -1,57 +1,60 @@
import React, { memo } from "react"; import React, { memo } from "react";
import Template from "./Template"; import DefaultTemplate from "./Template";
import type { TemplateProps } from "./Template";
import type { KcProps } from "./KcProps"; import type { KcProps } from "./KcProps";
import { assert } from "../tools/assert"; import { assert } from "../tools/assert";
import type { KcContextBase } from "../getKcContext/KcContextBase"; import type { KcContextBase } from "../getKcContext/KcContextBase";
import type { I18n } from "../i18n"; import type { I18n } from "../i18n";
const Info = memo( export type InfoProps = KcProps & {
({ kcContext: KcContextBase.Info;
kcContext, i18n: I18n;
i18n, doFetchDefaultThemeResources?: boolean;
doFetchDefaultThemeResources = true, Template?: (props: TemplateProps) => JSX.Element | null;
...props };
}: { kcContext: KcContextBase.Info; i18n: I18n; doFetchDefaultThemeResources?: boolean } & KcProps) => {
const { msgStr, msg } = i18n;
assert(kcContext.message !== undefined); const Info = memo((props: InfoProps) => {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template = DefaultTemplate, ...kcProps } = props;
const { messageHeader, message, requiredActions, skipLink, pageRedirectUri, actionUri, client } = kcContext; const { msgStr, msg } = i18n;
return ( assert(kcContext.message !== undefined);
<Template
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...props }}
displayMessage={false}
headerNode={messageHeader !== undefined ? <>{messageHeader}</> : <>{message.summary}</>}
formNode={
<div id="kc-info-message">
<p className="instruction">
{message.summary}
{requiredActions !== undefined && ( const { messageHeader, message, requiredActions, skipLink, pageRedirectUri, actionUri, client } = kcContext;
<b>{requiredActions.map(requiredAction => msgStr(`requiredAction.${requiredAction}` as const)).join(",")}</b>
)} return (
</p> <Template
{!skipLink && pageRedirectUri !== undefined ? ( {...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
<p> displayMessage={false}
<a href={pageRedirectUri}>{msg("backToApplication")}</a> headerNode={messageHeader !== undefined ? <>{messageHeader}</> : <>{message.summary}</>}
</p> formNode={
) : actionUri !== undefined ? ( <div id="kc-info-message">
<p> <p className="instruction">
<a href={actionUri}>{msg("proceedWithAction")}</a> {message.summary}
</p>
) : ( {requiredActions !== undefined && (
client.baseUrl !== undefined && ( <b>{requiredActions.map(requiredAction => msgStr(`requiredAction.${requiredAction}` as const)).join(",")}</b>
<p>
<a href={client.baseUrl}>{msg("backToApplication")}</a>
</p>
)
)} )}
</div> </p>
} {!skipLink && pageRedirectUri !== undefined ? (
/> <p>
); <a href={pageRedirectUri}>{msg("backToApplication")}</a>
} </p>
); ) : actionUri !== undefined ? (
<p>
<a href={actionUri}>{msg("proceedWithAction")}</a>
</p>
) : (
client.baseUrl !== undefined && (
<p>
<a href={client.baseUrl}>{msg("backToApplication")}</a>
</p>
)
)}
</div>
}
/>
);
});
export default Info; export default Info;

View File

@ -3,6 +3,8 @@ import type { KcContextBase } from "../getKcContext/KcContextBase";
import type { KcProps } from "./KcProps"; import type { KcProps } from "./KcProps";
import { __unsafe_useI18n as useI18n } from "../i18n"; import { __unsafe_useI18n as useI18n } from "../i18n";
import type { I18n } from "../i18n"; import type { I18n } from "../i18n";
import DefaultTemplate from "./Template";
import type { TemplateProps } from "./Template";
const Login = lazy(() => import("./Login")); const Login = lazy(() => import("./Login"));
const Register = lazy(() => import("./Register")); const Register = lazy(() => import("./Register"));
@ -26,79 +28,82 @@ const LogoutConfirm = lazy(() => import("./LogoutConfirm"));
const UpdateUserProfile = lazy(() => import("./UpdateUserProfile")); const UpdateUserProfile = lazy(() => import("./UpdateUserProfile"));
const IdpReviewUserProfile = lazy(() => import("./IdpReviewUserProfile")); const IdpReviewUserProfile = lazy(() => import("./IdpReviewUserProfile"));
const KcApp = memo( export type KcAppProps = KcProps & {
({ kcContext: KcContextBase;
kcContext, i18n?: I18n;
i18n: userProvidedI18n, doFetchDefaultThemeResources?: boolean;
...kcProps Template: (props: TemplateProps) => JSX.Element | null;
}: { kcContext: KcContextBase; i18n?: I18n; doFetchDefaultThemeResources?: boolean } & KcProps) => { };
const i18n = (function useClosure() {
const i18n = useI18n({
kcContext,
"extraMessages": {},
"doSkip": userProvidedI18n !== undefined
});
return userProvidedI18n ?? i18n; const KcApp = memo((props_: KcAppProps) => {
})(); const { kcContext, i18n: userProvidedI18n, Template = DefaultTemplate, ...kcProps } = props_;
if (i18n === null) { const i18n = (function useClosure() {
return null; const i18n = useI18n({
} kcContext,
"extraMessages": {},
"doSkip": userProvidedI18n !== undefined
});
const props = { i18n, ...kcProps }; return userProvidedI18n ?? i18n;
})();
return ( if (i18n === null) {
<Suspense> return null;
{(() => {
switch (kcContext.pageId) {
case "login.ftl":
return <Login {...{ kcContext, ...props }} />;
case "register.ftl":
return <Register {...{ kcContext, ...props }} />;
case "register-user-profile.ftl":
return <RegisterUserProfile {...{ kcContext, ...props }} />;
case "info.ftl":
return <Info {...{ kcContext, ...props }} />;
case "error.ftl":
return <Error {...{ kcContext, ...props }} />;
case "login-reset-password.ftl":
return <LoginResetPassword {...{ kcContext, ...props }} />;
case "login-verify-email.ftl":
return <LoginVerifyEmail {...{ kcContext, ...props }} />;
case "terms.ftl":
return <Terms {...{ kcContext, ...props }} />;
case "login-otp.ftl":
return <LoginOtp {...{ kcContext, ...props }} />;
case "login-username.ftl":
return <LoginUsername {...{ kcContext, ...props }} />;
case "login-password.ftl":
return <LoginPassword {...{ kcContext, ...props }} />;
case "webauthn-authenticate.ftl":
return <WebauthnAuthenticate {...{ kcContext, ...props }} />;
case "login-update-password.ftl":
return <LoginUpdatePassword {...{ kcContext, ...props }} />;
case "login-update-profile.ftl":
return <LoginUpdateProfile {...{ kcContext, ...props }} />;
case "login-idp-link-confirm.ftl":
return <LoginIdpLinkConfirm {...{ kcContext, ...props }} />;
case "login-idp-link-email.ftl":
return <LoginIdpLinkEmail {...{ kcContext, ...props }} />;
case "login-page-expired.ftl":
return <LoginPageExpired {...{ kcContext, ...props }} />;
case "login-config-totp.ftl":
return <LoginConfigTotp {...{ kcContext, ...props }} />;
case "logout-confirm.ftl":
return <LogoutConfirm {...{ kcContext, ...props }} />;
case "update-user-profile.ftl":
return <UpdateUserProfile {...{ kcContext, ...props }} />;
case "idp-review-user-profile.ftl":
return <IdpReviewUserProfile {...{ kcContext, ...props }} />;
}
})()}
</Suspense>
);
} }
);
const commonProps = { i18n, Template, ...kcProps };
return (
<Suspense>
{(() => {
switch (kcContext.pageId) {
case "login.ftl":
return <Login {...{ kcContext, ...commonProps }} />;
case "register.ftl":
return <Register {...{ kcContext, ...commonProps }} />;
case "register-user-profile.ftl":
return <RegisterUserProfile {...{ kcContext, ...commonProps }} />;
case "info.ftl":
return <Info {...{ kcContext, ...commonProps }} />;
case "error.ftl":
return <Error {...{ kcContext, ...commonProps }} />;
case "login-reset-password.ftl":
return <LoginResetPassword {...{ kcContext, ...commonProps }} />;
case "login-verify-email.ftl":
return <LoginVerifyEmail {...{ kcContext, ...commonProps }} />;
case "terms.ftl":
return <Terms {...{ kcContext, ...commonProps }} />;
case "login-otp.ftl":
return <LoginOtp {...{ kcContext, ...commonProps }} />;
case "login-username.ftl":
return <LoginUsername {...{ kcContext, ...commonProps }} />;
case "login-password.ftl":
return <LoginPassword {...{ kcContext, ...commonProps }} />;
case "webauthn-authenticate.ftl":
return <WebauthnAuthenticate {...{ kcContext, ...commonProps }} />;
case "login-update-password.ftl":
return <LoginUpdatePassword {...{ kcContext, ...commonProps }} />;
case "login-update-profile.ftl":
return <LoginUpdateProfile {...{ kcContext, ...commonProps }} />;
case "login-idp-link-confirm.ftl":
return <LoginIdpLinkConfirm {...{ kcContext, ...commonProps }} />;
case "login-idp-link-email.ftl":
return <LoginIdpLinkEmail {...{ kcContext, ...commonProps }} />;
case "login-page-expired.ftl":
return <LoginPageExpired {...{ kcContext, ...commonProps }} />;
case "login-config-totp.ftl":
return <LoginConfigTotp {...{ kcContext, ...commonProps }} />;
case "logout-confirm.ftl":
return <LogoutConfirm {...{ kcContext, ...commonProps }} />;
case "update-user-profile.ftl":
return <UpdateUserProfile {...{ kcContext, ...commonProps }} />;
case "idp-review-user-profile.ftl":
return <IdpReviewUserProfile {...{ kcContext, ...commonProps }} />;
}
})()}
</Suspense>
);
});
export default KcApp; export default KcApp;

View File

@ -1,5 +1,6 @@
import React, { useState, memo } from "react"; import React, { useState, memo } from "react";
import Template from "./Template"; import DefaultTemplate from "./Template";
import type { TemplateProps } from "./Template";
import type { KcProps } from "./KcProps"; import type { KcProps } from "./KcProps";
import type { KcContextBase } from "../getKcContext/KcContextBase"; import type { KcContextBase } from "../getKcContext/KcContextBase";
import { useCssAndCx } from "../tools/useCssAndCx"; import { useCssAndCx } from "../tools/useCssAndCx";
@ -7,197 +8,199 @@ import { useConstCallback } from "powerhooks/useConstCallback";
import type { FormEventHandler } from "react"; import type { FormEventHandler } from "react";
import type { I18n } from "../i18n"; import type { I18n } from "../i18n";
const Login = memo( export type LoginProps = KcProps & {
({ kcContext: KcContextBase.Login;
kcContext, i18n: I18n;
i18n, doFetchDefaultThemeResources?: boolean;
doFetchDefaultThemeResources = true, Template?: (props: TemplateProps) => JSX.Element | null;
...props };
}: { kcContext: KcContextBase.Login; i18n: I18n; doFetchDefaultThemeResources?: boolean } & KcProps) => {
const { social, realm, url, usernameEditDisabled, login, auth, registrationDisabled } = kcContext;
const { msg, msgStr } = i18n; const Login = memo((props: LoginProps) => {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template = DefaultTemplate, ...kcProps } = props;
const { cx } = useCssAndCx(); const { social, realm, url, usernameEditDisabled, login, auth, registrationDisabled } = kcContext;
const [isLoginButtonDisabled, setIsLoginButtonDisabled] = useState(false); const { msg, msgStr } = i18n;
const onSubmit = useConstCallback<FormEventHandler<HTMLFormElement>>(e => { const { cx } = useCssAndCx();
e.preventDefault();
setIsLoginButtonDisabled(true); const [isLoginButtonDisabled, setIsLoginButtonDisabled] = useState(false);
const formElement = e.target as HTMLFormElement; const onSubmit = useConstCallback<FormEventHandler<HTMLFormElement>>(e => {
e.preventDefault();
//NOTE: Even if we login with email Keycloak expect username and password in setIsLoginButtonDisabled(true);
//the POST request.
formElement.querySelector("input[name='email']")?.setAttribute("name", "username");
formElement.submit(); const formElement = e.target as HTMLFormElement;
});
return ( //NOTE: Even if we login with email Keycloak expect username and password in
<Template //the POST request.
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...props }} formElement.querySelector("input[name='email']")?.setAttribute("name", "username");
displayInfo={social.displayInfo}
displayWide={realm.password && social.providers !== undefined}
headerNode={msg("doLogIn")}
formNode={
<div id="kc-form" className={cx(realm.password && social.providers !== undefined && props.kcContentWrapperClass)}>
<div
id="kc-form-wrapper"
className={cx(
realm.password && social.providers && [props.kcFormSocialAccountContentClass, props.kcFormSocialAccountClass]
)}
>
{realm.password && (
<form id="kc-form-login" onSubmit={onSubmit} action={url.loginAction} method="post">
<div className={cx(props.kcFormGroupClass)}>
{(() => {
const label = !realm.loginWithEmailAllowed
? "username"
: realm.registrationEmailAsUsername
? "email"
: "usernameOrEmail";
const autoCompleteHelper: typeof label = label === "usernameOrEmail" ? "username" : label; formElement.submit();
});
return ( return (
<> <Template
<label htmlFor={autoCompleteHelper} className={cx(props.kcLabelClass)}> {...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
{msg(label)} displayInfo={social.displayInfo}
</label> displayWide={realm.password && social.providers !== undefined}
headerNode={msg("doLogIn")}
formNode={
<div id="kc-form" className={cx(realm.password && social.providers !== undefined && kcProps.kcContentWrapperClass)}>
<div
id="kc-form-wrapper"
className={cx(
realm.password && social.providers && [kcProps.kcFormSocialAccountContentClass, kcProps.kcFormSocialAccountClass]
)}
>
{realm.password && (
<form id="kc-form-login" onSubmit={onSubmit} action={url.loginAction} method="post">
<div className={cx(kcProps.kcFormGroupClass)}>
{(() => {
const label = !realm.loginWithEmailAllowed
? "username"
: realm.registrationEmailAsUsername
? "email"
: "usernameOrEmail";
const autoCompleteHelper: typeof label = label === "usernameOrEmail" ? "username" : label;
return (
<>
<label htmlFor={autoCompleteHelper} className={cx(kcProps.kcLabelClass)}>
{msg(label)}
</label>
<input
tabIndex={1}
id={autoCompleteHelper}
className={cx(kcProps.kcInputClass)}
//NOTE: This is used by Google Chrome auto fill so we use it to tell
//the browser how to pre fill the form but before submit we put it back
//to username because it is what keycloak expects.
name={autoCompleteHelper}
defaultValue={login.username ?? ""}
type="text"
{...(usernameEditDisabled
? { "disabled": true }
: {
"autoFocus": true,
"autoComplete": "off"
})}
/>
</>
);
})()}
</div>
<div className={cx(kcProps.kcFormGroupClass)}>
<label htmlFor="password" className={cx(kcProps.kcLabelClass)}>
{msg("password")}
</label>
<input
tabIndex={2}
id="password"
className={cx(kcProps.kcInputClass)}
name="password"
type="password"
autoComplete="off"
/>
</div>
<div className={cx(kcProps.kcFormGroupClass, kcProps.kcFormSettingClass)}>
<div id="kc-form-options">
{realm.rememberMe && !usernameEditDisabled && (
<div className="checkbox">
<label>
<input <input
tabIndex={1} tabIndex={3}
id={autoCompleteHelper} id="rememberMe"
className={cx(props.kcInputClass)} name="rememberMe"
//NOTE: This is used by Google Chrome auto fill so we use it to tell type="checkbox"
//the browser how to pre fill the form but before submit we put it back {...(login.rememberMe
//to username because it is what keycloak expects. ? {
name={autoCompleteHelper} "checked": true
defaultValue={login.username ?? ""} }
type="text" : {})}
{...(usernameEditDisabled
? { "disabled": true }
: {
"autoFocus": true,
"autoComplete": "off"
})}
/> />
</> {msg("rememberMe")}
); </label>
})()} </div>
)}
</div> </div>
<div className={cx(props.kcFormGroupClass)}> <div className={cx(kcProps.kcFormOptionsWrapperClass)}>
<label htmlFor="password" className={cx(props.kcLabelClass)}> {realm.resetPasswordAllowed && (
{msg("password")} <span>
</label> <a tabIndex={5} href={url.loginResetCredentialsUrl}>
<input {msg("doForgotPassword")}
tabIndex={2} </a>
id="password" </span>
className={cx(props.kcInputClass)} )}
name="password"
type="password"
autoComplete="off"
/>
</div> </div>
<div className={cx(props.kcFormGroupClass, props.kcFormSettingClass)}> </div>
<div id="kc-form-options"> <div id="kc-form-buttons" className={cx(kcProps.kcFormGroupClass)}>
{realm.rememberMe && !usernameEditDisabled && ( <input
<div className="checkbox"> type="hidden"
<label> id="id-hidden-input"
<input name="credentialId"
tabIndex={3} {...(auth?.selectedCredential !== undefined
id="rememberMe" ? {
name="rememberMe" "value": auth.selectedCredential
type="checkbox" }
{...(login.rememberMe : {})}
? { />
"checked": true <input
} tabIndex={4}
: {})} className={cx(
/> kcProps.kcButtonClass,
{msg("rememberMe")} kcProps.kcButtonPrimaryClass,
</label> kcProps.kcButtonBlockClass,
</div> kcProps.kcButtonLargeClass
)} )}
</div> name="login"
<div className={cx(props.kcFormOptionsWrapperClass)}> id="kc-login"
{realm.resetPasswordAllowed && ( type="submit"
<span> value={msgStr("doLogIn")}
<a tabIndex={5} href={url.loginResetCredentialsUrl}> disabled={isLoginButtonDisabled}
{msg("doForgotPassword")} />
</a> </div>
</span> </form>
)}
</div>
</div>
<div id="kc-form-buttons" className={cx(props.kcFormGroupClass)}>
<input
type="hidden"
id="id-hidden-input"
name="credentialId"
{...(auth?.selectedCredential !== undefined
? {
"value": auth.selectedCredential
}
: {})}
/>
<input
tabIndex={4}
className={cx(
props.kcButtonClass,
props.kcButtonPrimaryClass,
props.kcButtonBlockClass,
props.kcButtonLargeClass
)}
name="login"
id="kc-login"
type="submit"
value={msgStr("doLogIn")}
disabled={isLoginButtonDisabled}
/>
</div>
</form>
)}
</div>
{realm.password && social.providers !== undefined && (
<div id="kc-social-providers" className={cx(props.kcFormSocialAccountContentClass, props.kcFormSocialAccountClass)}>
<ul
className={cx(
props.kcFormSocialAccountListClass,
social.providers.length > 4 && props.kcFormSocialAccountDoubleListClass
)}
>
{social.providers.map(p => (
<li key={p.providerId} className={cx(props.kcFormSocialAccountListLinkClass)}>
<a href={p.loginUrl} id={`zocial-${p.alias}`} className={cx("zocial", p.providerId)}>
<span>{p.displayName}</span>
</a>
</li>
))}
</ul>
</div>
)} )}
</div> </div>
} {realm.password && social.providers !== undefined && (
infoNode={ <div id="kc-social-providers" className={cx(kcProps.kcFormSocialAccountContentClass, kcProps.kcFormSocialAccountClass)}>
realm.password && <ul
realm.registrationAllowed && className={cx(
!registrationDisabled && ( kcProps.kcFormSocialAccountListClass,
<div id="kc-registration"> social.providers.length > 4 && kcProps.kcFormSocialAccountDoubleListClass
<span> )}
{msg("noAccount")} >
<a tabIndex={6} href={url.registrationUrl}> {social.providers.map(p => (
{msg("doRegister")} <li key={p.providerId} className={cx(kcProps.kcFormSocialAccountListLinkClass)}>
</a> <a href={p.loginUrl} id={`zocial-${p.alias}`} className={cx("zocial", p.providerId)}>
</span> <span>{p.displayName}</span>
</a>
</li>
))}
</ul>
</div> </div>
) )}
} </div>
/> }
); infoNode={
} realm.password &&
); realm.registrationAllowed &&
!registrationDisabled && (
<div id="kc-registration">
<span>
{msg("noAccount")}
<a tabIndex={6} href={url.registrationUrl}>
{msg("doRegister")}
</a>
</span>
</div>
)
}
/>
);
});
export default Login; export default Login;

View File

@ -1,192 +1,195 @@
import React, { memo } from "react"; import React, { memo } from "react";
import Template from "./Template"; import DefaultTemplate from "./Template";
import type { TemplateProps } from "./Template";
import type { KcProps } from "./KcProps"; import type { KcProps } from "./KcProps";
import type { KcContextBase } from "../getKcContext/KcContextBase"; import type { KcContextBase } from "../getKcContext/KcContextBase";
import { useCssAndCx } from "../tools/useCssAndCx"; import { useCssAndCx } from "../tools/useCssAndCx";
import type { I18n } from "../i18n"; import type { I18n } from "../i18n";
const LoginConfigTotp = memo( export type LoginConfigTotpProps = KcProps & {
({ kcContext: KcContextBase.LoginConfigTotp;
kcContext, i18n: I18n;
i18n, doFetchDefaultThemeResources?: boolean;
doFetchDefaultThemeResources = true, Template?: (props: TemplateProps) => JSX.Element | null;
...props };
}: { kcContext: KcContextBase.LoginConfigTotp; i18n: I18n; doFetchDefaultThemeResources?: boolean } & KcProps) => {
const { url, isAppInitiatedAction, totp, mode, messagesPerField } = kcContext;
const { cx } = useCssAndCx(); const LoginConfigTotp = memo((props: LoginConfigTotpProps) => {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template = DefaultTemplate, ...kcProps } = props;
const { msg, msgStr } = i18n; const { url, isAppInitiatedAction, totp, mode, messagesPerField } = kcContext;
const algToKeyUriAlg: Record<KcContextBase.LoginConfigTotp["totp"]["policy"]["algorithm"], string> = { const { cx } = useCssAndCx();
HmacSHA1: "SHA1",
HmacSHA256: "SHA256",
HmacSHA512: "SHA512"
};
return ( const { msg, msgStr } = i18n;
<Template
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...props }}
headerNode={msg("loginTotpTitle")}
formNode={
<>
<ol id="kc-totp-settings">
<li>
<p>{msg("loginTotpStep1")}</p>
<ul id="kc-totp-supported-apps"> const algToKeyUriAlg: Record<KcContextBase.LoginConfigTotp["totp"]["policy"]["algorithm"], string> = {
{totp.policy.supportedApplications.map(app => ( "HmacSHA1": "SHA1",
<li>{app}</li> "HmacSHA256": "SHA256",
))} "HmacSHA512": "SHA512"
</ul> };
</li>
{mode && mode == "manual" ? ( return (
<> <Template
<li> {...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
<p>{msg("loginTotpManualStep2")}</p> headerNode={msg("loginTotpTitle")}
<p> formNode={
<span id="kc-totp-secret-key">{totp.totpSecretEncoded}</span> <>
</p> <ol id="kc-totp-settings">
<p> <li>
<a href={totp.qrUrl} id="mode-barcode"> <p>{msg("loginTotpStep1")}</p>
{msg("loginTotpScanBarcode")}
</a> <ul id="kc-totp-supported-apps">
</p> {totp.policy.supportedApplications.map(app => (
</li> <li>{app}</li>
<li> ))}
<p>{msg("loginTotpManualStep3")}</p> </ul>
<p> </li>
<ul>
<li id="kc-totp-type"> {mode && mode == "manual" ? (
{msg("loginTotpType")}: {msg(`loginTotp.${totp.policy.type}`)} <>
</li>
<li id="kc-totp-algorithm">
{msg("loginTotpAlgorithm")}: {algToKeyUriAlg?.[totp.policy.algorithm] ?? totp.policy.algorithm}
</li>
<li id="kc-totp-digits">
{msg("loginTotpDigits")}: {totp.policy.digits}
</li>
{totp.policy.type === "totp" ? (
<li id="kc-totp-period">
{msg("loginTotpInterval")}: {totp.policy.period}
</li>
) : (
<li id="kc-totp-counter">
{msg("loginTotpCounter")}: {totp.policy.initialCounter}
</li>
)}
</ul>
</p>
</li>
</>
) : (
<li> <li>
<p>{msg("loginTotpStep2")}</p> <p>{msg("loginTotpManualStep2")}</p>
<img id="kc-totp-secret-qr-code" src={`data:image/png;base64, ${totp.totpSecretQrCode}`} alt="Figure: Barcode" />
<br />
<p> <p>
<a href={totp.manualUrl} id="mode-manual"> <span id="kc-totp-secret-key">{totp.totpSecretEncoded}</span>
{msg("loginTotpUnableToScan")} </p>
<p>
<a href={totp.qrUrl} id="mode-barcode">
{msg("loginTotpScanBarcode")}
</a> </a>
</p> </p>
</li> </li>
)} <li>
<p>{msg("loginTotpManualStep3")}</p>
<p>
<ul>
<li id="kc-totp-type">
{msg("loginTotpType")}: {msg(`loginTotp.${totp.policy.type}`)}
</li>
<li id="kc-totp-algorithm">
{msg("loginTotpAlgorithm")}: {algToKeyUriAlg?.[totp.policy.algorithm] ?? totp.policy.algorithm}
</li>
<li id="kc-totp-digits">
{msg("loginTotpDigits")}: {totp.policy.digits}
</li>
{totp.policy.type === "totp" ? (
<li id="kc-totp-period">
{msg("loginTotpInterval")}: {totp.policy.period}
</li>
) : (
<li id="kc-totp-counter">
{msg("loginTotpCounter")}: {totp.policy.initialCounter}
</li>
)}
</ul>
</p>
</li>
</>
) : (
<li> <li>
<p>{msg("loginTotpStep3")}</p> <p>{msg("loginTotpStep2")}</p>
<p>{msg("loginTotpStep3DeviceName")}</p> <img id="kc-totp-secret-qr-code" src={`data:image/png;base64, ${totp.totpSecretQrCode}`} alt="Figure: Barcode" />
<br />
<p>
<a href={totp.manualUrl} id="mode-manual">
{msg("loginTotpUnableToScan")}
</a>
</p>
</li> </li>
</ol> )}
<li>
<p>{msg("loginTotpStep3")}</p>
<p>{msg("loginTotpStep3DeviceName")}</p>
</li>
</ol>
<form action={url.loginAction} className={cx(props.kcFormClass)} id="kc-totp-settings-form" method="post"> <form action={url.loginAction} className={cx(kcProps.kcFormClass)} id="kc-totp-settings-form" method="post">
<div className={cx(props.kcFormGroupClass)}> <div className={cx(kcProps.kcFormGroupClass)}>
<div className={cx(props.kcInputWrapperClass)}> <div className={cx(kcProps.kcInputWrapperClass)}>
<label htmlFor="totp" className={cx(props.kcLabelClass)}> <label htmlFor="totp" className={cx(kcProps.kcLabelClass)}>
{msg("authenticatorCode")} {msg("authenticatorCode")}
</label>{" "} </label>{" "}
<span className="required">*</span> <span className="required">*</span>
</div>
<div className={cx(props.kcInputWrapperClass)}>
<input
type="text"
id="totp"
name="totp"
autoComplete="off"
className={cx(props.kcInputClass)}
aria-invalid={messagesPerField.existsError("totp")}
/>
{messagesPerField.existsError("totp") && (
<span id="input-error-otp-code" className={cx(props.kcInputErrorMessageClass)} aria-live="polite">
{messagesPerField.get("totp")}
</span>
)}
</div>
<input type="hidden" id="totpSecret" name="totpSecret" value={totp.totpSecret} />
{mode && <input type="hidden" id="mode" value={mode} />}
</div> </div>
<div className={cx(kcProps.kcInputWrapperClass)}>
<input
type="text"
id="totp"
name="totp"
autoComplete="off"
className={cx(kcProps.kcInputClass)}
aria-invalid={messagesPerField.existsError("totp")}
/>
<div className={cx(props.kcFormGroupClass)}> {messagesPerField.existsError("totp") && (
<div className={cx(props.kcInputWrapperClass)}> <span id="input-error-otp-code" className={cx(kcProps.kcInputErrorMessageClass)} aria-live="polite">
<label htmlFor="userLabel" className={cx(props.kcLabelClass)}> {messagesPerField.get("totp")}
{msg("loginTotpDeviceName")} </span>
</label>{" "} )}
{totp.otpCredentials.length >= 1 && <span className="required">*</span>}
</div>
<div className={cx(props.kcInputWrapperClass)}>
<input
type="text"
id="userLabel"
name="userLabel"
autoComplete="off"
className={cx(props.kcInputClass)}
aria-invalid={messagesPerField.existsError("userLabel")}
/>
{messagesPerField.existsError("userLabel") && (
<span id="input-error-otp-label" className={cx(props.kcInputErrorMessageClass)} aria-live="polite">
{messagesPerField.get("userLabel")}
</span>
)}
</div>
</div> </div>
<input type="hidden" id="totpSecret" name="totpSecret" value={totp.totpSecret} />
{mode && <input type="hidden" id="mode" value={mode} />}
</div>
{isAppInitiatedAction ? ( <div className={cx(kcProps.kcFormGroupClass)}>
<> <div className={cx(kcProps.kcInputWrapperClass)}>
<input <label htmlFor="userLabel" className={cx(kcProps.kcLabelClass)}>
type="submit" {msg("loginTotpDeviceName")}
className={cx(props.kcButtonClass, props.kcButtonPrimaryClass, props.kcButtonLargeClass)} </label>{" "}
id="saveTOTPBtn" {totp.otpCredentials.length >= 1 && <span className="required">*</span>}
value={msgStr("doSubmit")} </div>
/> <div className={cx(kcProps.kcInputWrapperClass)}>
<button <input
type="submit" type="text"
className={cx( id="userLabel"
props.kcButtonClass, name="userLabel"
props.kcButtonDefaultClass, autoComplete="off"
props.kcButtonLargeClass, className={cx(kcProps.kcInputClass)}
props.kcButtonLargeClass aria-invalid={messagesPerField.existsError("userLabel")}
)} />
id="cancelTOTPBtn" {messagesPerField.existsError("userLabel") && (
name="cancel-aia" <span id="input-error-otp-label" className={cx(kcProps.kcInputErrorMessageClass)} aria-live="polite">
value="true" {messagesPerField.get("userLabel")}
> </span>
${msg("doCancel")} )}
</button> </div>
</> </div>
) : (
{isAppInitiatedAction ? (
<>
<input <input
type="submit" type="submit"
className={cx(props.kcButtonClass, props.kcButtonPrimaryClass, props.kcButtonLargeClass)} className={cx(kcProps.kcButtonClass, kcProps.kcButtonPrimaryClass, kcProps.kcButtonLargeClass)}
id="saveTOTPBtn" id="saveTOTPBtn"
value={msgStr("doSubmit")} value={msgStr("doSubmit")}
/> />
)} <button
</form> type="submit"
</> className={cx(
} kcProps.kcButtonClass,
/> kcProps.kcButtonDefaultClass,
); kcProps.kcButtonLargeClass,
} kcProps.kcButtonLargeClass
); )}
id="cancelTOTPBtn"
name="cancel-aia"
value="true"
>
${msg("doCancel")}
</button>
</>
) : (
<input
type="submit"
className={cx(kcProps.kcButtonClass, kcProps.kcButtonPrimaryClass, kcProps.kcButtonLargeClass)}
id="saveTOTPBtn"
value={msgStr("doSubmit")}
/>
)}
</form>
</>
}
/>
);
});
export default LoginConfigTotp; export default LoginConfigTotp;

View File

@ -1,54 +1,67 @@
import React, { memo } from "react"; import React, { memo } from "react";
import Template from "./Template"; import DefaultTemplate from "./Template";
import type { TemplateProps } from "./Template";
import type { KcProps } from "./KcProps"; import type { KcProps } from "./KcProps";
import type { KcContextBase } from "../getKcContext/KcContextBase"; import type { KcContextBase } from "../getKcContext/KcContextBase";
import { useCssAndCx } from "../tools/useCssAndCx"; import { useCssAndCx } from "../tools/useCssAndCx";
import type { I18n } from "../i18n"; import type { I18n } from "../i18n";
const LoginIdpLinkConfirm = memo( export type LoginIdpLinkConfirmProps = KcProps & {
({ kcContext: KcContextBase.LoginIdpLinkConfirm;
kcContext, i18n: I18n;
i18n, doFetchDefaultThemeResources?: boolean;
doFetchDefaultThemeResources = true, Template?: (props: TemplateProps) => JSX.Element | null;
...props };
}: { kcContext: KcContextBase.LoginIdpLinkConfirm; i18n: I18n; doFetchDefaultThemeResources?: boolean } & KcProps) => {
const { url, idpAlias } = kcContext;
const { msg } = i18n; const LoginIdpLinkConfirm = memo((props: LoginIdpLinkConfirmProps) => {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template = DefaultTemplate, ...kcProps } = props;
const { cx } = useCssAndCx(); const { url, idpAlias } = kcContext;
return ( const { msg } = i18n;
<Template
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...props }} const { cx } = useCssAndCx();
headerNode={msg("confirmLinkIdpTitle")}
formNode={ return (
<form id="kc-register-form" action={url.loginAction} method="post"> <Template
<div className={cx(props.kcFormGroupClass)}> {...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
<button headerNode={msg("confirmLinkIdpTitle")}
type="submit" formNode={
className={cx(props.kcButtonClass, props.kcButtonDefaultClass, props.kcButtonBlockClass, props.kcButtonLargeClass)} <form id="kc-register-form" action={url.loginAction} method="post">
name="submitAction" <div className={cx(kcProps.kcFormGroupClass)}>
id="updateProfile" <button
value="updateProfile" type="submit"
> className={cx(
{msg("confirmLinkIdpReviewProfile")} kcProps.kcButtonClass,
</button> kcProps.kcButtonDefaultClass,
<button kcProps.kcButtonBlockClass,
type="submit" kcProps.kcButtonLargeClass
className={cx(props.kcButtonClass, props.kcButtonDefaultClass, props.kcButtonBlockClass, props.kcButtonLargeClass)} )}
name="submitAction" name="submitAction"
id="linkAccount" id="updateProfile"
value="linkAccount" value="updateProfile"
> >
{msg("confirmLinkIdpContinue", idpAlias)} {msg("confirmLinkIdpReviewProfile")}
</button> </button>
</div> <button
</form> type="submit"
} className={cx(
/> kcProps.kcButtonClass,
); kcProps.kcButtonDefaultClass,
} kcProps.kcButtonBlockClass,
); kcProps.kcButtonLargeClass
)}
name="submitAction"
id="linkAccount"
value="linkAccount"
>
{msg("confirmLinkIdpContinue", idpAlias)}
</button>
</div>
</form>
}
/>
);
});
export default LoginIdpLinkConfirm; export default LoginIdpLinkConfirm;

View File

@ -1,40 +1,43 @@
import React, { memo } from "react"; import React, { memo } from "react";
import Template from "./Template"; import DefaultTemplate from "./Template";
import type { TemplateProps } from "./Template";
import type { KcProps } from "./KcProps"; import type { KcProps } from "./KcProps";
import type { KcContextBase } from "../getKcContext/KcContextBase"; import type { KcContextBase } from "../getKcContext/KcContextBase";
import type { I18n } from "../i18n"; import type { I18n } from "../i18n";
const LoginIdpLinkEmail = memo( export type LoginIdpLinkEmailProps = KcProps & {
({ kcContext: KcContextBase.LoginIdpLinkEmail;
kcContext, i18n: I18n;
i18n, doFetchDefaultThemeResources?: boolean;
doFetchDefaultThemeResources = true, Template?: (props: TemplateProps) => JSX.Element | null;
...props };
}: { kcContext: KcContextBase.LoginIdpLinkEmail; i18n: I18n; doFetchDefaultThemeResources?: boolean } & KcProps) => {
const { url, realm, brokerContext, idpAlias } = kcContext;
const { msg } = i18n; const LoginIdpLinkEmail = memo((props: LoginIdpLinkEmailProps) => {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template = DefaultTemplate, ...kcProps } = props;
return ( const { url, realm, brokerContext, idpAlias } = kcContext;
<Template
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...props }} const { msg } = i18n;
headerNode={msg("emailLinkIdpTitle", idpAlias)}
formNode={ return (
<> <Template
<p id="instruction1" className="instruction"> {...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
{msg("emailLinkIdp1", idpAlias, brokerContext.username, realm.displayName)} headerNode={msg("emailLinkIdpTitle", idpAlias)}
</p> formNode={
<p id="instruction2" className="instruction"> <>
{msg("emailLinkIdp2")} <a href={url.loginAction}>{msg("doClickHere")}</a> {msg("emailLinkIdp3")} <p id="instruction1" className="instruction">
</p> {msg("emailLinkIdp1", idpAlias, brokerContext.username, realm.displayName)}
<p id="instruction3" className="instruction"> </p>
{msg("emailLinkIdp4")} <a href={url.loginAction}>{msg("doClickHere")}</a> {msg("emailLinkIdp5")} <p id="instruction2" className="instruction">
</p> {msg("emailLinkIdp2")} <a href={url.loginAction}>{msg("doClickHere")}</a> {msg("emailLinkIdp3")}
</> </p>
} <p id="instruction3" className="instruction">
/> {msg("emailLinkIdp4")} <a href={url.loginAction}>{msg("doClickHere")}</a> {msg("emailLinkIdp5")}
); </p>
} </>
); }
/>
);
});
export default LoginIdpLinkEmail; export default LoginIdpLinkEmail;

View File

@ -1,5 +1,6 @@
import React, { useEffect, memo } from "react"; import React, { useEffect, memo } from "react";
import Template from "./Template"; import DefaultTemplate from "./Template";
import type { TemplateProps } from "./Template";
import type { KcProps } from "./KcProps"; import type { KcProps } from "./KcProps";
import type { KcContextBase } from "../getKcContext/KcContextBase"; import type { KcContextBase } from "../getKcContext/KcContextBase";
import { headInsert } from "../tools/headInsert"; import { headInsert } from "../tools/headInsert";
@ -7,95 +8,97 @@ import { pathJoin } from "../../bin/tools/pathJoin";
import { useCssAndCx } from "../tools/useCssAndCx"; import { useCssAndCx } from "../tools/useCssAndCx";
import type { I18n } from "../i18n"; import type { I18n } from "../i18n";
const LoginOtp = memo( export type LoginOtpProps = KcProps & {
({ kcContext: KcContextBase.LoginOtp;
kcContext, i18n: I18n;
i18n, doFetchDefaultThemeResources?: boolean;
doFetchDefaultThemeResources = true, Template?: (props: TemplateProps) => JSX.Element | null;
...props };
}: { kcContext: KcContextBase.LoginOtp; i18n: I18n; doFetchDefaultThemeResources?: boolean } & KcProps) => {
const { otpLogin, url } = kcContext;
const { cx } = useCssAndCx(); const LoginOtp = memo((props: LoginOtpProps) => {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template = DefaultTemplate, ...kcProps } = props;
const { msg, msgStr } = i18n; const { otpLogin, url } = kcContext;
useEffect(() => { const { cx } = useCssAndCx();
let isCleanedUp = false;
headInsert({ const { msg, msgStr } = i18n;
"type": "javascript",
"src": pathJoin(kcContext.url.resourcesCommonPath, "node_modules/jquery/dist/jquery.min.js")
}).then(() => {
if (isCleanedUp) return;
evaluateInlineScript(); useEffect(() => {
}); let isCleanedUp = false;
return () => { headInsert({
isCleanedUp = true; "type": "javascript",
}; "src": pathJoin(kcContext.url.resourcesCommonPath, "node_modules/jquery/dist/jquery.min.js")
}, []); }).then(() => {
if (isCleanedUp) return;
return ( evaluateInlineScript();
<Template });
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...props }}
headerNode={msg("doLogIn")} return () => {
formNode={ isCleanedUp = true;
<form id="kc-otp-login-form" className={cx(props.kcFormClass)} action={url.loginAction} method="post"> };
{otpLogin.userOtpCredentials.length > 1 && ( }, []);
<div className={cx(props.kcFormGroupClass)}>
<div className={cx(props.kcInputWrapperClass)}> return (
{otpLogin.userOtpCredentials.map(otpCredential => ( <Template
<div key={otpCredential.id} className={cx(props.kcSelectOTPListClass)}> {...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
<input type="hidden" value="${otpCredential.id}" /> headerNode={msg("doLogIn")}
<div className={cx(props.kcSelectOTPListItemClass)}> formNode={
<span className={cx(props.kcAuthenticatorOtpCircleClass)} /> <form id="kc-otp-login-form" className={cx(kcProps.kcFormClass)} action={url.loginAction} method="post">
<h2 className={cx(props.kcSelectOTPItemHeadingClass)}>{otpCredential.userLabel}</h2> {otpLogin.userOtpCredentials.length > 1 && (
</div> <div className={cx(kcProps.kcFormGroupClass)}>
<div className={cx(kcProps.kcInputWrapperClass)}>
{otpLogin.userOtpCredentials.map(otpCredential => (
<div key={otpCredential.id} className={cx(kcProps.kcSelectOTPListClass)}>
<input type="hidden" value="${otpCredential.id}" />
<div className={cx(kcProps.kcSelectOTPListItemClass)}>
<span className={cx(kcProps.kcAuthenticatorOtpCircleClass)} />
<h2 className={cx(kcProps.kcSelectOTPItemHeadingClass)}>{otpCredential.userLabel}</h2>
</div> </div>
))} </div>
</div> ))}
</div>
)}
<div className={cx(props.kcFormGroupClass)}>
<div className={cx(props.kcLabelWrapperClass)}>
<label htmlFor="otp" className={cx(props.kcLabelClass)}>
{msg("loginOtpOneTime")}
</label>
</div>
<div className={cx(props.kcInputWrapperClass)}>
<input id="otp" name="otp" autoComplete="off" type="text" className={cx(props.kcInputClass)} autoFocus />
</div> </div>
</div> </div>
)}
<div className={cx(props.kcFormGroupClass)}> <div className={cx(kcProps.kcFormGroupClass)}>
<div id="kc-form-options" className={cx(props.kcFormOptionsClass)}> <div className={cx(kcProps.kcLabelWrapperClass)}>
<div className={cx(props.kcFormOptionsWrapperClass)} /> <label htmlFor="otp" className={cx(kcProps.kcLabelClass)}>
</div> {msg("loginOtpOneTime")}
</label>
<div id="kc-form-buttons" className={cx(props.kcFormButtonsClass)}>
<input
className={cx(
props.kcButtonClass,
props.kcButtonPrimaryClass,
props.kcButtonBlockClass,
props.kcButtonLargeClass
)}
name="login"
id="kc-login"
type="submit"
value={msgStr("doLogIn")}
/>
</div>
</div> </div>
</form>
} <div className={cx(kcProps.kcInputWrapperClass)}>
/> <input id="otp" name="otp" autoComplete="off" type="text" className={cx(kcProps.kcInputClass)} autoFocus />
); </div>
} </div>
);
<div className={cx(kcProps.kcFormGroupClass)}>
<div id="kc-form-options" className={cx(kcProps.kcFormOptionsClass)}>
<div className={cx(kcProps.kcFormOptionsWrapperClass)} />
</div>
<div id="kc-form-buttons" className={cx(kcProps.kcFormButtonsClass)}>
<input
className={cx(
kcProps.kcButtonClass,
kcProps.kcButtonPrimaryClass,
kcProps.kcButtonBlockClass,
kcProps.kcButtonLargeClass
)}
name="login"
id="kc-login"
type="submit"
value={msgStr("doLogIn")}
/>
</div>
</div>
</form>
}
/>
);
});
declare const $: any; declare const $: any;

View File

@ -1,44 +1,47 @@
import React, { memo } from "react"; import React, { memo } from "react";
import Template from "./Template"; import DefaultTemplate from "./Template";
import type { TemplateProps } from "./Template";
import type { KcProps } from "./KcProps"; import type { KcProps } from "./KcProps";
import type { KcContextBase } from "../getKcContext/KcContextBase"; import type { KcContextBase } from "../getKcContext/KcContextBase";
import type { I18n } from "../i18n"; import type { I18n } from "../i18n";
const LoginPageExpired = memo( export type LoginPageExpired = KcProps & {
({ kcContext: KcContextBase.LoginPageExpired;
kcContext, i18n: I18n;
i18n, doFetchDefaultThemeResources?: boolean;
doFetchDefaultThemeResources = true, Template?: (props: TemplateProps) => JSX.Element | null;
...props };
}: { kcContext: KcContextBase.LoginPageExpired; i18n: I18n; doFetchDefaultThemeResources?: boolean } & KcProps) => {
const { url } = kcContext;
const { msg } = i18n; const LoginPageExpired = memo((props: LoginPageExpired) => {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template = DefaultTemplate, ...kcProps } = props;
return ( const { url } = kcContext;
<Template
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...props }} const { msg } = i18n;
displayMessage={false}
headerNode={msg("pageExpiredTitle")} return (
formNode={ <Template
<> {...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
<p id="instruction1" className="instruction"> displayMessage={false}
{msg("pageExpiredMsg1")} headerNode={msg("pageExpiredTitle")}
<a id="loginRestartLink" href={url.loginRestartFlowUrl}> formNode={
{msg("doClickHere")} <>
</a>{" "} <p id="instruction1" className="instruction">
.<br /> {msg("pageExpiredMsg1")}
{msg("pageExpiredMsg2")}{" "} <a id="loginRestartLink" href={url.loginRestartFlowUrl}>
<a id="loginContinueLink" href={url.loginAction}> {msg("doClickHere")}
{msg("doClickHere")} </a>{" "}
</a>{" "} .<br />
. {msg("pageExpiredMsg2")}{" "}
</p> <a id="loginContinueLink" href={url.loginAction}>
</> {msg("doClickHere")}
} </a>{" "}
/> .
); </p>
} </>
); }
/>
);
});
export default LoginPageExpired; export default LoginPageExpired;

View File

@ -1,5 +1,6 @@
import React, { useState, memo } from "react"; import React, { useState, memo } from "react";
import Template from "./Template"; import DefaultTemplate from "./Template";
import type { TemplateProps } from "./Template";
import type { KcProps } from "./KcProps"; import type { KcProps } from "./KcProps";
import type { KcContextBase } from "../getKcContext/KcContextBase"; import type { KcContextBase } from "../getKcContext/KcContextBase";
import { useCssAndCx } from "../tools/useCssAndCx"; import { useCssAndCx } from "../tools/useCssAndCx";
@ -7,90 +8,92 @@ import { useConstCallback } from "powerhooks/useConstCallback";
import type { FormEventHandler } from "react"; import type { FormEventHandler } from "react";
import type { I18n } from "../i18n"; import type { I18n } from "../i18n";
const LoginPassword = memo( export type LoginPasswordProps = KcProps & {
({ kcContext: KcContextBase.LoginPassword;
kcContext, i18n: I18n;
i18n, doFetchDefaultThemeResources?: boolean;
doFetchDefaultThemeResources = true, Template?: (props: TemplateProps) => JSX.Element | null;
...props };
}: { kcContext: KcContextBase.LoginPassword; i18n: I18n; doFetchDefaultThemeResources?: boolean } & KcProps) => {
const { realm, url, login } = kcContext;
const { msg, msgStr } = i18n; const LoginPassword = memo((props: LoginPasswordProps) => {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template = DefaultTemplate, ...kcProps } = props;
const { cx } = useCssAndCx(); const { realm, url, login } = kcContext;
const [isLoginButtonDisabled, setIsLoginButtonDisabled] = useState(false); const { msg, msgStr } = i18n;
const onSubmit = useConstCallback<FormEventHandler<HTMLFormElement>>(e => { const { cx } = useCssAndCx();
e.preventDefault();
setIsLoginButtonDisabled(true); const [isLoginButtonDisabled, setIsLoginButtonDisabled] = useState(false);
const formElement = e.target as HTMLFormElement; const onSubmit = useConstCallback<FormEventHandler<HTMLFormElement>>(e => {
e.preventDefault();
formElement.submit(); setIsLoginButtonDisabled(true);
});
return ( const formElement = e.target as HTMLFormElement;
<Template
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...props }} formElement.submit();
headerNode={msg("doLogIn")} });
formNode={
<div id="kc-form"> return (
<div id="kc-form-wrapper"> <Template
<form id="kc-form-login" onSubmit={onSubmit} action={url.loginAction} method="post"> {...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
<div className={cx(props.kcFormGroupClass)}> headerNode={msg("doLogIn")}
<hr /> formNode={
<label htmlFor="password" className={cx(props.kcLabelClass)}> <div id="kc-form">
{msg("password")} <div id="kc-form-wrapper">
</label> <form id="kc-form-login" onSubmit={onSubmit} action={url.loginAction} method="post">
<input <div className={cx(kcProps.kcFormGroupClass)}>
tabIndex={2} <hr />
id="password" <label htmlFor="password" className={cx(kcProps.kcLabelClass)}>
className={cx(props.kcInputClass)} {msg("password")}
name="password" </label>
type="password" <input
autoFocus={true} tabIndex={2}
autoComplete="on" id="password"
defaultValue={login.password ?? ""} className={cx(kcProps.kcInputClass)}
/> name="password"
type="password"
autoFocus={true}
autoComplete="on"
defaultValue={login.password ?? ""}
/>
</div>
<div className={cx(kcProps.kcFormGroupClass, kcProps.kcFormSettingClass)}>
<div id="kc-form-options" />
<div className={cx(kcProps.kcFormOptionsWrapperClass)}>
{realm.resetPasswordAllowed && (
<span>
<a tabIndex={5} href={url.loginResetCredentialsUrl}>
{msg("doForgotPassword")}
</a>
</span>
)}
</div> </div>
<div className={cx(props.kcFormGroupClass, props.kcFormSettingClass)}> </div>
<div id="kc-form-options" /> <div id="kc-form-buttons" className={cx(kcProps.kcFormGroupClass)}>
<div className={cx(props.kcFormOptionsWrapperClass)}> <input
{realm.resetPasswordAllowed && ( tabIndex={4}
<span> className={cx(
<a tabIndex={5} href={url.loginResetCredentialsUrl}> kcProps.kcButtonClass,
{msg("doForgotPassword")} kcProps.kcButtonPrimaryClass,
</a> kcProps.kcButtonBlockClass,
</span> kcProps.kcButtonLargeClass
)} )}
</div> name="login"
</div> id="kc-login"
<div id="kc-form-buttons" className={cx(props.kcFormGroupClass)}> type="submit"
<input value={msgStr("doLogIn")}
tabIndex={4} disabled={isLoginButtonDisabled}
className={cx( />
props.kcButtonClass, </div>
props.kcButtonPrimaryClass, </form>
props.kcButtonBlockClass,
props.kcButtonLargeClass
)}
name="login"
id="kc-login"
type="submit"
value={msgStr("doLogIn")}
disabled={isLoginButtonDisabled}
/>
</div>
</form>
</div>
</div> </div>
} </div>
/> }
); />
} );
); });
export default LoginPassword; export default LoginPassword;

View File

@ -1,79 +1,82 @@
import React, { memo } from "react"; import React, { memo } from "react";
import Template from "./Template"; import DefaultTemplate from "./Template";
import type { TemplateProps } from "./Template";
import type { KcProps } from "./KcProps"; import type { KcProps } from "./KcProps";
import type { KcContextBase } from "../getKcContext/KcContextBase"; import type { KcContextBase } from "../getKcContext/KcContextBase";
import { useCssAndCx } from "../tools/useCssAndCx"; import { useCssAndCx } from "../tools/useCssAndCx";
import type { I18n } from "../i18n"; import type { I18n } from "../i18n";
const LoginResetPassword = memo( export type LoginResetPasswordProps = KcProps & {
({ kcContext: KcContextBase.LoginResetPassword;
kcContext, i18n: I18n;
i18n, doFetchDefaultThemeResources?: boolean;
doFetchDefaultThemeResources = true, Template?: (props: TemplateProps) => JSX.Element | null;
...props };
}: { kcContext: KcContextBase.LoginResetPassword; i18n: I18n; doFetchDefaultThemeResources?: boolean } & KcProps) => {
const { url, realm, auth } = kcContext;
const { msg, msgStr } = i18n; const LoginResetPassword = memo((props: LoginResetPasswordProps) => {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template = DefaultTemplate, ...kcProps } = props;
const { cx } = useCssAndCx(); const { url, realm, auth } = kcContext;
return ( const { msg, msgStr } = i18n;
<Template
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...props }} const { cx } = useCssAndCx();
displayMessage={false}
headerNode={msg("emailForgotTitle")} return (
formNode={ <Template
<form id="kc-reset-password-form" className={cx(props.kcFormClass)} action={url.loginAction} method="post"> {...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
<div className={cx(props.kcFormGroupClass)}> displayMessage={false}
<div className={cx(props.kcLabelWrapperClass)}> headerNode={msg("emailForgotTitle")}
<label htmlFor="username" className={cx(props.kcLabelClass)}> formNode={
{!realm.loginWithEmailAllowed <form id="kc-reset-password-form" className={cx(kcProps.kcFormClass)} action={url.loginAction} method="post">
? msg("username") <div className={cx(kcProps.kcFormGroupClass)}>
: !realm.registrationEmailAsUsername <div className={cx(kcProps.kcLabelWrapperClass)}>
? msg("usernameOrEmail") <label htmlFor="username" className={cx(kcProps.kcLabelClass)}>
: msg("email")} {!realm.loginWithEmailAllowed
</label> ? msg("username")
</div> : !realm.registrationEmailAsUsername
<div className={cx(props.kcInputWrapperClass)}> ? msg("usernameOrEmail")
<input : msg("email")}
type="text" </label>
id="username" </div>
name="username" <div className={cx(kcProps.kcInputWrapperClass)}>
className={cx(props.kcInputClass)} <input
autoFocus type="text"
defaultValue={auth !== undefined && auth.showUsername ? auth.attemptedUsername : undefined} id="username"
/> name="username"
className={cx(kcProps.kcInputClass)}
autoFocus
defaultValue={auth !== undefined && auth.showUsername ? auth.attemptedUsername : undefined}
/>
</div>
</div>
<div className={cx(kcProps.kcFormGroupClass, kcProps.kcFormSettingClass)}>
<div id="kc-form-options" className={cx(kcProps.kcFormOptionsClass)}>
<div className={cx(kcProps.kcFormOptionsWrapperClass)}>
<span>
<a href={url.loginUrl}>{msg("backToLogin")}</a>
</span>
</div> </div>
</div> </div>
<div className={cx(props.kcFormGroupClass, props.kcFormSettingClass)}>
<div id="kc-form-options" className={cx(props.kcFormOptionsClass)}>
<div className={cx(props.kcFormOptionsWrapperClass)}>
<span>
<a href={url.loginUrl}>{msg("backToLogin")}</a>
</span>
</div>
</div>
<div id="kc-form-buttons" className={cx(props.kcFormButtonsClass)}> <div id="kc-form-buttons" className={cx(kcProps.kcFormButtonsClass)}>
<input <input
className={cx( className={cx(
props.kcButtonClass, kcProps.kcButtonClass,
props.kcButtonPrimaryClass, kcProps.kcButtonPrimaryClass,
props.kcButtonBlockClass, kcProps.kcButtonBlockClass,
props.kcButtonLargeClass kcProps.kcButtonLargeClass
)} )}
type="submit" type="submit"
value={msgStr("doSubmit")} value={msgStr("doSubmit")}
/> />
</div>
</div> </div>
</form> </div>
} </form>
infoNode={msg("emailInstruction")} }
/> infoNode={msg("emailInstruction")}
); />
} );
); });
export default LoginResetPassword; export default LoginResetPassword;

View File

@ -1,125 +1,128 @@
import React, { memo } from "react"; import React, { memo } from "react";
import Template from "./Template"; import DefaultTemplate from "./Template";
import type { TemplateProps } from "./Template";
import type { KcProps } from "./KcProps"; import type { KcProps } from "./KcProps";
import type { KcContextBase } from "../getKcContext/KcContextBase"; import type { KcContextBase } from "../getKcContext/KcContextBase";
import { useCssAndCx } from "../tools/useCssAndCx"; import { useCssAndCx } from "../tools/useCssAndCx";
import type { I18n } from "../i18n"; import type { I18n } from "../i18n";
const LoginUpdatePassword = memo( export type LoginUpdatePasswordProps = KcProps & {
({ kcContext: KcContextBase.LoginUpdatePassword;
kcContext, i18n: I18n;
i18n, doFetchDefaultThemeResources?: boolean;
doFetchDefaultThemeResources = true, Template?: (props: TemplateProps) => JSX.Element | null;
...props };
}: { kcContext: KcContextBase.LoginUpdatePassword; i18n: I18n; doFetchDefaultThemeResources?: boolean } & KcProps) => {
const { cx } = useCssAndCx();
const { msg, msgStr } = i18n; const LoginUpdatePassword = memo((props: LoginUpdatePasswordProps) => {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template = DefaultTemplate, ...kcProps } = props;
const { url, messagesPerField, isAppInitiatedAction, username } = kcContext; const { cx } = useCssAndCx();
return ( const { msg, msgStr } = i18n;
<Template
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...props }}
headerNode={msg("updatePasswordTitle")}
formNode={
<form id="kc-passwd-update-form" className={cx(props.kcFormClass)} action={url.loginAction} method="post">
<input
type="text"
id="username"
name="username"
value={username}
readOnly={true}
autoComplete="username"
style={{ display: "none" }}
/>
<input type="password" id="password" name="password" autoComplete="current-password" style={{ display: "none" }} />
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("password", props.kcFormGroupErrorClass))}> const { url, messagesPerField, isAppInitiatedAction, username } = kcContext;
<div className={cx(props.kcLabelWrapperClass)}>
<label htmlFor="password-new" className={cx(props.kcLabelClass)}> return (
{msg("passwordNew")} <Template
</label> {...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
</div> headerNode={msg("updatePasswordTitle")}
<div className={cx(props.kcInputWrapperClass)}> formNode={
<input <form id="kc-passwd-update-form" className={cx(kcProps.kcFormClass)} action={url.loginAction} method="post">
type="password" <input
id="password-new" type="text"
name="password-new" id="username"
autoFocus name="username"
autoComplete="new-password" value={username}
className={cx(props.kcInputClass)} readOnly={true}
/> autoComplete="username"
</div> style={{ display: "none" }}
/>
<input type="password" id="password" name="password" autoComplete="current-password" style={{ display: "none" }} />
<div className={cx(kcProps.kcFormGroupClass, messagesPerField.printIfExists("password", kcProps.kcFormGroupErrorClass))}>
<div className={cx(kcProps.kcLabelWrapperClass)}>
<label htmlFor="password-new" className={cx(kcProps.kcLabelClass)}>
{msg("passwordNew")}
</label>
</div> </div>
<div className={cx(kcProps.kcInputWrapperClass)}>
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("password-confirm", props.kcFormGroupErrorClass))}> <input
<div className={cx(props.kcLabelWrapperClass)}> type="password"
<label htmlFor="password-confirm" className={cx(props.kcLabelClass)}> id="password-new"
{msg("passwordConfirm")} name="password-new"
</label> autoFocus
</div> autoComplete="new-password"
<div className={cx(props.kcInputWrapperClass)}> className={cx(kcProps.kcInputClass)}
<input />
type="password"
id="password-confirm"
name="password-confirm"
autoComplete="new-password"
className={cx(props.kcInputClass)}
/>
</div>
</div> </div>
</div>
<div className={cx(props.kcFormGroupClass)}> <div className={cx(kcProps.kcFormGroupClass, messagesPerField.printIfExists("password-confirm", kcProps.kcFormGroupErrorClass))}>
<div id="kc-form-options" className={cx(props.kcFormOptionsClass)}> <div className={cx(kcProps.kcLabelWrapperClass)}>
<div className={cx(props.kcFormOptionsWrapperClass)}> <label htmlFor="password-confirm" className={cx(kcProps.kcLabelClass)}>
{isAppInitiatedAction && ( {msg("passwordConfirm")}
<div className="checkbox"> </label>
<label> </div>
<input type="checkbox" id="logout-sessions" name="logout-sessions" value="on" checked /> <div className={cx(kcProps.kcInputWrapperClass)}>
{msgStr("logoutOtherSessions")} <input
</label> type="password"
</div> id="password-confirm"
)} name="password-confirm"
</div> autoComplete="new-password"
</div> className={cx(kcProps.kcInputClass)}
/>
</div>
</div>
<div id="kc-form-buttons" className={cx(props.kcFormButtonsClass)}> <div className={cx(kcProps.kcFormGroupClass)}>
{isAppInitiatedAction ? ( <div id="kc-form-options" className={cx(kcProps.kcFormOptionsClass)}>
<> <div className={cx(kcProps.kcFormOptionsWrapperClass)}>
<input {isAppInitiatedAction && (
className={cx(props.kcButtonClass, props.kcButtonPrimaryClass, props.kcButtonLargeClass)} <div className="checkbox">
type="submit" <label>
defaultValue={msgStr("doSubmit")} <input type="checkbox" id="logout-sessions" name="logout-sessions" value="on" checked />
/> {msgStr("logoutOtherSessions")}
<button </label>
className={cx(props.kcButtonClass, props.kcButtonDefaultClass, props.kcButtonLargeClass)} </div>
type="submit"
name="cancel-aia"
value="true"
>
{msg("doCancel")}
</button>
</>
) : (
<input
className={cx(
props.kcButtonClass,
props.kcButtonPrimaryClass,
props.kcButtonBlockClass,
props.kcButtonLargeClass
)}
type="submit"
defaultValue={msgStr("doSubmit")}
/>
)} )}
</div> </div>
</div> </div>
</form>
} <div id="kc-form-buttons" className={cx(kcProps.kcFormButtonsClass)}>
/> {isAppInitiatedAction ? (
); <>
} <input
); className={cx(kcProps.kcButtonClass, kcProps.kcButtonPrimaryClass, kcProps.kcButtonLargeClass)}
type="submit"
defaultValue={msgStr("doSubmit")}
/>
<button
className={cx(kcProps.kcButtonClass, kcProps.kcButtonDefaultClass, kcProps.kcButtonLargeClass)}
type="submit"
name="cancel-aia"
value="true"
>
{msg("doCancel")}
</button>
</>
) : (
<input
className={cx(
kcProps.kcButtonClass,
kcProps.kcButtonPrimaryClass,
kcProps.kcButtonBlockClass,
kcProps.kcButtonLargeClass
)}
type="submit"
defaultValue={msgStr("doSubmit")}
/>
)}
</div>
</div>
</form>
}
/>
);
});
export default LoginUpdatePassword; export default LoginUpdatePassword;

View File

@ -1,134 +1,137 @@
import React, { memo } from "react"; import React, { memo } from "react";
import Template from "./Template"; import DefaultTemplate from "./Template";
import type { TemplateProps } from "./Template";
import type { KcProps } from "./KcProps"; import type { KcProps } from "./KcProps";
import type { KcContextBase } from "../getKcContext/KcContextBase"; import type { KcContextBase } from "../getKcContext/KcContextBase";
import { useCssAndCx } from "../tools/useCssAndCx"; import { useCssAndCx } from "../tools/useCssAndCx";
import type { I18n } from "../i18n"; import type { I18n } from "../i18n";
const LoginUpdateProfile = memo( export type LoginUpdateProfile = KcProps & {
({ kcContext: KcContextBase.LoginUpdateProfile;
kcContext, i18n: I18n;
i18n, doFetchDefaultThemeResources?: boolean;
doFetchDefaultThemeResources = true, Template?: (props: TemplateProps) => JSX.Element | null;
...props };
}: { kcContext: KcContextBase.LoginUpdateProfile; i18n: I18n; doFetchDefaultThemeResources?: boolean } & KcProps) => {
const { cx } = useCssAndCx();
const { msg, msgStr } = i18n; const LoginUpdateProfile = memo((props: LoginUpdateProfile) => {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template = DefaultTemplate, ...kcProps } = props;
const { url, user, messagesPerField, isAppInitiatedAction } = kcContext; const { cx } = useCssAndCx();
return ( const { msg, msgStr } = i18n;
<Template
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...props }}
headerNode={msg("loginProfileTitle")}
formNode={
<form id="kc-update-profile-form" className={cx(props.kcFormClass)} action={url.loginAction} method="post">
{user.editUsernameAllowed && (
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("username", props.kcFormGroupErrorClass))}>
<div className={cx(props.kcLabelWrapperClass)}>
<label htmlFor="username" className={cx(props.kcLabelClass)}>
{msg("username")}
</label>
</div>
<div className={cx(props.kcInputWrapperClass)}>
<input
type="text"
id="username"
name="username"
defaultValue={user.username ?? ""}
className={cx(props.kcInputClass)}
/>
</div>
</div>
)}
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("email", props.kcFormGroupErrorClass))}> const { url, user, messagesPerField, isAppInitiatedAction } = kcContext;
<div className={cx(props.kcLabelWrapperClass)}>
<label htmlFor="email" className={cx(props.kcLabelClass)}> return (
{msg("email")} <Template
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
headerNode={msg("loginProfileTitle")}
formNode={
<form id="kc-update-profile-form" className={cx(kcProps.kcFormClass)} action={url.loginAction} method="post">
{user.editUsernameAllowed && (
<div className={cx(kcProps.kcFormGroupClass, messagesPerField.printIfExists("username", kcProps.kcFormGroupErrorClass))}>
<div className={cx(kcProps.kcLabelWrapperClass)}>
<label htmlFor="username" className={cx(kcProps.kcLabelClass)}>
{msg("username")}
</label> </label>
</div> </div>
<div className={cx(props.kcInputWrapperClass)}> <div className={cx(kcProps.kcInputWrapperClass)}>
<input type="text" id="email" name="email" defaultValue={user.email ?? ""} className={cx(props.kcInputClass)} />
</div>
</div>
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("firstName", props.kcFormGroupErrorClass))}>
<div className={cx(props.kcLabelWrapperClass)}>
<label htmlFor="firstName" className={cx(props.kcLabelClass)}>
{msg("firstName")}
</label>
</div>
<div className={cx(props.kcInputWrapperClass)}>
<input <input
type="text" type="text"
id="firstName" id="username"
name="firstName" name="username"
defaultValue={user.firstName ?? ""} defaultValue={user.username ?? ""}
className={cx(props.kcInputClass)} className={cx(kcProps.kcInputClass)}
/> />
</div> </div>
</div> </div>
)}
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("lastName", props.kcFormGroupErrorClass))}> <div className={cx(kcProps.kcFormGroupClass, messagesPerField.printIfExists("email", kcProps.kcFormGroupErrorClass))}>
<div className={cx(props.kcLabelWrapperClass)}> <div className={cx(kcProps.kcLabelWrapperClass)}>
<label htmlFor="lastName" className={cx(props.kcLabelClass)}> <label htmlFor="email" className={cx(kcProps.kcLabelClass)}>
{msg("lastName")} {msg("email")}
</label> </label>
</div> </div>
<div className={cx(props.kcInputWrapperClass)}> <div className={cx(kcProps.kcInputWrapperClass)}>
<input <input type="text" id="email" name="email" defaultValue={user.email ?? ""} className={cx(kcProps.kcInputClass)} />
type="text" </div>
id="lastName" </div>
name="lastName"
defaultValue={user.lastName ?? ""} <div className={cx(kcProps.kcFormGroupClass, messagesPerField.printIfExists("firstName", kcProps.kcFormGroupErrorClass))}>
className={cx(props.kcInputClass)} <div className={cx(kcProps.kcLabelWrapperClass)}>
/> <label htmlFor="firstName" className={cx(kcProps.kcLabelClass)}>
</div> {msg("firstName")}
</label>
</div>
<div className={cx(kcProps.kcInputWrapperClass)}>
<input
type="text"
id="firstName"
name="firstName"
defaultValue={user.firstName ?? ""}
className={cx(kcProps.kcInputClass)}
/>
</div>
</div>
<div className={cx(kcProps.kcFormGroupClass, messagesPerField.printIfExists("lastName", kcProps.kcFormGroupErrorClass))}>
<div className={cx(kcProps.kcLabelWrapperClass)}>
<label htmlFor="lastName" className={cx(kcProps.kcLabelClass)}>
{msg("lastName")}
</label>
</div>
<div className={cx(kcProps.kcInputWrapperClass)}>
<input
type="text"
id="lastName"
name="lastName"
defaultValue={user.lastName ?? ""}
className={cx(kcProps.kcInputClass)}
/>
</div>
</div>
<div className={cx(kcProps.kcFormGroupClass)}>
<div id="kc-form-options" className={cx(kcProps.kcFormOptionsClass)}>
<div className={cx(kcProps.kcFormOptionsWrapperClass)} />
</div> </div>
<div className={cx(props.kcFormGroupClass)}> <div id="kc-form-buttons" className={cx(kcProps.kcFormButtonsClass)}>
<div id="kc-form-options" className={cx(props.kcFormOptionsClass)}> {isAppInitiatedAction ? (
<div className={cx(props.kcFormOptionsWrapperClass)} /> <>
</div>
<div id="kc-form-buttons" className={cx(props.kcFormButtonsClass)}>
{isAppInitiatedAction ? (
<>
<input
className={cx(props.kcButtonClass, props.kcButtonPrimaryClass, props.kcButtonLargeClass)}
type="submit"
defaultValue={msgStr("doSubmit")}
/>
<button
className={cx(props.kcButtonClass, props.kcButtonDefaultClass, props.kcButtonLargeClass)}
type="submit"
name="cancel-aia"
value="true"
>
{msg("doCancel")}
</button>
</>
) : (
<input <input
className={cx( className={cx(kcProps.kcButtonClass, kcProps.kcButtonPrimaryClass, kcProps.kcButtonLargeClass)}
props.kcButtonClass,
props.kcButtonPrimaryClass,
props.kcButtonBlockClass,
props.kcButtonLargeClass
)}
type="submit" type="submit"
defaultValue={msgStr("doSubmit")} defaultValue={msgStr("doSubmit")}
/> />
)} <button
</div> className={cx(kcProps.kcButtonClass, kcProps.kcButtonDefaultClass, kcProps.kcButtonLargeClass)}
type="submit"
name="cancel-aia"
value="true"
>
{msg("doCancel")}
</button>
</>
) : (
<input
className={cx(
kcProps.kcButtonClass,
kcProps.kcButtonPrimaryClass,
kcProps.kcButtonBlockClass,
kcProps.kcButtonLargeClass
)}
type="submit"
defaultValue={msgStr("doSubmit")}
/>
)}
</div> </div>
</form> </div>
} </form>
/> }
); />
} );
); });
export default LoginUpdateProfile; export default LoginUpdateProfile;

View File

@ -1,5 +1,6 @@
import React, { useState, memo } from "react"; import React, { useState, memo } from "react";
import Template from "./Template"; import DefaultTemplate from "./Template";
import type { TemplateProps } from "./Template";
import type { KcProps } from "./KcProps"; import type { KcProps } from "./KcProps";
import type { KcContextBase } from "../getKcContext/KcContextBase"; import type { KcContextBase } from "../getKcContext/KcContextBase";
import { useCssAndCx } from "../tools/useCssAndCx"; import { useCssAndCx } from "../tools/useCssAndCx";
@ -7,162 +8,164 @@ import { useConstCallback } from "powerhooks/useConstCallback";
import type { FormEventHandler } from "react"; import type { FormEventHandler } from "react";
import type { I18n } from "../i18n"; import type { I18n } from "../i18n";
const LoginUsername = memo( export type LoginUsernameProps = KcProps & {
({ kcContext: KcContextBase.LoginUsername;
kcContext, i18n: I18n;
i18n, doFetchDefaultThemeResources?: boolean;
doFetchDefaultThemeResources = true, Template?: (props: TemplateProps) => JSX.Element | null;
...props };
}: { kcContext: KcContextBase.LoginUsername; i18n: I18n; doFetchDefaultThemeResources?: boolean } & KcProps) => {
const { social, realm, url, usernameHidden, login, registrationDisabled } = kcContext;
const { msg, msgStr } = i18n; const LoginUsername = memo((props: LoginUsernameProps) => {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template = DefaultTemplate, ...kcProps } = props;
const { cx } = useCssAndCx(); const { social, realm, url, usernameHidden, login, registrationDisabled } = kcContext;
const [isLoginButtonDisabled, setIsLoginButtonDisabled] = useState(false); const { msg, msgStr } = i18n;
const onSubmit = useConstCallback<FormEventHandler<HTMLFormElement>>(e => { const { cx } = useCssAndCx();
e.preventDefault();
setIsLoginButtonDisabled(true); const [isLoginButtonDisabled, setIsLoginButtonDisabled] = useState(false);
const formElement = e.target as HTMLFormElement; const onSubmit = useConstCallback<FormEventHandler<HTMLFormElement>>(e => {
e.preventDefault();
//NOTE: Even if we login with email Keycloak expect username and password in setIsLoginButtonDisabled(true);
//the POST request.
formElement.querySelector("input[name='email']")?.setAttribute("name", "username");
formElement.submit(); const formElement = e.target as HTMLFormElement;
});
return ( //NOTE: Even if we login with email Keycloak expect username and password in
<Template //the POST request.
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...props }} formElement.querySelector("input[name='email']")?.setAttribute("name", "username");
displayInfo={social.displayInfo}
displayWide={realm.password && social.providers !== undefined}
headerNode={msg("doLogIn")}
formNode={
<div id="kc-form" className={cx(realm.password && social.providers !== undefined && props.kcContentWrapperClass)}>
<div
id="kc-form-wrapper"
className={cx(
realm.password && social.providers && [props.kcFormSocialAccountContentClass, props.kcFormSocialAccountClass]
)}
>
{realm.password && (
<form id="kc-form-login" onSubmit={onSubmit} action={url.loginAction} method="post">
<div className={cx(props.kcFormGroupClass)}>
{!usernameHidden &&
(() => {
const label = !realm.loginWithEmailAllowed
? "username"
: realm.registrationEmailAsUsername
? "email"
: "usernameOrEmail";
const autoCompleteHelper: typeof label = label === "usernameOrEmail" ? "username" : label; formElement.submit();
});
return ( return (
<> <Template
<label htmlFor={autoCompleteHelper} className={cx(props.kcLabelClass)}> {...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
{msg(label)} displayInfo={social.displayInfo}
</label> displayWide={realm.password && social.providers !== undefined}
<input headerNode={msg("doLogIn")}
tabIndex={1} formNode={
id={autoCompleteHelper} <div id="kc-form" className={cx(realm.password && social.providers !== undefined && kcProps.kcContentWrapperClass)}>
className={cx(props.kcInputClass)} <div
//NOTE: This is used by Google Chrome auto fill so we use it to tell id="kc-form-wrapper"
//the browser how to pre fill the form but before submit we put it back className={cx(
//to username because it is what keycloak expects. realm.password && social.providers && [kcProps.kcFormSocialAccountContentClass, kcProps.kcFormSocialAccountClass]
name={autoCompleteHelper} )}
defaultValue={login.username ?? ""} >
type="text" {realm.password && (
autoFocus={true} <form id="kc-form-login" onSubmit={onSubmit} action={url.loginAction} method="post">
autoComplete="off" <div className={cx(kcProps.kcFormGroupClass)}>
/> {!usernameHidden &&
</> (() => {
); const label = !realm.loginWithEmailAllowed
})()} ? "username"
</div> : realm.registrationEmailAsUsername
<div className={cx(props.kcFormGroupClass, props.kcFormSettingClass)}> ? "email"
<div id="kc-form-options"> : "usernameOrEmail";
{realm.rememberMe && !usernameHidden && (
<div className="checkbox"> const autoCompleteHelper: typeof label = label === "usernameOrEmail" ? "username" : label;
<label>
<input return (
tabIndex={3} <>
id="rememberMe" <label htmlFor={autoCompleteHelper} className={cx(kcProps.kcLabelClass)}>
name="rememberMe" {msg(label)}
type="checkbox"
{...(login.rememberMe
? {
"checked": true
}
: {})}
/>
{msg("rememberMe")}
</label> </label>
</div> <input
)} tabIndex={1}
</div> id={autoCompleteHelper}
className={cx(kcProps.kcInputClass)}
//NOTE: This is used by Google Chrome auto fill so we use it to tell
//the browser how to pre fill the form but before submit we put it back
//to username because it is what keycloak expects.
name={autoCompleteHelper}
defaultValue={login.username ?? ""}
type="text"
autoFocus={true}
autoComplete="off"
/>
</>
);
})()}
</div>
<div className={cx(kcProps.kcFormGroupClass, kcProps.kcFormSettingClass)}>
<div id="kc-form-options">
{realm.rememberMe && !usernameHidden && (
<div className="checkbox">
<label>
<input
tabIndex={3}
id="rememberMe"
name="rememberMe"
type="checkbox"
{...(login.rememberMe
? {
"checked": true
}
: {})}
/>
{msg("rememberMe")}
</label>
</div>
)}
</div> </div>
<div id="kc-form-buttons" className={cx(props.kcFormGroupClass)}> </div>
<input <div id="kc-form-buttons" className={cx(kcProps.kcFormGroupClass)}>
tabIndex={4} <input
className={cx( tabIndex={4}
props.kcButtonClass, className={cx(
props.kcButtonPrimaryClass, kcProps.kcButtonClass,
props.kcButtonBlockClass, kcProps.kcButtonPrimaryClass,
props.kcButtonLargeClass kcProps.kcButtonBlockClass,
)} kcProps.kcButtonLargeClass
name="login" )}
id="kc-login" name="login"
type="submit" id="kc-login"
value={msgStr("doLogIn")} type="submit"
disabled={isLoginButtonDisabled} value={msgStr("doLogIn")}
/> disabled={isLoginButtonDisabled}
</div> />
</form> </div>
)} </form>
</div>
{realm.password && social.providers !== undefined && (
<div id="kc-social-providers" className={cx(props.kcFormSocialAccountContentClass, props.kcFormSocialAccountClass)}>
<ul
className={cx(
props.kcFormSocialAccountListClass,
social.providers.length > 4 && props.kcFormSocialAccountDoubleListClass
)}
>
{social.providers.map(p => (
<li key={p.providerId} className={cx(props.kcFormSocialAccountListLinkClass)}>
<a href={p.loginUrl} id={`zocial-${p.alias}`} className={cx("zocial", p.providerId)}>
<span>{p.displayName}</span>
</a>
</li>
))}
</ul>
</div>
)} )}
</div> </div>
} {realm.password && social.providers !== undefined && (
infoNode={ <div id="kc-social-providers" className={cx(kcProps.kcFormSocialAccountContentClass, kcProps.kcFormSocialAccountClass)}>
realm.password && <ul
realm.registrationAllowed && className={cx(
!registrationDisabled && ( kcProps.kcFormSocialAccountListClass,
<div id="kc-registration"> social.providers.length > 4 && kcProps.kcFormSocialAccountDoubleListClass
<span> )}
{msg("noAccount")} >
<a tabIndex={6} href={url.registrationUrl}> {social.providers.map(p => (
{msg("doRegister")} <li key={p.providerId} className={cx(kcProps.kcFormSocialAccountListLinkClass)}>
</a> <a href={p.loginUrl} id={`zocial-${p.alias}`} className={cx("zocial", p.providerId)}>
</span> <span>{p.displayName}</span>
</a>
</li>
))}
</ul>
</div> </div>
) )}
} </div>
/> }
); infoNode={
} realm.password &&
); realm.registrationAllowed &&
!registrationDisabled && (
<div id="kc-registration">
<span>
{msg("noAccount")}
<a tabIndex={6} href={url.registrationUrl}>
{msg("doRegister")}
</a>
</span>
</div>
)
}
/>
);
});
export default LoginUsername; export default LoginUsername;

View File

@ -1,40 +1,43 @@
import React, { memo } from "react"; import React, { memo } from "react";
import Template from "./Template"; import DefaultTemplate from "./Template";
import type { TemplateProps } from "./Template";
import type { KcProps } from "./KcProps"; import type { KcProps } from "./KcProps";
import type { KcContextBase } from "../getKcContext/KcContextBase"; import type { KcContextBase } from "../getKcContext/KcContextBase";
import type { I18n } from "../i18n"; import type { I18n } from "../i18n";
const LoginVerifyEmail = memo( export type LoginVerifyEmailProps = KcProps & {
({ kcContext: KcContextBase.LoginVerifyEmail;
kcContext, i18n: I18n;
i18n, doFetchDefaultThemeResources?: boolean;
doFetchDefaultThemeResources = true, Template?: (props: TemplateProps) => JSX.Element | null;
...props };
}: { kcContext: KcContextBase.LoginVerifyEmail; i18n: I18n; doFetchDefaultThemeResources?: boolean } & KcProps) => {
const { msg } = i18n;
const { url, user } = kcContext; const LoginVerifyEmail = memo((props: LoginVerifyEmailProps) => {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template = DefaultTemplate, ...kcProps } = props;
return ( const { msg } = i18n;
<Template
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...props }} const { url, user } = kcContext;
displayMessage={false}
headerNode={msg("emailVerifyTitle")} return (
formNode={ <Template
<> {...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
<p className="instruction">{msg("emailVerifyInstruction1", user?.email)}</p> displayMessage={false}
<p className="instruction"> headerNode={msg("emailVerifyTitle")}
{msg("emailVerifyInstruction2")} formNode={
<br /> <>
<a href={url.loginAction}>{msg("doClickHere")}</a> <p className="instruction">{msg("emailVerifyInstruction1", user?.email)}</p>
&nbsp; <p className="instruction">
{msg("emailVerifyInstruction3")} {msg("emailVerifyInstruction2")}
</p> <br />
</> <a href={url.loginAction}>{msg("doClickHere")}</a>
} &nbsp;
/> {msg("emailVerifyInstruction3")}
); </p>
} </>
); }
/>
);
});
export default LoginVerifyEmail; export default LoginVerifyEmail;

View File

@ -1,68 +1,71 @@
import React, { memo } from "react"; import React, { memo } from "react";
import { useCssAndCx } from "../tools/useCssAndCx"; import { useCssAndCx } from "../tools/useCssAndCx";
import Template from "./Template"; import DefaultTemplate from "./Template";
import type { TemplateProps } from "./Template";
import type { KcProps } from "./KcProps"; import type { KcProps } from "./KcProps";
import type { KcContextBase } from "../getKcContext/KcContextBase"; import type { KcContextBase } from "../getKcContext/KcContextBase";
import type { I18n } from "../i18n"; import type { I18n } from "../i18n";
const LogoutConfirm = memo( export type LogoutConfirmProps = KcProps & {
({ kcContext: KcContextBase.LogoutConfirm;
kcContext, i18n: I18n;
i18n, doFetchDefaultThemeResources?: boolean;
doFetchDefaultThemeResources = true, Template?: (props: TemplateProps) => JSX.Element | null;
...props };
}: { kcContext: KcContextBase.LogoutConfirm; i18n: I18n; doFetchDefaultThemeResources?: boolean } & KcProps) => {
const { url, client, logoutConfirm } = kcContext;
const { cx } = useCssAndCx(); const LogoutConfirm = memo((props: LogoutConfirmProps) => {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template = DefaultTemplate, ...kcProps } = props;
const { msg, msgStr } = i18n; const { url, client, logoutConfirm } = kcContext;
return ( const { cx } = useCssAndCx();
<Template
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...props }} const { msg, msgStr } = i18n;
displayMessage={false}
headerNode={msg("logoutConfirmTitle")} return (
formNode={ <Template
<> {...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
<div id="kc-logout-confirm" className="content-area"> displayMessage={false}
<p className="instruction">{msg("logoutConfirmHeader")}</p> headerNode={msg("logoutConfirmTitle")}
<form className="form-actions" action={url.logoutConfirmAction} method="POST"> formNode={
<input type="hidden" name="session_code" value={logoutConfirm.code} /> <>
<div className={cx(props.kcFormGroupClass)}> <div id="kc-logout-confirm" className="content-area">
<div id="kc-form-options"> <p className="instruction">{msg("logoutConfirmHeader")}</p>
<div className={cx(props.kcFormOptionsWrapperClass)}></div> <form className="form-actions" action={url.logoutConfirmAction} method="POST">
</div> <input type="hidden" name="session_code" value={logoutConfirm.code} />
<div id="kc-form-buttons" className={cx(props.kcFormGroupClass)}> <div className={cx(kcProps.kcFormGroupClass)}>
<input <div id="kc-form-options">
tabIndex={4} <div className={cx(kcProps.kcFormOptionsWrapperClass)}></div>
className={cx( </div>
props.kcButtonClass, <div id="kc-form-buttons" className={cx(kcProps.kcFormGroupClass)}>
props.kcButtonPrimaryClass, <input
props.kcButtonBlockClass, tabIndex={4}
props.kcButtonLargeClass className={cx(
)} kcProps.kcButtonClass,
name="confirmLogout" kcProps.kcButtonPrimaryClass,
id="kc-logout" kcProps.kcButtonBlockClass,
type="submit" kcProps.kcButtonLargeClass
value={msgStr("doLogout")} )}
/> name="confirmLogout"
</div> id="kc-logout"
type="submit"
value={msgStr("doLogout")}
/>
</div> </div>
</form>
<div id="kc-info-message">
{!logoutConfirm.skipLink && client.baseUrl && (
<p>
<a href={client.baseUrl} dangerouslySetInnerHTML={{ __html: msgStr("backToApplication") }} />
</p>
)}
</div> </div>
</form>
<div id="kc-info-message">
{!logoutConfirm.skipLink && client.baseUrl && (
<p>
<a href={client.baseUrl} dangerouslySetInnerHTML={{ __html: msgStr("backToApplication") }} />
</p>
)}
</div> </div>
</> </div>
} </>
/> }
); />
} );
); });
export default LogoutConfirm; export default LogoutConfirm;

View File

@ -1,169 +1,172 @@
import React, { memo } from "react"; import React, { memo } from "react";
import Template from "./Template"; import DefaultTemplate from "./Template";
import type { TemplateProps } from "./Template";
import type { KcProps } from "./KcProps"; import type { KcProps } from "./KcProps";
import type { KcContextBase } from "../getKcContext/KcContextBase"; import type { KcContextBase } from "../getKcContext/KcContextBase";
import { useCssAndCx } from "../tools/useCssAndCx"; import { useCssAndCx } from "../tools/useCssAndCx";
import type { I18n } from "../i18n"; import type { I18n } from "../i18n";
const Register = memo( export type RegisterProps = KcProps & {
({ kcContext: KcContextBase.Register;
kcContext, i18n: I18n;
i18n, doFetchDefaultThemeResources?: boolean;
doFetchDefaultThemeResources = true, Template?: (props: TemplateProps) => JSX.Element | null;
...props };
}: { kcContext: KcContextBase.Register; i18n: I18n; doFetchDefaultThemeResources?: boolean } & KcProps) => {
const { url, messagesPerField, register, realm, passwordRequired, recaptchaRequired, recaptchaSiteKey } = kcContext;
const { msg, msgStr } = i18n; const Register = memo((props: RegisterProps) => {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template = DefaultTemplate, ...kcProps } = props;
const { cx } = useCssAndCx(); const { url, messagesPerField, register, realm, passwordRequired, recaptchaRequired, recaptchaSiteKey } = kcContext;
return ( const { msg, msgStr } = i18n;
<Template
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...props }} const { cx } = useCssAndCx();
headerNode={msg("registerTitle")}
formNode={ return (
<form id="kc-register-form" className={cx(props.kcFormClass)} action={url.registrationAction} method="post"> <Template
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("firstName", props.kcFormGroupErrorClass))}> {...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
<div className={cx(props.kcLabelWrapperClass)}> headerNode={msg("registerTitle")}
<label htmlFor="firstName" className={cx(props.kcLabelClass)}> formNode={
{msg("firstName")} <form id="kc-register-form" className={cx(kcProps.kcFormClass)} action={url.registrationAction} method="post">
<div className={cx(kcProps.kcFormGroupClass, messagesPerField.printIfExists("firstName", kcProps.kcFormGroupErrorClass))}>
<div className={cx(kcProps.kcLabelWrapperClass)}>
<label htmlFor="firstName" className={cx(kcProps.kcLabelClass)}>
{msg("firstName")}
</label>
</div>
<div className={cx(kcProps.kcInputWrapperClass)}>
<input
type="text"
id="firstName"
className={cx(kcProps.kcInputClass)}
name="firstName"
defaultValue={register.formData.firstName ?? ""}
/>
</div>
</div>
<div className={cx(kcProps.kcFormGroupClass, messagesPerField.printIfExists("lastName", kcProps.kcFormGroupErrorClass))}>
<div className={cx(kcProps.kcLabelWrapperClass)}>
<label htmlFor="lastName" className={cx(kcProps.kcLabelClass)}>
{msg("lastName")}
</label>
</div>
<div className={cx(kcProps.kcInputWrapperClass)}>
<input
type="text"
id="lastName"
className={cx(kcProps.kcInputClass)}
name="lastName"
defaultValue={register.formData.lastName ?? ""}
/>
</div>
</div>
<div className={cx(kcProps.kcFormGroupClass, messagesPerField.printIfExists("email", kcProps.kcFormGroupErrorClass))}>
<div className={cx(kcProps.kcLabelWrapperClass)}>
<label htmlFor="email" className={cx(kcProps.kcLabelClass)}>
{msg("email")}
</label>
</div>
<div className={cx(kcProps.kcInputWrapperClass)}>
<input
type="text"
id="email"
className={cx(kcProps.kcInputClass)}
name="email"
defaultValue={register.formData.email ?? ""}
autoComplete="email"
/>
</div>
</div>
{!realm.registrationEmailAsUsername && (
<div className={cx(kcProps.kcFormGroupClass, messagesPerField.printIfExists("username", kcProps.kcFormGroupErrorClass))}>
<div className={cx(kcProps.kcLabelWrapperClass)}>
<label htmlFor="username" className={cx(kcProps.kcLabelClass)}>
{msg("username")}
</label> </label>
</div> </div>
<div className={cx(props.kcInputWrapperClass)}> <div className={cx(kcProps.kcInputWrapperClass)}>
<input <input
type="text" type="text"
id="firstName" id="username"
className={cx(props.kcInputClass)} className={cx(kcProps.kcInputClass)}
name="firstName" name="username"
defaultValue={register.formData.firstName ?? ""} defaultValue={register.formData.username ?? ""}
autoComplete="username"
/> />
</div> </div>
</div> </div>
)}
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("lastName", props.kcFormGroupErrorClass))}> {passwordRequired && (
<div className={cx(props.kcLabelWrapperClass)}> <>
<label htmlFor="lastName" className={cx(props.kcLabelClass)}> <div className={cx(kcProps.kcFormGroupClass, messagesPerField.printIfExists("password", kcProps.kcFormGroupErrorClass))}>
{msg("lastName")} <div className={cx(kcProps.kcLabelWrapperClass)}>
</label> <label htmlFor="password" className={cx(kcProps.kcLabelClass)}>
</div> {msg("password")}
<div className={cx(props.kcInputWrapperClass)}>
<input
type="text"
id="lastName"
className={cx(props.kcInputClass)}
name="lastName"
defaultValue={register.formData.lastName ?? ""}
/>
</div>
</div>
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("email", props.kcFormGroupErrorClass))}>
<div className={cx(props.kcLabelWrapperClass)}>
<label htmlFor="email" className={cx(props.kcLabelClass)}>
{msg("email")}
</label>
</div>
<div className={cx(props.kcInputWrapperClass)}>
<input
type="text"
id="email"
className={cx(props.kcInputClass)}
name="email"
defaultValue={register.formData.email ?? ""}
autoComplete="email"
/>
</div>
</div>
{!realm.registrationEmailAsUsername && (
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("username", props.kcFormGroupErrorClass))}>
<div className={cx(props.kcLabelWrapperClass)}>
<label htmlFor="username" className={cx(props.kcLabelClass)}>
{msg("username")}
</label> </label>
</div> </div>
<div className={cx(props.kcInputWrapperClass)}> <div className={cx(kcProps.kcInputWrapperClass)}>
<input <input
type="text" type="password"
id="username" id="password"
className={cx(props.kcInputClass)} className={cx(kcProps.kcInputClass)}
name="username" name="password"
defaultValue={register.formData.username ?? ""} autoComplete="new-password"
autoComplete="username"
/> />
</div> </div>
</div> </div>
)}
{passwordRequired && (
<>
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("password", props.kcFormGroupErrorClass))}>
<div className={cx(props.kcLabelWrapperClass)}>
<label htmlFor="password" className={cx(props.kcLabelClass)}>
{msg("password")}
</label>
</div>
<div className={cx(props.kcInputWrapperClass)}>
<input
type="password"
id="password"
className={cx(props.kcInputClass)}
name="password"
autoComplete="new-password"
/>
</div>
</div>
<div <div
className={cx( className={cx(
props.kcFormGroupClass, kcProps.kcFormGroupClass,
messagesPerField.printIfExists("password-confirm", props.kcFormGroupErrorClass) messagesPerField.printIfExists("password-confirm", kcProps.kcFormGroupErrorClass)
)} )}
> >
<div className={cx(props.kcLabelWrapperClass)}> <div className={cx(kcProps.kcLabelWrapperClass)}>
<label htmlFor="password-confirm" className={cx(props.kcLabelClass)}> <label htmlFor="password-confirm" className={cx(kcProps.kcLabelClass)}>
{msg("passwordConfirm")} {msg("passwordConfirm")}
</label> </label>
</div>
<div className={cx(props.kcInputWrapperClass)}>
<input type="password" id="password-confirm" className={cx(props.kcInputClass)} name="password-confirm" />
</div>
</div> </div>
</> <div className={cx(kcProps.kcInputWrapperClass)}>
)} <input type="password" id="password-confirm" className={cx(kcProps.kcInputClass)} name="password-confirm" />
{recaptchaRequired && (
<div className="form-group">
<div className={cx(props.kcInputWrapperClass)}>
<div className="g-recaptcha" data-size="compact" data-sitekey={recaptchaSiteKey}></div>
</div> </div>
</div> </div>
)} </>
<div className={cx(props.kcFormGroupClass)}> )}
<div id="kc-form-options" className={cx(props.kcFormOptionsClass)}> {recaptchaRequired && (
<div className={cx(props.kcFormOptionsWrapperClass)}> <div className="form-group">
<span> <div className={cx(kcProps.kcInputWrapperClass)}>
<a href={url.loginUrl}>{msg("backToLogin")}</a> <div className="g-recaptcha" data-size="compact" data-sitekey={recaptchaSiteKey}></div>
</span>
</div>
</div>
<div id="kc-form-buttons" className={cx(props.kcFormButtonsClass)}>
<input
className={cx(
props.kcButtonClass,
props.kcButtonPrimaryClass,
props.kcButtonBlockClass,
props.kcButtonLargeClass
)}
type="submit"
value={msgStr("doRegister")}
/>
</div> </div>
</div> </div>
</form> )}
} <div className={cx(kcProps.kcFormGroupClass)}>
/> <div id="kc-form-options" className={cx(kcProps.kcFormOptionsClass)}>
); <div className={cx(kcProps.kcFormOptionsWrapperClass)}>
} <span>
); <a href={url.loginUrl}>{msg("backToLogin")}</a>
</span>
</div>
</div>
<div id="kc-form-buttons" className={cx(kcProps.kcFormButtonsClass)}>
<input
className={cx(
kcProps.kcButtonClass,
kcProps.kcButtonPrimaryClass,
kcProps.kcButtonBlockClass,
kcProps.kcButtonLargeClass
)}
type="submit"
value={msgStr("doRegister")}
/>
</div>
</div>
</form>
}
/>
);
});
export default Register; export default Register;

View File

@ -1,78 +1,81 @@
import React, { useMemo, memo, useState } from "react"; import React, { useMemo, memo, useState } from "react";
import Template from "./Template"; import DefaultTemplate from "./Template";
import type { TemplateProps } from "./Template";
import type { KcProps } from "./KcProps"; import type { KcProps } from "./KcProps";
import type { KcContextBase } from "../getKcContext/KcContextBase"; import type { KcContextBase } from "../getKcContext/KcContextBase";
import { useCssAndCx } from "../tools/useCssAndCx"; import { useCssAndCx } from "../tools/useCssAndCx";
import type { I18n } from "../i18n"; import type { I18n } from "../i18n";
import { UserProfileFormFields } from "./shared/UserProfileCommons"; import { UserProfileFormFields } from "./shared/UserProfileCommons";
const RegisterUserProfile = memo( export type RegisterUserProfileProps = KcProps & {
({ kcContext: KcContextBase.RegisterUserProfile;
kcContext, i18n: I18n;
i18n, doFetchDefaultThemeResources?: boolean;
doFetchDefaultThemeResources = true, Template?: (props: TemplateProps) => JSX.Element | null;
...props_ };
}: { kcContext: KcContextBase.RegisterUserProfile; i18n: I18n; doFetchDefaultThemeResources?: boolean } & KcProps) => {
const { url, messagesPerField, recaptchaRequired, recaptchaSiteKey } = kcContext;
const { msg, msgStr } = i18n; const RegisterUserProfile = memo((props: RegisterUserProfileProps) => {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template = DefaultTemplate, ...kcProps_ } = props;
const { cx, css } = useCssAndCx(); const { url, messagesPerField, recaptchaRequired, recaptchaSiteKey } = kcContext;
const props = useMemo( const { msg, msgStr } = i18n;
() => ({
...props_,
"kcFormGroupClass": cx(props_.kcFormGroupClass, css({ "marginBottom": 20 }))
}),
[cx, css]
);
const [isFomSubmittable, setIsFomSubmittable] = useState(false); const { cx, css } = useCssAndCx();
return ( const kcProps = useMemo(
<Template () => ({
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...props }} ...kcProps_,
displayMessage={messagesPerField.exists("global")} "kcFormGroupClass": cx(kcProps_.kcFormGroupClass, css({ "marginBottom": 20 }))
displayRequiredFields={true} }),
headerNode={msg("registerTitle")} [cx, css]
formNode={ );
<form id="kc-register-form" className={cx(props.kcFormClass)} action={url.registrationAction} method="post">
<UserProfileFormFields kcContext={kcContext} onIsFormSubmittableValueChange={setIsFomSubmittable} i18n={i18n} {...props} />
{recaptchaRequired && (
<div className="form-group">
<div className={cx(props.kcInputWrapperClass)}>
<div className="g-recaptcha" data-size="compact" data-sitekey={recaptchaSiteKey} />
</div>
</div>
)}
<div className={cx(props.kcFormGroupClass)}>
<div id="kc-form-options" className={cx(props.kcFormOptionsClass)}>
<div className={cx(props.kcFormOptionsWrapperClass)}>
<span>
<a href={url.loginUrl}>{msg("backToLogin")}</a>
</span>
</div>
</div>
<div id="kc-form-buttons" className={cx(props.kcFormButtonsClass)}> const [isFomSubmittable, setIsFomSubmittable] = useState(false);
<input
className={cx( return (
props.kcButtonClass, <Template
props.kcButtonPrimaryClass, {...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
props.kcButtonBlockClass, displayMessage={messagesPerField.exists("global")}
props.kcButtonLargeClass displayRequiredFields={true}
)} headerNode={msg("registerTitle")}
type="submit" formNode={
value={msgStr("doRegister")} <form id="kc-register-form" className={cx(kcProps.kcFormClass)} action={url.registrationAction} method="post">
disabled={!isFomSubmittable} <UserProfileFormFields kcContext={kcContext} onIsFormSubmittableValueChange={setIsFomSubmittable} i18n={i18n} {...kcProps} />
/> {recaptchaRequired && (
<div className="form-group">
<div className={cx(kcProps.kcInputWrapperClass)}>
<div className="g-recaptcha" data-size="compact" data-sitekey={recaptchaSiteKey} />
</div> </div>
</div> </div>
</form> )}
} <div className={cx(kcProps.kcFormGroupClass)}>
/> <div id="kc-form-options" className={cx(kcProps.kcFormOptionsClass)}>
); <div className={cx(kcProps.kcFormOptionsWrapperClass)}>
} <span>
); <a href={url.loginUrl}>{msg("backToLogin")}</a>
</span>
</div>
</div>
<div id="kc-form-buttons" className={cx(kcProps.kcFormButtonsClass)}>
<input
className={cx(
kcProps.kcButtonClass,
kcProps.kcButtonPrimaryClass,
kcProps.kcButtonBlockClass,
kcProps.kcButtonLargeClass
)}
type="submit"
value={msgStr("doRegister")}
disabled={!isFomSubmittable}
/>
</div>
</div>
</form>
}
/>
);
});
export default RegisterUserProfile; export default RegisterUserProfile;

View File

@ -1,5 +1,6 @@
import React, { useEffect, memo } from "react"; import React, { useEffect, memo } from "react";
import Template from "./Template"; import DefaultTemplate from "./Template";
import type { TemplateProps } from "./Template";
import type { KcProps } from "./KcProps"; import type { KcProps } from "./KcProps";
import type { KcContextBase } from "../getKcContext/KcContextBase"; import type { KcContextBase } from "../getKcContext/KcContextBase";
import { useCssAndCx } from "../tools/useCssAndCx"; import { useCssAndCx } from "../tools/useCssAndCx";
@ -12,6 +13,7 @@ import memoize from "memoizee";
import { useConst } from "powerhooks/useConst"; import { useConst } from "powerhooks/useConst";
import { useConstCallback } from "powerhooks/useConstCallback"; import { useConstCallback } from "powerhooks/useConstCallback";
import { Markdown } from "../tools/Markdown"; import { Markdown } from "../tools/Markdown";
import type { Extends } from "tsafe";
export const evtTermMarkdown = Evt.create<string | undefined>(undefined); export const evtTermMarkdown = Evt.create<string | undefined>(undefined);
@ -22,7 +24,7 @@ export type KcContextLike = {
}; };
}; };
assert<KcContextBase extends KcContextLike ? true : false>(); assert<Extends<KcContextBase, KcContextLike>>();
/** Allow to avoid bundling the terms and download it on demand*/ /** Allow to avoid bundling the terms and download it on demand*/
export function useDownloadTerms(params: { export function useDownloadTerms(params: {
@ -54,61 +56,63 @@ export function useDownloadTerms(params: {
}, []); }, []);
} }
const Terms = memo( export type TermsProps = KcProps & {
({ kcContext: KcContextBase.Terms;
kcContext, i18n: I18n;
i18n, doFetchDefaultThemeResources?: boolean;
doFetchDefaultThemeResources = true, Template?: (props: TemplateProps) => JSX.Element | null;
...props };
}: { kcContext: KcContextBase.Terms; i18n: I18n; doFetchDefaultThemeResources?: boolean } & KcProps) => {
const { msg, msgStr } = i18n;
useRerenderOnStateChange(evtTermMarkdown); const Terms = memo((props: TermsProps) => {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template = DefaultTemplate, ...kcProps } = props;
const { cx } = useCssAndCx(); const { msg, msgStr } = i18n;
const { url } = kcContext; useRerenderOnStateChange(evtTermMarkdown);
if (evtTermMarkdown.state === undefined) { const { cx } = useCssAndCx();
return null;
}
return ( const { url } = kcContext;
<Template
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...props }} if (evtTermMarkdown.state === undefined) {
displayMessage={false} return null;
headerNode={msg("termsTitle")}
formNode={
<>
<div id="kc-terms-text">{evtTermMarkdown.state && <Markdown>{evtTermMarkdown.state}</Markdown>}</div>
<form className="form-actions" action={url.loginAction} method="POST">
<input
className={cx(
props.kcButtonClass,
props.kcButtonClass,
props.kcButtonClass,
props.kcButtonPrimaryClass,
props.kcButtonLargeClass
)}
name="accept"
id="kc-accept"
type="submit"
value={msgStr("doAccept")}
/>
<input
className={cx(props.kcButtonClass, props.kcButtonDefaultClass, props.kcButtonLargeClass)}
name="cancel"
id="kc-decline"
type="submit"
value={msgStr("doDecline")}
/>
</form>
<div className="clearfix" />
</>
}
/>
);
} }
);
return (
<Template
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
displayMessage={false}
headerNode={msg("termsTitle")}
formNode={
<>
<div id="kc-terms-text">{evtTermMarkdown.state && <Markdown>{evtTermMarkdown.state}</Markdown>}</div>
<form className="form-actions" action={url.loginAction} method="POST">
<input
className={cx(
kcProps.kcButtonClass,
kcProps.kcButtonClass,
kcProps.kcButtonClass,
kcProps.kcButtonPrimaryClass,
kcProps.kcButtonLargeClass
)}
name="accept"
id="kc-accept"
type="submit"
value={msgStr("doAccept")}
/>
<input
className={cx(kcProps.kcButtonClass, kcProps.kcButtonDefaultClass, kcProps.kcButtonLargeClass)}
name="cancel"
id="kc-decline"
type="submit"
value={msgStr("doDecline")}
/>
</form>
<div className="clearfix" />
</>
}
/>
);
});
export default Terms; export default Terms;

View File

@ -1,77 +1,80 @@
import React, { useState, memo } from "react"; import React, { useState, memo } from "react";
import Template from "./Template"; import DefaultTemplate from "./Template";
import type { TemplateProps } from "./Template";
import type { KcProps } from "./KcProps"; import type { KcProps } from "./KcProps";
import type { KcContextBase } from "../getKcContext/KcContextBase"; import type { KcContextBase } from "../getKcContext/KcContextBase";
import { useCssAndCx } from "../tools/useCssAndCx"; import { useCssAndCx } from "../tools/useCssAndCx";
import type { I18n } from "../i18n"; import type { I18n } from "../i18n";
import { UserProfileFormFields } from "./shared/UserProfileCommons"; import { UserProfileFormFields } from "./shared/UserProfileCommons";
const UpdateUserProfile = memo( export type UpdateUserProfileProps = KcProps & {
({ kcContext: KcContextBase.UpdateUserProfile;
kcContext, i18n: I18n;
i18n, doFetchDefaultThemeResources?: boolean;
doFetchDefaultThemeResources = true, Template?: (props: TemplateProps) => JSX.Element | null;
...props };
}: { kcContext: KcContextBase.UpdateUserProfile; i18n: I18n; doFetchDefaultThemeResources?: boolean } & KcProps) => {
const { cx } = useCssAndCx();
const { msg, msgStr } = i18n; const UpdateUserProfile = memo((props: UpdateUserProfileProps) => {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template = DefaultTemplate, ...kcProps } = props;
const { url, isAppInitiatedAction } = kcContext; const { cx } = useCssAndCx();
const [isFomSubmittable, setIsFomSubmittable] = useState(false); const { msg, msgStr } = i18n;
return ( const { url, isAppInitiatedAction } = kcContext;
<Template
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...props }}
headerNode={msg("loginProfileTitle")}
formNode={
<form id="kc-update-profile-form" className={cx(props.kcFormClass)} action={url.loginAction} method="post">
<UserProfileFormFields kcContext={kcContext} onIsFormSubmittableValueChange={setIsFomSubmittable} i18n={i18n} {...props} />
<div className={cx(props.kcFormGroupClass)}> const [isFomSubmittable, setIsFomSubmittable] = useState(false);
<div id="kc-form-options" className={cx(props.kcFormOptionsClass)}>
<div className={cx(props.kcFormOptionsWrapperClass)}></div>
</div>
<div id="kc-form-buttons" className={cx(props.kcFormButtonsClass)}> return (
{isAppInitiatedAction ? ( <Template
<> {...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
<input headerNode={msg("loginProfileTitle")}
className={cx(props.kcButtonClass, props.kcButtonPrimaryClass, props.kcButtonLargeClass)} formNode={
type="submit" <form id="kc-update-profile-form" className={cx(kcProps.kcFormClass)} action={url.loginAction} method="post">
value={msgStr("doSubmit")} <UserProfileFormFields kcContext={kcContext} onIsFormSubmittableValueChange={setIsFomSubmittable} i18n={i18n} {...kcProps} />
/>
<button <div className={cx(kcProps.kcFormGroupClass)}>
className={cx(props.kcButtonClass, props.kcButtonDefaultClass, props.kcButtonLargeClass)} <div id="kc-form-options" className={cx(kcProps.kcFormOptionsClass)}>
type="submit" <div className={cx(kcProps.kcFormOptionsWrapperClass)}></div>
name="cancel-aia"
value="true"
formNoValidate
>
{msg("doCancel")}
</button>
</>
) : (
<input
className={cx(
props.kcButtonClass,
props.kcButtonPrimaryClass,
props.kcButtonBlockClass,
props.kcButtonLargeClass
)}
type="submit"
defaultValue={msgStr("doSubmit")}
disabled={!isFomSubmittable}
/>
)}
</div>
</div> </div>
</form>
} <div id="kc-form-buttons" className={cx(kcProps.kcFormButtonsClass)}>
/> {isAppInitiatedAction ? (
); <>
} <input
); className={cx(kcProps.kcButtonClass, kcProps.kcButtonPrimaryClass, kcProps.kcButtonLargeClass)}
type="submit"
value={msgStr("doSubmit")}
/>
<button
className={cx(kcProps.kcButtonClass, kcProps.kcButtonDefaultClass, kcProps.kcButtonLargeClass)}
type="submit"
name="cancel-aia"
value="true"
formNoValidate
>
{msg("doCancel")}
</button>
</>
) : (
<input
className={cx(
kcProps.kcButtonClass,
kcProps.kcButtonPrimaryClass,
kcProps.kcButtonBlockClass,
kcProps.kcButtonLargeClass
)}
type="submit"
defaultValue={msgStr("doSubmit")}
disabled={!isFomSubmittable}
/>
)}
</div>
</div>
</form>
}
/>
);
});
export default UpdateUserProfile; export default UpdateUserProfile;

View File

@ -1,5 +1,6 @@
import React, { useRef, useState, memo } from "react"; import React, { useRef, useState, memo } from "react";
import Template from "./Template"; import DefaultTemplate from "./Template";
import type { TemplateProps } from "./Template";
import type { KcProps } from "./KcProps"; import type { KcProps } from "./KcProps";
import type { KcContextBase } from "../getKcContext/KcContextBase"; import type { KcContextBase } from "../getKcContext/KcContextBase";
import { useCssAndCx } from "../tools/useCssAndCx"; import { useCssAndCx } from "../tools/useCssAndCx";
@ -7,198 +8,198 @@ import type { I18n, MessageKeyBase } from "../i18n";
import { base64url } from "rfc4648"; import { base64url } from "rfc4648";
import { useConstCallback } from "powerhooks/useConstCallback"; import { useConstCallback } from "powerhooks/useConstCallback";
const WebauthnAuthenticate = memo( export type WebauthnAuthenticateProps = KcProps & {
({ kcContext: KcContextBase.WebauthnAuthenticate;
kcContext, i18n: I18n;
i18n, doFetchDefaultThemeResources?: boolean;
doFetchDefaultThemeResources = true, Template?: (props: TemplateProps) => JSX.Element | null;
...props };
}: { kcContext: KcContextBase.WebauthnAuthenticate; i18n: I18n; doFetchDefaultThemeResources?: boolean } & KcProps) => {
const { url } = kcContext;
const { msg, msgStr } = i18n; const WebauthnAuthenticate = memo((props: WebauthnAuthenticateProps) => {
const { kcContext, i18n, doFetchDefaultThemeResources = true, Template = DefaultTemplate, ...kcProps } = props;
const { authenticators, challenge, shouldDisplayAuthenticators, userVerification, rpId } = kcContext; const { url } = kcContext;
const createTimeout = Number(kcContext.createTimeout);
const isUserIdentified = kcContext.isUserIdentified == "true";
const { cx } = useCssAndCx(); const { msg, msgStr } = i18n;
const webAuthnAuthenticate = useConstCallback(async () => { const { authenticators, challenge, shouldDisplayAuthenticators, userVerification, rpId } = kcContext;
if (!isUserIdentified) { const createTimeout = Number(kcContext.createTimeout);
return; const isUserIdentified = kcContext.isUserIdentified == "true";
}
const allowCredentials = authenticators.authenticators.map(
authenticator =>
({
id: base64url.parse(authenticator.credentialId, { loose: true }),
type: "public-key"
} as PublicKeyCredentialDescriptor)
);
// Check if WebAuthn is supported by this browser
if (!window.PublicKeyCredential) {
setError(msgStr("webauthn-unsupported-browser-text"));
submitForm();
return;
}
const publicKey: PublicKeyCredentialRequestOptions = { const { cx } = useCssAndCx();
rpId,
challenge: base64url.parse(challenge, { loose: true })
};
if (createTimeout !== 0) { const webAuthnAuthenticate = useConstCallback(async () => {
publicKey.timeout = createTimeout * 1000; if (!isUserIdentified) {
} return;
}
const allowCredentials = authenticators.authenticators.map(
authenticator =>
({
id: base64url.parse(authenticator.credentialId, { loose: true }),
type: "public-key"
} as PublicKeyCredentialDescriptor)
);
// Check if WebAuthn is supported by this browser
if (!window.PublicKeyCredential) {
setError(msgStr("webauthn-unsupported-browser-text"));
submitForm();
return;
}
if (allowCredentials.length) { const publicKey: PublicKeyCredentialRequestOptions = {
publicKey.allowCredentials = allowCredentials; rpId,
} challenge: base64url.parse(challenge, { loose: true })
};
if (userVerification !== "not specified") { if (createTimeout !== 0) {
publicKey.userVerification = userVerification; publicKey.timeout = createTimeout * 1000;
} }
try { if (allowCredentials.length) {
const resultRaw = await navigator.credentials.get({ publicKey }); publicKey.allowCredentials = allowCredentials;
if (!resultRaw || resultRaw.type != "public-key") return; }
const result = resultRaw as PublicKeyCredential;
if (!("authenticatorData" in result.response)) return;
const response = result.response as AuthenticatorAssertionResponse;
const clientDataJSON = response.clientDataJSON;
const authenticatorData = response.authenticatorData;
const signature = response.signature;
setClientDataJSON(base64url.stringify(new Uint8Array(clientDataJSON), { pad: false })); if (userVerification !== "not specified") {
setAuthenticatorData(base64url.stringify(new Uint8Array(authenticatorData), { pad: false })); publicKey.userVerification = userVerification;
setSignature(base64url.stringify(new Uint8Array(signature), { pad: false })); }
setCredentialId(result.id);
setUserHandle(base64url.stringify(new Uint8Array(response.userHandle!), { pad: false }));
submitForm();
} catch (err) {
setError(String(err));
submitForm();
}
});
const webAuthForm = useRef<HTMLFormElement>(null); try {
const submitForm = useConstCallback(() => { const resultRaw = await navigator.credentials.get({ publicKey });
webAuthForm.current!.submit(); if (!resultRaw || resultRaw.type != "public-key") return;
}); const result = resultRaw as PublicKeyCredential;
if (!("authenticatorData" in result.response)) return;
const response = result.response as AuthenticatorAssertionResponse;
const clientDataJSON = response.clientDataJSON;
const authenticatorData = response.authenticatorData;
const signature = response.signature;
const [clientDataJSON, setClientDataJSON] = useState(""); setClientDataJSON(base64url.stringify(new Uint8Array(clientDataJSON), { pad: false }));
const [authenticatorData, setAuthenticatorData] = useState(""); setAuthenticatorData(base64url.stringify(new Uint8Array(authenticatorData), { pad: false }));
const [signature, setSignature] = useState(""); setSignature(base64url.stringify(new Uint8Array(signature), { pad: false }));
const [credentialId, setCredentialId] = useState(""); setCredentialId(result.id);
const [userHandle, setUserHandle] = useState(""); setUserHandle(base64url.stringify(new Uint8Array(response.userHandle!), { pad: false }));
const [error, setError] = useState(""); submitForm();
} catch (err) {
setError(String(err));
submitForm();
}
});
return ( const webAuthForm = useRef<HTMLFormElement>(null);
<Template const submitForm = useConstCallback(() => {
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...props }} webAuthForm.current!.submit();
headerNode={msg("webauthn-login-title")} });
formNode={
<div id="kc-form-webauthn" className={cx(props.kcFormClass)}>
<form id="webauth" action={url.loginAction} ref={webAuthForm} method="post">
<input type="hidden" id="clientDataJSON" name="clientDataJSON" value={clientDataJSON} />
<input type="hidden" id="authenticatorData" name="authenticatorData" value={authenticatorData} />
<input type="hidden" id="signature" name="signature" value={signature} />
<input type="hidden" id="credentialId" name="credentialId" value={credentialId} />
<input type="hidden" id="userHandle" name="userHandle" value={userHandle} />
<input type="hidden" id="error" name="error" value={error} />
</form>
<div className={cx(props.kcFormGroupClass)}>
{authenticators &&
(() => (
<form id="authn_select" className={cx(props.kcFormClass)}>
{authenticators.authenticators.map(authenticator => (
<input
type="hidden"
name="authn_use_chk"
value={authenticator.credentialId}
key={authenticator.credentialId}
/>
))}
</form>
))()}
{authenticators &&
shouldDisplayAuthenticators &&
(() => (
<>
{authenticators.authenticators.length > 1 && (
<p className={cx(props.kcSelectAuthListItemTitle)}>{msg("webauthn-available-authenticators")}</p>
)}
<div className={cx(props.kcFormClass)}>
{authenticators.authenticators.map(authenticator => (
<div id="kc-webauthn-authenticator" className={cx(props.kcSelectAuthListItemClass)}>
<div className={cx(props.kcSelectAuthListItemIconClass)}>
<i
className={cx(
props[authenticator.transports.iconClass] ?? props.kcWebAuthnDefaultIcon,
props.kcSelectAuthListItemIconPropertyClass
)}
/>
</div>
<div className={cx(props.kcSelectAuthListItemBodyClass)}>
<div
id="kc-webauthn-authenticator-label"
className={cx(props.kcSelectAuthListItemHeadingClass)}
>
{authenticator.label}
</div>
{authenticator.transports && authenticator.transports.displayNameProperties.length && ( const [clientDataJSON, setClientDataJSON] = useState("");
<div const [authenticatorData, setAuthenticatorData] = useState("");
id="kc-webauthn-authenticator-transport" const [signature, setSignature] = useState("");
className={cx(props.kcSelectAuthListItemDescriptionClass)} const [credentialId, setCredentialId] = useState("");
> const [userHandle, setUserHandle] = useState("");
{authenticator.transports.displayNameProperties.map( const [error, setError] = useState("");
(transport: MessageKeyBase, index: number) => (
<>
<span>{msg(transport)}</span>
{index < authenticator.transports.displayNameProperties.length - 1 && (
<span>{", "}</span>
)}
</>
)
)}
</div>
)}
<div className={cx(props.kcSelectAuthListItemDescriptionClass)}> return (
<span id="kc-webauthn-authenticator-created-label"> <Template
{msg("webauthn-createdAt-label")} {...{ kcContext, i18n, doFetchDefaultThemeResources, ...kcProps }}
</span> headerNode={msg("webauthn-login-title")}
<span id="kc-webauthn-authenticator-created">{authenticator.createdAt}</span> formNode={
</div> <div id="kc-form-webauthn" className={cx(kcProps.kcFormClass)}>
</div> <form id="webauth" action={url.loginAction} ref={webAuthForm} method="post">
<div className={cx(props.kcSelectAuthListItemFillClass)} /> <input type="hidden" id="clientDataJSON" name="clientDataJSON" value={clientDataJSON} />
</div> <input type="hidden" id="authenticatorData" name="authenticatorData" value={authenticatorData} />
))} <input type="hidden" id="signature" name="signature" value={signature} />
</div> <input type="hidden" id="credentialId" name="credentialId" value={credentialId} />
</> <input type="hidden" id="userHandle" name="userHandle" value={userHandle} />
))()} <input type="hidden" id="error" name="error" value={error} />
<div id="kc-form-buttons" className={cx(props.kcFormButtonsClass)}> </form>
<input <div className={cx(kcProps.kcFormGroupClass)}>
id="authenticateWebAuthnButton" {authenticators &&
type="button" (() => (
onClick={webAuthnAuthenticate} <form id="authn_select" className={cx(kcProps.kcFormClass)}>
autoFocus={true} {authenticators.authenticators.map(authenticator => (
value={msgStr("webauthn-doAuthenticate")} <input
className={cx( type="hidden"
props.kcButtonClass, name="authn_use_chk"
props.kcButtonPrimaryClass, value={authenticator.credentialId}
props.kcButtonBlockClass, key={authenticator.credentialId}
props.kcButtonLargeClass />
))}
</form>
))()}
{authenticators &&
shouldDisplayAuthenticators &&
(() => (
<>
{authenticators.authenticators.length > 1 && (
<p className={cx(kcProps.kcSelectAuthListItemTitle)}>{msg("webauthn-available-authenticators")}</p>
)} )}
/> <div className={cx(kcProps.kcFormClass)}>
</div> {authenticators.authenticators.map(authenticator => (
<div id="kc-webauthn-authenticator" className={cx(kcProps.kcSelectAuthListItemClass)}>
<div className={cx(kcProps.kcSelectAuthListItemIconClass)}>
<i
className={cx(
kcProps[authenticator.transports.iconClass] ?? kcProps.kcWebAuthnDefaultIcon,
kcProps.kcSelectAuthListItemIconPropertyClass
)}
/>
</div>
<div className={cx(kcProps.kcSelectAuthListItemBodyClass)}>
<div
id="kc-webauthn-authenticator-label"
className={cx(kcProps.kcSelectAuthListItemHeadingClass)}
>
{authenticator.label}
</div>
{authenticator.transports && authenticator.transports.displayNameProperties.length && (
<div
id="kc-webauthn-authenticator-transport"
className={cx(kcProps.kcSelectAuthListItemDescriptionClass)}
>
{authenticator.transports.displayNameProperties.map(
(transport: MessageKeyBase, index: number) => (
<>
<span>{msg(transport)}</span>
{index < authenticator.transports.displayNameProperties.length - 1 && (
<span>{", "}</span>
)}
</>
)
)}
</div>
)}
<div className={cx(kcProps.kcSelectAuthListItemDescriptionClass)}>
<span id="kc-webauthn-authenticator-created-label">{msg("webauthn-createdAt-label")}</span>
<span id="kc-webauthn-authenticator-created">{authenticator.createdAt}</span>
</div>
</div>
<div className={cx(kcProps.kcSelectAuthListItemFillClass)} />
</div>
))}
</div>
</>
))()}
<div id="kc-form-buttons" className={cx(kcProps.kcFormButtonsClass)}>
<input
id="authenticateWebAuthnButton"
type="button"
onClick={webAuthnAuthenticate}
autoFocus={true}
value={msgStr("webauthn-doAuthenticate")}
className={cx(
kcProps.kcButtonClass,
kcProps.kcButtonPrimaryClass,
kcProps.kcButtonBlockClass,
kcProps.kcButtonLargeClass
)}
/>
</div> </div>
</div> </div>
} </div>
/> }
); />
} );
); });
export default WebauthnAuthenticate; export default WebauthnAuthenticate;