Merge pull request #13670 from fabuloso/secure-google-oauth-logout

Revoke Google OAuth refresh token on logout
This commit is contained in:
Johan Haals
2022-10-04 10:36:00 +02:00
committed by GitHub
12 changed files with 105 additions and 12 deletions
@@ -32,6 +32,7 @@ import {
ProfileInfoApi,
ProfileInfo,
useApi,
errorApiRef,
IconComponent,
} from '@backstage/core-plugin-api';
import { ProviderSettingsAvatar } from './ProviderSettingsAvatar';
@@ -46,6 +47,7 @@ export const ProviderSettingsItem = (props: {
const { title, description, icon: Icon, apiRef } = props;
const api = useApi(apiRef);
const errorApi = useApi(errorApiRef);
const [signedIn, setSignedIn] = useState(false);
const emptyProfile: ProfileInfo = {};
const [profile, setProfile] = useState(emptyProfile);
@@ -126,7 +128,10 @@ export const ProviderSettingsItem = (props: {
<Button
variant="outlined"
color="primary"
onClick={() => (signedIn ? api.signOut() : api.signIn())}
onClick={() => {
const action = signedIn ? api.signOut() : api.signIn();
action.catch(error => errorApi.post(error));
}}
>
{signedIn ? `Sign out` : `Sign in`}
</Button>
@@ -26,7 +26,7 @@ import { UserSettingsAuthProviders } from './UserSettingsAuthProviders';
import { ApiProvider, ConfigReader } from '@backstage/core-app-api';
import { configApiRef, googleAuthApiRef } from '@backstage/core-plugin-api';
const mockSignInHandler = jest.fn().mockReturnValue('');
const mockSignInHandler = jest.fn().mockReturnValue(Promise.resolve());
const mockGoogleAuth = {
sessionState$: () => ({
[Symbol.observable]: jest.fn(),
@@ -14,8 +14,15 @@
* limitations under the License.
*/
import { renderWithEffects, wrapInTestApp } from '@backstage/test-utils';
import { fireEvent } from '@testing-library/react';
import {
MockErrorApi,
renderInTestApp,
TestApiProvider,
renderWithEffects,
wrapInTestApp,
} from '@backstage/test-utils';
import { errorApiRef, identityApiRef } from '@backstage/core-plugin-api';
import { fireEvent, waitFor } from '@testing-library/react';
import React from 'react';
import { UserSettingsMenu } from './UserSettingsMenu';
@@ -30,4 +37,29 @@ describe('<UserSettingsMenu />', () => {
expect(rendered.getByText('Sign Out')).toBeInTheDocument();
});
it('handles errors that occur when signing out', async () => {
const failingIdentityApi = {
signOut: jest.fn().mockRejectedValue(new Error('Logout error')),
};
const mockErrorApi = new MockErrorApi({ collect: true });
const rendered = await renderInTestApp(
<TestApiProvider
apis={[
[errorApiRef, mockErrorApi],
[identityApiRef, failingIdentityApi],
]}
>
<UserSettingsMenu />
</TestApiProvider>,
);
const menuButton = rendered.getByLabelText('more');
fireEvent.click(menuButton);
fireEvent.click(rendered.getByText('Sign Out'));
await waitFor(() => {
expect(mockErrorApi.getErrors()).toHaveLength(1);
});
});
});
@@ -18,10 +18,15 @@ import React from 'react';
import { IconButton, ListItemIcon, Menu, MenuItem } from '@material-ui/core';
import SignOutIcon from '@material-ui/icons/MeetingRoom';
import MoreVertIcon from '@material-ui/icons/MoreVert';
import { identityApiRef, useApi } from '@backstage/core-plugin-api';
import {
identityApiRef,
errorApiRef,
useApi,
} from '@backstage/core-plugin-api';
/** @public */
export const UserSettingsMenu = () => {
const errorApi = useApi(errorApiRef);
const identityApi = useApi(identityApiRef);
const [open, setOpen] = React.useState(false);
const [anchorEl, setAnchorEl] = React.useState<undefined | HTMLElement>(
@@ -48,7 +53,12 @@ export const UserSettingsMenu = () => {
<MoreVertIcon />
</IconButton>
<Menu anchorEl={anchorEl} open={open} onClose={handleClose}>
<MenuItem data-testid="sign-out" onClick={() => identityApi.signOut()}>
<MenuItem
data-testid="sign-out"
onClick={() =>
identityApi.signOut().catch(error => errorApi.post(error))
}
>
<ListItemIcon>
<SignOutIcon />
</ListItemIcon>