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:
Mo Tarbin
2025-12-25 22:54:39 -05:00
parent 1123fb3ca6
commit 8edd3774d2
20 changed files with 493 additions and 428 deletions
+144
View File
@@ -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>
}