diff --git a/_scripts/config-fxios.js b/_scripts/config-fxios.js index 579752058d..43bc54b748 100644 --- a/_scripts/config-fxios.js +++ b/_scripts/config-fxios.js @@ -19,7 +19,12 @@ const replaceFrom = /\bserver: server\b/; const replaceTo = 'contentUrl: "http://localhost:3030"'; function replace() { - const filePath = path.join(iosPath, 'RustFxA', 'RustFirefoxAccounts.swift'); + const filePath = path.join( + iosPath, + 'firefox-ios', + 'RustFxA', + 'RustFirefoxAccounts.swift' + ); let fileContent = fs.readFileSync(filePath, 'utf8'); const match = fileContent.match(regex); diff --git a/packages/fxa-settings/src/lib/channels/firefox.ts b/packages/fxa-settings/src/lib/channels/firefox.ts index 6f6d591654..2925666b7d 100644 --- a/packages/fxa-settings/src/lib/channels/firefox.ts +++ b/packages/fxa-settings/src/lib/channels/firefox.ts @@ -108,7 +108,7 @@ export type FxAOAuthLogin = { code: string; redirect: string; state: string; - // For sync oauth + // For sync oauth signup declinedSyncEngines?: string[]; offeredSyncEngines?: string[]; }; @@ -337,7 +337,7 @@ export class Firefox extends EventTarget { } fxaCanLinkAccount(options: FxACanLinkAccount) { - this.send(FirefoxCommand.Login, options); + this.send(FirefoxCommand.CanLinkAccount, options); } async requestSignedInUser(context: string) { diff --git a/packages/fxa-settings/src/pages/Signin/SigninTokenCode/container.test.tsx b/packages/fxa-settings/src/pages/Signin/SigninTokenCode/container.test.tsx index 745e3fadaa..12abb422ae 100644 --- a/packages/fxa-settings/src/pages/Signin/SigninTokenCode/container.test.tsx +++ b/packages/fxa-settings/src/pages/Signin/SigninTokenCode/container.test.tsx @@ -131,9 +131,7 @@ describe('SigninTokenCode container', () => { await waitFor(() => { expect(CacheModule.currentAccount).not.toBeCalled(); }); - expect(currentSigninTokenCodeProps?.signinLocationState.email).toBe( - MOCK_EMAIL - ); + expect(currentSigninTokenCodeProps?.signinState.email).toBe(MOCK_EMAIL); expect(currentSigninTokenCodeProps?.integration).toBe(integration); expect(SigninTokenCodeModule.default).toBeCalled(); }); @@ -142,7 +140,7 @@ describe('SigninTokenCode container', () => { render(); expect(CacheModule.currentAccount).not.toBeCalled(); await waitFor(() => { - expect(currentSigninTokenCodeProps?.signinLocationState.email).toBe( + expect(currentSigninTokenCodeProps?.signinState.email).toBe( MOCK_EMAIL ); }); @@ -154,7 +152,7 @@ describe('SigninTokenCode container', () => { render(); expect(CacheModule.currentAccount).toBeCalled(); await waitFor(() => { - expect(currentSigninTokenCodeProps?.signinLocationState.email).toBe( + expect(currentSigninTokenCodeProps?.signinState.email).toBe( MOCK_STORED_ACCOUNT.email ); }); diff --git a/packages/fxa-settings/src/pages/Signin/SigninTokenCode/container.tsx b/packages/fxa-settings/src/pages/Signin/SigninTokenCode/container.tsx index 509c0b8912..ccc1f3b9e0 100644 --- a/packages/fxa-settings/src/pages/Signin/SigninTokenCode/container.tsx +++ b/packages/fxa-settings/src/pages/Signin/SigninTokenCode/container.tsx @@ -29,7 +29,7 @@ const SigninTokenCodeContainer = ({ state?: SigninLocationState; }; - const signinLocationState = + const signinState = location.state && Object.keys(location.state).length > 0 ? location.state : getStoredAccountInfo(); @@ -44,8 +44,8 @@ const SigninTokenCodeContainer = ({ const { data: totpData, loading: totpLoading } = useQuery(GET_TOTP_STATUS); - if (Object.keys(signinLocationState).length < 1) { - hardNavigateToContentServer(`/${location.search ? location.search : ''}`); + if (Object.keys(signinState).length < 1) { + hardNavigateToContentServer(`/${location.search || ''}`); return ; } @@ -77,7 +77,7 @@ const SigninTokenCodeContainer = ({ {...{ finishOAuthFlowHandler, integration, - signinLocationState, + signinState, }} /> ); diff --git a/packages/fxa-settings/src/pages/Signin/SigninTokenCode/index.test.tsx b/packages/fxa-settings/src/pages/Signin/SigninTokenCode/index.test.tsx index f1da95cfb0..29862164f4 100644 --- a/packages/fxa-settings/src/pages/Signin/SigninTokenCode/index.test.tsx +++ b/packages/fxa-settings/src/pages/Signin/SigninTokenCode/index.test.tsx @@ -18,8 +18,13 @@ import { Subject } from './mocks'; import { MOCK_SIGNUP_CODE } from '../../Signup/ConfirmSignupCode/mocks'; import { MOCK_EMAIL, MOCK_OAUTH_FLOW_HANDLER_RESPONSE } from '../../mocks'; import { AuthUiErrors } from '../../../lib/auth-errors/auth-errors'; -import { createMockSigninOAuthIntegration } from '../mocks'; +import { + createMockSigninOAuthIntegration, + createMockSigninSyncIntegration, +} from '../mocks'; +import { createMockSigninLocationState } from './mocks'; import VerificationReasons from '../../../constants/verification-reasons'; +import firefox from '../../../lib/channels/firefox'; jest.mock('../../../lib/metrics', () => ({ usePageViewEvent: jest.fn(), @@ -45,22 +50,13 @@ function applyDefaultMocks() { mockReactUtilsModule(); } -// Set this when testing location state -let mockLocationState = {}; -const mockLocation = () => { - return { - pathname: '/signin_token_code', - search: '?' + new URLSearchParams(mockLocationState), - state: mockLocationState, - }; -}; const mockNavigate = jest.fn(); jest.mock('@reach/router', () => { return { __esModule: true, ...jest.requireActual('@reach/router'), useNavigate: () => mockNavigate, - useLocation: () => mockLocation(), + useLocation: () => () => {}, }; }); @@ -129,6 +125,24 @@ describe('SigninTokenCode page', () => { expect(GleanMetrics.loginConfirmation.view).toBeCalledTimes(1); }); + describe('fxaLogin webchannel message', () => { + let fxaLoginSpy: jest.SpyInstance; + beforeEach(() => { + fxaLoginSpy = jest.spyOn(firefox, 'fxaLogin'); + }); + it('is sent if Sync integration', () => { + const integration = createMockSigninSyncIntegration(); + render({ integration }); + expect(fxaLoginSpy).toHaveBeenCalledWith({ + ...createMockSigninLocationState(integration.wantsKeys()), + }); + }); + it('is not sent otherwise', () => { + render(); + expect(fxaLoginSpy).not.toBeCalled(); + }); + }); + describe('handleResendCode submission', () => { async function renderAndResend() { render(); diff --git a/packages/fxa-settings/src/pages/Signin/SigninTokenCode/index.tsx b/packages/fxa-settings/src/pages/Signin/SigninTokenCode/index.tsx index 8826473e46..2f1c50853a 100644 --- a/packages/fxa-settings/src/pages/Signin/SigninTokenCode/index.tsx +++ b/packages/fxa-settings/src/pages/Signin/SigninTokenCode/index.tsx @@ -26,6 +26,7 @@ import Banner, { ResendEmailSuccessBanner, } from '../../../components/Banner'; import { handleNavigation } from '../utils'; +import firefox from '../../../lib/channels/firefox'; export const viewName = 'signin-token-code'; @@ -34,7 +35,7 @@ const SIX_DIGIT_NUMBER_REGEX = /^\d{6}$/; const SigninTokenCode = ({ finishOAuthFlowHandler, integration, - signinLocationState, + signinState, }: SigninTokenCodeProps & RouteComponentProps) => { usePageViewEvent(viewName, REACT_ENTRYPOINT); const session = useSession(); @@ -48,7 +49,7 @@ const SigninTokenCode = ({ verificationReason, keyFetchToken, unwrapBKey, - } = signinLocationState; + } = signinState; const [banner, setBanner] = useState>({ type: undefined, @@ -81,6 +82,24 @@ const SigninTokenCode = ({ GleanMetrics.loginConfirmation.view(); }, []); + useEffect(() => { + (async () => { + if (integration.isSync()) { + await firefox.fxaLogin({ + email, + // keyFetchToken and unwrapBKey should always exist if Sync integration + keyFetchToken: keyFetchToken!, + unwrapBKey: unwrapBKey!, + sessionToken, + uid, + verified: false, + }); + } + })(); + // Only send webchannel message if sync on initial render + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + const handleAnimationEnd = () => { // We add the "shake" animation to bring attention to the success banner // when the success banner was already displayed. We have to remove the diff --git a/packages/fxa-settings/src/pages/Signin/SigninTokenCode/interfaces.ts b/packages/fxa-settings/src/pages/Signin/SigninTokenCode/interfaces.ts index 836d02117c..46647f0b4d 100644 --- a/packages/fxa-settings/src/pages/Signin/SigninTokenCode/interfaces.ts +++ b/packages/fxa-settings/src/pages/Signin/SigninTokenCode/interfaces.ts @@ -9,7 +9,7 @@ import { SigninIntegration, SigninLocationState } from '../interfaces'; export interface SigninTokenCodeProps { finishOAuthFlowHandler: FinishOAuthFlowHandler; integration: SigninIntegration; - signinLocationState: SigninLocationState; + signinState: SigninLocationState; } export interface TotpStatusResponse { diff --git a/packages/fxa-settings/src/pages/Signin/SigninTokenCode/mocks.tsx b/packages/fxa-settings/src/pages/Signin/SigninTokenCode/mocks.tsx index 8d0c1b999c..0d1e98f62b 100644 --- a/packages/fxa-settings/src/pages/Signin/SigninTokenCode/mocks.tsx +++ b/packages/fxa-settings/src/pages/Signin/SigninTokenCode/mocks.tsx @@ -3,13 +3,15 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { LocationProvider } from '@reach/router'; -import { Integration, IntegrationType } from '../../../models'; +import { IntegrationType } from '../../../models'; import { SigninTokenCodeProps } from './interfaces'; import SigninTokenCode from '.'; import { MOCK_EMAIL, + MOCK_KEY_FETCH_TOKEN, MOCK_SESSION_TOKEN, MOCK_UID, + MOCK_UNWRAP_BKEY, mockFinishOAuthFlowHandler, } from '../../mocks'; import { MozServices } from '../../../lib/types'; @@ -21,10 +23,11 @@ export function createMockWebIntegration() { getService: () => MozServices.Default, isSync: () => false, wantsKeys: () => false, - } as Integration; + }; } export const createMockSigninLocationState = ( + wantsKeys = false, verificationReason?: VerificationReasons ) => { return { @@ -32,7 +35,11 @@ export const createMockSigninLocationState = ( uid: MOCK_UID, sessionToken: MOCK_SESSION_TOKEN, verified: false, - ...(verificationReason && { verificationReason }), + verificationReason, + ...(wantsKeys && { + keyFetchToken: MOCK_KEY_FETCH_TOKEN, + unwrapBKey: MOCK_UNWRAP_BKEY, + }), }; }; @@ -50,7 +57,10 @@ export const Subject = ({ finishOAuthFlowHandler, integration, }} - signinLocationState={createMockSigninLocationState(verificationReason)} + signinState={createMockSigninLocationState( + integration.wantsKeys(), + verificationReason + )} /> ); diff --git a/packages/fxa-settings/src/pages/Signin/SigninTotpCode/index.test.tsx b/packages/fxa-settings/src/pages/Signin/SigninTotpCode/index.test.tsx index c519b2742f..5f627dfae2 100644 --- a/packages/fxa-settings/src/pages/Signin/SigninTotpCode/index.test.tsx +++ b/packages/fxa-settings/src/pages/Signin/SigninTotpCode/index.test.tsx @@ -16,9 +16,14 @@ import { } from '../../../lib/auth-errors/auth-errors'; import { Subject } from './mocks'; import { MOCK_OAUTH_FLOW_HANDLER_RESPONSE } from '../../mocks'; -import { createMockSigninOAuthIntegration } from '../mocks'; +import { + createMockSigninOAuthIntegration, + createMockSigninSyncIntegration, +} from '../mocks'; import { SigninIntegration } from '../interfaces'; import { FinishOAuthFlowHandler } from '../../../lib/oauth/hooks'; +import firefox from '../../../lib/channels/firefox'; +import * as utils from 'fxa-react/lib/utils'; jest.mock('../../../lib/metrics', () => ({ usePageViewEvent: jest.fn(), @@ -142,6 +147,41 @@ describe('Sign in with TOTP code page', () => { expect(mockNavigate).toHaveBeenCalledWith('/settings'); }); + // When CAD is converted to React, just test navigation since CAD will handle fxaLogin + describe('fxaLogin webchannel message (tempHandleSyncLogin)', () => { + let fxaLoginSpy: jest.SpyInstance; + let hardNavigateSpy: jest.SpyInstance; + beforeEach(() => { + fxaLoginSpy = jest.spyOn(firefox, 'fxaLogin'); + hardNavigateSpy = jest + .spyOn(utils, 'hardNavigate') + .mockImplementation(() => {}); + }); + it('is sent if Sync integration and navigates to CAD', async () => { + const integration = createMockSigninSyncIntegration(); + await waitFor(() => + renderAndSubmitTotpCode( + { + status: true, + }, + undefined, + integration + ) + ); + expect(fxaLoginSpy).toHaveBeenCalled(); + expect(hardNavigateSpy).toHaveBeenCalledWith( + '/connect_another_device?showSuccessMessage=true' + ); + }); + it('is not sent otherwise', async () => { + await renderAndSubmitTotpCode({ + status: true, + }); + expect(fxaLoginSpy).not.toHaveBeenCalled(); + expect(hardNavigateSpy).not.toBeCalled(); + }); + }); + it('shows error on invalid code', async () => { await renderAndSubmitTotpCode({ status: false, @@ -167,7 +207,7 @@ describe('Sign in with TOTP code page', () => { expect(mockNavigate).not.toHaveBeenCalled(); }); - describe('withOAuth integration', () => { + describe('with OAuth integration', () => { it('navigates to relying party on success', async () => { const finishOAuthFlowHandler = jest .fn() diff --git a/packages/fxa-settings/src/pages/Signin/SigninTotpCode/index.tsx b/packages/fxa-settings/src/pages/Signin/SigninTotpCode/index.tsx index bb892258d3..20e3794bf5 100644 --- a/packages/fxa-settings/src/pages/Signin/SigninTotpCode/index.tsx +++ b/packages/fxa-settings/src/pages/Signin/SigninTotpCode/index.tsx @@ -145,7 +145,7 @@ export const SigninTotpCode = ({ queryParams: location.search, }; - handleNavigation(navigationOptions, navigate); + await handleNavigation(navigationOptions, navigate, true); } }; diff --git a/packages/fxa-settings/src/pages/Signin/container.test.tsx b/packages/fxa-settings/src/pages/Signin/container.test.tsx index f938b20b8f..54839cd823 100644 --- a/packages/fxa-settings/src/pages/Signin/container.test.tsx +++ b/packages/fxa-settings/src/pages/Signin/container.test.tsx @@ -385,25 +385,6 @@ describe('signin container', () => { ); }); }); - describe('web channel messages', () => { - let fxaLoginSpy: jest.SpyInstance; - beforeEach(() => { - fxaLoginSpy = jest.spyOn(firefox, 'fxaCanLinkAccount'); - }); - - // TODO in Sync ticket. Is this being sent under the right conditions? - // it('are sent for sync', async () => { - // mockSyncDesktopV3Integration(); - // render(); - // await waitFor(() => { - // expect(fxaLoginSpy).toBeCalledWith({ email: MOCK_QUERY_PARAM_EMAIL }); - // }); - // }); - it('are not sent for non-sync', () => { - render([mockGqlAvatarUseQuery()]); - expect(fxaLoginSpy).not.toBeCalled(); - }); - }); }); describe('hasLinkedAccount and hasPassword are provided', () => { diff --git a/packages/fxa-settings/src/pages/Signin/container.tsx b/packages/fxa-settings/src/pages/Signin/container.tsx index ce1afc2afa..b59cdc1f7b 100644 --- a/packages/fxa-settings/src/pages/Signin/container.tsx +++ b/packages/fxa-settings/src/pages/Signin/container.tsx @@ -6,8 +6,6 @@ import { RouteComponentProps, useLocation, useNavigate } from '@reach/router'; import Signin from '.'; import { Integration, - isOAuthIntegration, - isSyncDesktopV3Integration, useAuthClient, useFtlMsgResolver, useConfig, @@ -16,7 +14,6 @@ import { MozServices } from '../../lib/types'; import { useValidatedQueryParams } from '../../lib/hooks/useValidate'; import { SigninQueryParams } from '../../models/pages/signin'; import { useCallback, useEffect, useState } from 'react'; -import firefox from '../../lib/channels/firefox'; import LoadingSpinner from 'fxa-react/components/LoadingSpinner'; import { cache, currentAccount, discardSessionToken } from '../../lib/cache'; import { FetchResult, useMutation, useQuery } from '@apollo/client'; @@ -138,10 +135,7 @@ const SigninContainer = ({ queryParamModel.email || emailFromLocationState ); - const isOAuth = isOAuthIntegration(integration); - const isSyncOAuth = isOAuth && integration.isSync(); - const isSyncDesktopV3 = isSyncDesktopV3Integration(integration); - const isSyncWebChannel = isSyncOAuth || isSyncDesktopV3; + const wantsKeys = integration.wantsKeys(); useEffect(() => { (async () => { @@ -162,31 +156,24 @@ const SigninContainer = ({ // For now, just pass back emailStatusChecked. When we convert the Index page // we'll want to read from router state. navigate(`/signup?email=${email}&emailStatusChecked=true`); - // TODO: Probably move this to the Index page onsubmit once - // the index page is converted to React, we need to run it in - // signup and signin for Sync } else { // TODO: in FXA-9177, also set hasLinkedAccount and hasPassword in Apollo cache setAccountStatus({ hasLinkedAccount, hasPassword, }); - if (isSyncWebChannel) { - firefox.fxaCanLinkAccount({ email }); - } } } catch (error) { hardNavigateToContentServer(`/?prefillEmail=${email}`); } - } else if (isSyncWebChannel) { - // TODO: Probably move this to the Index page onsubmit once - // the index page is converted to React, we need to run it in - // signup and signin for Sync - firefox.fxaCanLinkAccount({ email }); } + } else { + hardNavigateToContentServer('/'); } })(); - }); + // Only run this on initial render + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); const { data: avatarData, loading: avatarLoading } = useQuery(AVATAR_QUERY); @@ -214,7 +201,7 @@ const SigninContainer = ({ const service = integration.getService(); const options = { verificationMethod: VerificationMethods.EMAIL_OTP, - keys: integration.wantsKeys(), + keys: wantsKeys, ...(service !== MozServices.Default && { service }), }; @@ -296,10 +283,10 @@ const SigninContainer = ({ password, clientSalt: credentialStatusData.data?.credentialStatus.clientSalt || - (await createSaltV2()), + createSaltV2(), }); - const kB = await unwrapKB(wrapKb, v1Credentials.unwrapBKey); + const kB = unwrapKB(wrapKb, v1Credentials.unwrapBKey); const keys = await getKeysV2({ kB, v1: v1Credentials, @@ -347,7 +334,17 @@ const SigninContainer = ({ }, }); - return { data }; + if (data) { + return { + data: { + ...data, + ...(wantsKeys && { + unwrapBKey: v2Credentials.unwrapBKey, + }), + }, + }; + } + return { data: undefined }; } catch (error) { return handleGQLError(error); } @@ -370,7 +367,18 @@ const SigninContainer = ({ }, }, }); - return { data }; + + if (data) { + return { + data: { + ...data, + ...(wantsKeys && { + unwrapBKey: v1Credentials.unwrapBKey, + }), + }, + }; + } + return { data: undefined }; } catch (error) { // TODO consider additional error handling - any non-gql errors will return an unexpected error return handleGQLError(error); @@ -385,6 +393,7 @@ const SigninContainer = ({ keyStretchExp.queryParamModel, passwordChangeFinish, passwordChangeStart, + wantsKeys, ] ); diff --git a/packages/fxa-settings/src/pages/Signin/index.stories.tsx b/packages/fxa-settings/src/pages/Signin/index.stories.tsx index 9a96f7d0e6..dab5ea95d6 100644 --- a/packages/fxa-settings/src/pages/Signin/index.stories.tsx +++ b/packages/fxa-settings/src/pages/Signin/index.stories.tsx @@ -6,7 +6,11 @@ import React from 'react'; import Signin from '.'; import { MozServices } from '../../lib/types'; import { Meta } from '@storybook/react'; -import { Subject, createMockSigninSyncIntegration } from './mocks'; +import { + Subject, + createMockSigninOAuthIntegration, + createMockSigninSyncIntegration, +} from './mocks'; import { withLocalization } from 'fxa-react/lib/storybooks'; import { SigninProps } from './interfaces'; import { MOCK_SERVICE, MOCK_SESSION_TOKEN } from '../mocks'; @@ -28,9 +32,10 @@ export const SignInToSettingsWithPassword = storyWithProps(); export const SignInToRelyingPartyWithPassword = storyWithProps({ serviceName: MOCK_SERVICE, }); -// TODO with OAuth ticket, needs OAuth integration + export const SignInToPocketWithPassword = storyWithProps({ serviceName: MozServices.Pocket, + integration: createMockSigninOAuthIntegration(), }); export const SignInToSettingsWithCachedCredentials = storyWithProps({ @@ -40,10 +45,16 @@ export const SignInToRelyingPartyWithCachedCredentials = storyWithProps({ sessionToken: MOCK_SESSION_TOKEN, serviceName: MOCK_SERVICE, }); -// TODO with OAuth ticket, needs OAuth integration + export const SignInToPocketWithCachedCredentials = storyWithProps({ sessionToken: MOCK_SESSION_TOKEN, serviceName: MozServices.Pocket, + integration: createMockSigninOAuthIntegration(), +}); + +export const SignInToSyncWithCachedCredentials = storyWithProps({ + sessionToken: MOCK_SESSION_TOKEN, + integration: createMockSigninOAuthIntegration({ wantsKeys: true }), }); export const HasLinkedAccountAndNoPassword = storyWithProps({ diff --git a/packages/fxa-settings/src/pages/Signin/index.test.tsx b/packages/fxa-settings/src/pages/Signin/index.test.tsx index 31ad929858..2cdd25b731 100644 --- a/packages/fxa-settings/src/pages/Signin/index.test.tsx +++ b/packages/fxa-settings/src/pages/Signin/index.test.tsx @@ -12,6 +12,7 @@ import { createBeginSigninResponseError, createCachedSigninResponseError, createMockSigninOAuthIntegration, + createMockSigninSyncIntegration, Subject, } from './mocks'; import { @@ -34,6 +35,7 @@ import { MONITOR_CLIENTIDS, POCKET_CLIENTIDS, } from '../../models/integrations/client-matching'; +import firefox from '../../lib/channels/firefox'; // import { getFtlBundle, testAllL10n } from 'fxa-react/lib/test-utils'; // import { FluentBundle } from '@fluent/bundle'; jest.mock('../../lib/metrics', () => ({ @@ -173,6 +175,19 @@ describe('Signin', () => { differentAccountLinkRendered(); }); + it('does not render third party auth for sync', () => { + const integration = createMockSigninSyncIntegration(); + render({ integration }); + enterPasswordAndSubmit(); + + expect( + screen.queryByRole('button', { name: /Continue with Google/ }) + ).not.toBeInTheDocument(); + expect( + screen.queryByRole('button', { name: /Continue with Apple/ }) + ).not.toBeInTheDocument(); + }); + it('emits an event on forgot password link click', async () => { render(); fireEvent.click(screen.getByText('Forgot password?')); @@ -306,82 +321,159 @@ describe('Signin', () => { }); }); - describe('OAuth integration', () => { - describe('wants keys', () => { - it('navigates to /confirm_signup_code with router state including keys', async () => { - const beginSigninHandler = jest.fn().mockReturnValueOnce( - createBeginSigninResponse({ - verified: false, - verificationReason: VerificationReasons.SIGN_UP, - keyFetchToken: MOCK_KEY_FETCH_TOKEN, - unwrapBKey: MOCK_UNWRAP_BKEY, - }) - ); - const finishOAuthFlowHandler = jest - .fn() - .mockReturnValueOnce(MOCK_OAUTH_FLOW_HANDLER_RESPONSE); - const integration = createMockSigninOAuthIntegration(); - render({ - beginSigninHandler, - integration, - finishOAuthFlowHandler, - }); + // When CAD is converted to React, just test navigation since CAD will handle fxaLogin + describe('fxaLogin webchannel message (tempHandleSyncLogin)', () => { + let fxaLoginSpy: jest.SpyInstance; + let hardNavigateSpy: jest.SpyInstance; + beforeEach(() => { + fxaLoginSpy = jest.spyOn(firefox, 'fxaLogin'); + hardNavigateSpy = jest + .spyOn(utils, 'hardNavigate') + .mockImplementation(() => {}); + }); + it('is sent if Sync integration and navigates to CAD', async () => { + const beginSigninHandler = jest + .fn() + .mockReturnValueOnce(createBeginSigninResponse()); + const integration = createMockSigninSyncIntegration(); + render({ beginSigninHandler, integration }); + enterPasswordAndSubmit(); + await waitFor(() => { + expect(fxaLoginSpy).toHaveBeenCalled(); + }); + expect(hardNavigateSpy).toHaveBeenCalledWith( + '/connect_another_device?showSuccessMessage=true' + ); + }); + it('is not sent otherwise', async () => { + render(); + enterPasswordAndSubmit(); + expect(fxaLoginSpy).not.toHaveBeenCalled(); + expect(hardNavigateSpy).not.toBeCalled(); + }); + }); - enterPasswordAndSubmit(); - await waitFor(() => { - expect(mockNavigate).toHaveBeenCalledWith( - '/confirm_signup_code', - { - state: { - email: MOCK_EMAIL, - uid: MOCK_UID, - sessionToken: MOCK_SESSION_TOKEN, - verified: false, - verificationReason: 'signup', - verificationMethod: 'email-otp', - keyFetchToken: MOCK_KEY_FETCH_TOKEN, - unwrapBKey: MOCK_UNWRAP_BKEY, - }, - } - ); - }); + describe('OAuth integration', () => { + let fxaOAuthLoginSpy: jest.SpyInstance; + let hardNavigateSpy: jest.SpyInstance; + let finishOAuthFlowHandler: jest.Mock; + beforeEach(() => { + fxaOAuthLoginSpy = jest.spyOn(firefox, 'fxaOAuthLogin'); + hardNavigateSpy = jest + .spyOn(utils, 'hardNavigate') + .mockImplementation(() => {}); + finishOAuthFlowHandler = jest + .fn() + .mockReturnValueOnce(MOCK_OAUTH_FLOW_HANDLER_RESPONSE); + }); + it('unverified, wantsKeys, navigates to /confirm_signup_code with keys', async () => { + const beginSigninHandler = jest.fn().mockReturnValueOnce( + createBeginSigninResponse({ + verified: false, + verificationReason: VerificationReasons.SIGN_UP, + keyFetchToken: MOCK_KEY_FETCH_TOKEN, + unwrapBKey: MOCK_UNWRAP_BKEY, + }) + ); + const integration = createMockSigninOAuthIntegration(); + render({ + beginSigninHandler, + integration, + finishOAuthFlowHandler, + }); + + enterPasswordAndSubmit(); + await waitFor(() => { + expect(mockNavigate).toHaveBeenCalledWith( + '/confirm_signup_code', + { + state: { + email: MOCK_EMAIL, + uid: MOCK_UID, + sessionToken: MOCK_SESSION_TOKEN, + verified: false, + verificationReason: 'signup', + verificationMethod: 'email-otp', + keyFetchToken: MOCK_KEY_FETCH_TOKEN, + unwrapBKey: MOCK_UNWRAP_BKEY, + }, + } + ); }); }); + it('unverified, does not want keys, navigates to /confirm_signup_code without keys', async () => { + const beginSigninHandler = jest.fn().mockReturnValueOnce( + createBeginSigninResponse({ + verified: false, + verificationReason: VerificationReasons.SIGN_UP, + }) + ); + const integration = createMockSigninOAuthIntegration(); + render({ + beginSigninHandler, + integration, + finishOAuthFlowHandler, + }); - describe('does not want keys', () => { - it('navigates to /confirm_signup_code with router state and no keys', async () => { - const beginSigninHandler = jest.fn().mockReturnValueOnce( - createBeginSigninResponse({ - verified: false, - verificationReason: VerificationReasons.SIGN_UP, - }) + enterPasswordAndSubmit(); + await waitFor(() => { + expect(mockNavigate).toHaveBeenCalledWith( + '/confirm_signup_code', + { + state: { + email: MOCK_EMAIL, + uid: MOCK_UID, + sessionToken: MOCK_SESSION_TOKEN, + verified: false, + verificationReason: 'signup', + verificationMethod: 'email-otp', + }, + } + ); + }); + }); + it('verified, not sync, navigates to RP redirect', async () => { + const beginSigninHandler = jest.fn().mockReturnValueOnce( + createBeginSigninResponse({ + keyFetchToken: MOCK_KEY_FETCH_TOKEN, + unwrapBKey: MOCK_UNWRAP_BKEY, + }) + ); + const integration = createMockSigninOAuthIntegration(); + render({ + beginSigninHandler, + integration, + finishOAuthFlowHandler, + }); + enterPasswordAndSubmit(); + await waitFor(() => { + expect(fxaOAuthLoginSpy).not.toHaveBeenCalled(); + expect(hardNavigateSpy).toHaveBeenCalledWith( + MOCK_OAUTH_FLOW_HANDLER_RESPONSE.redirect + ); + }); + }); + it('verified, sync, navigates to CAD and sends fxaOAuthLogin', async () => { + const beginSigninHandler = jest.fn().mockReturnValueOnce( + createBeginSigninResponse({ + keyFetchToken: MOCK_KEY_FETCH_TOKEN, + unwrapBKey: MOCK_UNWRAP_BKEY, + }) + ); + const integration = createMockSigninOAuthIntegration({ + isSync: true, + }); + render({ + beginSigninHandler, + integration, + finishOAuthFlowHandler, + }); + enterPasswordAndSubmit(); + await waitFor(() => { + expect(fxaOAuthLoginSpy).toHaveBeenCalled(); + expect(hardNavigateSpy).toHaveBeenCalledWith( + '/connect_another_device?showSuccessMessage=true' ); - const finishOAuthFlowHandler = jest - .fn() - .mockReturnValueOnce(MOCK_OAUTH_FLOW_HANDLER_RESPONSE); - const integration = createMockSigninOAuthIntegration(); - render({ - beginSigninHandler, - integration, - finishOAuthFlowHandler, - }); - - enterPasswordAndSubmit(); - await waitFor(() => { - expect(mockNavigate).toHaveBeenCalledWith( - '/confirm_signup_code', - { - state: { - email: MOCK_EMAIL, - uid: MOCK_UID, - sessionToken: MOCK_SESSION_TOKEN, - verified: false, - verificationReason: 'signup', - verificationMethod: 'email-otp', - }, - } - ); - }); }); }); }); @@ -583,6 +675,14 @@ describe('with sessionToken', () => { hardNavigateSpy.mockRestore(); }); + it('always renders password input when integration wants keys', () => { + const integration = createMockSigninOAuthIntegration({ + wantsKeys: true, + }); + render({ integration }); + passwordInputRendered(); + }); + it('navigates to OAuth redirect', async () => { const cachedSigninHandler = jest .fn() @@ -682,10 +782,10 @@ describe('with sessionToken', () => { ); @@ -696,7 +796,9 @@ describe('with sessionToken', () => { it('shows Pocket-specific TOS', () => { renderWithLocalizationProvider( ); @@ -723,7 +825,9 @@ describe('with sessionToken', () => { it('shows Monitor-specific TOS', async () => { renderWithLocalizationProvider( ); @@ -746,5 +850,3 @@ describe('with sessionToken', () => { }); }); }); - -// TODO in FXA-9059: make sure third party auth is not rendered for sync if account has a password diff --git a/packages/fxa-settings/src/pages/Signin/index.tsx b/packages/fxa-settings/src/pages/Signin/index.tsx index 30d2e6cda1..15e3dc6c23 100644 --- a/packages/fxa-settings/src/pages/Signin/index.tsx +++ b/packages/fxa-settings/src/pages/Signin/index.tsx @@ -79,7 +79,8 @@ const Signin = ({ // We must use a ref because we may update this value in a callback let isPasswordNeededRef = useRef( (!sessionToken && hasPassword) || - (isOAuth && (integration.wantsKeys() || integration.wantsLogin())) + integration.wantsKeys() || + (isOAuth && integration.wantsLogin()) ); const localizedPasswordFormLabel = ftlMsgResolver.getMsg( @@ -190,12 +191,13 @@ const Signin = ({ email, signinData: data.signIn, unwrapBKey: data.unwrapBKey, + verified: data.signIn.verified, integration, finishOAuthFlowHandler, queryParams: location.search, }; - await handleNavigation(navigationOptions, navigate); + await handleNavigation(navigationOptions, navigate, true); } if (error) { GleanMetrics.login.error({ reason: error.message }); @@ -289,8 +291,7 @@ const Signin = ({ ] ); - const hideThirdPartyAuth = - integration.isSync() && hasLinkedAccount && hasPassword; + const hideThirdPartyAuth = integration.isSync() && hasPassword; return ( diff --git a/packages/fxa-settings/src/pages/Signin/interfaces.ts b/packages/fxa-settings/src/pages/Signin/interfaces.ts index 691f02aa17..8f57a093ac 100644 --- a/packages/fxa-settings/src/pages/Signin/interfaces.ts +++ b/packages/fxa-settings/src/pages/Signin/interfaces.ts @@ -17,7 +17,7 @@ export interface AvatarResponse { } export type SigninIntegration = - | Pick + | Pick | SigninOAuthIntegration; export type SigninOAuthIntegration = Pick< @@ -160,10 +160,11 @@ export interface NavigationOptions { verified: boolean; verificationMethod?: VerificationMethods; verificationReason?: VerificationReasons; - // keyFetchToken and unwrapBKey are included if options.keys=true - // These will never exist for the cached signin (prompt=none) + // keyFetchToken is included if options.keys=true + // This (and unwrapBKey) will never exist for the cached signin (prompt=none) keyFetchToken?: hexstring; }; + // unwrapBKey is included if integration.wantsKeys() unwrapBKey?: hexstring; integration: SigninIntegration; finishOAuthFlowHandler: FinishOAuthFlowHandler; diff --git a/packages/fxa-settings/src/pages/Signin/mocks.tsx b/packages/fxa-settings/src/pages/Signin/mocks.tsx index 5c7220ea94..060ec6a165 100644 --- a/packages/fxa-settings/src/pages/Signin/mocks.tsx +++ b/packages/fxa-settings/src/pages/Signin/mocks.tsx @@ -93,6 +93,7 @@ export function createMockSigninWebIntegration(): SigninIntegration { type: IntegrationType.Web, isSync: () => false, getService: () => MozServices.Default, + wantsKeys: () => false, }; } @@ -105,14 +106,19 @@ export function createMockSigninSyncIntegration(): SigninIntegration { }; } -export function createMockSigninOAuthIntegration( - clientId?: string, - wantsKeys: boolean = true -): SigninOAuthIntegration { +export function createMockSigninOAuthIntegration({ + clientId, + wantsKeys = true, + isSync = false, +}: { + clientId?: string; + wantsKeys?: boolean; + isSync?: boolean; +} = {}): SigninOAuthIntegration { return { type: IntegrationType.OAuth, getService: () => clientId || MOCK_CLIENT_ID, - isSync: () => false, + isSync: () => isSync, wantsKeys: () => wantsKeys, wantsLogin: () => false, wantsTwoStepAuthentication: () => false, diff --git a/packages/fxa-settings/src/pages/Signin/utils.ts b/packages/fxa-settings/src/pages/Signin/utils.ts index fd88b37eb5..3b7e1b012b 100644 --- a/packages/fxa-settings/src/pages/Signin/utils.ts +++ b/packages/fxa-settings/src/pages/Signin/utils.ts @@ -17,24 +17,56 @@ import { import { isOAuthIntegration } from '../../models'; import { NavigateFn } from '@reach/router'; import { hardNavigate } from 'fxa-react/lib/utils'; -import { FinishOAuthFlowHandler } from '../../lib/oauth/hooks'; import { currentAccount } from '../../lib/cache'; +import firefox from '../../lib/channels/firefox'; -// TODO in FXA-9059: -// function getSyncNavigate() { -// const searchParams = new URLSearchParams(location.search); -// searchParams.set('showSuccessMessage', 'true'); -// const to = `/connect_another_device?${searchParams}` -// } +interface NavigationTarget { + to: string; + state?: SigninLocationState; + shouldHardNavigate?: boolean; +} +// TODO: don't hard navigate once ConnectAnotherDevice is converted to React +export function getSyncNavigate(queryParams: string) { + const searchParams = new URLSearchParams(queryParams); + searchParams.set('showSuccessMessage', 'true'); + return { + to: `/connect_another_device?${searchParams}`, + shouldHardNavigate: true, + }; +} + +// In Backbone and React, 'confirm_signup_code' and 'signin_token_code' send key +// and token data up to Sync with fxa_login and then the CAD page (currently +// Backbone) completes the signin with fxa_status. +// +// In Backbone happy path signin (session is verified after signin) as well as +// Backbone 'signin_totp_code', we send key/token data up on the CAD page itself +// with an fxa_status message. We don't want to do this with React signin until +// CAD is converted to React because we'd need to pass this data back to +// Backbone. This means temporarily we need to send the sync data up _before_ +// we hard navigate to CAD in these two flows. export async function handleNavigation( navigationOptions: NavigationOptions, - navigate: NavigateFn + navigate: NavigateFn, + tempHandleSyncLogin = false ) { const { to, state, shouldHardNavigate } = await getNavigationTarget( navigationOptions ); if (shouldHardNavigate) { + if (tempHandleSyncLogin && navigationOptions.integration.isSync()) { + firefox.fxaLogin({ + email: navigationOptions.email, + // keyFetchToken and unwrapBKey should always exist if Sync integration + keyFetchToken: navigationOptions.signinData.keyFetchToken!, + unwrapBKey: navigationOptions.unwrapBKey!, + sessionToken: navigationOptions.signinData.sessionToken, + uid: navigationOptions.signinData.uid, + verified: navigationOptions.signinData.verified, + }); + } + // Hard navigate to RP, or (temp until CAD is Reactified) CAD hardNavigate(to); return; @@ -46,46 +78,6 @@ export async function handleNavigation( } } -export async function getOAuthRedirectAndHandleSync( - finishOAuthFlowHandler: FinishOAuthFlowHandler, - { - uid, - sessionToken, - keyFetchToken, - unwrapBKey, - }: { - uid: hexstring; - sessionToken: hexstring; - keyFetchToken?: string; - unwrapBKey?: string; - } -) { - const { redirect } = - keyFetchToken && unwrapBKey - ? await finishOAuthFlowHandler( - uid, - sessionToken, - keyFetchToken, - unwrapBKey - ) - : await finishOAuthFlowHandler(uid, sessionToken); - - // TODO in FXA-9059 Sync signin ticket. Do we want to do firefox.fxAOAuthLogin here - // if the session isn't verified? - // - // if (integration.isSync()) { - // firefox.fxaOAuthLogin({ - // action: 'signin', - // code, - // redirect, - // state, - // }) - // TODO: don't hard navigate once ConnectAnotherDevice is converted to React - // return { to: getSyncNavigate(), shouldHardNavigate: true } - // } - return { to: redirect, shouldHardNavigate: true }; -} - const getNavigationTarget = async ({ email, signinData, @@ -94,7 +86,7 @@ const getNavigationTarget = async ({ finishOAuthFlowHandler, redirectTo, queryParams = '', -}: NavigationOptions) => { +}: NavigationOptions): Promise => { const isOAuth = isOAuthIntegration(integration); const { verified, @@ -105,73 +97,73 @@ const getNavigationTarget = async ({ sessionToken, } = signinData; - // oAuthResult result will need to be obtained at the next step, once session is verified - if (!verified) { + const getUnverifiedNav = () => { const state = { email, uid, sessionToken, verified, - ...(verificationMethod && { verificationMethod }), - ...(verificationReason && { verificationReason }), - ...(keyFetchToken && { keyFetchToken }), - ...(unwrapBKey && { unwrapBKey }), + verificationMethod, + verificationReason, + keyFetchToken, + unwrapBKey, }; - // TODO in FXA-9177 Consider storing state in Apollo cache instead of location state - if ( - ((verificationReason === VerificationReasons.SIGN_IN || - verificationReason === VerificationReasons.CHANGE_PASSWORD) && - verificationMethod === VerificationMethods.TOTP_2FA) || - (isOAuth && integration.wantsTwoStepAuthentication()) - ) { - return { - to: `/signin_totp_code${queryParams}`, - state, - }; - } else if (verificationReason === VerificationReasons.SIGN_UP) { - return { - to: `/confirm_signup_code${queryParams}`, - state, - }; - } else { - return { - to: `/signin_token_code${queryParams}`, - state, - }; - } + const getUnverifiedNavTo = () => { + // TODO in FXA-9177 Consider storing state in Apollo cache instead of location state + if ( + ((verificationReason === VerificationReasons.SIGN_IN || + verificationReason === VerificationReasons.CHANGE_PASSWORD) && + verificationMethod === VerificationMethods.TOTP_2FA) || + (isOAuth && integration.wantsTwoStepAuthentication()) + ) { + return `/signin_totp_code${queryParams}`; + } else if (verificationReason === VerificationReasons.SIGN_UP) { + return `/confirm_signup_code${queryParams}`; + } + return `/signin_token_code${queryParams}`; + }; + + return { to: getUnverifiedNavTo(), state }; + }; + + if (!verified) { + return getUnverifiedNav(); } if (verificationReason === VerificationReasons.CHANGE_PASSWORD) { return { - to: - queryParams.length > 1 - ? `/post_verify/password/force_password_change${queryParams}` - : '/post_verify/password/force_password_change', + to: `/post_verify/password/force_password_change${ + (queryParams.length > 1 && queryParams) || '' + }`, // TODO in FXA-6653: remove shouldHardNavigate when this route is converted to React shouldHardNavigate: true, }; } - // TODO in FXA-9059 handle sync desktop v3 integration post-sign in navigation - - // oAuthResult can only be obtained when the session is verified + // OAuth redirect can only be obtained when the session is verified // otherwise oauth/authorization endpoint throws an "unconfirmed session" error - if (verified && isOAuth) { - const oAuthResult = await getOAuthRedirectAndHandleSync( - finishOAuthFlowHandler, - { - uid, - sessionToken, - keyFetchToken, - unwrapBKey, - } + if (isOAuth) { + const { redirect, code, state } = await finishOAuthFlowHandler( + uid, + sessionToken, + keyFetchToken, + unwrapBKey ); - return { - to: oAuthResult.to, - shouldHardNavigate: oAuthResult.shouldHardNavigate, - }; + if (integration.isSync()) { + firefox.fxaOAuthLogin({ + action: 'signin', + code, + redirect, + state, + }); + return getSyncNavigate(queryParams); + } + return { to: redirect, shouldHardNavigate: true }; + } + if (integration.isSync()) { + return getSyncNavigate(queryParams); } if (redirectTo) { diff --git a/packages/fxa-settings/src/pages/Signup/ConfirmSignupCode/index.tsx b/packages/fxa-settings/src/pages/Signup/ConfirmSignupCode/index.tsx index 98144a5572..adf569f3f8 100644 --- a/packages/fxa-settings/src/pages/Signup/ConfirmSignupCode/index.tsx +++ b/packages/fxa-settings/src/pages/Signup/ConfirmSignupCode/index.tsx @@ -43,6 +43,7 @@ import firefox from '../../../lib/channels/firefox'; import GleanMetrics from '../../../lib/glean'; import { useWebRedirect } from '../../../lib/hooks/useWebRedirect'; import { storeAccountData } from '../../../lib/storage-utils'; +import { getSyncNavigate } from '../../Signin/utils'; export const viewName = 'confirm-signup-code'; @@ -119,14 +120,6 @@ const ConfirmSignupCode = ({ } } - function navigateToCAD() { - // Connect another device tells Sync the user is signed in - // TODO: regular navigate when this page is converted - const searchParams = new URLSearchParams(location.search); - searchParams.set('showSuccessMessage', 'true'); - hardNavigateToContentServer(`/connect_another_device?${searchParams}`); - } - async function verifySession(code: string) { logViewEvent(`flow.${viewName}`, 'submit', REACT_ENTRYPOINT); GleanMetrics.signupConfirmation.submit(); @@ -162,7 +155,8 @@ const ConfirmSignupCode = ({ } if (isSyncDesktopV3Integration(integration)) { - navigateToCAD(); + const { to } = getSyncNavigate(location.search); + hardNavigateToContentServer(to); } else if (isOAuthIntegration(integration)) { // Check to see if the relier wants TOTP. // Newly created accounts wouldn't have this so lets redirect them to signin. @@ -197,7 +191,8 @@ const ConfirmSignupCode = ({ state, }); // Mobile sync will close the web view, OAuth Desktop mimics DesktopV3 behavior - navigateToCAD(); + const { to } = getSyncNavigate(location.search); + hardNavigateToContentServer(to); return; } else { // Navigate to relying party diff --git a/packages/fxa-settings/src/pages/Signup/container.tsx b/packages/fxa-settings/src/pages/Signup/container.tsx index 73a33a1bb0..c7d0f23a42 100644 --- a/packages/fxa-settings/src/pages/Signup/container.tsx +++ b/packages/fxa-settings/src/pages/Signup/container.tsx @@ -99,45 +99,38 @@ const SignupContainer = ({ const isSyncOAuth = isOAuth && integration.isSync(); const isSyncDesktopV3 = isSyncDesktopV3Integration(integration); const isSyncWebChannel = isSyncOAuth || isSyncDesktopV3; + const wantsKeys = integration.wantsKeys(); useEffect(() => { (async () => { // Modify this once index is converted to React - if (!validationError) { - // emailStatusChecked can be passed from React Signin when users hit /signin - // with an email query param that we already determined doesn't exist. - // It's supplied by Backbone when going from Backbone Index page to React signup. - if (!queryParamModel.emailStatusChecked && !emailStatusChecked) { - const { exists, hasLinkedAccount, hasPassword } = - await authClient.accountStatusByEmail(queryParamModel.email, { - thirdPartyAuthStatus: true, + // emailStatusChecked can be passed from React Signin when users hit /signin + // with an email query param that we already determined doesn't exist. + // It's supplied by Backbone when going from Backbone Index page to React signup. + if ( + !validationError && + !queryParamModel.emailStatusChecked && + !emailStatusChecked + ) { + const { exists, hasLinkedAccount, hasPassword } = + await authClient.accountStatusByEmail(queryParamModel.email, { + thirdPartyAuthStatus: true, + }); + if (exists) { + if (config.showReactApp.signInRoutes) { + navigate(`/signin`, { + replace: true, + state: { + email: queryParamModel.email, + hasLinkedAccount, + hasPassword, + }, }); - if (exists) { - if (config.showReactApp.signInRoutes) { - navigate(`/signin`, { - replace: true, - state: { - email: queryParamModel.email, - hasLinkedAccount, - hasPassword, - }, - }); - } else { - hardNavigateToContentServer( - `/signin?email=${queryParamModel.email}` - ); - } - // TODO: Probably move this to the Index page onsubmit once - // the index page is converted to React, we need to run it in - // signup and signin for Sync - } else if (isSyncWebChannel) { - firefox.fxaCanLinkAccount({ email: queryParamModel.email }); + } else { + hardNavigateToContentServer( + `/signin?email=${queryParamModel.email}` + ); } - } else if (isSyncWebChannel) { - // TODO: Probably move this to the Index page onsubmit once - // the index page is converted to React, we need to run it in - // signup and signin for Sync - firefox.fxaCanLinkAccount({ email: queryParamModel.email }); } } setShowLoadingSpinner(false); @@ -195,7 +188,7 @@ const SignupContainer = ({ const service = integration.getService(); const options: BeginSignUpOptions = { verificationMethod: VerificationMethods.EMAIL_OTP, - keys: integration.wantsKeys(), + keys: wantsKeys, ...(service !== MozServices.Default && { service }), atLeast18AtReg, }; @@ -247,9 +240,11 @@ const SignupContainer = ({ return { data: { ...data, - unwrapBKey: credentialsV2 - ? credentialsV2.unwrapBKey - : credentialsV1.unwrapBKey, + ...(wantsKeys && { + unwrapBKey: credentialsV2 + ? credentialsV2.unwrapBKey + : credentialsV1.unwrapBKey, + }), }, }; } else return { data: undefined }; @@ -258,7 +253,7 @@ const SignupContainer = ({ return handleGQLError(error); } }, - [beginSignup, integration, keyStretchExp, config] + [beginSignup, integration, keyStretchExp, config, wantsKeys] ); // TODO: probably a better way to read this? diff --git a/packages/fxa-settings/src/pages/Signup/index.tsx b/packages/fxa-settings/src/pages/Signup/index.tsx index 7c15d889e2..14b764e639 100644 --- a/packages/fxa-settings/src/pages/Signup/index.tsx +++ b/packages/fxa-settings/src/pages/Signup/index.tsx @@ -8,7 +8,6 @@ import { useForm } from 'react-hook-form'; import { isOAuthIntegration, isSyncDesktopV3Integration, - isSyncOAuthIntegration, useFtlMsgResolver, } from '../../models'; import { @@ -246,10 +245,7 @@ export const Signup = ({ const getOfferedSyncEngines = () => getSyncEngineIds(offeredSyncEngineConfigs || []); - if ( - isSyncDesktopV3Integration(integration) || - isSyncOAuthIntegration(integration) - ) { + if (integration.isSync()) { await firefox.fxaLogin({ email, // keyFetchToken and unwrapBKey should always exist if Sync integration