16848: Adds expiration date to the "Get API Token" dialog.
authorLucas Di Pentima <lucas@di-pentima.com.ar>
Tue, 23 Feb 2021 19:30:26 +0000 (16:30 -0300)
committerLucas Di Pentima <lucas@di-pentima.com.ar>
Tue, 23 Feb 2021 19:30:26 +0000 (16:30 -0300)
Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas@di-pentima.com.ar>

src/store/auth/auth-action.test.ts
src/store/auth/auth-action.ts
src/store/auth/auth-reducer.ts
src/store/token-dialog/token-dialog-actions.tsx
src/views-components/token-dialog/token-dialog.tsx

index 79f93daa66d34d4d5b1695f2cc725205790444b6..f1a534c64818cdae5b8b866dbd8fcf7347bb6628 100644 (file)
@@ -58,6 +58,12 @@ describe('auth-actions', () => {
                 prefs: {}
             });
 
+        axiosMock
+            .onGet("/api_client_authorizations/current")
+            .reply(200, {
+                expires_at: "2140-01-01T00:00:00.000Z"
+            });
+
         axiosMock
             .onGet("https://xc59z.arvadosapi.com/discovery/v1/apis/arvados/v1/rest")
             .reply(200, {
@@ -101,6 +107,7 @@ describe('auth-actions', () => {
                 try {
                     expect(auth).toEqual({
                         apiToken: "token",
+                        apiTokenExpiration: new Date("2140-01-01T00:00:00.000Z"),
                         config: {
                             apiRevision: 12345678,
                             clusterConfig: {
@@ -116,6 +123,7 @@ describe('auth-actions', () => {
                         },
                         sshKeys: [],
                         extraApiToken: undefined,
+                        extraApiTokenExpiration: undefined,
                         homeCluster: "zzzzz",
                         localCluster: "zzzzz",
                         loginCluster: undefined,
index faf098f76bf2b3c75f15857165fde2eacc48d13c..2819364fb1958925c88335a153a9d8238444ff64 100644 (file)
@@ -22,8 +22,8 @@ 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 }>(),
+    SET_EXTRA_TOKEN: ofType<{ extraApiToken: string, extraApiTokenExpiration?: Date }>(),
+    INIT_USER: ofType<{ user: User, token: string, tokenExpiration?: Date }>(),
     USER_DETAILS_REQUEST: {},
     USER_DETAILS_SUCCESS: ofType<User>(),
     SET_SSH_KEYS: ofType<SshKeyResource[]>(),
@@ -88,7 +88,9 @@ export const saveApiToken = (token: string) => async (dispatch: Dispatch, getSta
     setAuthorizationHeader(svc, token);
     try {
         const user = await svc.authService.getUserDetails();
-        dispatch(authActions.INIT_USER({ user, token }));
+        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 }));
     }
@@ -108,7 +110,10 @@ export const getNewExtraToken = (reuseStored: boolean = false) =>
             // 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({ extraToken: newExtraToken }));
+            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.");
index d55e8301df50713625a1cf13861018779661b0db..7459b7ac0c3a651e239a9b560baebd795d1884e0 100644 (file)
@@ -12,7 +12,9 @@ import { Config, mockConfig } from '~/common/config';
 export interface AuthState {
     user?: User;
     apiToken?: string;
+    apiTokenExpiration?: Date;
     extraApiToken?: string;
+    extraApiTokenExpiration?: Date;
     sshKeys: SshKeyResource[];
     sessions: Session[];
     localCluster: string;
@@ -26,7 +28,9 @@ export interface AuthState {
 const initialState: AuthState = {
     user: undefined,
     apiToken: undefined,
+    apiTokenExpiration: undefined,
     extraApiToken: undefined,
+    extraApiTokenExpiration: undefined,
     sshKeys: [],
     sessions: [],
     localCluster: "",
@@ -39,70 +43,66 @@ const initialState: AuthState = {
 
 export const authReducer = (services: ServiceRepository) => (state = initialState, action: AuthAction) => {
     return authActions.match(action, {
-        SET_CONFIG: ({ config }) => {
-            return {
+        SET_CONFIG: ({ config }) =>
+            ({
                 ...state,
                 config,
                 localCluster: config.uuidPrefix,
-                remoteHosts: { ...config.remoteHosts, [config.uuidPrefix]: new URL(config.rootUrl).host },
+                remoteHosts: {
+                    ...config.remoteHosts,
+                    [config.uuidPrefix]: new URL(config.rootUrl).host
+                },
                 homeCluster: config.loginCluster || config.uuidPrefix,
                 loginCluster: config.loginCluster,
-                remoteHostsConfig: { ...state.remoteHostsConfig, [config.uuidPrefix]: config }
-            };
-        },
-        REMOTE_CLUSTER_CONFIG: ({ config }) => {
-            return {
+                remoteHostsConfig: {
+                    ...state.remoteHostsConfig,
+                    [config.uuidPrefix]: config
+                }
+            }),
+        REMOTE_CLUSTER_CONFIG: ({ config }) =>
+            ({
                 ...state,
-                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) };
-        },
-        LOGIN: () => {
-            return state;
-        },
-        LOGOUT: () => {
-            return { ...state, apiToken: undefined };
-        },
-        USER_DETAILS_SUCCESS: (user: User) => {
-            return { ...state, user, homeCluster: user.uuid.substr(0, 5) };
-        },
-        SET_SSH_KEYS: (sshKeys: SshKeyResource[]) => {
-            return { ...state, sshKeys };
-        },
-        ADD_SSH_KEY: (sshKey: SshKeyResource) => {
-            return { ...state, sshKeys: state.sshKeys.concat(sshKey) };
-        },
-        REMOVE_SSH_KEY: (uuid: string) => {
-            return { ...state, sshKeys: state.sshKeys.filter((sshKey) => sshKey.uuid !== uuid) };
-        },
-        SET_HOME_CLUSTER: (homeCluster: string) => {
-            return { ...state, homeCluster };
-        },
-        SET_SESSIONS: (sessions: Session[]) => {
-            return { ...state, sessions };
-        },
-        ADD_SESSION: (session: Session) => {
-            return { ...state, sessions: state.sessions.concat(session) };
-        },
-        REMOVE_SESSION: (clusterId: string) => {
-            return {
+                remoteHostsConfig: {
+                    ...state.remoteHostsConfig,
+                    [config.uuidPrefix]: config
+                },
+            }),
+        SET_EXTRA_TOKEN: ({ extraApiToken, extraApiTokenExpiration }) =>
+            ({ ...state, extraApiToken, extraApiTokenExpiration }),
+        INIT_USER: ({ user, token, tokenExpiration }) =>
+            ({ ...state,
+                user,
+                apiToken: token,
+                apiTokenExpiration: tokenExpiration,
+                homeCluster: user.uuid.substr(0, 5)
+            }),
+        LOGIN: () => state,
+        LOGOUT: () => ({ ...state, apiToken: undefined }),
+        USER_DETAILS_SUCCESS: (user: User) =>
+            ({ ...state, user, homeCluster: user.uuid.substr(0, 5) }),
+        SET_SSH_KEYS: (sshKeys: SshKeyResource[]) => ({ ...state, sshKeys }),
+        ADD_SSH_KEY: (sshKey: SshKeyResource) =>
+            ({ ...state, sshKeys: state.sshKeys.concat(sshKey) }),
+        REMOVE_SSH_KEY: (uuid: string) =>
+            ({ ...state, sshKeys: state.sshKeys.filter((sshKey) => sshKey.uuid !== uuid) }),
+        SET_HOME_CLUSTER: (homeCluster: string) => ({ ...state, homeCluster }),
+        SET_SESSIONS: (sessions: Session[]) => ({ ...state, sessions }),
+        ADD_SESSION: (session: Session) =>
+            ({ ...state, sessions: state.sessions.concat(session) }),
+        REMOVE_SESSION: (clusterId: string) =>
+            ({
                 ...state,
                 sessions: state.sessions.filter(
                     session => session.clusterId !== clusterId
                 )
-            };
-        },
-        UPDATE_SESSION: (session: Session) => {
-            return {
+            }),
+        UPDATE_SESSION: (session: Session) =>
+            ({
                 ...state,
                 sessions: state.sessions.map(
                     s => s.clusterId === session.clusterId ? session : s
                 )
-            };
-        },
+            }),
         default: () => state
     });
 };
index 2cf573bcae8a004607fb667d16e44a212dafeaef..c6eb014570b726c71633125e46c38c68d0c47f1d 100644 (file)
@@ -12,6 +12,7 @@ const API_HOST_PROPERTY_NAME = 'apiHost';
 
 export interface TokenDialogData {
     token: string;
+    tokenExpiration?: Date;
     apiHost: string;
     canCreateNewTokens: boolean;
 }
@@ -26,6 +27,9 @@ export const getTokenDialogData = (state: RootState): TokenDialogData => {
     return {
         apiHost: getProperty<string>(API_HOST_PROPERTY_NAME)(state.properties) || '',
         token: state.auth.extraApiToken || state.auth.apiToken || '',
+        tokenExpiration: state.auth.extraApiToken
+            ? state.auth.extraApiTokenExpiration
+            : state.auth.apiTokenExpiration,
         canCreateNewTokens,
     };
 };
index 5bbcaf57db5f6aeb3517c6a6ae95c5d6f58d3188..60ef360c72e0db5f4cfbda52b120344e3a4f1181 100644 (file)
@@ -111,6 +111,10 @@ unset ARVADOS_API_HOST_INSECURE`
                     Paste the following lines at a shell prompt to set up the necessary environment for Arvados SDKs to authenticate to your account.
                 </Typography>
                 <DefaultCodeSnippet lines={[this.getSnippet(data)]} />
+                { data.tokenExpiration
+                    ? <Typography component='span'>Expires at: {data.tokenExpiration.toLocaleString()}</Typography>
+                    : null
+                }
                 <CopyToClipboard text={this.getSnippet(data)} onCopy={() => this.onCopy('Token copied to clipboard')}>
                     <Button
                         color="primary"