Merge pull request #18029 from mozilla/session-expired-fix

fix(sync): Always send firefox.fxaLogout on sign out
This commit is contained in:
Lauren Zugai 2024-11-15 18:24:00 -06:00 коммит произвёл GitHub
Родитель 3ad136a5d5 2a4e264623
Коммит ea8f8b1e9d
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
23 изменённых файлов: 67 добавлений и 133 удалений

Просмотреть файл

@ -8,7 +8,7 @@ import { LocationProvider } from '@reach/router';
import FormPassword from '.';
import { Meta } from '@storybook/react';
import { withLocalization } from 'fxa-react/lib/storybooks';
import { MockSettingsAppLayout } from '../Settings/SettingsLayout/mocks';
import SettingsLayout from '../Settings/SettingsLayout';
export default {
title: 'Components/FormPassword',
@ -17,20 +17,20 @@ export default {
} as Meta;
export const WithCurrentPassword = () => (
<LocationProvider>
<MockSettingsAppLayout>
<SettingsLayout>
<div className="max-w-lg mx-auto">
<Subject />
</div>
</MockSettingsAppLayout>
</SettingsLayout>
</LocationProvider>
);
export const WithoutCurrentPassword = () => (
<LocationProvider>
<MockSettingsAppLayout>
<SettingsLayout>
<div className="max-w-lg mx-auto">
<Subject includeCurrentPw={false} />
</div>
</MockSettingsAppLayout>
</SettingsLayout>
</LocationProvider>
);

Просмотреть файл

@ -8,7 +8,6 @@ import { withLocalization } from 'fxa-react/lib/storybooks';
import DropDownAvatarMenu from '.';
import { Account, AppContext } from 'fxa-settings/src/models';
import { mockAppContext, MOCK_ACCOUNT } from 'fxa-settings/src/models/mocks';
import { createMockSettingsIntegration } from '../mocks';
export default {
title: 'Components/Settings/DropDownAvatarMenu',
@ -35,14 +34,12 @@ const accountWithoutAvatar = {
},
} as unknown as Account;
const integration = createMockSettingsIntegration();
const storyWithContext = (account: Partial<Account>) => {
const context = { account: account as Account };
const story = () => (
<AppContext.Provider value={mockAppContext(context)}>
<DropDownAvatarMenu {...{ integration }} />
<DropDownAvatarMenu />
</AppContext.Provider>
);
return story;
@ -51,6 +48,4 @@ const storyWithContext = (account: Partial<Account>) => {
export const DefaultNoAvatarOrDisplayName =
storyWithContext(accountWithoutAvatar);
export const WithAvatarAndDisplayName = () => (
<DropDownAvatarMenu {...{ integration }} />
);
export const WithAvatarAndDisplayName = () => <DropDownAvatarMenu />;

Просмотреть файл

@ -14,7 +14,6 @@ import DropDownAvatarMenu from '.';
import { logViewEvent, settingsViewName } from 'fxa-settings/src/lib/metrics';
import { Account, AppContext } from '../../../models';
import { SettingsContext } from '../../../models/contexts/SettingsContext';
import { createMockSettingsIntegration } from '../mocks';
import firefox from '../../../lib/channels/firefox';
import { PLACEHOLDER_IMAGE_URL } from '../../../pages/mocks';
@ -49,7 +48,7 @@ describe('DropDownAvatarMenu', () => {
} as unknown as Account;
renderWithLocalizationProvider(
<AppContext.Provider value={mockAppContext({ account })}>
<DropDownAvatarMenu integration={createMockSettingsIntegration()} />
<DropDownAvatarMenu />
</AppContext.Provider>
);
@ -76,7 +75,7 @@ describe('DropDownAvatarMenu', () => {
it('renders as expected with avatar url and displayName set', () => {
renderWithLocalizationProvider(
<AppContext.Provider value={mockAppContext({ account })}>
<DropDownAvatarMenu integration={createMockSettingsIntegration()} />
<DropDownAvatarMenu />
</AppContext.Provider>
);
fireEvent.click(screen.getByTestId('drop-down-avatar-menu-toggle'));
@ -88,7 +87,7 @@ describe('DropDownAvatarMenu', () => {
it('closes on esc keypress', () => {
renderWithLocalizationProvider(
<AppContext.Provider value={mockAppContext({ account })}>
<DropDownAvatarMenu integration={createMockSettingsIntegration()} />
<DropDownAvatarMenu />
</AppContext.Provider>
);
@ -103,7 +102,7 @@ describe('DropDownAvatarMenu', () => {
<AppContext.Provider value={mockAppContext({ account })}>
<div className="w-full flex justify-end">
<div className="flex pr-10 pt-4">
<DropDownAvatarMenu integration={createMockSettingsIntegration()} />
<DropDownAvatarMenu />
</div>
</div>
</AppContext.Provider>
@ -128,7 +127,7 @@ describe('DropDownAvatarMenu', () => {
<AppContext.Provider
value={mockAppContext({ account, session: mockSession() })}
>
<DropDownAvatarMenu integration={createMockSettingsIntegration()} />
<DropDownAvatarMenu />
</AppContext.Provider>
);
@ -154,7 +153,7 @@ describe('DropDownAvatarMenu', () => {
renderWithLocalizationProvider(
<AppContext.Provider value={context}>
<SettingsContext.Provider value={settingsContext}>
<DropDownAvatarMenu integration={createMockSettingsIntegration()} />
<DropDownAvatarMenu />
</SettingsContext.Provider>
</AppContext.Provider>
);
@ -175,14 +174,12 @@ describe('DropDownAvatarMenu', () => {
afterEach(() => {
jest.restoreAllMocks();
});
it('is called integration is sync', async () => {
it('is called', async () => {
renderWithLocalizationProvider(
<AppContext.Provider
value={mockAppContext({ account, session: mockSession() })}
>
<DropDownAvatarMenu
integration={createMockSettingsIntegration({ isSync: true })}
/>
<DropDownAvatarMenu />
</AppContext.Provider>
);
fireEvent.click(screen.getByTestId('drop-down-avatar-menu-toggle'));
@ -191,20 +188,5 @@ describe('DropDownAvatarMenu', () => {
});
expect(fxaLogoutSpy).toBeCalledWith({ uid: account.uid });
});
it('is not called when integration is not sync', async () => {
renderWithLocalizationProvider(
<AppContext.Provider
value={mockAppContext({ account, session: mockSession() })}
>
<DropDownAvatarMenu integration={createMockSettingsIntegration()} />
</AppContext.Provider>
);
fireEvent.click(screen.getByTestId('drop-down-avatar-menu-toggle'));
await act(async () => {
fireEvent.click(screen.getByTestId('avatar-menu-sign-out'));
});
expect(fxaLogoutSpy).not.toBeCalled();
});
});
});

Просмотреть файл

@ -11,13 +11,8 @@ import { ReactComponent as SignOut } from './sign-out.svg';
import { logViewEvent, settingsViewName } from '../../../lib/metrics';
import { Localized, useLocalization } from '@fluent/react';
import firefox from '../../../lib/channels/firefox';
import { SettingsIntegration } from '../interfaces';
export const DropDownAvatarMenu = ({
integration,
}: {
integration: SettingsIntegration;
}) => {
export const DropDownAvatarMenu = () => {
const { displayName, primaryEmail, avatar, uid } = useAccount();
const session = useSession();
const [isRevealed, setRevealed] = useState(false);
@ -39,9 +34,10 @@ export const DropDownAvatarMenu = ({
try {
await session.destroy();
if (integration.isSync()) {
firefox.fxaLogout({ uid });
}
// Send a logout event to Firefox even if the user is in a non-Sync flow.
// If the user is signed into the browser, they need to drop the now
// destroyed session token.
firefox.fxaLogout({ uid });
logViewEvent(settingsViewName, 'signout.success');
window.location.assign(window.location.origin);

Просмотреть файл

@ -9,7 +9,7 @@ import { Account, AppContext, useFtlMsgResolver } from '../../../models';
import { MOCK_ACCOUNT, mockAppContext } from '../../../models/mocks';
import { AuthUiErrors } from '../../../lib/auth-errors/auth-errors';
import { withLocalization } from 'fxa-react/lib/storybooks';
import { MockSettingsAppLayout } from '../SettingsLayout/mocks';
import SettingsLayout from '../SettingsLayout';
export default {
title: 'Components/Settings/FlowRecoveryKeyConfirmPwd',
@ -76,7 +76,7 @@ const StoryWithContext = (account: Account) => {
return (
<AppContext.Provider value={mockAppContext({ account })}>
<MockSettingsAppLayout>
<SettingsLayout>
<FlowRecoveryKeyConfirmPwd
{...{
localizedBackButtonTitle,
@ -87,7 +87,7 @@ const StoryWithContext = (account: Account) => {
viewName,
}}
/>
</MockSettingsAppLayout>
</SettingsLayout>
</AppContext.Provider>
);
};

Просмотреть файл

@ -8,7 +8,6 @@ import { withLocalization } from 'fxa-react/lib/storybooks';
import { HeaderLockup } from '.';
import { Account, AppContext } from '../../../models';
import { mockAppContext, MOCK_ACCOUNT } from 'fxa-settings/src/models/mocks';
import { createMockSettingsIntegration } from '../mocks';
export default {
title: 'Components/Settings/HeaderLockup',
@ -26,14 +25,12 @@ const accountWithoutAvatar = {
},
} as unknown as Account;
const integration = createMockSettingsIntegration();
const storyWithContext = (account: Partial<Account>) => {
const context = { account: account as Account };
const story = () => (
<AppContext.Provider value={mockAppContext(context)}>
<HeaderLockup {...{ integration }} />
<HeaderLockup />
</AppContext.Provider>
);
return story;
@ -41,4 +38,4 @@ const storyWithContext = (account: Partial<Account>) => {
export const WithDefaultAvatar = storyWithContext(accountWithoutAvatar);
export const WithCustomAvatar = () => <HeaderLockup {...{ integration }} />;
export const WithCustomAvatar = () => <HeaderLockup />;

Просмотреть файл

@ -6,7 +6,6 @@ import React from 'react';
import { screen } from '@testing-library/react';
import { renderWithLocalizationProvider } from 'fxa-react/lib/test-utils/localizationProvider';
import HeaderLockup from '.';
import { createMockSettingsIntegration } from '../mocks';
import { userEvent } from '@testing-library/user-event';
import GleanMetrics from '../../../lib/glean';
@ -21,9 +20,7 @@ jest.mock('../../../lib/glean', () => ({
describe('HeaderLockup', () => {
it('renders as expected', () => {
renderWithLocalizationProvider(
<HeaderLockup integration={createMockSettingsIntegration()} />
);
renderWithLocalizationProvider(<HeaderLockup />);
const headerMenu = screen.getByTestId('header-menu');
expect(
@ -45,9 +42,7 @@ describe('HeaderLockup', () => {
});
it('emits Glean event on help link click', async () => {
renderWithLocalizationProvider(
<HeaderLockup integration={createMockSettingsIntegration()} />
);
renderWithLocalizationProvider(<HeaderLockup />);
await userEvent.click(screen.getByRole('link', { name: 'Help' }));
expect(GleanMetrics.accountPref.help).toHaveBeenCalled();
});

Просмотреть файл

@ -12,15 +12,10 @@ import DropDownAvatarMenu from '../DropDownAvatarMenu';
import { ReactComponent as Help } from './help.svg';
import { ReactComponent as Menu } from './menu.svg';
import { ReactComponent as Close } from './close.svg';
import { SettingsIntegration } from '../interfaces';
import Sidebar from '../Sidebar';
import GleanMetrics from '../../../lib/glean';
export const HeaderLockup = ({
integration,
}: {
integration: SettingsIntegration;
}) => {
export const HeaderLockup = () => {
const [sidebarRevealedState, setNavState] = useState(false);
const { l10n } = useLocalization();
const localizedHelpText = l10n.getString('header-help', null, 'Help');
@ -87,7 +82,7 @@ export const HeaderLockup = ({
/>
</LinkExternal>
<BentoMenu />
<DropDownAvatarMenu {...{ integration }} />
<DropDownAvatarMenu />
</>
);

Просмотреть файл

@ -13,7 +13,7 @@ import { Page2faReplaceRecoveryCodes } from '.';
import { Meta } from '@storybook/react';
import { LocationProvider } from '@reach/router';
import { withLocalization } from 'fxa-react/lib/storybooks';
import { MockSettingsAppLayout } from '../SettingsLayout/mocks';
import SettingsLayout from '../SettingsLayout';
const session = mockSession(true);
const account = {
@ -41,9 +41,9 @@ export default {
export const Default = () => (
<LocationProvider>
<AppContext.Provider value={mockAppContext({ account, session })}>
<MockSettingsAppLayout>
<SettingsLayout>
<Page2faReplaceRecoveryCodes />
</MockSettingsAppLayout>
</SettingsLayout>
</AppContext.Provider>
</LocationProvider>
);

Просмотреть файл

@ -7,7 +7,7 @@ import { LocationProvider } from '@reach/router';
import { Meta } from '@storybook/react';
import PageAvatar from './';
import { withLocalization } from 'fxa-react/lib/storybooks';
import { MockSettingsAppLayout } from '../SettingsLayout/mocks';
import SettingsLayout from '../SettingsLayout';
export default {
title: 'Pages/Settings/Avatar',
@ -17,8 +17,8 @@ export default {
export const Default = () => (
<LocationProvider>
<MockSettingsAppLayout>
<SettingsLayout>
<PageAvatar />
</MockSettingsAppLayout>
</SettingsLayout>
</LocationProvider>
);

Просмотреть файл

@ -7,7 +7,7 @@ import { PageChangePassword } from '.';
import { LocationProvider } from '@reach/router';
import { Meta } from '@storybook/react';
import { withLocalization } from 'fxa-react/lib/storybooks';
import { MockSettingsAppLayout } from '../SettingsLayout/mocks';
import SettingsLayout from '../SettingsLayout';
export default {
title: 'Pages/Settings/ChangePassword',
@ -17,8 +17,8 @@ export default {
export const Default = () => (
<LocationProvider>
<MockSettingsAppLayout>
<SettingsLayout>
<PageChangePassword />
</MockSettingsAppLayout>
</SettingsLayout>
</LocationProvider>
);

Просмотреть файл

@ -7,7 +7,7 @@ import React from 'react';
import { LocationProvider } from '@reach/router';
import { Meta } from '@storybook/react';
import { withLocalization } from 'fxa-react/lib/storybooks';
import { MockSettingsAppLayout } from '../SettingsLayout/mocks';
import SettingsLayout from '../SettingsLayout';
export default {
title: 'Pages/Settings/CreatePassword',
@ -17,8 +17,8 @@ export default {
export const Default = () => (
<LocationProvider>
<MockSettingsAppLayout>
<SettingsLayout>
<PageCreatePassword />
</MockSettingsAppLayout>
</SettingsLayout>
</LocationProvider>
);

Просмотреть файл

@ -7,7 +7,7 @@ import { PageDeleteAccount } from '.';
import { LocationProvider } from '@reach/router';
import { Meta } from '@storybook/react';
import { withLocalization } from 'fxa-react/lib/storybooks';
import { MockSettingsAppLayout } from '../SettingsLayout/mocks';
import SettingsLayout from '../SettingsLayout';
export default {
title: 'Pages/Settings/DeleteAccount',
@ -17,8 +17,8 @@ export default {
export const Default = () => (
<LocationProvider>
<MockSettingsAppLayout>
<SettingsLayout>
<PageDeleteAccount />
</MockSettingsAppLayout>
</SettingsLayout>
</LocationProvider>
);

Просмотреть файл

@ -7,7 +7,7 @@ import React from 'react';
import { PageDisplayName } from '.';
import { Meta } from '@storybook/react';
import { withLocalization } from 'fxa-react/lib/storybooks';
import { MockSettingsAppLayout } from '../SettingsLayout/mocks';
import SettingsLayout from '../SettingsLayout';
export default {
title: 'Pages/Settings/DisplayName',
@ -17,8 +17,8 @@ export default {
export const Default = () => (
<LocationProvider>
<MockSettingsAppLayout>
<SettingsLayout>
<PageDisplayName />
</MockSettingsAppLayout>
</SettingsLayout>
</LocationProvider>
);

Просмотреть файл

@ -7,7 +7,7 @@ import { LocationProvider } from '@reach/router';
import { PageSecondaryEmailAdd } from '.';
import { Meta } from '@storybook/react';
import { withLocalization } from 'fxa-react/lib/storybooks';
import { MockSettingsAppLayout } from '../SettingsLayout/mocks';
import SettingsLayout from '../SettingsLayout';
export default {
title: 'Pages/Settings/SecondaryEmailAdd',
@ -17,8 +17,8 @@ export default {
export const Default = () => (
<LocationProvider>
<MockSettingsAppLayout>
<SettingsLayout>
<PageSecondaryEmailAdd />
</MockSettingsAppLayout>
</SettingsLayout>
</LocationProvider>
);

Просмотреть файл

@ -7,7 +7,7 @@ import { Meta } from '@storybook/react';
import { PageSecondaryEmailVerify } from '.';
import { WindowLocation, LocationProvider } from '@reach/router';
import { withLocalization } from 'fxa-react/lib/storybooks';
import { MockSettingsAppLayout } from '../SettingsLayout/mocks';
import SettingsLayout from '../SettingsLayout';
export default {
title: 'Pages/Settings/SecondaryEmailVerify',
@ -21,8 +21,8 @@ const mockLocation = {
export const Default = () => (
<LocationProvider>
<MockSettingsAppLayout>
<SettingsLayout>
<PageSecondaryEmailVerify location={mockLocation} />
</MockSettingsAppLayout>
</SettingsLayout>
</LocationProvider>
);

Просмотреть файл

@ -16,7 +16,7 @@ import { AppContext } from 'fxa-settings/src/models';
import { MOCK_LINKED_ACCOUNTS } from '../LinkedAccounts/mocks';
import { Meta } from '@storybook/react';
import { withLocalization } from 'fxa-react/lib/storybooks';
import { MockSettingsAppLayout } from '../SettingsLayout/mocks';
import SettingsLayout from '../SettingsLayout';
const SERVICES_NON_MOBILE = MOCK_SERVICES.filter((d) => !isMobileDevice(d));
@ -72,9 +72,9 @@ const storyWithContext = (
const story = () => (
<LocationProvider>
<AppContext.Provider value={mockAppContext(context)}>
<MockSettingsAppLayout>
<SettingsLayout>
<PageSettings />
</MockSettingsAppLayout>
</SettingsLayout>
</AppContext.Provider>
</LocationProvider>
);

Просмотреть файл

@ -7,7 +7,7 @@ import React from 'react';
import { PageTwoStepAuthentication } from '.';
import { Meta } from '@storybook/react';
import { withLocalization } from 'fxa-react/lib/storybooks';
import { MockSettingsAppLayout } from '../SettingsLayout/mocks';
import SettingsLayout from '../SettingsLayout';
export default {
title: 'Pages/Settings/TwoStepAuthentication',
@ -17,8 +17,8 @@ export default {
export const Default = () => (
<LocationProvider>
<MockSettingsAppLayout>
<SettingsLayout>
<PageTwoStepAuthentication />
</MockSettingsAppLayout>
</SettingsLayout>
</LocationProvider>
);

Просмотреть файл

@ -2,7 +2,6 @@ import React from 'react';
import SettingsLayout from './index';
import { Meta } from '@storybook/react';
import { withLocalization } from 'fxa-react/lib/storybooks';
import { createMockSettingsIntegration } from '../mocks';
export default {
title: 'Components/Settings/SettingsLayout',
@ -10,10 +9,8 @@ export default {
decorators: [withLocalization],
} as Meta;
const integration = createMockSettingsIntegration();
export const Basic = () => (
<SettingsLayout {...{ integration }}>
<SettingsLayout>
<p>App content goes here</p>
</SettingsLayout>
);

Просмотреть файл

@ -6,15 +6,15 @@ import React from 'react';
import { screen } from '@testing-library/react';
import { renderWithRouter } from '../../../models/mocks';
import { SETTINGS_PATH } from '../../../constants';
import { MockSettingsAppLayout } from './mocks';
import SettingsLayout from '.';
it('renders the app with children', async () => {
const {
history: { navigate },
} = renderWithRouter(
<MockSettingsAppLayout>
<SettingsLayout>
<p data-testid="test-child">Hello, world!</p>
</MockSettingsAppLayout>
</SettingsLayout>
);
await navigate(SETTINGS_PATH);
expect(screen.getByTestId('app')).toBeInTheDocument();

Просмотреть файл

@ -7,14 +7,12 @@ import HeaderLockup from '../HeaderLockup';
import ContentSkip from '../ContentSkip';
import Footer from 'fxa-react/components/Footer';
import { AlertBar } from '../AlertBar';
import { SettingsIntegration } from '../interfaces';
type AppLayoutProps = {
type SettingsLayoutProps = {
children: React.ReactNode;
integration: SettingsIntegration;
};
export const SettingsLayout = ({ children, integration }: AppLayoutProps) => {
export const SettingsLayout = ({ children }: SettingsLayoutProps) => {
return (
<div
className="flex flex-col justify-between min-h-screen"
@ -22,7 +20,7 @@ export const SettingsLayout = ({ children, integration }: AppLayoutProps) => {
>
<ContentSkip />
<div id="body-top" className="hidden mobileLandscape:block" />
<HeaderLockup {...{ integration }} />
<HeaderLockup />
<div className="max-w-screen-desktopXl flex-1 w-full mx-auto tablet:px-20 desktop:px-12">
<main id="main" data-testid="main" className="w-full">
<AlertBar />

Просмотреть файл

@ -1,21 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import React from 'react';
import SettingsLayout from '.';
import { createMockSettingsIntegration } from '../mocks';
// Does not follow "Subject" naming convention for clarity because we use
// this throughout the app and not isolated tests.
export const MockSettingsAppLayout = ({
children,
}: {
children: React.ReactNode;
}) => {
return (
<SettingsLayout integration={createMockSettingsIntegration()}>
{children}
</SettingsLayout>
);
};

Просмотреть файл

@ -141,7 +141,7 @@ export const Settings = ({
}
return (
<SettingsLayout {...{ integration }}>
<SettingsLayout>
<Head />
<Router basepath={SETTINGS_PATH}>
<ScrollToTop default>