Skip to content

Commit ed66f11

Browse files
committed
fix: secure session handling and landing page redirect
1 parent c5b0bb8 commit ed66f11

6 files changed

Lines changed: 81 additions & 9 deletions

File tree

backend/controllers/app_handlers.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ func (a *App) OAuthHandler(w http.ResponseWriter, r *http.Request) {
5252
// Store state in session for validation in callback
5353
session, _ := a.SessionStore.Get(r, "session-name")
5454
session.Values["oauth_state"] = state
55-
if err := session.Save(r, w); err != nil {
55+
if err := utils.SaveSessionWithSecureCookie(session, r, w); err != nil {
5656
utils.Logger.Errorf("Failed to save OAuth state to session: %v", err)
5757
http.Error(w, "Internal server error", http.StatusInternalServerError)
5858
return
@@ -125,7 +125,7 @@ func (a *App) OAuthCallbackHandler(w http.ResponseWriter, r *http.Request) {
125125
userInfo["uuid"] = uuidStr
126126
userInfo["encryption_secret"] = encryptionSecret
127127
session.Values["user"] = userInfo
128-
if err := session.Save(r, w); err != nil {
128+
if err := utils.SaveSessionWithSecureCookie(session, r, w); err != nil {
129129
utils.Logger.Errorf("Failed to save session: %v", err)
130130
http.Error(w, "Session error", http.StatusInternalServerError)
131131
return
@@ -221,7 +221,7 @@ func (a *App) EnableCORS(handler http.Handler) http.Handler {
221221
func (a *App) LogoutHandler(w http.ResponseWriter, r *http.Request) {
222222
session, _ := a.SessionStore.Get(r, "session-name")
223223
session.Options.MaxAge = -1
224-
if err := session.Save(r, w); err != nil {
224+
if err := utils.SaveSessionWithSecureCookie(session, r, w); err != nil {
225225
utils.Logger.Errorf("Failed to clear session on logout: %v", err)
226226
http.Error(w, "Logout failed", http.StatusInternalServerError)
227227
return

backend/main.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,10 @@ func main() {
8484
// Configure secure cookie options
8585
store.Options = &sessions.Options{
8686
Path: "/",
87-
MaxAge: 86400 * 7, // 7 days
88-
HttpOnly: true, // Prevent JavaScript access
89-
Secure: os.Getenv("ENV") == "production", // HTTPS only in production
90-
SameSite: http.SameSiteLaxMode, // CSRF protection (Lax allows OAuth redirects)
87+
MaxAge: 86400 * 30, // 30 days
88+
HttpOnly: true, // Prevent JavaScript access
89+
Secure: false, // Handled dynamically by SaveSessionWithSecureCookie / IsSecure
90+
SameSite: http.SameSiteLaxMode, // CSRF protection (Lax allows OAuth redirects)
9191
}
9292

9393
gob.Register(map[string]interface{}{})

backend/middleware/auth.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,11 @@ func AuthMiddleware(store *sessions.CookieStore) func(http.Handler) http.Handler
5151
return
5252
}
5353

54+
// Inject session credentials into headers for GET requests
55+
r.Header.Set("X-User-Email", sessionEmail)
56+
r.Header.Set("X-User-UUID", sessionUUID)
57+
r.Header.Set("X-Encryption-Secret", sessionSecret)
58+
5459
// For POST requests with JSON body, inject session credentials
5560
if r.Method == http.MethodPost && r.Body != nil {
5661
// Read the body

backend/utils/session.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package utils
2+
3+
import (
4+
"net/http"
5+
6+
"github.com/gorilla/sessions"
7+
)
8+
9+
func IsSecure(r *http.Request) bool {
10+
if r.TLS != nil {
11+
return true
12+
}
13+
return r.Header.Get("X-Forwarded-Proto") == "https"
14+
}
15+
16+
func SaveSessionWithSecureCookie(session *sessions.Session, r *http.Request, w http.ResponseWriter) error {
17+
original := session.Options.Secure
18+
session.Options.Secure = IsSecure(r)
19+
err := session.Save(r, w)
20+
session.Options.Secure = original
21+
return err
22+
}

frontend/src/components/LandingPage.tsx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,31 @@ import { ScrollToTop } from '../components/utils/ScrollToTop';
88
import { Contact } from './LandingComponents/Contact/Contact';
99
import '../App.css';
1010

11+
import { useEffect } from 'react';
12+
import { useNavigate } from 'react-router-dom';
13+
import { url } from './utils/URLs';
14+
1115
export const LandingPage = () => {
16+
const navigate = useNavigate();
17+
18+
useEffect(() => {
19+
const checkLoginStatus = async () => {
20+
try {
21+
const response = await fetch(url.backendURL + 'api/user', {
22+
method: 'GET',
23+
credentials: 'include',
24+
});
25+
if (response.ok) {
26+
navigate('/home');
27+
}
28+
} catch (error) {
29+
console.error('Error checking login status:', error);
30+
}
31+
};
32+
33+
checkLoginStatus();
34+
}, [navigate]);
35+
1236
return (
1337
<div className="overflow-x-hidden">
1438
<Navbar />

frontend/src/components/__tests__/LandingPage.test.tsx

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { render, screen } from '@testing-library/react';
2+
import { BrowserRouter } from 'react-router-dom';
23
import { LandingPage } from '../LandingPage';
34

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

31+
// Mock fetch for auth check
32+
global.fetch = jest.fn(() =>
33+
Promise.resolve({
34+
ok: false,
35+
status: 401,
36+
} as Response)
37+
);
38+
3039
describe('LandingPage', () => {
40+
beforeEach(() => {
41+
jest.clearAllMocks();
42+
});
43+
3144
it('renders all components correctly', () => {
32-
render(<LandingPage />);
45+
render(
46+
<BrowserRouter>
47+
<LandingPage />
48+
</BrowserRouter>
49+
);
3350

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

4562
describe('LandingPage Component using Snapshot', () => {
4663
it('renders landing page correctly', () => {
47-
const { asFragment } = render(<LandingPage />);
64+
const { asFragment } = render(
65+
<BrowserRouter>
66+
<LandingPage />
67+
</BrowserRouter>
68+
);
4869
expect(asFragment()).toMatchSnapshot('landing-page');
4970
});
5071
});

0 commit comments

Comments
 (0)