Files
donetick/src/hooks/useAuth.jsx
Mo Tarbin 359202a594 fix: clear offline data on logout, session expiry, server change, and login
Offline DB was never wiped during logout, token expiry, or server URL
change, allowing stale data to persist across users and servers. Also
plugs the refresh-token expiry path which bypassed handleLogout entirely.
2026-07-14 11:08:46 -04:00

142 lines
3.7 KiB
JavaScript

import { createContext, useContext, useEffect, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { apiClient } from '../utils/ApiClient'
import { offlineDB } from '../utils/OfflineDB'
import { clearAllTokens, saveTokens } from '../utils/TokenStorage'
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 = apiClient.getApiURL()
const isAuthenticated = !!token
const isTokenExpired = () => {
const expiry = localStorage.getItem('token_expiry')
if (!expiry) return false
return new Date() >= new Date(expiry)
}
const clearAuth = async () => {
setToken(null)
setUser(null)
await clearAllTokens()
}
const login = async credentials => {
setIsLoading(true)
try {
// Ensure apiClient is initialized with the correct URL
await apiClient.init()
const currentBaseURL = apiClient.getApiURL()
const isNative =
typeof window !== 'undefined' && window.Capacitor?.isNativePlatform?.()
const config = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(credentials),
}
// Only use credentials for web, not for native apps
if (!isNative) {
config.credentials = 'include'
}
const response = await fetch(`${currentBaseURL}/auth/login`, config)
if (!response.ok) {
const res = await response.json()
return { success: false, error: res?.error || 'Login failed' }
}
const data = await response.json()
const userToken = data.token || data.access_token
if (userToken) {
setToken(userToken)
try {
await offlineDB.clearAll()
} catch (e) {
console.error('Error clearing offline data on login', e)
}
// Use centralized token storage
await saveTokens({
accessToken: userToken,
accessTokenExpiry: data.expire || data.access_token_expiry,
refreshToken: data.refresh_token,
refreshTokenExpiry: data.refresh_token_expiry,
})
}
setIsLoading(false)
return { success: true, data }
} catch (error) {
setIsLoading(false)
return { success: false, error: 'Network error' }
}
}
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,
fetchUser,
}
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>
}