X-Git-Url: https://git.arvados.org/arvados-workbench2.git/blobdiff_plain/27f584f8f0d3270f3e737c401a794a37aa1b45c5..eb633efdb3fa8ecb0c2c6a5e35916585282436ea:/src/store/auth/auth-action.ts?ds=sidebyside diff --git a/src/store/auth/auth-action.ts b/src/store/auth/auth-action.ts index 923d3c9e..4f576fe4 100644 --- a/src/store/auth/auth-action.ts +++ b/src/store/auth/auth-action.ts @@ -15,12 +15,16 @@ import { createServices, setAuthorizationHeader } from "~/services/services"; import { cancelLinking } from '~/store/link-account-panel/link-account-panel-actions'; import { progressIndicatorActions } from "~/store/progress-indicator/progress-indicator-actions"; import { WORKBENCH_LOADING_SCREEN } from '~/store/workbench/workbench-actions'; +import { addRemoteConfig } from './auth-action-session'; +import { getTokenV2 } from '~/models/api-client-authorization'; export const authActions = unionize({ LOGIN: {}, LOGOUT: ofType<{ deleteLinkData: boolean }>(), SET_CONFIG: ofType<{ config: Config }>(), - INIT_USER: ofType<{ user: User, token: string }>(), + SET_EXTRA_TOKEN: ofType<{ extraApiToken: string, extraApiTokenExpiration?: Date }>(), + RESET_EXTRA_TOKEN: {}, + INIT_USER: ofType<{ user: User, token: string, tokenExpiration?: Date }>(), USER_DETAILS_REQUEST: {}, USER_DETAILS_SUCCESS: ofType(), SET_SSH_KEYS: ofType(), @@ -38,23 +42,30 @@ export const initAuth = (config: Config) => (dispatch: Dispatch, getState: () => // Cancel any link account ops in progress unless the user has // just logged in or there has been a successful link operation const data = services.linkAccountService.getLinkOpStatus(); - if (!matchTokenRoute(location.pathname) && (!matchFedTokenRoute(location.pathname)) && data === undefined) { + if (!matchTokenRoute(location.pathname) && + (!matchFedTokenRoute(location.pathname)) && data === undefined) { dispatch(cancelLinking()).then(() => { dispatch(init(config)); }); - } - else { + } else { dispatch(init(config)); } }; const init = (config: Config) => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { + const remoteHosts = () => getState().auth.remoteHosts; const token = services.authService.getApiToken(); let homeCluster = services.authService.getHomeCluster(); if (homeCluster && !config.remoteHosts[homeCluster]) { homeCluster = undefined; } dispatch(authActions.SET_CONFIG({ config })); + Object.keys(remoteHosts()).forEach((remoteUuid: string) => { + const remoteHost = remoteHosts()[remoteUuid]; + if (remoteUuid !== config.uuidPrefix) { + dispatch(addRemoteConfig(remoteHost)); + } + }); dispatch(authActions.SET_HOME_CLUSTER(config.loginCluster || homeCluster || config.uuidPrefix)); if (token && token !== "undefined") { @@ -72,25 +83,68 @@ export const getConfig = (dispatch: Dispatch, getState: () => RootState, service return state.remoteHostsConfig[state.localCluster]; }; -export const saveApiToken = (token: string) => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository): Promise => { +export const saveApiToken = (token: string) => async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository): Promise => { const config = dispatch(getConfig); const svc = createServices(config, { progressFn: () => { }, errorFn: () => { } }); setAuthorizationHeader(svc, token); - return svc.authService.getUserDetails().then((user: User) => { - dispatch(authActions.INIT_USER({ user, token })); - }).catch(() => { + try { + const user = await svc.authService.getUserDetails(); + const client = await svc.apiClientAuthorizationService.get('current'); + const tokenExpiration = client.expiresAt ? new Date(client.expiresAt) : undefined; + dispatch(authActions.INIT_USER({ user, token, tokenExpiration })); + } catch (e) { dispatch(authActions.LOGOUT({ deleteLinkData: false })); - }); + } }; +export const getNewExtraToken = (reuseStored: boolean = false) => + async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { + const extraToken = getState().auth.extraApiToken; + if (reuseStored && extraToken !== undefined) { + const config = dispatch(getConfig); + const svc = createServices(config, { progressFn: () => { }, errorFn: () => { } }); + setAuthorizationHeader(svc, extraToken); + try { + // Check the extra token's validity before using it. Refresh its + // expiration date just in case it changed. + const client = await svc.apiClientAuthorizationService.get('current'); + dispatch(authActions.SET_EXTRA_TOKEN({ + extraApiToken: extraToken, + extraApiTokenExpiration: client.expiresAt ? new Date(client.expiresAt): undefined, + })); + return extraToken; + } catch (e) { + dispatch(authActions.RESET_EXTRA_TOKEN()); + } + } + const user = getState().auth.user; + const loginCluster = getState().auth.config.clusterConfig.Login.LoginCluster; + if (user === undefined) { return; } + if (loginCluster !== "" && getState().auth.homeCluster !== loginCluster) { return; } + try { + // Do not show errors on the create call, cluster security configuration may not + // allow token creation and there's no way to know that from workbench2 side in advance. + const client = await services.apiClientAuthorizationService.create(undefined, false); + const newExtraToken = getTokenV2(client); + dispatch(authActions.SET_EXTRA_TOKEN({ + extraApiToken: newExtraToken, + extraApiTokenExpiration: client.expiresAt ? new Date(client.expiresAt): undefined, + })); + return newExtraToken; + } catch { + console.warn("Cannot create new tokens with the current token, probably because of cluster's security settings."); + return; + } + }; + export const login = (uuidPrefix: string, homeCluster: string, loginCluster: string, remoteHosts: { [key: string]: string }) => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { services.authService.login(uuidPrefix, homeCluster, loginCluster, remoteHosts); dispatch(authActions.LOGIN()); }; -export const logout = (deleteLinkData: boolean = false) => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { - dispatch(authActions.LOGOUT({ deleteLinkData })); -}; +export const logout = (deleteLinkData: boolean = false) => + (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => + dispatch(authActions.LOGOUT({ deleteLinkData })); export type AuthAction = UnionOf;