diff --git a/app/Http/Controllers/Api/Client/TwoFactorController.php b/app/Http/Controllers/Api/Client/TwoFactorController.php index 07eeb297..b14f9d4b 100644 --- a/app/Http/Controllers/Api/Client/TwoFactorController.php +++ b/app/Http/Controllers/Api/Client/TwoFactorController.php @@ -61,9 +61,7 @@ class TwoFactorController extends ClientApiController } return new JsonResponse([ - 'data' => [ - 'image_url_data' => $this->setupService->handle($request->user()), - ], + 'data' => $this->setupService->handle($request->user()), ]); } diff --git a/app/Services/Users/TwoFactorSetupService.php b/app/Services/Users/TwoFactorSetupService.php index e3649634..be234d46 100644 --- a/app/Services/Users/TwoFactorSetupService.php +++ b/app/Services/Users/TwoFactorSetupService.php @@ -49,7 +49,7 @@ class TwoFactorSetupService * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function handle(User $user): string + public function handle(User $user): array { $secret = ''; try { @@ -66,11 +66,14 @@ class TwoFactorSetupService $company = urlencode(preg_replace('/\s/', '', $this->config->get('app.name'))); - return sprintf( - 'otpauth://totp/%1$s:%2$s?secret=%3$s&issuer=%1$s', - rawurlencode($company), - rawurlencode($user->email), - rawurlencode($secret) - ); + return [ + 'image_url_data' => sprintf( + 'otpauth://totp/%1$s:%2$s?secret=%3$s&issuer=%1$s', + rawurlencode($company), + rawurlencode($user->email), + rawurlencode($secret), + ), + 'secret' => $secret, + ]; } } diff --git a/resources/scripts/api/account/getTwoFactorTokenData.ts b/resources/scripts/api/account/getTwoFactorTokenData.ts new file mode 100644 index 00000000..b3669d2d --- /dev/null +++ b/resources/scripts/api/account/getTwoFactorTokenData.ts @@ -0,0 +1,15 @@ +import http from '@/api/http'; + +export interface TwoFactorTokenData { + // eslint-disable-next-line camelcase + image_url_data: string; + secret: string; +} + +export default (): Promise => { + return new Promise((resolve, reject) => { + http.get('/api/client/account/two-factor') + .then(({ data }) => resolve(data.data)) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/account/getTwoFactorTokenUrl.ts b/resources/scripts/api/account/getTwoFactorTokenUrl.ts deleted file mode 100644 index 6d9a2aa9..00000000 --- a/resources/scripts/api/account/getTwoFactorTokenUrl.ts +++ /dev/null @@ -1,9 +0,0 @@ -import http from '@/api/http'; - -export default (): Promise => { - return new Promise((resolve, reject) => { - http.get('/api/client/account/two-factor') - .then(({ data }) => resolve(data.data.image_url_data)) - .catch(reject); - }); -}; diff --git a/resources/scripts/components/dashboard/forms/SetupTwoFactorModal.tsx b/resources/scripts/components/dashboard/forms/SetupTwoFactorModal.tsx index 72709478..4bb73f36 100644 --- a/resources/scripts/components/dashboard/forms/SetupTwoFactorModal.tsx +++ b/resources/scripts/components/dashboard/forms/SetupTwoFactorModal.tsx @@ -1,7 +1,7 @@ import React, { useContext, useEffect, useState } from 'react'; import { Form, Formik, FormikHelpers } from 'formik'; import { object, string } from 'yup'; -import getTwoFactorTokenUrl from '@/api/account/getTwoFactorTokenUrl'; +import getTwoFactorTokenData, { TwoFactorTokenData } from '@/api/account/getTwoFactorTokenData'; import enableAccountTwoFactor from '@/api/account/enableAccountTwoFactor'; import { Actions, useStoreActions } from 'easy-peasy'; import { ApplicationStore } from '@/state'; @@ -12,13 +12,14 @@ import Button from '@/components/elements/Button'; import asModal from '@/hoc/asModal'; import ModalContext from '@/context/ModalContext'; import QRCode from 'qrcode.react'; +import CopyOnClick from '@/components/elements/CopyOnClick'; interface Values { code: string; } const SetupTwoFactorModal = () => { - const [ token, setToken ] = useState(''); + const [ token, setToken ] = useState(null); const [ recoveryTokens, setRecoveryTokens ] = useState([]); const { dismiss, setPropOverrides } = useContext(ModalContext); @@ -26,7 +27,7 @@ const SetupTwoFactorModal = () => { const { clearAndAddHttpError } = useStoreActions((actions: Actions) => actions.flashes); useEffect(() => { - getTwoFactorTokenUrl() + getTwoFactorTokenData() .then(setToken) .catch(error => { console.error(error); @@ -102,13 +103,17 @@ const SetupTwoFactorModal = () => {
- {!token || !token.length ? + {!token ? : - + }
@@ -121,11 +126,21 @@ const SetupTwoFactorModal = () => { title={'Code From Authenticator'} description={'Enter the code from your authenticator device after scanning the QR image.'} /> + {token && +
+ Alternatively, enter the following token into your authenticator application: + +
+ + {token.secret} + +
+
+
+ }
- +