web: add support for disabling 2fa

This commit is contained in:
Abdullah Atta
2026-04-17 11:24:53 +05:00
parent 342ef37ef0
commit f796762bfe
2 changed files with 66 additions and 25 deletions

View File

@@ -87,21 +87,42 @@ export const AuthenticationSettings: SettingsGroup[] = [
header: strings.twoFactorAuth(),
key: "2fa",
section: "auth",
onStateChange: (listener) =>
useUserStore.subscribe((s) => s.user?.mfa, listener),
settings: [
{
key: "2fa-enabled",
title: strings.twoFactorAuthEnabled(),
description: strings.accountIsSecure(),
title: strings.twoFactorAuth(),
description: () =>
useUserStore.getState().user?.mfa.isEnabled
? strings.accountIsSecure()
: strings.twoFactorAuthDesc(),
keywords: [],
components: []
components: [
{
type: "toggle",
isToggled: () => !!useUserStore.getState().user?.mfa.isEnabled,
toggle: async () => {
if (useUserStore.getState().user?.mfa.isEnabled) {
await db.mfa.disable();
showToast("success", strings.twoFactorAuthDisabled());
await useUserStore.getState().refreshUser();
} else {
await MultifactorDialog.show({});
await useUserStore.getState().refreshUser();
}
}
}
]
},
{
key: "primary-2fa-method",
title: strings.change2faMethod(),
keywords: ["primary 2fa method"],
description: strings.twoFactorAuthDesc(),
onStateChange: (listener) =>
useUserStore.subscribe((s) => s.user?.mfa.primaryMethod, listener),
isHidden: () =>
!useUserStore.getState().user?.mfa ||
!useUserStore.getState().user?.mfa.isEnabled,
components: [
{
type: "button",
@@ -121,8 +142,9 @@ export const AuthenticationSettings: SettingsGroup[] = [
title: strings.addFallback2faMethod(),
description: strings.addFallback2faMethodDesc(),
keywords: ["backup 2fa methods"],
onStateChange: (listener) =>
useUserStore.subscribe((s) => s.user?.mfa.secondaryMethod, listener),
isHidden: () =>
!useUserStore.getState().user?.mfa ||
!useUserStore.getState().user?.mfa.isEnabled,
components: () => [
{
type: "button",
@@ -147,6 +169,9 @@ export const AuthenticationSettings: SettingsGroup[] = [
title: strings.viewRecoveryCodes(),
description: strings.viewRecoveryCodesDesc(),
keywords: ["recovery codes"],
isHidden: () =>
!useUserStore.getState().user?.mfa ||
!useUserStore.getState().user?.mfa.isEnabled,
components: [
{
type: "button",

View File

@@ -251,16 +251,24 @@ function LoginEmail(props: BaseAuthComponentProps<"login:email">) {
subtitle: strings.authWait()
}}
onSubmit={async (form) => {
const { primaryMethod, phoneNumber, secondaryMethod } =
(await userstore.login(form)) as MFAErrorData;
const loginResult = await userstore.login(form);
if (typeof loginResult === "boolean" || !loginResult)
throw new Error(strings.loginFailed());
navigate("mfa:code", {
email: form.email,
selectedMethod: primaryMethod,
primaryMethod,
phoneNumber,
secondaryMethod
});
if (loginResult.redirect === "mfa") {
navigate("mfa:code", {
email: form.email,
selectedMethod: loginResult.primaryMethod,
primaryMethod: loginResult.primaryMethod,
phoneNumber: loginResult.phoneNumber,
secondaryMethod: loginResult.secondaryMethod
});
} else {
navigate("login:password", {
email: form.email,
password: ""
});
}
}}
>
{(form?: EmailFormData) => (
@@ -472,16 +480,24 @@ function SessionExpiry(props: BaseAuthComponentProps<"sessionExpiry">) {
onSubmit={async () => {
if (!user) return;
const { primaryMethod, phoneNumber, secondaryMethod } =
(await userstore.login(user)) as MFAErrorData;
const loginResult = await userstore.login(user);
if (typeof loginResult === "boolean" || !loginResult)
throw new Error(strings.loginFailed());
navigate("mfa:code", {
email: user.email,
selectedMethod: primaryMethod,
primaryMethod,
phoneNumber,
secondaryMethod
});
if (loginResult.redirect === "mfa") {
navigate("mfa:code", {
email: user.email,
selectedMethod: loginResult.primaryMethod,
primaryMethod: loginResult.primaryMethod,
phoneNumber: loginResult.phoneNumber,
secondaryMethod: loginResult.secondaryMethod
});
} else {
navigate("login:password", {
email: user.email,
password: ""
});
}
}}
>
<AuthField