feat: Add disable 2FA

This commit is contained in:
Myzel394 2023-02-26 11:53:08 +01:00
parent 564861fc9f
commit 7ec6b81b5e
No known key found for this signature in database
GPG Key ID: 79CC92F37B3E1A2B
5 changed files with 155 additions and 6 deletions

View File

@ -307,6 +307,20 @@
"description": "These codes are used to recover your account if you lose access to your authenticator app. Note them down and store them in a safe place. You will not be able to view them again. Do not store them in your password manager. IF YOU LOSE YOUR RECOVERY CODES, YOU WILL LOSE ACCESS TO YOUR ACCOUNT. WE WILL NOT BE ABLE TO HELP YOU.", "description": "These codes are used to recover your account if you lose access to your authenticator app. Note them down and store them in a safe place. You will not be able to view them again. Do not store them in your password manager. IF YOU LOSE YOUR RECOVERY CODES, YOU WILL LOSE ACCESS TO YOUR ACCOUNT. WE WILL NOT BE ABLE TO HELP YOU.",
"submit": "I have noted down my recovery codes" "submit": "I have noted down my recovery codes"
} }
},
"delete": {
"showAction": "Disable 2FA",
"askType": {
"code": "I have my 2FA code",
"recoveryCode": "I have a recovery code"
},
"askCode": {
"label": "Code"
},
"askRecoveryCode": {
"label": "Recovery Code"
},
"submit": "Disable 2FA"
} }
} }
}, },

22
src/apis/delete-2fa.ts Normal file
View File

@ -0,0 +1,22 @@
import {SimpleDetailResponse} from "~/server-types"
import {client} from "~/constants/axios-client"
export interface Delete2FAData {
code?: string
recoveryCode?: string
}
export default async function delete2FA({
recoveryCode,
code,
}: Delete2FAData): Promise<SimpleDetailResponse> {
const {data} = await client.delete(`${import.meta.env.VITE_SERVER_BASE_URL}/v1/setup-otp`, {
withCredentials: true,
data: {
code,
recoveryCode,
},
})
return data
}

View File

@ -64,3 +64,5 @@ export * from "./setup-2fa"
export {default as setup2FA} from "./setup-2fa" export {default as setup2FA} from "./setup-2fa"
export * from "./setup-2fa-verify" export * from "./setup-2fa-verify"
export {default as verify2FASetup} from "./setup-2fa-verify" export {default as verify2FASetup} from "./setup-2fa-verify"
export * from "./delete-2fa"
export {default as delete2FA} from "./delete-2fa"

View File

@ -0,0 +1,105 @@
import {ReactElement, useState} from "react"
import {AxiosError} from "axios"
import {useTranslation} from "react-i18next"
import {BsPhone, BsShieldLockFill} from "react-icons/bs"
import {MdSettingsBackupRestore} from "react-icons/md"
import {useMutation} from "@tanstack/react-query"
import {Button, Grid, TextField} from "@mui/material"
import {LoadingButton} from "@mui/lab"
import {Delete2FAData, delete2FA} from "~/apis"
import {useErrorSuccessSnacks} from "~/hooks"
import {SimpleDetailResponse} from "~/server-types"
export interface Delete2FAProps {
onSuccess: () => void
}
export default function Delete2FA({onSuccess}: Delete2FAProps): ReactElement {
const {t} = useTranslation()
const {showError} = useErrorSuccessSnacks()
const {mutate} = useMutation<SimpleDetailResponse, AxiosError, Delete2FAData>(delete2FA, {
onSuccess,
onError: showError,
})
const [view, setView] = useState<"showAction" | "askType" | "askCode" | "askRecoveryCode">(
"showAction",
)
const [value, setValue] = useState<string>("")
switch (view) {
case "showAction":
return (
<Button onClick={() => setView("askType")} startIcon={<BsShieldLockFill />}>
{t("routes.SettingsRoute.2fa.delete.showAction")}
</Button>
)
case "askType":
return (
<Grid container spacing={2}>
<Grid item>
<Button onClick={() => setView("askCode")} startIcon={<BsPhone />}>
{t("routes.SettingsRoute.2fa.delete.askType.code")}
</Button>
</Grid>
<Grid item>
<Button
onClick={() => setView("askRecoveryCode")}
startIcon={<MdSettingsBackupRestore />}
>
{t("routes.SettingsRoute.2fa.delete.askType.recoveryCode")}
</Button>
</Grid>
</Grid>
)
case "askCode":
return (
<Grid container spacing={2} alignItems="center">
<Grid item>
<TextField
fullWidth
label={t("routes.SettingsRoute.2fa.delete.askCode.label")}
value={value}
onChange={e => setValue(e.target.value)}
/>
</Grid>
<Grid item>
<LoadingButton
onClick={() => mutate({code: value})}
variant="contained"
startIcon={<BsShieldLockFill />}
>
{t("routes.SettingsRoute.2fa.delete.submit")}
</LoadingButton>
</Grid>
</Grid>
)
case "askRecoveryCode":
return (
<Grid container spacing={2} alignItems="center">
<Grid item>
<TextField
fullWidth
label={t("routes.SettingsRoute.2fa.delete.askRecoveryCode.label")}
value={value}
onChange={e => setValue(e.target.value)}
/>
</Grid>
<Grid item>
<LoadingButton
onClick={() => mutate({recoveryCode: value})}
variant="contained"
startIcon={<BsShieldLockFill />}
>
{t("routes.SettingsRoute.2fa.delete.submit")}
</LoadingButton>
</Grid>
</Grid>
)
}
}

View File

@ -3,9 +3,10 @@ import {AxiosError} from "axios"
import {useTranslation} from "react-i18next" import {useTranslation} from "react-i18next"
import {useQuery} from "@tanstack/react-query" import {useQuery} from "@tanstack/react-query"
import {Alert} from "@mui/material" import {Alert, Grid} from "@mui/material"
import {QueryResult, SimplePageBuilder} from "~/components" import {QueryResult, SimplePageBuilder} from "~/components"
import Delete2FA from "~/route-widgets/Settings2FARoute/Delete2FA"
import Setup2FA from "~/route-widgets/Settings2FARoute/Setup2FA" import Setup2FA from "~/route-widgets/Settings2FARoute/Setup2FA"
import getHas2FAEnabled from "~/apis/get-has-2fa-enabled" import getHas2FAEnabled from "~/apis/get-has-2fa-enabled"
@ -19,11 +20,16 @@ export default function Settings2FARoute(): ReactElement {
<QueryResult<boolean, AxiosError> query={query}> <QueryResult<boolean, AxiosError> query={query}>
{has2FAEnabled => {has2FAEnabled =>
has2FAEnabled ? ( has2FAEnabled ? (
<> <Grid container spacing={4} direction="column" alignItems="center">
<Grid item>
<Alert severity="success"> <Alert severity="success">
{t("routes.SettingsRoute.2fa.alreadyEnabled")} {t("routes.SettingsRoute.2fa.alreadyEnabled")}
</Alert> </Alert>
</> </Grid>
<Grid item>
<Delete2FA onSuccess={query.refetch} />
</Grid>
</Grid>
) : ( ) : (
<Setup2FA onSuccess={query.refetch} /> <Setup2FA onSuccess={query.refetch} />
) )