diff --git a/examples/next/pages/ui/components/account-settings/configure-totp/aws-exports.js b/examples/next/pages/ui/components/account-settings/configure-totp/aws-exports.js deleted file mode 100644 index ed7194f6bca..00000000000 --- a/examples/next/pages/ui/components/account-settings/configure-totp/aws-exports.js +++ /dev/null @@ -1,2 +0,0 @@ -import awsExports from '@environments/auth/auth-with-optional-totp-and-sms-mfa/src/aws-exports'; -export default awsExports; diff --git a/examples/next/pages/ui/components/account-settings/configure-totp/index.page.tsx b/examples/next/pages/ui/components/account-settings/configure-totp/index.page.tsx deleted file mode 100644 index 0d98a884f00..00000000000 --- a/examples/next/pages/ui/components/account-settings/configure-totp/index.page.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import React from 'react'; - -import { Amplify } from 'aws-amplify'; - -import { - Authenticator, - Button, - Card, - Flex, - Heading, -} from '@aws-amplify/ui-react'; -import '@aws-amplify/ui-react/styles.css'; - -import { Alert, ConfigureTOTP } from '@aws-amplify/ui-react'; - -import awsExports from './aws-exports'; -Amplify.configure(awsExports); - -export default function App() { - const [isSuccessful, setIsSuccessful] = React.useState(false); - return ( - - {({ signOut }) => ( - - - - - Setup MFA: - { - setIsSuccessful(true); - }} - /> - {isSuccessful ? ( - - TOTP has been set up successfully - - ) : null} - - - - - - )} - - ); -} diff --git a/packages/react/__tests__/__snapshots__/exports.ts.snap b/packages/react/__tests__/__snapshots__/exports.ts.snap index 76ab1df106d..c4ba279580e 100644 --- a/packages/react/__tests__/__snapshots__/exports.ts.snap +++ b/packages/react/__tests__/__snapshots__/exports.ts.snap @@ -17,7 +17,6 @@ Array [ "ComponentClassObject", "ComponentPropsToStylePropsMap", "ComponentPropsToStylePropsMapKeys", - "ConfigureTOTP", "DeleteUser", "Divider", "Expander", diff --git a/packages/react/src/components/AccountSettings/ConfigureTOTP/ConfigureTOTP.tsx b/packages/react/src/components/AccountSettings/ConfigureTOTP/ConfigureTOTP.tsx deleted file mode 100644 index ce6bed0436f..00000000000 --- a/packages/react/src/components/AccountSettings/ConfigureTOTP/ConfigureTOTP.tsx +++ /dev/null @@ -1,175 +0,0 @@ -import React from 'react'; -import QRCode from 'qrcode'; - -import { - AmplifyUser, - getLogger, - getTotpCodeURL, - setupTOTP, - translate, - verifyTOTPToken, -} from '@aws-amplify/ui'; - -import { useAuth } from '../../../internal'; -import { View, Flex } from '../../../primitives'; -import { FormValues } from '../types'; -import { - ConfirmationCode, - CopySecretKey, - Error, - SecretKeyQRCode, - SubmitButton, -} from './defaults'; -import { ConfigureTOTPProps, TotpSecret, VerifyTotpStatus } from './types'; -import { QR_CODE_DIMENSIONS } from './constants'; - -const logger = getLogger('Auth'); - -function ConfigureTOTP({ - totpIssuer = 'AWSCognito', - totpUsername, - onSuccess, - onError, -}: ConfigureTOTPProps): JSX.Element | null { - const [formValues, setFormValues] = React.useState({ code: '' }); - const [totpSecret, setTotpSecret] = React.useState(null); - const [verifyTotpStatus, setVerifyTotpStatus] = - React.useState({ - isVerifying: false, - errorMessage: null, - }); - - const { user, isLoading } = useAuth(); - - const hasInit = React.useRef(false); - - const generateQRCode = React.useCallback( - async (currentUser: AmplifyUser): Promise => { - try { - const secretKey = await setupTOTP(currentUser); - const username = totpUsername || currentUser?.username; - const totpCode = getTotpCodeURL(totpIssuer, username, secretKey); - const qrCode = await QRCode.toDataURL(totpCode); - - setTotpSecret({ secretKey, qrCode }); - } catch (e) { - logger.error(e); - } - }, - [totpIssuer, totpUsername] - ); - - React.useEffect(() => { - if (user && !hasInit.current) { - hasInit.current = true; - generateQRCode(user); - } - }, [generateQRCode, user]); - - // translations - const confirmText = translate('Confirm'); - const copyCodeText = translate('Copy Secret Code'); - - // event handlers - const handleChange = React.useCallback( - (event: React.ChangeEvent) => { - event.preventDefault(); - const { name, value } = event.target; - setFormValues((prevFormValues) => ({ ...prevFormValues, [name]: value })); - }, - [] - ); - - const runVerifyTotpToken = React.useCallback( - async ({ user, code }: { user: AmplifyUser; code: string }) => { - setVerifyTotpStatus({ isVerifying: true, errorMessage: null }); - - try { - await verifyTOTPToken({ user, code }); - - setVerifyTotpStatus({ isVerifying: false, errorMessage: null }); - - onSuccess?.(); // notify success to the parent - } catch (e) { - const error = e as Error; - - setVerifyTotpStatus({ - isVerifying: false, - errorMessage: error.message, - }); - - onError?.(error); // notify error to the parent - } - }, - [onError, onSuccess] - ); - - const handleSubmit = React.useCallback( - (event: React.FormEvent) => { - event.preventDefault(); - const { code } = formValues; - runVerifyTotpToken({ user, code }); - }, - [user, formValues, runVerifyTotpToken] - ); - - const handleCopy = React.useCallback(() => { - navigator.clipboard.writeText(totpSecret.secretKey); - }, [totpSecret]); - - /** Return null if user isn't authenticated in the first place */ - if (!user) { - logger.warn(' requires user to be authenticated.'); - return null; - } - - /** Return null if Auth.getCurrentAuthenticatedUser is still in progress */ - if (isLoading) { - return null; - } - - const { isVerifying, errorMessage } = verifyTotpStatus; - - return ( - - - {totpSecret?.qrCode ? ( - - ) : null} - - {copyCodeText} - - - - - {confirmText} - - {errorMessage ? {errorMessage} : null} - - - ); -} - -export default ConfigureTOTP; diff --git a/packages/react/src/components/AccountSettings/ConfigureTOTP/__tests__/ConfigureTOTP.test.tsx b/packages/react/src/components/AccountSettings/ConfigureTOTP/__tests__/ConfigureTOTP.test.tsx deleted file mode 100644 index 3b07399d515..00000000000 --- a/packages/react/src/components/AccountSettings/ConfigureTOTP/__tests__/ConfigureTOTP.test.tsx +++ /dev/null @@ -1,120 +0,0 @@ -import React from 'react'; -import { - render, - screen, - fireEvent, - waitFor, - act, -} from '@testing-library/react'; - -import * as UIModule from '@aws-amplify/ui'; - -import ConfigureTOTP from '../ConfigureTOTP'; - -const user = { username: 'testuser' } as unknown as UIModule.AmplifyUser; -jest.mock('../../../../internal', () => ({ - useAuth: () => ({ - user, - isLoading: false, - }), -})); - -const setupTOTPSpy = jest.spyOn(UIModule, 'setupTOTP'); -const verifyTOTPToken = jest.spyOn(UIModule, 'verifyTOTPToken'); - -describe('ConfigureTOTP', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('renders as expected', () => { - setupTOTPSpy.mockResolvedValue('secretcode'); - const { container } = render(); - expect(container).toMatchSnapshot(); - }); - - it('calls setupTOTP with expected arguments', async () => { - setupTOTPSpy.mockResolvedValue('secretcode'); - - render(); - - await screen.findByAltText('qr code'); - expect(setupTOTPSpy).toHaveBeenCalledWith(user); - }); - - it('onSuccess is called after successful totp verification', async () => { - setupTOTPSpy.mockResolvedValue('secretcode'); - verifyTOTPToken.mockResolvedValue(); - - const onSuccess = jest.fn(); - - await act(async () => { - render(); - }); - - const submitButton = await screen.findByRole('button', { - name: 'Confirm', - }); - fireEvent.submit(submitButton); - - // submit handling is async, wait for onSuccess to be called - // https://testing-library.com/docs/dom-testing-library/api-async/#waitfor - await waitFor(() => expect(onSuccess).toBeCalledTimes(1)); - }); - - it('onError is called after unsuccessful submit', async () => { - setupTOTPSpy.mockResolvedValue('secretCode'); - verifyTOTPToken.mockRejectedValue(new Error('Mock Error')); - - const onError = jest.fn(); - await act(async () => { - render(); - }); - const submitButton = await screen.findByRole('button', { - name: 'Confirm', - }); - - fireEvent.submit(submitButton); - - // submit handling is async, wait for onError to be called - await waitFor(() => expect(onError).toBeCalledTimes(1)); - }); - - it('error message is displayed after unsuccessful submit', async () => { - setupTOTPSpy.mockResolvedValue('secretCode'); - verifyTOTPToken.mockRejectedValue(new Error('Mock Error')); - - const onError = jest.fn(); - - await act(async () => { - render(); - }); - - const submitButton = await screen.findByRole('button', { - name: 'Confirm', - }); - - fireEvent.submit(submitButton); - - // submit handling is async, wait for error to be displayed - expect(await screen.findByText('Mock Error')).toBeDefined(); - }); - - it('error message is displayed after unsuccessful submit', async () => { - setupTOTPSpy.mockResolvedValue('secretCode'); - verifyTOTPToken.mockRejectedValue(new Error('Mock Error')); - - const onError = jest.fn(); - await act(async () => { - render(); - }); - - const submitButton = await screen.findByRole('button', { - name: 'Confirm', - }); - - fireEvent.submit(submitButton); - - expect(await screen.findByText('Mock Error')).toBeDefined(); - }); -}); diff --git a/packages/react/src/components/AccountSettings/ConfigureTOTP/__tests__/__snapshots__/ConfigureTOTP.test.tsx.snap b/packages/react/src/components/AccountSettings/ConfigureTOTP/__tests__/__snapshots__/ConfigureTOTP.test.tsx.snap deleted file mode 100644 index ee951f69035..00000000000 --- a/packages/react/src/components/AccountSettings/ConfigureTOTP/__tests__/__snapshots__/ConfigureTOTP.test.tsx.snap +++ /dev/null @@ -1,61 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ConfigureTOTP renders as expected 1`] = ` -
-
-
- -
- -
-
- -
-
-
- -
-
-
-`; diff --git a/packages/react/src/components/AccountSettings/ConfigureTOTP/constants.ts b/packages/react/src/components/AccountSettings/ConfigureTOTP/constants.ts deleted file mode 100644 index 3557f271985..00000000000 --- a/packages/react/src/components/AccountSettings/ConfigureTOTP/constants.ts +++ /dev/null @@ -1,5 +0,0 @@ -const QR_CODE_SIZE = '228px'; -export const QR_CODE_DIMENSIONS = { - height: QR_CODE_SIZE, - width: QR_CODE_SIZE, -}; diff --git a/packages/react/src/components/AccountSettings/ConfigureTOTP/defaults.tsx b/packages/react/src/components/AccountSettings/ConfigureTOTP/defaults.tsx deleted file mode 100644 index 1b5e9602d7e..00000000000 --- a/packages/react/src/components/AccountSettings/ConfigureTOTP/defaults.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import React from 'react'; - -import { Alert, Button, Image, TextField } from '../../../primitives'; -import { - AccountSettingsError, - AccountSettingsImage, - AccountSettingsSubmitButton, - AccountSettingsTextField, -} from '../types'; - -export const ConfirmationCode: AccountSettingsTextField = TextField; - -export const SecretKeyQRCode: AccountSettingsImage = Image; - -export const CopySecretKey: AccountSettingsSubmitButton = Button; - -export const SubmitButton: AccountSettingsSubmitButton = (props) => ( -