From 59a5c9dbf010019974bde204a80043e37645f390 Mon Sep 17 00:00:00 2001 From: Lenin Alevski Date: Mon, 29 Jun 2020 20:58:56 -0700 Subject: [PATCH] Fix login and logout flow for MCS (#185) fixes: https://github.com/minio/mcs/issues/184 There was a bug in Safari in related to the browser not setting the session token correctly in localstorage, this was because we were using window.location.href for redirect instead of history.push after login, the redirect execution was faster was faster that the promise function getting the response after the login request and it seems to be that Safari will kill all current request of a window when the page is getting redirected. Test this: Try to sign-in using Safari browser (latest version is recommended) --- portal-ui/src/common/api/index.ts | 11 ++++++--- portal-ui/src/common/utils.ts | 16 +++++++++++++ portal-ui/src/screens/Console/Console.tsx | 10 +++++--- portal-ui/src/screens/Console/Menu/Menu.tsx | 5 ++-- portal-ui/src/screens/LoginPage/LoginPage.tsx | 23 +++++++++---------- 5 files changed, 45 insertions(+), 20 deletions(-) diff --git a/portal-ui/src/common/api/index.ts b/portal-ui/src/common/api/index.ts index 83592e5d8..d97e0b83f 100644 --- a/portal-ui/src/common/api/index.ts +++ b/portal-ui/src/common/api/index.ts @@ -17,6 +17,7 @@ import storage from "local-storage-fallback"; import request from "superagent"; import get from "lodash/get"; +import { clearSession } from "../utils"; export class API { invoke(method: string, url: string, data?: object) { @@ -28,8 +29,11 @@ export class API { .catch((err) => { // if we get unauthorized, kick out the user if (err.status === 401) { - storage.removeItem("token"); - window.location.href = "/"; + clearSession(); + // Refresh the whole page to ensure cache is clear + // and we dont end on an infinite loop + window.location.href = "/login"; + return; } return this.onError(err); }); @@ -48,7 +52,8 @@ export class API { return Promise.reject(throwMessage); } else { - return Promise.reject("Unknown error"); + clearSession(); + window.location.href = "/login"; } } } diff --git a/portal-ui/src/common/utils.ts b/portal-ui/src/common/utils.ts index d17963e9a..3b3e1626d 100644 --- a/portal-ui/src/common/utils.ts +++ b/portal-ui/src/common/utils.ts @@ -14,6 +14,8 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +import storage from "local-storage-fallback"; + export const units = [ "B", "KiB", @@ -50,6 +52,20 @@ export const setCookie = (name: string, val: string) => { name + "=" + value + "; expires=" + date.toUTCString() + "; path=/"; }; +export const deleteCookie = (name: string) => { + document.cookie = name + "=; expires=Thu, 01 Jan 1970 00:00:01 GMT;"; +}; + +export const setSession = (token: string) => { + setCookie("token", token); + storage.setItem("token", token); +}; + +export const clearSession = () => { + storage.removeItem("token"); + deleteCookie("token"); +}; + // timeFromdate gets time string from date input export const timeFromDate = (d: Date) => { let h = d.getHours() < 10 ? `0${d.getHours()}` : `${d.getHours()}`; diff --git a/portal-ui/src/screens/Console/Console.tsx b/portal-ui/src/screens/Console/Console.tsx index ac5b0f676..a69d8470b 100644 --- a/portal-ui/src/screens/Console/Console.tsx +++ b/portal-ui/src/screens/Console/Console.tsx @@ -68,6 +68,7 @@ import ListTenants from "./Tenants/ListTenants/ListTenants"; import { ISessionResponse } from "./types"; import { saveSessionResponse } from "./actions"; import TenantDetails from "./Tenants/TenantDetails/TenantDetails"; +import { clearSession } from "../../common/utils"; function Copyright() { return ( @@ -206,9 +207,12 @@ const Console = ({ .then((res) => { saveSessionResponse(res); }) - .catch((err) => { - storage.removeItem("token"); - history.push("/"); + .catch(() => { + // if server returns 401 for /api/v1/session call invoke function will internally call clearSession() + // and redirecto to window.location.href = "/"; and this code will be not reached + // in case that not happen we clear session here and redirect as well + clearSession(); + window.location.href = "/login"; }); }, [saveSessionResponse]); diff --git a/portal-ui/src/screens/Console/Menu/Menu.tsx b/portal-ui/src/screens/Console/Menu/Menu.tsx index 59544ac0a..bf7d632b6 100644 --- a/portal-ui/src/screens/Console/Menu/Menu.tsx +++ b/portal-ui/src/screens/Console/Menu/Menu.tsx @@ -50,6 +50,7 @@ import { UsersIcon, WarpIcon, } from "../../../icons"; +import { clearSession } from "../../../common/utils"; const styles = (theme: Theme) => createStyles({ @@ -156,9 +157,9 @@ const Menu = ({ userLoggedIn, classes, pages }: IMenuProps) => { const logout = () => { const deleteSession = () => { - storage.removeItem("token"); + clearSession(); userLoggedIn(false); - history.push("/"); + history.push("/login"); }; api .invoke("POST", `/api/v1/logout`) diff --git a/portal-ui/src/screens/LoginPage/LoginPage.tsx b/portal-ui/src/screens/LoginPage/LoginPage.tsx index 1b03cea4a..9db749fbb 100644 --- a/portal-ui/src/screens/LoginPage/LoginPage.tsx +++ b/portal-ui/src/screens/LoginPage/LoginPage.tsx @@ -28,7 +28,8 @@ import { SystemState } from "../../types"; import { userLoggedIn } from "../../actions"; import api from "../../common/api"; import { ILoginDetails, loginStrategyType } from "./types"; -import { setCookie } from "../../common/utils"; +import { setSession } from "../../common/utils"; +import history from "../../history"; const styles = (theme: Theme) => createStyles({ @@ -120,13 +121,13 @@ const Login = ({ classes, userLoggedIn }: ILoginProps) => { }); const loginStrategyEndpoints: LoginStrategyRoutes = { - "form": "/api/v1/login", + form: "/api/v1/login", "service-account": "/api/v1/login/mkube", - } + }; const loginStrategyPayload: LoginStrategyPayload = { - "form": { accessKey, secretKey }, + form: { accessKey, secretKey }, "service-account": { jwt }, - } + }; const fetchConfiguration = () => { setLoading(true); @@ -147,15 +148,15 @@ const Login = ({ classes, userLoggedIn }: ILoginProps) => { const formSubmit = (e: React.FormEvent) => { e.preventDefault(); request - .post(loginStrategyEndpoints[loginStrategy.loginStrategy] || "/api/v1/login") + .post( + loginStrategyEndpoints[loginStrategy.loginStrategy] || "/api/v1/login" + ) .send(loginStrategyPayload[loginStrategy.loginStrategy]) .then((res: any) => { const bodyResponse = res.body; if (bodyResponse.sessionId) { // store the jwt token - setCookie("token", bodyResponse.sessionId); - storage.setItem("token", bodyResponse.sessionId); - //return res.body.sessionId; + setSession(bodyResponse.sessionId); } else if (bodyResponse.error) { // throw will be moved to catch block once bad login returns 403 throw bodyResponse.error; @@ -164,9 +165,7 @@ const Login = ({ classes, userLoggedIn }: ILoginProps) => { .then(() => { // We set the state in redux userLoggedIn(true); - // There is a browser cache issue if we change the policy associated to an account and then logout and history.push("/") after login - // therefore after login we need to use window.location redirect - window.location.href = "/"; + history.push("/"); }) .catch((err) => { setError(err.message);