From ed66f1133d4fd42853d5889b696cef78af061c1a Mon Sep 17 00:00:00 2001 From: Vasist10 <155972527+Vasist10@users.noreply.github.com> Date: Mon, 9 Feb 2026 00:45:10 +0530 Subject: [PATCH] fix: secure session handling and landing page redirect --- backend/controllers/app_handlers.go | 6 ++--- backend/main.go | 8 +++--- backend/middleware/auth.go | 5 ++++ backend/utils/session.go | 22 ++++++++++++++++ frontend/src/components/LandingPage.tsx | 24 ++++++++++++++++++ .../components/__tests__/LandingPage.test.tsx | 25 +++++++++++++++++-- 6 files changed, 81 insertions(+), 9 deletions(-) create mode 100644 backend/utils/session.go diff --git a/backend/controllers/app_handlers.go b/backend/controllers/app_handlers.go index 61ac915d..7216e3c6 100644 --- a/backend/controllers/app_handlers.go +++ b/backend/controllers/app_handlers.go @@ -52,7 +52,7 @@ func (a *App) OAuthHandler(w http.ResponseWriter, r *http.Request) { // Store state in session for validation in callback session, _ := a.SessionStore.Get(r, "session-name") session.Values["oauth_state"] = state - if err := session.Save(r, w); err != nil { + if err := utils.SaveSessionWithSecureCookie(session, r, w); err != nil { utils.Logger.Errorf("Failed to save OAuth state to session: %v", err) http.Error(w, "Internal server error", http.StatusInternalServerError) return @@ -125,7 +125,7 @@ func (a *App) OAuthCallbackHandler(w http.ResponseWriter, r *http.Request) { userInfo["uuid"] = uuidStr userInfo["encryption_secret"] = encryptionSecret session.Values["user"] = userInfo - if err := session.Save(r, w); err != nil { + if err := utils.SaveSessionWithSecureCookie(session, r, w); err != nil { utils.Logger.Errorf("Failed to save session: %v", err) http.Error(w, "Session error", http.StatusInternalServerError) return @@ -221,7 +221,7 @@ func (a *App) EnableCORS(handler http.Handler) http.Handler { func (a *App) LogoutHandler(w http.ResponseWriter, r *http.Request) { session, _ := a.SessionStore.Get(r, "session-name") session.Options.MaxAge = -1 - if err := session.Save(r, w); err != nil { + if err := utils.SaveSessionWithSecureCookie(session, r, w); err != nil { utils.Logger.Errorf("Failed to clear session on logout: %v", err) http.Error(w, "Logout failed", http.StatusInternalServerError) return diff --git a/backend/main.go b/backend/main.go index e76e0c1f..d27e4b78 100644 --- a/backend/main.go +++ b/backend/main.go @@ -84,10 +84,10 @@ func main() { // Configure secure cookie options store.Options = &sessions.Options{ Path: "/", - MaxAge: 86400 * 7, // 7 days - HttpOnly: true, // Prevent JavaScript access - Secure: os.Getenv("ENV") == "production", // HTTPS only in production - SameSite: http.SameSiteLaxMode, // CSRF protection (Lax allows OAuth redirects) + MaxAge: 86400 * 30, // 30 days + HttpOnly: true, // Prevent JavaScript access + Secure: false, // Handled dynamically by SaveSessionWithSecureCookie / IsSecure + SameSite: http.SameSiteLaxMode, // CSRF protection (Lax allows OAuth redirects) } gob.Register(map[string]interface{}{}) diff --git a/backend/middleware/auth.go b/backend/middleware/auth.go index 8349fe69..681f0f6d 100644 --- a/backend/middleware/auth.go +++ b/backend/middleware/auth.go @@ -51,6 +51,11 @@ func AuthMiddleware(store *sessions.CookieStore) func(http.Handler) http.Handler return } + // Inject session credentials into headers for GET requests + r.Header.Set("X-User-Email", sessionEmail) + r.Header.Set("X-User-UUID", sessionUUID) + r.Header.Set("X-Encryption-Secret", sessionSecret) + // For POST requests with JSON body, inject session credentials if r.Method == http.MethodPost && r.Body != nil { // Read the body diff --git a/backend/utils/session.go b/backend/utils/session.go new file mode 100644 index 00000000..ae2958f2 --- /dev/null +++ b/backend/utils/session.go @@ -0,0 +1,22 @@ +package utils + +import ( + "net/http" + + "github.com/gorilla/sessions" +) + +func IsSecure(r *http.Request) bool { + if r.TLS != nil { + return true + } + return r.Header.Get("X-Forwarded-Proto") == "https" +} + +func SaveSessionWithSecureCookie(session *sessions.Session, r *http.Request, w http.ResponseWriter) error { + original := session.Options.Secure + session.Options.Secure = IsSecure(r) + err := session.Save(r, w) + session.Options.Secure = original + return err +} diff --git a/frontend/src/components/LandingPage.tsx b/frontend/src/components/LandingPage.tsx index e176c8bc..4ca0e2d0 100644 --- a/frontend/src/components/LandingPage.tsx +++ b/frontend/src/components/LandingPage.tsx @@ -8,7 +8,31 @@ import { ScrollToTop } from '../components/utils/ScrollToTop'; import { Contact } from './LandingComponents/Contact/Contact'; import '../App.css'; +import { useEffect } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { url } from './utils/URLs'; + export const LandingPage = () => { + const navigate = useNavigate(); + + useEffect(() => { + const checkLoginStatus = async () => { + try { + const response = await fetch(url.backendURL + 'api/user', { + method: 'GET', + credentials: 'include', + }); + if (response.ok) { + navigate('/home'); + } + } catch (error) { + console.error('Error checking login status:', error); + } + }; + + checkLoginStatus(); + }, [navigate]); + return (
diff --git a/frontend/src/components/__tests__/LandingPage.test.tsx b/frontend/src/components/__tests__/LandingPage.test.tsx index 79880182..b3234751 100644 --- a/frontend/src/components/__tests__/LandingPage.test.tsx +++ b/frontend/src/components/__tests__/LandingPage.test.tsx @@ -1,4 +1,5 @@ import { render, screen } from '@testing-library/react'; +import { BrowserRouter } from 'react-router-dom'; import { LandingPage } from '../LandingPage'; // Mock dependencies @@ -27,9 +28,25 @@ jest.mock('../../components/utils/ScrollToTop', () => ({ ScrollToTop: () =>
Mocked ScrollToTop
, })); +// Mock fetch for auth check +global.fetch = jest.fn(() => + Promise.resolve({ + ok: false, + status: 401, + } as Response) +); + describe('LandingPage', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + it('renders all components correctly', () => { - render(); + render( + + + + ); expect(screen.getByText('Mocked Navbar')).toBeInTheDocument(); expect(screen.getByText('Mocked Hero')).toBeInTheDocument(); @@ -44,7 +61,11 @@ describe('LandingPage', () => { describe('LandingPage Component using Snapshot', () => { it('renders landing page correctly', () => { - const { asFragment } = render(); + const { asFragment } = render( + + + + ); expect(asFragment()).toMatchSnapshot('landing-page'); }); });