Kinda working checkpoint magic

This commit is contained in:
Dane Everitt 2019-06-16 18:07:57 -07:00
parent 4eeec58c59
commit 2a626a3e1f
No known key found for this signature in database
GPG key ID: EEA66103B3D71F53
4 changed files with 119 additions and 3 deletions

View file

@ -50,6 +50,7 @@ input[type=number] {
*/ */
.input:not(.open-label) { .input:not(.open-label) {
@apply .appearance-none .p-3 .rounded .border .border-neutral-200 .text-neutral-800 .w-full; @apply .appearance-none .p-3 .rounded .border .border-neutral-200 .text-neutral-800 .w-full;
min-width: 0;
transition: border 150ms linear; transition: border 150ms linear;
&:focus { &:focus {

View file

@ -0,0 +1,105 @@
import * as React from 'react';
import { RouteComponentProps } from 'react-router';
import { connect } from 'react-redux';
import { pushFlashMessage, clearAllFlashMessages } from '@/redux/actions/flash';
import NetworkErrorMessage from '@/components/NetworkErrorMessage';
type State = Readonly<{
isLoading: boolean;
errorMessage?: string;
code: string;
}>;
class LoginCheckpointContainer extends React.PureComponent<RouteComponentProps, State> {
state: State = {
code: '',
isLoading: false,
};
moveToNextInput (e: React.KeyboardEvent<HTMLInputElement>, isBackspace: boolean = false) {
const form = e.currentTarget.form;
if (form) {
const index = Array.prototype.indexOf.call(form, e.currentTarget);
const element = form.elements[index + (isBackspace ? -1 : 1)];
// @ts-ignore
element && element.focus();
}
}
handleNumberInput = (e: React.KeyboardEvent<HTMLInputElement>) => {
const number = Number(e.key);
if (isNaN(number)) {
return;
}
this.setState(s => ({ code: s.code + number.toString() }));
this.moveToNextInput(e);
};
handleBackspace = (e: React.KeyboardEvent<HTMLInputElement>) => {
const isBackspace = e.key === 'Delete' || e.key === 'Backspace';
if (!isBackspace || e.currentTarget.value.length > 0) {
e.currentTarget.value = '';
return;
}
this.setState(s => ({ code: s.code.substring(0, s.code.length - 2) }));
e.currentTarget.value = '';
this.moveToNextInput(e, true);
};
render () {
return (
<React.Fragment>
<h2 className={'text-center text-neutral-100 font-medium py-4'}>
Device Checkpoint
</h2>
<NetworkErrorMessage message={this.state.errorMessage}/>
<form className={'login-box'} onSubmit={() => null}>
<p className={'text-sm text-neutral-700'}>
This account is protected with two-factor authentication. Please provide an authentication
code from your device in order to continue.
</p>
<div className={'flex mt-6'}>
{
[1, 2, 3, 4, 5, 6].map((_, index) => (
<input
autoFocus={index === 0}
key={`input_${index}`}
type={'number'}
onKeyPress={this.handleNumberInput}
onKeyDown={this.handleBackspace}
maxLength={1}
className={`input block flex-1 text-center text-lg ${index === 5 ? undefined : 'mr-6'}`}
/>
))
}
</div>
<div className={'mt-6'}>
<button
type={'submit'}
className={'btn btn-primary btn-jumbo'}
disabled={this.state.isLoading || this.state.code.length !== 6}
>
{this.state.isLoading ?
<span className={'spinner white'}>&nbsp;</span>
:
'Continue'
}
</button>
</div>
</form>
</React.Fragment>
);
}
}
const mapDispatchToProps = {
pushFlashMessage,
clearAllFlashMessages,
};
export default connect(null, mapDispatchToProps)(LoginCheckpointContainer);

View file

@ -1,6 +1,6 @@
import * as React from 'react'; import * as React from 'react';
import OpenInputField from '@/components/forms/OpenInputField'; import OpenInputField from '@/components/forms/OpenInputField';
import { Link } from 'react-router-dom'; import { Link, RouteComponentProps } from 'react-router-dom';
import login from '@/api/auth/login'; import login from '@/api/auth/login';
import { httpErrorToHuman } from '@/api/http'; import { httpErrorToHuman } from '@/api/http';
import NetworkErrorMessage from '@/components/NetworkErrorMessage'; import NetworkErrorMessage from '@/components/NetworkErrorMessage';
@ -12,7 +12,7 @@ type State = Readonly<{
password?: string; password?: string;
}>; }>;
export default class LoginContainer extends React.PureComponent<{}, State> { export default class LoginContainer extends React.PureComponent<RouteComponentProps, State> {
username = React.createRef<HTMLInputElement>(); username = React.createRef<HTMLInputElement>();
state: State = { state: State = {
@ -27,7 +27,15 @@ export default class LoginContainer extends React.PureComponent<{}, State> {
this.setState({ isLoading: true }, () => { this.setState({ isLoading: true }, () => {
login(username!, password!) login(username!, password!)
.then(response => { .then(response => {
if (response.complete) {
// @ts-ignore
window.location = response.intended || '/';
return;
}
this.props.history.replace('/login/checkpoint', {
token: response.token,
});
}) })
.catch(error => this.setState({ .catch(error => this.setState({
isLoading: false, isLoading: false,

View file

@ -5,6 +5,7 @@ import { CSSTransition, TransitionGroup } from 'react-transition-group';
import ForgotPasswordContainer from '@/components/auth/ForgotPasswordContainer'; import ForgotPasswordContainer from '@/components/auth/ForgotPasswordContainer';
import FlashMessageRender from '@/components/FlashMessageRender'; import FlashMessageRender from '@/components/FlashMessageRender';
import ResetPasswordContainer from '@/components/auth/ResetPasswordContainer'; import ResetPasswordContainer from '@/components/auth/ResetPasswordContainer';
import LoginCheckpointContainer from '@/components/auth/LoginCheckpointContainer';
export default class AuthenticationRouter extends React.PureComponent { export default class AuthenticationRouter extends React.PureComponent {
render () { render () {
@ -17,7 +18,8 @@ export default class AuthenticationRouter extends React.PureComponent {
<section> <section>
<FlashMessageRender/> <FlashMessageRender/>
<Switch location={location}> <Switch location={location}>
<Route path={'/login'} component={LoginContainer}/> <Route path={'/login'} component={LoginContainer} exact/>
<Route path={'/login/checkpoint'} component={LoginCheckpointContainer}/>
<Route path={'/password'} component={ForgotPasswordContainer} exact/> <Route path={'/password'} component={ForgotPasswordContainer} exact/>
<Route path={'/password/reset/:token'} component={ResetPasswordContainer}/> <Route path={'/password/reset/:token'} component={ResetPasswordContainer}/>
<Route path={'/checkpoint'}/> <Route path={'/checkpoint'}/>