diff --git a/src/CONST.ts b/src/CONST.ts index 1babbabe054f..9a9294621338 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -75,6 +75,11 @@ const CONST = { FAILED: 'failed', }, + AUTH_TOKEN_TYPES: { + ANONYMOUS: 'anonymousAccount', + SUPPORT: 'support', + }, + AVATAR_MAX_ATTACHMENT_SIZE: 6291456, AVATAR_ALLOWED_EXTENSIONS: ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'svg'], @@ -3341,10 +3346,6 @@ const CONST = { SESSION_STORAGE_KEYS: { INITIAL_URL: 'INITIAL_URL', }, - - AUTH_TOKEN_TYPE: { - ANONYMOUS: 'anonymousAccount', - }, } as const; type Country = keyof typeof CONST.ALL_COUNTRIES; diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index d4a0b8a21d66..f6b5c635e4ae 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -40,6 +40,7 @@ const ONYXKEYS = { /** Credentials to authenticate the user */ CREDENTIALS: 'credentials', + STASHED_CREDENTIALS: 'stashedCredentials', // Contains loading data for the IOU feature (MoneyRequestModal, IOUDetail, & MoneyRequestPreview Components) IOU: 'iou', @@ -102,6 +103,7 @@ const ONYXKEYS = { /** Information about the current session (authToken, accountID, email, loading, error) */ SESSION: 'session', + STASHED_SESSION: 'stashedSession', BETAS: 'betas', /** NVP keys @@ -491,6 +493,7 @@ type OnyxValuesMapping = { [ONYXKEYS.PERSISTED_REQUESTS]: OnyxTypes.Request[]; [ONYXKEYS.CURRENT_DATE]: string; [ONYXKEYS.CREDENTIALS]: OnyxTypes.Credentials; + [ONYXKEYS.STASHED_CREDENTIALS]: OnyxTypes.Credentials; [ONYXKEYS.IOU]: OnyxTypes.IOU; [ONYXKEYS.MODAL]: OnyxTypes.Modal; [ONYXKEYS.NETWORK]: OnyxTypes.Network; @@ -509,6 +512,7 @@ type OnyxValuesMapping = { [ONYXKEYS.USER_LOCATION]: OnyxTypes.UserLocation; [ONYXKEYS.LOGIN_LIST]: OnyxTypes.LoginList; [ONYXKEYS.SESSION]: OnyxTypes.Session; + [ONYXKEYS.STASHED_SESSION]: OnyxTypes.Session; [ONYXKEYS.BETAS]: OnyxTypes.Beta[]; [ONYXKEYS.NVP_PRIORITY_MODE]: ValueOf; [ONYXKEYS.NVP_BLOCKED_FROM_CONCIERGE]: OnyxTypes.BlockedFromConcierge; diff --git a/src/languages/en.ts b/src/languages/en.ts index cfbc3ecd0565..bb40462c4e2a 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -866,6 +866,7 @@ export default { }, security: 'Security', signOut: 'Sign out', + restoreStashed: 'Restore stashed login', signOutConfirmationText: "You'll lose any offline changes if you sign-out.", versionLetter: 'v', readTheTermsAndPrivacy: { diff --git a/src/languages/es.ts b/src/languages/es.ts index 9aea06797cc5..ab4791711a19 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -860,6 +860,7 @@ export default { logSizeTooLarge: ({size}: LogSizeParams) => `El tamaño del registro excede el límite de ${size} MB. Utilice "Guardar registro" para descargar el archivo de registro.`, }, security: 'Seguridad', + restoreStashed: 'Restablecer login guardado', signOut: 'Desconectar', signOutConfirmationText: 'Si cierras sesión perderás los cambios hechos mientras estabas desconectado', versionLetter: 'v', diff --git a/src/libs/API/parameters/SignInWithSupportAuthTokenParams.ts b/src/libs/API/parameters/SignInWithSupportAuthTokenParams.ts new file mode 100644 index 000000000000..ad205fce236e --- /dev/null +++ b/src/libs/API/parameters/SignInWithSupportAuthTokenParams.ts @@ -0,0 +1,5 @@ +type SignInWithSupportAuthTokenParams = { + authToken: string; +}; + +export default SignInWithSupportAuthTokenParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index 4fbc597b8186..679379c8089a 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -51,6 +51,7 @@ export type {default as SendPerformanceTimingParams} from './SendPerformanceTimi export type {default as SetContactMethodAsDefaultParams} from './SetContactMethodAsDefaultParams'; export type {default as SignInUserWithLinkParams} from './SignInUserWithLinkParams'; export type {default as SignInWithShortLivedAuthTokenParams} from './SignInWithShortLivedAuthTokenParams'; +export type {default as SignInWithSupportAuthTokenParams} from './SignInWithSupportAuthTokenParams'; export type {default as UnlinkLoginParams} from './UnlinkLoginParams'; export type {default as UpdateAutomaticTimezoneParams} from './UpdateAutomaticTimezoneParams'; export type {default as UpdateChatPriorityModeParams} from './UpdateChatPriorityModeParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index ba49bc5fa27b..cacbfebdc120 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -336,6 +336,7 @@ const READ_COMMANDS = { OPEN_ENABLE_PAYMENTS_PAGE: 'OpenEnablePaymentsPage', BEGIN_SIGNIN: 'BeginSignIn', SIGN_IN_WITH_SHORT_LIVED_AUTH_TOKEN: 'SignInWithShortLivedAuthToken', + SIGN_IN_WITH_SUPPORT_AUTH_TOKEN: 'SignInWithSupportAuthToken', OPEN_WORKSPACE_REIMBURSE_VIEW: 'OpenWorkspaceReimburseView', OPEN_WORKSPACE: 'OpenWorkspace', OPEN_WORKSPACE_MEMBERS_PAGE: 'OpenWorkspaceMembersPage', @@ -369,6 +370,7 @@ type ReadCommandParameters = { [READ_COMMANDS.OPEN_ENABLE_PAYMENTS_PAGE]: EmptyObject; [READ_COMMANDS.BEGIN_SIGNIN]: Parameters.BeginSignInParams; [READ_COMMANDS.SIGN_IN_WITH_SHORT_LIVED_AUTH_TOKEN]: Parameters.SignInWithShortLivedAuthTokenParams; + [READ_COMMANDS.SIGN_IN_WITH_SUPPORT_AUTH_TOKEN]: Parameters.SignInWithSupportAuthTokenParams; [READ_COMMANDS.OPEN_WORKSPACE_REIMBURSE_VIEW]: Parameters.OpenWorkspaceReimburseViewParams; [READ_COMMANDS.OPEN_WORKSPACE]: Parameters.OpenWorkspaceParams; [READ_COMMANDS.OPEN_WORKSPACE_MEMBERS_PAGE]: Parameters.OpenWorkspaceMembersPageParams; diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.tsx b/src/libs/Navigation/AppNavigator/AuthScreens.tsx index 30a8010ad801..fc89b53fbefd 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreens.tsx +++ b/src/libs/Navigation/AppNavigator/AuthScreens.tsx @@ -169,8 +169,9 @@ function AuthScreens({session, lastOpenedPublicRoomID, isUsingMemoryOnlyKeys = f const shouldGetAllData = !!isUsingMemoryOnlyKeys || SessionUtils.didUserLogInDuringSession(); // Sign out the current user if we're transitioning with a different user const isTransitioning = currentUrl.includes(ROUTES.TRANSITION_BETWEEN_APPS); + const isSupportalTransition = currentUrl.includes('authTokenType=support'); if (isLoggingInAsNewUser && isTransitioning) { - Session.signOutAndRedirectToSignIn(); + Session.signOutAndRedirectToSignIn(false, isSupportalTransition); return; } diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 9426ca6e900c..6ffbb3a358b0 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -519,14 +519,17 @@ type BottomTabNavigatorParamList = { [SCREENS.WORKSPACE.INITIAL]: undefined; }; -type PublicScreensParamList = { +type SharedScreensParamList = { [NAVIGATORS.BOTTOM_TAB_NAVIGATOR]: NavigatorScreenParams; [SCREENS.TRANSITION_BETWEEN_APPS]: { email?: string; + accountID?: number; error?: string; shortLivedAuthToken?: string; shortLivedToken?: string; + authTokenType?: ValueOf; exitTo?: Routes | HybridAppRoute; + shouldForceLogin: string; domain?: Routes; }; [SCREENS.VALIDATE_LOGIN]: { @@ -534,6 +537,9 @@ type PublicScreensParamList = { validateCode: string; exitTo?: Routes | HybridAppRoute; }; +}; + +type PublicScreensParamList = SharedScreensParamList & { [SCREENS.UNLINK_LOGIN]: { accountID?: string; validateCode?: string; @@ -543,19 +549,8 @@ type PublicScreensParamList = { [SCREENS.SAML_SIGN_IN]: undefined; }; -type AuthScreensParamList = { - [NAVIGATORS.BOTTOM_TAB_NAVIGATOR]: NavigatorScreenParams; +type AuthScreensParamList = SharedScreensParamList & { [NAVIGATORS.CENTRAL_PANE_NAVIGATOR]: NavigatorScreenParams; - [SCREENS.VALIDATE_LOGIN]: { - accountID: string; - validateCode: string; - }; - [SCREENS.TRANSITION_BETWEEN_APPS]: { - shouldForceLogin: string; - email: string; - shortLivedAuthToken: string; - exitTo: string; - }; [SCREENS.CONCIERGE]: undefined; [SCREENS.REPORT_ATTACHMENTS]: { reportID: string; diff --git a/src/libs/Network/NetworkStore.ts b/src/libs/Network/NetworkStore.ts index 9f79064d5acc..086beddc970e 100644 --- a/src/libs/Network/NetworkStore.ts +++ b/src/libs/Network/NetworkStore.ts @@ -1,11 +1,13 @@ import Onyx from 'react-native-onyx'; +import type {ValueOf} from 'type-fest'; import {SIDE_EFFECT_REQUEST_COMMANDS, WRITE_COMMANDS} from '@libs/API/types'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type Credentials from '@src/types/onyx/Credentials'; let credentials: Credentials | null = null; let authToken: string | null = null; -let supportAuthToken: string | null = null; +let authTokenType: ValueOf | null; let currentUserEmail: string | null = null; let offline = false; let authenticating = false; @@ -51,7 +53,7 @@ Onyx.connect({ key: ONYXKEYS.SESSION, callback: (val) => { authToken = val?.authToken ?? null; - supportAuthToken = val?.supportAuthToken ?? null; + authTokenType = val?.authTokenType ?? null; currentUserEmail = val?.email ?? null; checkRequiredData(); }, @@ -99,12 +101,8 @@ function isSupportRequest(command: string): boolean { return [WRITE_COMMANDS.OPEN_APP, SIDE_EFFECT_REQUEST_COMMANDS.RECONNECT_APP, SIDE_EFFECT_REQUEST_COMMANDS.OPEN_REPORT].some((cmd) => cmd === command); } -function getSupportAuthToken(): string | null { - return supportAuthToken; -} - -function setSupportAuthToken(newSupportAuthToken: string) { - supportAuthToken = newSupportAuthToken; +function isSupportAuthToken(): boolean { + return authTokenType === CONST.AUTH_TOKEN_TYPES.SUPPORT; } function setAuthToken(newAuthToken: string | null) { @@ -139,7 +137,6 @@ export { setIsAuthenticating, getCredentials, checkRequiredData, - getSupportAuthToken, - setSupportAuthToken, + isSupportAuthToken, isSupportRequest, }; diff --git a/src/libs/Network/enhanceParameters.ts b/src/libs/Network/enhanceParameters.ts index e14acda5ff56..b8b8993a52b0 100644 --- a/src/libs/Network/enhanceParameters.ts +++ b/src/libs/Network/enhanceParameters.ts @@ -16,12 +16,8 @@ function isAuthTokenRequired(command: string): boolean { export default function enhanceParameters(command: string, parameters: Record): Record { const finalParameters = {...parameters}; - if (isAuthTokenRequired(command)) { - if (NetworkStore.getSupportAuthToken() && NetworkStore.isSupportRequest(command)) { - finalParameters.authToken = NetworkStore.getSupportAuthToken(); - } else if (!parameters.authToken) { - finalParameters.authToken = NetworkStore.getAuthToken(); - } + if (isAuthTokenRequired(command) && !parameters.authToken) { + finalParameters.authToken = NetworkStore.getAuthToken(); } finalParameters.referer = CONFIG.EXPENSIFY.EXPENSIFY_CASH_REFERER; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 094bf3026357..060b1911605e 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -439,7 +439,7 @@ Onyx.connect({ currentUserEmail = value.email; currentUserAccountID = value.accountID; - isAnonymousUser = value.authTokenType === CONST.AUTH_TOKEN_TYPE.ANONYMOUS; + isAnonymousUser = value.authTokenType === CONST.AUTH_TOKEN_TYPES.ANONYMOUS; currentUserPrivateDomain = isEmailPublicDomain(currentUserEmail ?? '') ? '' : Str.extractEmailDomain(currentUserEmail ?? ''); }, }); diff --git a/src/libs/Request.ts b/src/libs/Request.ts index fc31160bbc1c..8d51486efad1 100644 --- a/src/libs/Request.ts +++ b/src/libs/Request.ts @@ -12,7 +12,7 @@ function makeXHR(request: Request): Promise { return NetworkStore.hasReadRequiredDataFromStorage().then((): Promise => { // If we're using the Supportal token and this is not a Supportal request // let's just return a promise that will resolve itself. - if (NetworkStore.getSupportAuthToken() && !NetworkStore.isSupportRequest(request.command)) { + if (NetworkStore.isSupportAuthToken() && !NetworkStore.isSupportRequest(request.command)) { return new Promise((resolve) => { resolve(); }); diff --git a/src/libs/actions/Session/index.ts b/src/libs/actions/Session/index.ts index be389b1aa9c1..6a0f53c3d058 100644 --- a/src/libs/actions/Session/index.ts +++ b/src/libs/actions/Session/index.ts @@ -12,12 +12,10 @@ import type { BeginAppleSignInParams, BeginGoogleSignInParams, BeginSignInParams, - LogOutParams, RequestAccountValidationLinkParams, RequestNewValidateCodeParams, RequestUnlinkValidationLinkParams, SignInUserWithLinkParams, - SignInWithShortLivedAuthTokenParams, UnlinkLoginParams, ValidateTwoFactorAuthParams, } from '@libs/API/parameters'; @@ -50,44 +48,120 @@ import ROUTES from '@src/ROUTES'; import SCREENS from '@src/SCREENS'; import type Credentials from '@src/types/onyx/Credentials'; import type {AutoAuthState} from '@src/types/onyx/Session'; +import type Session from '@src/types/onyx/Session'; import clearCache from './clearCache'; -let sessionAuthTokenType: string | null = ''; -let sessionAuthToken: string | null = null; +let session: Session = {}; let authPromiseResolver: ((value: boolean) => void) | null = null; - Onyx.connect({ key: ONYXKEYS.SESSION, - callback: (session) => { - sessionAuthTokenType = session?.authTokenType ?? null; - sessionAuthToken = session?.authToken ?? null; - - if (sessionAuthToken && authPromiseResolver) { + callback: (value) => { + session = value ?? {}; + if (session.authToken && authPromiseResolver) { authPromiseResolver(true); authPromiseResolver = null; } }, }); +let stashedSession: Session = {}; +Onyx.connect({ + key: ONYXKEYS.STASHED_SESSION, + callback: (value) => (stashedSession = value ?? {}), +}); + let credentials: Credentials = {}; Onyx.connect({ key: ONYXKEYS.CREDENTIALS, callback: (value) => (credentials = value ?? {}), }); +let stashedCredentials: Credentials = {}; +Onyx.connect({ + key: ONYXKEYS.STASHED_CREDENTIALS, + callback: (value) => (stashedCredentials = value ?? {}), +}); + let preferredLocale: ValueOf | null = null; Onyx.connect({ key: ONYXKEYS.NVP_PREFERRED_LOCALE, callback: (val) => (preferredLocale = val), }); +function isSupportAuthToken(): boolean { + return session.authTokenType === CONST.AUTH_TOKEN_TYPES.SUPPORT; +} + +/** + * Sets the SupportToken. This method will only be used on dev. + */ +function setSupportAuthToken(supportAuthToken: string, email: string, accountID: number) { + Onyx.merge(ONYXKEYS.SESSION, { + authTokenType: CONST.AUTH_TOKEN_TYPES.SUPPORT, + authToken: supportAuthToken, + email, + accountID, + }).then(() => { + Log.info('[Supportal] Authtoken set'); + }); + Onyx.set(ONYXKEYS.LAST_VISITED_PATH, ''); +} + +function getShortLivedLoginParams() { + const optimisticData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.ACCOUNT, + value: { + ...CONST.DEFAULT_ACCOUNT_DATA, + isLoading: true, + }, + }, + // We are making a temporary modification to 'signedInWithShortLivedAuthToken' to ensure that 'App.openApp' will be called at least once + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.SESSION, + value: { + signedInWithShortLivedAuthToken: true, + }, + }, + ]; + + // Subsequently, we revert it back to the default value of 'signedInWithShortLivedAuthToken' in 'finallyData' to ensure the user is logged out on refresh + const finallyData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.ACCOUNT, + value: { + isLoading: false, + }, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.SESSION, + value: { + signedInWithShortLivedAuthToken: null, + }, + }, + ]; + + return {optimisticData, finallyData}; +} + +/** + * This method should be used when we are being redirected from oldDot to NewDot on a supportal request + */ +function signInWithSupportAuthToken(authToken: string) { + const {optimisticData, finallyData} = getShortLivedLoginParams(); + API.read(READ_COMMANDS.SIGN_IN_WITH_SUPPORT_AUTH_TOKEN, {authToken}, {optimisticData, finallyData}); +} + /** * Clears the Onyx store and redirects user to the sign in page */ function signOut() { Log.info('Flushing logs before signing out', true, {}, true); - - const params: LogOutParams = { + const params = { // Send current authToken because we will immediately clear it once triggering this command authToken: NetworkStore.getAuthToken(), partnerUserID: credentials?.autoGeneratedLogin ?? '', @@ -103,15 +177,58 @@ function signOut() { * Checks if the account is an anonymous account. */ function isAnonymousUser(): boolean { - return sessionAuthTokenType === CONST.AUTH_TOKEN_TYPE.ANONYMOUS; + return session.authTokenType === CONST.AUTH_TOKEN_TYPES.ANONYMOUS; +} + +function hasStashedSession(): boolean { + return Boolean(stashedSession.authToken && stashedCredentials.autoGeneratedLogin && stashedCredentials.autoGeneratedLogin !== ''); } -function signOutAndRedirectToSignIn(shouldReplaceCurrentScreen?: boolean) { +function signOutAndRedirectToSignIn(shouldReplaceCurrentScreen?: boolean, shouldStashSession?: boolean) { Log.info('Redirecting to Sign In because signOut() was called'); hideContextMenu(false); if (!isAnonymousUser()) { - signOut(); - redirectToSignIn(); + // We'll only call signOut if we're not stashing the session and this is not a supportal session, + // otherwise we'll call the API to invalidate the autogenerated credentials used for infinite + // session. + const isSupportal = isSupportAuthToken(); + if (!isSupportal && !shouldStashSession) { + signOut(); + } + + // The function redirectToSignIn will clear the whole storage, so let's create our onyx params + // updates for the credentials before we call it + let onyxSetParams = {}; + + // If we are not currently using a support token, and we received stashSession as true, we need to + // store the credentials so the user doesn't need to login again after they finish their supportal + // action. This needs to computed before we call `redirectToSignIn` + if (!isSupportal && shouldStashSession) { + onyxSetParams = { + [ONYXKEYS.STASHED_CREDENTIALS]: credentials, + [ONYXKEYS.STASHED_SESSION]: session, + }; + } + // If this is a supportal token, and we've received the parameters to stashSession as true, and + // we already have a stashedSession, that means we are supportaled, currently supportaling + // into another account and we want to keep the stashed data from the original account. + if (isSupportal && shouldStashSession && hasStashedSession()) { + onyxSetParams = { + [ONYXKEYS.STASHED_CREDENTIALS]: stashedCredentials, + [ONYXKEYS.STASHED_SESSION]: stashedSession, + }; + } + // Now if this is a supportal access, we do not want to stash the current session and we have a + // stashed session, then we need to restore the stashed session instead of completely logging out + if (isSupportal && !shouldStashSession && hasStashedSession()) { + onyxSetParams = { + [ONYXKEYS.CREDENTIALS]: stashedCredentials, + [ONYXKEYS.SESSION]: stashedSession, + }; + } + redirectToSignIn().then(() => { + Onyx.multiSet(onyxSetParams); + }); } else { if (Navigation.isActiveRoute(ROUTES.SIGN_IN_MODAL)) { return; @@ -310,55 +427,13 @@ function beginGoogleSignIn(token: string | null) { * re-authenticating after an authToken expires. */ function signInWithShortLivedAuthToken(email: string, authToken: string) { - const optimisticData: OnyxUpdate[] = [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.ACCOUNT, - value: { - ...CONST.DEFAULT_ACCOUNT_DATA, - isLoading: true, - }, - }, - // We are making a temporary modification to 'signedInWithShortLivedAuthToken' to ensure that 'App.openApp' will be called at least once - { - onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.SESSION, - value: { - signedInWithShortLivedAuthToken: true, - }, - }, - ]; - - // Subsequently, we revert it back to the default value of 'signedInWithShortLivedAuthToken' in 'successData' or 'failureData' to ensure the user is logged out on refresh - // We are combining both success and failure data params into one const as they are identical - const resolutionData: OnyxUpdate[] = [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.ACCOUNT, - value: { - isLoading: false, - }, - }, - { - onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.SESSION, - value: { - signedInWithShortLivedAuthToken: null, - }, - }, - ]; - - const successData = resolutionData; - const failureData = resolutionData; + const {optimisticData, finallyData} = getShortLivedLoginParams(); // 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 ? credentials.autoGeneratedLogin : ''; - - const params: SignInWithShortLivedAuthTokenParams = {authToken, oldPartnerUserID, skipReauthentication: true}; - - API.read(READ_COMMANDS.SIGN_IN_WITH_SHORT_LIVED_AUTH_TOKEN, params, {optimisticData, successData, failureData}); + API.read(READ_COMMANDS.SIGN_IN_WITH_SHORT_LIVED_AUTH_TOKEN, {authToken, oldPartnerUserID, skipReauthentication: true}, {optimisticData, finallyData}); } /** @@ -526,23 +601,6 @@ function invalidateAuthToken() { Onyx.merge(ONYXKEYS.SESSION, {authToken: 'pizza'}); } -/** - * Sets the SupportToken - */ -function setSupportAuthToken(supportAuthToken: string, email: string, accountID: number) { - if (supportAuthToken) { - Onyx.merge(ONYXKEYS.SESSION, { - authToken: '1', - supportAuthToken, - email, - accountID, - }); - } else { - Onyx.set(ONYXKEYS.SESSION, {}); - } - NetworkStore.setSupportAuthToken(supportAuthToken); -} - /** * Clear the credentials and partial sign in session so the user can taken back to first Login step */ @@ -843,7 +901,7 @@ function validateTwoFactorAuth(twoFactorAuthCode: string) { */ function waitForUserSignIn(): Promise { return new Promise((resolve) => { - if (sessionAuthToken) { + if (session.authToken) { resolve(true); } else { authPromiseResolver = resolve; @@ -929,4 +987,7 @@ export { validateTwoFactorAuth, waitForUserSignIn, canAccessRouteByAnonymousUser, + signInWithSupportAuthToken, + isSupportAuthToken, + hasStashedSession, }; diff --git a/src/libs/actions/SignInRedirect.ts b/src/libs/actions/SignInRedirect.ts index ecb09ccd1804..c6d4ffcaa30f 100644 --- a/src/libs/actions/SignInRedirect.ts +++ b/src/libs/actions/SignInRedirect.ts @@ -13,7 +13,7 @@ Onyx.connect({ }, }); -function clearStorageAndRedirect(errorMessage?: string) { +function clearStorageAndRedirect(errorMessage?: string): Promise { // Under certain conditions, there are key-values we'd like to keep in storage even when a user is logged out. // We pass these into the clear() method in order to avoid having to reset them on a delayed tick and getting // flashes of unwanted default state. @@ -28,7 +28,7 @@ function clearStorageAndRedirect(errorMessage?: string) { keysToPreserve.push(ONYXKEYS.NETWORK); } - Onyx.clear(keysToPreserve).then(() => { + return Onyx.clear(keysToPreserve).then(() => { if (!errorMessage) { return; } @@ -46,8 +46,8 @@ function clearStorageAndRedirect(errorMessage?: string) { * * @param [errorMessage] error message to be displayed on the sign in page */ -function redirectToSignIn(errorMessage?: string) { - clearStorageAndRedirect(errorMessage); +function redirectToSignIn(errorMessage?: string): Promise { + return clearStorageAndRedirect(errorMessage); } export default redirectToSignIn; diff --git a/src/pages/LogInWithShortLivedAuthTokenPage.tsx b/src/pages/LogInWithShortLivedAuthTokenPage.tsx index 4e7372f10dc6..d002b87b15fa 100644 --- a/src/pages/LogInWithShortLivedAuthTokenPage.tsx +++ b/src/pages/LogInWithShortLivedAuthTokenPage.tsx @@ -16,7 +16,9 @@ import Log from '@libs/Log'; import Navigation from '@libs/Navigation/Navigation'; import type {PublicScreensParamList} from '@libs/Navigation/types'; import * as Session from '@userActions/Session'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; import type {Account} from '@src/types/onyx'; @@ -31,12 +33,22 @@ function LogInWithShortLivedAuthTokenPage({route, account}: LogInWithShortLivedA const theme = useTheme(); const styles = useThemeStyles(); const {translate} = useLocalize(); - const {email = '', shortLivedAuthToken = '', shortLivedToken = '', exitTo, error} = route?.params ?? {}; + const {email = '', shortLivedAuthToken = '', shortLivedToken = '', authTokenType, exitTo, error} = route?.params ?? {}; useEffect(() => { // We have to check for both shortLivedAuthToken and shortLivedToken, as the old mobile app uses shortLivedToken, and is not being actively updated. const token = shortLivedAuthToken || shortLivedToken; + if (!account?.isLoading && authTokenType === CONST.AUTH_TOKEN_TYPES.SUPPORT) { + Session.signInWithSupportAuthToken(shortLivedAuthToken); + Navigation.isNavigationReady().then(() => { + // We must call goBack() to remove the /transition route from history + Navigation.goBack(); + Navigation.navigate(ROUTES.HOME); + }); + return; + } + // Try to authenticate using the shortLivedToken if we're not already trying to load the accounts if (token && !account?.isLoading) { Log.info('LogInWithShortLivedAuthTokenPage - Successfully received shortLivedAuthToken. Signing in...'); diff --git a/src/pages/LogOutPreviousUserPage.tsx b/src/pages/LogOutPreviousUserPage.tsx index f68344604dfa..3df7a4cdb9f3 100644 --- a/src/pages/LogOutPreviousUserPage.tsx +++ b/src/pages/LogOutPreviousUserPage.tsx @@ -4,10 +4,13 @@ import {Linking} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; +import Navigation from '@libs/Navigation/Navigation'; import * as SessionUtils from '@libs/SessionUtils'; import type {AuthScreensParamList} from '@navigation/types'; import * as SessionActions from '@userActions/Session'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; import type {Session} from '@src/types/onyx'; @@ -27,9 +30,20 @@ function LogOutPreviousUserPage({session, route}: LogOutPreviousUserPageProps) { Linking.getInitialURL().then((transitionURL) => { const sessionEmail = session?.email; const isLoggingInAsNewUser = SessionUtils.isLoggingInAsNewUser(transitionURL ?? undefined, sessionEmail); + const isSupportalLogin = route.params.authTokenType === CONST.AUTH_TOKEN_TYPES.SUPPORT; if (isLoggingInAsNewUser) { - SessionActions.signOutAndRedirectToSignIn(); + SessionActions.signOutAndRedirectToSignIn(false, isSupportalLogin); + } + + if (isSupportalLogin) { + SessionActions.signInWithSupportAuthToken(route.params.shortLivedAuthToken ?? ''); + Navigation.isNavigationReady().then(() => { + // We must call goBack() to remove the /transition route from history + Navigation.goBack(); + Navigation.navigate(ROUTES.HOME); + }); + return; } // We need to signin and fetch a new authToken, if a user was already authenticated in NewDot, and was redirected to OldDot diff --git a/src/pages/home/report/ReportActionsView.js b/src/pages/home/report/ReportActionsView.js index ed414de134f4..5e9d863dd62d 100755 --- a/src/pages/home/report/ReportActionsView.js +++ b/src/pages/home/report/ReportActionsView.js @@ -140,7 +140,7 @@ function ReportActionsView(props) { }, [props.network, isReportFullyVisible]); useEffect(() => { - const wasLoginChangedDetected = prevAuthTokenType === CONST.AUTH_TOKEN_TYPE.ANONYMOUS && !props.session.authTokenType; + const wasLoginChangedDetected = prevAuthTokenType === CONST.AUTH_TOKEN_TYPES.ANONYMOUS && !props.session.authTokenType; if (wasLoginChangedDetected && didUserLogInDuringSession() && isUserCreatedPolicyRoom(props.report)) { if (isReportFullyVisible) { openReportIfNecessary(); diff --git a/src/pages/home/report/ReportFooter.js b/src/pages/home/report/ReportFooter.js index 3b9f8f03c320..ec242116269e 100644 --- a/src/pages/home/report/ReportFooter.js +++ b/src/pages/home/report/ReportFooter.js @@ -67,7 +67,7 @@ function ReportFooter(props) { const {isOffline} = useNetwork(); const chatFooterStyles = {...styles.chatFooter, minHeight: !isOffline ? CONST.CHAT_FOOTER_MIN_HEIGHT : 0}; const isArchivedRoom = ReportUtils.isArchivedRoom(props.report); - const isAnonymousUser = props.session.authTokenType === CONST.AUTH_TOKEN_TYPE.ANONYMOUS; + const isAnonymousUser = props.session.authTokenType === CONST.AUTH_TOKEN_TYPES.ANONYMOUS; const isSmallSizeLayout = props.windowWidth - (props.isSmallScreenWidth ? 0 : variables.sideBarWidth) < variables.anonymousReportFooterBreakpoint; const hideComposer = !ReportUtils.canUserPerformWriteAction(props.report); diff --git a/src/pages/settings/InitialSettingsPage.tsx b/src/pages/settings/InitialSettingsPage.tsx index 2f21ee61e8cc..b29fd600ae16 100755 --- a/src/pages/settings/InitialSettingsPage.tsx +++ b/src/pages/settings/InitialSettingsPage.tsx @@ -129,7 +129,7 @@ function InitialSettingsPage({session, userWallet, bankAccountList, fundList, wa const accountMenuItemsData: Menu = useMemo(() => { const profileBrickRoadIndicator = UserUtils.getLoginListBrickRoadIndicator(loginList); const paymentCardList = fundList; - + const signOutTranslationKey = Session.isSupportAuthToken() && Session.hasStashedSession() ? 'initialSettingsPage.restoreStashed' : 'initialSettingsPage.signOut'; const defaultMenu: Menu = { sectionStyle: styles.accountSettingsSectionContainer, sectionTranslationKey: 'initialSettingsPage.account', @@ -165,7 +165,7 @@ function InitialSettingsPage({session, userWallet, bankAccountList, fundList, wa routeName: ROUTES.SETTINGS_SECURITY, }, { - translationKey: 'initialSettingsPage.signOut', + translationKey: signOutTranslationKey, icon: Expensicons.Exit, action: () => { signOut(false); diff --git a/src/pages/signin/SignInModal.tsx b/src/pages/signin/SignInModal.tsx index e71ee6e9a988..2029d07353a5 100644 --- a/src/pages/signin/SignInModal.tsx +++ b/src/pages/signin/SignInModal.tsx @@ -23,7 +23,7 @@ function SignInModal({session}: SignInModalProps) { const StyleUtils = useStyleUtils(); useEffect(() => { - const isAnonymousUser = session?.authTokenType === CONST.AUTH_TOKEN_TYPE.ANONYMOUS; + const isAnonymousUser = session?.authTokenType === CONST.AUTH_TOKEN_TYPES.ANONYMOUS; if (!isAnonymousUser) { // Signing in RHP is only for anonymous users Navigation.isNavigationReady().then(() => Navigation.dismissModal()); diff --git a/src/types/onyx/Session.ts b/src/types/onyx/Session.ts index 88417a03d903..d181114d02d3 100644 --- a/src/types/onyx/Session.ts +++ b/src/types/onyx/Session.ts @@ -12,7 +12,7 @@ type Session = { authToken?: string; /** Currently logged in user authToken type */ - authTokenType?: string; + authTokenType?: ValueOf; /** Currently logged in user support authToken */ supportAuthToken?: string;