Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
pterodactyl
GitHub Repository: pterodactyl/panel
Path: blob/develop/resources/scripts/components/dashboard/forms/SetupTOTPDialog.tsx
7454 views
1
import { useContext, useEffect, useState } from 'react';
2
import * as React from 'react';
3
import { Dialog, DialogWrapperContext } from '@/components/elements/dialog';
4
import getTwoFactorTokenData, { TwoFactorTokenData } from '@/api/account/getTwoFactorTokenData';
5
import { useFlashKey } from '@/plugins/useFlash';
6
import tw from 'twin.macro';
7
import QRCode from 'qrcode.react';
8
import { Button } from '@/components/elements/button/index';
9
import Spinner from '@/components/elements/Spinner';
10
import { Input } from '@/components/elements/inputs';
11
import CopyOnClick from '@/components/elements/CopyOnClick';
12
import Tooltip from '@/components/elements/tooltip/Tooltip';
13
import enableAccountTwoFactor from '@/api/account/enableAccountTwoFactor';
14
import FlashMessageRender from '@/components/FlashMessageRender';
15
import { Actions, useStoreActions } from 'easy-peasy';
16
import { ApplicationStore } from '@/state';
17
import asDialog from '@/hoc/asDialog';
18
19
interface Props {
20
onTokens: (tokens: string[]) => void;
21
}
22
23
const ConfigureTwoFactorForm = ({ onTokens }: Props) => {
24
const [submitting, setSubmitting] = useState(false);
25
const [value, setValue] = useState('');
26
const [password, setPassword] = useState('');
27
const [token, setToken] = useState<TwoFactorTokenData | null>(null);
28
const { clearAndAddHttpError } = useFlashKey('account:two-step');
29
const updateUserData = useStoreActions((actions: Actions<ApplicationStore>) => actions.user.updateUserData);
30
31
const { close, setProps } = useContext(DialogWrapperContext);
32
33
useEffect(() => {
34
getTwoFactorTokenData()
35
.then(setToken)
36
.catch(error => clearAndAddHttpError(error));
37
}, []);
38
39
useEffect(() => {
40
setProps(state => ({ ...state, preventExternalClose: submitting }));
41
}, [submitting]);
42
43
const submit = (e: React.FormEvent<HTMLFormElement>) => {
44
e.preventDefault();
45
e.stopPropagation();
46
47
if (submitting) return;
48
49
setSubmitting(true);
50
clearAndAddHttpError();
51
enableAccountTwoFactor(value, password)
52
.then(tokens => {
53
updateUserData({ useTotp: true });
54
onTokens(tokens);
55
})
56
.catch(error => {
57
clearAndAddHttpError(error);
58
setSubmitting(false);
59
});
60
};
61
62
return (
63
<form id={'enable-totp-form'} onSubmit={submit}>
64
<FlashMessageRender byKey={'account:two-step'} className={'mt-4'} />
65
<div className={'mx-auto mt-6 flex h-56 w-56 items-center justify-center bg-slate-50 p-2 shadow'}>
66
{!token ? (
67
<Spinner />
68
) : (
69
<QRCode renderAs={'svg'} value={token.image_url_data} css={tw`w-full h-full shadow-none`} />
70
)}
71
</div>
72
<CopyOnClick text={token?.secret}>
73
<p className={'mt-2 text-center font-mono text-sm text-slate-100'}>
74
{token?.secret.match(/.{1,4}/g)!.join(' ') || 'Loading...'}
75
</p>
76
</CopyOnClick>
77
<p id={'totp-code-description'} className={'mt-6'}>
78
Scan the QR code above using the two-step authentication app of your choice. Then, enter the 6-digit
79
code generated into the field below.
80
</p>
81
<Input.Text
82
aria-labelledby={'totp-code-description'}
83
variant={Input.Text.Variants.Loose}
84
value={value}
85
onChange={e => setValue(e.currentTarget.value)}
86
className={'mt-3'}
87
placeholder={'000000'}
88
type={'text'}
89
inputMode={'numeric'}
90
autoComplete={'one-time-code'}
91
pattern={'\\d{6}'}
92
/>
93
<label htmlFor={'totp-password'} className={'mt-3 block'}>
94
Account Password
95
</label>
96
<Input.Text
97
variant={Input.Text.Variants.Loose}
98
className={'mt-1'}
99
type={'password'}
100
value={password}
101
onChange={e => setPassword(e.currentTarget.value)}
102
/>
103
<Dialog.Footer>
104
<Button.Text onClick={close}>Cancel</Button.Text>
105
<Tooltip
106
disabled={password.length > 0 && value.length === 6}
107
content={
108
!token
109
? 'Waiting for QR code to load...'
110
: 'You must enter the 6-digit code and your password to continue.'
111
}
112
delay={100}
113
>
114
<Button
115
disabled={!token || value.length !== 6 || !password.length}
116
type={'submit'}
117
form={'enable-totp-form'}
118
>
119
Enable
120
</Button>
121
</Tooltip>
122
</Dialog.Footer>
123
</form>
124
);
125
};
126
127
export default asDialog({
128
title: 'Enable Two-Step Verification',
129
description:
130
"Help protect your account from unauthorized access. You'll be prompted for a verification code each time you sign in.",
131
})(ConfigureTwoFactorForm);
132
133