131 lines
5.2 KiB
TypeScript
131 lines
5.2 KiB
TypeScript
import { useContext, useEffect, useState } from 'react';
|
|
import * as React from 'react';
|
|
import { Dialog, DialogWrapperContext } from '@/components/elements/dialog';
|
|
import getTwoFactorTokenData, { TwoFactorTokenData } from '@/api/account/getTwoFactorTokenData';
|
|
import { useFlashKey } from '@/plugins/useFlash';
|
|
import tw from 'twin.macro';
|
|
import QRCode from 'qrcode.react';
|
|
import { Button } from '@/components/elements/button/index';
|
|
import Spinner from '@/components/elements/Spinner';
|
|
import { Input } from '@/components/elements/inputs';
|
|
import CopyOnClick from '@/components/elements/CopyOnClick';
|
|
import Tooltip from '@/components/elements/tooltip/Tooltip';
|
|
import enableAccountTwoFactor from '@/api/account/enableAccountTwoFactor';
|
|
import FlashMessageRender from '@/components/FlashMessageRender';
|
|
import { Actions, useStoreActions } from 'easy-peasy';
|
|
import { ApplicationStore } from '@/state';
|
|
import asDialog from '@/hoc/asDialog';
|
|
|
|
interface Props {
|
|
onTokens: (tokens: string[]) => void;
|
|
}
|
|
|
|
const ConfigureTwoFactorForm = ({ onTokens }: Props) => {
|
|
const [submitting, setSubmitting] = useState(false);
|
|
const [value, setValue] = useState('');
|
|
const [password, setPassword] = useState('');
|
|
const [token, setToken] = useState<TwoFactorTokenData | null>(null);
|
|
const { clearAndAddHttpError } = useFlashKey('account:two-step');
|
|
const updateUserData = useStoreActions((actions: Actions<ApplicationStore>) => actions.user.updateUserData);
|
|
|
|
const { close, setProps } = useContext(DialogWrapperContext);
|
|
|
|
useEffect(() => {
|
|
getTwoFactorTokenData()
|
|
.then(setToken)
|
|
.catch(error => clearAndAddHttpError(error));
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
setProps(state => ({ ...state, preventExternalClose: submitting }));
|
|
}, [submitting]);
|
|
|
|
const submit = (e: React.FormEvent<HTMLFormElement>) => {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
|
|
if (submitting) return;
|
|
|
|
setSubmitting(true);
|
|
clearAndAddHttpError();
|
|
enableAccountTwoFactor(value, password)
|
|
.then(tokens => {
|
|
updateUserData({ useTotp: true });
|
|
onTokens(tokens);
|
|
})
|
|
.catch(error => {
|
|
clearAndAddHttpError(error);
|
|
setSubmitting(false);
|
|
});
|
|
};
|
|
|
|
return (
|
|
<form id={'enable-totp-form'} onSubmit={submit}>
|
|
<FlashMessageRender byKey={'account:two-step'} className={'mt-4'} />
|
|
<div className={'mx-auto mt-6 flex h-56 w-56 items-center justify-center bg-slate-50 p-2 shadow'}>
|
|
{!token ? (
|
|
<Spinner />
|
|
) : (
|
|
<QRCode renderAs={'svg'} value={token.image_url_data} css={tw`w-full h-full shadow-none`} />
|
|
)}
|
|
</div>
|
|
<CopyOnClick text={token?.secret}>
|
|
<p className={'mt-2 text-center font-mono text-sm text-slate-100'}>
|
|
{token?.secret.match(/.{1,4}/g)!.join(' ') || 'Loading...'}
|
|
</p>
|
|
</CopyOnClick>
|
|
<p id={'totp-code-description'} className={'mt-6'}>
|
|
Scan the QR code above using the two-step authentication app of your choice. Then, enter the 6-digit
|
|
code generated into the field below.
|
|
</p>
|
|
<Input.Text
|
|
aria-labelledby={'totp-code-description'}
|
|
variant={Input.Text.Variants.Loose}
|
|
value={value}
|
|
onChange={e => setValue(e.currentTarget.value)}
|
|
className={'mt-3'}
|
|
placeholder={'000000'}
|
|
type={'text'}
|
|
inputMode={'numeric'}
|
|
autoComplete={'one-time-code'}
|
|
pattern={'\\d{6}'}
|
|
/>
|
|
<label htmlFor={'totp-password'} className={'mt-3 block'}>
|
|
Account Password
|
|
</label>
|
|
<Input.Text
|
|
variant={Input.Text.Variants.Loose}
|
|
className={'mt-1'}
|
|
type={'password'}
|
|
value={password}
|
|
onChange={e => setPassword(e.currentTarget.value)}
|
|
/>
|
|
<Dialog.Footer>
|
|
<Button.Text onClick={close}>Cancel</Button.Text>
|
|
<Tooltip
|
|
disabled={password.length > 0 && value.length === 6}
|
|
content={
|
|
!token
|
|
? 'Waiting for QR code to load...'
|
|
: 'You must enter the 6-digit code and your password to continue.'
|
|
}
|
|
delay={100}
|
|
>
|
|
<Button
|
|
disabled={!token || value.length !== 6 || !password.length}
|
|
type={'submit'}
|
|
form={'enable-totp-form'}
|
|
>
|
|
Enable
|
|
</Button>
|
|
</Tooltip>
|
|
</Dialog.Footer>
|
|
</form>
|
|
);
|
|
};
|
|
|
|
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);
|