Display generated recovery tokens when enabling two factor

This commit is contained in:
Dane Everitt 2020-07-02 22:23:25 -07:00
parent c522935403
commit 795e045950
No known key found for this signature in database
GPG key ID: EEA66103B3D71F53
2 changed files with 82 additions and 49 deletions

View file

@ -1,9 +1,7 @@
import http from '@/api/http'; import http from '@/api/http';
export default (code: string): Promise<void> => { export default async (code: string): Promise<string[]> => {
return new Promise((resolve, reject) => { const { data } = await http.post('/api/client/account/two-factor', { code });
http.post('/api/client/account/two-factor', { code })
.then(() => resolve()) return data.attributes.tokens;
.catch(reject);
});
}; };

View file

@ -2,21 +2,22 @@ import React, { useEffect, useState } from 'react';
import Modal, { RequiredModalProps } from '@/components/elements/Modal'; import Modal, { RequiredModalProps } from '@/components/elements/Modal';
import { Form, Formik, FormikHelpers } from 'formik'; import { Form, Formik, FormikHelpers } from 'formik';
import { object, string } from 'yup'; import { object, string } from 'yup';
import Field from '@/components/elements/Field';
import getTwoFactorTokenUrl from '@/api/account/getTwoFactorTokenUrl'; import getTwoFactorTokenUrl from '@/api/account/getTwoFactorTokenUrl';
import enableAccountTwoFactor from '@/api/account/enableAccountTwoFactor'; import enableAccountTwoFactor from '@/api/account/enableAccountTwoFactor';
import FlashMessageRender from '@/components/FlashMessageRender';
import { Actions, useStoreActions } from 'easy-peasy'; import { Actions, useStoreActions } from 'easy-peasy';
import { ApplicationStore } from '@/state'; import { ApplicationStore } from '@/state';
import { httpErrorToHuman } from '@/api/http'; import { httpErrorToHuman } from '@/api/http';
import FlashMessageRender from '@/components/FlashMessageRender';
import Field from '@/components/elements/Field';
interface Values { interface Values {
code: string; code: string;
} }
export default ({ ...props }: RequiredModalProps) => { export default ({ onDismissed, ...props }: RequiredModalProps) => {
const [ token, setToken ] = useState(''); const [ token, setToken ] = useState('');
const [ loading, setLoading ] = useState(true); const [ loading, setLoading ] = useState(true);
const [ recoveryTokens, setRecoveryTokens ] = useState<string[]>([]);
const updateUserData = useStoreActions((actions: Actions<ApplicationStore>) => actions.user.updateUserData); const updateUserData = useStoreActions((actions: Actions<ApplicationStore>) => actions.user.updateUserData);
const { addError, clearFlashes } = useStoreActions((actions: Actions<ApplicationStore>) => actions.flashes); const { addError, clearFlashes } = useStoreActions((actions: Actions<ApplicationStore>) => actions.flashes);
@ -27,22 +28,30 @@ export default ({ ...props }: RequiredModalProps) => {
.then(setToken) .then(setToken)
.catch(error => { .catch(error => {
console.error(error); console.error(error);
addError({ message: httpErrorToHuman(error), key: 'account:two-factor' });
}); });
}, []); }, []);
const submit = ({ code }: Values, { setSubmitting }: FormikHelpers<Values>) => { const submit = ({ code }: Values, { setSubmitting }: FormikHelpers<Values>) => {
clearFlashes('account:two-factor'); clearFlashes('account:two-factor');
enableAccountTwoFactor(code) enableAccountTwoFactor(code)
.then(() => { .then(tokens => {
updateUserData({ useTotp: true }); setRecoveryTokens(tokens);
props.onDismissed();
}) })
.catch(error => { .catch(error => {
console.error(error); console.error(error);
addError({ message: httpErrorToHuman(error), key: 'account:two-factor' }); addError({ message: httpErrorToHuman(error), key: 'account:two-factor' });
setSubmitting(false); })
}); .then(() => setSubmitting(false));
};
const dismiss = () => {
if (recoveryTokens.length > 0) {
updateUserData({ useTotp: true });
}
onDismissed();
}; };
return ( return (
@ -58,9 +67,34 @@ export default ({ ...props }: RequiredModalProps) => {
{({ isSubmitting, isValid }) => ( {({ isSubmitting, isValid }) => (
<Modal <Modal
{...props} {...props}
onDismissed={dismiss}
dismissable={!isSubmitting} dismissable={!isSubmitting}
showSpinnerOverlay={loading || isSubmitting} showSpinnerOverlay={loading || isSubmitting}
closeOnEscape={!recoveryTokens}
closeOnBackground={!recoveryTokens}
> >
{recoveryTokens.length > 0 ?
<>
<h2 className={'mb-4'}>Two-factor authentication enabled</h2>
<p className={'text-neutral-300'}>
Two-factor authentication has been enabled on your account. Should you loose access to
this device you'll need to use on of the codes displayed below in order to access your
account.
</p>
<p className={'text-neutral-300 mt-4'}>
<strong>These codes will not be displayed again.</strong> Please take note of them now
by storing them in a secure repository such as a password manager.
</p>
<pre className={'mt-4 rounded font-mono bg-neutral-900 p-4'}>
{recoveryTokens.map(token => <code key={token} className={'block mb-1'}>{token}</code>)}
</pre>
<div className={'text-right'}>
<button className={'mt-6 btn btn-lg btn-primary'} onClick={dismiss}>
Close
</button>
</div>
</>
:
<Form className={'mb-0'}> <Form className={'mb-0'}>
<FlashMessageRender className={'mb-6'} byKey={'account:two-factor'}/> <FlashMessageRender className={'mb-6'} byKey={'account:two-factor'}/>
<div className={'flex flex-wrap'}> <div className={'flex flex-wrap'}>
@ -99,6 +133,7 @@ export default ({ ...props }: RequiredModalProps) => {
</div> </div>
</div> </div>
</Form> </Form>
}
</Modal> </Modal>
)} )}
</Formik> </Formik>