import { NextRequest, NextResponse } from 'next/server' const PUBLIC_PATHS = ['/login'] const CSP = [ "default-src 'self'", "script-src 'self' 'unsafe-inline'", "style-src 'self' 'unsafe-inline'", "font-src 'self'", "img-src 'self' data:", "connect-src 'self'", "frame-ancestors 'none'", "base-uri 'self'", "form-action 'self'", ].join('; ') const CSRF_COOKIE = 'csrf_token' const CSRF_HEADER = 'x-csrf-token' export default function proxy(request: NextRequest) { const { method, nextUrl } = request const { pathname } = nextUrl if (method === 'POST' && pathname.startsWith('/api/')) { const cookieToken = request.cookies.get(CSRF_COOKIE)?.value const headerToken = request.headers.get(CSRF_HEADER) if (!cookieToken || cookieToken !== headerToken) { return new NextResponse( JSON.stringify({ error: 'CSRF validation failed' }), { status: 403, headers: { 'Content-Type': 'application/json' } }, ) } } if (!pathname.startsWith('/api/')) { const isPublic = PUBLIC_PATHS.some((p) => pathname.startsWith(p)) const hasSession = request.cookies.has('ops_session') if (!isPublic && !hasSession) { return NextResponse.redirect(new URL('/login', request.url)) } if (isPublic && hasSession) { return NextResponse.redirect(new URL('/', request.url)) } } const response = NextResponse.next() response.headers.set('Content-Security-Policy', CSP) response.headers.set('X-Frame-Options', 'DENY') response.headers.set('X-Content-Type-Options', 'nosniff') response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin') if (method === 'GET' && !request.cookies.get(CSRF_COOKIE)) { response.cookies.set(CSRF_COOKIE, crypto.randomUUID(), { httpOnly: false, sameSite: 'strict', secure: process.env.NODE_ENV === 'production', path: '/', }) } return response } export const config = { matcher: ['/((?!_next/static|_next/image|favicon.ico|.*\\.(?:png|ico|svg)$).*)'], }