/** * Authentication Hooks */ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { login, logout, getCurrentUser, masquerade, stopMasquerade, LoginCredentials, User, MasqueradeStackEntry } from '../api/auth'; import { getCookie, setCookie, deleteCookie } from '../utils/cookies'; /** * Hook to get current user */ export const useCurrentUser = () => { return useQuery({ queryKey: ['currentUser'], queryFn: async () => { // Check if token exists before making request (from cookie) const token = getCookie('access_token'); if (!token) { return null; // No token, return null instead of making request } try { return await getCurrentUser(); } catch (error) { // If getCurrentUser fails (e.g., 401), return null // The API client interceptor will handle token refresh console.error('Failed to get current user:', error); return null; } }, retry: 1, // Retry once in case of token refresh staleTime: 5 * 60 * 1000, // 5 minutes refetchOnMount: true, // Always refetch when component mounts refetchOnWindowFocus: false, }); }; /** * Hook to login */ export const useLogin = () => { const queryClient = useQueryClient(); return useMutation({ mutationFn: login, onSuccess: (data) => { // Store tokens in cookies (domain=.lvh.me for cross-subdomain access) setCookie('access_token', data.access, 7); setCookie('refresh_token', data.refresh, 7); // Set user in cache queryClient.setQueryData(['currentUser'], data.user); }, }); }; /** * Hook to logout */ export const useLogout = () => { const queryClient = useQueryClient(); return useMutation({ mutationFn: logout, onSuccess: () => { // Clear tokens (from cookies) deleteCookie('access_token'); deleteCookie('refresh_token'); // Clear user cache queryClient.removeQueries({ queryKey: ['currentUser'] }); queryClient.clear(); // Redirect to login page window.location.href = '/login'; }, }); }; /** * Check if user is authenticated */ export const useIsAuthenticated = (): boolean => { const { data: user, isLoading } = useCurrentUser(); return !isLoading && !!user; }; /** * Hook to masquerade as another user */ export const useMasquerade = () => { const queryClient = useQueryClient(); return useMutation({ mutationFn: async (username: string) => { // Get current masquerading stack from localStorage const stackJson = localStorage.getItem('masquerade_stack'); const currentStack: MasqueradeStackEntry[] = stackJson ? JSON.parse(stackJson) : []; // Call masquerade API with current stack return masquerade(username, currentStack); }, onSuccess: async (data) => { // Store the updated masquerading stack if (data.masquerade_stack) { localStorage.setItem('masquerade_stack', JSON.stringify(data.masquerade_stack)); } const user = data.user; const currentHostname = window.location.hostname; const currentPort = window.location.port; let targetSubdomain: string | null = null; if (['superuser', 'platform_manager', 'platform_support'].includes(user.role)) { targetSubdomain = 'platform'; } else if (user.business_subdomain) { targetSubdomain = user.business_subdomain; } const needsRedirect = targetSubdomain && currentHostname !== `${targetSubdomain}.lvh.me`; if (needsRedirect) { // CRITICAL: Clear the session cookie BEFORE redirect // Call logout API to clear HttpOnly sessionid cookie try { await fetch('http://api.lvh.me:8000/api/auth/logout/', { method: 'POST', credentials: 'include', }); } catch (e) { // Continue anyway } const portStr = currentPort ? `:${currentPort}` : ''; // Pass tokens AND masquerading stack in URL (for cross-domain transfer) const stackEncoded = encodeURIComponent(JSON.stringify(data.masquerade_stack || [])); const redirectUrl = `http://${targetSubdomain}.lvh.me${portStr}/?access_token=${data.access}&refresh_token=${data.refresh}&masquerade_stack=${stackEncoded}`; window.location.href = redirectUrl; return; } // If no redirect needed (same subdomain), we can just set cookies and reload setCookie('access_token', data.access, 7); setCookie('refresh_token', data.refresh, 7); queryClient.setQueryData(['currentUser'], data.user); window.location.reload(); }, }); }; /** * Hook to stop masquerading and return to previous user */ export const useStopMasquerade = () => { const queryClient = useQueryClient(); return useMutation({ mutationFn: async () => { // Get current masquerading stack from localStorage const stackJson = localStorage.getItem('masquerade_stack'); const currentStack: MasqueradeStackEntry[] = stackJson ? JSON.parse(stackJson) : []; if (currentStack.length === 0) { throw new Error('No masquerading session to stop'); } // Call stop_masquerade API with current stack return stopMasquerade(currentStack); }, onSuccess: async (data) => { // Update the masquerading stack if (data.masquerade_stack && data.masquerade_stack.length > 0) { localStorage.setItem('masquerade_stack', JSON.stringify(data.masquerade_stack)); } else { // Clear the stack if empty localStorage.removeItem('masquerade_stack'); } const user = data.user; const currentHostname = window.location.hostname; const currentPort = window.location.port; let targetSubdomain: string | null = null; if (['superuser', 'platform_manager', 'platform_support'].includes(user.role)) { targetSubdomain = 'platform'; } else if (user.business_subdomain) { targetSubdomain = user.business_subdomain; } const needsRedirect = targetSubdomain && currentHostname !== `${targetSubdomain}.lvh.me`; if (needsRedirect) { // CRITICAL: Clear the session cookie BEFORE redirect try { await fetch('http://api.lvh.me:8000/api/auth/logout/', { method: 'POST', credentials: 'include', }); } catch (e) { // Continue anyway } const portStr = currentPort ? `:${currentPort}` : ''; // Pass tokens AND masquerading stack in URL (for cross-domain transfer) const stackEncoded = encodeURIComponent(JSON.stringify(data.masquerade_stack || [])); const redirectUrl = `http://${targetSubdomain}.lvh.me${portStr}/?access_token=${data.access}&refresh_token=${data.refresh}&masquerade_stack=${stackEncoded}`; window.location.href = redirectUrl; return; } // If no redirect needed (same subdomain), we can just set cookies and reload setCookie('access_token', data.access, 7); setCookie('refresh_token', data.refresh, 7); queryClient.setQueryData(['currentUser'], data.user); window.location.reload(); }, }); };