diff --git a/settings/src/components/backup-codes-setup.js b/settings/src/components/backup-codes-setup.js new file mode 100644 index 00000000..17fa7961 --- /dev/null +++ b/settings/src/components/backup-codes-setup.js @@ -0,0 +1,158 @@ +import apiFetch from '@wordpress/api-fetch'; +import { useContext, useCallback, useEffect, useState } from '@wordpress/element'; +import { Button, ButtonGroup, CheckboxControl, Flex, Notice, Spinner } from '@wordpress/components'; +import { Icon, warning, cancelCircleFilled } from '@wordpress/icons'; +import { GlobalContext } from '../script'; +import { refreshRecord } from '../utilities/common'; +import { IntroText } from './backup-codes'; +import CopyToClipboardButton from './copy-to-clipboard-button'; +import DownloadButton from './download-button'; +import PrintButton from './print-button'; + +/** + * Setup the Backup Codes provider. + * + * @param props + * @param props.onSuccess + */ +export function Setup( { onSuccess } ) { + const { + setGlobalNotice, + user: { userRecord }, + setError, + error, + setBackupCodesVerified, + } = useContext( GlobalContext ); + const [ backupCodes, setBackupCodes ] = useState( [] ); + const [ hasPrinted, setHasPrinted ] = useState( false ); + + // Generate new backup codes and save them in usermeta. + useEffect( () => { + // useEffect callbacks can't be async directly, because that'd return the promise as a "cleanup" function. + const generateCodes = async () => { + // This will save the backup codes and enable the provider, which isn't really what we want, but that + // mimics the upstream plugin. It's probably better to fix it there first, and then update this, to + // make sure we stay in sync with upstream. + // See https://github.com/WordPress/two-factor/issues/507 + try { + const response = await apiFetch( { + path: '/two-factor/1.0/generate-backup-codes', + method: 'POST', + data: { + user_id: userRecord.record.id, + enable_provider: true, + }, + } ); + + setBackupCodes( response.codes ); + + // Update the Account Status screen in case they click `Back` without verifying the codes, but + // don't redirect to the Manage screen yet. This is mainly due to the side-effects of + // `two-factor/#507`, so it will need to be modified or maybe removed when that is fixed upstream. + setBackupCodesVerified( false ); + await refreshRecord( userRecord ); + } catch ( apiFetchError ) { + setError( apiFetchError ); + } + }; + + generateCodes(); + // This should only run once on mount. + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [] ); + + // Finish the setup process. + 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. + setBackupCodesVerified( true ); + setGlobalNotice( 'Backup codes have been enabled.' ); + onSuccess(); + } ); + + return ( + <> +
+ + +

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

+ + + + 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 + be able to view these codes again. + +
+ + { error ? ( + + + { error.message } + + ) : ( + <> + + + + + ) } + + + + + + ); +} + +/** + * Display a list of backup codes and actions + * + * @param props + * @param props.codes + */ + +export function CodeList( { codes } ) { + const hasCodes = !! codes.length; + + return ( + <> +
+ { hasCodes ? ( +
    + { codes.map( ( code ) => { + return ( +
  1. + { code.slice( 0, 4 ) + ' ' + code.slice( 4 ) } +
  2. + ); + } ) } +
+ ) : ( +

+ Generating backup codes... +

+ ) } +
+ { hasCodes && ( + + + + + + ) } + + ); +} diff --git a/settings/src/components/backup-codes.js b/settings/src/components/backup-codes.js index e769f95f..fedd1415 100644 --- a/settings/src/components/backup-codes.js +++ b/settings/src/components/backup-codes.js @@ -1,20 +1,15 @@ /** * WordPress dependencies */ -import apiFetch from '@wordpress/api-fetch'; -import { useContext, useCallback, useEffect, useState } from '@wordpress/element'; -import { Button, ButtonGroup, CheckboxControl, Flex, Notice, Spinner } from '@wordpress/components'; +import { useContext } from '@wordpress/element'; +import { Button, Notice } from '@wordpress/components'; import { Icon, warning, cancelCircleFilled } from '@wordpress/icons'; /** * Internal dependencies */ import { GlobalContext } from '../script'; -import { refreshRecord } from '../utilities/common'; import ScreenLink from './screen-link'; -import CopyToClipboardButton from './copy-to-clipboard-button'; -import PrintButton from './print-button'; -import DownloadButton from './download-button'; /** * Setup and manage backup codes. @@ -22,20 +17,10 @@ import DownloadButton from './download-button'; * @param props * @param props.onSuccess */ -export default function BackupCodes( { onSuccess = () => {} } ) { +export default function BackupCodes() { const { user: { hasPrimaryProvider }, - shouldAutoGenerateBackupCodes, } = useContext( GlobalContext ); - const [ generating, setGenerating ] = useState( false ); - - useEffect( () => { - if ( shouldAutoGenerateBackupCodes ) { - setGenerating( true ); - } - // This should only run once on mount. - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [] ); // Prevent users from accessing directly through the URL. if ( ! hasPrimaryProvider ) { @@ -49,165 +34,10 @@ export default function BackupCodes( { onSuccess = () => {} } ) { ); } - return generating ? ( - - ) : ( - - ); -} - -/** - * Setup the Backup Codes provider. - * - * @param props - * @param props.setGenerating - * @param props.onSuccess - */ -function Setup( { setGenerating, onSuccess } ) { - const { - setGlobalNotice, - user: { userRecord }, - setError, - error, - setBackupCodesVerified, - setShouldAutoGenerateBackupCodes, - } = useContext( GlobalContext ); - const [ backupCodes, setBackupCodes ] = useState( [] ); - const [ hasPrinted, setHasPrinted ] = useState( false ); - - // Generate new backup codes and save them in usermeta. - useEffect( () => { - // useEffect callbacks can't be async directly, because that'd return the promise as a "cleanup" function. - const generateCodes = async () => { - // This will save the backup codes and enable the provider, which isn't really what we want, but that - // mimics the upstream plugin. It's probably better to fix it there first, and then update this, to - // make sure we stay in sync with upstream. - // See https://github.com/WordPress/two-factor/issues/507 - try { - const response = await apiFetch( { - path: '/two-factor/1.0/generate-backup-codes', - method: 'POST', - data: { - user_id: userRecord.record.id, - enable_provider: true, - }, - } ); - - setBackupCodes( response.codes ); - setShouldAutoGenerateBackupCodes( false ); - - // Update the Account Status screen in case they click `Back` without verifying the codes, but - // don't redirect to the Manage screen yet. This is mainly due to the side-effects of - // `two-factor/#507`, so it will need to be modified or maybe removed when that is fixed upstream. - setBackupCodesVerified( false ); - await refreshRecord( userRecord ); - } catch ( apiFetchError ) { - setError( apiFetchError ); - } - }; - - generateCodes(); - // This should only run once on mount. - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [] ); - - // Finish the setup process. - 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. - setBackupCodesVerified( true ); - setGlobalNotice( 'Backup codes have been enabled.' ); - setGenerating( false ); - onSuccess(); - } ); - - return ( - <> -
- - -

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

- - - - 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 - be able to view these codes again. - -
- - { error ? ( - - - { error.message } - - ) : ( - <> - - - - - ) } - - - - - - ); + return ; } -/** - * Display a list of backup codes and actions - * - * @param props - * @param props.codes - */ -function CodeList( { codes } ) { - const hasCodes = !! codes.length; - - return ( - <> -
- { hasCodes ? ( -
    - { codes.map( ( code ) => { - return ( -
  1. - { code.slice( 0, 4 ) + ' ' + code.slice( 4 ) } -
  2. - ); - } ) } -
- ) : ( -

- Generating backup codes... -

- ) } -
- { hasCodes && ( - - - - - - ) } - - ); -} - -const IntroText = () => ( +export const IntroText = () => (

Backup codes let you access your account if your primary two-factor authentication method is unavailable, like if your phone is lost or stolen. Each code can only be used once. @@ -216,13 +46,11 @@ const IntroText = () => ( /** * Render the screen where users can manage Backup Codes. - * - * @param props - * @param props.setGenerating */ -function Manage( { setGenerating } ) { +function Manage() { const { user: { backupCodesEnabled, backupCodesRemaining }, + navigateToScreen, } = useContext( GlobalContext ); return ( @@ -249,7 +77,7 @@ function Manage( { setGenerating } ) { ) } - diff --git a/settings/src/components/settings.js b/settings/src/components/settings.js index 55719647..a68a23c8 100644 --- a/settings/src/components/settings.js +++ b/settings/src/components/settings.js @@ -15,6 +15,7 @@ import WebAuthn from './webauthn/webauthn'; import BackupCodes from './backup-codes'; import { GlobalContext } from '../script'; +import { Setup } from './backup-codes-setup'; /** * Render the correct component based on the URL. @@ -24,7 +25,6 @@ export default function Settings() { user: { backupCodesEnabled }, navigateToScreen, screen, - setShouldAutoGenerateBackupCodes, } = useContext( GlobalContext ); // The index is the URL slug and the value is the React component. @@ -36,8 +36,7 @@ export default function Settings() { { if ( ! backupCodesEnabled ) { - setShouldAutoGenerateBackupCodes( true ); - navigateToScreen( 'backup-codes' ); + navigateToScreen( 'backup-codes-setup' ); } else { navigateToScreen( 'home' ); } @@ -48,13 +47,13 @@ export default function Settings() { { if ( ! backupCodesEnabled ) { - setShouldAutoGenerateBackupCodes( true ); - navigateToScreen( 'backup-codes' ); + navigateToScreen( 'backup-codes-setup' ); } } } /> ), 'backup-codes': navigateToScreen( 'home' ) } />, + 'backup-codes-setup': navigateToScreen( 'backup-codes' ) } />, }; const currentScreenComponent = diff --git a/settings/src/script.js b/settings/src/script.js index 5494a018..7ee31545 100644 --- a/settings/src/script.js +++ b/settings/src/script.js @@ -61,7 +61,6 @@ function Main( { userId, isOnboarding } ) { const [ globalNotice, setGlobalNotice ] = useState( '' ); const [ error, setError ] = useState( '' ); const [ backupCodesVerified, setBackupCodesVerified ] = useState( true ); - const [ shouldAutoGenerateBackupCodes, setShouldAutoGenerateBackupCodes ] = useState( false ); let currentUrl = new URL( document.location.href ); const initialScreen = currentUrl.searchParams.get( 'screen' ); @@ -151,8 +150,6 @@ function Main( { userId, isOnboarding } ) { setBackupCodesVerified, setScreen, screen, - shouldAutoGenerateBackupCodes, - setShouldAutoGenerateBackupCodes, } } >