From 04b1bb734f832934fa2d2d9d764f73ec65d14264 Mon Sep 17 00:00:00 2001 From: BaoVu2k4 Date: Tue, 25 Aug 2026 13:49:52 +0700 Subject: [PATCH] Gate /profile behind login, same pattern as /admin --- apps/web/middleware.ts | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/apps/web/middleware.ts b/apps/web/middleware.ts index bf9725a..d5e8965 100644 --- a/apps/web/middleware.ts +++ b/apps/web/middleware.ts @@ -124,10 +124,34 @@ async function guardAdmin(request: NextRequest): Promise { } } +/** `/profile` requires any valid session (no specific role) — it renders + * unconditionally otherwise (`profile/page.tsx` has no server component + * data fetch to fail on), so an anonymous visitor would see the full form + * shell with every save silently 401ing instead of being sent to log in. */ +async function guardProfile(request: NextRequest): Promise { + const { pathname } = request.nextUrl; + if (pathname !== "/profile") return null; + + const token = request.cookies.get(SESSION_COOKIE)?.value; + const secret = process.env.JWT_SECRET; + if (!token || !secret) { + return NextResponse.redirect(new URL("/login", request.url)); + } + try { + await jwtVerify(token, new TextEncoder().encode(secret)); + return null; + } catch { + return NextResponse.redirect(new URL("/login", request.url)); + } +} + export async function middleware(request: NextRequest) { const adminRedirect = await guardAdmin(request); if (adminRedirect) return adminRedirect; + const profileRedirect = await guardProfile(request); + if (profileRedirect) return profileRedirect; + const rules = matchRules(request.nextUrl.pathname); if (!rules) return NextResponse.next(); @@ -177,5 +201,5 @@ export async function middleware(request: NextRequest) { } export const config = { - matcher: ["/api/:path*", "/admin/:path*"], + matcher: ["/api/:path*", "/admin/:path*", "/profile"], };