diff --git a/resources/scripts/components/auth/LoginCheckpointContainer.tsx b/resources/scripts/components/auth/LoginCheckpointContainer.tsx index 2c3b04cff..f8fa306f0 100644 --- a/resources/scripts/components/auth/LoginCheckpointContainer.tsx +++ b/resources/scripts/components/auth/LoginCheckpointContainer.tsx @@ -1,12 +1,10 @@ import React, { useEffect, useState } from 'react'; -import { StaticContext, useLocation } from 'react-router'; -import { Link, RouteComponentProps, useHistory } from 'react-router-dom'; +import { StaticContext } from 'react-router'; +import { RouteComponentProps } from 'react-router-dom'; import loginCheckpoint from '@/api/auth/loginCheckpoint'; import LoginFormContainer from '@/components/auth/LoginFormContainer'; -import { ActionCreator } from 'easy-peasy'; -import { useFormikContext, withFormik } from 'formik'; +import { Formik, FormikHelpers } from 'formik'; import useFlash from '@/plugins/useFlash'; -import { FlashStore } from '@/state/flashes'; import Field from '@/components/elements/Field'; import tw from 'twin.macro'; import Button from '@/components/elements/Button'; @@ -16,130 +14,80 @@ interface Values { recoveryCode: '', } -type OwnProps = RouteComponentProps, StaticContext, { token?: string }> +type OwnProps = RouteComponentProps, StaticContext, { token?: string, recovery?: boolean }> -type Props = OwnProps & { - clearAndAddHttpError: ActionCreator; -} +export default ({ history, location }: OwnProps) => { + const { clearFlashes, clearAndAddHttpError } = useFlash(); -const LoginCheckpointContainer = () => { - const history = useHistory(); - const location = useLocation(); - - const { isSubmitting, setFieldValue } = useFormikContext(); - const [ isMissingDevice, setIsMissingDevice ] = useState(false); - - const switchToSecurityKey = () => { - history.replace('/auth/login/key', { ...location.state }); - }; - - useEffect(() => { - setFieldValue('code', ''); - setFieldValue('recoveryCode', ''); - setIsMissingDevice(location.state?.recovery || false); - }, [ location.state ]); - - return ( - -
-
- -
-
- -
- -
- -
- { - setFieldValue('code', ''); - setFieldValue('recoveryCode', ''); - setIsMissingDevice(s => !s); - }} - css={tw`cursor-pointer text-xs text-neutral-500 tracking-wide uppercase no-underline hover:text-neutral-700`} - > - {!isMissingDevice ? 'I\'ve Lost My Device' : 'I Have My Device'} - -
-
-
- - Return to Login - -
-
-
- ); -}; - -const EnhancedForm = withFormik({ - handleSubmit: ({ code, recoveryCode }, { setSubmitting, props: { clearAndAddHttpError, location } }) => { + const onSubmit = ({ code, recoveryCode }: Values, { setSubmitting }: FormikHelpers) => { + clearFlashes(); loginCheckpoint(location.state?.token || '', code, recoveryCode) .then(response => { if (response.complete) { // @ts-ignore window.location = response.intended || '/'; - return; } - - setSubmitting(false); }) - .catch(error => { - console.error(error); - setSubmitting(false); - clearAndAddHttpError({ error }); - }); - }, + .catch(error => clearAndAddHttpError({ error })) + .then(() => setSubmitting(false)); + }; - mapPropsToValues: () => ({ - code: '', - recoveryCode: '', - }), -})(LoginCheckpointContainer); + const [ isMissingDevice, setIsMissingDevice ] = useState(false); -export default ({ history, location, ...props }: OwnProps) => { - const { clearAndAddHttpError } = useFlash(); + useEffect(() => { + if (!location.state?.token) { + history.replace('/auth/login'); + } + }, []); - if (!location.state?.token) { - history.replace('/auth/login'); + useEffect(() => { + setIsMissingDevice(location.state?.recovery || false); + }, [ location.state ]); - return null; - } - - return ; + return ( + + {({ isSubmitting, setFieldValue }) => ( + +
+
+ +
+ + +
+
+ )} +
+ ); }; diff --git a/resources/scripts/components/auth/LoginFormContainer.tsx b/resources/scripts/components/auth/LoginFormContainer.tsx index 83ed2e7ba..49d313ee0 100644 --- a/resources/scripts/components/auth/LoginFormContainer.tsx +++ b/resources/scripts/components/auth/LoginFormContainer.tsx @@ -3,6 +3,7 @@ import { Form } from 'formik'; import FlashMessageRender from '@/components/FlashMessageRender'; import tw, { styled } from 'twin.macro'; import PterodactylLogo from '@/assets/images/pterodactyl.svg'; +import { Link } from 'react-router-dom'; const Wrapper = styled.div` ${tw`sm:w-4/5 sm:mx-auto md:p-10 lg:w-3/5 xl:w-full`} @@ -17,7 +18,7 @@ interface InnerContentProps { const InnerContainer = ({ children, sidebar }: InnerContentProps) => (
- {sidebar || } + {sidebar || }
{children} diff --git a/resources/scripts/components/auth/LoginKeyCheckpointContainer.tsx b/resources/scripts/components/auth/LoginKeyCheckpointContainer.tsx index 1b14d227d..6a42ca065 100644 --- a/resources/scripts/components/auth/LoginKeyCheckpointContainer.tsx +++ b/resources/scripts/components/auth/LoginKeyCheckpointContainer.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; import tw from 'twin.macro'; import { DivContainer as LoginFormContainer } from '@/components/auth/LoginFormContainer'; import useFlash from '@/plugins/useFlash'; @@ -20,7 +20,7 @@ interface Credential extends PublicKeyCredential { response: AuthenticatorAssertionResponse; } -const challenge = async (publicKey: PublicKeyCredentialRequestOptions): Promise => { +const challenge = async (publicKey: PublicKeyCredentialRequestOptions, signal?: AbortSignal): Promise => { const publicKeyCredential = Object.assign({}, publicKey); publicKeyCredential.challenge = bufferDecode(base64Decode(publicKey.challenge.toString())); @@ -28,7 +28,7 @@ const challenge = async (publicKey: PublicKeyCredentialRequestOptions): Promise< publicKeyCredential.allowCredentials = decodeSecurityKeyCredentials(publicKey.allowCredentials); } - const credential = await navigator.credentials.get({ publicKey: publicKeyCredential }) as Credential | null; + const credential = await navigator.credentials.get({ signal, publicKey: publicKeyCredential }) as Credential | null; if (!credential) return Promise.reject(new Error('No credentials provided for challenge.')); return credential; @@ -37,13 +37,14 @@ const challenge = async (publicKey: PublicKeyCredentialRequestOptions): Promise< export default () => { const history = useHistory(); const location = useLocation(); + const controller = useRef(new AbortController()); const { clearFlashes, clearAndAddHttpError } = useFlash(); const [ redirecting, setRedirecting ] = useState(false); const triggerChallengePrompt = () => { clearFlashes(); - challenge(location.state.publicKey) + challenge(location.state.publicKey, controller.current.signal) .then((credential) => { setRedirecting(true); @@ -80,6 +81,12 @@ export default () => { }); }; + useEffect(() => { + return () => { + controller.current.abort(); + }; + }); + useEffect(() => { if (!location.state?.token) { history.replace('/auth/login'); @@ -111,14 +118,14 @@ export default () => {
{'I\'ve Lost My Device'}