diff --git a/public/locales/en-US/translation.json b/public/locales/en-US/translation.json index 9057d18..7c6ebbf 100644 --- a/public/locales/en-US/translation.json +++ b/public/locales/en-US/translation.json @@ -104,6 +104,19 @@ } } }, + "Recover2FARoute": { + "title": "Recover Two-Factor-Authentication", + "description": "We are very sorry if you lost your codes. Please enter a recovery code to continue. Note that this will disable two-factor authentication for your account. You can enable it again in the settings.", + "forms": { + "recoveryCode": { + "label": "Recovery Code" + }, + "submit": "Disable 2FA" + }, + "unauthorized": "Please make sure to log in first and then reset your two-factor authentication on its screen.", + "canLoginNow": "Two-factor authentication has been disabled. You can now log in.", + "loggedIn": "Two-factor authentication has been disabled. You are now logged in." + }, "CompleteAccountRoute": { "forms": { "generateReports": { diff --git a/src/App.tsx b/src/App.tsx index 79c00e0..c54ecea 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -20,6 +20,7 @@ import GlobalSettingsRoute from "~/routes/GlobalSettingsRoute" import I18nHandler from "./I18nHandler" import LoginRoute from "~/routes/LoginRoute" import LogoutRoute from "~/routes/LogoutRoute" +import Recover2FARoute from "./routes/Recover2FARoute" import RedirectRoute from "./routes/RedirectRoute" import ReportDetailRoute from "~/routes/ReportDetailRoute" import ReportsRoute from "~/routes/ReportsRoute" @@ -53,6 +54,10 @@ const router = createBrowserRouter([ loader: getServerSettings, element: , }, + { + path: "/auth/recover-2fa", + element: , + }, { path: "/auth/signup", loader: getServerSettings, diff --git a/src/route-widgets/LoginRoute/OTPForm.tsx b/src/route-widgets/LoginRoute/OTPForm.tsx index 95ac2e2..457c21d 100644 --- a/src/route-widgets/LoginRoute/OTPForm.tsx +++ b/src/route-widgets/LoginRoute/OTPForm.tsx @@ -96,6 +96,7 @@ export default function OTPForm({ (delete2FA, { + onSuccess: async () => { + try { + const user = await getMe() + + showSuccess(t("routes.Recover2FARoute.loggedIn").toString()) + login(user) + navigate("/aliases") + } catch (error) { + showSuccess(t("routes.Recover2FARoute.canLoginNow").toString()) + navigate("/auth/login") + } + }, + onError: error => { + if (error.response?.status == 401) { + showError(t("routes.Recover2FARoute.unauthorized").toString()) + navigate("/auth/login") + } else { + showError(error) + } + }, + }) + + const schema = yup.object().shape({ + recoveryCode: yup.string().required().label(t("routes.LoginRoute.forms.otp.code.label")), + }) + + const formik = useFormik
({ + validationSchema: schema, + initialValues: { + recoveryCode: "", + }, + onSubmit: async values => + mutateAsync({ + recoveryCode: values.recoveryCode.replaceAll("-", ""), + }), + }) + + return ( + + + + + + + {t("routes.Recover2FARoute.title")} + + + + + + + + + + {t("routes.Recover2FARoute.description")} + + + + + + + } + > + {t("routes.Recover2FARoute.forms.submit")} + + + + + +
+ ) +}