[ARVADOS-WORKBENCH2] updated: 2.1.0-202-g5620b9c4

Git user git at public.arvados.org
Tue Feb 16 23:12:11 UTC 2021


Summary of changes:
 src/store/auth/auth-action.test.ts                 |  2 +-
 src/store/auth/auth-action.ts                      | 18 +++++++++
 src/store/auth/auth-reducer.ts                     |  3 ++
 src/store/collections/collection-info-actions.ts   |  2 +-
 src/store/token-dialog/token-dialog-actions.tsx    |  2 +-
 .../auto-logout/auto-logout.test.tsx               | 22 ++++++++++-
 src/views-components/auto-logout/auto-logout.tsx   | 38 +++++++++++-------
 src/views-components/token-dialog/token-dialog.tsx | 46 +++++++++++++++++++---
 8 files changed, 109 insertions(+), 24 deletions(-)

       via  5620b9c4236bab9e9c98b6a4e955498823de4136 (commit)
       via  f326404ea8ff55f3b177877eeef1136af34d18ba (commit)
       via  b7eb7d60846333d93e91f98ec3e6fcdd94fca570 (commit)
       via  ba4e011fc9132aa17cc6c4e8e74a6310a308291e (commit)
      from  5f272aec410b2b1dce368c36570c6c786aefbd71 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.


commit 5620b9c4236bab9e9c98b6a4e955498823de4136
Author: Lucas Di Pentima <lucas at di-pentima.com.ar>
Date:   Tue Feb 16 20:11:07 2021 -0300

    16848: Makes 'Open as S3 bucket' dialog use the extra api token if available.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas at di-pentima.com.ar>

