From e7837aea88d62b35108fdfae2e37e1c6e03bfb68 Mon Sep 17 00:00:00 2001 From: Void <32745739+0x-Void@users.noreply.github.com> Date: Fri, 31 Mar 2023 17:38:22 +0200 Subject: [PATCH] feat: add select-authenticator page --- .../keycloakify/generateFtl/generateFtl.ts | 3 +- src/login/Fallback.tsx | 3 + src/login/kcContext/KcContext.ts | 36 ++++++++- src/login/kcContext/kcContextMocks.ts | 20 +++++ src/login/pages/SelectAuthenticator.tsx | 73 +++++++++++++++++++ 5 files changed, 133 insertions(+), 2 deletions(-) create mode 100644 src/login/pages/SelectAuthenticator.tsx diff --git a/src/bin/keycloakify/generateFtl/generateFtl.ts b/src/bin/keycloakify/generateFtl/generateFtl.ts index f13f5cd7..f4c9554f 100644 --- a/src/bin/keycloakify/generateFtl/generateFtl.ts +++ b/src/bin/keycloakify/generateFtl/generateFtl.ts @@ -36,7 +36,8 @@ export const loginThemePageIds = [ "logout-confirm.ftl", "update-user-profile.ftl", "idp-review-user-profile.ftl", - "update-email.ftl" + "update-email.ftl", + "select-authenticator.ftl" ] as const; export const accountThemePageIds = ["password.ftl", "account.ftl"] as const; diff --git a/src/login/Fallback.tsx b/src/login/Fallback.tsx index 413040c1..6a3dbbec 100644 --- a/src/login/Fallback.tsx +++ b/src/login/Fallback.tsx @@ -26,6 +26,7 @@ const LogoutConfirm = lazy(() => import("keycloakify/login/pages/LogoutConfirm") const UpdateUserProfile = lazy(() => import("keycloakify/login/pages/UpdateUserProfile")); const IdpReviewUserProfile = lazy(() => import("keycloakify/login/pages/IdpReviewUserProfile")); const UpdateEmail = lazy(() => import("keycloakify/login/pages/UpdateEmail")); +const SelectAuthenticator = lazy(() => import("keycloakify/login/pages/SelectAuthenticator")); export default function Fallback(props: PageProps) { const { kcContext, ...rest } = props; @@ -78,6 +79,8 @@ export default function Fallback(props: PageProps) { return ; case "update-email.ftl": return ; + case "select-authenticator.ftl": + return ; } assert>(false); })()} diff --git a/src/login/kcContext/KcContext.ts b/src/login/kcContext/KcContext.ts index 475154f9..9f692ae3 100644 --- a/src/login/kcContext/KcContext.ts +++ b/src/login/kcContext/KcContext.ts @@ -31,7 +31,8 @@ export type KcContext = | KcContext.LogoutConfirm | KcContext.UpdateUserProfile | KcContext.IdpReviewUserProfile - | KcContext.UpdateEmail; + | KcContext.UpdateEmail + | KcContext.SelectAuthenticator; export declare namespace KcContext { export type Common = { @@ -389,6 +390,39 @@ export declare namespace KcContext { 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 = { diff --git a/src/login/kcContext/kcContextMocks.ts b/src/login/kcContext/kcContextMocks.ts index ebfd908c..e28bce3c 100644 --- a/src/login/kcContext/kcContextMocks.ts +++ b/src/login/kcContext/kcContextMocks.ts @@ -498,5 +498,25 @@ export const kcContextMocks: KcContext[] = [ "email": { value: "email@example.com" } + }), + id({ + ...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" + } + ] + } }) ]; diff --git a/src/login/pages/SelectAuthenticator.tsx b/src/login/pages/SelectAuthenticator.tsx new file mode 100644 index 00000000..37dc74f8 --- /dev/null +++ b/src/login/pages/SelectAuthenticator.tsx @@ -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, I18n>) { + const { kcContext, i18n, doUseDefaultCss, Template, classes } = props; + const { url, auth } = kcContext; + + const { getClassName } = useGetClassName({ doUseDefaultCss, classes }); + const { msg } = i18n; + + const selectCredentialsForm = useRef(null); + const authExecIdInput = useRef(null); + + const submitForm = useConstCallback(() => { + selectCredentialsForm.current?.submit(); + }); + + const onSelectedAuthenticator = useConstCallback((event: MouseEvent) => { + const divElement = event.currentTarget; + const authExecId = divElement.dataset.authExecId; + + if (!authExecIdInput.current || !authExecId) { + return; + } + + authExecIdInput.current.value = authExecId; + submitForm(); + }); + + return ( + + ); +}