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

[SAML NewDot] Add SAML flow for web, mweb, desktop #28372

Merged
merged 51 commits into from
Oct 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
7f9875e
add SAMLEnabledForm
NikkiWines Sep 26, 2023
b7f9376
update SignInPage to route to SAMLEnabledForm and SAMLSignInPage
NikkiWines Sep 26, 2023
0551e55
add SAMLSignInPage and routing
NikkiWines Sep 26, 2023
a4b6622
add beta and consts
NikkiWines Sep 26, 2023
e619191
add routing for SAML required flow
NikkiWines Sep 27, 2023
66a46b4
Merge branch 'main' of github.com:Expensify/App into nikki-saml-newdo…
NikkiWines Sep 27, 2023
75e4482
add updated copy for SAML sign in page
NikkiWines Sep 28, 2023
31222f1
redirect back to SignInPage instead of showing ValidateCodePage in su…
NikkiWines Sep 28, 2023
f99cf33
fix login redirecting when SAML enabled user opts to sign in with a m…
NikkiWines Sep 28, 2023
f144da8
add saml spanish translations
NikkiWines Sep 28, 2023
0c74b4a
minor style and props validation
NikkiWines Sep 28, 2023
464f2dd
include login in useEffect dependency array
NikkiWines Sep 28, 2023
bf8f5d4
minor style and linting fixes
NikkiWines Sep 28, 2023
4b08ced
update copy
NikkiWines Sep 29, 2023
4308871
prettier style
NikkiWines Sep 29, 2023
3813e1a
use destructured props and hooks
NikkiWines Oct 2, 2023
e66d4d7
rename file according to conventions
NikkiWines Oct 2, 2023
6c8e9cc
remove useless compose
NikkiWines Oct 2, 2023
5f82eb1
minor cleanup
NikkiWines Oct 2, 2023
a3ab496
prettier
NikkiWines Oct 2, 2023
4ba9b0d
fix state logic for whether or not the user has opted to use magic co…
NikkiWines Oct 3, 2023
960bda8
minor pr comments
NikkiWines Oct 3, 2023
a4dd935
Merge branch 'main' of https://github.com/Expensify/App into nikki-sa…
NikkiWines Oct 3, 2023
9c34b6b
add support for showing error messages when returned in url
NikkiWines Oct 3, 2023
a11f6f6
add desktop support because it works out of the box ????
NikkiWines Oct 3, 2023
e3ffff9
Revert "add desktop support because it works out of the box ????" - i…
NikkiWines Oct 3, 2023
12bf1cc
some edge case handling and saml required redirect logic
NikkiWines Oct 4, 2023
49c03d1
Merge branch 'main' of https://github.com/Expensify/App into nikki-sa…
NikkiWines Oct 4, 2023
eb8468b
don't re-call signInWithShortLivedAUthToken if we're already loading …
NikkiWines Oct 4, 2023
2a86c47
fix props
NikkiWines Oct 4, 2023
ef1d7f9
some linter fixes
NikkiWines Oct 4, 2023
292fa79
minor style
NikkiWines Oct 4, 2023
44ecdaa
Merge branch 'main' of https://github.com/Expensify/App into nikki-sa…
NikkiWines Oct 9, 2023
887960c
fix merge issue
NikkiWines Oct 9, 2023
a5c1152
prettier
NikkiWines Oct 9, 2023
26585bc
fix console log for rendering and ensure users that are SAML required…
NikkiWines Oct 10, 2023
5a27e2b
remove redundant bool cast
NikkiWines Oct 10, 2023
894b588
re-add erroneously removed logic
NikkiWines Oct 10, 2023
a96f0b1
re-add erroneously removed logic
NikkiWines Oct 10, 2023
12c291b
fix import
NikkiWines Oct 10, 2023
d19ce5b
Merge branch 'main' of https://github.com/Expensify/App into nikki-sa…
NikkiWines Oct 10, 2023
0c67e94
fix import...again
NikkiWines Oct 10, 2023
062fb6b
Merge branch 'main' of https://github.com/Expensify/App into nikki-sa…
NikkiWines Oct 11, 2023
df377f2
Revert "Merge branch 'main' of https://github.com/Expensify/App into …
NikkiWines Oct 11, 2023
371372c
Merge branch 'main' of https://github.com/Expensify/App into nikki-sa…
NikkiWines Oct 11, 2023
23aaee1
resolve weird account state when saml required users navigate back to…
NikkiWines Oct 11, 2023
a6b7b6b
use const where appropriate
NikkiWines Oct 11, 2023
4447dc2
update comment
NikkiWines Oct 13, 2023
f6c0d58
Merge branch 'main' of https://github.com/Expensify/App into nikki-sa…
NikkiWines Oct 13, 2023
d7982fa
remove beta, update some old styles
NikkiWines Oct 13, 2023
b793275
remove unused import
NikkiWines Oct 14, 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
1 change: 1 addition & 0 deletions src/CONFIG.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ export default {
CONCIERGE_URL_PATHNAME: 'concierge/',
DEVPORTAL_URL_PATHNAME: '_devportal/',
CONCIERGE_URL: `${expensifyURL}concierge/`,
SAML_URL: `${expensifyURL}authentication/saml/login`,
},
IS_IN_PRODUCTION: Platform.OS === 'web' ? process.env.NODE_ENV === 'production' : !__DEV__,
IS_IN_STAGING: ENVIRONMENT === CONST.ENVIRONMENT.STAGING,
Expand Down
1 change: 1 addition & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,7 @@ const CONST = {
CUSTOM_STATUS: 'customStatus',
NEW_DOT_CATEGORIES: 'newDotCategories',
NEW_DOT_TAGS: 'newDotTags',
NEW_DOT_SAML: 'newDotSAML',
},
BUTTON_STATES: {
DEFAULT: 'default',
Expand Down
2 changes: 2 additions & 0 deletions src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ export default {
APPLE_SIGN_IN: 'sign-in-with-apple',
GOOGLE_SIGN_IN: 'sign-in-with-google',
DESKTOP_SIGN_IN_REDIRECT: 'desktop-signin-redirect',
SAML_SIGN_IN: 'sign-in-with-saml',

// This is a special validation URL that will take the user to /workspace/new after validation. This is used
// when linking users from e.com in order to share a session in this app.
ENABLE_PAYMENTS: 'enable-payments',
Expand Down
1 change: 1 addition & 0 deletions src/SCREENS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export default {
SIGN_IN_WITH_APPLE_DESKTOP: 'AppleSignInDesktop',
SIGN_IN_WITH_GOOGLE_DESKTOP: 'GoogleSignInDesktop',
DESKTOP_SIGN_IN_REDIRECT: 'DesktopSignInRedirect',
SAML_SIGN_IN: 'SAMLSignIn',
VALIDATE_LOGIN: 'ValidateLogin',

// Iframe screens from olddot
Expand Down
8 changes: 8 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,14 @@ export default {
termsOfService: 'Terms of Service',
privacy: 'Privacy',
},
samlSignIn: {
welcomeSAMLEnabled: 'Continue logging in with single sign-on:',
orContinueWithMagicCode: 'Or optionally, your company allows signing in with a magic code',
useSingleSignOn: 'Use single sign-on',
useMagicCode: 'Use magic code',
launching: 'Launching...',
oneMoment: "One moment while we redirect you to your company's single sign-on portal.",
},
reportActionCompose: {
addAction: 'Actions',
dropToUpload: 'Drop to upload',
Expand Down
8 changes: 8 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,14 @@ export default {
termsOfService: 'Términos de servicio',
privacy: 'Privacidad',
},
samlSignIn: {
welcomeSAMLEnabled: 'Continua iniciando sesión con el inicio de sesión único:',
orContinueWithMagicCode: 'O, opcionalmente, tu empresa te permite iniciar sesión con un código mágico',
useSingleSignOn: 'Usar el inicio de sesión único',
useMagicCode: 'Usar código mágico',
launching: 'Cargando...',
oneMoment: 'Un momento mientras te redirigimos al portal de inicio de sesión único de tu empresa.',
},
reportActionCompose: {
addAction: 'Acción',
dropToUpload: 'Suelta el archivo aquí para compartirlo',
Expand Down
6 changes: 6 additions & 0 deletions src/libs/Navigation/AppNavigator/PublicScreens.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import defaultScreenOptions from './defaultScreenOptions';
import UnlinkLoginPage from '../../../pages/UnlinkLoginPage';
import AppleSignInDesktopPage from '../../../pages/signin/AppleSignInDesktopPage';
import GoogleSignInDesktopPage from '../../../pages/signin/GoogleSignInDesktopPage';
import SAMLSignInPage from '../../../pages/signin/SAMLSignInPage';

const RootStack = createStackNavigator();

Expand Down Expand Up @@ -44,6 +45,11 @@ function PublicScreens() {
options={defaultScreenOptions}
component={GoogleSignInDesktopPage}
/>
<RootStack.Screen
name="SAMLSignIn"
options={defaultScreenOptions}
component={SAMLSignInPage}
/>
</RootStack.Navigator>
);
}
Expand Down
1 change: 1 addition & 0 deletions src/libs/Navigation/linkingConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export default {
[SCREENS.CONCIERGE]: ROUTES.CONCIERGE,
AppleSignInDesktop: ROUTES.APPLE_SIGN_IN,
GoogleSignInDesktop: ROUTES.GOOGLE_SIGN_IN,
SAMLSignIn: ROUTES.SAML_SIGN_IN,
[SCREENS.DESKTOP_SIGN_IN_REDIRECT]: ROUTES.DESKTOP_SIGN_IN_REDIRECT,
[SCREENS.REPORT_ATTACHMENTS]: ROUTES.REPORT_ATTACHMENTS.route,

Expand Down
7 changes: 6 additions & 1 deletion src/libs/actions/Session/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ function signInWithShortLivedAuthToken(email, authToken) {
// If the user is signing in with a different account from the current app, should not pass the auto-generated login as it may be tied to the old account.
// scene 1: the user is transitioning to newDot from a different account on oldDot.
// scene 2: the user is transitioning to desktop app from a different account on web app.
const oldPartnerUserID = credentials.login === email ? credentials.autoGeneratedLogin : '';
const oldPartnerUserID = credentials.login === email && credentials.autoGeneratedLogin ? credentials.autoGeneratedLogin : '';
API.read('SignInWithShortLivedAuthToken', {authToken, oldPartnerUserID, skipReauthentication: true}, {optimisticData, successData, failureData});
}

Expand Down Expand Up @@ -541,6 +541,10 @@ function clearAccountMessages() {
});
}

function setAccountError(error) {
Onyx.merge(ONYXKEYS.ACCOUNT, {errors: ErrorUtils.getMicroSecondOnyxError(error)});
}

// It's necessary to throttle requests to reauthenticate since calling this multiple times will cause Pusher to
// reconnect each time when we only need to reconnect once. This way, if an authToken is expired and we try to
// subscribe to a bunch of channels at once we will only reauthenticate and force reconnect Pusher once.
Expand Down Expand Up @@ -807,6 +811,7 @@ export {
unlinkLogin,
clearSignInData,
clearAccountMessages,
setAccountError,
authenticatePusher,
reauthenticatePusher,
invalidateCredentials,
Expand Down
40 changes: 27 additions & 13 deletions src/pages/LogInWithShortLivedAuthTokenPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ import themeColors from '../styles/themes/default';
import Icon from '../components/Icon';
import * as Expensicons from '../components/Icon/Expensicons';
import * as Illustrations from '../components/Icon/Illustrations';
import withLocalize, {withLocalizePropTypes} from '../components/withLocalize';
import compose from '../libs/compose';
import useLocalize from '../hooks/useLocalize';
import TextLink from '../components/TextLink';
import ONYXKEYS from '../ONYXKEYS';

Expand All @@ -33,8 +32,6 @@ const propTypes = {
}),
}).isRequired,

...withLocalizePropTypes,

/** The details about the account that the user is signing in with */
account: PropTypes.shape({
/** Whether a sign is loading */
Expand All @@ -49,15 +46,26 @@ const defaultProps = {
};

function LogInWithShortLivedAuthTokenPage(props) {
const {translate} = useLocalize();

useEffect(() => {
const email = lodashGet(props, 'route.params.email', '');

// We have to check for both shortLivedAuthToken and shortLivedToken, as the old mobile app uses shortLivedToken, and is not being actively updated.
const shortLivedAuthToken = lodashGet(props, 'route.params.shortLivedAuthToken', '') || lodashGet(props, 'route.params.shortLivedToken', '');
if (shortLivedAuthToken) {

// Try to authenticate using the shortLivedToken if we're not already trying to load the accounts
if (shortLivedAuthToken && !props.account.isLoading) {
Session.signInWithShortLivedAuthToken(email, shortLivedAuthToken);
return;
}

// If an error is returned as part of the route, ensure we set it in the onyxData for the account
const error = lodashGet(props, 'route.params.error', '');
if (error) {
Session.setAccountError(error);
}

const exitTo = lodashGet(props, 'route.params.exitTo', '');
if (exitTo) {
Navigation.isNavigationReady().then(() => {
Expand All @@ -82,10 +90,18 @@ function LogInWithShortLivedAuthTokenPage(props) {
src={Illustrations.RocketBlue}
/>
</View>
<Text style={[styles.textHeadline, styles.textXXLarge]}>{props.translate('deeplinkWrapper.launching')}</Text>
<Text style={[styles.textHeadline, styles.textXXLarge]}>{translate('deeplinkWrapper.launching')}</Text>
<View style={styles.mt2}>
<Text style={[styles.fontSizeNormal, styles.textAlignCenter]}>
{props.translate('deeplinkWrapper.expired')} <TextLink onPress={() => Navigation.navigate()}>{props.translate('deeplinkWrapper.signIn')}</TextLink>
{translate('deeplinkWrapper.expired')}{' '}
<TextLink
onPress={() => {
Session.clearSignInData();
Navigation.navigate();
}}
>
{translate('deeplinkWrapper.signIn')}
</TextLink>
</Text>
</View>
</View>
Expand All @@ -105,9 +121,7 @@ LogInWithShortLivedAuthTokenPage.propTypes = propTypes;
LogInWithShortLivedAuthTokenPage.defaultProps = defaultProps;
LogInWithShortLivedAuthTokenPage.displayName = 'LogInWithShortLivedAuthTokenPage';

export default compose(
withLocalize,
withOnyx({
account: {key: ONYXKEYS.ACCOUNT},
}),
)(LogInWithShortLivedAuthTokenPage);
export default withOnyx({
account: {key: ONYXKEYS.ACCOUNT},
session: {key: ONYXKEYS.SESSION},
Copy link
Contributor

Choose a reason for hiding this comment

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

The session prop appears unused. Do you know if it's needed here?

Copy link
Contributor

Choose a reason for hiding this comment

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

We can remove it most probably. Seems like we removed the usage, but forgot to remove this key here. /cc @NikkiWines to be double sure

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah yes, we can remove this 👍 I can do that as part of #29526 unless you'd like to make a separate PR fro it @roryabraham

Copy link
Contributor

Choose a reason for hiding this comment

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

@NikkiWines if you want to do it as part of #29526 that would be great!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done!

})(LogInWithShortLivedAuthTokenPage);
108 changes: 108 additions & 0 deletions src/pages/signin/ChooseSSOOrMagicCode.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import React from 'react';
import {View} from 'react-native';
import {withOnyx} from 'react-native-onyx';
import PropTypes from 'prop-types';
import _ from 'underscore';
import styles from '../../styles/styles';
import ONYXKEYS from '../../ONYXKEYS';
import Text from '../../components/Text';
import Button from '../../components/Button';
import * as Session from '../../libs/actions/Session';
import ChangeExpensifyLoginLink from './ChangeExpensifyLoginLink';
import Terms from './Terms';
import CONST from '../../CONST';
import ROUTES from '../../ROUTES';
import Navigation from '../../libs/Navigation/Navigation';
import * as ErrorUtils from '../../libs/ErrorUtils';
import useLocalize from '../../hooks/useLocalize';
import useNetwork from '../../hooks/useNetwork';
import useWindowDimensions from '../../hooks/useWindowDimensions';
import FormHelpMessage from '../../components/FormHelpMessage';

const propTypes = {
/* Onyx Props */

/** The credentials of the logged in person */
credentials: PropTypes.shape({
/** The email/phone the user logged in with */
login: PropTypes.string,
}),

/** The details about the account that the user is signing in with */
account: PropTypes.shape({
/** Whether or not a sign on form is loading (being submitted) */
isLoading: PropTypes.bool,

/** Form that is being loaded */
loadingForm: PropTypes.oneOf(_.values(CONST.FORMS)),

/** Whether this account has 2FA enabled or not */
requiresTwoFactorAuth: PropTypes.bool,

/** Server-side errors in the submitted authentication code */
errors: PropTypes.objectOf(PropTypes.string),
}),

/** Function that returns whether the user is using SAML or magic codes to log in */
setIsUsingMagicCode: PropTypes.func.isRequired,
};

const defaultProps = {
credentials: {},
account: {},
};

function ChooseSSOOrMagicCode({credentials, account, setIsUsingMagicCode}) {
const {translate} = useLocalize();
const {isOffline} = useNetwork();
const {isSmallScreenWidth} = useWindowDimensions();

return (
<>
<View>
<Text style={[styles.loginHeroBody, styles.mb5, styles.textNormal, !isSmallScreenWidth ? styles.textAlignLeft : {}]}>{translate('samlSignIn.welcomeSAMLEnabled')}</Text>
<Button
isDisabled={isOffline}
success
style={[styles.mv3]}
text={translate('samlSignIn.useSingleSignOn')}
isLoading={account.isLoading}
onPress={() => {
Navigation.navigate(ROUTES.SAML_SIGN_IN);
}}
/>

<View style={[styles.mt5]}>
<Text style={[styles.loginHeroBody, styles.mb5, styles.textNormal, !isSmallScreenWidth ? styles.textAlignLeft : {}]}>
{translate('samlSignIn.orContinueWithMagicCode')}
</Text>
</View>

<Button
isDisabled={isOffline}
style={[styles.mv3]}
text={translate('samlSignIn.useMagicCode')}
isLoading={account.isLoading && account.loadingForm === (account.requiresTwoFactorAuth ? CONST.FORMS.VALIDATE_TFA_CODE_FORM : CONST.FORMS.VALIDATE_CODE_FORM)}
onPress={() => {
Session.resendValidateCode(credentials.login);
setIsUsingMagicCode(true);
}}
/>
{Boolean(account) && !_.isEmpty(account.errors) && <FormHelpMessage message={ErrorUtils.getLatestErrorMessage(account)} />}
<ChangeExpensifyLoginLink onPress={() => Session.clearSignInData()} />
</View>
<View style={[styles.mt5, styles.signInPageWelcomeTextContainer]}>
<Terms />
</View>
</>
);
}

ChooseSSOOrMagicCode.propTypes = propTypes;
ChooseSSOOrMagicCode.defaultProps = defaultProps;
ChooseSSOOrMagicCode.displayName = 'ChooseSSOOrMagicCode';

export default withOnyx({
credentials: {key: ONYXKEYS.CREDENTIALS},
account: {key: ONYXKEYS.ACCOUNT},
})(ChooseSSOOrMagicCode);
2 changes: 1 addition & 1 deletion src/pages/signin/LoginForm/BaseLoginForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ function LoginForm(props) {
useEffect(() => {
// Just call clearAccountMessages on the login page (home route), because when the user is in the transition route and not yet authenticated,
// this component will also be mounted, resetting account.isLoading will cause the app to briefly display the session expiration page.
if (props.isFocused) {
if (props.isFocused && props.isVisible) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Sorry, why this change?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is so that if we get an error back in the URL after the user tries to sign in with SSO that we display the error message instead of clearing it.

You can test this by navigating to http://localhost:8082/transition?error=meepmeep&exitTo=%2F

Copy link
Contributor

Choose a reason for hiding this comment

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

Clarification - the error is supposed to be displayed on ChooseSSOOrMagicCode or ValidateCodeForm page, right?
Simply navigating to the URL does not display an error on the BaseLoginForm.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, yes - I was thinking on the ChooseSSOOrMagicCode view, though then it wouldn't show for SAML required users 🤔

I think that can be handled more fully in a follow up PR though, I'd like to get this merged ASAP so it's easier to develop for the remaining platforms 🙏

Copy link
Contributor

Choose a reason for hiding this comment

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

Sounds good 👍

Session.clearAccountMessages();
}
if (!canFocusInputOnScreenFocus() || !input.current || !props.isVisible) {
Expand Down
66 changes: 66 additions & 0 deletions src/pages/signin/SAMLSignInPage/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import React, {useEffect} from 'react';
import {withOnyx} from 'react-native-onyx';
import {View} from 'react-native';
import PropTypes from 'prop-types';
import ONYXKEYS from '../../../ONYXKEYS';
import CONFIG from '../../../CONFIG';
import Icon from '../../../components/Icon';
import Text from '../../../components/Text';
import * as Expensicons from '../../../components/Icon/Expensicons';
import * as Illustrations from '../../../components/Icon/Illustrations';
import styles from '../../../styles/styles';
import themeColors from '../../../styles/themes/default';
import useLocalize from '../../../hooks/useLocalize';

const propTypes = {
/** The credentials of the logged in person */
credentials: PropTypes.shape({
/** The email/phone the user logged in with */
login: PropTypes.string,
}),
};

const defaultProps = {
credentials: {},
};

function SAMLSignInPage({credentials}) {
const {translate} = useLocalize();

useEffect(() => {
window.open(`${CONFIG.EXPENSIFY.SAML_URL}?email=${credentials.login}&referer=${CONFIG.EXPENSIFY.EXPENSIFY_CASH_REFERER}`, '_self');
Copy link
Contributor

Choose a reason for hiding this comment

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

window.open broke route history and user couldn't go back from SAML page. ref: #29825

}, [credentials.login]);

return (
<View style={styles.deeplinkWrapperContainer}>
<View style={styles.deeplinkWrapperMessage}>
<View style={styles.mb2}>
<Icon
width={200}
height={164}
src={Illustrations.RocketBlue}
/>
</View>
<Text style={[styles.textHeadline, styles.textXXLarge, styles.textAlignCenter]}>{translate('samlSignIn.launching')}</Text>
<View style={[styles.mt2, styles.mh2, styles.fontSizeNormal, styles.textAlignCenter]}>
<Text style={[styles.textAlignCenter]}>{translate('samlSignIn.oneMoment')}</Text>
</View>
</View>
<View style={styles.deeplinkWrapperFooter}>
<Icon
width={154}
height={34}
fill={themeColors.success}
src={Expensicons.ExpensifyWordmark}
/>
</View>
</View>
);
}

SAMLSignInPage.propTypes = propTypes;
SAMLSignInPage.defaultProps = defaultProps;

export default withOnyx({
credentials: {key: ONYXKEYS.CREDENTIALS},
})(SAMLSignInPage);
Loading
Loading