17229: Webshell use localstorage if available
authorStephen Smith <stephen@curii.com>
Thu, 16 Sep 2021 19:45:49 +0000 (15:45 -0400)
committerStephen Smith <stephen@curii.com>
Thu, 16 Sep 2021 19:45:49 +0000 (15:45 -0400)
Arvados-DCO-1.1-Signed-off-by: Stephen Smith <stephen@curii.com>

public/webshell/index.html
src/services/auth-service/auth-service.ts
src/store/auth/auth-action.ts
src/store/auth/auth-reducer.ts
src/views/virtual-machine-panel/virtual-machine-user-panel.tsx

index 77800137517d72fe74d2a31428032719c38c3184..ce903aa08d0e9f7969691e1fa49cf9016dfe1758 100644 (file)
           var token = urlParams.get('token');
           if (token) {
             history.replaceState(null, "", `/webshell/?host=${encodeURIComponent(urlParams.get('host'))}&login=${encodeURIComponent(urlParams.get('login'))}`)
+          } else if (localStorage.getItem('apiToken')) {
+            token = localStorage.getItem('apiToken');
+          } else {
+            // No token
           }
           // change this text when PAM is reconfigured to present a
           // password prompt that we can wait for.
index 362f7b5127f48b815ce34870128403b80a4ac784..5b97596983e05e8d047212620565b135c491786e 100644 (file)
@@ -22,6 +22,8 @@ export const USER_IS_ACTIVE = 'isActive';
 export const USER_USERNAME = 'username';
 export const USER_PREFS = 'prefs';
 export const HOME_CLUSTER = 'homeCluster';
