Open moghaddam01 opened 2 years ago
Hi @moghaddam01 !
You are correct, we no longer support the standalone AmplifyTotpSetup
page. To access the Setup TOTP page your users would need to sign up or sign in first. We will then detect if they have TOTP and if it still needs to be setup, we'll show them the Setup TOTP page.
We are looking into adding in more setup pages, in the future. I'll mark this as a feature request.
In the meantime, you maybe better off with using the Amplify JS library to create your own Setup TOTP page, as described here. https://docs.amplify.aws/lib/auth/mfa/q/platform/js/
Then you can customize that page exactly the way you want it. Sorry about the confusion!
It's worth mentioning that you can customize the Setup TOTP page. You just can't use it as a standalone component, or have it be the initialState. https://ui.docs.amplify.aws/react/connected-components/authenticator/customization#update-setup-totp-qr-issuer-and-username
Hi @ErikCH! Thanks for your guidance!
I have implemented the standalone custom setup TOTP page and put the code here, maybe useful for other developers that have same problem or want to upgrade it to v2.
import { CognitoUserAmplify } from '@aws-amplify/ui';
import { Alert, Button, TextField } from '@mui/material';
import { Auth } from 'aws-amplify';
import QRCode from 'qrcode';
import React, { useState } from 'react';
import intl from 'react-intl-universal';
import { makeStyles } from 'tss-react/mui';
interface CustomSetupTOTPProps {
user: CognitoUserAmplify | undefined;
issuer: string;
handleAuthStateChange: () => void;
}
const useStyles = makeStyles()(theme => ({
form: {
width: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: theme.spacing(1.5),
'& input': {
padding: theme.spacing(1),
width: '100%',
},
'& button': {
padding: theme.spacing(2),
width: '100%',
textTransform: 'uppercase',
},
},
}));
export function CustomSetupTOTP(props: CustomSetupTOTPProps) {
const [isLoading, setIsLoading] = useState(true);
const [isVerifyingToken, setIsVerifyingToken] = useState(false);
const [qrCode, setQrCode] = React.useState('');
const [token, setToken] = React.useState('');
const [errorMessage, setErrorMessage] = React.useState('');
const { classes } = useStyles();
const getTotpCode = (
issuer: string,
username: string,
secret: string,
): string =>
encodeURI(
`otpauth://totp/${issuer}:${username}?secret=${secret}&issuer=${issuer}`,
);
const totpUsername = props.user?.getUsername() || '';
const generateQRCode = React.useCallback(
async (currentUser: CognitoUserAmplify): Promise<void> => {
try {
const newSecretKey = await Auth.setupTOTP(currentUser);
const totpCode = getTotpCode(props.issuer, totpUsername, newSecretKey);
const qrCodeImageSource = await QRCode.toDataURL(totpCode);
setQrCode(qrCodeImageSource);
} catch (error) {
console.error(error);
} finally {
setIsLoading(false);
}
},
[props.issuer, totpUsername],
);
const verifyTotpToken = () => {
// After verifying, user will have TOTP account in his TOTP-generating app (like Google Authenticator)
// Use the generated one-time password to verify the setup
setErrorMessage('');
setIsVerifyingToken(true);
Auth.verifyTotpToken(props.user, token)
.then(async () => {
await Auth.setPreferredMFA(props.user, 'TOTP');
props.handleAuthStateChange();
return null;
})
.catch(e => {
console.error(e);
if (/Code mismatch/.test(e.toString())) {
setErrorMessage(intl.get('custom-setup-totp.security-code-mismatch'));
}
})
.finally(() => setIsVerifyingToken(false));
};
React.useEffect(() => {
if (!props.user) {
return;
}
void generateQRCode(props.user);
}, [generateQRCode, props.user]);
const isValidToken = () => {
return /^\d{6}$/gm.test(token);
};
return (
<form onSubmit={() => false} className={classes.form}>
{isLoading && <div>{intl.get('generic.loading')}</div>}
{!isLoading && (
<>
<img
data-amplify-qrcode
src={qrCode}
alt="qr code"
width="228"
height="228"
/>
<div>{intl.get('custom-setup-totp.enter-security-code')}</div>
<TextField
variant="outlined"
onChange={e => {
setToken(e.target.value);
}}
></TextField>
{errorMessage && (
<Alert
variant="filled"
severity="error"
onClose={() => {
setErrorMessage('');
}}
>
{errorMessage}
</Alert>
)}
<Button
size="large"
disabled={!isValidToken() || isVerifyingToken}
color="primary"
variant="contained"
type="submit"
onClick={verifyTotpToken}
>
{isVerifyingToken
? intl.get('custom-setup-totp.verifying-security-code')
: intl.get('custom-setup-totp.verify-security-code')}
</Button>
</>
)}
</form>
);
}
Thanks @moghaddam01 for sharing this! I appreciate it!
I'm trying to upgrade Amplify UI to v2 and since
AmplifyTotpSetup
has been removed from '@aws-amplify/ui-react' package, the question is what is the replacement for that in v2 to use it in ?I want to allow my users to set up their 2FA from within the settings page of my app (i.e. not from the login page itself). For this, I want to show only the TOTP setup view from Amplify-UI - not the login/sign-up/etc view. With Amplify-UI v1, I used the
AmplifyTotpSetup
as a standalone component to achieve this:I already tried these:
AmplifyTotpSetup
)initialState?: 'signIn' | 'signUp' | 'resetPassword'
)Any guide would be appreciated.
Related issue: https://github.com/aws-amplify/amplify-ui/issues/1150