import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server'; import { jwtVerify } from 'jose'; const CUSTOMER_SESSION_COOKIE = 'customer_session'; const PUBLIC_ACCOUNT_PATHS = ['/account/login', '/account/register', '/account/forgot-password', '/account/reset-password']; const ADMIN_SESSION_COOKIE = 'admin_session'; const PUBLIC_ADMIN_PATHS = ['/admin/login']; // Protects everything under /admin with the shop owner's own login session — // entirely separate cookie and secret from the customer auth below, so // logging into one never grants access to the other. async function checkAdminAuth(request: NextRequest, requestHeaders: Headers) { if (PUBLIC_ADMIN_PATHS.includes(request.nextUrl.pathname)) { return NextResponse.next({ request: { headers: requestHeaders } }); } const token = request.cookies.get(ADMIN_SESSION_COOKIE)?.value; const secret = process.env.ADMIN_SESSION_SECRET; if (token && secret) { try { await jwtVerify(token, new TextEncoder().encode(secret)); return NextResponse.next({ request: { headers: requestHeaders } }); } catch { // fall through to redirect } } const loginUrl = new URL('/admin/login', request.url); loginUrl.searchParams.set('next', request.nextUrl.pathname); return NextResponse.redirect(loginUrl); } // Protects everything under /account (except the login/register/reset pages // themselves) with a customer's own login session — entirely separate cookie // and secret from the admin auth above, so one never grants access to the other. async function checkCustomerAuth(request: NextRequest, requestHeaders: Headers) { if (PUBLIC_ACCOUNT_PATHS.includes(request.nextUrl.pathname)) { return NextResponse.next({ request: { headers: requestHeaders } }); } const token = request.cookies.get(CUSTOMER_SESSION_COOKIE)?.value; const secret = process.env.SESSION_SECRET; if (token && secret) { try { await jwtVerify(token, new TextEncoder().encode(secret)); return NextResponse.next({ request: { headers: requestHeaders } }); } catch { // fall through to redirect } } const loginUrl = new URL('/account/login', request.url); loginUrl.searchParams.set('next', request.nextUrl.pathname); return NextResponse.redirect(loginUrl); } export function middleware(request: NextRequest) { const { pathname } = request.nextUrl; // Expose the path to server components (the root layout reads x-pathname to // decide whether to show the maintenance page). Edge middleware can't read the // DB, so the maintenance flag itself is checked in the layout, not here. const requestHeaders = new Headers(request.headers); requestHeaders.set('x-pathname', pathname); if (pathname.startsWith('/admin')) { return checkAdminAuth(request, requestHeaders); } if (pathname.startsWith('/account')) { return checkCustomerAuth(request, requestHeaders); } return NextResponse.next({ request: { headers: requestHeaders } }); } export const config = { // Run on everything except static assets, so the x-pathname header is set on // every page. The auth branches above still only gate /admin and /account. matcher: ['/((?!_next/static|_next/image|favicon.ico|logo.png).*)'], };