Skip to content

Commit

Permalink
upcoming: [M3-7814, M3-7819] - Fix Account Switching (#10234)
Browse files Browse the repository at this point in the history
Co-authored-by: Jaalah Ramos <jaalah.ramos@gmail.com>
Co-authored-by: Mariah Jacobs <114685994+mjac0bs@users.noreply.github.com>
Co-authored-by: Joe D'Amore <jdamore@linode.com>
  • Loading branch information
4 people authored Feb 27, 2024
1 parent d53f3f5 commit e255d5b
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 63 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/manager": Upcoming Features
---

Fix Account Switching ([#10234](https://github.com/linode/manager/pull/10234))
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/manager": Upcoming Features
---

Fix to ensure ChildAccountList receives proper account token ([#10234](https://github.com/linode/manager/pull/10234))
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ const assertAuthLocalStorage = (
expiry: string,
scopes: string
) => {
assertLocalStorageValue('authentication/token', token);
assertLocalStorageValue('authentication/token', `Bearer ${token}`);
assertLocalStorageValue('authentication/expire', expiry);
assertLocalStorageValue('authentication/scopes', scopes);
};
Expand Down Expand Up @@ -126,6 +126,23 @@ describe('Parent/Child account switching', () => {
.should('be.enabled')
.click();

// Prepare up mocks in advance of the account switch. As soon as the child account is clicked,
// Cloud will replace its stored token with the token provided by the API and then reload.
// From that point forward, we will not have a valid test account token stored in local storage,
// so all non-intercepted API requests will respond with a 401 status code and we will get booted to login.
// We'll mitigate this by broadly mocking ALL API-v4 requests, then applying more specific mocks to the
// individual requests as needed.
mockAllApiRequests();
mockGetLinodes([]);
mockGetRegions([]);
mockGetEvents([]);
mockGetNotifications([]);
mockGetAccount(mockChildAccount);
mockGetProfile(mockParentProfile);
mockGetUser(mockParentUser);

// Mock the account switch itself -- we have to do this after the mocks above
// to ensure that it is applied.
mockCreateChildAccountToken(mockChildAccount, mockChildAccountToken).as(
'switchAccount'
);
Expand All @@ -147,24 +164,6 @@ describe('Parent/Child account switching', () => {
mockChildAccountToken.scopes
);

// From this point forward, we will not have a valid test account token stored in local storage,
// so all non-intercepted API requests will respond with a 401 status code and we will get booted to login.
// We'll mitigate this by broadly mocking ALL API-v4 requests, then applying more specific mocks to the
// individual requests as needed.
mockAllApiRequests();
mockGetLinodes([]);
mockGetRegions([]);
mockGetEvents([]);
mockGetNotifications([]);

mockGetAccount(mockChildAccount);
mockGetProfile(mockParentProfile);
mockGetUser(mockParentUser);

// TODO Remove the call to `cy.reload()` once Cloud Manager automatically updates itself upon account switching.
// TODO Add assertions for toast upon account switch. This might involve improving mocks for events/notifications.
cy.reload();

// Confirm expected username and company are shown in user menu button.
assertUserMenuButton(
mockParentProfile.username,
Expand Down Expand Up @@ -205,6 +204,21 @@ describe('Parent/Child account switching', () => {
.click();
});

// Prepare up mocks in advance of the account switch. As soon as the child account is clicked,
// Cloud will replace its stored token with the token provided by the API and then reload.
// From that point forward, we will not have a valid test account token stored in local storage,
// so all non-intercepted API requests will respond with a 401 status code and we will get booted to login.
// We'll mitigate this by broadly mocking ALL API-v4 requests, then applying more specific mocks to the
// individual requests as needed.
mockAllApiRequests();
mockGetLinodes([]);
mockGetRegions([]);
mockGetEvents([]);
mockGetNotifications([]);
mockGetAccount(mockChildAccount);
mockGetProfile(mockParentProfile);
mockGetUser(mockParentUser);

// Click mock company name in "Switch Account" drawer.
mockCreateChildAccountToken(mockChildAccount, mockChildAccountToken).as(
'switchAccount'
Expand All @@ -227,23 +241,6 @@ describe('Parent/Child account switching', () => {
mockChildAccountToken.scopes
);

// From this point forward, we will not have a valid test account token stored in local storage,
// so all non-intercepted API requests will respond with a 401 status code and we will get booted to login.
// We'll mitigate this by broadly mocking ALL API-v4 requests, then applying more specific mocks to the
// individual requests as needed.
mockAllApiRequests();
mockGetLinodes([]);
mockGetRegions([]);
mockGetEvents([]);
mockGetNotifications([]);
mockGetAccount(mockChildAccount);
mockGetProfile(mockParentProfile);
mockGetUser(mockParentUser);

// TODO Remove the call to `cy.reload()` once Cloud Manager automatically updates itself upon account switching.
// TODO Add assertions for toast upon account switch. This might involve improving mocks for events/notifications.
cy.reload();

// Confirm expected username and company are shown in user menu button.
assertUserMenuButton(
mockParentProfile.username,
Expand Down
45 changes: 18 additions & 27 deletions packages/manager/src/features/Account/SwitchAccountDrawer.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { createChildAccountPersonalAccessToken } from '@linode/api-v4';
import React from 'react';
import { useHistory } from 'react-router-dom';

import { StyledLinkButton } from 'src/components/Button/StyledLinkButton';
import { Drawer } from 'src/components/Drawer';
Expand All @@ -18,6 +17,7 @@ import { getStorage } from 'src/utilities/storage';

import { ChildAccountList } from './SwitchAccounts/ChildAccountList';

import type { Token } from '@linode/api-v4';
import type { APIError, ChildAccountPayload, UserType } from '@linode/api-v4';
import type { State as AuthState } from 'src/store/authentication';

Expand All @@ -38,7 +38,8 @@ export const SwitchAccountDrawer = (props: Props) => {
);

const currentTokenWithBearer = useCurrentToken() ?? '';
const history = useHistory();
const currentParentTokenWithBearer =
getStorage('authentication/parent_token/token') ?? '';

const handleClose = React.useCallback(() => {
onClose();
Expand Down Expand Up @@ -76,11 +77,9 @@ export const SwitchAccountDrawer = (props: Props) => {
[]
);

// Navigate to the current location, triggering a re-render without a full page reload.
const refreshPage = React.useCallback(() => {
// TODO: Parent/Child: We need to test this against the real API.
history.push(history.location.pathname);
}, [history]);
location.reload();
}, []);

const handleSwitchToChildAccount = React.useCallback(
async ({
Expand All @@ -97,17 +96,13 @@ export const SwitchAccountDrawer = (props: Props) => {
isProxyUser: boolean;
}) => {
try {
// TODO: Parent/Child: FOR MSW ONLY, REMOVE WHEN API IS READY
// ================================================================
// throw new Error(
// `Account switching failed. Try again.`
// );
// ================================================================

// We don't need to worry about this if we're a proxy user.
if (!isProxyUser) {
const parentToken = {
const parentToken: Token = {
created: getStorage('authentication/created'),
expiry: getStorage('authentication/expire'),
id: getStorage('authentication/id'),
label: getStorage('authentication/label'),
scopes: getStorage('authentication/scopes'),
token: currentTokenWithBearer ?? '',
};
Expand All @@ -128,7 +123,10 @@ export const SwitchAccountDrawer = (props: Props) => {

setTokenInLocalStorage({
prefix: 'authentication/proxy_token',
token: proxyToken,
token: {
...proxyToken,
token: `Bearer ${proxyToken.token}`,
},
});

updateCurrentTokenBasedOnUserType({
Expand All @@ -139,16 +137,6 @@ export const SwitchAccountDrawer = (props: Props) => {
refreshPage();
} catch (error) {
setIsProxyTokenError(error as APIError[]);

// TODO: Parent/Child: FOR MSW ONLY, REMOVE WHEN API IS READY
// ================================================================
// setIsProxyTokenError([
// {
// field: 'token',
// reason: error.message,
// },
// ]);
// ================================================================
}
},
[getProxyToken, refreshPage]
Expand All @@ -168,7 +156,8 @@ export const SwitchAccountDrawer = (props: Props) => {

updateCurrentTokenBasedOnUserType({ userType: 'parent' });
handleClose();
}, [handleClose, isProxyUser]);
refreshPage();
}, [handleClose, refreshPage]);

return (
<Drawer onClose={handleClose} open={open} title="Switch Account">
Expand Down Expand Up @@ -201,7 +190,9 @@ export const SwitchAccountDrawer = (props: Props) => {
.
</Typography>
<ChildAccountList
currentTokenWithBearer={currentTokenWithBearer}
currentTokenWithBearer={
isProxyUser ? currentParentTokenWithBearer : currentTokenWithBearer
}
isProxyUser={isProxyUser}
onClose={handleClose}
onSwitchAccount={handleSwitchToChildAccount}
Expand Down

0 comments on commit e255d5b

Please sign in to comment.