From 2a626a3e1f94afcd997a2f8276c49ff436d573f6 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sun, 16 Jun 2019 18:07:57 -0700 Subject: [PATCH] Kinda working checkpoint magic --- resources/assets/styles/components/forms.css | 1 + .../auth/LoginCheckpointContainer.tsx | 105 ++++++++++++++++++ .../components/auth/LoginContainer.tsx | 12 +- .../scripts/routers/AuthenticationRouter.tsx | 4 +- 4 files changed, 119 insertions(+), 3 deletions(-) create mode 100644 resources/scripts/components/auth/LoginCheckpointContainer.tsx diff --git a/resources/assets/styles/components/forms.css b/resources/assets/styles/components/forms.css index ffa96c39..b083a8f7 100644 --- a/resources/assets/styles/components/forms.css +++ b/resources/assets/styles/components/forms.css @@ -50,6 +50,7 @@ input[type=number] { */ .input:not(.open-label) { @apply .appearance-none .p-3 .rounded .border .border-neutral-200 .text-neutral-800 .w-full; + min-width: 0; transition: border 150ms linear; &:focus { diff --git a/resources/scripts/components/auth/LoginCheckpointContainer.tsx b/resources/scripts/components/auth/LoginCheckpointContainer.tsx new file mode 100644 index 00000000..b2fe6ad5 --- /dev/null +++ b/resources/scripts/components/auth/LoginCheckpointContainer.tsx @@ -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 { + state: State = { + code: '', + isLoading: false, + }; + + moveToNextInput (e: React.KeyboardEvent, 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) => { + const number = Number(e.key); + if (isNaN(number)) { + return; + } + + this.setState(s => ({ code: s.code + number.toString() })); + this.moveToNextInput(e); + }; + + handleBackspace = (e: React.KeyboardEvent) => { + 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 ( + +

+ Device Checkpoint +

+ +
null}> +

+ This account is protected with two-factor authentication. Please provide an authentication + code from your device in order to continue. +

+
+ { + [1, 2, 3, 4, 5, 6].map((_, index) => ( + + )) + } +
+
+ +
+
+
+ ); + } +} + +const mapDispatchToProps = { + pushFlashMessage, + clearAllFlashMessages, +}; + +export default connect(null, mapDispatchToProps)(LoginCheckpointContainer); diff --git a/resources/scripts/components/auth/LoginContainer.tsx b/resources/scripts/components/auth/LoginContainer.tsx index 787f60f5..6a388a45 100644 --- a/resources/scripts/components/auth/LoginContainer.tsx +++ b/resources/scripts/components/auth/LoginContainer.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; 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 { httpErrorToHuman } from '@/api/http'; import NetworkErrorMessage from '@/components/NetworkErrorMessage'; @@ -12,7 +12,7 @@ type State = Readonly<{ password?: string; }>; -export default class LoginContainer extends React.PureComponent<{}, State> { +export default class LoginContainer extends React.PureComponent { username = React.createRef(); state: State = { @@ -27,7 +27,15 @@ export default class LoginContainer extends React.PureComponent<{}, State> { this.setState({ isLoading: true }, () => { login(username!, password!) .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({ isLoading: false, diff --git a/resources/scripts/routers/AuthenticationRouter.tsx b/resources/scripts/routers/AuthenticationRouter.tsx index 28572572..673278c7 100644 --- a/resources/scripts/routers/AuthenticationRouter.tsx +++ b/resources/scripts/routers/AuthenticationRouter.tsx @@ -5,6 +5,7 @@ import { CSSTransition, TransitionGroup } from 'react-transition-group'; import ForgotPasswordContainer from '@/components/auth/ForgotPasswordContainer'; import FlashMessageRender from '@/components/FlashMessageRender'; import ResetPasswordContainer from '@/components/auth/ResetPasswordContainer'; +import LoginCheckpointContainer from '@/components/auth/LoginCheckpointContainer'; export default class AuthenticationRouter extends React.PureComponent { render () { @@ -17,7 +18,8 @@ export default class AuthenticationRouter extends React.PureComponent {
- + +