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 { snackbarActions, SnackbarKind } from "~/store/snackbar/snackbar-actions";
10 import { LinkAccountType, AccountToLink } from "~/models/link-account";
11 import { logout, saveApiToken, saveUser } from "~/store/auth/auth-action";
12 import { unionize, ofType, UnionOf } from '~/common/unionize';
13 import { UserResource, User } from "~/models/user";
14 import { navigateToRootProject } from "~/store/navigation/navigation-action";
15 import { GroupResource } from "~/models/group";
16 import { LinkAccountPanelError } from "./link-account-panel-reducer";
18 export const linkAccountPanelActions = unionize({
19 INIT: ofType<{ user: UserResource | undefined }>(),
21 user: UserResource | undefined,
22 userToken: string | undefined,
23 userToLink: UserResource | undefined,
24 userToLinkToken: string | undefined }>(),
27 user: UserResource | undefined,
28 userToLink: UserResource | undefined,
29 error: LinkAccountPanelError }>(),
32 export type LinkAccountPanelAction = UnionOf<typeof linkAccountPanelActions>;
34 function validateLink(user: UserResource, userToLink: UserResource) {
35 if (user.uuid === userToLink.uuid) {
36 return LinkAccountPanelError.SAME_USER;
38 else if (!user.isAdmin && userToLink.isAdmin) {
39 return LinkAccountPanelError.NON_ADMIN;
41 return LinkAccountPanelError.NONE;
44 export const loadLinkAccountPanel = () =>
45 async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
46 dispatch(setBreadcrumbs([{ label: 'Link account'}]));
48 const curUser = getState().auth.user;
49 const curToken = getState().auth.apiToken;
50 if (curUser && curToken) {
51 const curUserResource = await services.userService.get(curUser.uuid);
52 const linkAccountData = services.linkAccountService.getFromSession();
54 // If there is link account session data, then the user has logged in a second time
55 if (linkAccountData) {
56 dispatch<any>(saveApiToken(linkAccountData.token));
57 const savedUserResource = await services.userService.get(linkAccountData.userUuid);
58 dispatch<any>(saveApiToken(curToken));
61 if (linkAccountData.type === LinkAccountType.ACCESS_OTHER_ACCOUNT) {
63 user: savedUserResource,
64 userToken: linkAccountData.token,
65 userToLink: curUserResource,
66 userToLinkToken: curToken
69 else if (linkAccountData.type === LinkAccountType.ADD_OTHER_LOGIN) {
71 user: curUserResource,
73 userToLink: savedUserResource,
74 userToLinkToken: linkAccountData.token
78 throw new Error("Invalid link account type.");
81 const error = validateLink(params.user, params.userToLink);
82 if (error === LinkAccountPanelError.NONE) {
83 dispatch<any>(linkAccountPanelActions.LOAD(params));
86 dispatch<any>(linkAccountPanelActions.INVALID({
88 userToLink: params.userToLink,
94 // If there is no link account session data, set the state to invoke the initial UI
95 dispatch<any>(linkAccountPanelActions.INIT({
96 user: curUserResource }
102 export const saveAccountLinkData = (t: LinkAccountType) =>
103 (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
104 const accountToLink = {type: t, userUuid: services.authService.getUuid(), token: services.authService.getApiToken()} as AccountToLink;
105 services.linkAccountService.saveToSession(accountToLink);
109 export const getAccountLinkData = () =>
110 (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
111 return services.linkAccountService.getFromSession();
114 export const removeAccountLinkData = () =>
115 async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
117 const linkAccountData = services.linkAccountService.getFromSession();
118 if (linkAccountData) {
119 const savedUser = await services.userService.get(linkAccountData.userUuid);
120 dispatch<any>(saveUser(savedUser));
121 dispatch<any>(saveApiToken(linkAccountData.token));
125 dispatch<any>(navigateToRootProject);
126 dispatch(linkAccountPanelActions.RESET());
127 services.linkAccountService.removeFromSession();
131 export const linkAccount = () =>
132 async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
133 const linkState = getState().linkAccountPanel;
134 const currentToken = getState().auth.apiToken;
135 if (linkState.userToLink && linkState.userToLinkToken && linkState.user && linkState.userToken && currentToken) {
137 // First create a project owned by the "userToLink" to accept everything from the current user
138 const projectName = `Migrated from ${linkState.user.email} (${linkState.user.uuid})`;
139 let newGroup: GroupResource;
141 dispatch<any>(saveApiToken(linkState.userToLinkToken));
142 newGroup = await services.projectService.create({
144 ensure_unique_name: true
148 dispatch<any>(saveApiToken(currentToken));
149 dispatch(snackbarActions.OPEN_SNACKBAR({
150 message: 'Account link failed.', kind: SnackbarKind.ERROR , hideDuration: 3000
156 // Use the token of the account that is getting merged to call the merge api
157 dispatch<any>(saveApiToken(linkState.userToken));
158 await services.linkAccountService.linkAccounts(linkState.userToLinkToken, newGroup.uuid);
160 // If the link was successful, switch to the account that was merged with
161 dispatch<any>(saveUser(linkState.userToLink));
162 dispatch<any>(saveApiToken(linkState.userToLinkToken));
165 // If the link operation fails, delete the previously made project
166 // and stay logged in to the current account.
168 dispatch<any>(saveApiToken(linkState.userToLinkToken));
169 await services.projectService.delete(newGroup.uuid);
172 dispatch<any>(saveApiToken(currentToken));
173 dispatch(snackbarActions.OPEN_SNACKBAR({
174 message: 'Account link failed.', kind: SnackbarKind.ERROR , hideDuration: 3000
180 dispatch<any>(navigateToRootProject);
181 services.linkAccountService.removeFromSession();
182 dispatch(linkAccountPanelActions.RESET());