From bf539c6e911858457aedbe2ef001d7d8dd8f7157 Mon Sep 17 00:00:00 2001 From: Play Life Bot Date: Fri, 2 Jan 2026 16:19:54 +0300 Subject: [PATCH] Fix: Improve auth persistence on container restart - distinguish network errors from auth errors (v2.7.3) --- VERSION | 2 +- .../src/components/auth/AuthContext.jsx | 70 ++++++++++++++----- 2 files changed, 55 insertions(+), 17 deletions(-) diff --git a/VERSION b/VERSION index 37c2961..2c9b4ef 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.7.2 +2.7.3 diff --git a/play-life-web/src/components/auth/AuthContext.jsx b/play-life-web/src/components/auth/AuthContext.jsx index f43af7d..c1c847f 100644 --- a/play-life-web/src/components/auth/AuthContext.jsx +++ b/play-life-web/src/components/auth/AuthContext.jsx @@ -38,20 +38,29 @@ export function AuthProvider({ children }) { const refresh = localStorage.getItem(REFRESH_TOKEN_KEY) if (!refresh) { - return false + return { success: false, isNetworkError: false } } try { + const controller = new AbortController() + const timeoutId = setTimeout(() => controller.abort(), 5000) // 5 second timeout + const response = await fetch('/api/auth/refresh', { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ refresh_token: refresh }) + body: JSON.stringify({ refresh_token: refresh }), + signal: controller.signal }) + clearTimeout(timeoutId) + if (!response.ok) { - return false + // 401 means invalid token (real auth error) + // Other errors might be temporary (503, 502, etc.) + const isAuthError = response.status === 401 + return { success: false, isNetworkError: !isAuthError } } const data = await response.json() @@ -61,10 +70,17 @@ export function AuthProvider({ children }) { localStorage.setItem(USER_KEY, JSON.stringify(data.user)) setUser(data.user) - return true + return { success: true, isNetworkError: false } } catch (err) { + // Network errors should be treated as temporary + if (err.name === 'AbortError' || + (err.name === 'TypeError' && (err.message.includes('fetch') || err.message.includes('Failed to fetch')))) { + console.warn('Refresh token network error, keeping session:', err.message) + return { success: false, isNetworkError: true } + } + // Other errors might be auth related console.error('Refresh token error:', err) - return false + return { success: false, isNetworkError: false } } }, []) @@ -74,9 +90,14 @@ export function AuthProvider({ children }) { const token = localStorage.getItem(TOKEN_KEY) const savedUser = localStorage.getItem(USER_KEY) + console.log('[Auth] Initializing auth, token exists:', !!token, 'user exists:', !!savedUser) + if (token && savedUser) { try { - setUser(JSON.parse(savedUser)) + const parsedUser = JSON.parse(savedUser) + setUser(parsedUser) // Set user immediately from localStorage + console.log('[Auth] User restored from localStorage:', parsedUser.email) + // Verify token is still valid with timeout const controller = new AbortController() const timeoutId = setTimeout(() => controller.abort(), 5000) // 5 second timeout @@ -95,32 +116,48 @@ export function AuthProvider({ children }) { const data = await response.json() setUser(data.user) localStorage.setItem(USER_KEY, JSON.stringify(data.user)) + console.log('[Auth] Token verified successfully') } else if (response.status === 401) { // Try to refresh token - const refreshed = await refreshToken() - if (!refreshed) { + console.log('[Auth] Access token expired, attempting refresh...') + const result = await refreshToken() + if (!result.success && !result.isNetworkError) { + // Only logout on real auth errors, not network errors + console.warn('[Auth] Refresh failed with auth error, logging out') logout() + } else if (!result.success) { + // Network error - keep session, backend might be starting up + console.warn('[Auth] Token refresh failed due to network error, keeping session. User remains logged in.') + // User is already set from localStorage above, so they stay logged in + } else { + console.log('[Auth] Token refreshed successfully') } } 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') + console.warn('[Auth] Auth check failed with status:', response.status, 'but keeping session. User remains logged in.') + // User is already set from localStorage above, so they stay logged in } } catch (err) { // Network errors (e.g., backend not ready) should not clear auth // 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.') + console.warn('[Auth] Auth check timeout, backend might be starting up. Keeping session. User remains logged in.') + // User is already set from localStorage above, so they stay logged in } 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) + console.warn('[Auth] Network error during auth check, keeping session:', err.message, 'User remains logged in.') + // User is already set from localStorage above, so they stay logged in } else { // Other errors - might be auth related - console.error('Auth init error:', err) + console.error('[Auth] Auth init error:', err) // Don't automatically logout on unknown errors + // User is already set from localStorage above, so they stay logged in } } + } else { + console.log('[Auth] No saved auth data found') } setLoading(false) } @@ -208,15 +245,16 @@ export function AuthProvider({ children }) { // If 401, try to refresh token and retry if (response.status === 401) { - const refreshed = await refreshToken() - if (refreshed) { + const result = await refreshToken() + if (result.success) { const newToken = localStorage.getItem(TOKEN_KEY) headers['Authorization'] = `Bearer ${newToken}` response = await fetch(url, { ...options, headers }) - } else { - // Only logout if refresh failed (real auth error) + } else if (!result.isNetworkError) { + // Only logout if refresh failed due to auth error (not network error) logout() } + // If network error, don't logout - let the caller handle the 401 } return response