diff --git a/src/store/collections/collection-info-actions.ts b/src/store/collections/collection-info-actions.ts
index 49fe54f6..b904da70 100644
--- a/src/store/collections/collection-info-actions.ts
+++ b/src/store/collections/collection-info-actions.ts
@@ -26,7 +26,7 @@ export const openWebDavS3InfoDialog = (uuid: string, activeTab?: number) =>
             id: COLLECTION_WEBDAV_S3_DIALOG_NAME,
             data: {
                 title: 'Access Collection using WebDAV or S3',
-                token: getState().auth.apiToken,
+                token: getState().auth.extraApiToken || getState().auth.apiToken,
                 downloadUrl: getState().auth.config.keepWebServiceUrl,
                 collectionsUrl: getState().auth.config.keepWebInlineServiceUrl,
                 localCluster: getState().auth.localCluster,

commit f326404ea8ff55f3b177877eeef1136af34d18ba
Author: Lucas Di Pentima <lucas at di-pentima.com.ar>
Date:   Tue Feb 16 20:00:57 2021 -0300

    16848: Adds an extra token to be displayed on the 'get token' dialog.
    
    Also, adds a "Get new token" button to the dialog to allow the user to
    request new tokens.
    These extra tokens won't be expired on logout, so they're suitable for use
    on S3 URLs and other tasks the user may need.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas at di-pentima.com.ar>

diff --git a/src/store/auth/auth-action.ts b/src/store/auth/auth-action.ts
index 15fe3d4d..04d1287a 100644
--- a/src/store/auth/auth-action.ts
+++ b/src/store/auth/auth-action.ts
@@ -21,6 +21,7 @@ export const authActions = unionize({
     LOGIN: {},
     LOGOUT: ofType<{ deleteLinkData: boolean }>(),
     SET_CONFIG: ofType<{ config: Config }>(),
+    SET_EXTRA_TOKEN: ofType<{ extraToken: string }>(),
     INIT_USER: ofType<{ user: User, token: string }>(),
     USER_DETAILS_REQUEST: {},
     USER_DETAILS_SUCCESS: ofType<User>(),
@@ -86,11 +87,28 @@ export const saveApiToken = (token: string) => (dispatch: Dispatch, getState: ()
     setAuthorizationHeader(svc, token);
     return svc.authService.getUserDetails().then((user: User) => {
         dispatch(authActions.INIT_USER({ user, token }));
+        // Upon user init, request an extra token that won't be expired on logout
+        // for other uses like the "get token" dialog, or S3 URL building.
+        dispatch<any>(getNewExtraToken());
     }).catch(() => {
         dispatch(authActions.LOGOUT({ deleteLinkData: false }));
     });
 };
 
+export const getNewExtraToken = () =>
+    async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+        const user = getState().auth.user;
+        if (user === undefined) { return; }
+        try {
+            const aca = await services.apiClientAuthorizationService.create();
+            const newExtraToken = `v2/${aca.uuid}/${aca.apiToken}`;
+            dispatch(authActions.SET_EXTRA_TOKEN({ extraToken: newExtraToken }));
+            return newExtraToken;
+        } catch {
+            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);
diff --git a/src/store/auth/auth-reducer.ts b/src/store/auth/auth-reducer.ts
index 946407fe..d55e8301 100644
--- a/src/store/auth/auth-reducer.ts
+++ b/src/store/auth/auth-reducer.ts
@@ -12,6 +12,7 @@ import { Config, mockConfig } from '~/common/config';
 export interface AuthState {
     user?: User;
     apiToken?: string;
+    extraApiToken?: string;
     sshKeys: SshKeyResource[];
     sessions: Session[];
     localCluster: string;
@@ -25,6 +26,7 @@ export interface AuthState {
 const initialState: AuthState = {
     user: undefined,
     apiToken: undefined,
+    extraApiToken: undefined,
     sshKeys: [],
     sessions: [],
     localCluster: "",
@@ -54,6 +56,7 @@ export const authReducer = (services: ServiceRepository) => (state = initialStat
                 remoteHostsConfig: { ...state.remoteHostsConfig, [config.uuidPrefix]: config },
             };
         },
+        SET_EXTRA_TOKEN: ({ extraToken }) => ({ ...state, extraApiToken: extraToken }),
         INIT_USER: ({ user, token }) => {
             return { ...state, user, apiToken: token, homeCluster: user.uuid.substr(0, 5) };
         },
diff --git a/src/store/token-dialog/token-dialog-actions.tsx b/src/store/token-dialog/token-dialog-actions.tsx
index 08a45992..656f532b 100644
--- a/src/store/token-dialog/token-dialog-actions.tsx
+++ b/src/store/token-dialog/token-dialog-actions.tsx
@@ -20,7 +20,7 @@ export const setTokenDialogApiHost = (apiHost: string) =>
 
 export const getTokenDialogData = (state: RootState): TokenDialogData => ({
     apiHost: getProperty<string>(API_HOST_PROPERTY_NAME)(state.properties) || '',
-    token: state.auth.apiToken || '',
+    token: state.auth.extraApiToken || state.auth.apiToken || '',
 });
 
 export const openTokenDialog = dialogActions.OPEN_DIALOG({ id: TOKEN_DIALOG_NAME, data: {} });
diff --git a/src/views-components/token-dialog/token-dialog.tsx b/src/views-components/token-dialog/token-dialog.tsx
index b0d5c67e..ed155541 100644
--- a/src/views-components/token-dialog/token-dialog.tsx
+++ b/src/views-components/token-dialog/token-dialog.tsx
@@ -3,17 +3,32 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import * as React from 'react';
-import { Dialog, DialogActions, DialogTitle, DialogContent, WithStyles, withStyles, StyleRulesCallback, Button, Typography } from '@material-ui/core';
+import {
+    Dialog,
+    DialogActions,
+    DialogTitle,
+    DialogContent,
+    WithStyles,
+    withStyles,
+    StyleRulesCallback,
+    Button,
+    Typography
+} from '@material-ui/core';
 import * as CopyToClipboard from 'react-copy-to-clipboard';
 import { ArvadosTheme } from '~/common/custom-theme';
 import { withDialog } from '~/store/dialog/with-dialog';
 import { WithDialogProps } from '~/store/dialog/with-dialog';
 import { connect, DispatchProp } from 'react-redux';
-import { TokenDialogData, getTokenDialogData, TOKEN_DIALOG_NAME } from '~/store/token-dialog/token-dialog-actions';
+import {
+    TokenDialogData,
+    getTokenDialogData,
+    TOKEN_DIALOG_NAME,
+} from '~/store/token-dialog/token-dialog-actions';
 import { DefaultCodeSnippet } from '~/components/default-code-snippet/default-code-snippet';
 import { snackbarActions, SnackbarKind } from '~/store/snackbar/snackbar-actions';
+import { getNewExtraToken } from '~/store/auth/auth-action';
 
-type CssRules = 'link' | 'paper' | 'button' | 'copyButton';
+type CssRules = 'link' | 'paper' | 'button' | 'actionButton';
 
 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     link: {
@@ -31,10 +46,11 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
         fontSize: '0.8125rem',
         fontWeight: 600
     },
-    copyButton: {
+    actionButton: {
         boxShadow: 'none',
         marginTop: theme.spacing.unit * 2,
         marginBottom: theme.spacing.unit * 2,
+        marginRight: theme.spacing.unit * 2,
     }
 });
 
@@ -49,6 +65,17 @@ export class TokenDialogComponent extends React.Component<TokenDialogProps> {
         }));
     }
 
+    onGetNewToken = async () => {
+        const newToken = await this.props.dispatch<any>(getNewExtraToken());
+        if (newToken) {
+            this.props.dispatch(snackbarActions.OPEN_SNACKBAR({
+                message: 'New token retrieved',
+                hideDuration: 2000,
+                kind: SnackbarKind.SUCCESS
+            }));
+        }
+    }
+
     getSnippet = ({ apiHost, token }: TokenDialogData) =>
         `HISTIGNORE=$HISTIGNORE:'export ARVADOS_API_TOKEN=*'
 export ARVADOS_API_TOKEN=${token}
@@ -83,11 +110,20 @@ unset ARVADOS_API_HOST_INSECURE`
                         color="primary"
                         size="small"
                         variant="contained"
-                        className={classes.copyButton}
+                        className={classes.actionButton}
                     >
                         COPY TO CLIPBOARD
                     </Button>
                 </CopyToClipboard>
+                <Button
+                    onClick={() => this.onGetNewToken()}
+                    color="primary"
+                    size="small"
+                    variant="contained"
+                    className={classes.actionButton}
+                >
+                    GET NEW TOKEN
+                </Button>
                 <Typography >
                     Arvados
                             <a href='http://doc.arvados.org/user/reference/api-tokens.html' target='blank' className={classes.link}>virtual machines</a>

commit b7eb7d60846333d93e91f98ec3e6fcdd94fca570
Author: Lucas Di Pentima <lucas at di-pentima.com.ar>
Date:   Mon Feb 15 18:03:30 2021 -0300

    16848: Adds test for idle timer reset via localStorage event.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas at di-pentima.com.ar>

diff --git a/src/views-components/auto-logout/auto-logout.test.tsx b/src/views-components/auto-logout/auto-logout.test.tsx
index f8daa764..49496724 100644
--- a/src/views-components/auto-logout/auto-logout.test.tsx
+++ b/src/views-components/auto-logout/auto-logout.test.tsx
@@ -5,7 +5,7 @@
 import * as React from 'react';
 import { configure, mount } from "enzyme";
 import * as Adapter from 'enzyme-adapter-react-16';
-import { AutoLogoutComponent, AutoLogoutProps } from './auto-logout';
+import { AutoLogoutComponent, AutoLogoutProps, LAST_ACTIVE_TIMESTAMP } from './auto-logout';
 
 configure({ adapter: new Adapter() });
 
@@ -13,8 +13,15 @@ describe('<AutoLogoutComponent />', () => {
     let props: AutoLogoutProps;
     const sessionIdleTimeout = 300;
     const lastWarningDuration = 60;
+    const eventListeners = {};
     jest.useFakeTimers();
 
+    beforeAll(() => {
+        window.addEventListener = jest.fn((event, cb) => {
+            eventListeners[event] = cb;
+        });
+    });
+
     beforeEach(() => {
         props = {
             sessionIdleTimeout: sessionIdleTimeout,
@@ -39,4 +46,17 @@ describe('<AutoLogoutComponent />', () => {
         jest.runTimersToTime(1*1000);
         expect(props.doWarn).toBeCalled();
     });
+
+    it('should reset the idle timer when activity event is received', () => {
+        jest.runTimersToTime((sessionIdleTimeout-lastWarningDuration-1)*1000);
+        expect(props.doWarn).not.toBeCalled();
+        // Simulate activity from other window/tab
+        eventListeners.storage({
+            key: LAST_ACTIVE_TIMESTAMP,
+            newValue: '42' // value currently doesn't matter
+        })
+        jest.runTimersToTime(1*1000);
+        // Warning should not appear because idle timer was reset
+        expect(props.doWarn).not.toBeCalled();
+    });
 });
\ No newline at end of file
diff --git a/src/views-components/auto-logout/auto-logout.tsx b/src/views-components/auto-logout/auto-logout.tsx
index 54411926..f7e6f4b8 100644
--- a/src/views-components/auto-logout/auto-logout.tsx
+++ b/src/views-components/auto-logout/auto-logout.tsx
@@ -50,7 +50,7 @@ const debounce = (delay: number | undefined, fn: Function) => {
     };
 };
 
-const LAST_ACTIVE_TIMESTAMP = 'lastActiveTimestamp';
+export const LAST_ACTIVE_TIMESTAMP = 'lastActiveTimestamp';
 
 export const AutoLogoutComponent = (props: AutoLogoutProps) => {
     let logoutTimer: NodeJS.Timer;

commit ba4e011fc9132aa17cc6c4e8e74a6310a308291e
Author: Lucas Di Pentima <lucas at di-pentima.com.ar>
Date:   Mon Feb 15 17:44:13 2021 -0300

    16848: Improved event handler installation/removal.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas at di-pentima.com.ar>

diff --git a/src/store/auth/auth-action.test.ts b/src/store/auth/auth-action.test.ts
index 13575d44..616f2d2c 100644
--- a/src/store/auth/auth-action.test.ts
+++ b/src/store/auth/auth-action.test.ts
@@ -9,7 +9,7 @@ import 'jest-localstorage-mock';
 import { ServiceRepository, createServices } from "~/services/services";
 import { configureStore, RootStore } from "../store";
 import { createBrowserHistory } from "history";
-import { mockConfig, DISCOVERY_DOC_PATH, } from '~/common/config';
+import { mockConfig } from '~/common/config';
 import { ApiActions } from "~/services/api/api-actions";
 import { ACCOUNT_LINK_STATUS_KEY } from '~/services/link-account-service/link-account-service';
 import Axios from "axios";
diff --git a/src/views-components/auto-logout/auto-logout.tsx b/src/views-components/auto-logout/auto-logout.tsx
index 9ccf79a5..54411926 100644
--- a/src/views-components/auto-logout/auto-logout.tsx
+++ b/src/views-components/auto-logout/auto-logout.tsx
@@ -24,12 +24,10 @@ interface AutoLogoutActionProps {
     doCloseWarn: () => void;
 }
 
-const mapStateToProps = (state: RootState, ownProps: any): AutoLogoutDataProps => {
-    return {
-        sessionIdleTimeout: parse(state.auth.config.clusterConfig.Workbench.IdleTimeout, 's') || 0,
-        lastWarningDuration: ownProps.lastWarningDuration || 60,
-    };
-};
+const mapStateToProps = (state: RootState, ownProps: any): AutoLogoutDataProps => ({
+    sessionIdleTimeout: parse(state.auth.config.clusterConfig.Workbench.IdleTimeout, 's') || 0,
+    lastWarningDuration: ownProps.lastWarningDuration || 60,
+});
 
 const mapDispatchToProps = (dispatch: Dispatch): AutoLogoutActionProps => ({
     doLogout: () => dispatch<any>(logout(true)),
@@ -57,15 +55,25 @@ const LAST_ACTIVE_TIMESTAMP = 'lastActiveTimestamp';
 export const AutoLogoutComponent = (props: AutoLogoutProps) => {
     let logoutTimer: NodeJS.Timer;
     const lastWarningDuration = min([props.lastWarningDuration, props.sessionIdleTimeout])! * 1000;
-    const handleOtherTabActivity = debounce(500, () => {
-        handleOnActive();
-        reset();
-    });
 
-    window.addEventListener('storage', (e: StorageEvent) => {
-        // Other tab activity detected by a localStorage change event.
-        if (e.key === LAST_ACTIVE_TIMESTAMP) { handleOtherTabActivity(); }
-    });
+    // Runs once after render
+    React.useEffect(() => {
+        window.addEventListener('storage', handleStorageEvents);
+        // Component cleanup
+        return () => {
+            window.removeEventListener('storage', handleStorageEvents);
+        };
+    }, []);
+
+    const handleStorageEvents = (e: StorageEvent) => {
+        if (e.key === LAST_ACTIVE_TIMESTAMP) {
+            // Other tab activity detected by a localStorage change event.
+            debounce(500, () => {
+                handleOnActive();
+                reset();
+            })();
+        }
+    };
 
     const handleOnIdle = () => {
         logoutTimer = setTimeout(

-----------------------------------------------------------------------


hooks/post-receive
-- 




More information about the arvados-commits mailing list