Add base logic to configure two factor on account
This commit is contained in:
parent
edf27a5542
commit
eb39826f46
15 changed files with 389 additions and 54 deletions
|
@ -2,16 +2,38 @@ import * as React from 'react';
|
|||
import ContentBox from '@/components/elements/ContentBox';
|
||||
import UpdatePasswordForm from '@/components/dashboard/forms/UpdatePasswordForm';
|
||||
import UpdateEmailAddressForm from '@/components/dashboard/forms/UpdateEmailAddressForm';
|
||||
import ConfigureTwoFactorForm from '@/components/dashboard/forms/ConfigureTwoFactorForm';
|
||||
import styled from 'styled-components';
|
||||
import { breakpoint } from 'styled-components-breakpoint';
|
||||
|
||||
const Container = styled.div`
|
||||
${tw`flex flex-wrap my-10`};
|
||||
|
||||
& > div {
|
||||
${tw`w-full`};
|
||||
|
||||
${breakpoint('md')`
|
||||
width: calc(50% - 1rem);
|
||||
`}
|
||||
|
||||
${breakpoint('xl')`
|
||||
${tw`w-auto flex-1`};
|
||||
`}
|
||||
}
|
||||
`;
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<div className={'flex my-10'}>
|
||||
<ContentBox className={'flex-1 mr-4'} title={'Update Password'} showFlashes={'account:password'}>
|
||||
<Container>
|
||||
<ContentBox title={'Update Password'} showFlashes={'account:password'}>
|
||||
<UpdatePasswordForm/>
|
||||
</ContentBox>
|
||||
<ContentBox className={'flex-1 ml-4'} title={'Update Email Address'} showFlashes={'account:email'}>
|
||||
<ContentBox className={'mt-8 md:mt-0 md:ml-8'} title={'Update Email Address'} showFlashes={'account:email'}>
|
||||
<UpdateEmailAddressForm/>
|
||||
</ContentBox>
|
||||
</div>
|
||||
<ContentBox className={'xl:ml-8 mt-8 xl:mt-0'} title={'Configure Two Factor'}>
|
||||
<ConfigureTwoFactorForm/>
|
||||
</ContentBox>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
import React, { useState } from 'react';
|
||||
import { useStoreState } from 'easy-peasy';
|
||||
import { ApplicationStore } from '@/state';
|
||||
import SetupTwoFactorModal from '@/components/dashboard/forms/SetupTwoFactorModal';
|
||||
|
||||
export default () => {
|
||||
const user = useStoreState((state: ApplicationStore) => state.user.data!);
|
||||
const [visible, setVisible] = useState(false);
|
||||
|
||||
return user.useTotp ?
|
||||
<div>
|
||||
<p className={'text-sm'}>
|
||||
Two-factor authentication is currently enabled on your account.
|
||||
</p>
|
||||
<div className={'mt-6'}>
|
||||
<button className={'btn btn-red btn-secondary btn-sm'}>
|
||||
Disable
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
:
|
||||
<div>
|
||||
<SetupTwoFactorModal visible={visible} onDismissed={() => setVisible(false)}/>
|
||||
<p className={'text-sm'}>
|
||||
You do not currently have two-factor authentication enabled on your account. Click
|
||||
the button below to begin configuring it.
|
||||
</p>
|
||||
<div className={'mt-6'}>
|
||||
<button
|
||||
onClick={() => setVisible(true)}
|
||||
className={'btn btn-green btn-secondary btn-sm'}
|
||||
>
|
||||
Begin Setup
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
;
|
||||
};
|
|
@ -0,0 +1,113 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import Modal, { RequiredModalProps } from '@/components/elements/Modal';
|
||||
import { Form, Formik, FormikActions } from 'formik';
|
||||
import { object, string } from 'yup';
|
||||
import Field from '@/components/elements/Field';
|
||||
import getTwoFactorTokenUrl from '@/api/account/getTwoFactorTokenUrl';
|
||||
import enableAccountTwoFactor from '@/api/account/enableAccountTwoFactor';
|
||||
import FlashMessageRender from '@/components/FlashMessageRender';
|
||||
import { Actions, useStoreActions } from 'easy-peasy';
|
||||
import { ApplicationStore } from '@/state';
|
||||
import { httpErrorToHuman } from '@/api/http';
|
||||
|
||||
interface Values {
|
||||
code: string;
|
||||
}
|
||||
|
||||
export default ({ visible, onDismissed }: RequiredModalProps) => {
|
||||
const [ token, setToken ] = useState('');
|
||||
const [ loading, setLoading ] = useState(true);
|
||||
const { addError, clearFlashes } = useStoreActions((actions: Actions<ApplicationStore>) => actions.flashes);
|
||||
|
||||
useEffect(() => {
|
||||
if (!visible) {
|
||||
clearFlashes('account:two-factor');
|
||||
getTwoFactorTokenUrl()
|
||||
.then(setToken)
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
});
|
||||
}
|
||||
}, [ visible ]);
|
||||
|
||||
const submit = ({ code }: Values, { resetForm, setSubmitting }: FormikActions<Values>) => {
|
||||
clearFlashes('account:two-factor');
|
||||
enableAccountTwoFactor(code)
|
||||
.then(() => {
|
||||
resetForm();
|
||||
setToken('');
|
||||
setLoading(true);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
|
||||
addError({ message: httpErrorToHuman(error), key: 'account:two-factor' });
|
||||
setSubmitting(false);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Formik
|
||||
onSubmit={submit}
|
||||
initialValues={{ code: '' }}
|
||||
validationSchema={object().shape({
|
||||
code: string()
|
||||
.required('You must provide an authentication code to continue.')
|
||||
.matches(/^(\d){6}$/, 'Authenticator code must be 6 digits.'),
|
||||
})}
|
||||
>
|
||||
{({ isSubmitting, isValid, resetForm }) => (
|
||||
<Modal
|
||||
visible={visible}
|
||||
onDismissed={() => {
|
||||
resetForm();
|
||||
setToken('');
|
||||
setLoading(true);
|
||||
onDismissed();
|
||||
}}
|
||||
dismissable={!isSubmitting}
|
||||
showSpinnerOverlay={loading || isSubmitting}
|
||||
>
|
||||
<Form className={'mb-0'}>
|
||||
<FlashMessageRender className={'mb-6'} byKey={'account:two-factor'}/>
|
||||
<div className={'flex'}>
|
||||
<div className={'flex-1'}>
|
||||
<div className={'w-64 h-64 bg-neutral-600 p-2 rounded'}>
|
||||
{!token || !token.length ?
|
||||
<img
|
||||
src={'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII='}
|
||||
className={'w-64 h-64 rounded'}
|
||||
/>
|
||||
:
|
||||
<img
|
||||
src={`https://api.qrserver.com/v1/create-qr-code/?size=500x500&data=${token}`}
|
||||
onLoad={() => setLoading(false)}
|
||||
className={'w-full h-full shadow-none rounded-0'}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div className={'flex-1 flex flex-col'}>
|
||||
<div className={'flex-1'}>
|
||||
<Field
|
||||
id={'code'}
|
||||
name={'code'}
|
||||
type={'text'}
|
||||
title={'Code From Authenticator'}
|
||||
description={'Enter the code from your authenticator device after scanning the QR image.'}
|
||||
autoFocus={!loading}
|
||||
/>
|
||||
</div>
|
||||
<div className={'text-right'}>
|
||||
<button className={'btn btn-primary btn-sm'} disabled={!isValid}>
|
||||
Setup
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
</Modal>
|
||||
)}
|
||||
</Formik>
|
||||
);
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue