From b45aedec4717ecd28badfa2953eee65262a914c1 Mon Sep 17 00:00:00 2001 From: Patrick Ear Date: Tue, 7 Dec 2021 16:06:25 +0100 Subject: [PATCH] ZKUI-29 - Fix reauth modal popup behavior The issue: When the user switch tab or leave the laptop for while, the reauth modal will popup but the behavior is not clear and it confuse the user. We identify 2 issues: 1. A race condition When the user go back to the tab, the UI will trigger both reauth (because the token might be expired) and refresh the instance (See the `useEffect` in `Routes.jsx`). This will result a 401 and display the popup even though the token is refresh right after. 2. A token expired for too long which require a relogin Keycloak. In this case if the user try to click on `Reload` is will try to reauth but, it will fail and the user will be stuck until the page is refreshed. To address both issues, we choose to : - check the expired time in the polling. This prevent to display a 401 right after the user come back from a new tab. - add `ADD_OIDC_USER` and `LOAD_CLIENTS_SUCCESS` to remove modal - only log out or reload the entire page if the modal appear Finally, this fix is a workaround before we migrate to Module Federation and react query. --- src/react/Routes.jsx | 20 ++++++++++++-------- src/react/ZenkoUI.js | 1 + src/react/reducers/networkActivity.js | 2 ++ src/react/ui-elements/ReauthDialog.jsx | 18 +++--------------- src/types/actions.js | 4 +++- 5 files changed, 21 insertions(+), 24 deletions(-) diff --git a/src/react/Routes.jsx b/src/react/Routes.jsx index 9d67075a2..050738043 100644 --- a/src/react/Routes.jsx +++ b/src/react/Routes.jsx @@ -35,14 +35,18 @@ function PrivateRoutes() { // That will fix management API request being canceled during autentication. dispatch(loadClients()); - const refreshIntervalStatsUnit = setInterval( - () => dispatch(loadInstanceLatestStatus()), - 10000, - ); - const refreshIntervalStatsSeries = setInterval( - () => dispatch(loadInstanceStats()), - 10000, - ); + const refreshIntervalStatsUnit = setInterval(() => { + const currentTime = Math.floor(Date.now() / 1000); + if (user.expires_at >= currentTime) { + dispatch(loadInstanceLatestStatus()); + } + }, 10000); + const refreshIntervalStatsSeries = setInterval(() => { + const currentTime = Math.floor(Date.now() / 1000); + if (user.expires_at >= currentTime) { + dispatch(loadInstanceStats()); + } + }, 10000); return () => { clearInterval(refreshIntervalStatsUnit); clearInterval(refreshIntervalStatsSeries); diff --git a/src/react/ZenkoUI.js b/src/react/ZenkoUI.js index e9d0b06a9..575e5dae8 100644 --- a/src/react/ZenkoUI.js +++ b/src/react/ZenkoUI.js @@ -21,6 +21,7 @@ function ZenkoUI() { const isConfigLoaded = useSelector( (state: AppState) => state.auth.isConfigLoaded, ); + const configFailure = useSelector( (state: AppState) => state.auth.configFailure, ); diff --git a/src/react/reducers/networkActivity.js b/src/react/reducers/networkActivity.js index c8b5ea401..d63ea73c2 100644 --- a/src/react/reducers/networkActivity.js +++ b/src/react/reducers/networkActivity.js @@ -26,6 +26,8 @@ export default function networkActivity( authFailure: true, }; case 'NETWORK_AUTH_RESET': + case 'ADD_OIDC_USER': + case 'LOAD_CLIENTS_SUCCESS': return { ...state, authFailure: false, diff --git a/src/react/ui-elements/ReauthDialog.jsx b/src/react/ui-elements/ReauthDialog.jsx index 4ac5be06f..45cb49a32 100644 --- a/src/react/ui-elements/ReauthDialog.jsx +++ b/src/react/ui-elements/ReauthDialog.jsx @@ -1,11 +1,9 @@ // @flow -import { loadClients, networkAuthReset } from '../actions'; -import { useDispatch, useSelector } from 'react-redux'; +import { useSelector } from 'react-redux'; import type { AppState } from '../../types/state'; import { Button } from '@scality/core-ui/dist/next'; import { CustomModal as Modal } from './Modal'; import React from 'react'; -import { push } from 'connected-react-router'; import { spacing } from '@scality/core-ui/dist/style/theme'; const DEFAULT_MESSAGE = 'We need to log you in.'; @@ -17,18 +15,8 @@ const ReauthDialog = () => { const errorMessage = useSelector((state: AppState) => state.uiErrors.errorType === 'byAuth' ? state.uiErrors.errorMsg : null, ); - const pathname = useSelector( - (state: AppState) => state.router.location.pathname, - ); const oidcLogout = useSelector((state: AppState) => state.auth.oidcLogout); - const dispatch = useDispatch(); - - const reauth = pathName => { - dispatch(networkAuthReset()); - dispatch(loadClients()).then(() => dispatch(push(pathName))); - }; - if (!needReauth) { return null; } @@ -36,7 +24,7 @@ const ReauthDialog = () => { return ( reauth(pathname)} + close={() => window.location.reload()} footer={
{oidcLogout && ( @@ -50,7 +38,7 @@ const ReauthDialog = () => { )}
diff --git a/src/types/actions.js b/src/types/actions.js index dc9ea5ad6..d5ae96729 100644 --- a/src/types/actions.js +++ b/src/types/actions.js @@ -317,7 +317,9 @@ export type NetworkActivityAction = | NetworkActivityAuthFailureAction | NetworkActivityStartAction | NetworkActivityEndAction - | NetworkActivityAuthResetAction; + | NetworkActivityAuthResetAction + | AddOIDCUserAction + | LoadClientsSuccessAction; // configuration actions export type InstanceStatusAction = {|