diff --git a/app/Http/Controllers/Auth/LoginCheckpointController.php b/app/Http/Controllers/Auth/LoginCheckpointController.php index a136779ce..1cc2fe1af 100644 --- a/app/Http/Controllers/Auth/LoginCheckpointController.php +++ b/app/Http/Controllers/Auth/LoginCheckpointController.php @@ -66,7 +66,7 @@ class LoginCheckpointController extends AbstractLoginController * provided a valid username and password. * * @param \Pterodactyl\Http\Requests\Auth\LoginCheckpointRequest $request - * @return \Illuminate\Http\JsonResponse + * @return \Illuminate\Http\JsonResponse|void * * @throws \PragmaRX\Google2FA\Exceptions\IncompatibleWithGoogleAuthenticatorException * @throws \PragmaRX\Google2FA\Exceptions\InvalidCharactersException @@ -75,18 +75,19 @@ class LoginCheckpointController extends AbstractLoginController */ public function __invoke(LoginCheckpointRequest $request): JsonResponse { + $token = $request->input('confirmation_token'); + try { - $user = $this->repository->find( - $this->cache->pull($request->input('confirmation_token'), 0) - ); + $user = $this->repository->find($this->cache->get($token, 0)); } catch (RecordNotFoundException $exception) { return $this->sendFailedLoginResponse($request); } $decrypted = $this->encrypter->decrypt($user->totp_secret); - $window = $this->config->get('pterodactyl.auth.2fa.window'); - if ($this->google2FA->verifyKey($decrypted, $request->input('authentication_code'), $window)) { + if ($this->google2FA->verifyKey($decrypted, (string) $request->input('authentication_code') ?? '', config('pterodactyl.auth.2fa.window'))) { + $this->cache->delete($token); + return $this->sendLoginResponse($user, $request); } diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index db1744a99..0d7f21978 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -70,7 +70,7 @@ class LoginController extends AbstractLoginController * Handle a login request to the application. * * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\JsonResponse + * @return \Illuminate\Http\JsonResponse|void * * @throws \Pterodactyl\Exceptions\DisplayException * @throws \Illuminate\Validation\ValidationException diff --git a/resources/scripts/components/auth/LoginCheckpointContainer.tsx b/resources/scripts/components/auth/LoginCheckpointContainer.tsx index d8515c587..fd1e9b13a 100644 --- a/resources/scripts/components/auth/LoginCheckpointContainer.tsx +++ b/resources/scripts/components/auth/LoginCheckpointContainer.tsx @@ -1,115 +1,116 @@ -import React, { useState } from 'react'; +import React from 'react'; import { Link, RouteComponentProps } from 'react-router-dom'; import loginCheckpoint from '@/api/auth/loginCheckpoint'; import { httpErrorToHuman } from '@/api/http'; import LoginFormContainer from '@/components/auth/LoginFormContainer'; -import { Actions, useStoreActions } from 'easy-peasy'; +import { ActionCreator } from 'easy-peasy'; import { StaticContext } from 'react-router'; -import FlashMessageRender from '@/components/FlashMessageRender'; -import { ApplicationStore } from '@/state'; import Spinner from '@/components/elements/Spinner'; -import styled from 'styled-components'; -import { breakpoint } from 'styled-components-breakpoint'; +import { useFormikContext, withFormik } from 'formik'; +import { object, string } from 'yup'; +import useFlash from '@/plugins/useFlash'; +import { FlashStore } from '@/state/flashes'; +import Field from '@/components/elements/Field'; -const Container = styled.div` - ${breakpoint('sm')` - ${tw`w-4/5 mx-auto`} - `}; +interface Values { + code: string; +} - ${breakpoint('md')` - ${tw`p-10`} - `}; +type OwnProps = RouteComponentProps<{}, StaticContext, { token?: string }> - ${breakpoint('lg')` - ${tw`w-3/5`} - `}; +type Props = OwnProps & { + addError: ActionCreator; + clearFlashes: ActionCreator; +} - ${breakpoint('xl')` - ${tw`w-full`} - max-width: 660px; - `}; -`; +const LoginCheckpointContainer = () => { + const { isSubmitting } = useFormikContext(); -export default ({ history, location: { state } }: RouteComponentProps<{}, StaticContext, { token?: string }>) => { - const [ code, setCode ] = useState(''); - const [ isLoading, setIsLoading ] = useState(false); + return ( + +
+ +
+
+ +
+
+ + Return to Login + +
+
+ ); +}; - const { clearFlashes, addFlash } = useStoreActions((actions: Actions) => actions.flashes); +const EnhancedForm = withFormik({ + handleSubmit: ({ code }, { setSubmitting, props: { addError, clearFlashes, location } }) => { + clearFlashes(); + console.log(location.state.token, code); + loginCheckpoint(location.state?.token || '', code) + .then(response => { + if (response.complete) { + // @ts-ignore + window.location = response.intended || '/'; + return; + } +å + setSubmitting(false); + }) + .catch(error => { + console.error(error); + setSubmitting(false); + addError({ message: httpErrorToHuman(error) }); + }); + }, - if (!state || !state.token) { + mapPropsToValues: () => ({ + code: '', + }), + + validationSchema: object().shape({ + code: string().required('An authentication code must be provided.') + .length(6, 'Authentication code must be 6 digits in length.'), + }), +})(LoginCheckpointContainer); + +export default ({ history, location, ...props }: OwnProps) => { + const { addError, clearFlashes } = useFlash(); + + if (!location.state?.token) { history.replace('/auth/login'); return null; } - const onChangeHandler = (e: React.ChangeEvent) => { - if (e.target.value.length <= 6) { - setCode(e.target.value); - } - }; - - const submit = (e: React.FormEvent) => { - e.preventDefault(); - - setIsLoading(true); - clearFlashes(); - - loginCheckpoint(state.token!, code) - .then(response => { - if (response.complete) { - // @ts-ignore - window.location = response.intended || '/'; - } - }) - .catch(error => { - console.error(error); - addFlash({ type: 'error', title: 'Error', message: httpErrorToHuman(error) }); - setIsLoading(false); - }); - }; - - return ( - -

- Device Checkpoint -

- - - -
- - -
-
- -
-
- - Return to Login - -
-
-
-
- ); + return ; };