Support using recovery tokens during the login process to bypass 2fa; closes #479
This commit is contained in:
parent
795e045950
commit
7b75e7a648
7 changed files with 84 additions and 30 deletions
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue