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

This commit is contained in:
Play Life Bot
2026-01-02 16:15:42 +03:00
parent 1cfaaa9506
commit 2326a774ad
2 changed files with 104 additions and 76 deletions

View File

@@ -1 +1 @@
2.7.1 2.7.2

View File

@@ -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 = {