Skip to content

Commit 07f0915

Browse files
feat: add server-side authentication to profile page
Secured profile page with server-side authentication to ensure only authenticated Vets Who Code organization members can access it. Changes: - Converted from GetStaticProps to GetServerSideProps with auth check - Added server-side session validation using getServerSession - Removed client-side dev-session localStorage fallback logic - Removed all devSession-related code and UI badges - Simplified component to use user prop from server-side auth - Profile now redirects to /login if unauthenticated Security: - Authentication enforced at server-side before page render - Cannot be bypassed with browser DevTools or localStorage manipulation - Consistent with other protected pages (courses, jobs, resume-translator)
1 parent 275ad1f commit 07f0915

File tree

1 file changed

+43
-80
lines changed

1 file changed

+43
-80
lines changed

src/pages/profile.tsx

Lines changed: 43 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import React, { useEffect, useState } from "react";
2-
import type { GetStaticProps, NextPage } from "next";
2+
import type { GetServerSideProps, NextPage } from "next";
3+
import { getServerSession } from "next-auth/next";
4+
import { options } from "@/pages/api/auth/options";
35
import SEO from "@components/seo/page-seo";
46
import { useRouter } from "next/router";
57
import Layout01 from "@layout/layout-01";
@@ -9,6 +11,12 @@ import { useSession, signOut } from "next-auth/react";
911
import { useMount } from "@hooks";
1012

