| @@ -12,6 +12,7 @@ export const predictUsageCount = 'predictUsageCount' | |||||
| export const windowCount = 'windowCount' | export const windowCount = 'windowCount' | ||||
| import {useNavigate} from "react-router-dom"; | import {useNavigate} from "react-router-dom"; | ||||
| import {useDispatch} from "react-redux"; | import {useDispatch} from "react-redux"; | ||||
| import { REFRESH_TOKEN } from 'utils/ApiPathConst'; | |||||
| // ** Handle User Login | // ** Handle User Login | ||||
| export const handleLogin = data => { | export const handleLogin = data => { | ||||
| @@ -104,6 +105,8 @@ export const SetupAxiosInterceptors = () => { | |||||
| const navigate = useNavigate() | const navigate = useNavigate() | ||||
| const dispatch = useDispatch(); | const dispatch = useDispatch(); | ||||
| //const updateLastRequestTime = useContext(TimerContext); | //const updateLastRequestTime = useContext(TimerContext); | ||||
| let isRefreshToken= false; | |||||
| axios.interceptors.request.use( | axios.interceptors.request.use( | ||||
| config => { | config => { | ||||
| // ** Get token from localStorage | // ** Get token from localStorage | ||||
| @@ -126,39 +129,73 @@ export const SetupAxiosInterceptors = () => { | |||||
| //updateLastRequestTime(Date.now()); | //updateLastRequestTime(Date.now()); | ||||
| return response; | return response; | ||||
| }, | }, | ||||
| error => { | |||||
| async (error) => { | |||||
| // ** const { config, response: { status } } = error | // ** const { config, response: { status } } = error | ||||
| const {response} = error | |||||
| if (error.response.status === 401) { | |||||
| if (localStorage.getItem("expiredAlertShown") === null) { | |||||
| localStorage.setItem("expiredAlertShown", true) | |||||
| alert("登入驗證已過期,請重新登入。") | |||||
| if (error.response.status === 401 && error.config.url !== apiPath + REFRESH_TOKEN) { | |||||
| // Make a request to refresh the access token | |||||
| const refreshToken = localStorage.getItem('refreshToken'); | |||||
| if (isRefreshToken) { | |||||
| return; | |||||
| } | } | ||||
| } | |||||
| // ** if (status === 401) { | |||||
| if (response.status === 401) { | |||||
| if (localStorage.getItem("expiredAlertShown") === null) { | |||||
| localStorage.setItem("expiredAlertShown", true) | |||||
| alert("登入驗證已過期,請重新登入。") | |||||
| isRefreshToken = true; | |||||
| return axios | |||||
| .post(`${apiPath}${REFRESH_TOKEN}`, { | |||||
| refreshToken: refreshToken // Replace with your refresh token | |||||
| }) | |||||
| .then((response) => { | |||||
| if (response.status === 200) { | |||||
| const newAccessToken = response.data.accessToken; | |||||
| const newRefreshToken = response.data.refreshToken; | |||||
| localStorage.setItem('accessToken', newAccessToken); | |||||
| localStorage.setItem('refreshToken', newRefreshToken); | |||||
| isRefreshToken = false; | |||||
| window.location.reload(); | |||||
| } | |||||
| }) | |||||
| .catch((refreshError) => { | |||||
| dispatch(handleLogoutFunction()); | |||||
| navigate('/login'); | |||||
| isRefreshToken = false; | |||||
| window.location.reload(); | |||||
| throw refreshError; | |||||
| }); | |||||
| } else { | |||||
| // if (error.response.status === 401) { | |||||
| // await dispatch(handleLogoutFunction()); | |||||
| // await navigate('/login'); | |||||
| // await window.location.reload(); | |||||
| // } | |||||
| if (error.response.status === 401) { | |||||
| if (localStorage.getItem("expiredAlertShown") === null) { | |||||
| localStorage.setItem("expiredAlertShown", true) | |||||
| alert("登入驗證已過期,請重新登入。") | |||||
| } | |||||
| } | } | ||||
| } | |||||
| if (response && response.status === 401) { | |||||
| if (localStorage.getItem("expiredAlertShown") === null) { | |||||
| localStorage.setItem("expiredAlertShown", true) | |||||
| alert("登入驗證已過期,請重新登入。") | |||||
| // ** if (status === 401) { | |||||
| if (response.status === 401) { | |||||
| if (localStorage.getItem("expiredAlertShown") === null) { | |||||
| localStorage.setItem("expiredAlertShown", true) | |||||
| alert("登入驗證已過期,請重新登入。") | |||||
| } | |||||
| } | |||||
| if (response && response.status === 401) { | |||||
| if (localStorage.getItem("expiredAlertShown") === null) { | |||||
| localStorage.setItem("expiredAlertShown", true) | |||||
| alert("登入驗證已過期,請重新登入。") | |||||
| } | |||||
| } | |||||
| if (localStorage.getItem("expiredAlertShown")) { | |||||
| dispatch(handleLogoutFunction()); | |||||
| navigate('/login'); | |||||
| } | } | ||||
| } | |||||
| if (localStorage.getItem("expiredAlertShown")) { | |||||
| dispatch(handleLogoutFunction()); | |||||
| navigate('/login'); | |||||
| } | } | ||||
| return Promise.reject(error) | return Promise.reject(error) | ||||
| }, | |||||
| } | |||||
| ) | ) | ||||
| } | } | ||||
| @@ -1,17 +1,29 @@ | |||||
| import React, { createContext, useState, useEffect } from 'react'; | import React, { createContext, useState, useEffect } from 'react'; | ||||
| //import {useNavigate} from "react-router-dom"; | |||||
| import {useNavigate} from "react-router-dom"; | |||||
| //import axios from "axios"; | //import axios from "axios"; | ||||
| import {getUserData} from "../auth/utils"; | import {getUserData} from "../auth/utils"; | ||||
| import {isObjEmpty} from "../utils/Utils"; | import {isObjEmpty} from "../utils/Utils"; | ||||
| import {useIdleTimer} from "react-idle-timer"; | import {useIdleTimer} from "react-idle-timer"; | ||||
| import { handleLogoutFunction } from 'auth/index'; | |||||
| import { useDispatch } from "react-redux"; | |||||
| import { | |||||
| isUserLoggedIn, | |||||
| isGLDLoggedIn, | |||||
| // isPrimaryLoggedIn, | |||||
| // isCreditorLoggedIn, | |||||
| // isINDLoggedIn, | |||||
| // isORGLoggedIn, | |||||
| // getUserId | |||||
| } from "utils/Utils"; | |||||
| const TimerContext = createContext(); | const TimerContext = createContext(); | ||||
| const AutoLogoutProvider = ({ children }) => { | const AutoLogoutProvider = ({ children }) => { | ||||
| const [lastRequestTime, setLastRequestTime] = useState(Date.now()); | const [lastRequestTime, setLastRequestTime] = useState(Date.now()); | ||||
| //const navigate = useNavigate(); | |||||
| const [logoutInterval /*, setLogoutInterval*/] = useState(1); | |||||
| const navigate = useNavigate(); | |||||
| const [logoutInterval, setLogoutInterval] = useState(1); | |||||
| const [state, setState] = useState('Active'); | const [state, setState] = useState('Active'); | ||||
| const dispatch = useDispatch() | |||||
| const onIdle = () => { | const onIdle = () => { | ||||
| setLastRequestTime(Date.now()); | setLastRequestTime(Date.now()); | ||||
| @@ -30,7 +42,7 @@ const AutoLogoutProvider = ({ children }) => { | |||||
| } = useIdleTimer({ | } = useIdleTimer({ | ||||
| onIdle, | onIdle, | ||||
| onActive, | onActive, | ||||
| timeout: 10_000, | |||||
| timeout: 1_000, | |||||
| throttle: 500, | throttle: 500, | ||||
| crossTab: true, | crossTab: true, | ||||
| syncTimers: 200, | syncTimers: 200, | ||||
| @@ -43,19 +55,23 @@ const AutoLogoutProvider = ({ children }) => { | |||||
| const userData = getUserData(); | const userData = getUserData(); | ||||
| if(!isObjEmpty(userData)){ | if(!isObjEmpty(userData)){ | ||||
| //TODO: get auto logout time here | //TODO: get auto logout time here | ||||
| if(isGLDLoggedIn()){ | |||||
| setLogoutInterval(240); | |||||
| }else{ | |||||
| setLogoutInterval(1); | |||||
| } | |||||
| // axios.get(`${apiPath}${GET_IDLE_LOGOUT_TIME}`, | // axios.get(`${apiPath}${GET_IDLE_LOGOUT_TIME}`, | ||||
| // ) | // ) | ||||
| // .then((response) => { | |||||
| // if (response.status === 200) { | |||||
| // setLastRequestTime(Date.now()); | |||||
| // setLogoutInterval(parseInt(response.data.data)); | |||||
| // } | |||||
| // }) | |||||
| // .catch(error => { | |||||
| // console.log(error); | |||||
| // return false; | |||||
| // }); | |||||
| // .then((response) => { | |||||
| // if (response.status === 200) { | |||||
| // setLastRequestTime(Date.now()); | |||||
| // setLogoutInterval(parseInt(response.data.data)); | |||||
| // } | |||||
| // }) | |||||
| // .catch(error => { | |||||
| // console.log(error); | |||||
| // return false; | |||||
| // }); | |||||
| } | } | ||||
| else{ | else{ | ||||
| //navigate('/login'); | //navigate('/login'); | ||||
| @@ -68,13 +84,14 @@ const AutoLogoutProvider = ({ children }) => { | |||||
| getRemainingTime(); | getRemainingTime(); | ||||
| if(state !== "Active" && lastActiveTab){ | if(state !== "Active" && lastActiveTab){ | ||||
| const timeElapsed = currentTime - lastRequestTime; | const timeElapsed = currentTime - lastRequestTime; | ||||
| // console.log(timeElapsed); | |||||
| // console.log(parseInt(timeElapsed/1000)); | |||||
| // console.log(logoutInterval* 60); | |||||
| if (timeElapsed >= logoutInterval * 60 * 1000) { | if (timeElapsed >= logoutInterval * 60 * 1000) { | ||||
| //TODO: auto logout here | |||||
| // console.log("logout"); | |||||
| //await dispatch(handleLogoutFunction()); | |||||
| //await navigate('/login'); | |||||
| //await window.location.reload(); | |||||
| if(isUserLoggedIn()){ | |||||
| alert("登入驗證已過期,請重新登入。") | |||||
| dispatch(handleLogoutFunction()); | |||||
| navigate('/login'); | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| else if(state === "Active"){ | else if(state === "Active"){ | ||||
| @@ -0,0 +1,72 @@ | |||||
| import { createContext, useEffect, useRef, useCallback } from 'react'; | |||||
| import { apiPath } from 'auth/utils'; | |||||
| import { REFRESH_TOKEN } from 'utils/ApiPathConst'; | |||||
| import axios from 'axios'; | |||||
| const RefreshTokenContext = createContext(); | |||||
| const RefreshTokenProvider = ({ children }) => { | |||||
| const token = useRef(localStorage.getItem('accessToken')); | |||||
| const isRefresh = useRef(false); | |||||
| // handle Refresh Token Logic | |||||
| const handleRefreshToken = useCallback(() => { | |||||
| if (!isRefresh.current) { | |||||
| const refreshToken = localStorage.getItem('refreshToken'); | |||||
| isRefresh.current = true; | |||||
| axios | |||||
| .post(`${apiPath}${REFRESH_TOKEN}`, { | |||||
| refreshToken: refreshToken | |||||
| }) | |||||
| .then((response) => { | |||||
| if (response.status === 200) { | |||||
| const newAccessToken = response.data.accessToken; | |||||
| const newRefreshToken = response.data.refreshToken; | |||||
| localStorage.setItem('accessToken', newAccessToken); | |||||
| localStorage.setItem('refreshToken', newRefreshToken); | |||||
| token.current = newAccessToken; | |||||
| isRefresh.current = false; | |||||
| } else { | |||||
| token.current = null; | |||||
| isRefresh.current = false; | |||||
| } | |||||
| }) | |||||
| .catch((refreshError) => { | |||||
| console.log('Failed to refresh token'); | |||||
| console.log(refreshError) | |||||
| token.current = null | |||||
| isRefresh.current = false; | |||||
| }); | |||||
| } | |||||
| }, []); | |||||
| // Function to check token expiry | |||||
| const checkTokenExpiry = useCallback(() => { | |||||
| // Check if token is present and its expiry time | |||||
| if (token.current) { | |||||
| const tokenExp = JSON.parse(atob(token.current.split('.')[1])).exp; | |||||
| const currentTime = Math.floor(Date.now() / 1000); | |||||
| const expiryTime = tokenExp - 30; // Refresh 30 seconds before expiry | |||||
| // console.log("check refresh Token"); | |||||
| // console.log(currentTime); | |||||
| // console.log(new Date(currentTime*1000).toLocaleString()); | |||||
| // console.log(expiryTime); | |||||
| // console.log(new Date(expiryTime*1000).toLocaleString()); | |||||
| // console.log('accessToken: ' + localStorage.getItem('accessToken')); | |||||
| // console.log('refreshToken: ' + localStorage.getItem('refreshToken')); | |||||
| if (currentTime >= expiryTime) { | |||||
| handleRefreshToken(); | |||||
| } | |||||
| } | |||||
| }, [token]); | |||||
| // Start the timer on component mount | |||||
| useEffect(() => { | |||||
| const timer = setInterval(checkTokenExpiry, 10000); // Check every 10 second | |||||
| return () => clearInterval(timer); // Cleanup timer on unmount | |||||
| }, [checkTokenExpiry]); | |||||
| return <RefreshTokenContext.Provider value={{}}>{children}</RefreshTokenContext.Provider>; | |||||
| }; | |||||
| export { RefreshTokenContext, RefreshTokenProvider }; | |||||
| @@ -18,6 +18,7 @@ import { store } from 'store'; | |||||
| import reportWebVitals from './reportWebVitals'; | import reportWebVitals from './reportWebVitals'; | ||||
| import {I18nProvider} from "./components/I18nProvider"; | import {I18nProvider} from "./components/I18nProvider"; | ||||
| import {AutoLogoutProvider} from "./components/AutoLogoutProvider"; | import {AutoLogoutProvider} from "./components/AutoLogoutProvider"; | ||||
| import {RefreshTokenProvider} from "./components/RefreshTokenProvider"; | |||||
| // ==============================|| MAIN - REACT DOM RENDER ||============================== // | // ==============================|| MAIN - REACT DOM RENDER ||============================== // | ||||
| @@ -29,11 +30,13 @@ root.render( | |||||
| <StrictMode> | <StrictMode> | ||||
| <ReduxProvider store={store}> | <ReduxProvider store={store}> | ||||
| <I18nProvider> | <I18nProvider> | ||||
| <BrowserRouter basename="/"> | |||||
| <AutoLogoutProvider> | |||||
| <App /> | |||||
| </AutoLogoutProvider> | |||||
| </BrowserRouter> | |||||
| <BrowserRouter basename="/"> | |||||
| <RefreshTokenProvider> | |||||
| <AutoLogoutProvider> | |||||
| <App /> | |||||
| </AutoLogoutProvider> | |||||
| </RefreshTokenProvider> | |||||
| </BrowserRouter> | |||||
| </I18nProvider> | </I18nProvider> | ||||
| </ReduxProvider> | </ReduxProvider> | ||||
| </StrictMode> | </StrictMode> | ||||
| @@ -1,6 +1,8 @@ | |||||
| import {apiPath} from "../auth/utils"; | import {apiPath} from "../auth/utils"; | ||||
| // GET request | // GET request | ||||
| export const REFRESH_TOKEN = "/refresh-token" | |||||
| export const CHANGE_PASSWORD_PATH = "/user/change-password" | |||||
| //Group Config | //Group Config | ||||
| export const GET_GROUP_LIST_PATH = '/group'; | export const GET_GROUP_LIST_PATH = '/group'; | ||||