feat: add select-authenticator page
This commit is contained in:
parent
b0db8caf65
commit
e7837aea88
@ -36,7 +36,8 @@ export const loginThemePageIds = [
|
|||||||
"logout-confirm.ftl",
|
"logout-confirm.ftl",
|
||||||
"update-user-profile.ftl",
|
"update-user-profile.ftl",
|
||||||
"idp-review-user-profile.ftl",
|
"idp-review-user-profile.ftl",
|
||||||
"update-email.ftl"
|
"update-email.ftl",
|
||||||
|
"select-authenticator.ftl"
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
export const accountThemePageIds = ["password.ftl", "account.ftl"] as const;
|
export const accountThemePageIds = ["password.ftl", "account.ftl"] as const;
|
||||||
|
@ -26,6 +26,7 @@ const LogoutConfirm = lazy(() => import("keycloakify/login/pages/LogoutConfirm")
|
|||||||
const UpdateUserProfile = lazy(() => import("keycloakify/login/pages/UpdateUserProfile"));
|
const UpdateUserProfile = lazy(() => import("keycloakify/login/pages/UpdateUserProfile"));
|
||||||
const IdpReviewUserProfile = lazy(() => import("keycloakify/login/pages/IdpReviewUserProfile"));
|
const IdpReviewUserProfile = lazy(() => import("keycloakify/login/pages/IdpReviewUserProfile"));
|
||||||
const UpdateEmail = lazy(() => import("keycloakify/login/pages/UpdateEmail"));
|
const UpdateEmail = lazy(() => import("keycloakify/login/pages/UpdateEmail"));
|
||||||
|
const SelectAuthenticator = lazy(() => import("keycloakify/login/pages/SelectAuthenticator"));
|
||||||
|
|
||||||
export default function Fallback(props: PageProps<KcContext, I18n>) {
|
export default function Fallback(props: PageProps<KcContext, I18n>) {
|
||||||
const { kcContext, ...rest } = props;
|
const { kcContext, ...rest } = props;
|
||||||
@ -78,6 +79,8 @@ export default function Fallback(props: PageProps<KcContext, I18n>) {
|
|||||||
return <IdpReviewUserProfile kcContext={kcContext} {...rest} />;
|
return <IdpReviewUserProfile kcContext={kcContext} {...rest} />;
|
||||||
case "update-email.ftl":
|
case "update-email.ftl":
|
||||||
return <UpdateEmail kcContext={kcContext} {...rest} />;
|
return <UpdateEmail kcContext={kcContext} {...rest} />;
|
||||||
|
case "select-authenticator.ftl":
|
||||||
|
return <SelectAuthenticator kcContext={kcContext} {...rest} />;
|
||||||
}
|
}
|
||||||
assert<Equals<typeof kcContext, never>>(false);
|
assert<Equals<typeof kcContext, never>>(false);
|
||||||
})()}
|
})()}
|
||||||
|
@ -31,7 +31,8 @@ export type KcContext =
|
|||||||
| KcContext.LogoutConfirm
|
| KcContext.LogoutConfirm
|
||||||
| KcContext.UpdateUserProfile
|
| KcContext.UpdateUserProfile
|
||||||
| KcContext.IdpReviewUserProfile
|
| KcContext.IdpReviewUserProfile
|
||||||
| KcContext.UpdateEmail;
|
| KcContext.UpdateEmail
|
||||||
|
| KcContext.SelectAuthenticator;
|
||||||
|
|
||||||
export declare namespace KcContext {
|
export declare namespace KcContext {
|
||||||
export type Common = {
|
export type Common = {
|
||||||
@ -389,6 +390,39 @@ export declare namespace KcContext {
|
|||||||
value?: string;
|
value?: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type SelectAuthenticator = Common & {
|
||||||
|
pageId: "select-authenticator.ftl";
|
||||||
|
auth: {
|
||||||
|
authenticationSelections: SelectAuthenticator.AuthenticationSelection[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export namespace SelectAuthenticator {
|
||||||
|
export type AuthenticationSelection = {
|
||||||
|
authExecId: string;
|
||||||
|
displayName:
|
||||||
|
| "otp-display-name"
|
||||||
|
| "password-display-name"
|
||||||
|
| "auth-username-form-display-name"
|
||||||
|
| "auth-username-password-form-display-name"
|
||||||
|
| "webauthn-display-name"
|
||||||
|
| "webauthn-passwordless-display-name";
|
||||||
|
helpText:
|
||||||
|
| "otp-help-text"
|
||||||
|
| "password-help-text"
|
||||||
|
| "auth-username-form-help-text"
|
||||||
|
| "auth-username-password-form-help-text"
|
||||||
|
| "webauthn-help-text"
|
||||||
|
| "webauthn-passwordless-help-text";
|
||||||
|
iconCssClass?:
|
||||||
|
| "kcAuthenticatorDefaultClass"
|
||||||
|
| "kcAuthenticatorPasswordClass"
|
||||||
|
| "kcAuthenticatorOTPClass"
|
||||||
|
| "kcAuthenticatorWebAuthnClass"
|
||||||
|
| "kcAuthenticatorWebAuthnPasswordlessClass";
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Attribute = {
|
export type Attribute = {
|
||||||
|
@ -498,5 +498,25 @@ export const kcContextMocks: KcContext[] = [
|
|||||||
"email": {
|
"email": {
|
||||||
value: "email@example.com"
|
value: "email@example.com"
|
||||||
}
|
}
|
||||||
|
}),
|
||||||
|
id<KcContext.SelectAuthenticator>({
|
||||||
|
...kcContextCommonMock,
|
||||||
|
pageId: "select-authenticator.ftl",
|
||||||
|
auth: {
|
||||||
|
authenticationSelections: [
|
||||||
|
{
|
||||||
|
authExecId: "f607f83c-537e-42b7-99d7-c52d459afe84",
|
||||||
|
displayName: "otp-display-name",
|
||||||
|
helpText: "otp-help-text",
|
||||||
|
iconCssClass: "kcAuthenticatorOTPClass"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
authExecId: "5ed881b1-84cd-4e9b-b4d9-f329ea61a58c",
|
||||||
|
displayName: "webauthn-display-name",
|
||||||
|
helpText: "webauthn-help-text",
|
||||||
|
iconCssClass: "kcAuthenticatorWebAuthnClass"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
})
|
})
|
||||||
];
|
];
|
||||||
|
73
src/login/pages/SelectAuthenticator.tsx
Normal file
73
src/login/pages/SelectAuthenticator.tsx
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import type { PageProps } from "keycloakify/login/pages/PageProps";
|
||||||
|
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
|
||||||
|
import type { KcContext } from "keycloakify/login/kcContext";
|
||||||
|
import type { I18n } from "keycloakify/login/i18n";
|
||||||
|
import { MouseEvent, useRef } from "react";
|
||||||
|
import { useConstCallback } from "keycloakify/tools/useConstCallback";
|
||||||
|
|
||||||
|
export default function SelectAuthenticator(props: PageProps<Extract<KcContext, { pageId: "select-authenticator.ftl" }>, I18n>) {
|
||||||
|
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
|
||||||
|
const { url, auth } = kcContext;
|
||||||
|
|
||||||
|
const { getClassName } = useGetClassName({ doUseDefaultCss, classes });
|
||||||
|
const { msg } = i18n;
|
||||||
|
|
||||||
|
const selectCredentialsForm = useRef<HTMLFormElement>(null);
|
||||||
|
const authExecIdInput = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
|
const submitForm = useConstCallback(() => {
|
||||||
|
selectCredentialsForm.current?.submit();
|
||||||
|
});
|
||||||
|
|
||||||
|
const onSelectedAuthenticator = useConstCallback((event: MouseEvent<HTMLDivElement>) => {
|
||||||
|
const divElement = event.currentTarget;
|
||||||
|
const authExecId = divElement.dataset.authExecId;
|
||||||
|
|
||||||
|
if (!authExecIdInput.current || !authExecId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
authExecIdInput.current.value = authExecId;
|
||||||
|
submitForm();
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Template {...{ kcContext, i18n, doUseDefaultCss, classes }} headerNode={msg("loginChooseAuthenticator")}>
|
||||||
|
<form
|
||||||
|
id="kc-select-credential-form"
|
||||||
|
className={getClassName("kcFormClass")}
|
||||||
|
ref={selectCredentialsForm}
|
||||||
|
action={url.loginAction}
|
||||||
|
method="post"
|
||||||
|
>
|
||||||
|
<div className={getClassName("kcSelectAuthListClass")}>
|
||||||
|
{auth.authenticationSelections.map((authenticationSelection, index) => (
|
||||||
|
<div key={index} className={getClassName("kcSelectAuthListItemClass")}>
|
||||||
|
<div
|
||||||
|
style={{ cursor: "pointer" }}
|
||||||
|
onClick={onSelectedAuthenticator}
|
||||||
|
data-auth-exec-id={authenticationSelection.authExecId}
|
||||||
|
className={getClassName("kcSelectAuthListItemInfoClass")}
|
||||||
|
>
|
||||||
|
<div className={getClassName("kcSelectAuthListItemLeftClass")}>
|
||||||
|
<span className={getClassName(authenticationSelection.iconCssClass ?? "kcAuthenticatorDefaultClass")}></span>
|
||||||
|
</div>
|
||||||
|
<div className={getClassName("kcSelectAuthListItemBodyClass")}>
|
||||||
|
<div className={getClassName("kcSelectAuthListItemDescriptionClass")}>
|
||||||
|
<div className={getClassName("kcSelectAuthListItemHeadingClass")}>
|
||||||
|
{msg(authenticationSelection.displayName)}
|
||||||
|
</div>
|
||||||
|
<div className={getClassName("kcSelectAuthListItemHelpTextClass")}>
|
||||||
|
{msg(authenticationSelection.helpText)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
<input type="hidden" id="authexec-hidden-input" name="authenticationExecution" ref={authExecIdInput} />
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Template>
|
||||||
|
);
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user