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
4 changes: 4 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {PortalProvider} from '@gorhom/portal';
import * as Sentry from '@sentry/react-native';
import {maybeCompleteAuthSession} from 'expo-web-browser';
import React from 'react';
import {LogBox, View} from 'react-native';
import {GestureHandlerRootView} from 'react-native-gesture-handler';
Expand Down Expand Up @@ -48,6 +49,9 @@ import './setup/fraudProtection';
import './setup/hybridApp';
import {SplashScreenStateContextProvider} from './SplashScreenStateContext';

// This is needed to close pop-up window during logout for users logged in via SSO
maybeCompleteAuthSession();

LogBox.ignoreLogs([
// Basically it means that if the app goes in the background and back to foreground on Android,
// the timer is lost. Currently Expensify is using a 30 minutes interval to refresh personal details.
Expand Down
7 changes: 5 additions & 2 deletions src/libs/actions/Session/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import * as ErrorUtils from '@libs/ErrorUtils';
import FraudProtection from '@libs/FraudProtection';
import Fullstory from '@libs/Fullstory';
import getPlatform from '@libs/getPlatform';
import HttpUtils from '@libs/HttpUtils';
import Log from '@libs/Log';
import Navigation from '@libs/Navigation/Navigation';
Expand Down Expand Up @@ -68,7 +69,7 @@
import type Session from '@src/types/onyx/Session';
import type {AutoAuthState} from '@src/types/onyx/Session';
import pkg from '../../../../package.json';
import {clearCachedAttachments} from '../Attachment';

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

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Unexpected parent import '../Attachment'. Use '@userActions/Attachment' instead
import clearCache from './clearCache';
import updateSessionAuthTokens from './updateSessionAuthTokens';

Expand All @@ -80,7 +81,7 @@
let isHybridAppSetupFinished = false;
let hasSwitchedAccountInHybridMode = false;

Onyx.connect({

Check warning on line 84 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 @@ -105,13 +106,13 @@
});

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

Check warning on line 109 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 115 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 ?? {}),
});
Expand Down Expand Up @@ -251,8 +252,10 @@
}

function callSAMLSignOut(params: LogOutParams, authToken: string): Promise<void | Response<never>> {
const queryString = CONFIG.IS_HYBRID_APP ? `appversion=${pkg.version}&referer=ecash&authToken=${authToken}` : `referer=ecash&authToken=${authToken}`;
return openAuthSessionAsync(`${CONFIG.EXPENSIFY.SAML_URL}/logout?${queryString}`, CONST.SAML_REDIRECT_URL)
const isWeb = getPlatform() === CONST.PLATFORM.WEB;
const queryString = isWeb ? `referer=ecash&authToken=${authToken}` : `appversion=${pkg.version}&referer=ecash&authToken=${authToken}`;
const expectedURL = isWeb ? CONFIG.EXPENSIFY.NEW_EXPENSIFY_URL : CONST.SAML_REDIRECT_URL;
return openAuthSessionAsync(`${CONFIG.EXPENSIFY.SAML_URL}/logout?${queryString}`, expectedURL)
.catch((error) => {
Log.hmmm('SAML sign out failed', {error});
})
Expand Down
30 changes: 29 additions & 1 deletion tests/actions/SessionTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {getAll as getAllPersistedRequests} from '@libs/actions/PersistedRequests
import * as SignInRedirect from '@libs/actions/SignInRedirect';
import {SIDE_EFFECT_REQUEST_COMMANDS, WRITE_COMMANDS} from '@libs/API/types';
import asyncOpenURL from '@libs/asyncOpenURL';
import getPlatform from '@libs/getPlatform';
import HttpUtils from '@libs/HttpUtils';
import {setHasRadio} from '@libs/NetworkState';
import PushNotification from '@libs/Notification/PushNotification';
Expand Down Expand Up @@ -47,6 +48,10 @@ jest.mock('@libs/actions/Link', () => {
};
});

jest.mock('@libs/getPlatform', () => jest.fn());

const mockedGetPlatform = jest.mocked(getPlatform);

Onyx.init({
keys: ONYXKEYS,
});
Expand Down Expand Up @@ -404,17 +409,40 @@ describe('Session', () => {
jest.restoreAllMocks();
});

test('SignOut should call openAuthSessionAsync when signedInWithSAML is true', async () => {
test('SignOut should call openAuthSessionAsync without appversion when signedInWithSAML is true on web', async () => {
await TestHelper.signInWithTestUser();
await waitForBatchedUpdates();

mockedOpenAuthSessionAsync.mockClear();

mockedGetPlatform.mockReturnValue(CONST.PLATFORM.WEB);

// eslint-disable-next-line rulesdir/no-multiple-api-calls
const makeRequestSpy = jest.spyOn(API, 'makeRequestWithSideEffects').mockResolvedValue(undefined);

await SessionUtil.signOut({signedInWithSAML: true, authToken: 'testAuthToken', autoGeneratedLogin: 'testLogin'});
await waitForBatchedUpdates();

expect(mockedOpenAuthSessionAsync).toHaveBeenCalledWith(expect.stringContaining('referer=ecash&authToken=testAuthToken'), CONFIG.EXPENSIFY.NEW_EXPENSIFY_URL);
expect(mockedOpenAuthSessionAsync).toHaveBeenCalledWith(expect.not.stringContaining('appversion='), CONFIG.EXPENSIFY.NEW_EXPENSIFY_URL);
expect(makeRequestSpy).toHaveBeenCalledWith(SIDE_EFFECT_REQUEST_COMMANDS.LOG_OUT, expect.objectContaining({authToken: 'testAuthToken', partnerUserID: 'testLogin'}), {});

makeRequestSpy.mockRestore();
});

test('SignOut should call openAuthSessionAsync with appversion when signedInWithSAML is true on mobile', async () => {
await TestHelper.signInWithTestUser();
await waitForBatchedUpdates();

mockedOpenAuthSessionAsync.mockClear();
// eslint-disable-next-line rulesdir/no-multiple-api-calls
const makeRequestSpy = jest.spyOn(API, 'makeRequestWithSideEffects').mockResolvedValue(undefined);
mockedGetPlatform.mockReturnValue(CONST.PLATFORM.ANDROID);

await SessionUtil.signOut({signedInWithSAML: true, authToken: 'testAuthToken', autoGeneratedLogin: 'testLogin'});
await waitForBatchedUpdates();

expect(mockedOpenAuthSessionAsync).toHaveBeenCalledWith(expect.stringContaining('appversion='), CONST.SAML_REDIRECT_URL);
expect(mockedOpenAuthSessionAsync).toHaveBeenCalledWith(expect.stringContaining('authToken=testAuthToken'), CONST.SAML_REDIRECT_URL);
expect(makeRequestSpy).toHaveBeenCalledWith(SIDE_EFFECT_REQUEST_COMMANDS.LOG_OUT, expect.objectContaining({authToken: 'testAuthToken', partnerUserID: 'testLogin'}), {});
makeRequestSpy.mockRestore();
Expand Down
Loading