Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
11 changes: 0 additions & 11 deletions package-lock.json

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

1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,6 @@
"expo-image-manipulator": "^13.1.5",
"expo-modules-core": "3.0.18",
"expo-secure-store": "~14.2.4",
"expo-web-browser": "14.2.0",
"fast-equals": "^5.2.2",
"focus-trap-react": "^11.0.3",
"group-ib-fp": "file:modules/group-ib-fp",
Expand Down
1 change: 0 additions & 1 deletion src/CONST/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -953,7 +953,6 @@ const CONST = {
GOOGLE_DOC_IMAGE_LINK_MATCH: 'googleusercontent.com',
IMAGE_BASE64_MATCH: 'base64',
DEEPLINK_BASE_URL: 'new-expensify://',
SAML_REDIRECT_URL: 'expensify://open',
PDF_VIEWER_URL: '/pdf/web/viewer.html',
CLOUDFRONT_DOMAIN_REGEX: /^https:\/\/\w+\.cloudfront\.net/i,
EXPENSIFY_ICON_URL: `${CLOUDFRONT_URL}/images/favicon-2019.png`,
Expand Down
15 changes: 0 additions & 15 deletions src/libs/actions/Session/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import HybridAppModule from '@expensify/react-native-hybrid-app';
import {openAuthSessionAsync} from 'expo-web-browser';
import throttle from 'lodash/throttle';
import type {ChannelAuthorizationData} from 'pusher-js/types/src/core/auth/options';
import type {ChannelAuthorizationCallback} from 'pusher-js/with-encryption';
Expand Down Expand Up @@ -67,7 +66,6 @@
import type Response from '@src/types/onyx/Response';
import type Session from '@src/types/onyx/Session';
import type {AutoAuthState} from '@src/types/onyx/Session';
import pkg from '../../../../package.json';
import clearCache from './clearCache';
import updateSessionAuthTokens from './updateSessionAuthTokens';

Expand All @@ -79,7 +77,7 @@
let isHybridAppSetupFinished = false;
let hasSwitchedAccountInHybridMode = false;

Onyx.connect({

Check warning on line 80 in src/libs/actions/Session/index.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.SESSION,
callback: (value) => {
session = value ?? {};
Expand All @@ -104,25 +102,25 @@
});

let stashedSession: Session = {};
Onyx.connect({

Check warning on line 105 in src/libs/actions/Session/index.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.STASHED_SESSION,
callback: (value) => (stashedSession = value ?? {}),
});

let credentials: Credentials = {};
Onyx.connect({

Check warning on line 111 in src/libs/actions/Session/index.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.CREDENTIALS,
callback: (value) => (credentials = value ?? {}),
});

let stashedCredentials: Credentials = {};
Onyx.connect({

Check warning on line 117 in src/libs/actions/Session/index.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.STASHED_CREDENTIALS,
callback: (value) => (stashedCredentials = value ?? {}),
});

let activePolicyID: OnyxEntry<string>;
Onyx.connect({

Check warning on line 123 in src/libs/actions/Session/index.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.NVP_ACTIVE_POLICY_ID,
callback: (newActivePolicyID) => {
activePolicyID = newActivePolicyID;
Expand Down Expand Up @@ -165,7 +163,6 @@
key: ONYXKEYS.SESSION,
value: {
signedInWithShortLivedAuthToken: true,
signedInWithSAML: isSAML,
isAuthenticatingWithShortLivedToken: true,
isSupportAuthTokenUsed,
},
Expand All @@ -186,7 +183,6 @@
key: ONYXKEYS.SESSION,
value: {
signedInWithShortLivedAuthToken: null,
signedInWithSAML: isSAML,
isSupportAuthTokenUsed: null,
isAuthenticatingWithShortLivedToken: false,
},
Expand Down Expand Up @@ -241,21 +237,10 @@
skipReauthentication: true,
};

if (session.signedInWithSAML) {
return callSAMLSignOut(params);
}
// eslint-disable-next-line rulesdir/no-api-side-effects-method
return API.makeRequestWithSideEffects(SIDE_EFFECT_REQUEST_COMMANDS.LOG_OUT, params, {});
}

function callSAMLSignOut(params: LogOutParams): Promise<void | Response> {
const queryString = `appversion=${pkg.version}&referer=ecash&authToken=${session.authToken}`;
return openAuthSessionAsync(`${CONST.EXPENSIFY_URL}/authentication/saml/logout?${queryString}`).then(() => {
// eslint-disable-next-line rulesdir/no-api-side-effects-method
API.makeRequestWithSideEffects(SIDE_EFFECT_REQUEST_COMMANDS.LOG_OUT, params, {});
});
}

/**
* Checks if the account is an anonymous account.
*/
Expand Down
123 changes: 50 additions & 73 deletions src/pages/signin/SAMLSignInPage/index.native.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import type {WebBrowserAuthSessionResult} from 'expo-web-browser';
import {openAuthSessionAsync} from 'expo-web-browser';
import React, {useCallback, useEffect, useRef, useState} from 'react';
import WebView from 'react-native-webview';
import type {WebViewNativeEvent} from 'react-native-webview/lib/WebViewTypes';
import FullPageOfflineBlockingView from '@components/BlockingViews/FullPageOfflineBlockingView';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import SAMLLoadingIndicator from '@components/SAMLLoadingIndicator';
import ScreenWrapper from '@components/ScreenWrapper';
import useLocalize from '@hooks/useLocalize';
import useOnyx from '@hooks/useOnyx';
import getPlatform from '@libs/getPlatform';
import getUAForWebView from '@libs/getUAForWebView';
import Log from '@libs/Log';
import {handleSAMLLoginError, postSAMLLogin} from '@libs/LoginUtils';
import Navigation from '@libs/Navigation/Navigation';
import {clearSignInData, setAccountError, signInWithShortLivedAuthToken} from '@userActions/Session';
import CONFIG from '@src/CONFIG';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';

Expand All @@ -22,75 +22,8 @@ function SAMLSignInPage() {
const [credentials] = useOnyx(ONYXKEYS.CREDENTIALS, {canBeMissing: false});
const [showNavigation, shouldShowNavigation] = useState(true);
const [SAMLUrl, setSAMLUrl] = useState('');
const webViewRef = useRef<WebView>(null);
const {translate} = useLocalize();
const hasOpenedAuthSession = useRef(false);

/**
* Handles in-app navigation once we get a response back from Expensify
*/
const handleNavigationStateChange = useCallback(
(url: string) => {
// If we've gotten a callback then remove the option to navigate back to the sign-in page
if (url.includes('loginCallback')) {
shouldShowNavigation(false);
}

const searchParams = new URLSearchParams(new URL(url).search);
const jsonParam = searchParams.get('json');

if (!jsonParam) {
Log.hmmm('SAMLSignInPage - No JSON parameter found in callback URL');
return;
}

let shortLivedAuthToken: string | null = null;
let error: string | null = null;

try {
const decodedData = JSON.parse(jsonParam) as Record<string, string | null>;
shortLivedAuthToken = decodedData.shortLivedAuthToken ?? null;
error = decodedData.error ?? null;
Log.info('SAMLSignInPage - Parsed data from JSON parameter');
} catch {
// We need to come generate translation for message indicating parsing error
Log.hmmm(`SAMLSignInPage - Failed to parse JSON parameter`);
error = 'Failed to parse JSON';
}

if (!account?.isLoading && credentials?.login && !!shortLivedAuthToken) {
Log.info('SAMLSignInPage - Successfully received shortLivedAuthToken. Signing in...');
signInWithShortLivedAuthToken(shortLivedAuthToken, true);
return;
}

if (error) {
clearSignInData();
setAccountError(error);

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],
);

useEffect(() => {
// Don't open auth session more than once. If user cancels it we should navigate back to ROUTES.HOME
if (!SAMLUrl || hasOpenedAuthSession.current) {
return;
}
hasOpenedAuthSession.current = true;
openAuthSessionAsync(SAMLUrl, CONST.SAML_REDIRECT_URL).then((response: WebBrowserAuthSessionResult) => {
if (response.type !== 'success') {
Navigation.goBack();
return;
}
handleNavigationStateChange(response.url);
});
}, [SAMLUrl, handleNavigationStateChange]);

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
Expand All @@ -108,7 +41,6 @@ function SAMLSignInPage() {
body.append('email', credentials.login);
body.append('referer', CONFIG.EXPENSIFY.EXPENSIFY_CASH_REFERER);
body.append('platform', getPlatform());
body.append('useBrowser', 'true');
postSAMLLogin(body)
.then((response) => {
if (!response || !response.url) {
Expand All @@ -122,6 +54,38 @@ function SAMLSignInPage() {
});
}, [credentials?.login, SAMLUrl, translate]);

/**
* Handles in-app navigation once we get a response back from Expensify
*/
const handleNavigationStateChange = useCallback(
({url}: WebViewNativeEvent) => {
// If we've gotten a callback then remove the option to navigate back to the sign-in page
if (url.includes('loginCallback')) {
shouldShowNavigation(false);
}

const searchParams = new URLSearchParams(new URL(url).search);
const shortLivedAuthToken = searchParams.get('shortLivedAuthToken');
if (!account?.isLoading && credentials?.login && !!shortLivedAuthToken) {
Log.info('SAMLSignInPage - Successfully received shortLivedAuthToken. Signing in...');
signInWithShortLivedAuthToken(shortLivedAuthToken, true);
}

// If the login attempt is unsuccessful, set the error message for the account and redirect to sign in page
if (searchParams.has('error')) {
clearSignInData();
setAccountError(searchParams.get('error') ?? '');

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],
);

return (
<ScreenWrapper
shouldShowOfflineIndicator={false}
Expand All @@ -140,7 +104,20 @@ function SAMLSignInPage() {
/>
)}
<FullPageOfflineBlockingView>
<SAMLLoadingIndicator />
{!SAMLUrl ? (
<SAMLLoadingIndicator />
) : (
<WebView
ref={webViewRef}
originWhitelist={['https://*']}
source={{uri: SAMLUrl}}
userAgent={getUAForWebView()}
incognito // 'incognito' prop required for Android, issue here https://github.com/react-native-webview/react-native-webview/issues/1352
startInLoadingState
renderLoading={() => <SAMLLoadingIndicator />}
onNavigationStateChange={handleNavigationStateChange}
/>
)}
</FullPageOfflineBlockingView>
</ScreenWrapper>
);
Expand Down
3 changes: 0 additions & 3 deletions src/types/onyx/Session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,6 @@ type Session = {
/** User signed in with short lived token */
signedInWithShortLivedAuthToken?: boolean;

/** User signed in with SAML */
signedInWithSAML?: boolean;

/** Indicates whether the user is re-authenticating with shortLivedToken */
isAuthenticatingWithShortLivedToken?: boolean;

Expand Down
Loading