Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add 2-factor authentication #18576

Merged
merged 34 commits into from
May 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
05bccf0
chore: add IsEnabledPage and DisablePage
thiagobrez Apr 28, 2023
cb53f86
chore: add CodesPage and VerifyPage
thiagobrez May 2, 2023
8174644
chore: finish VerifyPage and SuccessPage design
thiagobrez May 4, 2023
6a40c87
chore: finish all UI and connected to API
thiagobrez May 5, 2023
cf8c297
chore: fix codes download on Android
thiagobrez May 8, 2023
fc68b8f
chore: test fireworks animation on SuccessPage
thiagobrez May 8, 2023
ac00e92
chore: cleanup
thiagobrez May 8, 2023
a450dfb
chore: add spanish translations
thiagobrez May 8, 2023
4c59021
chore: replace fireworks svg for animation
thiagobrez May 8, 2023
c6bdb52
chore: replace TextInput with MagicCodeInput
thiagobrez May 8, 2023
9eaba25
chore: UX improvements
thiagobrez May 9, 2023
a2c5cc5
chore: fix keyboard on top of input
thiagobrez May 9, 2023
0cd1361
chore: fix BaseTwoFactorAuthForm component name
thiagobrez May 9, 2023
35e8116
chore: fix conflicts
thiagobrez May 10, 2023
f0e7499
chore: run prettier
thiagobrez May 10, 2023
6c23b16
chore: address pr comments
thiagobrez May 10, 2023
22af6ea
chore: fix incorrect behavior of hooks
thiagobrez May 11, 2023
aaefecf
chore: fix Spanish translations, add toggle API fn
thiagobrez May 11, 2023
2675466
chore: tweak stepCounter to accept text
thiagobrez May 11, 2023
93d8367
chore: refactor route names
thiagobrez May 12, 2023
3e8804c
chore: make disable button dangerous
thiagobrez May 12, 2023
b064454
chore: fix Button used in wrong way
thiagobrez May 12, 2023
465c78a
chore: fix design comments
thiagobrez May 12, 2023
cec6a9a
chore: cleanup
thiagobrez May 12, 2023
28129b4
chore: replace TextFileLink with function
thiagobrez May 15, 2023
717dfde
chore: resolve conflicts
thiagobrez May 15, 2023
8cc3b8b
chore: replace QRCode with QRShare
thiagobrez May 15, 2023
aa7222c
chore: create a reusable QRCode component
thiagobrez May 15, 2023
18e8c39
chore: resolve conflicts
thiagobrez May 15, 2023
1e02ef6
chore: add dark exfy logo to qrcode
thiagobrez May 16, 2023
68c37b3
chore: clear 2fa onyx store if interrupted
thiagobrez May 16, 2023
d774b88
chore: resolve conflicts
thiagobrez May 17, 2023
1c57a73
chore: fix QRCode logo PropType
thiagobrez May 17, 2023
9d68c57
chore: fix prettier
thiagobrez May 17, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added assets/images/expensify-logo-round-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions src/ROUTES.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ export default {
SETTINGS_CONTACT_METHOD_DETAILS: `${SETTINGS_CONTACT_METHODS}/:contactMethod/details`,
getEditContactMethodRoute: (contactMethod) => `${SETTINGS_CONTACT_METHODS}/${encodeURIComponent(contactMethod)}/details`,
SETTINGS_NEW_CONTACT_METHOD: `${SETTINGS_CONTACT_METHODS}/new`,
SETTINGS_2FA_IS_ENABLED: 'settings/security/two-factor-auth/enabled',
SETTINGS_2FA_DISABLE: 'settings/security/two-factor-auth/disable',
SETTINGS_2FA_CODES: 'settings/security/two-factor-auth/codes',
SETTINGS_2FA_VERIFY: 'settings/security/two-factor-auth/verify',
SETTINGS_2FA_SUCCESS: 'settings/security/two-factor-auth/success',
NEW_GROUP: 'new/group',
NEW_CHAT: 'new/chat',
NEW_TASK,
Expand Down
1 change: 1 addition & 0 deletions src/components/HeaderWithCloseButton.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ const propTypes = {
stepCounter: PropTypes.shape({
step: PropTypes.number,
total: PropTypes.number,
text: PropTypes.string,
}),

/** Whether we should show an avatar */
Expand Down
7 changes: 3 additions & 4 deletions src/components/Pressable/PressableWithFeedback.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,9 @@ const PressableWithFeedback = forwardRef((props, ref) => {
setDisabled(props.disabled);
return;
}
onPress
.finally(() => {
setDisabled(props.disabled);
});
onPress.finally(() => {
setDisabled(props.disabled);
});
});
}}
>
Expand Down
63 changes: 63 additions & 0 deletions src/components/QRCode/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import React from 'react';
import QRCodeLibrary from 'react-native-qrcode-svg';
import PropTypes from 'prop-types';
import defaultTheme from '../../styles/themes/default';

