From a4359064cabfda0246d5e2093b78e197ad37ecaa Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sun, 13 Feb 2022 17:44:59 -0500 Subject: [PATCH] Cleaner login flows; hide options that aren't relevant to the user --- .../auth/LoginCheckpointContainer.tsx | 23 +++++++++----- .../components/auth/LoginContainer.tsx | 13 ++++---- .../auth/LoginKeyCheckpointContainer.tsx | 31 +++++++++---------- .../dashboard/forms/SetupTwoFactorModal.tsx | 2 +- 4 files changed, 37 insertions(+), 32 deletions(-) diff --git a/resources/scripts/components/auth/LoginCheckpointContainer.tsx b/resources/scripts/components/auth/LoginCheckpointContainer.tsx index f8fa306f0..5afa79961 100644 --- a/resources/scripts/components/auth/LoginCheckpointContainer.tsx +++ b/resources/scripts/components/auth/LoginCheckpointContainer.tsx @@ -14,9 +14,16 @@ interface Values { recoveryCode: '', } -type OwnProps = RouteComponentProps, StaticContext, { token?: string, recovery?: boolean }> +export interface LoginCheckpointState { + token: string; + methods: string[]; + publicKey?: PublicKeyCredentialRequestOptions; + recovery?: boolean; +} -export default ({ history, location }: OwnProps) => { +type Props = RouteComponentProps, StaticContext, LoginCheckpointState>; + +export default ({ history, location }: Props) => { const { clearFlashes, clearAndAddHttpError } = useFlash(); const onSubmit = ({ code, recoveryCode }: Values, { setSubmitting }: FormikHelpers) => { @@ -47,9 +54,9 @@ export default ({ history, location }: OwnProps) => { return ( {({ isSubmitting, setFieldValue }) => ( - -
-
+ +
+
{ />
+ {(!isMissingDevice || (isMissingDevice && (location.state?.methods || []).includes('totp'))) && + }
)} diff --git a/resources/scripts/components/auth/LoginContainer.tsx b/resources/scripts/components/auth/LoginContainer.tsx index 6aca13c04..6cfed92a4 100644 --- a/resources/scripts/components/auth/LoginContainer.tsx +++ b/resources/scripts/components/auth/LoginContainer.tsx @@ -50,17 +50,16 @@ const LoginContainer = ({ history }: RouteComponentProps) => { return; } - if (response.methods?.includes('webauthn')) { + response.methods = response.methods || []; + + if (response.methods.includes('webauthn')) { history.replace('/auth/login/key', { token: response.confirmationToken, + methods: response.methods, publicKey: response.publicKey, - hasTotp: response.methods?.includes('totp'), }); - return; - } - - if (response.methods?.includes('totp')) { - history.replace('/auth/login/checkpoint', { token: response.confirmationToken }); + } else if (response.methods.includes('totp')) { + history.replace('/auth/login/checkpoint', { token: response.confirmationToken, methods: response.methods }); } }) .catch(async (error) => { diff --git a/resources/scripts/components/auth/LoginKeyCheckpointContainer.tsx b/resources/scripts/components/auth/LoginKeyCheckpointContainer.tsx index 6a42ca065..7eb0fe9ea 100644 --- a/resources/scripts/components/auth/LoginKeyCheckpointContainer.tsx +++ b/resources/scripts/components/auth/LoginKeyCheckpointContainer.tsx @@ -2,19 +2,14 @@ import React, { useEffect, useRef, useState } from 'react'; import tw from 'twin.macro'; import { DivContainer as LoginFormContainer } from '@/components/auth/LoginFormContainer'; import useFlash from '@/plugins/useFlash'; -import { useLocation } from 'react-router'; -import { Link, useHistory } from 'react-router-dom'; +import { StaticContext } from 'react-router'; +import { Link, RouteComponentProps } from 'react-router-dom'; import Button from '@/components/elements/Button'; import { authenticateSecurityKey } from '@/api/account/security-keys'; import { base64Decode, bufferDecode, bufferEncode, decodeSecurityKeyCredentials } from '@/helpers'; import { FingerPrintIcon } from '@heroicons/react/outline'; import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; - -interface LocationParams { - token: string; - publicKey: any; - hasTotp: boolean; -} +import { LoginCheckpointState } from '@/components/auth/LoginCheckpointContainer'; interface Credential extends PublicKeyCredential { response: AuthenticatorAssertionResponse; @@ -34,9 +29,9 @@ const challenge = async (publicKey: PublicKeyCredentialRequestOptions, signal?: return credential; }; -export default () => { - const history = useHistory(); - const location = useLocation(); +type Props = RouteComponentProps, StaticContext, LoginCheckpointState | undefined>; + +export default ({ history, location }: Props) => { const controller = useRef(new AbortController()); const { clearFlashes, clearAndAddHttpError } = useFlash(); const [ redirecting, setRedirecting ] = useState(false); @@ -44,12 +39,12 @@ export default () => { const triggerChallengePrompt = () => { clearFlashes(); - challenge(location.state.publicKey, controller.current.signal) + challenge(location.state!.publicKey!, controller.current.signal) .then((credential) => { setRedirecting(true); return authenticateSecurityKey({ - confirmation_token: location.state.token, + confirmation_token: location.state!.token, data: JSON.stringify({ id: credential.id, type: credential.type, @@ -88,7 +83,7 @@ export default () => { }); useEffect(() => { - if (!location.state?.token) { + if (!location.state?.token || !location.state?.publicKey) { history.replace('/auth/login'); } else { triggerChallengePrompt(); @@ -103,7 +98,7 @@ export default () => { >
-
+

Insert your security key and touch it.

If your security key does not respond,  @@ -116,16 +111,18 @@ export default () => { .

+ {(location.state?.methods || []).includes('totp') && + } {'I\'ve Lost My Device'} diff --git a/resources/scripts/components/dashboard/forms/SetupTwoFactorModal.tsx b/resources/scripts/components/dashboard/forms/SetupTwoFactorModal.tsx index 87f10d438..16424c899 100644 --- a/resources/scripts/components/dashboard/forms/SetupTwoFactorModal.tsx +++ b/resources/scripts/components/dashboard/forms/SetupTwoFactorModal.tsx @@ -139,7 +139,7 @@ const SetupTwoFactorModal = () => {
}
-
+