Skip to content

Commit 1475c05

Browse files
authored
Merge pull request #56 from CS3219-AY2425S1/ryan/auth-guard
Auth Guarding webpages
2 parents 02b9a80 + 6772a30 commit 1475c05

File tree

7 files changed

+72
-79
lines changed

7 files changed

+72
-79
lines changed

apps/frontend/src/app/profile/page.tsx

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import {
2020
ValidateUser,
2121
VerifyTokenResponseType,
2222
} from "../services/user";
23-
import { useRouter } from 'next/navigation';
2423

2524
interface ProfilePageProps {}
2625

@@ -33,30 +32,6 @@ const ProfilePage = (props: ProfilePageProps): JSX.Element => {
3332
const [refresh, setRefresh] = useState<boolean>(false);
3433
const [messageApi, contextHolder] = message.useMessage();
3534

36-
// used to check if user JWT is verified
37-
38-
const [userId, setUserId] = useState<string | undefined>(undefined);
39-
const [isAdmin, setIsAdmin] = useState<boolean | undefined>(undefined);
40-
41-
const router = useRouter();
42-
43-
useLayoutEffect(() => {
44-
var isAuth = false;
45-
46-
ValidateUser().then((data: VerifyTokenResponseType) => {
47-
setUserId(data.data.id);
48-
setEmail(data.data.email);
49-
setUsername(data.data.username);
50-
setIsAdmin(data.data.isAdmin);
51-
isAuth = true;
52-
}).finally(() => {
53-
if(!isAuth){
54-
// cannot verify
55-
router.push('/login'); // Client-side redirect using router.push
56-
}
57-
});
58-
}, [router])
59-
6035
useEffect(() => {
6136
ValidateUser().then((data: VerifyTokenResponseType) => {
6237
form.setFieldsValue({

apps/frontend/src/app/question/[id]/page.tsx

Lines changed: 1 addition & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -47,33 +47,7 @@ export default function QuestionPage() {
4747
const [categories, setCategories] = useState<string[]>([]); // Store the selected filter categories
4848
const [description, setDescription] = useState<string | undefined>(undefined);
4949
const [selectedItem, setSelectedItem] = useState("python"); // State to hold the selected language item
50-
51-
// used to check if user JWT is verified
52-
53-
const [userId, setUserId] = useState<string | undefined>(undefined);
54-
const [email, setEmail] = useState<string | undefined>(undefined);
55-
const [username, setUsername] = useState<string | undefined>(undefined);
56-
const [isAdmin, setIsAdmin] = useState<boolean | undefined>(undefined);
57-
58-
useLayoutEffect(() => {
59-
var isAuth = false;
60-
61-
ValidateUser()
62-
.then((data: VerifyTokenResponseType) => {
63-
setUserId(data.data.id);
64-
setEmail(data.data.email);
65-
setUsername(data.data.username);
66-
setIsAdmin(data.data.isAdmin);
67-
isAuth = true;
68-
})
69-
.finally(() => {
70-
if (!isAuth) {
71-
// cannot verify
72-
router.push("/login"); // Client-side redirect using router.push
73-
}
74-
});
75-
}, [router]);
76-
50+
7751
// When code editor page is initialised, fetch the particular question, and display in code editor
7852
useEffect(() => {
7953
if (!isLoading) {

apps/frontend/src/app/question/page.tsx

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ import {
4040
import Link from "next/link";
4141
import TextArea from "antd/es/input/TextArea";
4242
import { ValidateUser, VerifyTokenResponseType } from "../services/user";
43-
import { useRouter } from "next/navigation";
4443

4544
/**
4645
* defines the State of the page whe a user is deleing an object. Has 3 general states:
@@ -118,31 +117,14 @@ export default function QuestionListPage() {
118117

119118
// used to check if user JWT is verified
120119

121-
const [userId, setUserId] = useState<string | undefined>(undefined);
122-
const [email, setEmail] = useState<string | undefined>(undefined);
123-
const [username, setUsername] = useState<string | undefined>(undefined);
124120
const [isAdmin, setIsAdmin] = useState<boolean | undefined>(undefined);
125121

126-
const router = useRouter();
127-
128122
useLayoutEffect(() => {
129-
var isAuth = false;
130-
131123
ValidateUser()
132124
.then((data: VerifyTokenResponseType) => {
133-
setUserId(data.data.id);
134-
setEmail(data.data.email);
135-
setUsername(data.data.username);
136125
setIsAdmin(data.data.isAdmin);
137-
isAuth = true;
138126
})
139-
.finally(() => {
140-
if (!isAuth) {
141-
// cannot verify
142-
router.push("/login"); // Client-side redirect using router.push
143-
}
144-
});
145-
}, [router]);
127+
}, []);
146128

147129
const handleEditClick = (index: number, question: Question) => {
148130
// Open the modal for the specific question
Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,26 @@
1+
"use client";
2+
13
const KEY: string = "TOKEN";
24

35
export function setToken(token: string): void {
4-
window.localStorage[KEY] = token
6+
document.cookie = `${KEY}=${token}`
57
}
68

79
export function getToken(): string {
8-
return KEY in window.localStorage ? window.localStorage[KEY] : ""
10+
const keyValue = document.cookie.split("; ").find(kv => kv.startsWith(`${KEY}=`))
11+
if (keyValue == undefined) {
12+
return "";
13+
}
14+
15+
const [_, value] = keyValue.split("=");
16+
17+
if (value == undefined) {
18+
return "";
19+
}
20+
21+
return value;
922
}
1023

1124
export function deleteToken() {
12-
if (KEY in window.localStorage) {
13-
window.localStorage.removeItem(KEY);
14-
}
25+
document.cookie = `${KEY}=nothinghere;expires=0`;
1526
}

apps/frontend/src/app/services/user.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
"use client";
2+
3+
import { getToken } from "./login-store";
4+
15
const USER_SERVICE_URL = process.env.NEXT_PUBLIC_USER_SERVICE_URL;
26

37
export interface User {
@@ -91,7 +95,7 @@ export const UpdateUser = async (
9195
user: UpdateUserRequestType,
9296
id: string
9397
): Promise<UpdateUserResponseType> => {
94-
const JWT_TOKEN = localStorage.getItem("TOKEN") ?? undefined;
98+
const JWT_TOKEN = getToken();
9599
const response = await fetch(
96100
`${process.env.NEXT_PUBLIC_USER_SERVICE_URL}users/${id}`,
97101
{
@@ -116,7 +120,7 @@ export const UpdateUser = async (
116120
};
117121

118122
export const ValidateUser = async (): Promise<VerifyTokenResponseType> => {
119-
const JWT_TOKEN = localStorage.getItem("TOKEN") ?? undefined;
123+
const JWT_TOKEN = getToken();
120124
const response = await fetch(
121125
`${process.env.NEXT_PUBLIC_USER_SERVICE_URL}auth/verify-token`,
122126
{

apps/frontend/src/components/Header/header.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { useRouter } from "next/navigation";
1212
import "./styles.scss";
1313
import DropdownButton from "antd/es/dropdown/dropdown-button";
1414
import { LogoutOutlined, UserOutlined } from "@ant-design/icons";
15+
import { deleteToken } from "@/app/services/login-store";
1516

1617
interface HeaderProps {
1718
selectedKey: string[] | undefined;
@@ -55,7 +56,7 @@ const Header = (props: HeaderProps): JSX.Element => {
5556
),
5657
onClick: () => {
5758
// Clear away the previously stored jwt token in localstorage
58-
localStorage.clear();
59+
deleteToken();
5960
// Redirect user to login page
6061
push("/login");
6162
},

apps/frontend/src/middleware.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { NextURL } from 'next/dist/server/web/next-url';
2+
import { type NextRequest, NextResponse } from 'next/server';
3+
4+
const PUBLIC_ROUTES = ["/login", "/register"];
5+
6+
async function isValidToken(TOKEN: string): Promise<boolean> {
7+
const { status } = await fetch(
8+
`${process.env.NEXT_PUBLIC_USER_SERVICE_URL}auth/verify-token`,
9+
{
10+
method: "GET",
11+
headers: {
12+
"Content-Type": "application/json",
13+
Authorization: `Bearer ${TOKEN}`,
14+
},
15+
}
16+
);
17+
return status === 200;
18+
}
19+
20+
export default async function middleware(request: NextRequest) {
21+
const REDIRECT_TO_LOGIN = NextResponse.redirect(new NextURL("/login", request.url));
22+
const TOKEN = request.cookies.get("TOKEN");
23+
if (TOKEN == undefined) {
24+
return REDIRECT_TO_LOGIN;
25+
}
26+
27+
if (!await isValidToken(TOKEN.value)) {
28+
REDIRECT_TO_LOGIN.cookies.delete("TOKEN");
29+
return REDIRECT_TO_LOGIN;
30+
}
31+
32+
return NextResponse.next();
33+
34+
}
35+
36+
export const config = {
37+
matcher: "/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt|login|register).*)",
38+
// matcher: [
39+
// "/matching",
40+
// "/",
41+
// "/profile",
42+
// "/question",
43+
// "/question/.*",
44+
// ],
45+
}
46+

0 commit comments

Comments
 (0)