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

[Wave Collect][Xero] Enforce 2FA for xero #44059

Merged
merged 44 commits into from
Jul 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
6532961
show confirm modal if 2fa isn't enabled
rushatgabhane Jun 19, 2024
d68cb2c
add dismiss
rushatgabhane Jun 19, 2024
8329d45
add require 2fa modal
rushatgabhane Jun 19, 2024
2481d2a
add blue bg
rushatgabhane Jun 19, 2024
0e7cc39
cleanup and add margin
rushatgabhane Jun 20, 2024
0a19025
more margin
rushatgabhane Jun 20, 2024
f710a3e
fix padding and margin for desktop and mobile
rushatgabhane Jun 20, 2024
cfcc93b
navigate to 2fa page
rushatgabhane Jun 20, 2024
fb58fad
add forward to param
rushatgabhane Jun 20, 2024
c844340
add forward to type
rushatgabhane Jun 20, 2024
5799a37
navigate to forward to
rushatgabhane Jun 20, 2024
8cf4612
Revert "navigate to forward to"
rushatgabhane Jun 20, 2024
3c3a4d4
Revert "add forward to param"
rushatgabhane Jun 20, 2024
ccd36a5
add forward to param
rushatgabhane Jun 20, 2024
04e5d53
add forward to type for 2fa
rushatgabhane Jun 20, 2024
45c9812
add forward to link for xero
rushatgabhane Jun 20, 2024
d527edf
pass forward to as a prop from url
rushatgabhane Jun 20, 2024
a338137
navigate to the forwarded url in new tab
rushatgabhane Jun 20, 2024
e0dc00b
fix storybook
rushatgabhane Jun 20, 2024
96ffb8e
rm unused imports
rushatgabhane Jun 20, 2024
1f4e9f1
rm unused imports
rushatgabhane Jun 20, 2024
beae97b
add desc for 2fa
rushatgabhane Jun 20, 2024
fe7efba
add needsTwoFactorAuthSetup to account
rushatgabhane Jun 20, 2024
9873c32
rename desc
rushatgabhane Jun 20, 2024
b7feae6
show 2fa modal if 2fa is required for acc
rushatgabhane Jun 20, 2024
e1f5e13
prettay that thing
rushatgabhane Jun 20, 2024
8d9b210
check for 2fa on native
rushatgabhane Jun 20, 2024
6d5c025
add bottom padding on native
rushatgabhane Jun 20, 2024
8a6ac29
add bottom padding on native
rushatgabhane Jun 20, 2024
4e02d3b
Update src/languages/en.ts
rushatgabhane Jun 23, 2024
9fcd2f0
Update src/languages/es.ts
rushatgabhane Jun 23, 2024
9aa057d
Merge branch 'main' into 2fa
rushatgabhane Jun 23, 2024
bd51726
fix merge conflict
rushatgabhane Jul 6, 2024
d3819a5
dismiss modal on 2fa
rushatgabhane Jul 6, 2024
6f46e17
dismiss modal and fix unterminated strings
rushatgabhane Jul 6, 2024
626c154
reconnect app on 2fa complete
rushatgabhane Jul 7, 2024
a7bdb92
Merge branch 'main' of github.com:rushatgabhane/exfy into 2fa
rushatgabhane Jul 10, 2024
02670d3
run prettier
rushatgabhane Jul 10, 2024
74d03d0
fix merge
rushatgabhane Jul 11, 2024
859cf4a
Merge branch 'main' into 2fa
rushatgabhane Jul 14, 2024
57e752e
add height for illustration
rushatgabhane Jul 14, 2024
2290a74
default 2fa to false
rushatgabhane Jul 14, 2024
c4da238
Merge branch 'main' of github.com:rushatgabhane/exfy into 2fa
rushatgabhane Jul 16, 2024
dcddaa2
preserve personal details
rushatgabhane Jul 16, 2024
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
5 changes: 5 additions & 0 deletions .storybook/webpack.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,11 @@ const webpackConfig = ({config}: {config: Configuration}) => {
}),
);

config.module.rules?.push({
test: /\.lottie$/,
type: 'asset/resource',
});

return config;
};

