From dbe56f364e49bac531f53673964c2262e4defb02 Mon Sep 17 00:00:00 2001 From: Bagasbanuna02 Date: Thu, 22 May 2025 11:47:59 +0800 Subject: [PATCH] fix middleware deskrispi: - perbaiki middleware untuk versi diatas 1.4.6 No Issuee --- src/app/api/auth/logout/route-v.1.4.5t.xt | 38 ++ src/app/api/auth/logout/route.ts | 21 +- src/app/api/user-validate/route-v.1.4.5.txt | 67 ++++ src/app/api/user-validate/route.ts | 51 ++- src/app/api/validation/route.ts | 53 ++- src/middleware-v.1.4.5.txt | 386 ++++++++++++++++++++ src/middleware.ts | 364 +++++++++--------- 7 files changed, 764 insertions(+), 216 deletions(-) create mode 100644 src/app/api/auth/logout/route-v.1.4.5t.xt create mode 100644 src/app/api/user-validate/route-v.1.4.5.txt create mode 100644 src/middleware-v.1.4.5.txt diff --git a/src/app/api/auth/logout/route-v.1.4.5t.xt b/src/app/api/auth/logout/route-v.1.4.5t.xt new file mode 100644 index 00000000..82982ce6 --- /dev/null +++ b/src/app/api/auth/logout/route-v.1.4.5t.xt @@ -0,0 +1,38 @@ +import { cookies } from "next/headers"; +import { NextResponse } from "next/server"; + +export const dynamic = "force-dynamic"; + +export async function GET() { + const sessionKey = process.env.NEXT_PUBLIC_BASE_SESSION_KEY!; // Gunakan environment variable yang tidak diekspos ke client-side + if (!sessionKey) { + return NextResponse.json( + { success: false, message: "Session key tidak ditemukan" }, + { status: 500 } + ); + } + + const cookieStore = cookies(); + const sessionCookie = cookieStore.get(sessionKey); + + if (!sessionCookie) { + return NextResponse.json( + { success: false, message: "Session tidak ditemukan" }, + { status: 400 } + ); + } + + try { + cookieStore.delete(sessionKey); + return NextResponse.json( + { success: true, message: "Logout berhasil" }, + { status: 200 } + ); + } catch (error) { + console.error("Gagal menghapus cookie:", error); + return NextResponse.json( + { success: false, message: "Gagal melakukan logout" }, + { status: 500 } + ); + } +} diff --git a/src/app/api/auth/logout/route.ts b/src/app/api/auth/logout/route.ts index 82982ce6..3b8f52c1 100644 --- a/src/app/api/auth/logout/route.ts +++ b/src/app/api/auth/logout/route.ts @@ -4,7 +4,7 @@ import { NextResponse } from "next/server"; export const dynamic = "force-dynamic"; export async function GET() { - const sessionKey = process.env.NEXT_PUBLIC_BASE_SESSION_KEY!; // Gunakan environment variable yang tidak diekspos ke client-side + const sessionKey = process.env.NEXT_PUBLIC_BASE_SESSION_KEY!; if (!sessionKey) { return NextResponse.json( { success: false, message: "Session key tidak ditemukan" }, @@ -23,11 +23,20 @@ export async function GET() { } try { - cookieStore.delete(sessionKey); - return NextResponse.json( - { success: true, message: "Logout berhasil" }, - { status: 200 } - ); + const response = NextResponse.json({ + success: true, + message: "Logout berhasil", + }); + + // Menghapus cookie dengan set maxAge 0 + response.cookies.set({ + name: sessionKey, + value: "", + path: "/", + maxAge: 0, + }); + + return response; } catch (error) { console.error("Gagal menghapus cookie:", error); return NextResponse.json( diff --git a/src/app/api/user-validate/route-v.1.4.5.txt b/src/app/api/user-validate/route-v.1.4.5.txt new file mode 100644 index 00000000..ff4a3b61 --- /dev/null +++ b/src/app/api/user-validate/route-v.1.4.5.txt @@ -0,0 +1,67 @@ +import { decrypt } from "@/app/(auth)/_lib/decrypt"; +import { prisma } from "@/lib"; +import { cookies } from "next/headers"; +import { NextResponse } from "next/server"; +export const dynamic = "force-dynamic"; + +export async function GET(req: Request) { + try { + const SESSIONKEY = process.env.NEXT_PUBLIC_BASE_SESSION_KEY!; + // const token = req.headers.get("Authorization")?.split(" ")[1] + const token = + cookies().get(SESSIONKEY)?.value || + req.headers.get("Authorization")?.split(" ")[1]; + if (!token) { + return NextResponse.json( + { + success: false, + message: "Unauthorized token not found", + }, + { status: 401 } + ); + } + + const decripted = await decrypt({ + token: token!, + encodedKey: process.env.NEXT_PUBLIC_BASE_TOKEN_KEY!, + }); + + if (!decripted) { + await prisma.$disconnect(); + return NextResponse.json( + { + success: false, + message: "Unauthorized", + }, + { status: 401 } + ); + } + + const user = await prisma.user.findUnique({ + where: { + id: decripted.id, + }, + }); + + // Disconnect after successful query + + return NextResponse.json({ + success: true, + message: "Berhasil mendapatkan data", + data: user, + }); + } catch (error) { + // Ensure connection is closed even if error occurs + + console.error("Error in user validation:", error); + return NextResponse.json( + { + success: false, + message: "Terjadi kesalahan pada server", + }, + { status: 500 } + ); + } finally { + await prisma.$disconnect(); + } +} diff --git a/src/app/api/user-validate/route.ts b/src/app/api/user-validate/route.ts index ff4a3b61..a9c8fcb7 100644 --- a/src/app/api/user-validate/route.ts +++ b/src/app/api/user-validate/route.ts @@ -2,15 +2,22 @@ import { decrypt } from "@/app/(auth)/_lib/decrypt"; import { prisma } from "@/lib"; import { cookies } from "next/headers"; import { NextResponse } from "next/server"; + export const dynamic = "force-dynamic"; export async function GET(req: Request) { try { const SESSIONKEY = process.env.NEXT_PUBLIC_BASE_SESSION_KEY!; - // const token = req.headers.get("Authorization")?.split(" ")[1] - const token = - cookies().get(SESSIONKEY)?.value || - req.headers.get("Authorization")?.split(" ")[1]; + const TOKENKEY = process.env.NEXT_PUBLIC_BASE_TOKEN_KEY!; + const cookieStore = cookies(); + + const authHeader = req.headers.get("Authorization") || ""; + const bearerToken = authHeader.startsWith("Bearer ") + ? authHeader.split(" ")[1] + : undefined; + + const token = cookieStore.get(SESSIONKEY)?.value || bearerToken; + if (!token) { return NextResponse.json( { @@ -21,17 +28,16 @@ export async function GET(req: Request) { ); } - const decripted = await decrypt({ - token: token!, - encodedKey: process.env.NEXT_PUBLIC_BASE_TOKEN_KEY!, + const decrypted = await decrypt({ + token, + encodedKey: TOKENKEY, }); - if (!decripted) { - await prisma.$disconnect(); + if (!decrypted?.id) { return NextResponse.json( { success: false, - message: "Unauthorized", + message: "Unauthorized: invalid token data", }, { status: 401 } ); @@ -39,11 +45,30 @@ export async function GET(req: Request) { const user = await prisma.user.findUnique({ where: { - id: decripted.id, + id: decrypted.id, }, }); - // Disconnect after successful query + if (!user) { + return NextResponse.json( + { + success: false, + message: "User tidak ditemukan", + }, + { status: 404 } + ); + } + + if (!user.active) { + return NextResponse.json( + { + success: false, + message: "User belum aktif", + data: user, + }, + { status: 403 } + ); + } return NextResponse.json({ success: true, @@ -51,8 +76,6 @@ export async function GET(req: Request) { data: user, }); } catch (error) { - // Ensure connection is closed even if error occurs - console.error("Error in user validation:", error); return NextResponse.json( { diff --git a/src/app/api/validation/route.ts b/src/app/api/validation/route.ts index 5b1b20b0..8ad6c16b 100644 --- a/src/app/api/validation/route.ts +++ b/src/app/api/validation/route.ts @@ -1,9 +1,58 @@ import { NextResponse } from "next/server"; +import { jwtVerify } from "jose"; export async function GET(req: Request) { const token = req.headers.get("Authorization")?.split(" ")[1]; - if (!token) return NextResponse.json({ success: false }, { status: 401 }); + if (!token) { + console.warn("Token is missing in /api/validation"); + return NextResponse.json( + { success: false, error: "Token missing" }, + { status: 401 } + ); + } - return NextResponse.json({ success: true }); + try { + const secret = new TextEncoder().encode( + process.env.NEXT_PUBLIC_BASE_TOKEN_KEY! + ); + const { payload } = await jwtVerify(token, secret, { + algorithms: ["HS256"], + }); + + if (!payload || typeof payload !== "object" || !payload.user) { + console.warn("Invalid payload structure in /api/validation:", payload); + return NextResponse.json( + { success: false, error: "Invalid token payload" }, + { status: 401 } + ); + } + + return NextResponse.json({ + success: true, + user: payload.user, + }); + } catch (err) { + console.error("Token verification failed in /api/validation:", err); + return NextResponse.json( + { success: false, error: "Invalid or expired token" }, + { status: 401 } + ); + } } + +// Optional: handle disallowed methods +export async function POST() { + return NextResponse.json({ error: "Method Not Allowed" }, { status: 405 }); +} + + +// ==== Versi 1.4.5 ==== // + +// export async function GET(req: Request) { +// const token = req.headers.get("Authorization")?.split(" ")[1]; + +// if (!token) return NextResponse.json({ success: false }, { status: 401 }); + +// return NextResponse.json({ success: true }); +// } diff --git a/src/middleware-v.1.4.5.txt b/src/middleware-v.1.4.5.txt new file mode 100644 index 00000000..77d32d97 --- /dev/null +++ b/src/middleware-v.1.4.5.txt @@ -0,0 +1,386 @@ +import { jwtVerify } from "jose"; +import { NextRequest, NextResponse } from "next/server"; +import { cookies } from "next/headers"; + +type MiddlewareConfig = { + apiPath: string; + loginPath: string; + userPath: string; + publicRoutes: string[]; + encodedKey: string; + sessionKey: string; + validationApiRoute: string; + log: boolean; +}; + +const middlewareConfig: MiddlewareConfig = { + apiPath: "/api", + loginPath: "/login", + userPath: "/dev/home", + publicRoutes: [ + // API + "/", + "/api/voting/*", + "/api/collaboration/*", + "/api/notifikasi/*", + "/api/logs/*", + "/api/auth/*", + // "/api/origin-url", + // "/api/job*", + + // ADMIN API + // >> buat dibawah sini << + "/api/admin/donasi/*", + "/api/admin/investasi/*", + "/api/admin/collaboration/*", + + // Akses awal + "/api/get-cookie", + "/api/user/activation", + "/api/user-validate", + "/api/version", + "/api/validation", + + // PAGE + "/login", + "/register", + "/validasi", + "/splash", + "/invalid-user", + "/job-vacancy", + "/preview-image", + "/auth/login", + "/auth/api/login", + "/waiting-room", + "/zCoba/*", + + // ASSETS + "/aset/global/main_background.png", + "/aset/logo/logo-hipmi.png", + ], + encodedKey: process.env.NEXT_PUBLIC_BASE_TOKEN_KEY!, + sessionKey: process.env.NEXT_PUBLIC_BASE_SESSION_KEY!, + validationApiRoute: "/api/validation", + log: false, +}; + +export const middleware = async (req: NextRequest) => { + const { + apiPath, + encodedKey, + loginPath, + publicRoutes, + sessionKey, + validationApiRoute, + userPath, + } = middlewareConfig; + + const { pathname } = req.nextUrl; + const corsResponse = handleCors(req); + + if (corsResponse) { + return corsResponse; + } + + // Check if route is public + const isPublicRoute = isRoutePublic(pathname, publicRoutes, loginPath); + if (isPublicRoute && pathname !== loginPath) { + return setCorsHeaders(NextResponse.next()); + } + + // Get token from cookies or Authorization header + const token = getToken(req, sessionKey); + const user = await verifyToken({ token, encodedKey }); + + // Handle login page access + if (pathname === loginPath) { + if (user) { + const response = NextResponse.redirect(new URL(userPath, req.url)); + // Preserve token in cookie when redirecting + if (token) { + response.cookies.set(sessionKey, token, { + // httpOnly: true, + secure: process.env.NODE_ENV === "production", + sameSite: "lax", + path: "/", + }); + } + return setCorsHeaders(response); + } + return setCorsHeaders(NextResponse.next()); + } + + // Redirect to login if no user found + if (!user) { + const response = NextResponse.redirect(new URL(loginPath, req.url)); + // Clear invalid token + response.cookies.delete(sessionKey); + return setCorsHeaders(response); + } + + // Handle API requests + if (pathname.startsWith(apiPath)) { + // const reqToken = req.headers.get("Authorization")?.split(" ")[1]; + if (!token) { + return setCorsHeaders(unauthorizedResponseTokenAPI()); + } + + try { + + const apiBaseUrl = + process.env.NEXT_PUBLIC_API_URL || new URL(req.url).origin; + const validationResponse = await fetch(`${apiBaseUrl}/api/validation`, { + headers: { + Authorization: `Bearer ${token}`, + }, + }); + + if (!validationResponse.ok) { + console.error("Validation failed:", validationResponse.statusText); + return setCorsHeaders(unauthorizedResponseAPI()); + } + + const validationResponseJson = await validationResponse.json(); + + if (validationResponseJson.success === false) { + return setCorsHeaders(unauthorizedResponseDataUserNotFound(req)); + } + } catch (error) { + console.error( + "Error validating API request:", + (error as Error).message || error + ); + return setCorsHeaders(unauthorizedResponseValidationAPIRequest()); + } + } + + // Handle /dev routes that require active status + if (pathname.startsWith("/dev")) { + try { + const apiBaseUrl = + process.env.NEXT_PUBLIC_API_URL || new URL(req.url).origin; + + const userValidate = await fetch(`${apiBaseUrl}/api/user-validate`, { + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + }, + }); + + if (!userValidate.ok) { + console.error("Validation failed:", userValidate.statusText); + return setCorsHeaders(unauthorizedResponseAPIUserValidate()); + } + + const userValidateJson = await userValidate.json(); + + if (userValidateJson.success == true && !userValidateJson.data) { + unauthorizedResponseDataUserNotFound(req); + } + + if (!userValidateJson.data.active) { + return setCorsHeaders(unauthorizedResponseUserNotActive(req)); + } + } catch (error) { + console.error("Error api user validate:", error); + if (!token) { + console.error("Token is undefined"); + return setCorsHeaders(unauthorizedResponseTokenPAGE()); + } + return setCorsHeaders( + await unauthorizedResponseValidationUser({ + loginPath, + sessionKey, + token, + req, + }) + ); + } + } + + // // Ensure token is preserved in cookie + // if (token) { + // response.cookies.set(sessionKey, token, { + // // httpOnly: true, + // secure: process.env.NODE_ENV === "production", + // sameSite: "lax", + // path: "/", + // }); + // } + const response = NextResponse.next(); + return setCorsHeaders(response); +}; + +// ============================== RESPONSE HANDLERS ==============================// +function isRoutePublic( + pathname: string, + publicRoutes: string[], + loginPath: string +): boolean { + return [...publicRoutes, loginPath].some((route) => { + const pattern = route.replace(/\*/g, ".*"); + return new RegExp(`^${pattern}$`).test(pathname); + }); +} + +function getToken(req: NextRequest, sessionKey: string): string | undefined { + return ( + req.cookies.get(sessionKey)?.value || + req.headers.get("Authorization")?.split(" ")[1] + ); +} + +function unauthorizedResponse() { + return new NextResponse(JSON.stringify({ error: "Unauthorized" }), { + status: 401, + headers: { "Content-Type": "application/json" }, + }); +} + +function unauthorizedResponseAPIUserValidate() { + return new NextResponse( + JSON.stringify({ error: "Unauthorized api user validate" }), + { + status: 401, + headers: { "Content-Type": "application/json" }, + } + ); +} + +function unauthorizedResponseTokenAPI() { + return new NextResponse( + JSON.stringify({ error: "Unauthorized token on API" }), + { + status: 401, + headers: { "Content-Type": "application/json" }, + } + ); +} + +function unauthorizedResponseTokenPAGE() { + return new NextResponse(JSON.stringify({ error: "Unauthorized on page" }), { + status: 401, + headers: { "Content-Type": "application/json" }, + }); +} + +function unauthorizedResponseAPI() { + return new NextResponse( + JSON.stringify({ error: "Unauthorized Response API" }), + { + status: 401, + headers: { "Content-Type": "application/json" }, + } + ); +} + +function unauthorizedResponseValidationAPIRequest() { + return new NextResponse( + JSON.stringify({ error: "Unauthorized validation api request" }), + { + status: 401, + headers: { "Content-Type": "application/json" }, + } + ); +} + +function unauthorizedResponseDataUserNotFound(req: NextRequest) { + return setCorsHeaders( + NextResponse.redirect(new URL("/invalid-user", req.url)) + ); +} + +function unauthorizedResponseUserNotActive(req: NextRequest) { + return setCorsHeaders( + NextResponse.redirect(new URL("/waiting-room", req.url)) + ); +} + +async function unauthorizedResponseValidationUser({ + loginPath, + sessionKey, + token, + req, +}: { + loginPath: string; + sessionKey: string; + token: string; + req: NextRequest; +}) { + const userLogout = await fetch(new URL("/api/auth/logout", req.url), { + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + }, + }); + + if (userLogout.ok) { + const response = NextResponse.redirect(new URL(loginPath, req.url)); + // Clear invalid token + response.cookies.delete(sessionKey); + return setCorsHeaders(response); + } + console.error("Error logging out user:", await userLogout.json()); + return setCorsHeaders( + NextResponse.redirect(new URL("/invalid-user", req.url)) + ); + // return setCorsHeaders( + // new NextResponse(JSON.stringify({ error: "Logout failed" }), { + // status: 500, + // headers: { "Content-Type": "application/json" }, + // }) + // ); +} + +function setCorsHeaders(res: NextResponse): NextResponse { + res.headers.set("Access-Control-Allow-Origin", "*"); + res.headers.set( + "Access-Control-Allow-Methods", + "GET, POST, PUT, DELETE, OPTIONS" + ); + res.headers.set( + "Access-Control-Allow-Headers", + "Content-Type, Authorization" + ); + return res; +} + +function handleCors(req: NextRequest): NextResponse | null { + if (req.method === "OPTIONS") { + return new NextResponse(null, { + status: 204, + headers: { + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS", + "Access-Control-Allow-Headers": "Content-Type, Authorization", + "Access-Control-Max-Age": "86400", + }, + }); + } + return null; +} + +async function verifyToken({ + token, + encodedKey, +}: { + token: string | undefined; + encodedKey: string; +}): Promise | null> { + if (!token) return null; + + try { + const enc = new TextEncoder().encode(encodedKey); + const { payload } = await jwtVerify(token, enc, { + algorithms: ["HS256"], + }); + return (payload.user as Record) || null; + } catch (error) { + console.error("Token verification failed:", error); + return null; + } +} + +export const config = { + matcher: ["/((?!_next|static|favicon.ico|manifest).*)"], +}; diff --git a/src/middleware.ts b/src/middleware.ts index 77d32d97..e1fb0d34 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -1,6 +1,5 @@ import { jwtVerify } from "jose"; import { NextRequest, NextResponse } from "next/server"; -import { cookies } from "next/headers"; type MiddlewareConfig = { apiPath: string; @@ -13,35 +12,25 @@ type MiddlewareConfig = { log: boolean; }; -const middlewareConfig: MiddlewareConfig = { +const CONFIG: MiddlewareConfig = { apiPath: "/api", loginPath: "/login", userPath: "/dev/home", publicRoutes: [ - // API "/", "/api/voting/*", "/api/collaboration/*", "/api/notifikasi/*", "/api/logs/*", "/api/auth/*", - // "/api/origin-url", - // "/api/job*", - - // ADMIN API - // >> buat dibawah sini << "/api/admin/donasi/*", "/api/admin/investasi/*", "/api/admin/collaboration/*", - - // Akses awal "/api/get-cookie", "/api/user/activation", "/api/user-validate", "/api/version", "/api/validation", - - // PAGE "/login", "/register", "/validasi", @@ -53,8 +42,6 @@ const middlewareConfig: MiddlewareConfig = { "/auth/api/login", "/waiting-room", "/zCoba/*", - - // ASSETS "/aset/global/main_background.png", "/aset/logo/logo-hipmi.png", ], @@ -65,22 +52,14 @@ const middlewareConfig: MiddlewareConfig = { }; export const middleware = async (req: NextRequest) => { - const { - apiPath, - encodedKey, - loginPath, - publicRoutes, - sessionKey, - validationApiRoute, - userPath, - } = middlewareConfig; + const { apiPath, encodedKey, loginPath, publicRoutes, sessionKey, userPath } = + CONFIG; const { pathname } = req.nextUrl; - const corsResponse = handleCors(req); - if (corsResponse) { - return corsResponse; - } + // Handle CORS preflight + const corsResponse = handleCors(req); + if (corsResponse) return corsResponse; // Check if route is public const isPublicRoute = isRoutePublic(pathname, publicRoutes, loginPath); @@ -88,7 +67,7 @@ export const middleware = async (req: NextRequest) => { return setCorsHeaders(NextResponse.next()); } - // Get token from cookies or Authorization header + // Get token from cookie or Authorization header const token = getToken(req, sessionKey); const user = await verifyToken({ token, encodedKey }); @@ -96,97 +75,83 @@ export const middleware = async (req: NextRequest) => { if (pathname === loginPath) { if (user) { const response = NextResponse.redirect(new URL(userPath, req.url)); - // Preserve token in cookie when redirecting - if (token) { - response.cookies.set(sessionKey, token, { - // httpOnly: true, - secure: process.env.NODE_ENV === "production", - sameSite: "lax", - path: "/", - }); - } + if (token) setTokenCookie(response, sessionKey, token); return setCorsHeaders(response); } return setCorsHeaders(NextResponse.next()); } - // Redirect to login if no user found + // Redirect to login if no valid user if (!user) { const response = NextResponse.redirect(new URL(loginPath, req.url)); - // Clear invalid token - response.cookies.delete(sessionKey); + deleteTokenCookie(response, sessionKey); return setCorsHeaders(response); } - // Handle API requests + // Handle API requests under /api if (pathname.startsWith(apiPath)) { - // const reqToken = req.headers.get("Authorization")?.split(" ")[1]; - if (!token) { - return setCorsHeaders(unauthorizedResponseTokenAPI()); - } + if (!token) return setCorsHeaders(unauthorizedResponseTokenAPI()); try { - const apiBaseUrl = process.env.NEXT_PUBLIC_API_URL || new URL(req.url).origin; - const validationResponse = await fetch(`${apiBaseUrl}/api/validation`, { - headers: { - Authorization: `Bearer ${token}`, - }, - }); + const validationResponse = await fetch( + `${apiBaseUrl}${CONFIG.validationApiRoute}`, + { + headers: { Authorization: `Bearer ${token}` }, + } + ); if (!validationResponse.ok) { console.error("Validation failed:", validationResponse.statusText); return setCorsHeaders(unauthorizedResponseAPI()); } - const validationResponseJson = await validationResponse.json(); - - if (validationResponseJson.success === false) { + const validationData = await validationResponse.json(); + if (validationData.success === false) { return setCorsHeaders(unauthorizedResponseDataUserNotFound(req)); } } catch (error) { - console.error( - "Error validating API request:", - (error as Error).message || error - ); + console.error("Error validating API request:", error); return setCorsHeaders(unauthorizedResponseValidationAPIRequest()); } } - // Handle /dev routes that require active status + // Handle /dev routes - active user check if (pathname.startsWith("/dev")) { try { const apiBaseUrl = process.env.NEXT_PUBLIC_API_URL || new URL(req.url).origin; + const userValidateResponse = await fetch( + `${apiBaseUrl}/api/user-validate`, + { + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + }, + } + ); - const userValidate = await fetch(`${apiBaseUrl}/api/user-validate`, { - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${token}`, - }, - }); - - if (!userValidate.ok) { - console.error("Validation failed:", userValidate.statusText); + if (!userValidateResponse.ok) { + console.error( + "User validation failed:", + userValidateResponse.statusText + ); return setCorsHeaders(unauthorizedResponseAPIUserValidate()); } - const userValidateJson = await userValidate.json(); + const userValidateJson = await userValidateResponse.json(); - if (userValidateJson.success == true && !userValidateJson.data) { - unauthorizedResponseDataUserNotFound(req); + if (userValidateJson.success === true && !userValidateJson.data) { + return setCorsHeaders(unauthorizedResponseDataUserNotFound(req)); } if (!userValidateJson.data.active) { return setCorsHeaders(unauthorizedResponseUserNotActive(req)); } } catch (error) { - console.error("Error api user validate:", error); - if (!token) { - console.error("Token is undefined"); - return setCorsHeaders(unauthorizedResponseTokenPAGE()); - } + console.error("Error during user validation API:", error); + if (!token) return setCorsHeaders(unauthorizedResponseTokenPAGE()); return setCorsHeaders( await unauthorizedResponseValidationUser({ loginPath, @@ -198,20 +163,13 @@ export const middleware = async (req: NextRequest) => { } } - // // Ensure token is preserved in cookie - // if (token) { - // response.cookies.set(sessionKey, token, { - // // httpOnly: true, - // secure: process.env.NODE_ENV === "production", - // sameSite: "lax", - // path: "/", - // }); - // } + // Default: proceed with request and add CORS headers const response = NextResponse.next(); return setCorsHeaders(response); }; -// ============================== RESPONSE HANDLERS ==============================// +// ========================== HELPERS ========================== + function isRoutePublic( pathname: string, publicRoutes: string[], @@ -224,112 +182,36 @@ function isRoutePublic( } function getToken(req: NextRequest, sessionKey: string): string | undefined { - return ( - req.cookies.get(sessionKey)?.value || - req.headers.get("Authorization")?.split(" ")[1] - ); -} + const tokenFromCookie = req.cookies.get(sessionKey)?.value; + if (tokenFromCookie) return tokenFromCookie; -function unauthorizedResponse() { - return new NextResponse(JSON.stringify({ error: "Unauthorized" }), { - status: 401, - headers: { "Content-Type": "application/json" }, - }); -} - -function unauthorizedResponseAPIUserValidate() { - return new NextResponse( - JSON.stringify({ error: "Unauthorized api user validate" }), - { - status: 401, - headers: { "Content-Type": "application/json" }, - } - ); -} - -function unauthorizedResponseTokenAPI() { - return new NextResponse( - JSON.stringify({ error: "Unauthorized token on API" }), - { - status: 401, - headers: { "Content-Type": "application/json" }, - } - ); -} - -function unauthorizedResponseTokenPAGE() { - return new NextResponse(JSON.stringify({ error: "Unauthorized on page" }), { - status: 401, - headers: { "Content-Type": "application/json" }, - }); -} - -function unauthorizedResponseAPI() { - return new NextResponse( - JSON.stringify({ error: "Unauthorized Response API" }), - { - status: 401, - headers: { "Content-Type": "application/json" }, - } - ); -} - -function unauthorizedResponseValidationAPIRequest() { - return new NextResponse( - JSON.stringify({ error: "Unauthorized validation api request" }), - { - status: 401, - headers: { "Content-Type": "application/json" }, - } - ); -} - -function unauthorizedResponseDataUserNotFound(req: NextRequest) { - return setCorsHeaders( - NextResponse.redirect(new URL("/invalid-user", req.url)) - ); -} - -function unauthorizedResponseUserNotActive(req: NextRequest) { - return setCorsHeaders( - NextResponse.redirect(new URL("/waiting-room", req.url)) - ); -} - -async function unauthorizedResponseValidationUser({ - loginPath, - sessionKey, - token, - req, -}: { - loginPath: string; - sessionKey: string; - token: string; - req: NextRequest; -}) { - const userLogout = await fetch(new URL("/api/auth/logout", req.url), { - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${token}`, - }, - }); - - if (userLogout.ok) { - const response = NextResponse.redirect(new URL(loginPath, req.url)); - // Clear invalid token - response.cookies.delete(sessionKey); - return setCorsHeaders(response); + const authHeader = req.headers.get("Authorization"); + if (authHeader && authHeader.startsWith("Bearer ")) { + return authHeader.split(" ")[1]; } - console.error("Error logging out user:", await userLogout.json()); - return setCorsHeaders( - NextResponse.redirect(new URL("/invalid-user", req.url)) - ); - // return setCorsHeaders( - // new NextResponse(JSON.stringify({ error: "Logout failed" }), { - // status: 500, - // headers: { "Content-Type": "application/json" }, - // }) - // ); + return undefined; +} + + +function cookieOptions() { + return { + secure: process.env.NODE_ENV === "production", + sameSite: "lax" as const, + path: "/", + httpOnly: true, + }; +} + +function setTokenCookie( + response: NextResponse, + sessionKey: string, + token: string +) { + response.cookies.set(sessionKey, token, cookieOptions()); +} + +function deleteTokenCookie(response: NextResponse, sessionKey: string) { + response.cookies.delete(sessionKey); } function setCorsHeaders(res: NextResponse): NextResponse { @@ -371,16 +253,110 @@ async function verifyToken({ try { const enc = new TextEncoder().encode(encodedKey); - const { payload } = await jwtVerify(token, enc, { - algorithms: ["HS256"], - }); - return (payload.user as Record) || null; + const { payload } = await jwtVerify(token, enc, { algorithms: ["HS256"] }); + return (payload.user as Record) || null; } catch (error) { console.error("Token verification failed:", error); return null; } } +function unauthorizedResponse() { + return new NextResponse(JSON.stringify({ error: "Unauthorized" }), { + status: 401, + headers: { "Content-Type": "application/json" }, + }); +} + +function unauthorizedResponseTokenAPI() { + return new NextResponse( + JSON.stringify({ error: "Unauthorized token on API" }), + { + status: 401, + headers: { "Content-Type": "application/json" }, + } + ); +} + +function unauthorizedResponseTokenPAGE() { + return new NextResponse(JSON.stringify({ error: "Unauthorized on page" }), { + status: 401, + headers: { "Content-Type": "application/json" }, + }); +} + +function unauthorizedResponseAPI() { + return new NextResponse( + JSON.stringify({ error: "Unauthorized Response API" }), + { + status: 401, + headers: { "Content-Type": "application/json" }, + } + ); +} + +function unauthorizedResponseValidationAPIRequest() { + return new NextResponse( + JSON.stringify({ error: "Unauthorized validation api request" }), + { + status: 401, + headers: { "Content-Type": "application/json" }, + } + ); +} + +function unauthorizedResponseAPIUserValidate() { + return new NextResponse( + JSON.stringify({ error: "Unauthorized api user validate" }), + { + status: 401, + headers: { "Content-Type": "application/json" }, + } + ); +} + +function unauthorizedResponseDataUserNotFound(req: NextRequest) { + return setCorsHeaders( + NextResponse.redirect(new URL("/invalid-user", req.url)) + ); +} + +function unauthorizedResponseUserNotActive(req: NextRequest) { + return setCorsHeaders( + NextResponse.redirect(new URL("/waiting-room", req.url)) + ); +} + +async function unauthorizedResponseValidationUser({ + loginPath, + sessionKey, + token, + req, +}: { + loginPath: string; + sessionKey: string; + token: string; + req: NextRequest; +}) { + const userLogout = await fetch(new URL("/api/auth/logout", req.url), { + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + }, + }); + + if (userLogout.ok) { + const response = NextResponse.redirect(new URL(loginPath, req.url)); + deleteTokenCookie(response, sessionKey); + return setCorsHeaders(response); + } + + console.error("Error logging out user:", await userLogout.json()); + return setCorsHeaders( + NextResponse.redirect(new URL("/invalid-user", req.url)) + ); +} + export const config = { matcher: ["/((?!_next|static|favicon.ico|manifest).*)"], };