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 13 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
6 changes: 3 additions & 3 deletions ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1014,7 +1014,7 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
Airship: c70eed50e429f97f5adb285423c7291fb7a032ae
AirshipFrameworkProxy: 2eefb77bb77b5120b0f48814b0d44439aa3ad415
boost: a7c83b31436843459a1961bfd74b96033dc77234
boost: 57d2868c099736d80fcd648bf211b4431e51a558
CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99
DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54
FBLazyVector: ff54429f0110d3c722630a98096ba689c39f6d5f
Expand Down Expand Up @@ -1057,7 +1057,7 @@ SPEC CHECKSUMS:
Permission-LocationWhenInUse: 3ba99e45c852763f730eabecec2870c2382b7bd4
Plaid: 7d340abeadb46c7aa1a91f896c5b22395a31fcf2
PromisesObjC: 09985d6d70fbe7878040aa746d78236e6946d2ef
RCT-Folly: 0080d0a6ebf2577475bda044aa59e2ca1f909cda
RCT-Folly: 424b8c9a7a0b9ab2886ffe9c3b041ef628fd4fb1
RCTRequired: e9e7b8b45aa9bedb2fdad71740adf07a7265b9be
RCTTypeSafety: 9ae0e9206625e995f0df4d5b9ddc94411929fb30
React: a71c8e1380f07e01de721ccd52bcf9c03e81867d
Expand Down Expand Up @@ -1129,4 +1129,4 @@ SPEC CHECKSUMS:

PODFILE CHECKSUM: cd132e281e9e3d7e6ec2c99c08e6ec32b37886f8

COCOAPODS: 1.11.3
COCOAPODS: 1.12.0
38 changes: 38 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@
"react-native-webview": "^11.17.2",
"react-pdf": "5.7.2",
"react-plaid-link": "3.3.2",
"react-qr-code": "^2.0.11",
"react-web-config": "^1.0.0",
"save": "^2.4.0",
"semver": "^7.3.8",
Expand Down
5 changes: 5 additions & 0 deletions src/ROUTES.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,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_TWO_FACTOR_IS_ENABLED: 'settings/security/two-factor-auth/enabled',
SETTINGS_TWO_FACTOR_DISABLE: 'settings/security/two-factor-auth/disable',
SETTINGS_TWO_FACTOR_CODES: 'settings/security/two-factor-auth/codes',
SETTINGS_TWO_FACTOR_VERIFY: 'settings/security/two-factor-auth/verify',
SETTINGS_TWO_FACTOR_SUCCESS: 'settings/security/two-factor-auth/success',
NEW_GROUP: 'new/group',
NEW_CHAT: 'new/chat',
NEW_TASK,
Expand Down
49 changes: 49 additions & 0 deletions src/components/TextFileLink/index.android.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import PropTypes from 'prop-types';
import RNFetchBlob from 'react-native-blob-util';
import * as FileUtils from '../../libs/fileDownload/FileUtils';

const propTypes = {
fileName: PropTypes.string,
textContent: PropTypes.string,
children: PropTypes.func,
};

const defaultProps = {
fileName: '',
textContent: '',
children: () => {},
};

const TextFileLink = (props) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at the logic, I wonder why this is a functional component that returns undefined, name of the function relates to Text Link but it's not displaying any UI which is misleading. Shouldn't it be just a simple function?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree. What do you think of renaming it to ButtonFileLink and making it always return a Button?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with the thoughts - a functional component not rendering anything looks weird.
We can keep it is a simple function. I like that idea, but not sure where we'll have that function.
Making a ButtonLink component also looks like a good idea, but I wonder, we'll then be limited to have download functionality in other components.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we go with the function approach, we could probably do something similar to libs/fileDownload because we will need different implementations for web, ios and android. And maybe call it libs/localFileDownload, since this is only for local files. wdyt?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replaced TextFileLink with a simple function on /libs/localFileDownload

const downloadFile = () => {
const dir = RNFetchBlob.fs.dirs.DocumentDir;
const path = `${dir}/${props.fileName}.txt`;

RNFetchBlob.fs.writeFile(path, props.textContent, 'utf8')
.then(() => {
RNFetchBlob.MediaCollection.copyToMediaStore({
name: props.fileName,
parentFolder: '', // subdirectory in the Media Store, empty goes to 'Downloads'
mimeType: 'text/plain',
},
'Download',
path)
.then(() => {
FileUtils.showSuccessAlert();
})
.catch(() => {
FileUtils.showGeneralErrorAlert();
})
.finally(() => {
RNFetchBlob.fs.unlink(path);
});
});
};

return props.children(downloadFile);
};

TextFileLink.defaultProps = defaultProps;
TextFileLink.propTypes = propTypes;
TextFileLink.displayName = 'TextFileLink';
export default TextFileLink;
41 changes: 41 additions & 0 deletions src/components/TextFileLink/index.ios.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import {Share} from 'react-native';
import PropTypes from 'prop-types';
import RNFetchBlob from 'react-native-blob-util';
import * as FileUtils from '../../libs/fileDownload/FileUtils';

const propTypes = {
fileName: PropTypes.string,
textContent: PropTypes.string,
children: PropTypes.func,
};

const defaultProps = {
fileName: '',
textContent: '',
children: () => {},
};

