Skip to content

Commit

Permalink
Merge pull request #18576 from thiagobrez/two-factor-auth
Browse files Browse the repository at this point in the history
feat: add 2-factor authentication
  • Loading branch information
MonilBhavsar authored May 17, 2023
2 parents a108f42 + 9d68c57 commit 3bee6f9
Show file tree
Hide file tree
Showing 29 changed files with 1,071 additions and 28 deletions.
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
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 @@ -497,10 +499,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 @@ -627,7 +658,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 @@ -642,7 +672,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 @@ -726,7 +755,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 @@ -743,7 +783,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 @@ -496,10 +498,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',
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 @@ -627,7 +659,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 @@ -642,12 +673,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 @@ -726,7 +756,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 @@ -744,7 +785,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
35 changes: 35 additions & 0 deletions src/libs/Navigation/AppNavigator/ModalStackNavigators.js
Original file line number Diff line number Diff line change
Expand Up @@ -598,6 +598,41 @@ const SettingsModalStackNavigator = createModalStackNavigator([
},
name: 'GetAssistance',
},
{
getComponent: () => {
const SettingsTwoFactorAuthIsEnabled = require('../../../pages/settings/Security/TwoFactorAuth/IsEnabledPage').default;
return SettingsTwoFactorAuthIsEnabled;
},
name: 'Settings_TwoFactorAuthIsEnabled',
},
{
getComponent: () => {
const SettingsTwoFactorAuthDisable = require('../../../pages/settings/Security/TwoFactorAuth/DisablePage').default;
return SettingsTwoFactorAuthDisable;
},
name: 'Settings_TwoFactorAuthDisable',
},
{
getComponent: () => {
const SettingsTwoFactorAuthCodes = require('../../../pages/settings/Security/TwoFactorAuth/CodesPage').default;
return SettingsTwoFactorAuthCodes;
},
name: 'Settings_TwoFactorAuthCodes',
},
{
getComponent: () => {
const SettingsTwoFactorAuthVerify = require('../../../pages/settings/Security/TwoFactorAuth/VerifyPage').default;
return SettingsTwoFactorAuthVerify;
},
name: 'Settings_TwoFactorAuthVerify',
},
{
getComponent: () => {
const SettingsTwoFactorAuthSuccess = require('../../../pages/settings/Security/TwoFactorAuth/SuccessPage').default;
return SettingsTwoFactorAuthSuccess;
},
name: 'Settings_TwoFactorAuthSuccess',
},
]);

const EnablePaymentsStackNavigator = createModalStackNavigator([
Expand Down
Loading

0 comments on commit 3bee6f9

Please sign in to comment.