| @@ -12,6 +12,7 @@ export const predictUsageCount = 'predictUsageCount' | |||
| export const windowCount = 'windowCount' | |||
| import {useNavigate} from "react-router-dom"; | |||
| import {useDispatch} from "react-redux"; | |||
| import { REFRESH_TOKEN } from 'utils/ApiPathConst'; | |||
| // ** Handle User Login | |||
| export const handleLogin = data => { | |||
| @@ -104,6 +105,8 @@ export const SetupAxiosInterceptors = () => { | |||
| const navigate = useNavigate() | |||
| const dispatch = useDispatch(); | |||
| //const updateLastRequestTime = useContext(TimerContext); | |||
| let isRefreshToken= false; | |||
| axios.interceptors.request.use( | |||
| config => { | |||
| // ** Get token from localStorage | |||
| @@ -126,39 +129,73 @@ export const SetupAxiosInterceptors = () => { | |||
| //updateLastRequestTime(Date.now()); | |||
| return response; | |||
| }, | |||
| error => { | |||
| async (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) | |||
| }, | |||
| } | |||
| ) | |||
| } | |||
| @@ -1,17 +1,29 @@ | |||
| import React, { createContext, useState, useEffect } from 'react'; | |||
| //import {useNavigate} from "react-router-dom"; | |||
| import {useNavigate} from "react-router-dom"; | |||
| //import axios from "axios"; | |||
| import {getUserData} from "../auth/utils"; | |||
| import {isObjEmpty} from "../utils/Utils"; | |||
| 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 AutoLogoutProvider = ({ children }) => { | |||
| 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 dispatch = useDispatch() | |||
| const onIdle = () => { | |||
| setLastRequestTime(Date.now()); | |||
| @@ -30,7 +42,7 @@ const AutoLogoutProvider = ({ children }) => { | |||
| } = useIdleTimer({ | |||
| onIdle, | |||
| onActive, | |||
| timeout: 10_000, | |||
| timeout: 1_000, | |||
| throttle: 500, | |||
| crossTab: true, | |||
| syncTimers: 200, | |||
| @@ -43,19 +55,23 @@ const AutoLogoutProvider = ({ children }) => { | |||
| const userData = getUserData(); | |||
| if(!isObjEmpty(userData)){ | |||
| //TODO: get auto logout time here | |||
| if(isGLDLoggedIn()){ | |||
| setLogoutInterval(240); | |||
| }else{ | |||
| setLogoutInterval(1); | |||
| } | |||
| // 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{ | |||
| //navigate('/login'); | |||
| @@ -68,13 +84,14 @@ const AutoLogoutProvider = ({ children }) => { | |||
| getRemainingTime(); | |||
| if(state !== "Active" && lastActiveTab){ | |||
| const timeElapsed = currentTime - lastRequestTime; | |||
| // console.log(timeElapsed); | |||
| // console.log(parseInt(timeElapsed/1000)); | |||
| // console.log(logoutInterval* 60); | |||
| 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"){ | |||
| @@ -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 {I18nProvider} from "./components/I18nProvider"; | |||
| import {AutoLogoutProvider} from "./components/AutoLogoutProvider"; | |||
| import {RefreshTokenProvider} from "./components/RefreshTokenProvider"; | |||
| // ==============================|| MAIN - REACT DOM RENDER ||============================== // | |||
| @@ -29,11 +30,13 @@ root.render( | |||
| <StrictMode> | |||
| <ReduxProvider store={store}> | |||
| <I18nProvider> | |||
| <BrowserRouter basename="/"> | |||
| <AutoLogoutProvider> | |||
| <App /> | |||
| </AutoLogoutProvider> | |||
| </BrowserRouter> | |||
| <BrowserRouter basename="/"> | |||
| <RefreshTokenProvider> | |||
| <AutoLogoutProvider> | |||
| <App /> | |||
| </AutoLogoutProvider> | |||
| </RefreshTokenProvider> | |||
| </BrowserRouter> | |||
| </I18nProvider> | |||
| </ReduxProvider> | |||
| </StrictMode> | |||
| @@ -1,6 +1,8 @@ | |||
| import {apiPath} from "../auth/utils"; | |||
| // GET request | |||
| export const REFRESH_TOKEN = "/refresh-token" | |||
| export const CHANGE_PASSWORD_PATH = "/user/change-password" | |||
| //Group Config | |||
| export const GET_GROUP_LIST_PATH = '/group'; | |||