Expand Down
24 changes: 23 additions & 1 deletion src/Expensify.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ import React, {useCallback, useEffect, useLayoutEffect, useMemo, useRef, useStat
import type {NativeEventSubscription} from 'react-native';
import {AppState, Linking, NativeModules} from 'react-native';
import type {OnyxEntry} from 'react-native-onyx';
import Onyx, {withOnyx} from 'react-native-onyx';
import Onyx, {useOnyx, withOnyx} from 'react-native-onyx';
import ConfirmModal from './components/ConfirmModal';
import DeeplinkWrapper from './components/DeeplinkWrapper';
import EmojiPicker from './components/EmojiPicker/EmojiPicker';
import FocusModeNotification from './components/FocusModeNotification';
import GrowlNotification from './components/GrowlNotification';
import RequireTwoFactorAuthenticationModal from './components/RequireTwoFactorAuthenticationModal';
import AppleAuthWrapper from './components/SignInButtons/AppleAuthWrapper';
import SplashScreenHider from './components/SplashScreenHider';
import UpdateAppModal from './components/UpdateAppModal';
Expand Down Expand Up @@ -37,6 +38,7 @@ import ONYXKEYS from './ONYXKEYS';
import PopoverReportActionContextMenu from './pages/home/report/ContextMenu/PopoverReportActionContextMenu';
import * as ReportActionContextMenu from './pages/home/report/ContextMenu/ReportActionContextMenu';
import type {Route} from './ROUTES';
import ROUTES from './ROUTES';
import type {ScreenShareRequest, Session} from './types/onyx';

Onyx.registerLogger(({level, message}) => {
Expand Down Expand Up @@ -101,6 +103,16 @@ function Expensify({
const [isSplashHidden, setIsSplashHidden] = useState(false);
const [hasAttemptedToOpenPublicRoom, setAttemptedToOpenPublicRoom] = useState(false);
const {translate} = useLocalize();
const [account] = useOnyx(ONYXKEYS.ACCOUNT);
const [shouldShowRequire2FAModal, setShouldShowRequire2FAModal] = useState(false);

useEffect(() => {
if (!account?.needsTwoFactorAuthSetup || account.requiresTwoFactorAuth) {
return;
}
setShouldShowRequire2FAModal(true);
}, [account?.needsTwoFactorAuthSetup, account?.requiresTwoFactorAuth]);

const [initialUrl, setInitialUrl] = useState<string | null>(null);

useEffect(() => {
Expand Down Expand Up @@ -253,6 +265,16 @@ function Expensify({
/>
) : null}
{focusModeNotification ? <FocusModeNotification /> : null}
{shouldShowRequire2FAModal ? (
<RequireTwoFactorAuthenticationModal
onSubmit={() => {
setShouldShowRequire2FAModal(false);
Navigation.navigate(ROUTES.SETTINGS_2FA.getRoute(ROUTES.HOME));
}}
isVisible
description={translate('twoFactorAuth.twoFactorAuthIsRequiredForAdminsDescription')}
/>
) : null}
</>
)}

Expand Down
3 changes: 2 additions & 1 deletion src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,8 @@ const ROUTES = {
},
SETTINGS_2FA: {
route: 'settings/security/two-factor-auth',
getRoute: (backTo?: string) => getUrlWithBackToParam('settings/security/two-factor-auth', backTo),
getRoute: (backTo?: string, forwardTo?: string) =>
getUrlWithBackToParam(forwardTo ? `settings/security/two-factor-auth?forwardTo=${encodeURIComponent(forwardTo)}` : 'settings/security/two-factor-auth', backTo),
},
SETTINGS_STATUS: 'settings/profile/status',

Expand Down
26 changes: 25 additions & 1 deletion src/components/ConnectToXeroButton/index.native.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
import React, {useRef, useState} from 'react';
import type {OnyxEntry} from 'react-native-onyx';
import {withOnyx} from 'react-native-onyx';
import {useOnyx, withOnyx} from 'react-native-onyx';
import {WebView} from 'react-native-webview';
import AccountingConnectionConfirmationModal from '@components/AccountingConnectionConfirmationModal';
import FullPageOfflineBlockingView from '@components/BlockingViews/FullPageOfflineBlockingView';
import Button from '@components/Button';
import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import Modal from '@components/Modal';
import RequireTwoFactorAuthenticationModal from '@components/RequireTwoFactorAuthenticationModal';
import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
import useThemeStyles from '@hooks/useThemeStyles';
import {removePolicyConnection} from '@libs/actions/connections';
import {getXeroSetupLink} from '@libs/actions/connections/ConnectToXero';
import getUAForWebView from '@libs/getUAForWebView';
import Navigation from '@libs/Navigation/Navigation';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type {Session} from '@src/types/onyx';
import type {ConnectToXeroButtonProps} from './types';

Expand All @@ -33,13 +36,22 @@ function ConnectToXeroButton({policyID, session, shouldDisconnectIntegrationBefo
const authToken = session?.authToken ?? null;
const {isOffline} = useNetwork();

const [account] = useOnyx(ONYXKEYS.ACCOUNT);
const is2FAEnabled = account?.requiresTwoFactorAuth ?? false;

const renderLoading = () => <FullScreenLoadingIndicator />;
const [isDisconnectModalOpen, setIsDisconnectModalOpen] = useState(false);
const [isRequire2FAModalOpen, setIsRequire2FAModalOpen] = useState(false);

return (
<>
<Button
onPress={() => {
if (!is2FAEnabled) {
setIsRequire2FAModalOpen(true);
return;
}

if (shouldDisconnectIntegrationBeforeConnecting && integrationToDisconnect) {
setIsDisconnectModalOpen(true);
return;
Expand All @@ -62,6 +74,18 @@ function ConnectToXeroButton({policyID, session, shouldDisconnectIntegrationBefo
onCancel={() => setIsDisconnectModalOpen(false)}
/>
)}
{isRequire2FAModalOpen && (
<RequireTwoFactorAuthenticationModal
onSubmit={() => {
setIsRequire2FAModalOpen(false);
Navigation.dismissModal();
Navigation.navigate(ROUTES.SETTINGS_2FA.getRoute(ROUTES.POLICY_ACCOUNTING.getRoute(policyID), getXeroSetupLink(policyID)));
}}
onCancel={() => setIsRequire2FAModalOpen(false)}
isVisible
description={translate('twoFactorAuth.twoFactorAuthIsRequiredDescription')}
/>
)}
<Modal
onClose={() => setWebViewOpen(false)}
fullscreen
Expand Down
26 changes: 26 additions & 0 deletions src/components/ConnectToXeroButton/index.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
import React, {useState} from 'react';
import {useOnyx} from 'react-native-onyx';
import AccountingConnectionConfirmationModal from '@components/AccountingConnectionConfirmationModal';
import Button from '@components/Button';
import RequireTwoFactorAuthenticationModal from '@components/RequireTwoFactorAuthenticationModal';
import useEnvironment from '@hooks/useEnvironment';
import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
import useThemeStyles from '@hooks/useThemeStyles';
import {removePolicyConnection} from '@libs/actions/connections';
import {getXeroSetupLink} from '@libs/actions/connections/ConnectToXero';
import Navigation from '@libs/Navigation/Navigation';
import * as Link from '@userActions/Link';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type {ConnectToXeroButtonProps} from './types';

function ConnectToXeroButton({policyID, shouldDisconnectIntegrationBeforeConnecting, integrationToDisconnect}: ConnectToXeroButtonProps) {
Expand All @@ -17,12 +22,21 @@ function ConnectToXeroButton({policyID, shouldDisconnectIntegrationBeforeConnect
const {environmentURL} = useEnvironment();
const {isOffline} = useNetwork();

const [account] = useOnyx(ONYXKEYS.ACCOUNT);
const is2FAEnabled = account?.requiresTwoFactorAuth;

const [isDisconnectModalOpen, setIsDisconnectModalOpen] = useState(false);
const [isRequire2FAModalOpen, setIsRequire2FAModalOpen] = useState(false);

return (
<>
<Button
onPress={() => {
if (!is2FAEnabled) {
setIsRequire2FAModalOpen(true);
return;
}

if (shouldDisconnectIntegrationBeforeConnecting && integrationToDisconnect) {
setIsDisconnectModalOpen(true);
return;
Expand All @@ -45,6 +59,18 @@ function ConnectToXeroButton({policyID, shouldDisconnectIntegrationBeforeConnect
onCancel={() => setIsDisconnectModalOpen(false)}
/>
)}
{isRequire2FAModalOpen && (
<RequireTwoFactorAuthenticationModal
onSubmit={() => {
setIsRequire2FAModalOpen(false);
Navigation.dismissModal();
Navigation.navigate(ROUTES.SETTINGS_2FA.getRoute(ROUTES.POLICY_ACCOUNTING.getRoute(policyID), getXeroSetupLink(policyID)));
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
Navigation.navigate(ROUTES.SETTINGS_2FA.getRoute(ROUTES.POLICY_ACCOUNTING.getRoute(policyID), getXeroSetupLink(policyID)));
Navigation.dismissModal(); Navigation.navigate(ROUTES.SETTINGS_2FA.getRoute(ROUTES.POLICY_ACCOUNTING.getRoute(policyID), getXeroSetupLink(policyID)));

I think for narrow layout, another FullScreenNavigator is being pushed here because the top navigator is RightHandModalNavigator. Dismissing RHP before navigating fixes it. Same for index.native.tsx.

Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Contributor

Choose a reason for hiding this comment

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

Not convinced by this? @rushatgabhane

Copy link
Member Author

Choose a reason for hiding this comment

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

@c3024 sorry, let me give this a try

Copy link
Member Author

@rushatgabhane rushatgabhane Jul 7, 2024

Choose a reason for hiding this comment

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

this works well! thank you

}}
onCancel={() => setIsRequire2FAModalOpen(false)}
isVisible
description={translate('twoFactorAuth.twoFactorAuthIsRequiredDescription')}
/>
)}
</>
);
}
Expand Down
91 changes: 91 additions & 0 deletions src/components/RequireTwoFactorAuthenticationModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import React from 'react';
import {View} from 'react-native';
import useLocalize from '@hooks/useLocalize';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useStyleUtils from '@hooks/useStyleUtils';
import useThemeStyles from '@hooks/useThemeStyles';
import CONST from '@src/CONST';
import Button from './Button';
import Lottie from './Lottie';
import LottieAnimations from './LottieAnimations';
import Modal from './Modal';
import SafeAreaConsumer from './SafeAreaConsumer';
import Text from './Text';

type RequireTwoFactorAuthenticationModalProps = {
/** A callback to call when the form has been submitted */
onSubmit: () => void;

/** A callback to call when the form has been closed */
onCancel?: () => void;

/** Modal visibility */
isVisible: boolean;

/** Describe what is showing */
description: string;

/**
* Whether the modal should enable the new focus manager.
* We are attempting to migrate to a new refocus manager, adding this property for gradual migration.
* */
shouldEnableNewFocusManagement?: boolean;
};

function RequireTwoFactorAuthenticationModal({onCancel = () => {}, description, isVisible, onSubmit, shouldEnableNewFocusManagement}: RequireTwoFactorAuthenticationModalProps) {
const {shouldUseNarrowLayout} = useResponsiveLayout();
const styles = useThemeStyles();
const {translate} = useLocalize();
const StyleUtils = useStyleUtils();

return (
<SafeAreaConsumer>
{({safeAreaPaddingBottomStyle}) => (
<Modal
onClose={onCancel}
isVisible={isVisible}
type={shouldUseNarrowLayout ? CONST.MODAL.MODAL_TYPE.BOTTOM_DOCKED : CONST.MODAL.MODAL_TYPE.CONFIRM}
innerContainerStyle={{...styles.pb5, ...styles.pt3, ...styles.boxShadowNone}}
shouldEnableNewFocusManagement={shouldEnableNewFocusManagement}
>
<View style={safeAreaPaddingBottomStyle}>
<View
style={[
styles.mh3,
styles.br3,
styles.cardSectionIllustration,
styles.alignItemsCenter,
StyleUtils.getBackgroundColorStyle(LottieAnimations.Safe.backgroundColor),
]}
>
<Lottie
source={LottieAnimations.Safe}
style={styles.h100}
webStyle={styles.h100}
autoPlay
loop
/>
</View>
<View style={[styles.mt5, styles.mh5]}>
<View style={[styles.gap2, styles.mb10]}>
<Text style={[styles.textHeadlineH1]}>{translate('twoFactorAuth.pleaseEnableTwoFactorAuth')}</Text>
<Text style={styles.textSupporting}>{description}</Text>
</View>
<Button
large
success
pressOnEnter
onPress={onSubmit}
text={translate('twoFactorAuth.enableTwoFactorAuth')}
/>
</View>
</View>
</Modal>
)}
</SafeAreaConsumer>
);
}

RequireTwoFactorAuthenticationModal.displayName = 'RequireTwoFactorAuthenticationModal';

export default RequireTwoFactorAuthenticationModal;
5 changes: 5 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1068,6 +1068,11 @@ export default {
enabled: 'Two-factor authentication is now enabled!',
congrats: 'Congrats, now you’ve got that extra security.',
copy: 'Copy',
disable: 'Disable',
enableTwoFactorAuth: 'Enable two-factor authentication',
pleaseEnableTwoFactorAuth: 'Please enable two-factor authentication.',
twoFactorAuthIsRequiredDescription: 'Two-factor authentication is required for connecting to Xero. Please enable two-factor authentication to continue.',
twoFactorAuthIsRequiredForAdminsDescription: 'Two-factor authentication is required for Xero workspace admins. Please enable two-factor authentication to continue.',
},
recoveryCodeForm: {
error: {
Expand Down
6 changes: 6 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1076,6 +1076,12 @@ export default {
enabled: '¡La autenticación de dos factores está ahora habilitada!',
congrats: 'Felicidades, ahora tienes esa seguridad adicional.',
copy: 'Copiar',
disable: 'Deshabilitar',
enableTwoFactorAuth: 'Activar la autenticación de dos factores',
pleaseEnableTwoFactorAuth: 'Activa la autenticación de dos factores.',
twoFactorAuthIsRequiredDescription: 'La autenticación de dos factores es necesaria para conectarse a Xero. Activa la autenticación de dos factores para continuar.',
twoFactorAuthIsRequiredForAdminsDescription:
'La autenticación de dos factores es necesaria para los administradores del área de trabajo de Xero. Activa la autenticación de dos factores para continuar.',
},
recoveryCodeForm: {
error: {
Expand Down
4 changes: 2 additions & 2 deletions src/libs/API/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,6 @@ const WRITE_COMMANDS = {
UNLINK_LOGIN: 'UnlinkLogin',
ENABLE_TWO_FACTOR_AUTH: 'EnableTwoFactorAuth',
DISABLE_TWO_FACTOR_AUTH: 'DisableTwoFactorAuth',
TWO_FACTOR_AUTH_VALIDATE: 'TwoFactorAuth_Validate',
ADD_COMMENT: 'AddComment',
ADD_ATTACHMENT: 'AddAttachment',
ADD_TEXT_AND_ATTACHMENT: 'AddTextAndAttachment',
Expand Down Expand Up @@ -377,7 +376,6 @@ type WriteCommandParameters = {
[WRITE_COMMANDS.UNLINK_LOGIN]: Parameters.UnlinkLoginParams;
[WRITE_COMMANDS.ENABLE_TWO_FACTOR_AUTH]: null;
[WRITE_COMMANDS.DISABLE_TWO_FACTOR_AUTH]: null;
[WRITE_COMMANDS.TWO_FACTOR_AUTH_VALIDATE]: Parameters.ValidateTwoFactorAuthParams;
[WRITE_COMMANDS.ADD_COMMENT]: Parameters.AddCommentOrAttachementParams;
[WRITE_COMMANDS.ADD_ATTACHMENT]: Parameters.AddCommentOrAttachementParams;
[WRITE_COMMANDS.ADD_TEXT_AND_ATTACHMENT]: Parameters.AddCommentOrAttachementParams;
Expand Down Expand Up @@ -746,6 +744,7 @@ const SIDE_EFFECT_REQUEST_COMMANDS = {
ADD_PAYMENT_CARD_GBR: 'AddPaymentCardGBP',
REVEAL_EXPENSIFY_CARD_DETAILS: 'RevealExpensifyCardDetails',
SWITCH_TO_OLD_DOT: 'SwitchToOldDot',
TWO_FACTOR_AUTH_VALIDATE: 'TwoFactorAuth_Validate',
} as const;

type SideEffectRequestCommand = ValueOf<typeof SIDE_EFFECT_REQUEST_COMMANDS>;
Expand All @@ -761,6 +760,7 @@ type SideEffectRequestCommandParameters = {
[SIDE_EFFECT_REQUEST_COMMANDS.GENERATE_SPOTNANA_TOKEN]: Parameters.GenerateSpotnanaTokenParams;
[SIDE_EFFECT_REQUEST_COMMANDS.ADD_PAYMENT_CARD_GBR]: Parameters.AddPaymentCardParams;
[SIDE_EFFECT_REQUEST_COMMANDS.ACCEPT_SPOTNANA_TERMS]: null;
[SIDE_EFFECT_REQUEST_COMMANDS.TWO_FACTOR_AUTH_VALIDATE]: Parameters.ValidateTwoFactorAuthParams;
};

type ApiRequestCommandParameters = WriteCommandParameters & ReadCommandParameters & SideEffectRequestCommandParameters;
Expand Down
Loading
Loading