1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
5 import { Dispatch } from "redux";
6 import { RootState } from "~/store/store";
7 import { ServiceRepository } from "~/services/services";
8 import { setBreadcrumbs } from "~/store/breadcrumbs/breadcrumbs-actions";
9 import { LinkAccountType, AccountToLink } from "~/models/link-account";
10 import { logout, saveApiToken, saveUser } from "~/store/auth/auth-action";
11 import { unionize, ofType, UnionOf } from '~/common/unionize';
12 import { UserResource } from "~/models/user";
13 import { navigateToRootProject } from "~/store/navigation/navigation-action";
15 export const linkAccountPanelActions = unionize({
16 LOAD_LINKING: ofType<{
17 user: UserResource | undefined,
18 userToken: string | undefined,
19 userToLink: UserResource | undefined,
20 userToLinkToken: string | undefined }>(),
24 export type LinkAccountPanelAction = UnionOf<typeof linkAccountPanelActions>;
26 export const loadLinkAccountPanel = () =>
27 async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
28 dispatch(setBreadcrumbs([{ label: 'Link account'}]));
30 const curUser = getState().auth.user;
31 const curToken = getState().auth.apiToken;
32 if (curUser && curToken) {
33 const curUserResource = await services.userService.get(curUser.uuid);
34 const linkAccountData = services.linkAccountService.getFromSession();
36 // If there is link account data, then the user has logged in a second time
37 if (linkAccountData) {
38 // Use the saved token to make the api call to override the current users permissions
39 dispatch<any>(saveApiToken(linkAccountData.token));
40 const savedUserResource = await services.userService.get(linkAccountData.userUuid);
41 dispatch<any>(saveApiToken(curToken));
42 if (linkAccountData.type === LinkAccountType.ACCESS_OTHER_ACCOUNT) {
44 user: savedUserResource,
45 userToken: linkAccountData.token,
46 userToLink: curUserResource,
47 userToLinkToken: curToken
49 dispatch<any>(linkAccountPanelActions.LOAD_LINKING(params));
51 else if (linkAccountData.type === LinkAccountType.ADD_OTHER_LOGIN) {
53 user: curUserResource,
55 userToLink: savedUserResource,
56 userToLinkToken: linkAccountData.token
58 dispatch<any>(linkAccountPanelActions.LOAD_LINKING(params));
61 throw new Error("Invalid link account type.");
65 // If there is no link account session data, set the state to invoke the initial UI
66 dispatch<any>(linkAccountPanelActions.LOAD_LINKING({
67 user: curUserResource,
69 userToLink: undefined,
70 userToLinkToken: undefined }
76 export const saveAccountLinkData = (t: LinkAccountType) =>
77 (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
78 const accountToLink = {type: t, userUuid: services.authService.getUuid(), token: services.authService.getApiToken()} as AccountToLink;
79 services.linkAccountService.saveToSession(accountToLink);
83 export const getAccountLinkData = () =>
84 (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
85 return services.linkAccountService.getFromSession();
88 export const removeAccountLinkData = () =>
89 (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
90 const linkAccountData = services.linkAccountService.getFromSession();
91 services.linkAccountService.removeFromSession();
92 dispatch(linkAccountPanelActions.RESET_LINKING());
93 if (linkAccountData) {
94 services.userService.get(linkAccountData.userUuid).then(savedUser => {
95 dispatch(setBreadcrumbs([{ label: ''}]));
96 dispatch<any>(saveUser(savedUser));
97 dispatch<any>(saveApiToken(linkAccountData.token));
98 dispatch<any>(navigateToRootProject);
103 export const linkAccount = () =>
104 async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
105 const linkState = getState().linkAccountPanel;
106 const currentToken = getState().auth.apiToken;
107 if (linkState.userToLink && linkState.userToLinkToken && linkState.user && linkState.userToken && currentToken) {
109 // First create a project owned by the "userToLink" to accept everything from the current user
110 const projectName = `Migrated from ${linkState.user.email} (${linkState.user.uuid})`;
111 dispatch<any>(saveApiToken(linkState.userToLinkToken));
112 const newGroup = await services.projectService.create({
114 ensure_unique_name: true
116 dispatch<any>(saveApiToken(currentToken));
119 dispatch<any>(saveApiToken(linkState.userToken));
120 await services.linkAccountService.linkAccounts(linkState.userToLinkToken, newGroup.uuid);
121 dispatch<any>(saveApiToken(currentToken));
123 // If the link was successful, switch to the account that was merged with
124 if (linkState.userToLink && linkState.userToLinkToken) {
125 dispatch<any>(saveUser(linkState.userToLink));
126 dispatch<any>(saveApiToken(linkState.userToLinkToken));
127 dispatch<any>(navigateToRootProject);
129 services.linkAccountService.removeFromSession();
130 dispatch(linkAccountPanelActions.RESET_LINKING());
133 // If the account link operation fails, delete the previously made project
134 // and reset the link account state. The user will have to restart the process.
135 dispatch<any>(saveApiToken(linkState.userToLinkToken));
136 services.projectService.delete(newGroup.uuid);
137 dispatch<any>(saveApiToken(currentToken));
138 services.linkAccountService.removeFromSession();
139 dispatch(linkAccountPanelActions.RESET_LINKING());