const TextFileLink = (props) => {
const downloadFile = () => {
const dir = RNFetchBlob.fs.dirs.DocumentDir;
const path = `${dir}/${props.fileName}.txt`;

RNFetchBlob.fs.writeFile(path, props.textContent, 'utf8')
.then(() => {
Share.share({url: path, title: props.fileName})
.catch(() => {
FileUtils.showGeneralErrorAlert();
})
.finally(() => {
RNFetchBlob.fs.unlink(path);
});
});
};

return props.children(downloadFile);
};

TextFileLink.defaultProps = defaultProps;
TextFileLink.propTypes = propTypes;
TextFileLink.displayName = 'TextFileLink';
export default TextFileLink;
31 changes: 31 additions & 0 deletions src/components/TextFileLink/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import PropTypes from 'prop-types';

const propTypes = {
fileName: PropTypes.string,
textContent: PropTypes.string,
children: PropTypes.func,
};

const defaultProps = {
fileName: '',
textContent: '',
children: () => {},
};

const TextFileLink = (props) => {
const downloadFile = () => {
const blob = new Blob([props.textContent], {type: 'text/plain'});
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.download = `${props.fileName}.txt`;
link.href = url;
link.click();
};

return props.children(downloadFile);
};

TextFileLink.defaultProps = defaultProps;
TextFileLink.propTypes = propTypes;
TextFileLink.displayName = 'TextFileLink';
export default TextFileLink;
33 changes: 29 additions & 4 deletions src/languages/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export default {
yes: 'Yes',
no: 'No',
ok: 'OK',
buttonConfirm: 'Got it',
attachment: 'Attachment',
to: 'To',
optional: 'Optional',
Expand All @@ -24,6 +25,7 @@ export default {
zoom: 'Zoom',
password: 'Password',
magicCode: 'Magic code',
twoFactorCode: 'Two factor code',
workspaces: 'Workspaces',
profile: 'Profile',
payments: 'Payments',
Expand Down Expand Up @@ -477,10 +479,36 @@ 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: 'Step 1: 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: 'Step 2: 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: 'Step 3: Finished',
enabled: 'Two-factor authentication is now enabled!',
congrats: 'Congrats, now you’ve got that extra security.',

},
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 @@ -607,7 +635,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 @@ -622,7 +649,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 @@ -721,7 +747,6 @@ export default {
validateAccountError: '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
32 changes: 28 additions & 4 deletions src/languages/es.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export default {
yes: 'Sí',
no: 'No',
ok: 'OK',
buttonConfirm: 'Ok, entendido',
attachment: 'Archivo adjunto',
to: 'A',
optional: 'Opcional',
Expand All @@ -23,6 +24,7 @@ export default {
zoom: 'Zoom',
password: 'Contraseña',
magicCode: 'Código mágico',
twoFactorCode: 'Autenticación de 2 factores',
workspaces: 'Espacios de trabajo',
profile: 'Perfil',
payments: 'Pagos',
Expand Down Expand Up @@ -476,10 +478,35 @@ 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 2 factores',
twoFactorAuthEnabled: 'Autenticación de dos factores habilitada',
whatIsTwoFactorAuth: 'La autenticación de dos factores (2FA) ayuda a mantener su cuenta segura. Al iniciar sesión, deberá ingresar un código generado por su aplicación de autenticación preferida.',
disableTwoFactorAuth: 'Deshabilitar la autenticación de dos factores',
disableTwoFactorAuthConfirmation: 'La autenticación de dos factores mantiene su cuenta más segura. ¿Estás seguro de que quieres desactivarlo?',
disabled: 'La autenticación de dos factores ahora está deshabilitada',
noAuthenticatorApp: 'Ya no necesitará una aplicación de autenticación para iniciar sesión en Expensify.',
stepCodes: 'Paso 1: Códigos de recuperación',
keepCodesSafe: '¡Mantenga estos códigos de recuperación seguros!',
codesLoseAccess: 'Si pierde el acceso a su aplicación de autenticación y no tiene estos códigos, perderá el acceso a su cuenta. \n\nNota: Configurar la autenticación de dos factores cerrará la sesión de todas las demás sesiones activas.',
stepVerify: 'Paso 2: Verificar',
scanCode: 'Escanea el código QR con tu',
authenticatorApp: 'aplicación de autenticación',
addKey: 'O agregue esta clave secreta a su aplicación de autenticación:',
enterCode: 'Luego ingrese el código de seis dígitos generado desde su aplicación de autenticación.',
stepSuccess: 'Paso 3: Terminado',
enabled: '¡La autenticación de dos factores ahora está habilitada!',
congrats: 'Felicidades, ahora tienes esa seguridad adicional.',
},
twoFactorAuthForm: {
error: {
pleaseFillTwoFactorAuth: 'Por favor, introduce tu código 2 factores',
incorrect2fa: 'Código de autenticación de 2 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 @@ -606,7 +633,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 @@ -621,7 +647,6 @@ 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.',
Expand Down Expand Up @@ -720,7 +745,6 @@ export default {
validateAccountError: 'Para terminar de configurar tu cuenta bancaria, debes validar tu cuenta de Expensify. Por favor, revisa tu correo electrónico para validar tu cuenta y vuelve aquí para continuar.',
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