Skip to content

Commit

Permalink
Merge pull request #56 from CS3219-AY2425S1/ryan/auth-guard
Browse files Browse the repository at this point in the history
Auth Guarding webpages
  • Loading branch information
chiaryan authored Oct 30, 2024
2 parents 02b9a80 + 6772a30 commit 1475c05
Show file tree
Hide file tree
Showing 7 changed files with 72 additions and 79 deletions.
25 changes: 0 additions & 25 deletions apps/frontend/src/app/profile/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import {
ValidateUser,
VerifyTokenResponseType,
} from "../services/user";
import { useRouter } from 'next/navigation';

interface ProfilePageProps {}

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

// used to check if user JWT is verified

const [userId, setUserId] = useState<string | undefined>(undefined);
const [isAdmin, setIsAdmin] = useState<boolean | undefined>(undefined);

const router = useRouter();

useLayoutEffect(() => {
var isAuth = false;

ValidateUser().then((data: VerifyTokenResponseType) => {
setUserId(data.data.id);
setEmail(data.data.email);
setUsername(data.data.username);
setIsAdmin(data.data.isAdmin);
isAuth = true;
}).finally(() => {
if(!isAuth){
// cannot verify
router.push('/login'); // Client-side redirect using router.push
}
});
}, [router])

useEffect(() => {
ValidateUser().then((data: VerifyTokenResponseType) => {
form.setFieldsValue({
Expand Down
28 changes: 1 addition & 27 deletions apps/frontend/src/app/question/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,33 +47,7 @@ export default function QuestionPage() {
const [categories, setCategories] = useState<string[]>([]); // Store the selected filter categories
const [description, setDescription] = useState<string | undefined>(undefined);
const [selectedItem, setSelectedItem] = useState("python"); // State to hold the selected language item

// used to check if user JWT is verified

const [userId, setUserId] = useState<string | undefined>(undefined);
const [email, setEmail] = useState<string | undefined>(undefined);
const [username, setUsername] = useState<string | undefined>(undefined);
const [isAdmin, setIsAdmin] = useState<boolean | undefined>(undefined);

useLayoutEffect(() => {
var isAuth = false;

ValidateUser()
.then((data: VerifyTokenResponseType) => {
setUserId(data.data.id);
setEmail(data.data.email);
setUsername(data.data.username);
setIsAdmin(data.data.isAdmin);
isAuth = true;
})
.finally(() => {
if (!isAuth) {
// cannot verify
router.push("/login"); // Client-side redirect using router.push
}
});
}, [router]);


// When code editor page is initialised, fetch the particular question, and display in code editor
useEffect(() => {
if (!isLoading) {
Expand Down
20 changes: 1 addition & 19 deletions apps/frontend/src/app/question/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ import {
import Link from "next/link";
import TextArea from "antd/es/input/TextArea";
import { ValidateUser, VerifyTokenResponseType } from "../services/user";
import { useRouter } from "next/navigation";

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

// used to check if user JWT is verified

const [userId, setUserId] = useState<string | undefined>(undefined);
const [email, setEmail] = useState<string | undefined>(undefined);
const [username, setUsername] = useState<string | undefined>(undefined);
const [isAdmin, setIsAdmin] = useState<boolean | undefined>(undefined);

const router = useRouter();

useLayoutEffect(() => {
var isAuth = false;

ValidateUser()
.then((data: VerifyTokenResponseType) => {
setUserId(data.data.id);
setEmail(data.data.email);
setUsername(data.data.username);
setIsAdmin(data.data.isAdmin);
isAuth = true;
})
.finally(() => {
if (!isAuth) {
// cannot verify
router.push("/login"); // Client-side redirect using router.push
}
});
}, [router]);
}, []);

const handleEditClick = (index: number, question: Question) => {
// Open the modal for the specific question
Expand Down
21 changes: 16 additions & 5 deletions apps/frontend/src/app/services/login-store.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,26 @@
"use client";

const KEY: string = "TOKEN";

export function setToken(token: string): void {
window.localStorage[KEY] = token
document.cookie = `${KEY}=${token}`
}

export function getToken(): string {
return KEY in window.localStorage ? window.localStorage[KEY] : ""
const keyValue = document.cookie.split("; ").find(kv => kv.startsWith(`${KEY}=`))
if (keyValue == undefined) {
return "";
}

const [_, value] = keyValue.split("=");

if (value == undefined) {
return "";
}

return value;
}

export function deleteToken() {
if (KEY in window.localStorage) {
window.localStorage.removeItem(KEY);
}
document.cookie = `${KEY}=nothinghere;expires=0`;
}
8 changes: 6 additions & 2 deletions apps/frontend/src/app/services/user.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
"use client";

import { getToken } from "./login-store";

const USER_SERVICE_URL = process.env.NEXT_PUBLIC_USER_SERVICE_URL;

export interface User {
Expand Down Expand Up @@ -91,7 +95,7 @@ export const UpdateUser = async (
user: UpdateUserRequestType,
id: string
): Promise<UpdateUserResponseType> => {
const JWT_TOKEN = localStorage.getItem("TOKEN") ?? undefined;
const JWT_TOKEN = getToken();
const response = await fetch(
`${process.env.NEXT_PUBLIC_USER_SERVICE_URL}users/${id}`,
{
Expand All @@ -116,7 +120,7 @@ export const UpdateUser = async (
};

export const ValidateUser = async (): Promise<VerifyTokenResponseType> => {
const JWT_TOKEN = localStorage.getItem("TOKEN") ?? undefined;
const JWT_TOKEN = getToken();
const response = await fetch(
`${process.env.NEXT_PUBLIC_USER_SERVICE_URL}auth/verify-token`,
{
Expand Down
3 changes: 2 additions & 1 deletion apps/frontend/src/components/Header/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { useRouter } from "next/navigation";
import "./styles.scss";
import DropdownButton from "antd/es/dropdown/dropdown-button";
import { LogoutOutlined, UserOutlined } from "@ant-design/icons";
import { deleteToken } from "@/app/services/login-store";

interface HeaderProps {
selectedKey: string[] | undefined;
Expand Down Expand Up @@ -55,7 +56,7 @@ const Header = (props: HeaderProps): JSX.Element => {
),
onClick: () => {
// Clear away the previously stored jwt token in localstorage
localStorage.clear();
deleteToken();
// Redirect user to login page
push("/login");
},
Expand Down
46 changes: 46 additions & 0 deletions apps/frontend/src/middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { NextURL } from 'next/dist/server/web/next-url';
import { type NextRequest, NextResponse } from 'next/server';

const PUBLIC_ROUTES = ["/login", "/register"];

async function isValidToken(TOKEN: string): Promise<boolean> {
const { status } = await fetch(
`${process.env.NEXT_PUBLIC_USER_SERVICE_URL}auth/verify-token`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${TOKEN}`,
},
}
);
return status === 200;
}

export default async function middleware(request: NextRequest) {
const REDIRECT_TO_LOGIN = NextResponse.redirect(new NextURL("/login", request.url));
const TOKEN = request.cookies.get("TOKEN");
if (TOKEN == undefined) {
return REDIRECT_TO_LOGIN;
}

if (!await isValidToken(TOKEN.value)) {
REDIRECT_TO_LOGIN.cookies.delete("TOKEN");
return REDIRECT_TO_LOGIN;
}

return NextResponse.next();

}

export const config = {
matcher: "/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt|login|register).*)",
// matcher: [
// "/matching",
// "/",
// "/profile",
// "/question",
// "/question/.*",
// ],
}

0 comments on commit 1475c05

Please sign in to comment.