From 776c31d5c646e2d605fd4563044186770a87a5a8 Mon Sep 17 00:00:00 2001 From: Tanish Solanki Date: Tue, 26 May 2026 08:55:08 +0530 Subject: [PATCH] fix: preserve auth sessions with axios credentials --- backend/validators/authValidator.js | 2 +- src/components/__test__/Navbar.test.tsx | 21 ++++++++++----------- src/main.tsx | 4 ++++ src/pages/Login/Login.tsx | 4 +++- src/pages/Signup/Signup.tsx | 15 ++++++++------- 5 files changed, 26 insertions(+), 20 deletions(-) diff --git a/backend/validators/authValidator.js b/backend/validators/authValidator.js index 4fd9fac6..79928e22 100644 --- a/backend/validators/authValidator.js +++ b/backend/validators/authValidator.js @@ -18,7 +18,7 @@ const signupSchema = z.object({ .min(8, "Password must be at least 8 characters long") .max(100, "Password must be at most 100 characters long") .regex( - /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}+$/, + /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/, 'Password must contain uppercase, lowercase, number, and special character' ), }); diff --git a/src/components/__test__/Navbar.test.tsx b/src/components/__test__/Navbar.test.tsx index 780b9bb3..5aef36fc 100644 --- a/src/components/__test__/Navbar.test.tsx +++ b/src/components/__test__/Navbar.test.tsx @@ -51,31 +51,30 @@ describe('Navbar', () => { // --- Mobile menu --- it('mobile menu is hidden by default', () => { renderNavbar() - expect(screen.queryByText('About')).not.toBeInTheDocument() + expect(screen.getAllByRole('link', { name: /^tracker$/i })).toHaveLength(1) }) it('opens mobile menu when hamburger is clicked', () => { renderNavbar() - const hamburger = screen.getAllByRole('button')[1] // second button = hamburger + const hamburger = screen.getByRole('button', { name: /toggle menu/i }) fireEvent.click(hamburger) - expect(screen.getByText('About')).toBeInTheDocument() - expect(screen.getByText('Contact')).toBeInTheDocument() + expect(screen.getAllByRole('link', { name: /^tracker$/i })).toHaveLength(2) }) it('closes mobile menu when a nav link is clicked', () => { renderNavbar() - const hamburger = screen.getAllByRole('button')[1] + const hamburger = screen.getByRole('button', { name: /toggle menu/i }) fireEvent.click(hamburger) // open - const homeLinks = screen.getAllByRole('link', { name: /home/i }) - fireEvent.click(homeLinks[homeLinks.length - 1]) // click the mobile one - expect(screen.queryByText('About')).not.toBeInTheDocument() // closed + const trackerLinks = screen.getAllByRole('link', { name: /^tracker$/i }) + fireEvent.click(trackerLinks[trackerLinks.length - 1]) // click the mobile one + expect(screen.getAllByRole('link', { name: /^tracker$/i })).toHaveLength(1) // closed }) it('calls toggleTheme from the mobile menu button', () => { const { toggleTheme } = renderNavbar('dark') - const hamburger = screen.getAllByRole('button')[1] - fireEvent.click(hamburger) - fireEvent.click(screen.getByText(/light/i)) + // The mobile theme button is the first button inside md:hidden (which is button at index 1 of all buttons) + const mobileThemeBtn = screen.getAllByRole('button', { name: /toggle theme/i })[1] + fireEvent.click(mobileThemeBtn) expect(toggleTheme).toHaveBeenCalledTimes(1) }) diff --git a/src/main.tsx b/src/main.tsx index 4c5b79dd..23e1511c 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -4,6 +4,10 @@ import App from "./App.tsx"; import "./index.css"; import { BrowserRouter } from "react-router-dom"; import ThemeWrapper from "./context/ThemeContext.tsx"; +import axios from "axios"; + +axios.defaults.withCredentials = true; + createRoot(document.getElementById("root")!).render( diff --git a/src/pages/Login/Login.tsx b/src/pages/Login/Login.tsx index 92b7073e..7d1acbfe 100644 --- a/src/pages/Login/Login.tsx +++ b/src/pages/Login/Login.tsx @@ -30,7 +30,9 @@ const Login: React.FC = () => { setIsLoading(true); try { - const response = await axios.post(`${backendUrl}/api/auth/login`, formData); + const response = await axios.post(`${backendUrl}/api/auth/login`, formData, { + withCredentials: true, + }); setMessage(response.data.message); if (response.data.message === 'Login successful') { diff --git a/src/pages/Signup/Signup.tsx b/src/pages/Signup/Signup.tsx index d862756b..e104d042 100644 --- a/src/pages/Signup/Signup.tsx +++ b/src/pages/Signup/Signup.tsx @@ -53,8 +53,8 @@ const SignUp: React.FC = () => { if (name === "password") { if (!value.trim()) { errorMessage = "Password is required"; - } else if (!/^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d@$!%*#?&]{8,}$/.test(value)) { - errorMessage = "Password must be 8+ characters with letters and numbers"; + } else if (!/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/.test(value)) { + errorMessage = "Password must contain uppercase, lowercase, number, and special character"; } } setErrors((prev) => ({ ...prev, [name]: errorMessage })); @@ -64,8 +64,8 @@ const SignUp: React.FC = () => { e.preventDefault(); const usernameError = !formData.username.trim() ? "Username is required" - : !/^[A-Za-z\s]+$/.test(formData.username) - ? "Only letters are allowed" + : !/^[a-zA-Z0-9_]+$/.test(formData.username) + ? "Username can only contain letters, numbers, and underscores" : ""; const emailError = !formData.email.trim() ? "Email is required" @@ -74,8 +74,8 @@ const SignUp: React.FC = () => { : ""; const passwordError = !formData.password.trim() ? "Password is required" - : !/^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d@$!%*#?&]{8,}$/.test(formData.password) - ? "Password must be 8+ characters with letters and numbers" + : !/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/.test(formData.password) + ? "Password must contain uppercase, lowercase, number, and special character" : ""; if (usernameError || emailError || passwordError) { setErrors({ username: usernameError, email: emailError, password: passwordError }); @@ -84,7 +84,8 @@ const SignUp: React.FC = () => { setIsLoading(true); try { const response = await axios.post(`${backendUrl}/api/auth/signup`, - formData // Include cookies for session + formData, + { withCredentials: true } ); setMessage(response.data.message); // Show success message from backend