import { AxiosInstance } from "axios";
import { ApiActions } from "~/services/api/api-actions";
import { AccountToLink } from "~/models/link-account";
+import { CommonService } from "~/services/common-service/common-service";
+import { AuthService } from "../auth-service/auth-service";
export const USER_LINK_ACCOUNT_KEY = 'accountToLink';
export class LinkAccountService {
constructor(
- protected apiClient: AxiosInstance,
+ protected serverApi: AxiosInstance,
protected actions: ApiActions) { }
- public saveLinkAccount(account: AccountToLink) {
+ public saveToSession(account: AccountToLink) {
sessionStorage.setItem(USER_LINK_ACCOUNT_KEY, JSON.stringify(account));
}
- public removeLinkAccount() {
+ public removeFromSession() {
sessionStorage.removeItem(USER_LINK_ACCOUNT_KEY);
}
- public getLinkAccount() {
+ public getFromSession() {
const data = sessionStorage.getItem(USER_LINK_ACCOUNT_KEY);
return data ? JSON.parse(data) as AccountToLink : undefined;
}
+
+ public linkAccounts(newUserToken: string, newGroupUuid: string) {
+ const params = {
+ new_user_token: newUserToken,
+ new_owner_uuid: newGroupUuid,
+ redirect_to_new_user: true
+ };
+ return CommonService.defaultResponse(
+ this.serverApi.post('/users/merge/', params),
+ this.actions,
+ false
+ );
+ }
}
\ No newline at end of file
import { navigateToRootProject } from "~/store/navigation/navigation-action";
export const linkAccountPanelActions = unionize({
- LOAD_LINKING: ofType<{ user: UserResource | undefined, userToLink: UserResource | undefined }>(),
- REMOVE_LINKING: {}
+ LOAD_LINKING: ofType<{
+ user: UserResource | undefined,
+ userToken: string | undefined,
+ userToLink: UserResource | undefined,
+ userToLinkToken: string | undefined }>(),
+ RESET_LINKING: {}
});
export type LinkAccountPanelAction = UnionOf<typeof linkAccountPanelActions>;
export const loadLinkAccountPanel = () =>
- (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
+ async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
dispatch(setBreadcrumbs([{ label: 'Link account'}]));
const curUser = getState().auth.user;
- if (curUser) {
- services.userService.get(curUser.uuid).then(curUserResource => {
- const linkAccountData = services.linkAccountService.getLinkAccount();
- if (linkAccountData) {
- services.userService.get(linkAccountData.userUuid).then(savedUserResource => {
- if (linkAccountData.type === LinkAccountType.ADD_OTHER_LOGIN) {
- dispatch<any>(linkAccountPanelActions.LOAD_LINKING({ userToLink: curUserResource, user: savedUserResource }));
- }
- else if (linkAccountData.type === LinkAccountType.ACCESS_OTHER_ACCOUNT) {
- dispatch<any>(linkAccountPanelActions.LOAD_LINKING({ userToLink: savedUserResource, user: curUserResource }));
- }
- else {
- throw new Error('Invalid link account type.');
- }
- });
+ const curToken = getState().auth.apiToken;
+ if (curUser && curToken) {
+ const curUserResource = await services.userService.get(curUser.uuid);
+ const linkAccountData = services.linkAccountService.getFromSession();
+
+ // If there is link account data, then the user has logged in a second time
+ if (linkAccountData) {
+ // Use the saved token to make the api call to override the current users permissions
+ dispatch<any>(saveApiToken(linkAccountData.token));
+ const savedUserResource = await services.userService.get(linkAccountData.userUuid);
+ dispatch<any>(saveApiToken(curToken));
+ if (linkAccountData.type === LinkAccountType.ACCESS_OTHER_ACCOUNT) {
+ const params = {
+ user: savedUserResource,
+ userToken: linkAccountData.token,
+ userToLink: curUserResource,
+ userToLinkToken: curToken
+ };
+ dispatch<any>(linkAccountPanelActions.LOAD_LINKING(params));
+ }
+ else if (linkAccountData.type === LinkAccountType.ADD_OTHER_LOGIN) {
+ const params = {
+ user: curUserResource,
+ userToken: curToken,
+ userToLink: savedUserResource,
+ userToLinkToken: linkAccountData.token
+ };
+ dispatch<any>(linkAccountPanelActions.LOAD_LINKING(params));
}
else {
- dispatch<any>(linkAccountPanelActions.LOAD_LINKING({ userToLink: undefined, user: curUserResource }));
+ throw new Error("Invalid link account type.");
}
- });
+ }
+ else {
+ // If there is no link account session data, set the state to invoke the initial UI
+ dispatch<any>(linkAccountPanelActions.LOAD_LINKING({
+ user: curUserResource,
+ userToken: curToken,
+ userToLink: undefined,
+ userToLinkToken: undefined }
+ ));
+ }
}
};
export const saveAccountLinkData = (t: LinkAccountType) =>
(dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
const accountToLink = {type: t, userUuid: services.authService.getUuid(), token: services.authService.getApiToken()} as AccountToLink;
- services.linkAccountService.saveLinkAccount(accountToLink);
+ services.linkAccountService.saveToSession(accountToLink);
dispatch(logout());
};
export const getAccountLinkData = () =>
(dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
- return services.linkAccountService.getLinkAccount();
+ return services.linkAccountService.getFromSession();
};
export const removeAccountLinkData = () =>
(dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
- const linkAccountData = services.linkAccountService.getLinkAccount();
- services.linkAccountService.removeLinkAccount();
- dispatch(linkAccountPanelActions.REMOVE_LINKING());
+ const linkAccountData = services.linkAccountService.getFromSession();
+ services.linkAccountService.removeFromSession();
+ dispatch(linkAccountPanelActions.RESET_LINKING());
if (linkAccountData) {
services.userService.get(linkAccountData.userUuid).then(savedUser => {
dispatch(setBreadcrumbs([{ label: ''}]));
};
export const linkAccount = () =>
- (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
+ async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
+ const linkState = getState().linkAccountPanel;
+ const currentToken = getState().auth.apiToken;
+ if (linkState.userToLink && linkState.userToLinkToken && linkState.user && linkState.userToken && currentToken) {
+
+ // First create a project owned by the "userToLink" to accept everything from the current user
+ const projectName = `Migrated from ${linkState.user.email} (${linkState.user.uuid})`;
+ dispatch<any>(saveApiToken(linkState.userToLinkToken));
+ const newGroup = await services.projectService.create({
+ name: projectName,
+ ensure_unique_name: true
+ });
+ dispatch<any>(saveApiToken(currentToken));
+
+ try {
+ dispatch<any>(saveApiToken(linkState.userToken));
+ await services.linkAccountService.linkAccounts(linkState.userToLinkToken, newGroup.uuid);
+ dispatch<any>(saveApiToken(currentToken));
+
+ // If the link was successful, switch to the account that was merged with
+ if (linkState.userToLink && linkState.userToLinkToken) {
+ dispatch<any>(saveUser(linkState.userToLink));
+ dispatch<any>(saveApiToken(linkState.userToLinkToken));
+ dispatch<any>(navigateToRootProject);
+ }
+ services.linkAccountService.removeFromSession();
+ dispatch(linkAccountPanelActions.RESET_LINKING());
+ }
+ catch(e) {
+ // If the account link operation fails, delete the previously made project
+ // and reset the link account state. The user will have to restart the process.
+ dispatch<any>(saveApiToken(linkState.userToLinkToken));
+ services.projectService.delete(newGroup.uuid);
+ dispatch<any>(saveApiToken(currentToken));
+ services.linkAccountService.removeFromSession();
+ dispatch(linkAccountPanelActions.RESET_LINKING());
+ throw e;
+ }
+ }
};
\ No newline at end of file
Grid,
} from '@material-ui/core';
import { ArvadosTheme } from '~/common/custom-theme';
-import { User, UserResource } from "~/models/user";
-import { LinkAccountType, AccountToLink } from "~/models/link-account";
-import { formatDate }from "~/common/formatters";
+import { UserResource } from "~/models/user";
+import { LinkAccountType } from "~/models/link-account";
+import { formatDate } from "~/common/formatters";
type CssRules = 'root';// | 'gridItem' | 'label' | 'title' | 'actions';
{ userToLink && user && <Grid container spacing={24}>
<Grid container item direction="column" spacing={24}>
<Grid item>
- Clicking 'Link accounts' will link {displayUser(userToLink, true)} to {displayUser(user, true)}.
+ Clicking 'Link accounts' will link {displayUser(user, true)} to {displayUser(userToLink, true)}.
</Grid>
<Grid item>
- After linking, logging in as {displayUser(userToLink)} will log you into the same account as {displayUser(user)}.
+ After linking, logging in as {displayUser(user)} will log you into the same account as {displayUser(userToLink)}.
</Grid>
<Grid item>
- Any object owned by {displayUser(userToLink)} will be transfered to {displayUser(user)}.
+ Any object owned by {displayUser(user)} will be transfered to {displayUser(userToLink)}.
</Grid>
</Grid>
<Grid container item direction="row" spacing={24}>