import { login as apiLogin, signup as apiSignup } from '@/lib/api-client'; import { createContext, ReactNode, useCallback, useContext, useEffect, useState } from 'react'; export type UserRole = 'admin' | 'user' | 'content_manager' | 'viewer'; export interface User { id: string; email: string; name: string; role: UserRole; permissions?: string[]; // Optional: for future granular permissions from backend } interface AuthContextType { user: User | null; login: (email: string, password: string) => Promise; signup: (email: string, password: string, name: string, role: UserRole) => Promise; logout: () => void; isLoading: boolean; isAuthenticated: boolean; validateToken: () => Promise; refreshUser: () => Promise; } const AuthContext = createContext(undefined); export const useAuth = () => { const context = useContext(AuthContext); if (context === undefined) { throw new Error('useAuth must be used within an AuthProvider'); } return context; }; interface AuthProviderProps { children: ReactNode; } /** * Validate token by making an authenticated API call to the backend * This is the ONLY secure way to validate a JWT token */ async function validateTokenServerSide(token: string): Promise { try { // Make an authenticated request to validate the token // Using a lightweight endpoint to check token validity const isProduction = import.meta.env.PROD; const baseUrl = import.meta.env.VITE_API_BASE_URL || (isProduction ? 'https://api.bugulma.city' : ''); const response = await fetch(`${baseUrl}/api/v1/auth/me`, { method: 'GET', headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json', }, // Add timeout to prevent hanging requests signal: AbortSignal.timeout(5000), }); if (response.ok) { const userData = await response.json(); return { id: userData.id || userData.user_id, email: userData.email, name: userData.name || userData.username || 'User', role: (userData.role || 'user') as UserRole, permissions: userData.permissions, }; } return null; } catch (error) { console.warn('Token validation failed:', error); return null; } } export const AuthProvider = ({ children }: AuthProviderProps) => { const [user, setUser] = useState(null); const [isLoading, setIsLoading] = useState(true); // Secure token validation function const validateToken = useCallback(async (): Promise => { const token = localStorage.getItem('auth_token'); if (!token) { setUser(null); return false; } try { // Validate token server-side (secure approach) const validatedUser = await validateTokenServerSide(token); if (validatedUser) { setUser(validatedUser); return true; } else { // Token invalid, clean up localStorage.removeItem('auth_token'); setUser(null); return false; } } catch (error) { console.warn('Token validation error:', error); // On validation error, assume token is invalid for security localStorage.removeItem('auth_token'); setUser(null); return false; } }, []); useEffect(() => { // Initialize authentication state const initializeAuth = async () => { const token = localStorage.getItem('auth_token'); if (token) { // Validate token server-side instead of trusting client-side decoding await validateToken(); } setIsLoading(false); }; initializeAuth(); }, [validateToken]); const login = async (email: string, password: string) => { setIsLoading(true); try { const data = await apiLogin(email, password); // Validate the returned token immediately for security if (data.token) { const validatedUser = await validateTokenServerSide(data.token); if (validatedUser) { setUser(validatedUser); // Only store token after successful server-side validation localStorage.setItem('auth_token', data.token); } else { throw new Error('Invalid token received from server'); } } else { throw new Error('No token received from server'); } } catch (error) { setUser(null); throw error; } finally { setIsLoading(false); } }; const signup = async (email: string, password: string, name: string, role: UserRole) => { setIsLoading(true); try { const data = await apiSignup(email, password, name, role); // Validate the returned token immediately for security if (data.token) { const validatedUser = await validateTokenServerSide(data.token); if (validatedUser) { setUser(validatedUser); // Only store token after successful server-side validation localStorage.setItem('auth_token', data.token); } else { throw new Error('Invalid token received from server'); } } else { throw new Error('No token received from server'); } } catch (error) { setUser(null); throw error; } finally { setIsLoading(false); } }; const logout = useCallback(() => { setUser(null); localStorage.removeItem('auth_token'); // Clear any other auth-related storage sessionStorage.clear(); }, []); const refreshUser = useCallback(async () => { await validateToken(); }, [validateToken]); const value: AuthContextType = { user, login, signup, logout, isLoading, isAuthenticated: !!user, validateToken, refreshUser, }; return {children}; };