-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathproxy.ts
More file actions
156 lines (133 loc) · 5.59 KB
/
proxy.ts
File metadata and controls
156 lines (133 loc) · 5.59 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
// Simple in-memory rate limiter (Token Bucket-ish)
// Note: In a serverless/edge environment, this state is not shared between instances.
// For production robust rate limiting, use Redis (e.g., Upstash).
const rateLimitMap = new Map();
// Configuration
const RATE_LIMIT_WINDOW = 60 * 1000; // 1 minute
const MAX_REQUESTS_PER_WINDOW = 300; // 300 requests per minute per IP (Targeted routes)
const BLOCKED_IPS = new Set<string>(); // Add known bad IPs here
// WAF: Block common malicious patterns
const MALICIOUS_PATTERNS = [
/\.env$/,
/\.git/,
/\.aws/,
/wp-admin/,
/wp-login/,
/composer\.lock/,
/yarn\.lock/,
/package\.json/,
/eval\(/,
/union select/,
/<script>/,
/alert\(/,
];
// WAF: Block suspicious User Agents
const SUSPICIOUS_USER_AGENTS = [
'sqlmap',
'nikto',
'curb',
'gobuster',
'python-requests', // Often used by bots, be careful if you use python scripts extensively
'nmap',
'burp', // Burp Suite and similar
'nessus',
'nuclei',
'acunetix',
'netsparker',
];
function extractClientIp(request: NextRequest) {
const forwarded = request.headers.get('x-forwarded-for');
if (forwarded) {
return forwarded.split(',')[0].trim();
}
const realIp = request.headers.get('x-real-ip');
if (realIp) {
return realIp;
}
return 'unknown';
}
export function proxy(request: NextRequest) {
// 0. Filter out Next.js Prefetches
if (request.headers.get('x-nextjs-prefetch') || request.headers.get('x-purpose') === 'prefetch') {
return NextResponse.next();
}
const ip = extractClientIp(request);
const path = request.nextUrl.pathname;
const userAgent = request.headers.get('user-agent') || '';
// 1. IP Check
if (BLOCKED_IPS.has(ip)) {
return new NextResponse(JSON.stringify({ error: 'Access Denied: Your IP is blocked.' }), { status: 403, headers: { 'Content-Type': 'application/json' } });
}
// 2. WAF - User Agent Scan
const isSuspiciousAgent = SUSPICIOUS_USER_AGENTS.some((agent) =>
userAgent.toLowerCase().includes(agent.toLowerCase())
);
if (isSuspiciousAgent) {
console.warn(`[WAF] Blocked suspicious User-Agent (${userAgent}) from IP: ${ip}`);
return new NextResponse(JSON.stringify({ error: 'Access Denied: 403 Forbidden' }), { status: 403, headers: { 'Content-Type': 'application/json' } });
}
// 3. WAF - Path & Query Scan
const isMaliciousPath = MALICIOUS_PATTERNS.some((pattern) => pattern.test(path));
const queryParams = request.nextUrl.search;
const isMaliciousQuery = MALICIOUS_PATTERNS.some((pattern) => pattern.test(decodeURIComponent(queryParams)));
if (isMaliciousPath || isMaliciousQuery) {
console.warn(`[WAF] Blocked malicious request to ${path} from IP: ${ip}`);
return new NextResponse(JSON.stringify({ error: 'Access Denied: Malicious Request Detected' }), { status: 403, headers: { 'Content-Type': 'application/json' } });
}
// Security: Strict Mode for Admin Routes
// Prevent automated tools from bruteforcing /admin even if they bypass other checks
if (path.startsWith('/admin') && !path.startsWith('/admin/login')) {
// Here we could add stricter checks, e.g., requiring a specific header or cookie that only the login page sets.
// For now, we rely on the client-side Firebase Auth + Server-side API token verification.
}
// 4. Rate Limiting (Simple In-Memory)
// Clean up old entries periodically (simple implementation)
const now = Date.now();
const windowStart = now - RATE_LIMIT_WINDOW;
const requestHistory = rateLimitMap.get(ip) || [];
const validRequests = requestHistory.filter((timestamp: number) => timestamp > windowStart);
// Stricter Rate Limit for Admin Login
const limit = path.includes('/admin/login') ? 20 : MAX_REQUESTS_PER_WINDOW;
if (validRequests.length >= limit) {
return new NextResponse(
JSON.stringify({ error: 'Too Many Requests', retryAfter: Math.ceil((RATE_LIMIT_WINDOW - (now - validRequests[0])) / 1000) }),
{ status: 429, headers: { 'Content-Type': 'application/json', 'Retry-After': '60' } }
);
}
validRequests.push(now);
rateLimitMap.set(ip, validRequests);
// 5. Security Headers for all responses
const response = NextResponse.next();
// CSP: Allow typical sources but restrict strict inline scripts where possible
// Note: 'unsafe-inline' is often needed for Styled Components or some Next.js features if not using nonces.
// We'll trust self and firebase domains.
const cspHeader = `
default-src 'self';
connect-src 'self' https://identitytoolkit.googleapis.com https://securetoken.googleapis.com https://*.firebaseapp.com https://*.googleapis.com;
script-src 'self' 'unsafe-inline' 'unsafe-eval' https://apis.google.com https://*.firebaseapp.com https://*.googleapis.com;
style-src 'self' 'unsafe-inline';
img-src 'self' blob: data: https://*.blob.core.windows.net;
font-src 'self';
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
block-all-mixed-content;
upgrade-insecure-requests;
`;
response.headers.set('Content-Security-Policy', cspHeader.replace(/\s{2,}/g, ' ').trim());
response.headers.set('X-Content-Type-Options', 'nosniff');
response.headers.set('X-Frame-Options', 'DENY');
response.headers.set('X-XSS-Protection', '1; mode=block');
response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');
response.headers.set('Permissions-Policy', 'camera=(), microphone=(), geolocation=(), interest-cohort=()');
return response;
}
export const config = {
matcher: [
'/api/:path*',
'/admin/:path*',
],
};