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>(),
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);
export interface AuthState {
user?: User;
apiToken?: string;
+ extraApiToken?: string;
sshKeys: SshKeyResource[];
sessions: Session[];
localCluster: string;
const initialState: AuthState = {
user: undefined,
apiToken: undefined,
+ extraApiToken: undefined,
sshKeys: [],
sessions: [],
localCluster: "",
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) };
},
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: {} });
// 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: {
fontSize: '0.8125rem',
fontWeight: 600
},
- copyButton: {
+ actionButton: {
boxShadow: 'none',
marginTop: theme.spacing.unit * 2,
marginBottom: theme.spacing.unit * 2,
+ marginRight: theme.spacing.unit * 2,
}
});
}));
}
+ 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}
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>