diff --git a/src/auth/index.js b/src/auth/index.js index 1a6a8e9..5776050 100644 --- a/src/auth/index.js +++ b/src/auth/index.js @@ -20,6 +20,16 @@ let axiosInterceptorsSetup = false; // In-memory guard so only one 401/logout flow shows the alert (avoids race when multiple 401s or interceptors run) let expiredAlertShownInMemory = false; +/** Login / public auth endpoints must not trigger token refresh or session-expiry reload. */ +const isPublicAuthRequest = (reqUrl) => + reqUrl.includes('/login') || reqUrl.includes('/ldap-login'); + +/** Clear stale session-expiry flag so a new login attempt can show its own error dialog. */ +export const clearExpiredSessionAlert = () => { + localStorage.removeItem('expiredAlertShown'); + expiredAlertShownInMemory = false; +}; + // ** Handle User Login export const handleLogin = data => { return dispatch => { @@ -152,6 +162,12 @@ export const SetupAxiosInterceptors = () => { // const { config, response: { status } } = error const status = error.response?.status const reqUrl = error.config?.url || '' + + // Let login forms handle their own errors (wrong password, locked account, etc.) + if (isPublicAuthRequest(reqUrl)) { + return Promise.reject(error) + } + // iAM Smart registration: HKID already linked — caller must show /iamsmart/pleaseLogin (not refresh/logout) if ( status === 401 && @@ -160,11 +176,15 @@ export const SetupAxiosInterceptors = () => { ) { return Promise.reject(error) } - if (status === 401 && error.config?.url !== apiPath + REFRESH_TOKEN) { + if ( + status === 401 && + !isPublicAuthRequest(reqUrl) && + error.config?.url !== apiPath + REFRESH_TOKEN + ) { // Make a request to refresh the access token const refreshToken = localStorage.getItem('refreshToken'); if (isRefreshToken) { - return; + return Promise.reject(error); } isRefreshToken = true; return axios @@ -217,7 +237,7 @@ export const SetupAxiosInterceptors = () => { // } // } - if (localStorage.getItem("expiredAlertShown")) { + if (localStorage.getItem("expiredAlertShown") && !isPublicAuthRequest(reqUrl)) { await dispatch(handleLogoutFunction()); await navigate('/login'); await window.location.reload(); diff --git a/src/auth/jwt/jwtService.js b/src/auth/jwt/jwtService.js index 0df9dac..884d090 100644 --- a/src/auth/jwt/jwtService.js +++ b/src/auth/jwt/jwtService.js @@ -40,29 +40,46 @@ export default class JwtService { // ** const { config, response: { status } } = error const {config, response} = error const originalRequest = config + const reqUrl = config?.url || '' // ** if (status === 401) { if (response && response.status === 401) { + const isPublicAuthRequest = + reqUrl.includes('/login') || + reqUrl.includes('/ldap-login') || + reqUrl.includes('/refresh-token') + if (isPublicAuthRequest) { + return Promise.reject(error) + } + if (!this.isAlreadyFetchingAccessToken) { this.isAlreadyFetchingAccessToken = true - this.refreshToken().then(r => { - this.isAlreadyFetchingAccessToken = false - - // ** Update accessToken in localStorage - this.setToken(r.data.accessToken) - this.setRefreshToken(r.data.refreshToken) - - this.onAccessTokenFetched(r.data.accessToken) - }) + this.refreshToken() + .then(r => { + this.isAlreadyFetchingAccessToken = false + + // ** Update accessToken in localStorage + this.setToken(r.data.accessToken) + this.setRefreshToken(r.data.refreshToken) + + this.onAccessTokenFetched(r.data.accessToken) + }) + .catch(refreshError => { + this.isAlreadyFetchingAccessToken = false + this.onAccessTokenFetchFailed(refreshError) + }) } - const retryOriginalRequest = new Promise(resolve => { - this.addSubscriber(accessToken => { - // ** Make sure to assign accessToken according to your response. - // ** Check: https://pixinvent.ticksy.com/ticket/2413870 - // ** Change Authorization header - originalRequest.headers.Authorization = `${this.jwtConfig.tokenType} ${accessToken}` - resolve(this.axios(originalRequest)) - }) + const retryOriginalRequest = new Promise((resolve, reject) => { + this.addSubscriber( + accessToken => { + // ** Make sure to assign accessToken according to your response. + // ** Check: https://pixinvent.ticksy.com/ticket/2413870 + // ** Change Authorization header + originalRequest.headers.Authorization = `${this.jwtConfig.tokenType} ${accessToken}` + resolve(axios(originalRequest)) + }, + reject + ) }) return retryOriginalRequest } @@ -72,11 +89,17 @@ export default class JwtService { } onAccessTokenFetched(accessToken) { - this.subscribers = this.subscribers.filter(callback => callback(accessToken)) + this.subscribers.forEach(({ onSuccess }) => onSuccess(accessToken)) + this.subscribers = [] + } + + onAccessTokenFetchFailed(refreshError) { + this.subscribers.forEach(({ onFailure }) => onFailure(refreshError)) + this.subscribers = [] } - addSubscriber(callback) { - this.subscribers.push(callback) + addSubscriber(onSuccess, onFailure) { + this.subscribers.push({ onSuccess, onFailure }) } getToken() { diff --git a/src/pages/authentication/auth-forms/AuthLoginCustom.js b/src/pages/authentication/auth-forms/AuthLoginCustom.js index ec1480a..8d301f9 100644 --- a/src/pages/authentication/auth-forms/AuthLoginCustom.js +++ b/src/pages/authentication/auth-forms/AuthLoginCustom.js @@ -40,7 +40,7 @@ import { EyeOutlined, EyeInvisibleOutlined } from '@ant-design/icons'; // import axios from "axios"; import { useDispatch } from "react-redux"; -import { handleLogin } from "auth/index"; +import { handleLogin, clearExpiredSessionAlert } from "auth/index"; import useJwt from "auth/jwt/useJwt"; import { FormattedMessage, useIntl } from "react-intl"; import { SysContext } from "components/SysSettingProvider" @@ -74,6 +74,10 @@ const AuthLoginCustom = () => { /** Blocks a second login before React re-renders (double-click / rapid taps). */ const loginInFlightRef = useRef(false); + useEffect(() => { + clearExpiredSessionAlert(); + }, []); + const handleMouseDownPassword = (event) => { event.preventDefault(); }; @@ -84,6 +88,7 @@ const AuthLoginCustom = () => { return; } loginInFlightRef.current = true; + clearExpiredSessionAlert(); setOnLogin(true) useJwt .login({ username: values.username, password: values.password })