Support using recovery tokens during the login process to bypass 2fa; closes #479

This commit is contained in:
Dane Everitt 2020-07-02 23:01:02 -07:00
parent 795e045950
commit 7b75e7a648
No known key found for this signature in database
GPG key ID: EEA66103B3D71F53
7 changed files with 84 additions and 30 deletions

View file

@ -1,13 +1,14 @@
import http from '@/api/http';
import { LoginResponse } from '@/api/auth/login';
export default (token: string, code: string): Promise<LoginResponse> => {
export default (token: string, code: string, recoveryToken?: string): Promise<LoginResponse> => {
return new Promise((resolve, reject) => {
http.post('/auth/login/checkpoint', {
// eslint-disable-next-line @typescript-eslint/camelcase
/* eslint-disable @typescript-eslint/camelcase */
confirmation_token: token,
// eslint-disable-next-line @typescript-eslint/camelcase
authentication_code: code,
recovery_token: (recoveryToken && recoveryToken.length > 0) ? recoveryToken : undefined,
/* eslint-enable @typescript-eslint/camelcase */
})
.then(response => resolve({
complete: response.data.data.complete,

View file

@ -1,4 +1,4 @@
import React from 'react';
import React, { useState } from 'react';
import { Link, RouteComponentProps } from 'react-router-dom';
import loginCheckpoint from '@/api/auth/loginCheckpoint';
import { httpErrorToHuman } from '@/api/http';
@ -14,6 +14,7 @@ import Field from '@/components/elements/Field';
interface Values {
code: string;
recoveryCode: '',
}
type OwnProps = RouteComponentProps<{}, StaticContext, { token?: string }>
@ -24,7 +25,8 @@ type Props = OwnProps & {
}
const LoginCheckpointContainer = () => {
const { isSubmitting } = useFormikContext<Values>();
const { isSubmitting, setFieldValue } = useFormikContext<Values>();
const [ isMissingDevice, setIsMissingDevice ] = useState(false);
return (
<LoginFormContainer
@ -34,10 +36,14 @@ const LoginCheckpointContainer = () => {
<div className={'mt-6'}>
<Field
light={true}
name={'code'}
title={'Authentication Code'}
description={'Enter the two-factor token generated by your device.'}
type={'number'}
name={isMissingDevice ? 'recoveryCode' : 'code'}
title={isMissingDevice ? 'Recovery Code' : 'Authentication Code'}
description={
isMissingDevice
? 'Enter one of the recovery codes generated when you setup 2-Factor authentication on this account in order to continue.'
: 'Enter the two-factor token generated by your device.'
}
type={isMissingDevice ? 'text' : 'number'}
autoFocus={true}
/>
</div>
@ -54,6 +60,18 @@ const LoginCheckpointContainer = () => {
}
</button>
</div>
<div className={'mt-6 text-center'}>
<span
onClick={() => {
setFieldValue('code', '');
setFieldValue('recoveryCode', '');
setIsMissingDevice(s => !s);
}}
className={'cursor-pointer text-xs text-neutral-500 tracking-wide uppercase no-underline hover:text-neutral-700'}
>
{!isMissingDevice ? 'I\'ve Lost My Device' : 'I Have My Device'}
</span>
</div>
<div className={'mt-6 text-center'}>
<Link
to={'/auth/login'}
@ -67,10 +85,9 @@ const LoginCheckpointContainer = () => {
};
const EnhancedForm = withFormik<Props, Values>({
handleSubmit: ({ code }, { setSubmitting, props: { addError, clearFlashes, location } }) => {
handleSubmit: ({ code, recoveryCode }, { setSubmitting, props: { addError, clearFlashes, location } }) => {
clearFlashes();
console.log(location.state.token, code);
loginCheckpoint(location.state?.token || '', code)
loginCheckpoint(location.state?.token || '', code, recoveryCode)
.then(response => {
if (response.complete) {
// @ts-ignore
@ -89,11 +106,7 @@ const EnhancedForm = withFormik<Props, Values>({
mapPropsToValues: () => ({
code: '',
}),
validationSchema: object().shape({
code: string().required('An authentication code must be provided.')
.length(6, 'Authentication code must be 6 digits in length.'),
recoveryCode: '',
}),
})(LoginCheckpointContainer);