Fix: Prevent auth state loss on container restart (v2.7.2)
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 21s
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 21s
This commit is contained in:
@@ -11,6 +11,63 @@ export function AuthProvider({ children }) {
|
|||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
const [error, setError] = useState(null)
|
const [error, setError] = useState(null)
|
||||||
|
|
||||||
|
const logout = useCallback(async () => {
|
||||||
|
const token = localStorage.getItem(TOKEN_KEY)
|
||||||
|
|
||||||
|
if (token) {
|
||||||
|
try {
|
||||||
|
await fetch('/api/auth/logout', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${token}`,
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Logout error:', err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
localStorage.removeItem(TOKEN_KEY)
|
||||||
|
localStorage.removeItem(REFRESH_TOKEN_KEY)
|
||||||
|
localStorage.removeItem(USER_KEY)
|
||||||
|
setUser(null)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const refreshToken = useCallback(async () => {
|
||||||
|
const refresh = localStorage.getItem(REFRESH_TOKEN_KEY)
|
||||||
|
|
||||||
|
if (!refresh) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/auth/refresh', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ refresh_token: refresh })
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json()
|
||||||
|
|
||||||
|
localStorage.setItem(TOKEN_KEY, data.access_token)
|
||||||
|
localStorage.setItem(REFRESH_TOKEN_KEY, data.refresh_token)
|
||||||
|
localStorage.setItem(USER_KEY, JSON.stringify(data.user))
|
||||||
|
setUser(data.user)
|
||||||
|
|
||||||
|
return true
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Refresh token error:', err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
// Initialize from localStorage
|
// Initialize from localStorage
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const initAuth = async () => {
|
const initAuth = async () => {
|
||||||
@@ -20,14 +77,20 @@ export function AuthProvider({ children }) {
|
|||||||
if (token && savedUser) {
|
if (token && savedUser) {
|
||||||
try {
|
try {
|
||||||
setUser(JSON.parse(savedUser))
|
setUser(JSON.parse(savedUser))
|
||||||
// Verify token is still valid
|
// Verify token is still valid with timeout
|
||||||
|
const controller = new AbortController()
|
||||||
|
const timeoutId = setTimeout(() => controller.abort(), 5000) // 5 second timeout
|
||||||
|
|
||||||
const response = await fetch('/api/auth/me', {
|
const response = await fetch('/api/auth/me', {
|
||||||
headers: {
|
headers: {
|
||||||
'Authorization': `Bearer ${token}`,
|
'Authorization': `Bearer ${token}`,
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
}
|
},
|
||||||
|
signal: controller.signal
|
||||||
})
|
})
|
||||||
|
|
||||||
|
clearTimeout(timeoutId)
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const data = await response.json()
|
const data = await response.json()
|
||||||
setUser(data.user)
|
setUser(data.user)
|
||||||
@@ -38,17 +101,32 @@ export function AuthProvider({ children }) {
|
|||||||
if (!refreshed) {
|
if (!refreshed) {
|
||||||
logout()
|
logout()
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// For other errors (like 503, 502, network errors), don't clear auth
|
||||||
|
// Just log the error and keep the user logged in
|
||||||
|
console.warn('Auth check failed with status:', response.status, 'but keeping session')
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Auth init error:', err)
|
// Network errors (e.g., backend not ready) should not clear auth
|
||||||
logout()
|
// Only clear if it's a real auth error
|
||||||
|
if (err.name === 'AbortError') {
|
||||||
|
// Timeout - backend might be starting up, keep auth state
|
||||||
|
console.warn('Auth check timeout, backend might be starting up. Keeping session.')
|
||||||
|
} else if (err.name === 'TypeError' && (err.message.includes('fetch') || err.message.includes('Failed to fetch'))) {
|
||||||
|
// Network error - backend might be starting up, keep auth state
|
||||||
|
console.warn('Network error during auth check, keeping session:', err.message)
|
||||||
|
} else {
|
||||||
|
// Other errors - might be auth related
|
||||||
|
console.error('Auth init error:', err)
|
||||||
|
// Don't automatically logout on unknown errors
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
initAuth()
|
initAuth()
|
||||||
}, [])
|
}, [refreshToken, logout])
|
||||||
|
|
||||||
const login = useCallback(async (email, password) => {
|
const login = useCallback(async (email, password) => {
|
||||||
setError(null)
|
setError(null)
|
||||||
@@ -108,63 +186,6 @@ export function AuthProvider({ children }) {
|
|||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const logout = useCallback(async () => {
|
|
||||||
const token = localStorage.getItem(TOKEN_KEY)
|
|
||||||
|
|
||||||
if (token) {
|
|
||||||
try {
|
|
||||||
await fetch('/api/auth/logout', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Authorization': `Bearer ${token}`,
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Logout error:', err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
localStorage.removeItem(TOKEN_KEY)
|
|
||||||
localStorage.removeItem(REFRESH_TOKEN_KEY)
|
|
||||||
localStorage.removeItem(USER_KEY)
|
|
||||||
setUser(null)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const refreshToken = useCallback(async () => {
|
|
||||||
const refresh = localStorage.getItem(REFRESH_TOKEN_KEY)
|
|
||||||
|
|
||||||
if (!refresh) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch('/api/auth/refresh', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ refresh_token: refresh })
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await response.json()
|
|
||||||
|
|
||||||
localStorage.setItem(TOKEN_KEY, data.access_token)
|
|
||||||
localStorage.setItem(REFRESH_TOKEN_KEY, data.refresh_token)
|
|
||||||
localStorage.setItem(USER_KEY, JSON.stringify(data.user))
|
|
||||||
setUser(data.user)
|
|
||||||
|
|
||||||
return true
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Refresh token error:', err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const getToken = useCallback(() => {
|
const getToken = useCallback(() => {
|
||||||
return localStorage.getItem(TOKEN_KEY)
|
return localStorage.getItem(TOKEN_KEY)
|
||||||
}, [])
|
}, [])
|
||||||
@@ -182,21 +203,28 @@ export function AuthProvider({ children }) {
|
|||||||
headers['Authorization'] = `Bearer ${token}`
|
headers['Authorization'] = `Bearer ${token}`
|
||||||
}
|
}
|
||||||
|
|
||||||
let response = await fetch(url, { ...options, headers })
|
try {
|
||||||
|
let response = await fetch(url, { ...options, headers })
|
||||||
// If 401, try to refresh token and retry
|
|
||||||
if (response.status === 401) {
|
// If 401, try to refresh token and retry
|
||||||
const refreshed = await refreshToken()
|
if (response.status === 401) {
|
||||||
if (refreshed) {
|
const refreshed = await refreshToken()
|
||||||
const newToken = localStorage.getItem(TOKEN_KEY)
|
if (refreshed) {
|
||||||
headers['Authorization'] = `Bearer ${newToken}`
|
const newToken = localStorage.getItem(TOKEN_KEY)
|
||||||
response = await fetch(url, { ...options, headers })
|
headers['Authorization'] = `Bearer ${newToken}`
|
||||||
} else {
|
response = await fetch(url, { ...options, headers })
|
||||||
logout()
|
} else {
|
||||||
|
// Only logout if refresh failed (real auth error)
|
||||||
|
logout()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return response
|
||||||
|
} catch (err) {
|
||||||
|
// Network errors should not trigger logout
|
||||||
|
// Let the caller handle the error
|
||||||
|
throw err
|
||||||
}
|
}
|
||||||
|
|
||||||
return response
|
|
||||||
}, [refreshToken, logout])
|
}, [refreshToken, logout])
|
||||||
|
|
||||||
const value = {
|
const value = {
|
||||||
|
|||||||
Reference in New Issue
Block a user