X-Git-Url: https://git.arvados.org/arvados-workbench2.git/blobdiff_plain/2a5e5a512747d9bd92ffe1f89a991879a0897e4e..HEAD:/src/store/link-account-panel/link-account-panel-actions.ts diff --git a/src/store/link-account-panel/link-account-panel-actions.ts b/src/store/link-account-panel/link-account-panel-actions.ts index 61dc657e..5ca7494f 100644 --- a/src/store/link-account-panel/link-account-panel-actions.ts +++ b/src/store/link-account-panel/link-account-panel-actions.ts @@ -3,31 +3,44 @@ // SPDX-License-Identifier: AGPL-3.0 import { Dispatch } from "redux"; -import { RootState } from "~/store/store"; -import { ServiceRepository } from "~/services/services"; -import { setBreadcrumbs } from "~/store/breadcrumbs/breadcrumbs-actions"; -import { snackbarActions, SnackbarKind } from "~/store/snackbar/snackbar-actions"; -import { LinkAccountType, AccountToLink, LinkAccountStatus } from "~/models/link-account"; -import { saveApiToken, saveUser } from "~/store/auth/auth-action"; -import { unionize, ofType, UnionOf } from '~/common/unionize'; -import { UserResource } from "~/models/user"; -import { GroupResource } from "~/models/group"; +import { RootState } from "store/store"; +import { getUserUuid } from "common/getuser"; +import { ServiceRepository, createServices, setAuthorizationHeader } from "services/services"; +import { setBreadcrumbs } from "store/breadcrumbs/breadcrumbs-actions"; +import { snackbarActions, SnackbarKind } from "store/snackbar/snackbar-actions"; +import { LinkAccountType, AccountToLink, LinkAccountStatus } from "models/link-account"; +import { authActions, getConfig } from "store/auth/auth-action"; +import { unionize, ofType, UnionOf } from 'common/unionize'; +import { UserResource } from "models/user"; +import { GroupResource } from "models/group"; import { LinkAccountPanelError, OriginatingUser } from "./link-account-panel-reducer"; -import { login, logout } from "~/store/auth/auth-action"; +import { login, logout } from "store/auth/auth-action"; +import { progressIndicatorActions } from "store/progress-indicator/progress-indicator-actions"; +import { WORKBENCH_LOADING_SCREEN } from 'store/workbench/workbench-actions'; export const linkAccountPanelActions = unionize({ - LINK_INIT: ofType<{ targetUser: UserResource | undefined }>(), + LINK_INIT: ofType<{ + targetUser: UserResource | undefined + }>(), LINK_LOAD: ofType<{ originatingUser: OriginatingUser | undefined, targetUser: UserResource | undefined, targetUserToken: string | undefined, userToLink: UserResource | undefined, - userToLinkToken: string | undefined }>(), + userToLinkToken: string | undefined + }>(), LINK_INVALID: ofType<{ originatingUser: OriginatingUser | undefined, targetUser: UserResource | undefined, userToLink: UserResource | undefined, - error: LinkAccountPanelError }>(), + error: LinkAccountPanelError + }>(), + SET_SELECTED_CLUSTER: ofType<{ + selectedCluster: string + }>(), + SET_IS_PROCESSING: ofType<{ + isProcessing: boolean + }>(), HAS_SESSION_DATA: {} }); @@ -46,6 +59,13 @@ function validateLink(userToLink: UserResource, targetUser: UserResource) { return LinkAccountPanelError.NONE; } +const newServices = (dispatch: Dispatch, token: string) => { + const config = dispatch(getConfig); + const svc = createServices(config, { progressFn: () => { }, errorFn: () => { } }); + setAuthorizationHeader(svc, token); + return svc; +}; + export const checkForLinkStatus = () => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { const status = services.linkAccountService.getLinkOpStatus(); @@ -53,33 +73,29 @@ export const checkForLinkStatus = () => let msg: string; let msgKind: SnackbarKind; if (status.valueOf() === LinkAccountStatus.CANCELLED) { - msg = "Account link cancelled!", msgKind = SnackbarKind.INFO; + msg = "Account link cancelled!"; + msgKind = SnackbarKind.INFO; } else if (status.valueOf() === LinkAccountStatus.FAILED) { - msg = "Account link failed!", msgKind = SnackbarKind.ERROR; + msg = "Account link failed!"; + msgKind = SnackbarKind.ERROR; } else if (status.valueOf() === LinkAccountStatus.SUCCESS) { - msg = "Account link success!", msgKind = SnackbarKind.SUCCESS; + msg = "Account link success!"; + msgKind = SnackbarKind.SUCCESS; } else { - msg = "Unknown Error!", msgKind = SnackbarKind.ERROR; + msg = "Unknown Error!"; + msgKind = SnackbarKind.ERROR; } dispatch(snackbarActions.OPEN_SNACKBAR({ message: msg, kind: msgKind, hideDuration: 3000 })); services.linkAccountService.removeLinkOpStatus(); } }; -export const finishLinking = (status: LinkAccountStatus) => - (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { - services.linkAccountService.removeFromSession(); - services.linkAccountService.saveLinkOpStatus(status); - location.reload(); - }; - export const switchUser = (user: UserResource, token: string) => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { - dispatch(saveUser(user)); - dispatch(saveApiToken(token)); + dispatch(authActions.INIT_USER({ user, token })); }; export const linkFailed = () => @@ -89,125 +105,152 @@ export const linkFailed = () => if (linkState.userToLink && linkState.userToLinkToken && linkState.targetUser && linkState.targetUserToken) { if (linkState.originatingUser === OriginatingUser.TARGET_USER) { dispatch(switchUser(linkState.targetUser, linkState.targetUserToken)); - dispatch(linkAccountPanelActions.LINK_INIT({targetUser: linkState.targetUser})); } else if ((linkState.originatingUser === OriginatingUser.USER_TO_LINK)) { dispatch(switchUser(linkState.userToLink, linkState.userToLinkToken)); - dispatch(linkAccountPanelActions.LINK_INIT({targetUser: linkState.userToLink})); } - dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Account link failed.', kind: SnackbarKind.ERROR , hideDuration: 3000 })); } - dispatch(finishLinking(LinkAccountStatus.FAILED)); + services.linkAccountService.removeAccountToLink(); + services.linkAccountService.saveLinkOpStatus(LinkAccountStatus.FAILED); + window.location.reload(); }; export const loadLinkAccountPanel = () => async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { + try { + // If there are remote hosts, set the initial selected cluster by getting the first cluster that isn't the local cluster + if (getState().linkAccountPanel.selectedCluster === undefined) { + const localCluster = getState().auth.localCluster; + let selectedCluster = localCluster; + for (const key in getState().auth.remoteHosts) { + if (key !== localCluster) { + selectedCluster = key; + break; + } + } + dispatch(linkAccountPanelActions.SET_SELECTED_CLUSTER({ selectedCluster })); + } - // First check if an account link operation has completed - dispatch(checkForLinkStatus()); + // First check if an account link operation has completed + dispatch(checkForLinkStatus()); - // Continue loading the link account panel - dispatch(setBreadcrumbs([{ label: 'Link account'}])); - const curUser = getState().auth.user; - const curToken = getState().auth.apiToken; - if (curUser && curToken) { - const curUserResource = await services.userService.get(curUser.uuid); - const linkAccountData = services.linkAccountService.getFromSession(); + // Continue loading the link account panel + dispatch(setBreadcrumbs([{ label: 'Link account' }])); + const curUser = getState().auth.user; + const curToken = getState().auth.apiToken; + if (curUser && curToken) { - // If there is link account session data, then the user has logged in a second time - if (linkAccountData) { + // If there is link account session data, then the user has logged in a second time + const linkAccountData = services.linkAccountService.getAccountToLink(); + if (linkAccountData) { - // If the window is refreshed after the second login, cancel the linking - if (window.performance) { - if (performance.navigation.type === PerformanceNavigation.TYPE_BACK_FORWARD || - performance.navigation.type === PerformanceNavigation.TYPE_RELOAD) { - dispatch(cancelLinking()); - return; - } - } + dispatch(linkAccountPanelActions.SET_IS_PROCESSING({ isProcessing: true })); + const curUserResource = await services.userService.get(curUser.uuid); - // Use the token of the user we are getting data for. This avoids any admin/non-admin permissions - // issues since a user will always be able to query the api server for their own user data. - dispatch(saveApiToken(linkAccountData.token)); - const savedUserResource = await services.userService.get(linkAccountData.userUuid); - dispatch(saveApiToken(curToken)); - - let params: any; - if (linkAccountData.type === LinkAccountType.ACCESS_OTHER_ACCOUNT) { - params = { - originatingUser: OriginatingUser.USER_TO_LINK, - targetUser: curUserResource, - targetUserToken: curToken, - userToLink: savedUserResource, - userToLinkToken: linkAccountData.token - }; - } - else if (linkAccountData.type === LinkAccountType.ADD_OTHER_LOGIN) { - params = { - originatingUser: OriginatingUser.TARGET_USER, - targetUser: savedUserResource, - targetUserToken: linkAccountData.token, - userToLink: curUserResource, - userToLinkToken: curToken - }; - } - else { - // This should never really happen, but just in case, switch to the user that - // originated the linking operation (i.e. the user saved in session data) - dispatch(switchUser(savedUserResource, linkAccountData.token)); - dispatch(finishLinking(LinkAccountStatus.FAILED)); - } + // Use the token of the user we are getting data for. This avoids any admin/non-admin permissions + // issues since a user will always be able to query the api server for their own user data. + const svc = newServices(dispatch, linkAccountData.token); + const savedUserResource = await svc.userService.get(linkAccountData.userUuid); - dispatch(switchUser(params.targetUser, params.targetUserToken)); - const error = validateLink(params.userToLink, params.targetUser); - if (error === LinkAccountPanelError.NONE) { - dispatch(linkAccountPanelActions.LINK_LOAD(params)); + let params: any; + if (linkAccountData.type === LinkAccountType.ACCESS_OTHER_ACCOUNT || linkAccountData.type === LinkAccountType.ACCESS_OTHER_REMOTE_ACCOUNT) { + params = { + originatingUser: OriginatingUser.USER_TO_LINK, + targetUser: curUserResource, + targetUserToken: curToken, + userToLink: savedUserResource, + userToLinkToken: linkAccountData.token + }; + } + else if (linkAccountData.type === LinkAccountType.ADD_OTHER_LOGIN || linkAccountData.type === LinkAccountType.ADD_LOCAL_TO_REMOTE) { + params = { + originatingUser: OriginatingUser.TARGET_USER, + targetUser: savedUserResource, + targetUserToken: linkAccountData.token, + userToLink: curUserResource, + userToLinkToken: curToken + }; + } + else { + throw new Error("Unknown link account type"); + } + + dispatch(switchUser(params.targetUser, params.targetUserToken)); + const error = validateLink(params.userToLink, params.targetUser); + if (error === LinkAccountPanelError.NONE) { + dispatch(linkAccountPanelActions.LINK_LOAD(params)); + } + else { + dispatch(linkAccountPanelActions.LINK_INVALID({ + originatingUser: params.originatingUser, + targetUser: params.targetUser, + userToLink: params.userToLink, + error + })); + return; + } } else { - dispatch(linkAccountPanelActions.LINK_INVALID({ - originatingUser: params.originatingUser, - targetUser: params.targetUser, - userToLink: params.userToLink, - error})); + // If there is no link account session data, set the state to invoke the initial UI + const curUserResource = await services.userService.get(curUser.uuid); + dispatch(linkAccountPanelActions.LINK_INIT({ targetUser: curUserResource })); return; } } - else { - // If there is no link account session data, set the state to invoke the initial UI - dispatch(linkAccountPanelActions.LINK_INIT({ targetUser: curUserResource })); - return; - } + } + catch (e) { + dispatch(linkFailed()); + } + finally { + dispatch(linkAccountPanelActions.SET_IS_PROCESSING({ isProcessing: false })); } }; export const startLinking = (t: LinkAccountType) => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { - const accountToLink = {type: t, userUuid: services.authService.getUuid(), token: services.authService.getApiToken()} as AccountToLink; - services.linkAccountService.saveToSession(accountToLink); + const userUuid = getUserUuid(getState()); + if (!userUuid) { return; } + const accountToLink = { type: t, userUuid, token: services.authService.getApiToken() } as AccountToLink; + services.linkAccountService.saveAccountToLink(accountToLink); + const auth = getState().auth; + const isLocalUser = auth.user!.uuid.substring(0, 5) === auth.localCluster; + let homeCluster = auth.localCluster; + if (isLocalUser && t === LinkAccountType.ACCESS_OTHER_REMOTE_ACCOUNT) { + homeCluster = getState().linkAccountPanel.selectedCluster!; + } + dispatch(logout()); - dispatch(login(auth.localCluster, auth.remoteHosts[auth.homeCluster])); + dispatch(login(auth.localCluster, homeCluster, auth.loginCluster, auth.remoteHosts)); }; export const getAccountLinkData = () => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { - return services.linkAccountService.getFromSession(); + return services.linkAccountService.getAccountToLink(); }; -export const cancelLinking = () => +export const cancelLinking = (reload: boolean = false) => async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { let user: UserResource | undefined; try { // When linking is cancelled switch to the originating user (i.e. the user saved in session data) - const linkAccountData = services.linkAccountService.getFromSession(); + dispatch(progressIndicatorActions.START_WORKING(WORKBENCH_LOADING_SCREEN)); + const linkAccountData = services.linkAccountService.getAccountToLink(); if (linkAccountData) { - dispatch(saveApiToken(linkAccountData.token)); - user = await services.userService.get(linkAccountData.userUuid); + services.linkAccountService.removeAccountToLink(); + const svc = newServices(dispatch, linkAccountData.token); + user = await svc.userService.get(linkAccountData.userUuid); dispatch(switchUser(user, linkAccountData.token)); + services.linkAccountService.saveLinkOpStatus(LinkAccountStatus.CANCELLED); } } finally { - dispatch(finishLinking(LinkAccountStatus.CANCELLED)); + if (reload) { + window.location.reload(); + } + else { + dispatch(progressIndicatorActions.STOP_WORKING(WORKBENCH_LOADING_SCREEN)); + } } }; @@ -232,24 +275,24 @@ export const linkAccount = () => try { // The merge api links the user sending the request into the user - // specified in the request, so switch users for this api call - dispatch(saveApiToken(linkState.userToLinkToken)); - await services.linkAccountService.linkAccounts(linkState.targetUserToken, newGroup.uuid); + // specified in the request, so change the authorization header accordingly + const svc = newServices(dispatch, linkState.userToLinkToken); + await svc.linkAccountService.linkAccounts(linkState.targetUserToken, newGroup.uuid); dispatch(switchUser(linkState.targetUser, linkState.targetUserToken)); + services.linkAccountService.removeAccountToLink(); + services.linkAccountService.saveLinkOpStatus(LinkAccountStatus.SUCCESS); + window.location.reload(); } - catch(e) { + catch (e) { // If the link operation fails, delete the previously made project try { - dispatch(saveApiToken(linkState.targetUserToken)); - await services.projectService.delete(newGroup.uuid); + const svc = newServices(dispatch, linkState.targetUserToken); + await svc.projectService.delete(newGroup.uuid); } finally { dispatch(linkFailed()); } throw e; } - finally { - dispatch(finishLinking(LinkAccountStatus.SUCCESS)); - } } - }; \ No newline at end of file + };