From 8ae24aec8e027a79aa7a13910a0ca464247e8abf Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Fri, 4 Nov 2022 21:48:17 +0100 Subject: [PATCH] added ability to login from different devices --- public/locales/en/translation.json | 5 + ...llow-email-login-from-different-devices.ts | 2 +- src/apis/verify-login-with-email.ts | 2 +- .../ConfirmCodeForm/ConfirmCodeForm.tsx | 101 ++++++++++++++---- .../LoginRoute/ConfirmFromDifferentDevice.tsx | 71 ++++++++++++ src/routes/LoginRoute.tsx | 7 ++ 6 files changed, 164 insertions(+), 24 deletions(-) create mode 100644 src/route-widgets/LoginRoute/ConfirmFromDifferentDevice.tsx diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index d0cdd08..a5d738d 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -35,6 +35,7 @@ "title": "You got mail!", "description": "We sent you a code to your email. Enter it below to login", "continueAction": "Log in", + "allowLoginFromDifferentDevices": "Allow login from different devices", "form": { "code": { "label": "Verification Code", @@ -43,6 +44,10 @@ } } } + }, + "confirmFromDifferentDevice": { + "title": "Login failed", + "description": "You could not be logged in. This could either be because you are not allowed to login from different devices or the verification code is invalid or expired." } } }, diff --git a/src/apis/change-allow-email-login-from-different-devices.ts b/src/apis/change-allow-email-login-from-different-devices.ts index f3e4173..f7fab05 100644 --- a/src/apis/change-allow-email-login-from-different-devices.ts +++ b/src/apis/change-allow-email-login-from-different-devices.ts @@ -15,7 +15,7 @@ export default async function changeAllowEmailLoginFromDifferentDevices({ const {data} = await client.patch( `${ import.meta.env.VITE_SERVER_BASE_URL - }/auth/login/email-token/allow-email-login-from-different-devices`, + }/auth/login/email-token/allow-login-from-different-devices`, { email, sameRequestToken, diff --git a/src/apis/verify-login-with-email.ts b/src/apis/verify-login-with-email.ts index c74b68a..84bc899 100644 --- a/src/apis/verify-login-with-email.ts +++ b/src/apis/verify-login-with-email.ts @@ -5,7 +5,7 @@ import parseUser from "~/apis/helpers/parse-user" export interface VerifyLoginWithEmailData { email: string token: string - sameRequestToken: string + sameRequestToken?: string } export default async function verifyLoginWithEmail({ diff --git a/src/route-widgets/LoginRoute/ConfirmCodeForm/ConfirmCodeForm.tsx b/src/route-widgets/LoginRoute/ConfirmCodeForm/ConfirmCodeForm.tsx index 3f450ac..5a265b5 100644 --- a/src/route-widgets/LoginRoute/ConfirmCodeForm/ConfirmCodeForm.tsx +++ b/src/route-widgets/LoginRoute/ConfirmCodeForm/ConfirmCodeForm.tsx @@ -1,6 +1,6 @@ import * as yup from "yup" import {AxiosError} from "axios" -import {ReactElement} from "react" +import {ReactElement, useState} from "react" import {useFormik} from "formik" import {FaHashtag} from "react-icons/fa" import {MdChevronRight, MdMail} from "react-icons/md" @@ -8,13 +8,27 @@ import {useLoaderData} from "react-router-dom" import {useTranslation} from "react-i18next" import {useMutation} from "@tanstack/react-query" -import {Box, Grid, InputAdornment, TextField, Typography} from "@mui/material" +import { + Box, + FormControlLabel, + Grid, + InputAdornment, + Switch, + TextField, + Typography, +} from "@mui/material" import {LoadingButton} from "@mui/lab" -import {AuthenticationDetails, ServerSettings, ServerUser} from "~/server-types" +import { + AuthenticationDetails, + ServerSettings, + ServerUser, + SimpleDetailResponse, +} from "~/server-types" import {VerifyLoginWithEmailData, verifyLoginWithEmail} from "~/apis" import {MultiStepFormElement} from "~/components" import {parseFastAPIError} from "~/utils" +import changeAllowEmailLoginFromDifferentDevices from "~/apis/change-allow-email-login-from-different-devices" import ResendMailButton from "./ResendMailButton" @@ -84,6 +98,25 @@ export default function ConfirmCodeForm({ } }, }) + const [allowLoginFromDifferentDevices, setAllowLoginFromDifferentDevices] = + useState(false) + const {mutateAsync: changeAllowLoginFromDifferentDevice, isLoading} = useMutation< + SimpleDetailResponse, + AxiosError, + boolean + >( + allow => + changeAllowEmailLoginFromDifferentDevices({ + allow, + email, + sameRequestToken, + }), + { + onSuccess: (_, allow) => { + setAllowLoginFromDifferentDevices(allow) + }, + }, + ) return ( @@ -111,25 +144,49 @@ export default function ConfirmCodeForm({ - - - - ), - }} - /> + + + + changeAllowLoginFromDifferentDevice( + !allowLoginFromDifferentDevices, + ) + } + /> + } + labelPlacement="end" + label={t( + "routes.LoginRoute.forms.confirmCode.allowLoginFromDifferentDevices", + )} + /> + + + + + + ), + }} + /> + + diff --git a/src/route-widgets/LoginRoute/ConfirmFromDifferentDevice.tsx b/src/route-widgets/LoginRoute/ConfirmFromDifferentDevice.tsx new file mode 100644 index 0000000..d1c1244 --- /dev/null +++ b/src/route-widgets/LoginRoute/ConfirmFromDifferentDevice.tsx @@ -0,0 +1,71 @@ +import {ReactElement} from "react" +import {AxiosError} from "axios" +import {useMount} from "react-use" +import {useTranslation} from "react-i18next" + +import {useMutation} from "@tanstack/react-query" +import {Box, Grid, Paper, Typography} from "@mui/material" + +import {AuthenticationDetails, ServerUser} from "~/server-types" +import {verifyLoginWithEmail} from "~/apis" +import LoadingData from "~/components/LoadingData" + +export interface ConfirmFromDifferentDeviceProps { + email: string + token: string + + onConfirm: (user: ServerUser) => void +} + +export default function ConfirmFromDifferentDevice({ + email, + token, + onConfirm, +}: ConfirmFromDifferentDeviceProps): ReactElement { + const {t} = useTranslation() + const {mutate, isLoading, isError} = useMutation( + () => + verifyLoginWithEmail({ + email, + token, + }), + { + onSuccess: ({user}) => onConfirm(user), + }, + ) + + useMount(mutate) + + if (isLoading) { + return ( + + + + ) + } + + if (isError) { + return ( + + + + + + {t("routes.LoginRoute.forms.confirmFromDifferentDevice.title")} + + + + + {t( + "routes.LoginRoute.forms.confirmFromDifferentDevice.description", + )} + + + + + + ) + } + + return <> +} diff --git a/src/routes/LoginRoute.tsx b/src/routes/LoginRoute.tsx index 193cfe5..ab17e29 100644 --- a/src/routes/LoginRoute.tsx +++ b/src/routes/LoginRoute.tsx @@ -3,12 +3,15 @@ import {useNavigate} from "react-router-dom" import {useUpdateEffect} from "react-use" import {MultiStepForm} from "~/components" +import {useQueryParams} from "~/hooks" import AuthContext from "~/AuthContext/AuthContext" import ConfirmCodeForm from "~/route-widgets/LoginRoute/ConfirmCodeForm/ConfirmCodeForm" +import ConfirmFromDifferentDevice from "~/route-widgets/LoginRoute/ConfirmFromDifferentDevice" import EmailForm from "~/route-widgets/LoginRoute/EmailForm" export default function LoginRoute(): ReactElement { const navigate = useNavigate() + const {token, email: queryEmail} = useQueryParams<{token: string; email: string}>() const {login, user} = useContext(AuthContext) const [email, setEmail] = useState("") @@ -26,6 +29,10 @@ export default function LoginRoute(): ReactElement { } }, [user?.encryptedPassword]) + if (token && queryEmail) { + return + } + return (