feat: TOTP (two-factor) Authentication (#55)
* Working on some initial prototype stuff for TOTP * Fixed a bug that prevented the change password menu from working * Enable/disable totp working * Added the new login procedure including TOTP! :) * misc: Changed bad description for the TOTP_SECRET env var * I forgot to include the migration for the new prisma stuff * fix: refresh user context instead refreshing the page * refactor: simplify totp error handling * Removed U2F tab + format schema * fix: tokens not saved in cookies * refactor: deleted commented out code * refactor: move password text to input description * refactor: remove tabler icon package Co-authored-by: Elias Schneider <login@eliasschneider.com> Co-authored-by: Elias Schneider <58886915+stonith404@users.noreply.github.com>
This commit is contained in:
@@ -6,6 +6,7 @@ import {
|
||||
Paper,
|
||||
PasswordInput,
|
||||
Stack,
|
||||
Tabs,
|
||||
Text,
|
||||
TextInput,
|
||||
Title,
|
||||
@@ -13,14 +14,16 @@ import {
|
||||
import { useForm, yupResolver } from "@mantine/form";
|
||||
import { useModals } from "@mantine/modals";
|
||||
import { useRouter } from "next/router";
|
||||
import { Tb2Fa } from "react-icons/tb";
|
||||
import * as yup from "yup";
|
||||
import showEnableTotpModal from "../../components/account/showEnableTotpModal";
|
||||
import useUser from "../../hooks/user.hook";
|
||||
import authService from "../../services/auth.service";
|
||||
import userService from "../../services/user.service";
|
||||
import toast from "../../utils/toast.util";
|
||||
|
||||
const Account = () => {
|
||||
const user = useUser();
|
||||
const { user, setUser } = useUser();
|
||||
const modals = useModals();
|
||||
const router = useRouter();
|
||||
|
||||
@@ -50,6 +53,36 @@ const Account = () => {
|
||||
),
|
||||
});
|
||||
|
||||
const enableTotpForm = useForm({
|
||||
initialValues: {
|
||||
password: "",
|
||||
},
|
||||
validate: yupResolver(
|
||||
yup.object().shape({
|
||||
password: yup.string().min(8),
|
||||
})
|
||||
),
|
||||
});
|
||||
|
||||
const disableTotpForm = useForm({
|
||||
initialValues: {
|
||||
password: "",
|
||||
code: "",
|
||||
},
|
||||
validate: yupResolver(
|
||||
yup.object().shape({
|
||||
password: yup.string().min(8),
|
||||
code: yup
|
||||
.string()
|
||||
.min(6)
|
||||
.max(6)
|
||||
.matches(/^[0-9]+$/, { message: "Code must be a number" }),
|
||||
})
|
||||
),
|
||||
});
|
||||
|
||||
const refreshUser = async () => setUser(await userService.getCurrentUser());
|
||||
|
||||
if (!user) {
|
||||
router.push("/");
|
||||
return;
|
||||
@@ -117,31 +150,120 @@ const Account = () => {
|
||||
</Stack>
|
||||
</form>
|
||||
</Paper>
|
||||
<Center mt={80}>
|
||||
<Button
|
||||
variant="light"
|
||||
color="red"
|
||||
onClick={() =>
|
||||
modals.openConfirmModal({
|
||||
title: "Account deletion",
|
||||
children: (
|
||||
<Text size="sm">
|
||||
Do you really want to delete your account including all your
|
||||
active shares?
|
||||
</Text>
|
||||
),
|
||||
|
||||
labels: { confirm: "Delete", cancel: "Cancel" },
|
||||
confirmProps: { color: "red" },
|
||||
onConfirm: async () => {
|
||||
await userService.removeCurrentUser();
|
||||
window.location.reload();
|
||||
},
|
||||
})
|
||||
}
|
||||
>
|
||||
Delete Account
|
||||
</Button>
|
||||
<Paper withBorder p="xl" mt="lg">
|
||||
<Title order={5} mb="xs">
|
||||
Security
|
||||
</Title>
|
||||
|
||||
<Tabs defaultValue="totp">
|
||||
<Tabs.List>
|
||||
<Tabs.Tab value="totp" icon={<Tb2Fa size={14} />}>
|
||||
TOTP
|
||||
</Tabs.Tab>
|
||||
</Tabs.List>
|
||||
|
||||
<Tabs.Panel value="totp" pt="xs">
|
||||
{/* TODO: This is ugly, make it prettier */}
|
||||
{/* If we have totp enabled, show different text */}
|
||||
{user.totpVerified ? (
|
||||
<>
|
||||
<form
|
||||
onSubmit={disableTotpForm.onSubmit((values) => {
|
||||
authService
|
||||
.disableTOTP(values.code, values.password)
|
||||
.then(() => {
|
||||
toast.success("Successfully disabled TOTP");
|
||||
values.password = "";
|
||||
values.code = "";
|
||||
refreshUser();
|
||||
})
|
||||
.catch(toast.axiosError);
|
||||
})}
|
||||
>
|
||||
<Stack>
|
||||
<PasswordInput
|
||||
description="Enter your current password to disable TOTP"
|
||||
label="Password"
|
||||
{...disableTotpForm.getInputProps("password")}
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
variant="filled"
|
||||
label="Code"
|
||||
placeholder="******"
|
||||
{...disableTotpForm.getInputProps("code")}
|
||||
/>
|
||||
|
||||
<Group position="right">
|
||||
<Button color="red" type="submit">
|
||||
Disable
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</form>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<form
|
||||
onSubmit={enableTotpForm.onSubmit((values) => {
|
||||
authService
|
||||
.enableTOTP(values.password)
|
||||
.then((result) => {
|
||||
showEnableTotpModal(modals, refreshUser, {
|
||||
qrCode: result.qrCode,
|
||||
secret: result.totpSecret,
|
||||
password: values.password,
|
||||
});
|
||||
values.password = "";
|
||||
})
|
||||
.catch(toast.axiosError);
|
||||
})}
|
||||
>
|
||||
<Stack>
|
||||
<PasswordInput
|
||||
label="Password"
|
||||
description="Enter your current password to start enabling TOTP"
|
||||
{...enableTotpForm.getInputProps("password")}
|
||||
/>
|
||||
<Group position="right">
|
||||
<Button type="submit">Start</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</form>
|
||||
</>
|
||||
)}
|
||||
</Tabs.Panel>
|
||||
</Tabs>
|
||||
</Paper>
|
||||
|
||||
<Center mt={80}>
|
||||
<Stack>
|
||||
<Button
|
||||
variant="light"
|
||||
color="red"
|
||||
onClick={() =>
|
||||
modals.openConfirmModal({
|
||||
title: "Account deletion",
|
||||
children: (
|
||||
<Text size="sm">
|
||||
Do you really want to delete your account including all your
|
||||
active shares?
|
||||
</Text>
|
||||
),
|
||||
|
||||
labels: { confirm: "Delete", cancel: "Cancel" },
|
||||
confirmProps: { color: "red" },
|
||||
onConfirm: async () => {
|
||||
await userService.removeCurrentUser();
|
||||
window.location.reload();
|
||||
},
|
||||
})
|
||||
}
|
||||
>
|
||||
Delete Account
|
||||
</Button>
|
||||
</Stack>
|
||||
</Center>
|
||||
</Container>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user