- Fix CORS configuration to use CORS_ORIGIN env variable - Switch from Redis-based to in-memory rate limiting for stability - Fix frontend authentication error handling for public API - Disable problematic trackingRateLimit middleware - Update environment configuration for production This resolves hanging issues with tracking API and enables frontend forms to work properly on production.
155 lines
3.8 KiB
TypeScript
155 lines
3.8 KiB
TypeScript
/**
|
|
* Authentication Context for Redirect Intelligence v2
|
|
*
|
|
* Manages user authentication state and API interactions
|
|
*/
|
|
|
|
import { createContext, useContext, useEffect, useState, ReactNode } from 'react';
|
|
import { useToast } from '@chakra-ui/react';
|
|
import { authApi, AuthUser, LoginRequest, RegisterRequest } from '../services/api';
|
|
|
|
interface AuthContextType {
|
|
user: AuthUser | null;
|
|
isLoading: boolean;
|
|
isAuthenticated: boolean;
|
|
login: (credentials: LoginRequest) => Promise<void>;
|
|
register: (userData: RegisterRequest) => Promise<void>;
|
|
logout: () => void;
|
|
refreshUser: () => Promise<void>;
|
|
}
|
|
|
|
const AuthContext = createContext<AuthContextType | undefined>(undefined);
|
|
|
|
interface AuthProviderProps {
|
|
children: ReactNode;
|
|
}
|
|
|
|
export function AuthProvider({ children }: AuthProviderProps) {
|
|
const [user, setUser] = useState<AuthUser | null>(null);
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
const toast = useToast();
|
|
|
|
const isAuthenticated = !!user;
|
|
|
|
// Check for existing session on mount
|
|
useEffect(() => {
|
|
checkExistingSession();
|
|
}, []);
|
|
|
|
const checkExistingSession = async () => {
|
|
try {
|
|
// Check if auth endpoints are available first
|
|
const userData = await authApi.getCurrentUser();
|
|
setUser(userData);
|
|
} catch (error: any) {
|
|
// No existing session, session expired, or auth endpoints not available
|
|
console.log('Authentication not available or no existing session:', error.message);
|
|
setUser(null);
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
const login = async (credentials: LoginRequest) => {
|
|
try {
|
|
setIsLoading(true);
|
|
const response = await authApi.login(credentials);
|
|
setUser(response.user);
|
|
|
|
toast({
|
|
title: 'Login successful',
|
|
description: `Welcome back, ${response.user.name}!`,
|
|
status: 'success',
|
|
duration: 3000,
|
|
isClosable: true,
|
|
});
|
|
} catch (error: any) {
|
|
const message = error.response?.data?.message || 'Login failed';
|
|
toast({
|
|
title: 'Login failed',
|
|
description: message,
|
|
status: 'error',
|
|
duration: 5000,
|
|
isClosable: true,
|
|
});
|
|
throw error;
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
const register = async (userData: RegisterRequest) => {
|
|
try {
|
|
setIsLoading(true);
|
|
const response = await authApi.register(userData);
|
|
|
|
toast({
|
|
title: 'Registration successful',
|
|
description: `Welcome, ${response.user.name}! Please log in to continue.`,
|
|
status: 'success',
|
|
duration: 5000,
|
|
isClosable: true,
|
|
});
|
|
} catch (error: any) {
|
|
const message = error.response?.data?.message || 'Registration failed';
|
|
toast({
|
|
title: 'Registration failed',
|
|
description: message,
|
|
status: 'error',
|
|
duration: 5000,
|
|
isClosable: true,
|
|
});
|
|
throw error;
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
const logout = () => {
|
|
authApi.logout();
|
|
setUser(null);
|
|
|
|
toast({
|
|
title: 'Logged out',
|
|
description: 'You have been successfully logged out.',
|
|
status: 'info',
|
|
duration: 3000,
|
|
isClosable: true,
|
|
});
|
|
};
|
|
|
|
const refreshUser = async () => {
|
|
try {
|
|
const userData = await authApi.getCurrentUser();
|
|
setUser(userData);
|
|
} catch (error) {
|
|
// Session expired or invalid
|
|
setUser(null);
|
|
}
|
|
};
|
|
|
|
const value: AuthContextType = {
|
|
user,
|
|
isLoading,
|
|
isAuthenticated,
|
|
login,
|
|
register,
|
|
logout,
|
|
refreshUser,
|
|
};
|
|
|
|
return (
|
|
<AuthContext.Provider value={value}>
|
|
{children}
|
|
</AuthContext.Provider>
|
|
);
|
|
}
|
|
|
|
export function useAuth() {
|
|
const context = useContext(AuthContext);
|
|
if (context === undefined) {
|
|
throw new Error('useAuth must be used within an AuthProvider');
|
|
}
|
|
return context;
|
|
}
|