mirror of
https://github.com/Myzel394/kleckrelay-website.git
synced 2025-06-19 07:55:25 +02:00
feat: Add recover 2fa
This commit is contained in:
parent
74e8ad2c7b
commit
b1a1a25bf7
@ -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": {
|
"CompleteAccountRoute": {
|
||||||
"forms": {
|
"forms": {
|
||||||
"generateReports": {
|
"generateReports": {
|
||||||
|
@ -20,6 +20,7 @@ import GlobalSettingsRoute from "~/routes/GlobalSettingsRoute"
|
|||||||
import I18nHandler from "./I18nHandler"
|
import I18nHandler from "./I18nHandler"
|
||||||
import LoginRoute from "~/routes/LoginRoute"
|
import LoginRoute from "~/routes/LoginRoute"
|
||||||
import LogoutRoute from "~/routes/LogoutRoute"
|
import LogoutRoute from "~/routes/LogoutRoute"
|
||||||
|
import Recover2FARoute from "./routes/Recover2FARoute"
|
||||||
import RedirectRoute from "./routes/RedirectRoute"
|
import RedirectRoute from "./routes/RedirectRoute"
|
||||||
import ReportDetailRoute from "~/routes/ReportDetailRoute"
|
import ReportDetailRoute from "~/routes/ReportDetailRoute"
|
||||||
import ReportsRoute from "~/routes/ReportsRoute"
|
import ReportsRoute from "~/routes/ReportsRoute"
|
||||||
@ -53,6 +54,10 @@ const router = createBrowserRouter([
|
|||||||
loader: getServerSettings,
|
loader: getServerSettings,
|
||||||
element: <LoginRoute />,
|
element: <LoginRoute />,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/auth/recover-2fa",
|
||||||
|
element: <Recover2FARoute />,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "/auth/signup",
|
path: "/auth/signup",
|
||||||
loader: getServerSettings,
|
loader: getServerSettings,
|
||||||
|
@ -96,6 +96,7 @@ export default function OTPForm({
|
|||||||
</Grid>
|
</Grid>
|
||||||
<Grid item>
|
<Grid item>
|
||||||
<TextField
|
<TextField
|
||||||
|
autoFocus
|
||||||
key="code"
|
key="code"
|
||||||
fullWidth
|
fullWidth
|
||||||
name="code"
|
name="code"
|
||||||
|
123
src/routes/Recover2FARoute.tsx
Normal file
123
src/routes/Recover2FARoute.tsx
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
import * as yup from "yup"
|
||||||
|
import {ReactElement, useContext} from "react"
|
||||||
|
import {AxiosError} from "axios"
|
||||||
|
import {useTranslation} from "react-i18next"
|
||||||
|
import {BsShieldLockFill} from "react-icons/bs"
|
||||||
|
import {useFormik} from "formik"
|
||||||
|
import {useNavigate} from "react-router-dom"
|
||||||
|
|
||||||
|
import {useMutation} from "@tanstack/react-query"
|
||||||
|
import {LoadingButton} from "@mui/lab"
|
||||||
|
import {Box, Grid, Paper, TextField, Typography} from "@mui/material"
|
||||||
|
|
||||||
|
import {SimpleDetailResponse} from "~/server-types"
|
||||||
|
import {Delete2FAData, delete2FA, getMe} from "~/apis"
|
||||||
|
import {useErrorSuccessSnacks} from "~/hooks"
|
||||||
|
import {AuthContext} from "~/components"
|
||||||
|
|
||||||
|
interface Form {
|
||||||
|
recoveryCode: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Recover2FARoute(): ReactElement {
|
||||||
|
const {t} = useTranslation()
|
||||||
|
const {showError, showSuccess} = useErrorSuccessSnacks()
|
||||||
|
const {login} = useContext(AuthContext)
|
||||||
|
const navigate = useNavigate()
|
||||||
|
const {mutateAsync} = useMutation<SimpleDetailResponse, AxiosError, Delete2FAData>(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<Form>({
|
||||||
|
validationSchema: schema,
|
||||||
|
initialValues: {
|
||||||
|
recoveryCode: "",
|
||||||
|
},
|
||||||
|
onSubmit: async values =>
|
||||||
|
mutateAsync({
|
||||||
|
recoveryCode: values.recoveryCode.replaceAll("-", ""),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Paper>
|
||||||
|
<Box maxWidth="sm">
|
||||||
|
<form onSubmit={formik.handleSubmit}>
|
||||||
|
<Grid
|
||||||
|
container
|
||||||
|
spacing={4}
|
||||||
|
padding={4}
|
||||||
|
alignItems="center"
|
||||||
|
justifyContent="center"
|
||||||
|
flexDirection="column"
|
||||||
|
>
|
||||||
|
<Grid item>
|
||||||
|
<Typography variant="h5" component="h1" align="center">
|
||||||
|
{t("routes.Recover2FARoute.title")}
|
||||||
|
</Typography>
|
||||||
|
</Grid>
|
||||||
|
<Grid item>
|
||||||
|
<Box display="flex" justifyContent="center">
|
||||||
|
<BsShieldLockFill size={64} />
|
||||||
|
</Box>
|
||||||
|
</Grid>
|
||||||
|
<Grid item>
|
||||||
|
<Typography variant="body1" align="center">
|
||||||
|
{t("routes.Recover2FARoute.description")}
|
||||||
|
</Typography>
|
||||||
|
</Grid>
|
||||||
|
<Grid item>
|
||||||
|
<TextField
|
||||||
|
autoFocus
|
||||||
|
key="recoveryCode"
|
||||||
|
fullWidth
|
||||||
|
name="recoveryCode"
|
||||||
|
id="recoveryCode"
|
||||||
|
label={t("routes.Recover2FARoute.forms.recoveryCode.label")}
|
||||||
|
value={formik.values.recoveryCode}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
error={Boolean(
|
||||||
|
formik.touched.recoveryCode && formik.errors.recoveryCode,
|
||||||
|
)}
|
||||||
|
helperText={formik.errors.recoveryCode}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item>
|
||||||
|
<LoadingButton
|
||||||
|
type="submit"
|
||||||
|
variant="contained"
|
||||||
|
loading={formik.isSubmitting}
|
||||||
|
startIcon={<BsShieldLockFill />}
|
||||||
|
>
|
||||||
|
{t("routes.Recover2FARoute.forms.submit")}
|
||||||
|
</LoadingButton>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</form>
|
||||||
|
</Box>
|
||||||
|
</Paper>
|
||||||
|
)
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user