+export const LOCAL_STORAGE = 'localStorage';
+export const SESSION_STORAGE = 'sessionStorage';
 
 export interface UserDetailsResponse {
     email: string;
@@ -50,6 +52,13 @@ export class AuthService {
         return localStorage;
     }
 
+    public getStorageType() {
+        if (this.useSessionStorage) {
+            return SESSION_STORAGE;
+        }
+        return LOCAL_STORAGE;
+    }
+
     public saveApiToken(token: string) {
         this.removeApiToken();
         this.getStorage().setItem(API_TOKEN_KEY, token);
index 1bdb15d11e3fb53cf0c1d3d47d3f0cfe0860be0c..c7074704b4adf9be5abe416c54c30f71aabc4565 100644 (file)
@@ -24,7 +24,7 @@ export const authActions = unionize({
     SET_CONFIG: ofType<{ config: Config }>(),
     SET_EXTRA_TOKEN: ofType<{ extraApiToken: string, extraApiTokenExpiration?: Date }>(),
     RESET_EXTRA_TOKEN: {},
-    INIT_USER: ofType<{ user: User, token: string, tokenExpiration?: Date }>(),
+    INIT_USER: ofType<{ user: User, token: string, tokenExpiration?: Date, tokenLocation?: string }>(),
     USER_DETAILS_REQUEST: {},
     USER_DETAILS_SUCCESS: ofType<User>(),
     SET_SSH_KEYS: ofType<SshKeyResource[]>(),
@@ -97,7 +97,8 @@ export const saveApiToken = (token: string) => async (dispatch: Dispatch, getSta
         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 }));
+        const tokenLocation = await svc.authService.getStorageType();
+        dispatch(authActions.INIT_USER({ user, token, tokenExpiration, tokenLocation }));
     } catch (e) {
         dispatch(authActions.LOGOUT({ deleteLinkData: false }));
     }
index b8746ca59e0fdfb7f326a6d03aaa1bc5237edb9c..ce836a55600db742de78bd49a59108b5a957c4c0 100644 (file)
@@ -13,6 +13,7 @@ export interface AuthState {
     user?: User;
     apiToken?: string;
     apiTokenExpiration?: Date;
+    apiTokenLocation?: string;
     extraApiToken?: string;
     extraApiTokenExpiration?: Date;
     sshKeys: SshKeyResource[];
@@ -29,6 +30,7 @@ const initialState: AuthState = {
     user: undefined,
     apiToken: undefined,
     apiTokenExpiration: undefined,
+    apiTokenLocation: undefined,
     extraApiToken: undefined,
     extraApiTokenExpiration: undefined,
     sshKeys: [],
@@ -71,11 +73,12 @@ export const authReducer = (services: ServiceRepository) => (state = initialStat
             ({ ...state, extraApiToken, extraApiTokenExpiration }),
         RESET_EXTRA_TOKEN: () =>
             ({ ...state, extraApiToken: undefined, extraApiTokenExpiration: undefined }),
-        INIT_USER: ({ user, token, tokenExpiration }) =>
+        INIT_USER: ({ user, token, tokenExpiration, tokenLocation = state.apiTokenLocation }) =>
             ({ ...state,
                 user,
                 apiToken: token,
                 apiTokenExpiration: tokenExpiration,
+                apiTokenLocation: tokenLocation,
                 homeCluster: user.uuid.substr(0, 5)
             }),
         LOGIN: () => state,
index 44631ce32bee12ebe3ac50db534ce77be802994f..196cba1643606c86a2e3953a6ca154664d543847 100644 (file)
@@ -12,10 +12,13 @@ import { saveRequestedDate, loadVirtualMachinesUserData } from 'store/virtual-ma
 import { RootState } from 'store/store';
 import { ListResults } from 'services/common-service/common-service';
 import { HelpIcon } from 'components/icon/icon';
+import { SESSION_STORAGE } from "services/auth-service/auth-service";
 // import * as CopyToClipboard from 'react-copy-to-clipboard';
 
 type CssRules = 'button' | 'codeSnippet' | 'link' | 'linkIcon' | 'rightAlign' | 'cardWithoutMachines' | 'icon';
 
+const EXTRA_TOKEN = "exra";
+
 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     button: {
         marginTop: theme.spacing.unit,
@@ -62,6 +65,7 @@ const mapStateToProps = (state: RootState) => {
         helpText: state.auth.config.clusterConfig.Workbench.SSHHelpPageHTML,
         hostSuffix: state.auth.config.clusterConfig.Workbench.SSHHelpHostSuffix || "",
         token: state.auth.extraApiToken || state.auth.apiToken || '',
+        tokenLocation: state.auth.extraApiToken ? EXTRA_TOKEN : (state.auth.apiTokenLocation || ''),
         webshellUrl: state.auth.config.clusterConfig.Services.WebShell.ExternalURL,
         ...state.virtualMachines
     };
@@ -80,6 +84,7 @@ interface VirtualMachinesPanelDataProps {
     helpText: string;
     hostSuffix: string;
     token: string;
+    tokenLocation: string,
     webshellUrl: string;
 }
 
@@ -176,6 +181,10 @@ const virtualMachinesTable = (props: VirtualMachineProps) =>
                     if (lk.tailUuid === props.userUuid) {
                         const username = lk.properties.username;
                         const command = `ssh ${username}@${it.hostname}${props.hostSuffix}`;
+                        let tokenParam = "";
+                        if (props.tokenLocation === SESSION_STORAGE || props.tokenLocation === EXTRA_TOKEN) {
+                          tokenParam = `&token=${encodeURIComponent(props.token)}`;
+                        }
                         return <TableRow key={lk.uuid}>
                             <TableCell>{it.hostname}</TableCell>
                             <TableCell>{username}</TableCell>
@@ -183,7 +192,7 @@ const virtualMachinesTable = (props: VirtualMachineProps) =>
                                 {command}
                             </TableCell>
                             <TableCell>
-                                <a href={`/webshell/?host=${encodeURIComponent(props.webshellUrl + '/' + it.hostname)}&login=${username}&token=${encodeURIComponent(props.token)}`} target="_blank" rel="noopener noreferrer" className={props.classes.link}>
+                                <a href={`/webshell/?host=${encodeURIComponent(props.webshellUrl + '/' + it.hostname)}&login=${encodeURIComponent(username)}${tokenParam}`} target="_blank" rel="noopener noreferrer" className={props.classes.link}>
                                     Log in as {username}
                                 </a>
                             </TableCell>