const propTypes = {
/**
* The QR code URL
*/
url: PropTypes.string.isRequired,
/**
* The logo which will be displayed in the middle of the QR code.
* Follows `ImageSourcePropType` from react-native.
*/
logo: PropTypes.oneOfType([PropTypes.shape({uri: PropTypes.string}), PropTypes.number]),
/**
* The QRCode size
*/
size: PropTypes.number,
/**
* The QRCode color
*/
color: PropTypes.string,
/**
* The QRCode background color
*/
backgroundColor: PropTypes.string,
/**
* Function to retrieve the internal component ref and be able to call it's
* methods
*/
getRef: PropTypes.func,
};

const defaultProps = {
logo: undefined,
size: 120,
color: defaultTheme.text,
backgroundColor: defaultTheme.highlightBG,
getRef: undefined,
};

function QRCode(props) {
return (
<QRCodeLibrary
getRef={props.getRef}
value={props.url}
size={props.size}
logo={props.logo}
logoBackgroundColor="transparent"
logoSize={props.size * 0.3}
logoBorderRadius={props.size}
backgroundColor={props.backgroundColor}
color={props.color}
/>
);
}

QRCode.displayName = 'QRCode';
QRCode.propTypes = propTypes;
QRCode.defaultProps = defaultProps;

export default QRCode;
13 changes: 4 additions & 9 deletions src/components/QRShare/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import React, {Component} from 'react';
import QrCode from 'react-native-qrcode-svg';
import {View} from 'react-native';
import withLocalize, {withLocalizePropTypes} from '../withLocalize';
import defaultTheme from '../../styles/themes/default';
Expand All @@ -10,6 +9,7 @@ import compose from '../../libs/compose';
import variables from '../../styles/variables';
import ExpensifyWordmark from '../../../assets/images/expensify-wordmark.svg';
import {qrSharePropTypes, qrShareDefaultProps} from './propTypes';
import QRCode from '../QRCode';