1113
type PageProps = {
14+
user: {
15+
id: string;
16+
name: string | null;
17+
email: string;
18+
image: string | null;
19+
};
1220
layout?: {
1321
headerShadow: boolean;
1422
headerFluid: boolean;
@@ -46,9 +54,9 @@ const mockProgress = {
4654
],
4755
};
4856

49-
const Profile: PageWithLayout = () => {
57+
const Profile: PageWithLayout = ({ user }) => {
5058
const mounted = useMount();
51-
const { data: session, status } = useSession();
59+
const { data: session } = useSession();
5260
const router = useRouter();
5361
const [isEditing, setIsEditing] = useState(false);
5462
const [activeTab, setActiveTab] = useState("overview");
@@ -76,46 +84,16 @@ const Profile: PageWithLayout = () => {
7684
// Store original data to restore on cancel
7785
const [originalFormData, setOriginalFormData] = useState(formData);
7886

79-
// Check for dev session as fallback
80-
const [devSession, setDevSession] = React.useState<{
81-
user: { id: string; name: string; email: string; image: string };
82-
} | null>(null);
83-
const [devSessionLoaded, setDevSessionLoaded] = React.useState(false);
84-
85-
React.useEffect(() => {
86-
if (typeof window !== "undefined") {
87-
const stored = localStorage.getItem("dev-session");
88-
if (stored) {
89-
try {
90-
const user = JSON.parse(stored);
91-
setDevSession({ user });
92-
} catch {
93-
localStorage.removeItem("dev-session");
94-
}
95-
}
96-
setDevSessionLoaded(true);
97-
}
98-
}, []);
99-
100-
// Use either real session or dev session
101-
const currentSession = session || devSession;
102-
10387
// Initialize form data when session loads - fetch from database
10488
useEffect(() => {
10589
const fetchProfile = async () => {
106-
if (currentSession?.user) {
90+
if (session?.user) {
10791
try {
108-
// Add dev user ID header for dev mode
109-
const headers: HeadersInit = {};
110-
if (devSession) {
111-
headers["x-dev-user-id"] = devSession.user.id;
112-
}
113-
114-
const response = await fetch("/api/user/profile", { headers });
92+
const response = await fetch("/api/user/profile");
11593
if (response.ok) {
11694
const userData = await response.json();
11795
const profileData = {
118-
name: userData.name || currentSession.user.name || "",
96+
name: userData.name || user.name || "",
11997
bio: userData.bio || "",
12098
title: userData.title || "",
12199
location: userData.location || "",
@@ -132,7 +110,7 @@ const Profile: PageWithLayout = () => {
132110
} else {
133111
// Fallback to session data if API fails
134112
const fallbackData = {
135-
name: currentSession.user.name || "",
113+
name: user.name || "",
136114
bio: "",
137115
title: "",
138116
location: "",
@@ -151,7 +129,7 @@ const Profile: PageWithLayout = () => {
151129
console.error("Error fetching profile:", error);
152130
// Fallback to session data on error
153131
const fallbackData = {
154-
name: currentSession.user.name || "",
132+
name: user.name || "",
155133
bio: "",
156134
title: "",
157135
location: "",
@@ -169,24 +147,9 @@ const Profile: PageWithLayout = () => {
169147
}
170148
};
171149
fetchProfile();
172-
}, [currentSession]);
173-
174-
useEffect(() => {
175-
// Only redirect if we've loaded dev session and there's no session at all
176-
if (devSessionLoaded && status === "unauthenticated" && !devSession) {
177-
router.replace("/login");
178-
}
179-
}, [status, router, devSession, devSessionLoaded]);
180-
181-
if (!mounted || (!devSessionLoaded && status === "loading")) {
182-
return (
183-
<div className="tw-fixed tw-top-0 tw-z-50 tw-flex tw-h-screen tw-w-screen tw-items-center tw-justify-center tw-bg-white">
184-
<Spinner />
185-
</div>
186-
);
187-
}
150+
}, [session, user]);
188151

189-
if (!currentSession) {
152+
if (!mounted) {
190153
return (
191154
<div className="tw-fixed tw-top-0 tw-z-50 tw-flex tw-h-screen tw-w-screen tw-items-center tw-justify-center tw-bg-white">
192155
<Spinner />
@@ -196,13 +159,6 @@ const Profile: PageWithLayout = () => {
196159

197160
const handleLogout = async () => {
198161
try {
199-
// Handle dev session logout
200-
if (devSession && !session) {
201-
localStorage.removeItem("dev-session");
202-
setDevSession(null);
203-
await router.replace("/login");
204-
return;
205-
}
206162

207163
// Handle real session logout
208164
await signOut({ redirect: false });
@@ -225,15 +181,9 @@ const Profile: PageWithLayout = () => {
225181
const handleSaveProfile = async () => {
226182
setIsSaving(true);
227183
try {
228-
// Add dev user ID header for dev mode
229-
const headers: HeadersInit = { "Content-Type": "application/json" };
230-
if (devSession) {
231-
headers["x-dev-user-id"] = devSession.user.id;
232-
}
233-
234184
const response = await fetch("/api/user/profile", {
235185
method: "PUT",
236-
headers,
186+
headers: { "Content-Type": "application/json" },
237187
body: JSON.stringify(formData),
238188
});
239189

@@ -315,10 +265,10 @@ const Profile: PageWithLayout = () => {
315265
<div className="tw-mb-8 tw-rounded-xl tw-bg-gradient-to-r tw-from-primary tw-to-primary/80 tw-p-8 tw-text-white">
316266
<div className="tw-flex tw-flex-col tw-items-start tw-space-y-4 md:tw-flex-row md:tw-items-center md:tw-space-x-6 md:tw-space-y-0">
317267
<div className="tw-relative">
318-
{currentSession.user?.image ? (
268+
{user.image ? (
319269
<img
320-
src={currentSession.user.image}
321-
alt={currentSession.user.name || "User"}
270+
src={user.image}
271+
alt={user.name || "User"}
322272
className="tw-h-24 tw-w-24 tw-rounded-full tw-border-4 tw-border-white/20"
323273
/>
324274
) : (
@@ -338,15 +288,10 @@ const Profile: PageWithLayout = () => {
338288
<div className="tw-flex-1">
339289
<div className="tw-mb-2 tw-flex tw-items-center tw-space-x-2">
340290
<h1 className="tw-text-3xl tw-font-bold">
341-
{currentSession.user?.name || "User"}
291+
{user.name || "User"}
342292
</h1>
343-
{devSession && (
344-
<span className="tw-rounded tw-bg-yellow-100 tw-px-2 tw-py-1 tw-text-xs tw-text-yellow-800">
345-
Dev Mode
346-
</span>
347-
)}
348293
</div>
349-
<p className="tw-text-lg tw-opacity-90">{currentSession.user?.email}</p>
294+
<p className="tw-text-lg tw-opacity-90">{user.email}</p>
350295
<p className="tw-opacity-75">
351296
{formData.title || "Software Engineering Student"}{" "}
352297
{formData.branch ? `• ${formData.branch} Veteran` : "• Army Veteran"}
@@ -875,9 +820,27 @@ const Profile: PageWithLayout = () => {
875820

876821
Profile.Layout = Layout01;
877822

878-
export const getStaticProps: GetStaticProps<PageProps> = () => {
823+
export const getServerSideProps: GetServerSideProps<PageProps> = async (context) => {
824+
// Check authentication
825+
const session = await getServerSession(context.req, context.res, options);
826+
827+
if (!session?.user) {
828+
return {
829+
redirect: {
830+
destination: "/login?callbackUrl=/profile",
831+
permanent: false,
832+
},
833+
};
834+
}
835+
879836
return {
880837
props: {
838+
user: {
839+
id: session.user.id,
840+
name: session.user.name || null,
841+
email: session.user.email || "",
842+
image: session.user.image || null,
843+
},
881844
layout: {
882845
headerShadow: true,
883846
headerFluid: false,

0 commit comments

Comments
 (0)