import { useState, useEffect, useCallback } from 'react' const SERVER_URL = import.meta.env.VITE_SERVER_URL || 'http://localhost:8000' const SESSION_COOKIE_NAME = import.meta.env.VITE_SESSION_COOKIE_NAME || 'session_id' // Cookie utilities const getCookie = (name) => { const value = `; ${document.cookie}` const parts = value.split(`; ${name}=`) if (parts.length === 2) return parts.pop().split(';').shift() return null } const setCookie = (name, value, days = 30) => { const expires = new Date() expires.setTime(expires.getTime() + days * 24 * 60 * 60 * 1000) document.cookie = `${name}=${value};expires=${expires.toUTCString()};path=/;SameSite=Lax` } const deleteCookie = (name) => { document.cookie = `${name}=;expires=Thu, 01 Jan 1970 00:00:01 GMT;path=/` } const getClientId = () => localStorage.getItem('oauth_client_id') export const useAuth = () => { const [isAuthenticated, setIsAuthenticated] = useState(false) const [isLoading, setIsLoading] = useState(true) // Check if user has valid authentication const checkAuth = useCallback(async () => { const accessToken = getCookie('access_token') const sessionId = getCookie(SESSION_COOKIE_NAME) if (!accessToken && !sessionId) { setIsAuthenticated(false) setIsLoading(false) return false } try { // Validate token by making a test request const response = await apiCall('/auth/me') if (response.ok) { setIsAuthenticated(true) setIsLoading(false) return true } else { // Token is invalid, clear it logout() return false } } catch (error) { console.error('Auth check failed:', error) logout() return false } }, []) // Logout function const logout = useCallback(async () => { try { await apiCall('/auth/logout') } catch (error) { console.error('Logout failed:', error) } deleteCookie('access_token') deleteCookie('refresh_token') deleteCookie(SESSION_COOKIE_NAME) setIsAuthenticated(false) }, []) // Refresh access token using refresh token const refreshToken = useCallback(async () => { const refreshToken = getCookie('refresh_token') const clientId = getClientId() if (!refreshToken || !clientId) { logout() return false } try { const response = await apiCall('/token', { method: 'POST', body: { grant_type: 'refresh_token', refresh_token: refreshToken, client_id: clientId, }, }) if (response.ok) { const tokens = await response.json() setCookie('access_token', tokens.access_token, 30) if (tokens.refresh_token) { setCookie('refresh_token', tokens.refresh_token, 30) } return true } else { logout() return false } } catch (error) { console.error('Token refresh failed:', error) logout() return false } }, [logout]) // Make authenticated API calls with automatic token refresh const apiCall = useCallback(async (endpoint, options = {}) => { let accessToken = getCookie('access_token') if (!accessToken) { throw new Error('No access token available') } const defaultHeaders = { 'Authorization': `Bearer ${accessToken}`, 'Content-Type': 'application/json', } const requestOptions = { ...options, headers: { ...defaultHeaders, ...options.headers }, } try { let response = await fetch(`${SERVER_URL}${endpoint}`, requestOptions) // If unauthorized, try refreshing token once if (response.status === 401) { const refreshed = await refreshToken() if (refreshed) { accessToken = getCookie('access_token') requestOptions.headers['Authorization'] = `Bearer ${accessToken}` response = await fetch(`${SERVER_URL}${endpoint}`, requestOptions) } } return response } catch (error) { console.error('API call failed:', error) throw error } }, [refreshToken]) useEffect(() => { checkAuth() }, [checkAuth]) return { isAuthenticated, isLoading, logout, checkAuth, apiCall, refreshToken, } }