From 9a0ed6b29115adeac9cf5014006db423f9c7c263 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sun, 22 Dec 2019 20:41:25 -0800 Subject: [PATCH] Add ability to disable two factor authentication --- .../Api/Client/TwoFactorController.php | 25 ++++++- .../api/account/disableAccountTwoFactor.ts | 9 +++ .../forms/ConfigureTwoFactorForm.tsx | 9 ++- .../dashboard/forms/DisableTwoFactorModal.tsx | 67 +++++++++++++++++++ 4 files changed, 107 insertions(+), 3 deletions(-) create mode 100644 resources/scripts/api/account/disableAccountTwoFactor.ts create mode 100644 resources/scripts/components/dashboard/forms/DisableTwoFactorModal.tsx diff --git a/app/Http/Controllers/Api/Client/TwoFactorController.php b/app/Http/Controllers/Api/Client/TwoFactorController.php index 17bae9baf..0dae225e5 100644 --- a/app/Http/Controllers/Api/Client/TwoFactorController.php +++ b/app/Http/Controllers/Api/Client/TwoFactorController.php @@ -2,6 +2,7 @@ namespace Pterodactyl\Http\Controllers\Api\Client; +use Carbon\Carbon; use Illuminate\Http\Request; use Illuminate\Http\Response; use Illuminate\Http\JsonResponse; @@ -100,7 +101,29 @@ class TwoFactorController extends ClientApiController return JsonResponse::create([], Response::HTTP_NO_CONTENT); } - public function delete() + /** + * Disables two-factor authentication on an account if the password provided + * is valid. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\JsonResponse + */ + public function delete(Request $request) { + if (! password_verify($request->input('password') ?? '', $request->user()->password)) { + throw new BadRequestHttpException( + 'The password provided was not valid.' + ); + } + + /** @var \Pterodactyl\Models\User $user */ + $user = $request->user(); + + $user->update([ + 'totp_authenticated_at' => Carbon::now(), + 'use_totp' => false, + ]); + + return JsonResponse::create([], Response::HTTP_NO_CONTENT); } } diff --git a/resources/scripts/api/account/disableAccountTwoFactor.ts b/resources/scripts/api/account/disableAccountTwoFactor.ts new file mode 100644 index 000000000..2b41fe20f --- /dev/null +++ b/resources/scripts/api/account/disableAccountTwoFactor.ts @@ -0,0 +1,9 @@ +import http from '@/api/http'; + +export default (password: string): Promise => { + return new Promise((resolve, reject) => { + http.delete('/api/client/account/two-factor', { params: { password } }) + .then(() => resolve()) + .catch(reject); + }); +}; diff --git a/resources/scripts/components/dashboard/forms/ConfigureTwoFactorForm.tsx b/resources/scripts/components/dashboard/forms/ConfigureTwoFactorForm.tsx index 7f8e502ab..aadd388ee 100644 --- a/resources/scripts/components/dashboard/forms/ConfigureTwoFactorForm.tsx +++ b/resources/scripts/components/dashboard/forms/ConfigureTwoFactorForm.tsx @@ -2,18 +2,23 @@ import React, { useState } from 'react'; import { useStoreState } from 'easy-peasy'; import { ApplicationStore } from '@/state'; import SetupTwoFactorModal from '@/components/dashboard/forms/SetupTwoFactorModal'; +import DisableTwoFactorModal from '@/components/dashboard/forms/DisableTwoFactorModal'; export default () => { const user = useStoreState((state: ApplicationStore) => state.user.data!); - const [visible, setVisible] = useState(false); + const [ visible, setVisible ] = useState(false); return user.useTotp ?
+ {visible && setVisible(false)}/>}

Two-factor authentication is currently enabled on your account.

-
diff --git a/resources/scripts/components/dashboard/forms/DisableTwoFactorModal.tsx b/resources/scripts/components/dashboard/forms/DisableTwoFactorModal.tsx new file mode 100644 index 000000000..f3c3e1cbe --- /dev/null +++ b/resources/scripts/components/dashboard/forms/DisableTwoFactorModal.tsx @@ -0,0 +1,67 @@ +import React from 'react'; +import { Form, Formik, FormikActions } from 'formik'; +import Modal, { RequiredModalProps } from '@/components/elements/Modal'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import Field from '@/components/elements/Field'; +import { object, string } from 'yup'; +import { Actions, useStoreActions } from 'easy-peasy'; +import { ApplicationStore } from '@/state'; +import disableAccountTwoFactor from '@/api/account/disableAccountTwoFactor'; +import { httpErrorToHuman } from '@/api/http'; + +interface Values { + password: string; +} + +export default ({ ...props }: RequiredModalProps) => { + const { addError, clearFlashes } = useStoreActions((actions: Actions) => actions.flashes); + const updateUserData = useStoreActions((actions: Actions) => actions.user.updateUserData); + + const submit = ({ password }: Values, { setSubmitting }: FormikActions) => { + clearFlashes('account:two-factor'); + disableAccountTwoFactor(password) + .then(() => { + updateUserData({ useTotp: false }); + props.onDismissed(); + }) + .catch(error => { + console.error(error); + + addError({ message: httpErrorToHuman(error), key: 'account:two-factor' }); + setSubmitting(false); + }); + }; + + return ( + + {({ isSubmitting, isValid }) => ( + +
+ + +
+ +
+ +
+ )} +
+ ); +};