Skip to content

Commit

Permalink
Add success animation to security key and TOTP flows (#227)
Browse files Browse the repository at this point in the history
* Clear notices when moving through webauthn flow

* Add success animation to end of register key flow

* Reuse success animation for totp
  • Loading branch information
adamwoodnz authored Aug 23, 2023
1 parent 72575f2 commit 9dd67ca
Show file tree
Hide file tree
Showing 6 changed files with 146 additions and 68 deletions.
36 changes: 36 additions & 0 deletions settings/src/components/success.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/**
* WordPress dependencies
*/
import { useState } from '@wordpress/element';
import { Icon, check } from '@wordpress/icons';

/**
* Render the "Success" component.
*
* Shows a message and animation, then calls the `afterTimeout` callback
*
* @param props
* @param props.afterTimeout
* @param props.message
*/
export default function Success( { message, afterTimeout } ) {
const [ hasTimer, setHasTimer ] = useState( false );

if ( ! hasTimer ) {
// Time matches the length of the CSS animation property on .wporg-2fa__success
setTimeout( afterTimeout, 4000 );
setHasTimer( true );
}

return (
<>
<p className="wporg-2fa__screen-intro">{ message }</p>

<p className="wporg-2fa__webauthn-register-key-status" aria-hidden>
<div className="wporg-2fa__success">
<Icon icon={ check } />
</div>
</p>
</>
);
}
29 changes: 29 additions & 0 deletions settings/src/components/success.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
@keyframes success {
0% {
transform: scale(0);
}
10% {
transform: scale(1.5);
}
20% {
transform: scale(1);
}
80% {
transform: scale(1);
}
100% {
transform: scale(0);
}
}

.wporg-2fa__success {
transform: scale(0);
background: #33F078;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
animation: success 4s ease-in-out;
}
83 changes: 53 additions & 30 deletions settings/src/components/totp.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,28 +13,49 @@ import ScreenLink from './screen-link';
import AutoTabbingInput from './auto-tabbing-input';
import { refreshRecord } from '../utilities/common';
import { GlobalContext } from '../script';
import Success from './success';

export default function TOTP() {
const {
user: { totpEnabled },
navigateToScreen,
} = useContext( GlobalContext );
const [ success, setSuccess ] = useState( false );

const afterTimeout = useCallback( () => {
setSuccess( false );
navigateToScreen( 'backup-codes' );
}, [ navigateToScreen ] );

if ( success ) {
return (
<Success
message="Success! Your two-factor authentication app is set up."
afterTimeout={ afterTimeout }
/>
);
}

if ( totpEnabled ) {
return <Manage />;
}

return <Setup />;
return <Setup setSuccess={ setSuccess } />;
}

/**
* Setup the TOTP provider.
*
* @param props
* @param props.setSuccess
*/
function Setup() {
function Setup( { setSuccess } ) {
const {
navigateToScreen,
setGlobalNotice,
user: { userRecord },
} = useContext( GlobalContext );
const {
record: { id: userId },
} = userRecord;
const [ secretKey, setSecretKey ] = useState( '' );
const [ qrCodeUrl, setQrCodeUrl ] = useState( '' );
const [ error, setError ] = useState( '' );
Expand All @@ -46,41 +67,43 @@ function Setup() {
// useEffect callbacks can't be async directly, because that'd return the promise as a "cleanup" function.
const fetchSetupData = async () => {
const response = await apiFetch( {
path: '/wporg-two-factor/1.0/totp-setup?user_id=' + userRecord.record.id,
path: '/wporg-two-factor/1.0/totp-setup?user_id=' + userId,
} );

setSecretKey( response.secret_key );
setQrCodeUrl( response.qr_code_url );
};

fetchSetupData();
}, [] );
}, [ userId ] );

// Enable TOTP when button clicked.
const handleEnable = useCallback( async ( event ) => {
event.preventDefault();

const code = inputs.join( '' );

try {
await apiFetch( {
path: '/two-factor/1.0/totp/',
method: 'POST',
data: {
user_id: userRecord.record.id,
key: secretKey,
code,
enable_provider: true,
},
} );

await refreshRecord( userRecord );
navigateToScreen( 'backup-codes' );
setGlobalNotice( 'Successfully enabled One Time Passwords.' ); // Must be After `clickScreenEvent` clears it.
} catch ( handleEnableError ) {
setError( handleEnableError.message );
}
} );
const handleEnable = useCallback(
async ( event ) => {
event.preventDefault();

const code = inputs.join( '' );

try {
await apiFetch( {
path: '/two-factor/1.0/totp/',
method: 'POST',
data: {
user_id: userId,
key: secretKey,
code,
enable_provider: true,
},
} );

await refreshRecord( userRecord );
setSuccess( true );
} catch ( handleEnableError ) {
setError( handleEnableError.message );
}
},
[ inputs, secretKey, userId, userRecord, setSuccess ]
);

