Support Refresh token and improve authentication and security.
Refactor authentication handling and API client integration - Updated UserProfileAvatar to use 'access_token' instead of 'ca_token' for logout. - Removed SSEProvider from Contexts and adjusted related imports. - Introduced useAuth hook for centralized authentication logic, including login, logout, and token management. - Refactored useSSE to utilize the new useAuth hook for token validation. - Updated UserQueries to check for token validity using the new method. - Deleted AuthenticationService as its functionality is now handled by useAuth. - Created a new ApiClient utility for handling API requests and token management. - Updated various components and views to use the new ApiClient for API interactions. - Removed TokenManager and migrated its functionality to the new ApiClient. - Adjusted LoginView and related components to utilize the new authentication flow. - Cleaned up unused variables and improved code consistency across components.
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
import { createContext, useContext, useEffect, useState } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { API_URL } from '../Config'
|
||||
import { apiClient } from '../utils/ApiClient'
|
||||
|
||||
const AuthContext = createContext(null)
|
||||
|
||||
export const useAuth = () => {
|
||||
const context = useContext(AuthContext)
|
||||
if (!context) {
|
||||
throw new Error('useAuth must be used within an AuthProvider')
|
||||
}
|
||||
return context
|
||||
}
|
||||
|
||||
export const AuthProvider = ({ children }) => {
|
||||
const [token, setToken] = useState(() => localStorage.getItem('token'))
|
||||
const [user, setUser] = useState(null)
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
const navigate = useNavigate()
|
||||
|
||||
const baseURL = `${API_URL}/api/v1`
|
||||
const isAuthenticated = !!token
|
||||
|
||||
const isTokenExpired = () => {
|
||||
const expiry = localStorage.getItem('token_expiry')
|
||||
if (!expiry) return false
|
||||
return new Date() >= new Date(expiry)
|
||||
}
|
||||
|
||||
const clearAuth = () => {
|
||||
setToken(null)
|
||||
setUser(null)
|
||||
localStorage.removeItem('token')
|
||||
localStorage.removeItem('token_expiry')
|
||||
localStorage.removeItem('ca_token')
|
||||
localStorage.removeItem('ca_expiration')
|
||||
localStorage.removeItem('access_token')
|
||||
}
|
||||
|
||||
const login = async credentials => {
|
||||
setIsLoading(true)
|
||||
try {
|
||||
const response = await fetch(`${baseURL}/auth/login`, {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(credentials),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json()
|
||||
return { success: false, error: error.message || 'Login failed' }
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
const userToken = data.token || data.access_token
|
||||
|
||||
if (userToken) {
|
||||
setToken(userToken)
|
||||
localStorage.setItem('token', userToken)
|
||||
|
||||
if (data.expire || data.access_token_expiry) {
|
||||
localStorage.setItem(
|
||||
'token_expiry',
|
||||
data.expire || data.access_token_expiry,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
setIsLoading(false)
|
||||
return { success: true, data }
|
||||
} catch (error) {
|
||||
setIsLoading(false)
|
||||
return { success: false, error: 'Network error' }
|
||||
}
|
||||
}
|
||||
|
||||
const logout = async () => {
|
||||
setIsLoading(true)
|
||||
try {
|
||||
await fetch(`${baseURL}/auth/logout`, {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
})
|
||||
} catch (error) {
|
||||
console.warn('Logout API call failed:', error)
|
||||
} finally {
|
||||
clearAuth()
|
||||
setIsLoading(false)
|
||||
navigate('/login')
|
||||
}
|
||||
}
|
||||
|
||||
const fetchUser = async () => {
|
||||
if (!token) return null
|
||||
|
||||
try {
|
||||
const response = await apiClient.get('/users/profile')
|
||||
|
||||
if (!response.ok) {
|
||||
return null
|
||||
}
|
||||
|
||||
const userData = await response.json()
|
||||
setUser(userData)
|
||||
return userData
|
||||
} catch (error) {
|
||||
console.error('Fetch user error:', error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const initAuth = async () => {
|
||||
if (token && !isTokenExpired()) {
|
||||
await fetchUser()
|
||||
} else if (token && isTokenExpired()) {
|
||||
// Token is expired, but don't refresh here
|
||||
// Let the first API call handle refresh via ApiClient
|
||||
// Just try to fetch user - if it fails, ApiClient will handle refresh
|
||||
await fetchUser()
|
||||
} else {
|
||||
clearAuth()
|
||||
navigate('/login')
|
||||
}
|
||||
setIsLoading(false)
|
||||
}
|
||||
|
||||
initAuth()
|
||||
}, [token, navigate])
|
||||
|
||||
const value = {
|
||||
token,
|
||||
user,
|
||||
isLoading,
|
||||
isAuthenticated,
|
||||
login,
|
||||
logout,
|
||||
fetchUser,
|
||||
}
|
||||
|
||||
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>
|
||||
}
|
||||
Reference in New Issue
Block a user