diff --git a/.changeset/hot-oranges-join.md b/.changeset/hot-oranges-join.md new file mode 100644 index 000000000..9a0661a31 --- /dev/null +++ b/.changeset/hot-oranges-join.md @@ -0,0 +1,24 @@ +--- +"@logto/experience-legacy": minor +"@logto/integration-tests": minor +"@logto/experience": minor +"@logto/console": minor +"@logto/phrases": minor +"@logto/schemas": minor +"@logto/core": minor +--- + +new MFA prompt policy + +You can now cutomize the MFA prompt policy in the Console. + +First, choose if you want to enable **Require MFA**: + +- **Enable**: Users will be prompted to set up MFA during the sign-in process which cannot be skipped. If the user fails to set up MFA or deletes their MFA settings, they will be locked out of their account until they set up MFA again. +- **Disable**: Users can skip the MFA setup process during sign-up flow. + +If you choose to **Disable**, you can choose the MFA setup prompt: + +- Do not ask users to set up MFA. +- Ask users to set up MFA during registration (skippable, one-time prompt). **The same prompt as previous policy (UserControlled)** +- Ask users to set up MFA on their sign-in after registration (skippable, one-time prompt) diff --git a/packages/console/src/pages/Mfa/MfaForm/constants.ts b/packages/console/src/pages/Mfa/MfaForm/constants.ts deleted file mode 100644 index 4e291acbd..000000000 --- a/packages/console/src/pages/Mfa/MfaForm/constants.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { type AdminConsoleKey } from '@logto/phrases'; -import { MfaPolicy } from '@logto/schemas'; - -export const policyOptionTitleMap: Record = { - [MfaPolicy.UserControlled]: 'mfa.user_controlled', - [MfaPolicy.Mandatory]: 'mfa.mandatory', -}; diff --git a/packages/console/src/pages/Mfa/MfaForm/index.tsx b/packages/console/src/pages/Mfa/MfaForm/index.tsx index 9af5bb839..c2d8354e3 100644 --- a/packages/console/src/pages/Mfa/MfaForm/index.tsx +++ b/packages/console/src/pages/Mfa/MfaForm/index.tsx @@ -13,7 +13,7 @@ import { SubscriptionDataContext } from '@/contexts/SubscriptionDataProvider'; import DynamicT from '@/ds-components/DynamicT'; import FormField from '@/ds-components/FormField'; import InlineNotification from '@/ds-components/InlineNotification'; -import RadioGroup, { Radio } from '@/ds-components/RadioGroup'; +import Select from '@/ds-components/Select'; import Switch from '@/ds-components/Switch'; import useApi from '@/hooks/use-api'; import useDocumentationUrl from '@/hooks/use-documentation-url'; @@ -24,7 +24,6 @@ import { type MfaConfigForm, type MfaConfig } from '../types'; import FactorLabel from './FactorLabel'; import UpsellNotice from './UpsellNotice'; -import { policyOptionTitleMap } from './constants'; import styles from './index.module.scss'; import { convertMfaFormToConfig, convertMfaConfigToForm, validateBackupCodeFactor } from './utils'; @@ -69,6 +68,24 @@ function MfaForm({ data, onMfaUpdated }: Props) { return factors.length === 0; }, [formValues, isMfaDisabled]); + const mfaPolicyOptions = useMemo( + () => [ + { + value: MfaPolicy.NoPrompt, + title: t('mfa.no_prompt'), + }, + { + value: MfaPolicy.PromptAtSignInAndSignUp, + title: t('mfa.prompt_at_sign_in_and_sign_up'), + }, + { + value: MfaPolicy.PromptOnlyAtSignIn, + title: t('mfa.prompt_only_at_sign_in'), + }, + ], + [t] + ); + const onSubmit = handleSubmit( trySubmitSafe(async (formData) => { const mfaConfig = convertMfaFormToConfig(formData); @@ -143,28 +160,37 @@ function MfaForm({ data, onMfaUpdated }: Props) { /> )} - - - ( - - {Object.values(MfaPolicy).map((policy) => { - const title = policyOptionTitleMap[policy]; - return ( - - ); - })} - - )} + + + + {!formValues.isMandatory && ( + + ( +