return (
<Flex
Expand Down
42 changes: 8 additions & 34 deletions settings/src/components/webauthn/register-key.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*/
import { Button, Notice, Spinner, TextControl } from '@wordpress/components';
import { useCallback, useContext, useState } from '@wordpress/element';
import { Icon, check, cancelCircleFilled } from '@wordpress/icons';
import { Icon, cancelCircleFilled } from '@wordpress/icons';

/**
* Internal dependencies
Expand All @@ -14,6 +14,7 @@ import {
preparePublicKeyCreationOptions,
preparePublicKeyCredential,
} from '../../utilities/webauthn';
import Success from '../success';

/**
* Render the form to register new security keys.
Expand Down Expand Up @@ -77,7 +78,12 @@ export default function RegisterKey( { onSuccess, onCancel } ) {
}

if ( 'success' === step ) {
return <Success newKeyName={ keyName } afterTimeout={ onSuccess } />;
return (
<Success
message={ `Success! Your ${ keyName } is successfully registered.` }
afterTimeout={ onSuccess }
/>
);
}

return registerCeremonyActive ? (
Expand Down Expand Up @@ -138,35 +144,3 @@ function WaitingForSecurityKey() {
</>
);
}

/**
* Render the "Success" component.
*
* The user sees this once their security key has successfully been registered.
*
* @param props
* @param props.newKeyName
* @param props.afterTimeout
*/
function Success( { newKeyName, afterTimeout } ) {
const [ hasTimer, setHasTimer ] = useState( false );

if ( ! hasTimer ) {
// TODO need to sync this timing with the animation below
setTimeout( afterTimeout, 2000 );
setHasTimer( true );
}

return (
<>
<p className="wporg-2fa__screen-intro">
Success! Your { newKeyName } is successfully registered.
</p>

{ /* TODO replace w/ custom animation */ }
<p className="wporg-2fa__webauthn-register-key-status">
<Icon icon={ check } />
</p>
</>
);
}
23 changes: 19 additions & 4 deletions settings/src/components/webauthn/webauthn.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,18 @@ export default function WebAuthn() {
const [ statusWaiting, setStatusWaiting ] = useState( false );
const [ confirmingDisable, setConfirmingDisable ] = useState( false );

/**
* Clear any notices then move to the desired step in the flow
*/
const updateFlow = useCallback(
( nextFlow ) => {
setGlobalNotice( '' );
setStatusError( '' );
setFlow( nextFlow );
},
[ setGlobalNotice ]
);

/**
* Enable the WebAuthn provider.
*/
Expand Down Expand Up @@ -71,8 +83,8 @@ export default function WebAuthn() {
await toggleProvider();
}

setFlow( 'manage' );
}, [ webAuthnEnabled, toggleProvider ] );
updateFlow( 'manage' );
}, [ webAuthnEnabled, toggleProvider, updateFlow ] );

/**
* Display the modal to confirm disabling the WebAuthn provider.
Expand All @@ -90,7 +102,10 @@ export default function WebAuthn() {

if ( 'register' === flow ) {
return (
<RegisterKey onSuccess={ onRegisterSuccess } onCancel={ () => setFlow( 'manage' ) } />
<RegisterKey
onSuccess={ onRegisterSuccess }
onCancel={ () => updateFlow( 'manage' ) }
/>
);
}

Expand All @@ -106,7 +121,7 @@ export default function WebAuthn() {
{ keys.length > 0 && <ListKeys /> }

<p className="wporg-2fa__submit-actions">
<Button variant="primary" onClick={ () => setFlow( 'register' ) }>
<Button variant="primary" onClick={ () => updateFlow( 'register' ) }>
Register new key
</Button>

Expand Down
1 change: 1 addition & 0 deletions settings/src/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,4 @@ $alert-blue: #72aee6;
@import "components/screen-navigation";
@import "components/auto-tabbing-input";
@import "components/revalidate-modal";
@import "components/success";

0 comments on commit 9dd67ca

Please sign in to comment.