Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions backend/controllers/app_handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
8 changes: 4 additions & 4 deletions backend/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{}{})
Expand Down
5 changes: 5 additions & 0 deletions backend/middleware/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
22 changes: 22 additions & 0 deletions backend/utils/session.go
Original file line number Diff line number Diff line change
@@ -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
}
24 changes: 24 additions & 0 deletions frontend/src/components/LandingPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<div className="overflow-x-hidden">
<Navbar />
Expand Down
25 changes: 23 additions & 2 deletions frontend/src/components/__tests__/LandingPage.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { render, screen } from '@testing-library/react';
import { BrowserRouter } from 'react-router-dom';
import { LandingPage } from '../LandingPage';

// Mock dependencies
Expand Down Expand Up @@ -27,9 +28,25 @@ jest.mock('../../components/utils/ScrollToTop', () => ({
ScrollToTop: () => <div>Mocked ScrollToTop</div>,
}));

// 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(<LandingPage />);
render(
<BrowserRouter>
<LandingPage />
</BrowserRouter>
);

expect(screen.getByText('Mocked Navbar')).toBeInTheDocument();
expect(screen.getByText('Mocked Hero')).toBeInTheDocument();
Expand All @@ -44,7 +61,11 @@ describe('LandingPage', () => {

describe('LandingPage Component using Snapshot', () => {
it('renders landing page correctly', () => {
const { asFragment } = render(<LandingPage />);
const { asFragment } = render(
<BrowserRouter>
<LandingPage />
</BrowserRouter>
);
expect(asFragment()).toMatchSnapshot('landing-page');
});
});
Loading