X-Git-Url: https://git.arvados.org/arvados-workbench2.git/blobdiff_plain/3bc058f0bcd746912230e29683509d692fa8203e..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 e63da1a4..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,33 +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 { progressIndicatorActions } from "~/store/progress-indicator/progress-indicator-actions"; -import { WORKBENCH_LOADING_SCREEN } from '~/store/workbench/workbench-actions'; +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: {} }); @@ -48,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(); @@ -55,16 +73,20 @@ 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(); @@ -73,8 +95,7 @@ export const checkForLinkStatus = () => 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 = () => @@ -84,95 +105,123 @@ 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 })); } services.linkAccountService.removeAccountToLink(); + services.linkAccountService.saveLinkOpStatus(LinkAccountStatus.FAILED); + window.location.reload(); }; export const loadLinkAccountPanel = () => async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { - // 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.getAccountToLink(); + 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 })); + } - // If there is link account session data, then the user has logged in a second time - if (linkAccountData) { + // First check if an account link operation has completed + dispatch(checkForLinkStatus()); - // 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)); - services.linkAccountService.removeAccountToLink(); - dispatch(linkAccountPanelActions.LINK_INIT({targetUser:savedUserResource})); - } + // 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 + const linkAccountData = services.linkAccountService.getAccountToLink(); + if (linkAccountData) { + + dispatch(linkAccountPanelActions.SET_IS_PROCESSING({ isProcessing: true })); + const curUserResource = await services.userService.get(curUser.uuid); - dispatch(switchUser(params.targetUser, params.targetUserToken)); - const error = validateLink(params.userToLink, params.targetUser); - if (error === LinkAccountPanelError.NONE) { - dispatch(linkAccountPanelActions.LINK_LOAD(params)); + // 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); + + 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; + 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.homeCluster, auth.remoteHosts)); + dispatch(login(auth.localCluster, homeCluster, auth.loginCluster, auth.remoteHosts)); }; export const getAccountLinkData = () => @@ -180,23 +229,28 @@ export const getAccountLinkData = () => 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 { - dispatch(progressIndicatorActions.START_WORKING(WORKBENCH_LOADING_SCREEN)); // When linking is cancelled switch to the originating user (i.e. the user saved in session data) + 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 { - services.linkAccountService.removeAccountToLink(); - dispatch(linkAccountPanelActions.LINK_INIT({targetUser:user})); - dispatch(progressIndicatorActions.STOP_WORKING(WORKBENCH_LOADING_SCREEN)); + if (reload) { + window.location.reload(); + } + else { + dispatch(progressIndicatorActions.STOP_WORKING(WORKBENCH_LOADING_SCREEN)); + } } }; @@ -221,19 +275,19 @@ 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); - location.reload(); + 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()); @@ -241,4 +295,4 @@ export const linkAccount = () => throw e; } } - }; \ No newline at end of file + };