|
1 |
| -// middleware.js |
2 |
| -import { NextResponse } from 'next/server' |
| 1 | +// src/middleware.ts |
| 2 | +import { NextResponse, NextRequest } from 'next/server' |
3 | 3 |
|
4 |
| -import { NextRequest } from 'next/server' |
| 4 | +/** |
| 5 | + * ANSI color codes for console output in Docker |
| 6 | + */ |
| 7 | +const colors = { |
| 8 | + reset: '\x1b[0m', |
| 9 | + bright: '\x1b[1m', |
| 10 | + dim: '\x1b[2m', |
| 11 | + red: '\x1b[31m', |
| 12 | + green: '\x1b[32m', |
| 13 | + yellow: '\x1b[33m', |
| 14 | + blue: '\x1b[34m', |
| 15 | + magenta: '\x1b[35m', |
| 16 | + cyan: '\x1b[36m', |
| 17 | + white: '\x1b[37m', |
| 18 | + gray: '\x1b[90m', |
| 19 | +} |
| 20 | + |
| 21 | +/** |
| 22 | + * Get colored status code based on HTTP status |
| 23 | + */ |
| 24 | +function getColoredStatus(status: number): string { |
| 25 | + let color = colors.gray |
| 26 | + if (status >= 200 && status < 300) color = colors.green |
| 27 | + else if (status >= 300 && status < 400) color = colors.yellow |
| 28 | + else if (status >= 400 && status < 500) color = colors.red |
| 29 | + else if (status >= 500) color = colors.red + colors.bright |
| 30 | + |
| 31 | + return `${color}${status}${colors.reset}` |
| 32 | +} |
| 33 | + |
| 34 | +/** |
| 35 | + * Get colored HTTP method |
| 36 | + */ |
| 37 | +function getColoredMethod(method: string): string { |
| 38 | + const methodColors: Record<string, string> = { |
| 39 | + GET: colors.green, |
| 40 | + POST: colors.blue, |
| 41 | + PUT: colors.yellow, |
| 42 | + DELETE: colors.red, |
| 43 | + PATCH: colors.magenta, |
| 44 | + HEAD: colors.cyan, |
| 45 | + OPTIONS: colors.gray, |
| 46 | + } |
| 47 | + |
| 48 | + const color = methodColors[method] || colors.white |
| 49 | + return `${color}${method.padEnd(7)}${colors.reset}` |
| 50 | +} |
| 51 | + |
| 52 | +/** |
| 53 | + * Format duration with appropriate color |
| 54 | + */ |
| 55 | +function getColoredDuration(ms: number): string { |
| 56 | + let color = colors.green |
| 57 | + if (ms > 1000) color = colors.red |
| 58 | + else if (ms > 500) color = colors.yellow |
| 59 | + else if (ms > 100) color = colors.cyan |
| 60 | + |
| 61 | + return `${color}${ms.toFixed(0)}ms${colors.reset}` |
| 62 | +} |
| 63 | + |
| 64 | +/** |
| 65 | + * Get client IP address from various headers |
| 66 | + */ |
| 67 | +function getClientIP(request: NextRequest): string { |
| 68 | + // Check various headers that might contain the real IP |
| 69 | + const xForwardedFor = request.headers.get('x-forwarded-for') |
| 70 | + const xRealIP = request.headers.get('x-real-ip') |
| 71 | + const cfConnectingIP = request.headers.get('cf-connecting-ip') |
| 72 | + |
| 73 | + if (xForwardedFor) { |
| 74 | + // x-forwarded-for can contain multiple IPs, take the first one |
| 75 | + return xForwardedFor.split(',')[0].trim() |
| 76 | + } |
| 77 | + |
| 78 | + if (xRealIP) return xRealIP |
| 79 | + if (cfConnectingIP) return cfConnectingIP |
| 80 | + |
| 81 | + // Fallback to connection remote address (usually won't work in Docker) |
| 82 | + return 'unknown' |
| 83 | +} |
5 | 84 |
|
6 | 85 | /**
|
7 |
| - * Middleware function to handle incoming requests and set the Content-Security-Policy header. |
8 |
| - * |
9 |
| - * The Content-Security-Policy header is configured to: |
10 |
| - * - Allow resources from the same origin ('self') by default. |
11 |
| - * - Allow scripts from the same origin ('self'). |
12 |
| - * - Allow web workers from the same origin ('self') and https://new.codebuilder.org. |
13 |
| - * - Report CSP violations to https://new.codebuilder.org/csp-report. |
| 86 | + * Check if the request should be logged |
| 87 | + */ |
| 88 | +function shouldLog(pathname: string): boolean { |
| 89 | + // Skip logging for static assets and Next.js internal routes |
| 90 | + const skipPatterns = [ |
| 91 | + '/_next/', |
| 92 | + '/favicon.ico', |
| 93 | + '/robots.txt', |
| 94 | + '/sitemap.xml', |
| 95 | + '/.well-known/', |
| 96 | + '/api/health', |
| 97 | + '/health', |
| 98 | + ] |
| 99 | + |
| 100 | + return !skipPatterns.some((pattern) => pathname.startsWith(pattern)) |
| 101 | +} |
| 102 | + |
| 103 | +/** |
| 104 | + * Enhanced middleware function with comprehensive HTTP access logging |
14 | 105 | */
|
15 | 106 | export function middleware(request: NextRequest): NextResponse {
|
| 107 | + const startTime = Date.now() |
| 108 | + const { pathname, search } = request.nextUrl |
| 109 | + const method = request.method |
| 110 | + const userAgent = request.headers.get('user-agent') || 'unknown' |
| 111 | + const referer = request.headers.get('referer') || '-' |
| 112 | + const clientIP = getClientIP(request) |
| 113 | + |
| 114 | + // Create response |
16 | 115 | const response = NextResponse.next()
|
17 | 116 |
|
18 |
| - // response.headers.set( |
19 |
| - // 'Content-Security-Policy', |
20 |
| - // ` |
21 |
| - // default-src 'self'; |
22 |
| - // script-src 'self'; |
23 |
| - // worker-src 'self' https://new.codebuilder.org; |
24 |
| - // report-uri https://new.codebuilder.org/csp-report; |
25 |
| - // ` |
26 |
| - // .replace(/\s{2,}/g, ' ') |
27 |
| - // .trim() |
28 |
| - // ) |
| 117 | + // Only log if this request should be logged |
| 118 | + if (shouldLog(pathname)) { |
| 119 | + // Add a custom header to track response time |
| 120 | + response.headers.set('x-request-start', startTime.toString()) |
| 121 | + |
| 122 | + // Log the request immediately (before processing) |
| 123 | + const timestamp = new Date().toISOString() |
| 124 | + const fullUrl = pathname + search |
| 125 | + |
| 126 | + console.log( |
| 127 | + `${colors.gray}[${timestamp}]${colors.reset} ` + |
| 128 | + `${getColoredMethod(method)} ` + |
| 129 | + `${colors.cyan}${fullUrl}${colors.reset} ` + |
| 130 | + `${colors.gray}from ${clientIP}${colors.reset}` |
| 131 | + ) |
| 132 | + |
| 133 | + // Log additional details in development |
| 134 | + if (process.env.NODE_ENV === 'development') { |
| 135 | + console.log(`${colors.dim} └─ UA: ${userAgent}${colors.reset}`) |
| 136 | + if (referer !== '-') { |
| 137 | + console.log(`${colors.dim} └─ Referer: ${referer}${colors.reset}`) |
| 138 | + } |
| 139 | + } |
| 140 | + } |
29 | 141 |
|
30 | 142 | return response
|
31 | 143 | }
|
| 144 | + |
| 145 | +/** |
| 146 | + * Configure which paths the middleware should run on |
| 147 | + */ |
| 148 | +export const config = { |
| 149 | + matcher: [ |
| 150 | + /* |
| 151 | + * Match all request paths except for the ones starting with: |
| 152 | + * - api (API routes) |
| 153 | + * - _next/static (static files) |
| 154 | + * - _next/image (image optimization files) |
| 155 | + * - favicon.ico (favicon file) |
| 156 | + */ |
| 157 | + '/((?!_next/static|_next/image|favicon.ico).*)', |
| 158 | + ], |
| 159 | +} |
0 commit comments