Skip to content

Commit 68597f5

Browse files
fix: redirect logged-in users from / to /home (#470)
1 parent 0164c0c commit 68597f5

7 files changed

Lines changed: 153 additions & 8 deletions

File tree

backend/main.go

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -82,13 +82,7 @@ func main() {
8282
store := sessions.NewCookieStore(sessionKey)
8383

8484
// Configure secure cookie options
85-
store.Options = &sessions.Options{
86-
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)
91-
}
85+
store.Options = sessionCookieOptions()
9286

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

backend/session_options.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package main
2+
3+
import (
4+
"net/http"
5+
"os"
6+
7+
"github.com/gorilla/sessions"
8+
)
9+
10+
func sessionCookieOptions() *sessions.Options {
11+
return &sessions.Options{
12+
Path: "/",
13+
MaxAge: 86400 * 7, // 7 days
14+
HttpOnly: true, // Prevent JavaScript access
15+
Secure: os.Getenv("ENV") == "production", // HTTPS only in production
16+
SameSite: http.SameSiteLaxMode, // CSRF protection (Lax allows OAuth redirects)
17+
}
18+
}

backend/session_options_test.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package main
2+
3+
import (
4+
"net/http"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
)
9+
10+
func TestSessionCookieOptions_Production(t *testing.T) {
11+
t.Setenv("ENV", "production")
12+
13+
opts := sessionCookieOptions()
14+
15+
assert.Equal(t, "/", opts.Path)
16+
assert.Equal(t, 86400*7, opts.MaxAge)
17+
assert.True(t, opts.HttpOnly)
18+
assert.True(t, opts.Secure)
19+
assert.Equal(t, http.SameSiteLaxMode, opts.SameSite)
20+
}
21+
22+
func TestSessionCookieOptions_NonProduction(t *testing.T) {
23+
t.Setenv("ENV", "development")
24+
25+
opts := sessionCookieOptions()
26+
27+
assert.Equal(t, 86400*7, opts.MaxAge)
28+
assert.True(t, opts.HttpOnly)
29+
assert.False(t, opts.Secure)
30+
}
31+
32+
func TestSessionCookieOptions_UnsetENV(t *testing.T) {
33+
t.Setenv("ENV", "")
34+
35+
opts := sessionCookieOptions()
36+
37+
assert.Equal(t, 86400*7, opts.MaxAge)
38+
assert.False(t, opts.Secure)
39+
}

frontend/src/components/LandingPage.tsx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import { useEffect } from 'react';
2+
import { useNavigate } from 'react-router';
3+
import { url } from '@/components/utils/URLs';
14
import { About } from './LandingComponents/About/About';
25
import { FAQ } from './LandingComponents/FAQ/FAQ';
36
import { Footer } from './LandingComponents/Footer/Footer';
@@ -9,6 +12,26 @@ import { Contact } from './LandingComponents/Contact/Contact';
912
import '../App.css';
1013

1114
export const LandingPage = () => {
15+
const navigate = useNavigate();
16+
17+
useEffect(() => {
18+
const redirectIfLoggedIn = async () => {
19+
try {
20+
const response = await fetch(url.backendURL + 'api/user', {
21+
method: 'GET',
22+
credentials: 'include',
23+
});
24+
if (response.ok) {
25+
navigate('/home');
26+
}
27+
} catch (error) {
28+
console.error('Error checking login status:', error);
29+
}
30+
};
31+
32+
redirectIfLoggedIn();
33+
}, [navigate]);
34+
1235
return (
1336
<div className="overflow-x-hidden">
1437
<Navbar />

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

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
1-
import { render, screen } from '@testing-library/react';
1+
import { render, screen, waitFor } from '@testing-library/react';
22
import { LandingPage } from '../LandingPage';
33

4+
const mockedNavigate = jest.fn();
5+
const consoleErrorSpy = jest
6+
.spyOn(console, 'error')
7+
.mockImplementation(() => {});
8+
49
// Mock dependencies
510
jest.mock('../LandingComponents/Navbar/Navbar', () => ({
611
Navbar: () => <div>Mocked Navbar</div>,
@@ -27,7 +32,28 @@ jest.mock('../../components/utils/ScrollToTop', () => ({
2732
ScrollToTop: () => <div>Mocked ScrollToTop</div>,
2833
}));
2934

35+
jest.mock('react-router', () => ({
36+
useNavigate: () => mockedNavigate,
37+
}));
38+
39+
jest.mock('@/components/utils/URLs', () => ({
40+
url: {
41+
backendURL: 'http://mocked-backend-url/',
42+
},
43+
}));
44+
45+
global.fetch = jest.fn(() =>
46+
Promise.resolve({
47+
ok: false,
48+
})
49+
) as jest.Mock;
50+
3051
describe('LandingPage', () => {
52+
afterEach(() => {
53+
jest.clearAllMocks();
54+
consoleErrorSpy.mockClear();
55+
});
56+
3157
it('renders all components correctly', () => {
3258
render(<LandingPage />);
3359

@@ -40,6 +66,45 @@ describe('LandingPage', () => {
4066
expect(screen.getByText('Mocked Footer')).toBeInTheDocument();
4167
expect(screen.getByText('Mocked ScrollToTop')).toBeInTheDocument();
4268
});
69+
70+
it('redirects to /home when the user is already logged in', async () => {
71+
(fetch as jest.Mock).mockResolvedValueOnce({
72+
ok: true,
73+
});
74+
75+
render(<LandingPage />);
76+
77+
await waitFor(() => {
78+
expect(mockedNavigate).toHaveBeenCalledWith('/home');
79+
});
80+
expect(fetch).toHaveBeenCalledWith('http://mocked-backend-url/api/user', {
81+
method: 'GET',
82+
credentials: 'include',
83+
});
84+
});
85+
86+
it('stays on the landing page when the user is not logged in', async () => {
87+
render(<LandingPage />);
88+
89+
await waitFor(() => {
90+
expect(fetch).toHaveBeenCalledWith('http://mocked-backend-url/api/user', {
91+
method: 'GET',
92+
credentials: 'include',
93+
});
94+
});
95+
expect(mockedNavigate).not.toHaveBeenCalled();
96+
});
97+
98+
it('stays on the landing page when the session check fails', async () => {
99+
(fetch as jest.Mock).mockRejectedValueOnce(new Error('network error'));
100+
101+
render(<LandingPage />);
102+
103+
await waitFor(() => {
104+
expect(consoleErrorSpy).toHaveBeenCalled();
105+
});
106+
expect(mockedNavigate).not.toHaveBeenCalled();
107+
});
43108
});
44109

45110
describe('LandingPage Component using Snapshot', () => {

production/backend-deployment.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ spec:
4040
configMapKeyRef:
4141
key: CONTAINER_ORIGIN
4242
name: backend-env
43+
- name: ENV
44+
valueFrom:
45+
configMapKeyRef:
46+
key: ENV
47+
name: backend-env
4348
- name: FRONTEND_ORIGIN_DEV
4449
valueFrom:
4550
configMapKeyRef:

production/backend-env-configmap.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ data:
33
CLIENT_ID: "YOUR_GOOGLE_CLOUD_AUTH_CLIENT_ID" # Replace this in order to access the frontend
44
CLIENT_SEC: "YOUR_GOOGLE_CLOUD_AUTH_CLIENT_SECRET" # Replace this in order to access the frontend
55
CONTAINER_ORIGIN: http://syncserver:8080/
6+
ENV: "production" # Required for secure HTTPS-only cookies
67
FRONTEND_ORIGIN_DEV: http://localhost
78
PORT: "8000"
89
REDIRECT_URL_DEV: http://localhost:8000/auth/callback

0 commit comments

Comments
 (0)