1
1
mirror of https://github.com/pterodactyl/panel.git synced 2024-11-25 18:42:31 +01:00

Cleanup logic powering totp enabling modal

This commit is contained in:
DaneEveritt 2022-07-03 13:43:54 -04:00
parent a4feed24a8
commit 92926ca193
No known key found for this signature in database
GPG Key ID: EEA66103B3D71F53
2 changed files with 76 additions and 87 deletions

View File

@ -3,16 +3,24 @@ import { useStoreState } from 'easy-peasy';
import { ApplicationStore } from '@/state'; import { ApplicationStore } from '@/state';
import tw from 'twin.macro'; import tw from 'twin.macro';
import { Button } from '@/components/elements/button/index'; import { Button } from '@/components/elements/button/index';
import SetupTOTPModal from '@/components/dashboard/forms/SetupTOTPModal';
import DisableTwoFactorModal from '@/components/dashboard/forms/DisableTwoFactorModal'; import DisableTwoFactorModal from '@/components/dashboard/forms/DisableTwoFactorModal';
import SetupTOTPModal from '@/components/dashboard/forms/SetupTOTPModal';
import RecoveryTokensDialog from '@/components/dashboard/forms/RecoveryTokensDialog';
export default () => { export default () => {
const [tokens, setTokens] = useState<string[]>([]);
const [visible, setVisible] = useState<'enable' | 'disable' | null>(null); const [visible, setVisible] = useState<'enable' | 'disable' | null>(null);
const isEnabled = useStoreState((state: ApplicationStore) => state.user.data!.useTotp); const isEnabled = useStoreState((state: ApplicationStore) => state.user.data!.useTotp);
const onTokens = (tokens: string[]) => {
setTokens(tokens);
setVisible(null);
};
return ( return (
<div> <div>
<SetupTOTPModal open={visible === 'enable'} onClose={() => setVisible(null)} /> <SetupTOTPModal open={visible === 'enable'} onClose={() => setVisible(null)} onTokens={onTokens} />
<RecoveryTokensDialog tokens={tokens} open={tokens.length > 0} onClose={() => setTokens([])} />
<DisableTwoFactorModal visible={visible === 'disable'} onModalDismissed={() => setVisible(null)} /> <DisableTwoFactorModal visible={visible === 'disable'} onModalDismissed={() => setVisible(null)} />
<p css={tw`text-sm`}> <p css={tw`text-sm`}>
{isEnabled {isEnabled

View File

@ -1,5 +1,5 @@
import React, { useEffect, useState } from 'react'; import React, { useContext, useEffect, useState } from 'react';
import { Dialog, DialogProps } from '@/components/elements/dialog'; import { Dialog, DialogWrapperContext } from '@/components/elements/dialog';
import getTwoFactorTokenData, { TwoFactorTokenData } from '@/api/account/getTwoFactorTokenData'; import getTwoFactorTokenData, { TwoFactorTokenData } from '@/api/account/getTwoFactorTokenData';
import { useFlashKey } from '@/plugins/useFlash'; import { useFlashKey } from '@/plugins/useFlash';
import tw from 'twin.macro'; import tw from 'twin.macro';
@ -11,39 +11,28 @@ import CopyOnClick from '@/components/elements/CopyOnClick';
import Tooltip from '@/components/elements/tooltip/Tooltip'; import Tooltip from '@/components/elements/tooltip/Tooltip';
import enableAccountTwoFactor from '@/api/account/enableAccountTwoFactor'; import enableAccountTwoFactor from '@/api/account/enableAccountTwoFactor';
import FlashMessageRender from '@/components/FlashMessageRender'; import FlashMessageRender from '@/components/FlashMessageRender';
import RecoveryTokensDialog from '@/components/dashboard/forms/RecoveryTokensDialog';
import { Actions, useStoreActions } from 'easy-peasy'; import { Actions, useStoreActions } from 'easy-peasy';
import { ApplicationStore } from '@/state'; import { ApplicationStore } from '@/state';
import asDialog from '@/hoc/asDialog';
type SetupTOTPModalProps = DialogProps; interface Props {
onTokens: (tokens: string[]) => void;
}
export default ({ open, onClose }: SetupTOTPModalProps) => { const ConfigureTwoFactorForm = ({ onTokens }: Props) => {
const [submitting, setSubmitting] = useState(false); const [submitting, setSubmitting] = useState(false);
const [value, setValue] = useState(''); const [value, setValue] = useState('');
const [tokens, setTokens] = useState<string[]>([]);
const [token, setToken] = useState<TwoFactorTokenData | null>(null); const [token, setToken] = useState<TwoFactorTokenData | null>(null);
const { clearAndAddHttpError } = useFlashKey('account:two-step'); const { clearAndAddHttpError } = useFlashKey('account:two-step');
const updateUserData = useStoreActions((actions: Actions<ApplicationStore>) => actions.user.updateUserData); const updateUserData = useStoreActions((actions: Actions<ApplicationStore>) => actions.user.updateUserData);
useEffect(() => { const { close } = useContext(DialogWrapperContext);
if (!open) return;
useEffect(() => {
getTwoFactorTokenData() getTwoFactorTokenData()
.then(setToken) .then(setToken)
.then(() => updateUserData({ useTotp: true }))
.catch((error) => clearAndAddHttpError(error)); .catch((error) => clearAndAddHttpError(error));
}, [open]); }, []);
useEffect(() => {
if (!open) return;
return () => {
setToken(null);
setValue('');
setSubmitting(false);
clearAndAddHttpError(undefined);
};
}, [open]);
const submit = () => { const submit = () => {
if (submitting) return; if (submitting) return;
@ -52,37 +41,26 @@ export default ({ open, onClose }: SetupTOTPModalProps) => {
clearAndAddHttpError(); clearAndAddHttpError();
enableAccountTwoFactor(value) enableAccountTwoFactor(value)
.then(setTokens) .then((tokens) => {
.catch(clearAndAddHttpError) updateUserData({ useTotp: true });
.then(() => setSubmitting(false)); onTokens(tokens);
})
.catch((error) => {
clearAndAddHttpError(error);
setSubmitting(false);
});
}; };
return ( return (
<> <>
<RecoveryTokensDialog tokens={tokens} open={open && tokens.length > 0} onClose={onClose} />
<Dialog
open={open && !tokens.length}
onClose={onClose}
title={'Enable Two-Step Verification'}
preventExternalClose={submitting}
description={
"Help protect your account from unauthorized access. You'll be prompted for a verification code each time you sign in."
}
>
<FlashMessageRender byKey={'account:two-step'} className={'mt-4'} /> <FlashMessageRender byKey={'account:two-step'} className={'mt-4'} />
<div <div
className={ className={'flex items-center justify-center w-56 h-56 p-2 bg-gray-800 rounded-lg shadow mx-auto mt-6'}
'flex items-center justify-center w-56 h-56 p-2 bg-gray-800 rounded-lg shadow mx-auto mt-6'
}
> >
{!token ? ( {!token ? (
<Spinner /> <Spinner />
) : ( ) : (
<QRCode <QRCode renderAs={'svg'} value={token.image_url_data} css={tw`w-full h-full shadow-none rounded`} />
renderAs={'svg'}
value={token.image_url_data}
css={tw`w-full h-full shadow-none rounded`}
/>
)} )}
</div> </div>
<CopyOnClick text={token?.secret}> <CopyOnClick text={token?.secret}>
@ -92,8 +70,8 @@ export default ({ open, onClose }: SetupTOTPModalProps) => {
</CopyOnClick> </CopyOnClick>
<div className={'mt-6'}> <div className={'mt-6'}>
<p> <p>
Scan the QR code above using the two-step authentication app of your choice. Then, enter the Scan the QR code above using the two-step authentication app of your choice. Then, enter the 6-digit
6-digit code generated into the field below. code generated into the field below.
</p> </p>
</div> </div>
<Input.Text <Input.Text
@ -108,12 +86,10 @@ export default ({ open, onClose }: SetupTOTPModalProps) => {
pattern={'\\d{6}'} pattern={'\\d{6}'}
/> />
<Dialog.Footer> <Dialog.Footer>
<Button.Text onClick={onClose}>Cancel</Button.Text> <Button.Text onClick={close}>Cancel</Button.Text>
<Tooltip <Tooltip
disabled={value.length === 6} disabled={value.length === 6}
content={ content={!token ? 'Waiting for QR code to load...' : 'You must enter the 6-digit code to continue.'}
!token ? 'Waiting for QR code to load...' : 'You must enter the 6-digit code to continue.'
}
delay={100} delay={100}
> >
<Button disabled={!token || value.length !== 6} onClick={submit}> <Button disabled={!token || value.length !== 6} onClick={submit}>
@ -121,7 +97,12 @@ export default ({ open, onClose }: SetupTOTPModalProps) => {
</Button> </Button>
</Tooltip> </Tooltip>
</Dialog.Footer> </Dialog.Footer>
</Dialog>
</> </>
); );
}; };
export default asDialog({
title: 'Enable Two-Step Verification',
description:
"Help protect your account from unauthorized access. You'll be prompted for a verification code each time you sign in.",
})(ConfigureTwoFactorForm);