From d0738c6c3cf1f91e3ece625f00db2665aa42c363 Mon Sep 17 00:00:00 2001 From: revdrag Date: Sun, 20 Oct 2024 22:35:45 +0800 Subject: [PATCH 1/4] Bug fix for route protection --- docker-compose.yaml | 27 ++++++++++++++------------- frontend/.gitignore | 1 + frontend/middleware.ts | 4 +++- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/docker-compose.yaml b/docker-compose.yaml index 66f79be45d..a7e19c10ce 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,18 +1,19 @@ version: '3.8' services: - frontend: - build: - context: ./frontend - ports: - - "3000:3000" - environment: - - NODE_ENV=production - depends_on: - - user-service - - question-service - - matching-service - networks: - - app-network + # frontend: + # build: + # context: ./frontend + # ports: + # - "3000:3000" + # environment: + # - NODE_ENV=production + # - NEXT_PUBLIC_API_URL=http://nginx + # depends_on: + # - user-service + # - question-service + # - matching-service + # networks: + # - app-network user-service: build: diff --git a/frontend/.gitignore b/frontend/.gitignore index 8f322f0d8f..56818511eb 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -26,6 +26,7 @@ yarn-error.log* # local env files .env*.local +.env.docker # vercel .vercel diff --git a/frontend/middleware.ts b/frontend/middleware.ts index badfaae24f..e256b931b5 100644 --- a/frontend/middleware.ts +++ b/frontend/middleware.ts @@ -11,10 +11,12 @@ const publicRoutes = [ "/reset-password", ]; +const baseURL = process.env.NEXT_PUBLIC_API_URL; + const validateToken = async (accessToken: string | undefined): Promise => { try { const response = await fetch( - "http://localhost/api/user-service/auth/verify-token", + `${baseURL}/api/user-service/auth/verify-token`, { method: "GET", headers: { From 867e783b6cb91b1faa5c1dbec59cd7c8405f1b3b Mon Sep 17 00:00:00 2001 From: revdrag Date: Sun, 20 Oct 2024 22:43:25 +0800 Subject: [PATCH 2/4] Fix bug --- frontend/middleware.ts | 41 ++++++++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/frontend/middleware.ts b/frontend/middleware.ts index 006781bab3..e256b931b5 100644 --- a/frontend/middleware.ts +++ b/frontend/middleware.ts @@ -39,33 +39,40 @@ const validateToken = async (accessToken: string | undefined): Promise => { export async function middleware(req: NextRequest) { const url = req.nextUrl; const accessToken = req.cookies.get("accessToken")?.value; - const refreshToken = req.cookies.get("refreshToken")?.value; - - // Define public routes that don't require authentication - const publicRoutes = [ - "/", - "/login", - "/register", - "/forget-password", - "/reset-password", - ]; // Allow access to public routes without checking tokens if (publicRoutes.some((route) => url.pathname === route)) { return NextResponse.next(); } - // If both access token and refresh token are present, allow the user to proceed - if (accessToken && refreshToken) { + // If the access token is present, validate it + try { + const decodedAccessToken = await validateToken(accessToken); + + if (!decodedAccessToken) { + console.log("Invalid access token, redirecting to login"); + + return NextResponse.redirect(new URL("/login", req.nextUrl)); + } + + // Extract user role from the decoded token (assuming the role is stored in the token) + const isAdmin = decodedAccessToken.isAdmin; + + // Check if the user is trying to access an admin route (all routes starting with /admin) + if (url.pathname.startsWith("/admin") && !isAdmin) { + console.log("User is not an admin, redirecting to 403 page"); + + return NextResponse.redirect(new URL("/403", req.nextUrl)); // 403 Forbidden + } + + console.log("User is authenticated"); + return NextResponse.next(); - } + } catch (error) { + console.error("Token validation failed, redirecting to login:", error); - // If no access token and the user is not on a public route, redirect to login - if (!accessToken && !refreshToken) { return NextResponse.redirect(new URL("/login", req.nextUrl)); } - - return NextResponse.next(); } // Apply middleware to specific paths From 730ef7002caf448317981890f6c0ae1f8e89c7e5 Mon Sep 17 00:00:00 2001 From: revdrag Date: Sun, 20 Oct 2024 22:51:42 +0800 Subject: [PATCH 3/4] Add 403 page and verify token api --- docker-compose.yaml | 28 ++++++++++++++-------------- frontend/context/UserContext.tsx | 11 +++++++++++ frontend/hooks/api/auth.ts | 16 +++++++++++++++- frontend/pages/403/index.tsx | 17 +++++++++++++++++ 4 files changed, 57 insertions(+), 15 deletions(-) create mode 100644 frontend/pages/403/index.tsx diff --git a/docker-compose.yaml b/docker-compose.yaml index a7e19c10ce..cc0f21347d 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,19 +1,19 @@ version: '3.8' services: - # frontend: - # build: - # context: ./frontend - # ports: - # - "3000:3000" - # environment: - # - NODE_ENV=production - # - NEXT_PUBLIC_API_URL=http://nginx - # depends_on: - # - user-service - # - question-service - # - matching-service - # networks: - # - app-network + frontend: + build: + context: ./frontend + ports: + - "3000:3000" + environment: + - NODE_ENV=production + - NEXT_PUBLIC_API_URL=http://nginx + depends_on: + - user-service + - question-service + - matching-service + networks: + - app-network user-service: build: diff --git a/frontend/context/UserContext.tsx b/frontend/context/UserContext.tsx index 7341d5965f..df363ad012 100644 --- a/frontend/context/UserContext.tsx +++ b/frontend/context/UserContext.tsx @@ -1,6 +1,7 @@ import { createContext, useState, ReactNode, useEffect } from "react"; import { User } from "@/types/user"; +import { useVerifyToken } from "@/hooks/api/auth"; interface UserContextProps { user: User | null; @@ -12,6 +13,16 @@ export const UserContext = createContext(null); export const UserProvider = ({ children }: { children: ReactNode }) => { const [user, setUser] = useState(null); + const { data, error } = useVerifyToken(); + + useEffect(() => { + if (data) { + setUser(data); + } else if (error) { + setUser(null); + } + }, [data, error]); + return ( {children} diff --git a/frontend/hooks/api/auth.ts b/frontend/hooks/api/auth.ts index f908b86d12..b1a4e48ec3 100644 --- a/frontend/hooks/api/auth.ts +++ b/frontend/hooks/api/auth.ts @@ -1,4 +1,4 @@ -import { useMutation } from "@tanstack/react-query"; +import { useMutation, useQuery } from "@tanstack/react-query"; import { AxiosError } from "axios"; import { jwtDecode } from "jwt-decode"; @@ -10,6 +10,7 @@ import { RegisterCredentials, LogoutResponse, } from "@/types/auth"; +import { User } from "@/types/user"; // Login: Send credentials to the server and return the response const login = async (credentials: Credentials): Promise => { @@ -177,3 +178,16 @@ export const useLogout = () => { }, }); }; + +const verifyToken = async () => { + const response = await axios.get(`/user-service/auth/verify-token`); + + return response.data.data; +}; + +export const useVerifyToken = () => { + return useQuery({ + queryKey: ["verifyToken"], + queryFn: () => verifyToken(), + }); +}; diff --git a/frontend/pages/403/index.tsx b/frontend/pages/403/index.tsx new file mode 100644 index 0000000000..f7ec14689d --- /dev/null +++ b/frontend/pages/403/index.tsx @@ -0,0 +1,17 @@ +"use client"; + +import DefaultLayout from "@/layouts/default"; + +const The403Page = () => { + return ( + <> + +
+

403 Forbidden Access

+
+
+ + ); +}; + +export default The403Page; From 4579b4c06ccfaa621ef6c35c8396a16f1484a3dd Mon Sep 17 00:00:00 2001 From: revdrag Date: Sun, 20 Oct 2024 23:13:08 +0800 Subject: [PATCH 4/4] Fix isAdmin bug --- frontend/middleware.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/middleware.ts b/frontend/middleware.ts index e256b931b5..08b31636db 100644 --- a/frontend/middleware.ts +++ b/frontend/middleware.ts @@ -56,7 +56,7 @@ export async function middleware(req: NextRequest) { } // Extract user role from the decoded token (assuming the role is stored in the token) - const isAdmin = decodedAccessToken.isAdmin; + const isAdmin = decodedAccessToken.data.isAdmin; // Check if the user is trying to access an admin route (all routes starting with /admin) if (url.pathname.startsWith("/admin") && !isAdmin) {