diff --git a/src/languages/en.ts b/src/languages/en.ts index 3a8bcb0c18d5..e0a11514c1ca 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -361,6 +361,7 @@ const translations = { invalidRateError: 'Please enter a valid rate.', lowRateError: 'Rate must be greater than 0.', email: 'Please enter a valid email address.', + login: 'An error occurred while logging in. Please try again.', }, comma: 'comma', semicolon: 'semicolon', diff --git a/src/languages/es.ts b/src/languages/es.ts index 325a5bb59edc..48ee727c7fa6 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -352,6 +352,7 @@ const translations = { invalidRateError: 'Por favor, introduce una tarifa válida.', lowRateError: 'La tarifa debe ser mayor que 0.', email: 'Por favor, introduzca una dirección de correo electrónico válida.', + login: 'Se produjo un error al iniciar sesión. Por favor intente nuevamente.', }, comma: 'la coma', semicolon: 'el punto y coma', diff --git a/src/libs/LoginUtils.ts b/src/libs/LoginUtils.ts index 191fd72db4e9..f496c9de0e6e 100644 --- a/src/libs/LoginUtils.ts +++ b/src/libs/LoginUtils.ts @@ -1,7 +1,11 @@ import {PUBLIC_DOMAINS, Str} from 'expensify-common'; import Onyx from 'react-native-onyx'; +import CONFIG from '@src/CONFIG'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; +import * as Session from './actions/Session'; +import Navigation from './Navigation/Navigation'; import {parsePhoneNumber} from './PhoneNumber'; let countryCodeByIP: number; @@ -75,4 +79,26 @@ function areEmailsFromSamePrivateDomain(email1: string, email2: string): boolean return Str.extractEmailDomain(email1).toLowerCase() === Str.extractEmailDomain(email2).toLowerCase(); } -export {getPhoneNumberWithoutSpecialChars, appendCountryCode, isEmailPublicDomain, validateNumber, getPhoneLogin, areEmailsFromSamePrivateDomain}; +function postSAMLLogin(body: FormData): Promise { + return fetch(CONFIG.EXPENSIFY.SAML_URL, { + method: CONST.NETWORK.METHOD.POST, + body, + credentials: 'omit', + }).then((response) => { + if (!response.ok) { + throw new Error('An error occurred while logging in. Please try again'); + } + return response.json() as Promise; + }); +} + +function handleSAMLLoginError(errorMessage: string, cleanSignInData: boolean) { + if (cleanSignInData) { + Session.clearSignInData(); + } + + Session.setAccountError(errorMessage); + Navigation.goBack(ROUTES.HOME); +} + +export {getPhoneNumberWithoutSpecialChars, appendCountryCode, isEmailPublicDomain, validateNumber, getPhoneLogin, areEmailsFromSamePrivateDomain, postSAMLLogin, handleSAMLLoginError}; diff --git a/src/pages/LogInWithShortLivedAuthTokenPage.tsx b/src/pages/LogInWithShortLivedAuthTokenPage.tsx index 845722909b2c..5669a98fd484 100644 --- a/src/pages/LogInWithShortLivedAuthTokenPage.tsx +++ b/src/pages/LogInWithShortLivedAuthTokenPage.tsx @@ -49,6 +49,8 @@ function LogInWithShortLivedAuthTokenPage({route}: LogInWithShortLivedAuthTokenP // For HybridApp we have separate logic to handle transitions. if (!NativeModules.HybridAppModule && exitTo) { Navigation.isNavigationReady().then(() => { + // We must call goBack() to remove the /transition route from history + Navigation.goBack(); Navigation.navigate(exitTo as Route); }); } diff --git a/src/pages/signin/SAMLSignInPage/index.native.tsx b/src/pages/signin/SAMLSignInPage/index.native.tsx index 3b9bc456a680..5f2a90fba4a6 100644 --- a/src/pages/signin/SAMLSignInPage/index.native.tsx +++ b/src/pages/signin/SAMLSignInPage/index.native.tsx @@ -1,4 +1,4 @@ -import React, {useCallback, useState} from 'react'; +import React, {useCallback, useEffect, useRef, useState} from 'react'; import {useOnyx} from 'react-native-onyx'; import WebView from 'react-native-webview'; import type {WebViewNativeEvent} from 'react-native-webview/lib/WebViewTypes'; @@ -6,8 +6,11 @@ import FullPageOfflineBlockingView from '@components/BlockingViews/FullPageOffli import HeaderWithBackButton from '@components/HeaderWithBackButton'; import SAMLLoadingIndicator from '@components/SAMLLoadingIndicator'; import ScreenWrapper from '@components/ScreenWrapper'; +import useLocalize from '@hooks/useLocalize'; import getPlatform from '@libs/getPlatform'; +import getUAForWebView from '@libs/getUAForWebView'; import Log from '@libs/Log'; +import * as LoginUtils from '@libs/LoginUtils'; import Navigation from '@libs/Navigation/Navigation'; import * as Session from '@userActions/Session'; import CONFIG from '@src/CONFIG'; @@ -17,15 +20,45 @@ import ROUTES from '@src/ROUTES'; function SAMLSignInPage() { const [account] = useOnyx(ONYXKEYS.ACCOUNT); const [credentials] = useOnyx(ONYXKEYS.CREDENTIALS); - const samlLoginURL = `${CONFIG.EXPENSIFY.SAML_URL}?email=${credentials?.login}&referer=${CONFIG.EXPENSIFY.EXPENSIFY_CASH_REFERER}&platform=${getPlatform()}`; const [showNavigation, shouldShowNavigation] = useState(true); + const [SAMLUrl, setSAMLUrl] = useState(''); + const webViewRef = useRef(null); + const {translate} = useLocalize(); + + useEffect(() => { + // If we don't have a valid login to pass here, direct the user back to a clean sign in state to try again + if (!credentials?.login) { + LoginUtils.handleSAMLLoginError(translate('common.error.email'), true); + return; + } + + // If we've already gotten a url back to log into the user's Identity Provider (IdP), then don't re-fetch it + if (SAMLUrl) { + return; + } + + const body = new FormData(); + body.append('email', credentials.login); + body.append('referer', CONFIG.EXPENSIFY.EXPENSIFY_CASH_REFERER); + body.append('platform', getPlatform()); + LoginUtils.postSAMLLogin(body) + .then((response) => { + if (!response || !response.url) { + LoginUtils.handleSAMLLoginError(translate('common.error.login'), false); + return; + } + setSAMLUrl(response.url); + }) + .catch((error: Error) => { + LoginUtils.handleSAMLLoginError(error.message ?? translate('common.error.login'), false); + }); + }, [credentials?.login, SAMLUrl, translate]); /** * Handles in-app navigation once we get a response back from Expensify */ const handleNavigationStateChange = useCallback( ({url}: WebViewNativeEvent) => { - Log.info('SAMLSignInPage - Handling SAML navigation change'); // If we've gotten a callback then remove the option to navigate back to the sign-in page if (url.includes('loginCallback')) { shouldShowNavigation(false); @@ -42,7 +75,12 @@ function SAMLSignInPage() { if (searchParams.has('error')) { Session.clearSignInData(); Session.setAccountError(searchParams.get('error') ?? ''); - Navigation.navigate(ROUTES.HOME); + + Navigation.isNavigationReady().then(() => { + // We must call goBack() to remove the /transition route from history + Navigation.goBack(); + Navigation.navigate(ROUTES.HOME); + }); } }, [credentials?.login, shouldShowNavigation, account?.isLoading], @@ -66,14 +104,20 @@ function SAMLSignInPage() { /> )} - } - onNavigationStateChange={handleNavigationStateChange} - /> + {!SAMLUrl ? ( + + ) : ( + } + onNavigationStateChange={handleNavigationStateChange} + /> + )} ); diff --git a/src/pages/signin/SAMLSignInPage/index.tsx b/src/pages/signin/SAMLSignInPage/index.tsx index 1ff9d02672be..c78bce74c01b 100644 --- a/src/pages/signin/SAMLSignInPage/index.tsx +++ b/src/pages/signin/SAMLSignInPage/index.tsx @@ -1,21 +1,42 @@ import React, {useEffect} from 'react'; -import {withOnyx} from 'react-native-onyx'; +import {useOnyx} from 'react-native-onyx'; import SAMLLoadingIndicator from '@components/SAMLLoadingIndicator'; +import useLocalize from '@hooks/useLocalize'; +import * as LoginUtils from '@libs/LoginUtils'; import CONFIG from '@src/CONFIG'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {SAMLSignInPageOnyxProps, SAMLSignInPageProps} from './types'; -function SAMLSignInPage({credentials}: SAMLSignInPageProps) { +function SAMLSignInPage() { + const {translate} = useLocalize(); + const [credentials] = useOnyx(ONYXKEYS.CREDENTIALS); + useEffect(() => { - window.location.replace(`${CONFIG.EXPENSIFY.SAML_URL}?email=${credentials?.login}&referer=${CONFIG.EXPENSIFY.EXPENSIFY_CASH_REFERER}`); - }, [credentials?.login]); + // If we don't have a valid login to pass here, direct the user back to a clean sign in state to try again + if (!credentials?.login) { + LoginUtils.handleSAMLLoginError(translate('common.error.email'), true); + return; + } + + const body = new FormData(); + body.append('email', credentials.login); + body.append('referer', CONFIG.EXPENSIFY.EXPENSIFY_CASH_REFERER); + + LoginUtils.postSAMLLogin(body) + .then((response) => { + if (!response || !response.url) { + LoginUtils.handleSAMLLoginError(translate('common.error.login'), false); + return; + } + window.location.replace(response.url); + }) + .catch((error: Error) => { + LoginUtils.handleSAMLLoginError(error.message ?? translate('common.error.login'), false); + }); + }, [credentials?.login, translate]); return ; } SAMLSignInPage.displayName = 'SAMLSignInPage'; -export default withOnyx({ - account: {key: ONYXKEYS.ACCOUNT}, - credentials: {key: ONYXKEYS.CREDENTIALS}, -})(SAMLSignInPage); +export default SAMLSignInPage;