Repository navigation
Expand file tree
/
Copy pathmiddleware.js
More file actions
93 lines (85 loc) · 3.89 KB
/
Copy pathmiddleware.js
File metadata and controls
93 lines (85 loc) · 3.89 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
// 🛡️ [FIX-1] Vercel Edge Middleware — /api/config, /api/bootstrap-key,
// /api/identity, /api/auth, /api/ice-servers uçlarına IP başına dakikalık
// rate limit uygular.
//
// ⚠️ NOT: Bu sürüm HİÇBİR npm paketine ihtiyaç duymaz (Upstash kaldırıldı) —
// çünkü @upstash/ratelimit ve @upstash/redis, `npm install` ile projeye
// eklenip Vercel build'i bunları bundle etmeden Edge Runtime'da
// çalışmıyor ("referencing unsupported modules" hatası buradan geliyordu).
// Bu haliyle dosyayı GitHub'a yapıştırmanız yeterli, ekstra kurulum YOK.
//
// TRADE-OFF: Rate limit sayaçları bellekte (instance başına) tutulur.
// Yani (a) Vercel her cold start'ta veya farklı bir edge bölgesine
// yönlendirmede sayaç sıfırlanabilir, (b) çok yüksek trafikte %100 kesin
// bir garanti vermez. Yine de mevcut durumdan (SIFIR limit) çok daha
// iyidir ve script/bot spam'ini büyük ölçüde engeller. İleride daha güçlü
// bir garantiye ihtiyaç duyarsanız Upstash Redis'e geçebiliriz — o zaman
// package.json'a paketleri gerçekten eklemeniz (npm install) ve Vercel'de
// Upstash entegrasyonunu kurmanız gerekir.
//
// KONUM: Proje kökü — package.json, vercel.json ve api/ klasörüyle AYNI
// SEVİYEDE. api/ klasörünün İÇİNE KOYMAYIN.
export const config = {
matcher: ['/api/config', '/api/bootstrap-key', '/api/identity', '/api/auth', '/api/ice-servers'],
};
// IP başına dakikada izin verilen istek sayısı.
const WINDOW_MS = 60_000;
const MAX_REQUESTS = 20;
// Bellek-içi sayaç. Edge instance'ı yeniden başlayınca sıfırlanır — bu
// beklenen ve kabul edilebilir bir trade-off (yukarıdaki nota bakın).
const _hits = new Map();
function isAllowed(ip) {
const now = Date.now();
const arr = (_hits.get(ip) || []).filter((t) => now - t < WINDOW_MS);
// Map'in sınırsız büyümesini önlemek için basit bir temizlik:
// (limite takılan isteklerde de çalışsın ki benzersiz IP selinde Map
// şişemesin)
if (_hits.size > 5000) {
const cutoff = now - WINDOW_MS;
for (const [key, times] of _hits) {
if (!times.some((t) => t > cutoff)) _hits.delete(key);
}
}
// 🛡️ [SEC FIX] Sayaç, limit aşıldıktan sonra da her isteği kaydediyordu;
// tek bir IP dakikada yüz binlerce istekle diziyi şişirebiliyordu.
// Artık pencere doluysa push yapılmaz — dizi uzunluğu en fazla
// MAX_REQUESTS kalır (davranış aynı: pencere içinde MAX_REQUESTS'ten
// fazlası 429 döner).
if (arr.length >= MAX_REQUESTS) {
_hits.set(ip, arr);
return false;
}
arr.push(now);
_hits.set(ip, arr);
return true;
}
export default function middleware(request) {
// 🛡️ Basit origin kontrolü — spoof edilebilir ama savunma derinliği katar.
// ALLOWED_ORIGINS env'i tanımlıysa (virgülle ayrılmış liste), listede
// olmayan Origin header'lı istekler reddedilir. Tanımlı değilse bu
// kontrol devre dışı kalır (varsayılan: kapalı, açmak isterseniz Vercel'e
// ALLOWED_ORIGINS env'i ekleyin).
const origin = request.headers.get('origin');
const allowedOrigins = (process.env.ALLOWED_ORIGINS || '')
.split(',')
.map((s) => s.trim())
.filter(Boolean);
if (origin && allowedOrigins.length && !allowedOrigins.includes(origin)) {
return new Response(JSON.stringify({ error: 'forbidden_origin' }), {
status: 403,
headers: { 'content-type': 'application/json' },
});
}
const ip =
request.headers.get('x-forwarded-for')?.split(',')[0]?.trim() ||
request.headers.get('x-real-ip') ||
'unknown';
if (!isAllowed(ip)) {
return new Response(
JSON.stringify({ error: 'rate_limited', message: 'Çok fazla istek. Lütfen bir dakika sonra tekrar deneyin.' }),
{ status: 429, headers: { 'content-type': 'application/json', 'retry-after': '60' } }
);
}
// Limit aşılmadıysa devam et — isteği ilgili api/ fonksiyonuna geçir.
return undefined;
}