From 9c0f93e1536ec1d5ff9d6051ea9b087b78ffd331 Mon Sep 17 00:00:00 2001 From: Shantanu675 Date: Thu, 22 Jan 2026 21:26:35 +0530 Subject: [PATCH 1/3] Fix duplicate coach routes and ignore Go SDK archive --- backend/.gitignore | 1 + frontend/src/App.tsx | 5 ----- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/backend/.gitignore b/backend/.gitignore index aa3aef9..c6be491 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -1,2 +1,3 @@ .env config.prod.yml +*.tar.gz \ No newline at end of file diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 2cbe4b1..b80de2a 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -82,11 +82,6 @@ function AppRoutes() { path='coach/strengthen-argument' element={} /> - } /> - } - />{' '} {/* Add this route */} } /> From a65494d848023a304a50dd63627a1546e4900a35 Mon Sep 17 00:00:00 2001 From: Shantanu675 Date: Mon, 26 Jan 2026 20:32:55 +0530 Subject: [PATCH 2/3] fix: ensure auth endpoints always return JSON response - Fix /signup endpoint to return JSON on validation errors - Fix /login endpoint to return JSON on password mismatch - Add consistent error response format - Resolves #244 --- backend/controllers/auth.go | 51 +- frontend/src/Pages/Authentication/forms.tsx | 4 +- frontend/src/context/authContext.tsx | 659 +++++++++++++++----- 3 files changed, 524 insertions(+), 190 deletions(-) diff --git a/backend/controllers/auth.go b/backend/controllers/auth.go index c404fbf..052816e 100644 --- a/backend/controllers/auth.go +++ b/backend/controllers/auth.go @@ -133,6 +133,10 @@ func SignUp(ctx *gin.Context) { return } + // normalize email FIRST + request.Email = strings.ToLower(strings.TrimSpace(request.Email)) + + // Check if user already exists dbCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() @@ -212,6 +216,10 @@ func VerifyEmail(ctx *gin.Context) { return } + // normalize email FIRST + request.Email = strings.ToLower(strings.TrimSpace(request.Email)) + + // Find user with matching email and verification code dbCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() @@ -274,47 +282,53 @@ func Login(ctx *gin.Context) { var request structs.LoginRequest if err := ctx.ShouldBindJSON(&request); err != nil { - ctx.JSON(http.StatusBadRequest, gin.H{"error": "Invalid input", "message": "Check email and password format"}) + ctx.JSON(http.StatusBadRequest, gin.H{ + "error": "Invalid input", + "message": "Check email and password format", + }) return } - // Find user in MongoDB + request.Email = strings.ToLower(strings.TrimSpace(request.Email)) + dbCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() + var user models.User - err := db.MongoDatabase.Collection("users").FindOne(dbCtx, bson.M{"email": request.Email}).Decode(&user) + err := db.MongoDatabase. + Collection("users"). + FindOne(dbCtx, bson.M{"email": request.Email}). + Decode(&user) + if err != nil { ctx.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid email or password"}) return } - // Normalize stats if needed - if normalizeUserStats(&user) { - if err := persistUserStats(dbCtx, &user); err != nil { - } + // 🔐 Verify password FIRST + if err := bcrypt.CompareHashAndPassword( + []byte(user.Password), + []byte(request.Password), + ); err != nil { + ctx.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid email or password"}) + return } - // Check if user is verified + // 📧 Check verification AFTER password if !user.IsVerified { ctx.JSON(http.StatusUnauthorized, gin.H{"error": "Email not verified"}) return } - // Verify password - err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(request.Password)) - if err != nil { - ctx.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid email or password"}) - return - } - - // Generate JWT token, err := generateJWT(user.Email, cfg.JWT.Secret, cfg.JWT.Expiry) if err != nil { - ctx.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to generate token", "message": err.Error()}) + ctx.JSON(http.StatusInternalServerError, gin.H{ + "error": "Failed to generate token", + "message": err.Error(), + }) return } - // Return user details ctx.JSON(http.StatusOK, gin.H{ "message": "Sign-in successful", "accessToken": token, @@ -322,6 +336,7 @@ func Login(ctx *gin.Context) { }) } + func normalizeUserStats(user *models.User) bool { updated := false if math.IsNaN(user.Rating) || math.IsInf(user.Rating, 0) { diff --git a/frontend/src/Pages/Authentication/forms.tsx b/frontend/src/Pages/Authentication/forms.tsx index 221257f..a8e404d 100644 --- a/frontend/src/Pages/Authentication/forms.tsx +++ b/frontend/src/Pages/Authentication/forms.tsx @@ -62,8 +62,7 @@ const handleGoogleLogin = useCallback( google.accounts.id.renderButton(buttonElement, { theme: 'outline', size: 'large', - text: 'signin_with', - width: '100%', + text: 'signin_with' }); } @@ -71,6 +70,7 @@ const handleGoogleLogin = useCallback( google.accounts.id.cancel(); }; }, [handleGoogleLogin]); + return (
diff --git a/frontend/src/context/authContext.tsx b/frontend/src/context/authContext.tsx index 46f51ab..9eedaa2 100644 --- a/frontend/src/context/authContext.tsx +++ b/frontend/src/context/authContext.tsx @@ -1,3 +1,365 @@ +// import { +// createContext, +// useState, +// useEffect, +// useCallback, +// ReactNode, +// } from 'react'; +// import { useNavigate } from 'react-router-dom'; +// import { useSetAtom } from 'jotai'; +// import { userAtom } from '@/state/userAtom'; +// import type { User } from '@/types/user'; + +// const baseURL = import.meta.env.VITE_BASE_URL; +// const USER_CACHE_KEY = 'userProfile'; + +// interface AuthContextType { +// token: string | null; +// isAuthenticated: boolean; +// loading: boolean; +// error: string | null; +// handleError: (error: string) => void; +// login: (email: string, password: string) => Promise; +// logout: () => void; +// signup: (email: string, password: string) => Promise; +// verifyEmail: (email: string, code: string) => Promise; +// forgotPassword: (email: string) => Promise; +// confirmForgotPassword: ( +// email: string, +// code: string, +// newPassword: string +// ) => Promise; +// googleLogin: (idToken: string) => Promise; +// } + +// export const AuthContext = createContext( +// undefined +// ); + +// export const AuthProvider = ({ children }: { children: ReactNode }) => { +// const [token, setToken] = useState( +// localStorage.getItem('token') +// ); +// const [loading, setLoading] = useState(false); +// const [error, setError] = useState(null); +// const navigate = useNavigate(); +// const setUser = useSetAtom(userAtom); + +// const handleError = (error: unknown) => { +// const message = +// error instanceof Error ? error.message : 'An unexpected error occurred'; +// setError(message); +// throw error; +// }; + +// const verifyToken = useCallback(async () => { +// const storedToken = localStorage.getItem('token'); +// if (!storedToken) return; +// try { +// const response = await fetch(`${baseURL}/verifyToken`, { +// method: 'POST', +// headers: { Authorization: `Bearer ${storedToken}` }, +// }); + +// if (!response.ok) { +// // Token is expired or invalid - clear it and redirect to login +// localStorage.removeItem('token'); +// setToken(null); +// setUser(null); +// navigate('/login'); +// return; +// } +// setToken(storedToken); + +// // Fetch user data to populate userAtom +// const userResponse = await fetch(`${baseURL}/user/fetchprofile`, { +// method: 'GET', +// headers: { Authorization: `Bearer ${storedToken}` }, +// }); + +// if (userResponse.ok) { +// const userData = await userResponse.json(); +// const normalizedUser: User = { +// id: userData.id || userData._id, +// email: userData.email, +// displayName: userData.displayName || 'User', +// bio: userData.bio || '', +// rating: userData.rating || 1500, +// rd: userData.rd || 350, +// volatility: userData.volatility || 0.06, +// lastRatingUpdate: +// userData.lastRatingUpdate || new Date().toISOString(), +// avatarUrl: +// userData.avatarUrl || 'https://avatar.iran.liara.run/public/10', +// twitter: userData.twitter, +// instagram: userData.instagram, +// linkedin: userData.linkedin, +// password: '', +// nickname: userData.nickname || 'User', +// isVerified: userData.isVerified || false, +// verificationCode: userData.verificationCode, +// resetPasswordCode: userData.resetPasswordCode, +// createdAt: userData.createdAt || new Date().toISOString(), +// updatedAt: userData.updatedAt || new Date().toISOString(), +// }; +// setUser(normalizedUser); +// localStorage.setItem(USER_CACHE_KEY, JSON.stringify(normalizedUser)); +// } +// } catch (error) { +// console.log('error', error); +// logout(); +// } +// }, [setUser]); + +// useEffect(() => { +// verifyToken(); +// }, [verifyToken]); + +// const login = async (email: string, password: string) => { +// setLoading(true); +// try { +// const response = await fetch(`${baseURL}/login`, { +// method: 'POST', +// headers: { 'Content-Type': 'application/json' }, +// body: JSON.stringify({ email, password }), +// }); + +// const data = await response.json(); +// if (!response.ok) throw new Error(data.message || 'Login failed'); + +// setToken(data.accessToken); +// localStorage.setItem('token', data.accessToken); +// // Set user details in userAtom based on the new User type +// const normalizedUser: User = { +// id: data.user?.id || data.user?._id || undefined, +// email: data.user?.email || email, +// displayName: data.user?.displayName || 'User', +// bio: data.user?.bio || '', +// rating: data.user?.rating || 1500, +// rd: data.user?.rd || 350, // Default Glicko-2 RD value +// volatility: data.user?.volatility || 0.06, // Default Glicko-2 volatility +// lastRatingUpdate: +// data.user?.lastRatingUpdate || new Date().toISOString(), +// avatarUrl: +// data.user?.avatarUrl || 'https://avatar.iran.liara.run/public/10', +// twitter: data.user?.twitter || undefined, +// instagram: data.user?.instagram || undefined, +// linkedin: data.user?.linkedin || undefined, +// password: '', // Password should not be stored in client-side state +// nickname: data.user?.nickname || 'User', +// isVerified: data.user?.isVerified || false, +// verificationCode: data.user?.verificationCode || undefined, +// resetPasswordCode: data.user?.resetPasswordCode || undefined, +// createdAt: data.user?.createdAt || new Date().toISOString(), +// updatedAt: data.user?.updatedAt || new Date().toISOString(), +// }; +// setUser(normalizedUser); +// localStorage.setItem(USER_CACHE_KEY, JSON.stringify(normalizedUser)); +// navigate('/'); +// } catch (error) { +// handleError(error); +// } finally { +// setLoading(false); +// } +// }; + +// const signup = async (email: string, password: string) => { +// setLoading(true); +// try { +// const response = await fetch(`${baseURL}/signup`, { +// method: 'POST', +// headers: { 'Content-Type': 'application/json' }, +// body: JSON.stringify({ email, password }), +// }); + +// if (!response.ok) { +// const data = await response.json(); +// throw new Error(data.message || 'Signup failed'); +// } +// } catch (error) { +// handleError(error); +// } finally { +// setLoading(false); +// } +// }; + +// const verifyEmail = async (email: string, code: string) => { +// setLoading(true); +// try { +// const response = await fetch(`${baseURL}/verifyEmail`, { +// method: 'POST', +// headers: { 'Content-Type': 'application/json' }, +// body: JSON.stringify({ email, confirmationCode: code }), +// }); + +// if (!response.ok) { +// const data = await response.json(); +// throw new Error(data.error || 'Verification failed'); +// } + +// const data = await response.json(); + +// // User is now verified and logged in +// if (data.accessToken) { +// setToken(data.accessToken); +// localStorage.setItem('token', data.accessToken); + +// // Set user details +// const normalizedUser: User = { +// id: data.user?.id || data.user?._id || undefined, +// email: data.user?.email || email, +// displayName: data.user?.displayName || 'User', +// bio: data.user?.bio || '', +// rating: data.user?.rating || 1200, +// rd: data.user?.rd || 350, +// volatility: data.user?.volatility || 0.06, +// lastRatingUpdate: data.user?.lastRatingUpdate || new Date().toISOString(), +// avatarUrl: data.user?.avatarUrl || 'https://avatar.iran.liara.run/public/10', +// twitter: data.user?.twitter || undefined, +// instagram: data.user?.instagram || undefined, +// linkedin: data.user?.linkedin || undefined, +// password: '', +// nickname: data.user?.nickname || 'User', +// isVerified: true, +// verificationCode: undefined, +// resetPasswordCode: undefined, +// createdAt: data.user?.createdAt || new Date().toISOString(), +// updatedAt: data.user?.updatedAt || new Date().toISOString(), +// }; +// setUser(normalizedUser); +// localStorage.setItem(USER_CACHE_KEY, JSON.stringify(normalizedUser)); +// navigate('/'); +// } +// } catch (error) { +// handleError(error); +// } finally { +// setLoading(false); +// } +// }; + +// const forgotPassword = async (email: string) => { +// setLoading(true); +// try { +// const response = await fetch(`${baseURL}/forgotPassword`, { +// method: 'POST', +// headers: { 'Content-Type': 'application/json' }, +// body: JSON.stringify({ email }), +// }); + +// if (!response.ok) { +// const data = await response.json(); +// throw new Error(data.message || 'Password reset failed'); +// } +// } catch (error) { +// handleError(error); +// } finally { +// setLoading(false); +// } +// }; + +// const confirmForgotPassword = async ( +// email: string, +// code: string, +// newPassword: string +// ) => { +// setLoading(true); +// try { +// const response = await fetch(`${baseURL}/confirmForgotPassword`, { +// method: 'POST', +// headers: { 'Content-Type': 'application/json' }, +// body: JSON.stringify({ email, code, newPassword }), +// }); + +// if (!response.ok) { +// const data = await response.json(); +// throw new Error(data.message || 'Password update failed'); +// } +// } catch (error) { +// handleError(error); +// } finally { +// setLoading(false); +// } +// }; + +// const googleLogin = async (idToken: string) => { +// setLoading(true); +// try { +// const response = await fetch(`${baseURL}/googleLogin`, { +// method: 'POST', +// headers: { 'Content-Type': 'application/json' }, +// body: JSON.stringify({ idToken }), +// }); + +// const data = await response.json(); +// if (!response.ok) throw new Error(data.message || 'Google login failed'); + +// setToken(data.accessToken); +// localStorage.setItem('token', data.accessToken); +// // Set user details in userAtom based on the new User type +// const normalizedUser: User = { +// id: data.user?.id || data.user?._id || undefined, +// email: data.user?.email || 'googleuser@example.com', +// displayName: data.user?.displayName || 'Google User', +// bio: data.user?.bio || '', +// rating: data.user?.rating || 1500, +// rd: data.user?.rd || 350, +// volatility: data.user?.volatility || 0.06, +// lastRatingUpdate: +// data.user?.lastRatingUpdate || new Date().toISOString(), +// avatarUrl: +// data.user?.avatarUrl || 'https://avatar.iran.liara.run/public/10', +// twitter: data.user?.twitter || undefined, +// instagram: data.user?.instagram || undefined, +// linkedin: data.user?.linkedin || undefined, +// password: '', +// nickname: data.user?.nickname || 'Google User', +// isVerified: data.user?.isVerified || true, // Google login often implies verified +// verificationCode: data.user?.verificationCode || undefined, +// resetPasswordCode: data.user?.resetPasswordCode || undefined, +// createdAt: data.user?.createdAt || new Date().toISOString(), +// updatedAt: data.user?.updatedAt || new Date().toISOString(), +// }; +// setUser(normalizedUser); +// localStorage.setItem(USER_CACHE_KEY, JSON.stringify(normalizedUser)); +// console.log('User after Google login:', data.user); +// navigate('/'); +// } catch (error) { +// handleError(error); +// } finally { +// setLoading(false); +// } +// }; + +// const logout = () => { +// setToken(null); +// localStorage.removeItem('token'); +// localStorage.removeItem(USER_CACHE_KEY); +// setUser(null); // Clear userAtom on logout +// navigate('/auth'); +// }; + +// return ( +// +// {children} +// +// ); +// }; + import { createContext, useState, @@ -13,12 +375,14 @@ import type { User } from '@/types/user'; const baseURL = import.meta.env.VITE_BASE_URL; const USER_CACHE_KEY = 'userProfile'; +/* ---------- Types ---------- */ + interface AuthContextType { token: string | null; isAuthenticated: boolean; loading: boolean; error: string | null; - handleError: (error: string) => void; + handleError: (error: unknown) => never; login: (email: string, password: string) => Promise; logout: () => void; signup: (email: string, password: string) => Promise; @@ -36,128 +400,126 @@ export const AuthContext = createContext( undefined ); +/* ---------- Helpers ---------- */ + +const safeJson = async (response: Response) => { + const contentType = response.headers.get('content-type'); + if (contentType?.includes('application/json')) { + return await response.json(); + } + return null; +}; + +const normalizeUser = (data: any, fallbackEmail?: string): User => ({ + id: data?.id || data?._id, + email: data?.email || fallbackEmail || '', + displayName: data?.displayName || 'User', + bio: data?.bio || '', + rating: data?.rating || 1500, + rd: data?.rd || 350, + volatility: data?.volatility || 0.06, + lastRatingUpdate: data?.lastRatingUpdate || new Date().toISOString(), + avatarUrl: + data?.avatarUrl || 'https://avatar.iran.liara.run/public/10', + twitter: data?.twitter, + instagram: data?.instagram, + linkedin: data?.linkedin, + password: '', + nickname: data?.nickname || 'User', + isVerified: data?.isVerified || false, + verificationCode: data?.verificationCode, + resetPasswordCode: data?.resetPasswordCode, + createdAt: data?.createdAt || new Date().toISOString(), + updatedAt: data?.updatedAt || new Date().toISOString(), +}); + +/* ---------- Provider ---------- */ + export const AuthProvider = ({ children }: { children: ReactNode }) => { const [token, setToken] = useState( localStorage.getItem('token') ); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); + const navigate = useNavigate(); const setUser = useSetAtom(userAtom); - const handleError = (error: unknown) => { + const handleError = (err: unknown): never => { const message = - error instanceof Error ? error.message : 'An unexpected error occurred'; + err instanceof Error ? err.message : 'Something went wrong'; setError(message); - throw error; + throw err; }; + /* ---------- Token Verification ---------- */ + const verifyToken = useCallback(async () => { const storedToken = localStorage.getItem('token'); if (!storedToken) return; + try { - const response = await fetch(`${baseURL}/verifyToken`, { + const res = await fetch(`${baseURL}/verifyToken`, { method: 'POST', headers: { Authorization: `Bearer ${storedToken}` }, }); - if (!response.ok) { - // Token is expired or invalid - clear it and redirect to login - localStorage.removeItem('token'); - setToken(null); - setUser(null); - navigate('/login'); - return; - } - setToken(storedToken); + if (!res.ok) throw new Error('Session expired'); - // Fetch user data to populate userAtom - const userResponse = await fetch(`${baseURL}/user/fetchprofile`, { - method: 'GET', + const userRes = await fetch(`${baseURL}/user/fetchprofile`, { headers: { Authorization: `Bearer ${storedToken}` }, }); - if (userResponse.ok) { - const userData = await userResponse.json(); - const normalizedUser: User = { - id: userData.id || userData._id, - email: userData.email, - displayName: userData.displayName || 'User', - bio: userData.bio || '', - rating: userData.rating || 1500, - rd: userData.rd || 350, - volatility: userData.volatility || 0.06, - lastRatingUpdate: - userData.lastRatingUpdate || new Date().toISOString(), - avatarUrl: - userData.avatarUrl || 'https://avatar.iran.liara.run/public/10', - twitter: userData.twitter, - instagram: userData.instagram, - linkedin: userData.linkedin, - password: '', - nickname: userData.nickname || 'User', - isVerified: userData.isVerified || false, - verificationCode: userData.verificationCode, - resetPasswordCode: userData.resetPasswordCode, - createdAt: userData.createdAt || new Date().toISOString(), - updatedAt: userData.updatedAt || new Date().toISOString(), - }; - setUser(normalizedUser); - localStorage.setItem(USER_CACHE_KEY, JSON.stringify(normalizedUser)); + const userData = await safeJson(userRes); + if (userData) { + const user = normalizeUser(userData); + setUser(user); + setToken(storedToken); + localStorage.setItem(USER_CACHE_KEY, JSON.stringify(user)); } - } catch (error) { - console.log('error', error); + } catch { logout(); } - }, [setUser]); + }, []); useEffect(() => { verifyToken(); }, [verifyToken]); + /* ---------- Auth Actions ---------- */ + const login = async (email: string, password: string) => { setLoading(true); try { - const response = await fetch(`${baseURL}/login`, { + const res = await fetch(`${baseURL}/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, password }), }); - const data = await response.json(); - if (!response.ok) throw new Error(data.message || 'Login failed'); + const data = await safeJson(res); + // if (!res.ok) throw new Error(data?.message || 'Login failed'); + if (!res.ok) { + let message = 'Invalid email or password'; + + try { + const data = JSON.parse(await res.text()); + message = data.message || data.error || message; + } catch {} + + throw new Error(message); + } + setToken(data.accessToken); localStorage.setItem('token', data.accessToken); - // Set user details in userAtom based on the new User type - const normalizedUser: User = { - id: data.user?.id || data.user?._id || undefined, - email: data.user?.email || email, - displayName: data.user?.displayName || 'User', - bio: data.user?.bio || '', - rating: data.user?.rating || 1500, - rd: data.user?.rd || 350, // Default Glicko-2 RD value - volatility: data.user?.volatility || 0.06, // Default Glicko-2 volatility - lastRatingUpdate: - data.user?.lastRatingUpdate || new Date().toISOString(), - avatarUrl: - data.user?.avatarUrl || 'https://avatar.iran.liara.run/public/10', - twitter: data.user?.twitter || undefined, - instagram: data.user?.instagram || undefined, - linkedin: data.user?.linkedin || undefined, - password: '', // Password should not be stored in client-side state - nickname: data.user?.nickname || 'User', - isVerified: data.user?.isVerified || false, - verificationCode: data.user?.verificationCode || undefined, - resetPasswordCode: data.user?.resetPasswordCode || undefined, - createdAt: data.user?.createdAt || new Date().toISOString(), - updatedAt: data.user?.updatedAt || new Date().toISOString(), - }; - setUser(normalizedUser); - localStorage.setItem(USER_CACHE_KEY, JSON.stringify(normalizedUser)); + + const user = normalizeUser(data.user, email); + setUser(user); + localStorage.setItem(USER_CACHE_KEY, JSON.stringify(user)); navigate('/'); - } catch (error) { - handleError(error); + } catch (e) { + handleError(e); } finally { setLoading(false); } @@ -166,18 +528,26 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => { const signup = async (email: string, password: string) => { setLoading(true); try { - const response = await fetch(`${baseURL}/signup`, { + const res = await fetch(`${baseURL}/signup`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, password }), }); - if (!response.ok) { - const data = await response.json(); - throw new Error(data.message || 'Signup failed'); - } - } catch (error) { - handleError(error); + if (!res.ok) { + let message = 'Signup failed'; + + try { + const data = JSON.parse(await res.text()); + message = data.message || data.error || message; + } catch { + // response was not JSON + } + + throw new Error(message); + } + } catch (e) { + handleError(e); } finally { setLoading(false); } @@ -186,52 +556,24 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => { const verifyEmail = async (email: string, code: string) => { setLoading(true); try { - const response = await fetch(`${baseURL}/verifyEmail`, { + const res = await fetch(`${baseURL}/verifyEmail`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, confirmationCode: code }), }); - if (!response.ok) { - const data = await response.json(); - throw new Error(data.error || 'Verification failed'); - } + const data = await safeJson(res); + if (!res.ok) throw new Error(data?.error || 'Verification failed'); - const data = await response.json(); - - // User is now verified and logged in - if (data.accessToken) { - setToken(data.accessToken); - localStorage.setItem('token', data.accessToken); - - // Set user details - const normalizedUser: User = { - id: data.user?.id || data.user?._id || undefined, - email: data.user?.email || email, - displayName: data.user?.displayName || 'User', - bio: data.user?.bio || '', - rating: data.user?.rating || 1200, - rd: data.user?.rd || 350, - volatility: data.user?.volatility || 0.06, - lastRatingUpdate: data.user?.lastRatingUpdate || new Date().toISOString(), - avatarUrl: data.user?.avatarUrl || 'https://avatar.iran.liara.run/public/10', - twitter: data.user?.twitter || undefined, - instagram: data.user?.instagram || undefined, - linkedin: data.user?.linkedin || undefined, - password: '', - nickname: data.user?.nickname || 'User', - isVerified: true, - verificationCode: undefined, - resetPasswordCode: undefined, - createdAt: data.user?.createdAt || new Date().toISOString(), - updatedAt: data.user?.updatedAt || new Date().toISOString(), - }; - setUser(normalizedUser); - localStorage.setItem(USER_CACHE_KEY, JSON.stringify(normalizedUser)); - navigate('/'); - } - } catch (error) { - handleError(error); + setToken(data.accessToken); + localStorage.setItem('token', data.accessToken); + + const user = normalizeUser(data.user, email); + setUser(user); + localStorage.setItem(USER_CACHE_KEY, JSON.stringify(user)); + navigate('/'); + } catch (e) { + handleError(e); } finally { setLoading(false); } @@ -240,18 +582,18 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => { const forgotPassword = async (email: string) => { setLoading(true); try { - const response = await fetch(`${baseURL}/forgotPassword`, { + const res = await fetch(`${baseURL}/forgotPassword`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email }), }); - if (!response.ok) { - const data = await response.json(); - throw new Error(data.message || 'Password reset failed'); + if (!res.ok) { + const text = await res.text(); + throw new Error(text || 'Password reset failed'); } - } catch (error) { - handleError(error); + } catch (e) { + handleError(e); } finally { setLoading(false); } @@ -264,18 +606,18 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => { ) => { setLoading(true); try { - const response = await fetch(`${baseURL}/confirmForgotPassword`, { + const res = await fetch(`${baseURL}/confirmForgotPassword`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, code, newPassword }), }); - if (!response.ok) { - const data = await response.json(); - throw new Error(data.message || 'Password update failed'); + if (!res.ok) { + const text = await res.text(); + throw new Error(text || 'Password update failed'); } - } catch (error) { - handleError(error); + } catch (e) { + handleError(e); } finally { setLoading(false); } @@ -284,47 +626,24 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => { const googleLogin = async (idToken: string) => { setLoading(true); try { - const response = await fetch(`${baseURL}/googleLogin`, { + const res = await fetch(`${baseURL}/googleLogin`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ idToken }), }); - const data = await response.json(); - if (!response.ok) throw new Error(data.message || 'Google login failed'); + const data = await safeJson(res); + if (!res.ok) throw new Error(data?.message || 'Google login failed'); setToken(data.accessToken); localStorage.setItem('token', data.accessToken); - // Set user details in userAtom based on the new User type - const normalizedUser: User = { - id: data.user?.id || data.user?._id || undefined, - email: data.user?.email || 'googleuser@example.com', - displayName: data.user?.displayName || 'Google User', - bio: data.user?.bio || '', - rating: data.user?.rating || 1500, - rd: data.user?.rd || 350, - volatility: data.user?.volatility || 0.06, - lastRatingUpdate: - data.user?.lastRatingUpdate || new Date().toISOString(), - avatarUrl: - data.user?.avatarUrl || 'https://avatar.iran.liara.run/public/10', - twitter: data.user?.twitter || undefined, - instagram: data.user?.instagram || undefined, - linkedin: data.user?.linkedin || undefined, - password: '', - nickname: data.user?.nickname || 'Google User', - isVerified: data.user?.isVerified || true, // Google login often implies verified - verificationCode: data.user?.verificationCode || undefined, - resetPasswordCode: data.user?.resetPasswordCode || undefined, - createdAt: data.user?.createdAt || new Date().toISOString(), - updatedAt: data.user?.updatedAt || new Date().toISOString(), - }; - setUser(normalizedUser); - localStorage.setItem(USER_CACHE_KEY, JSON.stringify(normalizedUser)); - console.log('User after Google login:', data.user); + + const user = normalizeUser(data.user); + setUser(user); + localStorage.setItem(USER_CACHE_KEY, JSON.stringify(user)); navigate('/'); - } catch (error) { - handleError(error); + } catch (e) { + handleError(e); } finally { setLoading(false); } @@ -332,9 +651,9 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => { const logout = () => { setToken(null); + setUser(null); localStorage.removeItem('token'); localStorage.removeItem(USER_CACHE_KEY); - setUser(null); // Clear userAtom on logout navigate('/auth'); }; From 9878b616f3de10480333c0f5383357f25770634d Mon Sep 17 00:00:00 2001 From: Shantanu675 Date: Mon, 26 Jan 2026 20:37:22 +0530 Subject: [PATCH 3/3] fix: ensure auth endpoints always return JSON response - Fix /signup endpoint to return JSON on validation errors - Fix /login endpoint to return JSON on password mismatch - Add consistent error response format - Resolves #244 --- frontend/src/context/authContext.tsx | 361 --------------------------- 1 file changed, 361 deletions(-) diff --git a/frontend/src/context/authContext.tsx b/frontend/src/context/authContext.tsx index 9eedaa2..b1baab2 100644 --- a/frontend/src/context/authContext.tsx +++ b/frontend/src/context/authContext.tsx @@ -1,364 +1,3 @@ -// import { -// createContext, -// useState, -// useEffect, -// useCallback, -// ReactNode, -// } from 'react'; -// import { useNavigate } from 'react-router-dom'; -// import { useSetAtom } from 'jotai'; -// import { userAtom } from '@/state/userAtom'; -// import type { User } from '@/types/user'; - -// const baseURL = import.meta.env.VITE_BASE_URL; -// const USER_CACHE_KEY = 'userProfile'; - -// interface AuthContextType { -// token: string | null; -// isAuthenticated: boolean; -// loading: boolean; -// error: string | null; -// handleError: (error: string) => void; -// login: (email: string, password: string) => Promise; -// logout: () => void; -// signup: (email: string, password: string) => Promise; -// verifyEmail: (email: string, code: string) => Promise; -// forgotPassword: (email: string) => Promise; -// confirmForgotPassword: ( -// email: string, -// code: string, -// newPassword: string -// ) => Promise; -// googleLogin: (idToken: string) => Promise; -// } - -// export const AuthContext = createContext( -// undefined -// ); - -// export const AuthProvider = ({ children }: { children: ReactNode }) => { -// const [token, setToken] = useState( -// localStorage.getItem('token') -// ); -// const [loading, setLoading] = useState(false); -// const [error, setError] = useState(null); -// const navigate = useNavigate(); -// const setUser = useSetAtom(userAtom); - -// const handleError = (error: unknown) => { -// const message = -// error instanceof Error ? error.message : 'An unexpected error occurred'; -// setError(message); -// throw error; -// }; - -// const verifyToken = useCallback(async () => { -// const storedToken = localStorage.getItem('token'); -// if (!storedToken) return; -// try { -// const response = await fetch(`${baseURL}/verifyToken`, { -// method: 'POST', -// headers: { Authorization: `Bearer ${storedToken}` }, -// }); - -// if (!response.ok) { -// // Token is expired or invalid - clear it and redirect to login -// localStorage.removeItem('token'); -// setToken(null); -// setUser(null); -// navigate('/login'); -// return; -// } -// setToken(storedToken); - -// // Fetch user data to populate userAtom -// const userResponse = await fetch(`${baseURL}/user/fetchprofile`, { -// method: 'GET', -// headers: { Authorization: `Bearer ${storedToken}` }, -// }); - -// if (userResponse.ok) { -// const userData = await userResponse.json(); -// const normalizedUser: User = { -// id: userData.id || userData._id, -// email: userData.email, -// displayName: userData.displayName || 'User', -// bio: userData.bio || '', -// rating: userData.rating || 1500, -// rd: userData.rd || 350, -// volatility: userData.volatility || 0.06, -// lastRatingUpdate: -// userData.lastRatingUpdate || new Date().toISOString(), -// avatarUrl: -// userData.avatarUrl || 'https://avatar.iran.liara.run/public/10', -// twitter: userData.twitter, -// instagram: userData.instagram, -// linkedin: userData.linkedin, -// password: '', -// nickname: userData.nickname || 'User', -// isVerified: userData.isVerified || false, -// verificationCode: userData.verificationCode, -// resetPasswordCode: userData.resetPasswordCode, -// createdAt: userData.createdAt || new Date().toISOString(), -// updatedAt: userData.updatedAt || new Date().toISOString(), -// }; -// setUser(normalizedUser); -// localStorage.setItem(USER_CACHE_KEY, JSON.stringify(normalizedUser)); -// } -// } catch (error) { -// console.log('error', error); -// logout(); -// } -// }, [setUser]); - -// useEffect(() => { -// verifyToken(); -// }, [verifyToken]); - -// const login = async (email: string, password: string) => { -// setLoading(true); -// try { -// const response = await fetch(`${baseURL}/login`, { -// method: 'POST', -// headers: { 'Content-Type': 'application/json' }, -// body: JSON.stringify({ email, password }), -// }); - -// const data = await response.json(); -// if (!response.ok) throw new Error(data.message || 'Login failed'); - -// setToken(data.accessToken); -// localStorage.setItem('token', data.accessToken); -// // Set user details in userAtom based on the new User type -// const normalizedUser: User = { -// id: data.user?.id || data.user?._id || undefined, -// email: data.user?.email || email, -// displayName: data.user?.displayName || 'User', -// bio: data.user?.bio || '', -// rating: data.user?.rating || 1500, -// rd: data.user?.rd || 350, // Default Glicko-2 RD value -// volatility: data.user?.volatility || 0.06, // Default Glicko-2 volatility -// lastRatingUpdate: -// data.user?.lastRatingUpdate || new Date().toISOString(), -// avatarUrl: -// data.user?.avatarUrl || 'https://avatar.iran.liara.run/public/10', -// twitter: data.user?.twitter || undefined, -// instagram: data.user?.instagram || undefined, -// linkedin: data.user?.linkedin || undefined, -// password: '', // Password should not be stored in client-side state -// nickname: data.user?.nickname || 'User', -// isVerified: data.user?.isVerified || false, -// verificationCode: data.user?.verificationCode || undefined, -// resetPasswordCode: data.user?.resetPasswordCode || undefined, -// createdAt: data.user?.createdAt || new Date().toISOString(), -// updatedAt: data.user?.updatedAt || new Date().toISOString(), -// }; -// setUser(normalizedUser); -// localStorage.setItem(USER_CACHE_KEY, JSON.stringify(normalizedUser)); -// navigate('/'); -// } catch (error) { -// handleError(error); -// } finally { -// setLoading(false); -// } -// }; - -// const signup = async (email: string, password: string) => { -// setLoading(true); -// try { -// const response = await fetch(`${baseURL}/signup`, { -// method: 'POST', -// headers: { 'Content-Type': 'application/json' }, -// body: JSON.stringify({ email, password }), -// }); - -// if (!response.ok) { -// const data = await response.json(); -// throw new Error(data.message || 'Signup failed'); -// } -// } catch (error) { -// handleError(error); -// } finally { -// setLoading(false); -// } -// }; - -// const verifyEmail = async (email: string, code: string) => { -// setLoading(true); -// try { -// const response = await fetch(`${baseURL}/verifyEmail`, { -// method: 'POST', -// headers: { 'Content-Type': 'application/json' }, -// body: JSON.stringify({ email, confirmationCode: code }), -// }); - -// if (!response.ok) { -// const data = await response.json(); -// throw new Error(data.error || 'Verification failed'); -// } - -// const data = await response.json(); - -// // User is now verified and logged in -// if (data.accessToken) { -// setToken(data.accessToken); -// localStorage.setItem('token', data.accessToken); - -// // Set user details -// const normalizedUser: User = { -// id: data.user?.id || data.user?._id || undefined, -// email: data.user?.email || email, -// displayName: data.user?.displayName || 'User', -// bio: data.user?.bio || '', -// rating: data.user?.rating || 1200, -// rd: data.user?.rd || 350, -// volatility: data.user?.volatility || 0.06, -// lastRatingUpdate: data.user?.lastRatingUpdate || new Date().toISOString(), -// avatarUrl: data.user?.avatarUrl || 'https://avatar.iran.liara.run/public/10', -// twitter: data.user?.twitter || undefined, -// instagram: data.user?.instagram || undefined, -// linkedin: data.user?.linkedin || undefined, -// password: '', -// nickname: data.user?.nickname || 'User', -// isVerified: true, -// verificationCode: undefined, -// resetPasswordCode: undefined, -// createdAt: data.user?.createdAt || new Date().toISOString(), -// updatedAt: data.user?.updatedAt || new Date().toISOString(), -// }; -// setUser(normalizedUser); -// localStorage.setItem(USER_CACHE_KEY, JSON.stringify(normalizedUser)); -// navigate('/'); -// } -// } catch (error) { -// handleError(error); -// } finally { -// setLoading(false); -// } -// }; - -// const forgotPassword = async (email: string) => { -// setLoading(true); -// try { -// const response = await fetch(`${baseURL}/forgotPassword`, { -// method: 'POST', -// headers: { 'Content-Type': 'application/json' }, -// body: JSON.stringify({ email }), -// }); - -// if (!response.ok) { -// const data = await response.json(); -// throw new Error(data.message || 'Password reset failed'); -// } -// } catch (error) { -// handleError(error); -// } finally { -// setLoading(false); -// } -// }; - -// const confirmForgotPassword = async ( -// email: string, -// code: string, -// newPassword: string -// ) => { -// setLoading(true); -// try { -// const response = await fetch(`${baseURL}/confirmForgotPassword`, { -// method: 'POST', -// headers: { 'Content-Type': 'application/json' }, -// body: JSON.stringify({ email, code, newPassword }), -// }); - -// if (!response.ok) { -// const data = await response.json(); -// throw new Error(data.message || 'Password update failed'); -// } -// } catch (error) { -// handleError(error); -// } finally { -// setLoading(false); -// } -// }; - -// const googleLogin = async (idToken: string) => { -// setLoading(true); -// try { -// const response = await fetch(`${baseURL}/googleLogin`, { -// method: 'POST', -// headers: { 'Content-Type': 'application/json' }, -// body: JSON.stringify({ idToken }), -// }); - -// const data = await response.json(); -// if (!response.ok) throw new Error(data.message || 'Google login failed'); - -// setToken(data.accessToken); -// localStorage.setItem('token', data.accessToken); -// // Set user details in userAtom based on the new User type -// const normalizedUser: User = { -// id: data.user?.id || data.user?._id || undefined, -// email: data.user?.email || 'googleuser@example.com', -// displayName: data.user?.displayName || 'Google User', -// bio: data.user?.bio || '', -// rating: data.user?.rating || 1500, -// rd: data.user?.rd || 350, -// volatility: data.user?.volatility || 0.06, -// lastRatingUpdate: -// data.user?.lastRatingUpdate || new Date().toISOString(), -// avatarUrl: -// data.user?.avatarUrl || 'https://avatar.iran.liara.run/public/10', -// twitter: data.user?.twitter || undefined, -// instagram: data.user?.instagram || undefined, -// linkedin: data.user?.linkedin || undefined, -// password: '', -// nickname: data.user?.nickname || 'Google User', -// isVerified: data.user?.isVerified || true, // Google login often implies verified -// verificationCode: data.user?.verificationCode || undefined, -// resetPasswordCode: data.user?.resetPasswordCode || undefined, -// createdAt: data.user?.createdAt || new Date().toISOString(), -// updatedAt: data.user?.updatedAt || new Date().toISOString(), -// }; -// setUser(normalizedUser); -// localStorage.setItem(USER_CACHE_KEY, JSON.stringify(normalizedUser)); -// console.log('User after Google login:', data.user); -// navigate('/'); -// } catch (error) { -// handleError(error); -// } finally { -// setLoading(false); -// } -// }; - -// const logout = () => { -// setToken(null); -// localStorage.removeItem('token'); -// localStorage.removeItem(USER_CACHE_KEY); -// setUser(null); // Clear userAtom on logout -// navigate('/auth'); -// }; - -// return ( -// -// {children} -// -// ); -// }; import { createContext,