// File: proxy.ts import { NextResponse } from 'next/server'; import { auth } from '@/lib/auth'; // รายการ Route ที่ไม่ต้อง Login ก็เข้าได้ (Public Routes) const publicRoutes = ['/login', '/register', '/']; export default auth((req) => { const isLoggedIn = !!req.auth; const { nextUrl } = req; const isPublicRoute = publicRoutes.includes(nextUrl.pathname); const isAuthRoute = nextUrl.pathname.startsWith('/api/auth'); // 1. ถ้าเป็น API Auth routes ให้ผ่านไปเลย if (isAuthRoute) { return NextResponse.next(); // แก้ไขจาก null เพื่อความถูกต้องของ Type ใน Next.js 14 } // 2. ถ้า Login อยู่แล้ว แต่พยายามเข้าหน้า Login -> ให้ไป Dashboard if (isLoggedIn && nextUrl.pathname === '/login') { return Response.redirect(new URL('/dashboard', nextUrl)); } // 3. ถ้ายังไม่ Login และพยายามเข้า Private Route -> ให้ไป Login if (!isLoggedIn && !isPublicRoute) { // สร้าง URL สำหรับ Redirect กลับมาหน้าเดิมหลังจาก Login เสร็จ let callbackUrl = nextUrl.pathname; if (nextUrl.search) { callbackUrl += nextUrl.search; } const encodedCallbackUrl = encodeURIComponent(callbackUrl); return Response.redirect(new URL(`/login?callbackUrl=${encodedCallbackUrl}`, nextUrl)); } // 4. Protect Admin Routes (Security Phase 1) if (nextUrl.pathname.startsWith('/admin')) { const userRole = req.auth?.user?.role as string | undefined; if (userRole !== 'ADMIN' && userRole !== 'DC') { return Response.redirect(new URL('/dashboard', nextUrl)); } } // 5. Generate CSP with Nonce (Security Rule Tier 1) // ใช้ Nonce Strategy เพื่ออนุญาต Inline Script เฉพาะที่ระบุตัวตนได้ ป้องกัน XSS const nonce = Buffer.from(crypto.randomUUID()).toString('base64'); let connectSrcApi = 'http://localhost:3001'; if (process.env.NEXT_PUBLIC_API_URL) { try { connectSrcApi = new URL(process.env.NEXT_PUBLIC_API_URL).origin; } catch { connectSrcApi = process.env.NEXT_PUBLIC_API_URL; } } const cspHeader = ` default-src 'self'; script-src 'self' 'nonce-${nonce}' 'strict-dynamic' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' blob: data: https:; font-src 'self' data:; connect-src 'self' ws: wss: ${connectSrcApi}; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'; upgrade-insecure-requests; ` .replace(/\s{2,}/g, ' ') .trim(); const requestHeaders = new Headers(req.headers); requestHeaders.set('x-nonce', nonce); requestHeaders.set('Content-Security-Policy', cspHeader); const response = NextResponse.next({ request: { headers: requestHeaders, }, }); response.headers.set('Content-Security-Policy', cspHeader); return response; }); // กำหนดว่า Middleware นี้จะทำงานกับ Route ไหนบ้าง export const config = { matcher: [ /* * Match all request paths except for the ones starting with: * - api (API routes) * - _next/static (static files) * - _next/image (image optimization files) * - favicon.ico (favicon file) * - robots.txt (robots file) * - images (public images) */ '/((?!api|_next/static|_next/image|favicon.ico|robots.txt|images).*)', ], };