const propTypes = {
...qrSharePropTypes,
Expand Down Expand Up @@ -61,16 +61,11 @@ class QRShare extends Component {
/>
</View>

<QrCode
value={this.props.url}
logo={this.props.logo}
<QRCode
getRef={(svg) => (this.svg = svg)}
logoBackgroundColor="transparent"
logoSize={this.state.qrCodeSize * 0.3}
logoBorderRadius={this.state.qrCodeSize}
url={this.props.url}
logo={this.props.logo}
size={this.state.qrCodeSize}
backgroundColor={defaultTheme.highlightBG}
color={defaultTheme.text}
/>

<Text
Expand Down
7 changes: 6 additions & 1 deletion src/components/Section.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ const propTypes = {
/** Customize the Section container */
// eslint-disable-next-line react/forbid-prop-types
containerStyles: PropTypes.arrayOf(PropTypes.object),

/** Customize the Icon container */
// eslint-disable-next-line react/forbid-prop-types
iconContainerStyles: PropTypes.arrayOf(PropTypes.object),
};

const defaultProps = {
Expand All @@ -34,6 +38,7 @@ const defaultProps = {
icon: null,
IconComponent: null,
containerStyles: [],
iconContainerStyles: [],
};

const Section = (props) => {
Expand All @@ -45,7 +50,7 @@ const Section = (props) => {
<View style={[styles.flexShrink1]}>
<Text style={[styles.textHeadline, styles.cardSectionTitle]}>{props.title}</Text>
</View>
<View style={[styles.flexGrow1, styles.flexRow, styles.justifyContentEnd]}>
<View style={[styles.flexGrow1, styles.flexRow, styles.justifyContentEnd, ...props.iconContainerStyles]}>
{Boolean(props.icon) && (
<Icon
src={props.icon}
Expand Down
49 changes: 44 additions & 5 deletions src/languages/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export default {
yes: 'Yes',
no: 'No',
ok: 'OK',
buttonConfirm: 'Got it',
attachment: 'Attachment',
to: 'To',
optional: 'Optional',
Expand All @@ -25,6 +26,7 @@ export default {
zoom: 'Zoom',
password: 'Password',
magicCode: 'Magic code',
twoFactorCode: 'Two factor code',
workspaces: 'Workspaces',
profile: 'Profile',
payments: 'Payments',
Expand Down Expand Up @@ -496,10 +498,39 @@ export default {
newPassword: 'Your password must have at least 8 characters, 1 capital letter, 1 lowercase letter, and 1 number.',
},
},
twoFactorAuth: {
headerTitle: 'Two-factor authentication',
twoFactorAuthEnabled: 'Two-factor authentication enabled',
whatIsTwoFactorAuth: 'Two-factor authentication (2FA) helps keep your account safe. When logging in, you’ll need to enter a code generated by your preferred authenticator app.',
disableTwoFactorAuth: 'Disable two-factor authentication',
disableTwoFactorAuthConfirmation: 'Two-factor authentication keeps your account more secure. Are you sure you want to disable it?',
disabled: 'Two-factor authentication is now disabled',
noAuthenticatorApp: 'You’ll no longer require an authenticator app to log into Expensify.',
stepCodes: 'Recovery codes',
keepCodesSafe: 'Keep these recovery codes safe!',
codesLoseAccess:
'If you lose access to your authenticator app and don’t have these codes, you will lose access to your account. \n\nNote: Setting up two factor authentication will log you out of all other active sessions.',
stepVerify: 'Verify',
scanCode: 'Scan the QR code using your',
authenticatorApp: 'authenticator app',
addKey: 'Or add this secret key to your authenticator app:',
enterCode: 'Then enter the six digit code generated from your authenticator app.',
stepSuccess: 'Finished',
enabled: 'Two-factor authentication is now enabled!',
congrats: 'Congrats, now you’ve got that extra security.',
copyCodes: 'Copy codes',
copy: 'Copy',
disable: 'Disable',
},
twoFactorAuthForm: {
error: {
pleaseFillTwoFactorAuth: 'Please enter your two factor code',
incorrect2fa: 'Incorrect two factor authentication code. Please try again.',
},
},
passwordConfirmationScreen: {
passwordUpdated: 'Password updated!',
allSet: 'You’re all set. Keep your new password safe.',
gotIt: 'Got it',
},
addPayPalMePage: {
enterYourUsernameToGetPaidViaPayPal: 'Get paid back via PayPal.',
Expand Down Expand Up @@ -626,7 +657,6 @@ export default {
validateCodeForm: {
magicCodeNotReceived: "Didn't receive a magic code?",
enterAuthenticatorCode: 'Please enter your authenticator code',
twoFactorCode: 'Two factor code',
requiredWhen2FAEnabled: 'Required when 2FA is enabled',
codeSent: 'Magic code sent!',
error: {
Expand All @@ -641,7 +671,6 @@ export default {
pleaseFillTwoFactorAuth: 'Please enter your two factor code',
enterYourTwoFactorAuthenticationCodeToContinue: 'Enter your two factor authentication code to continue',
forgot: 'Forgot?',
twoFactorCode: 'Two factor code',
requiredWhen2FAEnabled: 'Required when 2FA is enabled',
error: {
incorrectPassword: 'Incorrect password. Please try again.',
Expand Down Expand Up @@ -725,7 +754,18 @@ export default {
setPasswordLinkInvalid: 'This set password link is invalid or has expired. A new one is waiting for you in your email inbox!',
validateAccount: 'Verify account',
},
stepCounter: ({step, total}) => `Step ${step} of ${total}`,
stepCounter: ({step, total, text}) => {
let result = `Step ${step}`;

if (total) {
result = `${result} of ${total}`;
}

if (text) {
result = `${result}: ${text}`;
}
return result;
},
bankAccount: {
accountNumber: 'Account number',
routingNumber: 'Routing number',
Expand All @@ -742,7 +782,6 @@ export default {
'In order to finish setting up your bank account, you must validate your account. Please check your email to validate your account, and return here to finish up!',
hasPhoneLoginError: 'To add a verified bank account please ensure your primary login is a valid email and try again. You can add your phone number as a secondary login.',
hasBeenThrottledError: 'There was an error adding your bank account. Please wait a few minutes and try again.',
buttonConfirm: 'Got it',
error: {
noBankAccountAvailable: 'Sorry, no bank account is available',
noBankAccountSelected: 'Please choose an account',
Expand Down
52 changes: 46 additions & 6 deletions src/languages/es.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export default {
yes: 'Sí',
no: 'No',
ok: 'OK',
buttonConfirm: 'Ok, entendido',
attachment: 'Archivo adjunto',
to: 'A',
optional: 'Opcional',
Expand All @@ -24,6 +25,7 @@ export default {
zoom: 'Zoom',
password: 'Contraseña',
magicCode: 'Código mágico',
twoFactorCode: 'Autenticación de dos factores',
workspaces: 'Espacios de trabajo',
profile: 'Perfil',
payments: 'Pagos',
Expand Down Expand Up @@ -495,10 +497,40 @@ export default {
newPassword: 'Su contraseña debe tener al menos 8 caracteres, 1 letra mayúscula, 1 letra minúscula y 1 número.',
},
},
twoFactorAuth: {
headerTitle: 'Autenticación de dos factores',
twoFactorAuthEnabled: 'Autenticación de dos factores habilitada',
whatIsTwoFactorAuth:
'La autenticación de dos factores (2FA) ayuda a mantener tu cuenta segura. Al iniciar sesión, deberás ingresar un código generado por tu aplicación de autenticación preferida.',
disableTwoFactorAuth: 'Deshabilitar la autenticación de dos factores',
disableTwoFactorAuthConfirmation: 'La autenticación de dos factores mantiene tu cuenta más segura. ¿Estás seguro de que quieres desactivarla?',
disabled: 'La autenticación de dos factores ahora está deshabilitada',
noAuthenticatorApp: 'Ya no necesitarás una aplicación de autenticación para iniciar sesión en Expensify.',
stepCodes: 'Códigos de recuperación',
keepCodesSafe: '¡Guarda los códigos de recuperación en un lugar seguro!',
codesLoseAccess:
'Si pierdes el acceso a tu aplicación de autenticación y no tienes estos códigos, perderás el acceso a tu cuenta. \n\nNota: Configurar la autenticación de dos factores cerrará la sesión de todas las demás sesiones activas.',
stepVerify: 'Verificar',
scanCode: 'Escanea el código QR usando tu',
authenticatorApp: 'aplicación de autenticación',
addKey: 'O agrega esta clave secreta a su aplicación de autenticación:',
enterCode: 'Luego ingresa el código de seis dígitos generado por su aplicación de autenticación.',
stepSuccess: 'Finalizado',
enabled: '¡La autenticación de dos factores ahora está habilitada!',
congrats: 'Felicidades, ahora tienes esa seguridad adicional.',
copyCodes: 'Copiar códigos',
copy: 'Copiar',
disable: 'Deshabilitar',
},
twoFactorAuthForm: {
error: {
pleaseFillTwoFactorAuth: 'Por favor, introduce tu código 2 factores',
thiagobrez marked this conversation as resolved.
Show resolved Hide resolved
incorrect2fa: 'Código de autenticación de dos factores incorrecto. Por favor, inténtalo de nuevo',
},
},
passwordConfirmationScreen: {
passwordUpdated: 'Contraseña actualizada!',
allSet: 'Todo está listo. Guarda tu contraseña en un lugar seguro.',
gotIt: 'Ok, entendido',
},
addPayPalMePage: {
enterYourUsernameToGetPaidViaPayPal: 'Recibe pagos vía PayPal.',
Expand Down Expand Up @@ -626,7 +658,6 @@ export default {
validateCodeForm: {
magicCodeNotReceived: '¿No recibiste un código mágico?',
enterAuthenticatorCode: 'Por favor, introduce el código de autenticador',
twoFactorCode: 'Autenticación de 2 factores',
requiredWhen2FAEnabled: 'Obligatorio cuando A2F está habilitado',
codeSent: '¡Código mágico enviado!',
error: {
Expand All @@ -641,12 +672,11 @@ export default {
pleaseFillTwoFactorAuth: 'Por favor, introduce tu código 2 factores',
enterYourTwoFactorAuthenticationCodeToContinue: 'Introduce el código de autenticación de dos factores para continuar',
forgot: '¿Has olvidado la contraseña?',
twoFactorCode: 'Autenticación de 2 factores',
requiredWhen2FAEnabled: 'Obligatorio cuando A2F está habilitado',
error: {
incorrectPassword: 'Contraseña incorrecta. Por favor, inténtalo de nuevo.',
incorrectLoginOrPassword: 'Usuario o contraseña incorrectos. Por favor, inténtalo de nuevo',
incorrect2fa: 'Código de autenticación de 2 factores incorrecto. Por favor, inténtalo de nuevo',
incorrect2fa: 'Código de autenticación de dos factores incorrecto. Por favor, inténtalo de nuevo',
twoFactorAuthenticationEnabled: 'Tienes autenticación de 2 factores activada en esta cuenta. Por favor, conéctate usando tu email o número de teléfono',
invalidLoginOrPassword: 'Usuario o clave incorrectos. Por favor, inténtalo de nuevo o restablece la contraseña',
unableToResetPassword:
Expand Down Expand Up @@ -725,7 +755,18 @@ export default {
setPasswordLinkInvalid: 'El enlace para configurar tu contraseña ha expirado. Te hemos enviado un nuevo enlace a tu correo.',
validateAccount: 'Verificar cuenta',
},
stepCounter: ({step, total}) => `Paso ${step} de ${total}`,
stepCounter: ({step, total, text}) => {
let result = `Paso ${step}`;

if (total) {
result = `${result} de ${total}`;
}

if (text) {
result = `${result}: ${text}`;
}
return result;
},
bankAccount: {
accountNumber: 'Número de cuenta',
routingNumber: 'Número de ruta',
Expand All @@ -743,7 +784,6 @@ export default {
hasPhoneLoginError:
'Para agregar una cuenta bancaria verificada, asegúrate de que tu nombre de usuario principal sea un correo electrónico válido y vuelve a intentarlo. Puedes agregar tu número de teléfono como nombre de usuario secundario.',
hasBeenThrottledError: 'Se produjo un error al intentar agregar tu cuenta bancaria. Por favor, espera unos minutos e inténtalo de nuevo.',
buttonConfirm: 'OK',
error: {
noBankAccountAvailable: 'Lo sentimos, no hay ninguna cuenta bancaria disponible',
noBankAccountSelected: 'Por favor, elige una cuenta bancaria',
Expand Down
Loading