From b064c7a88bea6c7ef53b317e03811ace67359591 Mon Sep 17 00:00:00 2001 From: ren <18050944+renintw@users.noreply.github.com> Date: Fri, 26 May 2023 04:14:38 +0800 Subject: [PATCH 01/20] pass setScreen to BackupCode --- settings/src/script.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/settings/src/script.js b/settings/src/script.js index 3ceccb3d..1cc3d6ac 100644 --- a/settings/src/script.js +++ b/settings/src/script.js @@ -63,6 +63,8 @@ function Main( { userId } ) { const [ globalNotice, setGlobalNotice ] = useState( '' ); let currentUrl = new URL( document.location.href ); + let initialScreen = currentUrl.searchParams.get( 'screen' ); + const [ screen, setScreen ] = useState( initialScreen ); // The index is the URL slug and the value is the React component. const components = { @@ -70,7 +72,7 @@ function Main( { userId } ) { email: , password: , totp: , - 'backup-codes': , + 'backup-codes': , }; // TODO: Only enable WebAuthn UI in development, until it's finished. @@ -81,17 +83,12 @@ function Main( { userId } ) { // The screens where a recent two factor challenge is required. const twoFactorRequiredScreens = [ 'webauthn', 'totp', 'backup-codes' ]; - let initialScreen = currentUrl.searchParams.get( 'screen' ); - if ( ! components[ initialScreen ] ) { initialScreen = 'account-status'; currentUrl.searchParams.set( 'screen', initialScreen ); window.history.pushState( {}, '', currentUrl ); } - const [ screen, setScreen ] = useState( initialScreen ); - const currentScreen = components[ screen ]; - // Listen for back/forward button clicks. useEffect( () => { window.addEventListener( 'popstate', handlePopState ); @@ -146,6 +143,7 @@ function Main( { userId } ) { return ; } + const currentScreen = components[ screen ]; let screenContent = currentScreen; if ( 'account-status' !== screen ) { From d7c332303382fea1aea5f91eea1b791f7f94e51d Mon Sep 17 00:00:00 2001 From: ren <18050944+renintw@users.noreply.github.com> Date: Tue, 30 May 2023 07:53:28 +0800 Subject: [PATCH 02/20] Redirect to account-status if backupCodes disabled --- settings/src/components/backup-codes.js | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/settings/src/components/backup-codes.js b/settings/src/components/backup-codes.js index 18c92f1f..a7c5adf2 100644 --- a/settings/src/components/backup-codes.js +++ b/settings/src/components/backup-codes.js @@ -14,13 +14,26 @@ import { refreshRecord } from '../utilities'; /** * Setup and manage backup codes. + * + * @param props + * @param props.setScreen */ -export default function BackupCodes() { +export default function BackupCodes( { setScreen } ) { const { - user: { backupCodesEnabled }, + user: { totpEnabled, backupCodesEnabled }, } = useContext( GlobalContext ); const [ regenerating, setRegenerating ] = useState( false ); + // If TOTP hasn't been enabled, the user should not have access to BackupCodes component. + // This is primarily added to prevent users from accessing through the URL. + if ( ! totpEnabled ) { + const currentUrl = new URL( document.location.href ); + currentUrl.searchParams.set( 'screen', 'account-status' ); + window.history.pushState( {}, '', currentUrl ); + setScreen( 'account-status' ); + return; + } + if ( backupCodesEnabled && ! regenerating ) { return ; } From f8abd296640198b3d9a84f426cda833afd407784 Mon Sep 17 00:00:00 2001 From: ren <18050944+renintw@users.noreply.github.com> Date: Fri, 26 May 2023 11:13:53 +0800 Subject: [PATCH 03/20] await refreshRecord save() promise was introduced in dd26ab3 for the function refreshRecord, so an await should be added. --- settings/src/components/backup-codes.js | 5 +++-- settings/src/components/revalidate-modal.js | 9 +++++++-- settings/src/components/tests/utlitites.test.js | 4 ++-- settings/src/components/totp.js | 4 ++-- 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/settings/src/components/backup-codes.js b/settings/src/components/backup-codes.js index a7c5adf2..c8a0c97b 100644 --- a/settings/src/components/backup-codes.js +++ b/settings/src/components/backup-codes.js @@ -84,9 +84,10 @@ function Setup( { setRegenerating } ) { }, [] ); // Finish the setup process. - const handleFinished = useCallback( () => { + const handleFinished = useCallback( async () => { + // TODO: Add try catch here after https://github.com/WordPress/wporg-two-factor/pull/187/files is merged. // The codes have already been saved to usermeta, see `generateCodes()` above. - refreshRecord( userRecord ); // This has the intended side-effect of redirecting to the Manage screen. + await refreshRecord( userRecord ); // This has the intended side-effect of redirecting to the Manage screen. setGlobalNotice( 'Backup codes have been enabled.' ); setRegenerating( false ); } ); diff --git a/settings/src/components/revalidate-modal.js b/settings/src/components/revalidate-modal.js index 3dbbafd2..51c07f1c 100644 --- a/settings/src/components/revalidate-modal.js +++ b/settings/src/components/revalidate-modal.js @@ -37,7 +37,7 @@ function RevalidateIframe() { const ref = useRef(); useEffect( () => { - function maybeRefreshUser( { data: { type, message } = {} } ) { + async function maybeRefreshUser( { data: { type, message } = {} } ) { if ( type !== 'reValidationComplete' ) { return; } @@ -49,7 +49,12 @@ function RevalidateIframe() { record[ '2fa_revalidation' ].expires_at = new Date().getTime() / 1000 + 3600; // Refresh the user record, to fetch the correct 2fa_revalidation data. - refreshRecord( userRecord ); + try { + await refreshRecord( userRecord ); + } catch ( error ) { + // TODO: handle error more properly here, likely by showing a error notice + console.error( 'Failed to refresh user record:', error ); + } } window.addEventListener( 'message', maybeRefreshUser ); diff --git a/settings/src/components/tests/utlitites.test.js b/settings/src/components/tests/utlitites.test.js index 0254742e..e90cfe8e 100644 --- a/settings/src/components/tests/utlitites.test.js +++ b/settings/src/components/tests/utlitites.test.js @@ -123,8 +123,8 @@ describe( 'refreshRecord', () => { mockRecord.save.mockReset(); } ); - it( 'should call edit and save methods on the record object', () => { - refreshRecord( mockRecord ); + it( 'should call edit and save methods on the record object', async () => { + await refreshRecord( mockRecord ); expect( mockRecord.edit ).toHaveBeenCalledWith( { refreshRecordFakeKey: '', diff --git a/settings/src/components/totp.js b/settings/src/components/totp.js index c3c85927..66150e1b 100644 --- a/settings/src/components/totp.js +++ b/settings/src/components/totp.js @@ -74,7 +74,7 @@ function Setup() { }, } ); - refreshRecord( userRecord ); + await refreshRecord( userRecord ); clickScreenLink( event, 'backup-codes' ); setGlobalNotice( 'Successfully enabled One Time Passwords.' ); // Must be After `clickScreenEvent` clears it. } catch ( handleEnableError ) { @@ -304,7 +304,7 @@ function Manage() { data: { user_id: userRecord.record.id }, } ); - refreshRecord( userRecord ); + await refreshRecord( userRecord ); setGlobalNotice( 'Successfully disabled One Time Passwords.' ); } catch ( handleDisableError ) { setError( handleDisableError.message ); From b3da33b06b8ed79d46f855916e60e8880e1f63d9 Mon Sep 17 00:00:00 2001 From: ren <18050944+renintw@users.noreply.github.com> Date: Fri, 26 May 2023 11:16:44 +0800 Subject: [PATCH 04/20] Fix eslint error --- settings/src/components/revalidate-modal.js | 1 + 1 file changed, 1 insertion(+) diff --git a/settings/src/components/revalidate-modal.js b/settings/src/components/revalidate-modal.js index 51c07f1c..db7ab779 100644 --- a/settings/src/components/revalidate-modal.js +++ b/settings/src/components/revalidate-modal.js @@ -53,6 +53,7 @@ function RevalidateIframe() { await refreshRecord( userRecord ); } catch ( error ) { // TODO: handle error more properly here, likely by showing a error notice + // eslint-disable-next-line no-console console.error( 'Failed to refresh user record:', error ); } } From 3d3152b52633171c1acfa8192b4cb57152b22b88 Mon Sep 17 00:00:00 2001 From: Adam Wood Date: Thu, 1 Jun 2023 15:15:29 +1200 Subject: [PATCH 05/20] Change clickScreenLink handler to navigation handler to be more reusable --------- Co-authored-by: Adam Wood --- settings/src/components/revalidate-modal.js | 9 ++++++--- settings/src/components/screen-link.js | 11 ++++++++--- settings/src/script.js | 8 +++----- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/settings/src/components/revalidate-modal.js b/settings/src/components/revalidate-modal.js index db7ab779..48738038 100644 --- a/settings/src/components/revalidate-modal.js +++ b/settings/src/components/revalidate-modal.js @@ -1,16 +1,19 @@ /** * WordPress dependencies */ -import { useContext, useEffect, useRef } from '@wordpress/element'; +import { useCallback, useContext, useEffect, useRef } from '@wordpress/element'; import { GlobalContext } from '../script'; import { Modal } from '@wordpress/components'; import { useMergeRefs, useFocusableIframe } from '@wordpress/compose'; import { refreshRecord } from '../utilities'; export default function RevalidateModal() { - const { clickScreenLink } = useContext( GlobalContext ); + const { navigateToScreen } = useContext( GlobalContext ); - const goBack = ( event ) => clickScreenLink( event, 'account-status' ); + const goBack = useCallback( ( event ) => { + event.preventDefault(); + navigateToScreen( 'account-status' ); + }, [] ); return ( { + event.preventDefault(); + navigateToScreen( screen ); + }, [] ); + return ( clickScreenLink( event, screen ) } + onClick={ onClick } className={ classes.join( ' ' ) } aria-label={ ariaLabel } > diff --git a/settings/src/script.js b/settings/src/script.js index 1cc3d6ac..75643c93 100644 --- a/settings/src/script.js +++ b/settings/src/script.js @@ -114,10 +114,8 @@ function Main( { userId } ) { * This is used in conjunction with real links in order to preserve deep linking and other foundational * behaviors that are broken otherwise. */ - const clickScreenLink = useCallback( - ( event, nextScreen ) => { - event.preventDefault(); - + const navigateToScreen = useCallback( + ( nextScreen ) => { // Reset to initial after navigating away from a page. // Note: password was initially not in record, this would prevent incomplete state // from resetting when leaving the password setting page. @@ -185,7 +183,7 @@ function Main( { userId } ) { } return ( - + { screenContent } From fac049fd216a3f32408573707de00da39d10d6ff Mon Sep 17 00:00:00 2001 From: Adam Wood Date: Thu, 1 Jun 2023 15:16:30 +1200 Subject: [PATCH 06/20] Block access to backup codes on render if totp is not enabled --------- Co-authored-by: Adam Wood --- settings/src/components/backup-codes.js | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/settings/src/components/backup-codes.js b/settings/src/components/backup-codes.js index c8a0c97b..55642ffa 100644 --- a/settings/src/components/backup-codes.js +++ b/settings/src/components/backup-codes.js @@ -14,23 +14,18 @@ import { refreshRecord } from '../utilities'; /** * Setup and manage backup codes. - * - * @param props - * @param props.setScreen */ -export default function BackupCodes( { setScreen } ) { +export default function BackupCodes() { const { - user: { totpEnabled, backupCodesEnabled }, + user: { backupCodesEnabled, totpEnabled }, + navigateToScreen, } = useContext( GlobalContext ); const [ regenerating, setRegenerating ] = useState( false ); // If TOTP hasn't been enabled, the user should not have access to BackupCodes component. // This is primarily added to prevent users from accessing through the URL. if ( ! totpEnabled ) { - const currentUrl = new URL( document.location.href ); - currentUrl.searchParams.set( 'screen', 'account-status' ); - window.history.pushState( {}, '', currentUrl ); - setScreen( 'account-status' ); + navigateToScreen( 'account-status' ); return; } From 8ec57fd4ef604788b951f2c54930f362a9fabb62 Mon Sep 17 00:00:00 2001 From: ren <18050944+renintw@users.noreply.github.com> Date: Tue, 30 May 2023 17:36:15 +0800 Subject: [PATCH 07/20] Handle 'revalidation_required' error in script.js --- settings/src/components/backup-codes.js | 3 ++- settings/src/script.js | 6 +++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/settings/src/components/backup-codes.js b/settings/src/components/backup-codes.js index 55642ffa..e6f2b18c 100644 --- a/settings/src/components/backup-codes.js +++ b/settings/src/components/backup-codes.js @@ -46,10 +46,11 @@ function Setup( { setRegenerating } ) { const { setGlobalNotice, user: { userRecord }, + setError, + error, } = useContext( GlobalContext ); const [ backupCodes, setBackupCodes ] = useState( [] ); const [ hasPrinted, setHasPrinted ] = useState( false ); - const [ error, setError ] = useState( '' ); // Generate new backup codes and save them in usermeta. useEffect( () => { diff --git a/settings/src/script.js b/settings/src/script.js index 75643c93..c1902a23 100644 --- a/settings/src/script.js +++ b/settings/src/script.js @@ -61,6 +61,7 @@ function Main( { userId } ) { hasPrimaryProvider, } = user; const [ globalNotice, setGlobalNotice ] = useState( '' ); + const [ error, setError ] = useState( '' ); let currentUrl = new URL( document.location.href ); let initialScreen = currentUrl.searchParams.get( 'screen' ); @@ -183,9 +184,12 @@ function Main( { userId } ) { } return ( - + { screenContent } + { 'revalidation_required' === error.code && } ); } From e0cd2bbdb30a868a2ae6d2f78e647477b2b16931 Mon Sep 17 00:00:00 2001 From: ren <18050944+renintw@users.noreply.github.com> Date: Tue, 30 May 2023 21:23:01 +0800 Subject: [PATCH 08/20] Streamline the flow of rendering Modal for DRYness --- settings/src/script.js | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/settings/src/script.js b/settings/src/script.js index c1902a23..aa31edb5 100644 --- a/settings/src/script.js +++ b/settings/src/script.js @@ -170,18 +170,14 @@ function Main( { userId } ) { { currentScreen } ); - } else if ( + } + + const isRevalidationExpired = twoFactorRequiredScreens.includes( screen ) && hasPrimaryProvider && - record[ '2fa_revalidation' ]?.expires_at <= new Date().getTime() / 1000 - ) { - screenContent = ( - <> - { currentScreen } - - - ); - } + record[ '2fa_revalidation' ]?.expires_at <= new Date().getTime() / 1000; + + const shouldRevalidate = 'revalidation_required' === error.code || isRevalidationExpired; return ( { screenContent } - { 'revalidation_required' === error.code && } + { shouldRevalidate && } ); } From 494358fd06d1b21329e9fde1092fa944594b7ef0 Mon Sep 17 00:00:00 2001 From: ren <18050944+renintw@users.noreply.github.com> Date: Thu, 1 Jun 2023 10:24:36 +0800 Subject: [PATCH 09/20] Clear error when navigating though navigateToScreen --- settings/src/script.js | 1 + 1 file changed, 1 insertion(+) diff --git a/settings/src/script.js b/settings/src/script.js index aa31edb5..29c26059 100644 --- a/settings/src/script.js +++ b/settings/src/script.js @@ -132,6 +132,7 @@ function Main( { userId } ) { currentUrl.searchParams.set( 'screen', nextScreen ); window.history.pushState( {}, '', currentUrl ); + setError( '' ); setGlobalNotice( '' ); setScreen( nextScreen ); }, From 22b3e6c234142eea2724f4adb5b2fff2fedfeeb1 Mon Sep 17 00:00:00 2001 From: ren <18050944+renintw@users.noreply.github.com> Date: Thu, 1 Jun 2023 03:54:28 +0800 Subject: [PATCH 10/20] Extract ScreenNavigation --- settings/src/components/screen-navigation.js | 42 +++++++++++++++++++ .../src/components/screen-navigation.scss | 22 ++++++++++ settings/src/script.js | 40 ++++-------------- settings/src/style.scss | 24 +---------- 4 files changed, 73 insertions(+), 55 deletions(-) create mode 100644 settings/src/components/screen-navigation.js create mode 100644 settings/src/components/screen-navigation.scss diff --git a/settings/src/components/screen-navigation.js b/settings/src/components/screen-navigation.js new file mode 100644 index 00000000..1951226d --- /dev/null +++ b/settings/src/components/screen-navigation.js @@ -0,0 +1,42 @@ +/** + * WordPress dependencies + */ +import { Icon, chevronLeft } from '@wordpress/icons'; +import { Card, CardHeader, CardBody } from '@wordpress/components'; + +/** + * Internal dependencies + */ +import ScreenLink from './screen-link'; + +/** + * @param props + * @param props.children + * @param props.screen + */ +const ScreenNavigation = ( { screen, children } ) => ( + + + + + Back + + } + /> + +

+ { screen + .replace( '-', ' ' ) + .replace( 'totp', 'Two-Factor Authentication' ) + .replace( 'webauthn', 'Two-Factor Security Key' ) } +

+
+ { children } +
+); + +export default ScreenNavigation; diff --git a/settings/src/components/screen-navigation.scss b/settings/src/components/screen-navigation.scss new file mode 100644 index 00000000..fdfd62c8 --- /dev/null +++ b/settings/src/components/screen-navigation.scss @@ -0,0 +1,22 @@ +.wporg-2fa__navigation { + padding: 24px 0 !important; /* Override Gutenberg auto-generated. */ + justify-content: center !important; + position: relative; + + a { + display: flex; + align-items: center; + position: absolute; + left: 24px; + } + + svg { + fill: #4ca6cf; + } + + h3 { + margin: unset; + text-transform: capitalize; + } + +} \ No newline at end of file diff --git a/settings/src/script.js b/settings/src/script.js index 29c26059..5f9496a6 100644 --- a/settings/src/script.js +++ b/settings/src/script.js @@ -9,14 +9,13 @@ import { useState, createRoot, } from '@wordpress/element'; -import { Icon, chevronLeft } from '@wordpress/icons'; -import { Card, CardHeader, CardBody, Spinner } from '@wordpress/components'; +import { Spinner } from '@wordpress/components'; /** * Internal dependencies */ import { useUser } from './utilities'; -import ScreenLink from './components/screen-link'; +import ScreenNavigation from './components/screen-navigation'; import AccountStatus from './components/account-status'; import Password from './components/password'; import EmailAddress from './components/email-address'; @@ -143,35 +142,12 @@ function Main( { userId } ) { return ; } - const currentScreen = components[ screen ]; - let screenContent = currentScreen; - - if ( 'account-status' !== screen ) { - screenContent = ( - - - - - Back - - } - /> - -

- { screen - .replace( '-', ' ' ) - .replace( 'totp', 'Two-Factor Authentication' ) - .replace( 'webauthn', 'Two-Factor Security Key' ) } -

-
- { currentScreen } -
+ const currentScreenComponent = + 'account-status' === screen ? ( + components[ screen ] + ) : ( + { components[ screen ] } ); - } const isRevalidationExpired = twoFactorRequiredScreens.includes( screen ) && @@ -185,7 +161,7 @@ function Main( { userId } ) { value={ { navigateToScreen, user, setGlobalNotice, setError, error } } > - { screenContent } + { currentScreenComponent } { shouldRevalidate && }
); diff --git a/settings/src/style.scss b/settings/src/style.scss index 5ddc1549..e3ed7684 100644 --- a/settings/src/style.scss +++ b/settings/src/style.scss @@ -50,29 +50,6 @@ $alert-blue: #72aee6; flex-wrap: wrap; } -.wporg-2fa__navigation { - padding: 24px 0 !important; /* Override Gutenberg auto-generated. */ - justify-content: center !important; - position: relative; - - a { - display: flex; - align-items: center; - position: absolute; - left: 24px; - } - - svg { - fill: #4ca6cf; - } - - h3 { - margin: unset; - text-transform: capitalize; - } - -} - .wporg-2fa__token { letter-spacing: .3em; } @@ -105,5 +82,6 @@ $alert-blue: #72aee6; @import "components/setup-progress-bar"; @import "components/global-notice"; @import "components/screen-link"; +@import "components/screen-navigation"; @import "components/auto-tabbing-input"; @import "components/revalidate-modal"; From 1f360d39b695bcef13c1deed1dbaed25fb9ac806 Mon Sep 17 00:00:00 2001 From: ren <18050944+renintw@users.noreply.github.com> Date: Thu, 1 Jun 2023 04:36:54 +0800 Subject: [PATCH 11/20] Add var. currentScreen to navigateToScreen --- settings/src/components/account-status.js | 6 +++++- settings/src/components/backup-codes.js | 2 +- settings/src/components/revalidate-modal.js | 2 +- settings/src/components/screen-link.js | 21 +++++++++++++++++--- settings/src/components/screen-navigation.js | 3 ++- settings/src/components/totp.js | 6 +++--- settings/src/script.js | 9 ++++++++- 7 files changed, 38 insertions(+), 11 deletions(-) diff --git a/settings/src/components/account-status.js b/settings/src/components/account-status.js index 3b730dc1..44abc60b 100644 --- a/settings/src/components/account-status.js +++ b/settings/src/components/account-status.js @@ -121,7 +121,11 @@ function SettingStatusCard( { screen, status, headerText, bodyText, disabled = f return ( - { disabled ? cardContent : } + { disabled ? ( + cardContent + ) : ( + + ) } ); } diff --git a/settings/src/components/backup-codes.js b/settings/src/components/backup-codes.js index e6f2b18c..0c9cbe85 100644 --- a/settings/src/components/backup-codes.js +++ b/settings/src/components/backup-codes.js @@ -25,7 +25,7 @@ export default function BackupCodes() { // If TOTP hasn't been enabled, the user should not have access to BackupCodes component. // This is primarily added to prevent users from accessing through the URL. if ( ! totpEnabled ) { - navigateToScreen( 'account-status' ); + navigateToScreen( { nextScreen: 'account-status' } ); return; } diff --git a/settings/src/components/revalidate-modal.js b/settings/src/components/revalidate-modal.js index 48738038..8408577c 100644 --- a/settings/src/components/revalidate-modal.js +++ b/settings/src/components/revalidate-modal.js @@ -12,7 +12,7 @@ export default function RevalidateModal() { const goBack = useCallback( ( event ) => { event.preventDefault(); - navigateToScreen( 'account-status' ); + navigateToScreen( { nextScreen: 'account-status' } ); }, [] ); return ( diff --git a/settings/src/components/screen-link.js b/settings/src/components/screen-link.js index 84ec24d1..91c62b6e 100644 --- a/settings/src/components/screen-link.js +++ b/settings/src/components/screen-link.js @@ -8,12 +8,27 @@ import { useCallback, useContext } from '@wordpress/element'; */ import { GlobalContext } from '../script'; -export default function ScreenLink( { screen, anchorText, buttonStyle = false, ariaLabel } ) { +/** + * + * @param props + * @param props.currentScreen + * @param props.nextScreen + * @param props.anchorText + * @param props.buttonStyle + * @param props.ariaLabel + */ +export default function ScreenLink( { + currentScreen = '', + nextScreen, + anchorText, + buttonStyle = false, + ariaLabel, +} ) { const { navigateToScreen } = useContext( GlobalContext ); const classes = []; const screenUrl = new URL( document.location.href ); - screenUrl.searchParams.set( 'screen', screen ); + screenUrl.searchParams.set( 'screen', nextScreen ); if ( 'primary' === buttonStyle ) { classes.push( 'components-button' ); @@ -25,7 +40,7 @@ export default function ScreenLink( { screen, anchorText, buttonStyle = false, a const onClick = useCallback( ( event ) => { event.preventDefault(); - navigateToScreen( screen ); + navigateToScreen( { currentScreen, nextScreen } ); }, [] ); return ( diff --git a/settings/src/components/screen-navigation.js b/settings/src/components/screen-navigation.js index 1951226d..abfd95af 100644 --- a/settings/src/components/screen-navigation.js +++ b/settings/src/components/screen-navigation.js @@ -18,7 +18,8 @@ const ScreenNavigation = ( { screen, children } ) => ( diff --git a/settings/src/components/totp.js b/settings/src/components/totp.js index 66150e1b..0bb1821f 100644 --- a/settings/src/components/totp.js +++ b/settings/src/components/totp.js @@ -75,7 +75,7 @@ function Setup() { } ); await refreshRecord( userRecord ); - clickScreenLink( event, 'backup-codes' ); + clickScreenLink( { event, nextScreen: 'backup-codes' } ); setGlobalNotice( 'Successfully enabled One Time Passwords.' ); // Must be After `clickScreenEvent` clears it. } catch ( handleEnableError ) { setError( handleEnableError.message ); @@ -321,8 +321,8 @@ function Manage() {

Make sure you've created{ ' ' } - and saved them in a - safe location, in case you ever lose your device. You may also need them when + and saved them in + a safe location, in case you ever lose your device. You may also need them when transitioning to a new device. Without them you may permanently lose access to your account.

diff --git a/settings/src/script.js b/settings/src/script.js index 5f9496a6..44688bc4 100644 --- a/settings/src/script.js +++ b/settings/src/script.js @@ -113,9 +113,16 @@ function Main( { userId } ) { * * This is used in conjunction with real links in order to preserve deep linking and other foundational * behaviors that are broken otherwise. + * + * @param currentScreen Optional. Only needed when you'd like to do some processing on the current screen before navigating away. + * @param nextScreen Next screen you're navigating to */ const navigateToScreen = useCallback( - ( nextScreen ) => { + ( { currentScreen, nextScreen } ) => { + if ( 'backup-codes' === currentScreen ) { + // TODO + } + // Reset to initial after navigating away from a page. // Note: password was initially not in record, this would prevent incomplete state // from resetting when leaving the password setting page. From 27ed028ecc030eac93e74fbf012098c1a0912a42 Mon Sep 17 00:00:00 2001 From: ren <18050944+renintw@users.noreply.github.com> Date: Thu, 1 Jun 2023 04:38:07 +0800 Subject: [PATCH 12/20] Extract hasPrinted to useUser --- settings/src/components/backup-codes.js | 9 +++++---- settings/src/script.js | 12 +++++++++++- settings/src/utilities.js | 4 ++++ 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/settings/src/components/backup-codes.js b/settings/src/components/backup-codes.js index 0c9cbe85..140fce57 100644 --- a/settings/src/components/backup-codes.js +++ b/settings/src/components/backup-codes.js @@ -48,9 +48,10 @@ function Setup( { setRegenerating } ) { user: { userRecord }, setError, error, + hasBackupCodesPrinted, + setHasBackupCodesPrinted, } = useContext( GlobalContext ); const [ backupCodes, setBackupCodes ] = useState( [] ); - const [ hasPrinted, setHasPrinted ] = useState( false ); // Generate new backup codes and save them in usermeta. useEffect( () => { @@ -116,15 +117,15 @@ function Setup( { setRegenerating } ) { ) }

-

diff --git a/settings/src/script.js b/settings/src/script.js index 44688bc4..d9ad65af 100644 --- a/settings/src/script.js +++ b/settings/src/script.js @@ -58,6 +58,8 @@ function Main( { userId } ) { const { userRecord: { record, edit, hasEdits, hasResolved }, hasPrimaryProvider, + hasBackupCodesPrinted, + setHasBackupCodesPrinted, } = user; const [ globalNotice, setGlobalNotice ] = useState( '' ); const [ error, setError ] = useState( '' ); @@ -165,7 +167,15 @@ function Main( { userId } ) { return ( { currentScreenComponent } diff --git a/settings/src/utilities.js b/settings/src/utilities.js index 0ae9f639..1b013f28 100644 --- a/settings/src/utilities.js +++ b/settings/src/utilities.js @@ -1,5 +1,6 @@ import { useSelect } from '@wordpress/data'; import { store as coreDataStore, useEntityRecord } from '@wordpress/core-data'; +import { useState } from '@wordpress/element'; /** * Get the user. @@ -8,6 +9,7 @@ import { store as coreDataStore, useEntityRecord } from '@wordpress/core-data'; */ export function useUser( userId ) { const userRecord = useEntityRecord( 'root', 'user', userId ); + const [ hasBackupCodesPrinted, setHasBackupCodesPrinted ] = useState( false ); const isSaving = useSelect( ( select ) => select( coreDataStore ).isSavingEntityRecord( 'root', 'user', userId ) ); @@ -25,6 +27,8 @@ export function useUser( userId ) { totpEnabled, backupCodesEnabled, webAuthnEnabled, + hasBackupCodesPrinted, + setHasBackupCodesPrinted, }; } From 21e4fead7a0ea27d885fd929d38039bc43c349e1 Mon Sep 17 00:00:00 2001 From: ren <18050944+renintw@users.noreply.github.com> Date: Thu, 1 Jun 2023 06:08:28 +0800 Subject: [PATCH 13/20] show notice when Back & checkbox is not confirmed --- settings/src/components/backup-codes.js | 49 +++++++++++++++---------- settings/src/script.js | 10 +++-- 2 files changed, 36 insertions(+), 23 deletions(-) diff --git a/settings/src/components/backup-codes.js b/settings/src/components/backup-codes.js index 140fce57..2998a766 100644 --- a/settings/src/components/backup-codes.js +++ b/settings/src/components/backup-codes.js @@ -87,7 +87,18 @@ function Setup( { setRegenerating } ) { await refreshRecord( userRecord ); // This has the intended side-effect of redirecting to the Manage screen. setGlobalNotice( 'Backup codes have been enabled.' ); setRegenerating( false ); - } ); + }, [] ); + + const handleCheckboxChange = useCallback( + ( checked ) => { + setHasBackupCodesPrinted( checked ); + // Error should disappear when the user interacts with the checkbox again. + if ( 'checkbox_confirmation_required' === error.code ) { + setError( '' ); + } + }, + [ error.code ] + ); return ( <> @@ -99,31 +110,29 @@ function Setup( { setRegenerating } ) {

Please print the codes and keep them in a safe place.

- { error ? ( + + + + + Without access to the one-time password app or a backup code, you will lose access + to your account. Once you navigate away from this page, you will not be able to view + these codes again. + + + { error && ( { error.message } - ) : ( - <> - - - - - Without access to the one-time password app or a backup code, you will lose - access to your account. Once you navigate away from this page, you will not - be able to view these codes again. - - - - ) } + +