11import { NextRequest , NextResponse } from 'next/server'
22import { getSessionCookie } from 'better-auth/cookies'
33import { verifyToken } from './lib/waitlist/token'
4+ import { createLogger } from '@/lib/logs/console-logger'
5+
6+ const logger = createLogger ( 'Middleware' )
47
58// Environment flag to check if we're in development mode
69const isDevelopment = process . env . NODE_ENV === 'development'
710
11+ const SUSPICIOUS_UA_PATTERNS = [
12+ / ^ \s * $ / , // Empty user agents
13+ / \. \. / , // Path traversal attempt
14+ / < \s * s c r i p t / i, // Potential XSS payloads
15+ / ^ \( \) \s * { / , // Command execution attempt
16+ / \b ( s q l m a p | n i k t o | g o b u s t e r | d i r b | n m a p ) \b / i // Known scanning tools
17+ ]
18+
819export async function middleware ( request : NextRequest ) {
920 // Check for active session
1021 const sessionCookie = getSessionCookie ( request )
@@ -66,7 +77,7 @@ export async function middleware(request: NextRequest) {
6677 return NextResponse . redirect ( new URL ( '/' , request . url ) )
6778 }
6879 } catch ( error ) {
69- console . error ( 'Token validation error:' , error )
80+ logger . error ( 'Token validation error:' , error )
7081 // In case of error, redirect signup attempts to home
7182 if ( request . nextUrl . pathname === '/signup' ) {
7283 return NextResponse . redirect ( new URL ( '/' , request . url ) )
@@ -80,15 +91,49 @@ export async function middleware(request: NextRequest) {
8091 }
8192 }
8293
83- return NextResponse . next ( )
94+ const userAgent = request . headers . get ( 'user-agent' ) || ''
95+
96+ const isSuspicious = SUSPICIOUS_UA_PATTERNS . some ( pattern =>
97+ pattern . test ( userAgent )
98+ )
99+
100+ if ( isSuspicious ) {
101+ logger . warn ( 'Blocked suspicious request' , {
102+ userAgent,
103+ ip : request . headers . get ( 'x-forwarded-for' ) || 'unknown' ,
104+ url : request . url ,
105+ method : request . method ,
106+ pattern : SUSPICIOUS_UA_PATTERNS . find ( pattern => pattern . test ( userAgent ) ) ?. toString ( )
107+ } )
108+
109+ // Return 403 with security headers
110+ return new NextResponse ( null , {
111+ status : 403 ,
112+ statusText : 'Forbidden' ,
113+ headers : {
114+ 'Content-Type' : 'text/plain' ,
115+ 'X-Content-Type-Options' : 'nosniff' ,
116+ 'X-Frame-Options' : 'DENY' ,
117+ 'Content-Security-Policy' : "default-src 'none'" ,
118+ 'Cache-Control' : 'no-store, no-cache, must-revalidate, proxy-revalidate' ,
119+ 'Pragma' : 'no-cache' ,
120+ 'Expires' : '0'
121+ }
122+ } )
123+ }
124+
125+ const response = NextResponse . next ( )
126+
127+ response . headers . set ( 'Vary' , 'User-Agent' )
128+
129+ return response
84130}
85131
86- // Update matcher to include admin routes
87132export const config = {
88133 matcher : [
89134 '/w' , // Match exactly /w
90135 '/w/:path*' , // Match protected routes
91136 '/login' ,
92- '/signup' ,
137+ '/signup'
93138 ] ,
94139}
0 commit comments