Pembatas Percobaan Akses & Tangkal Serangan Spam
Menahan serangan tebak password massal dan tombol yang diklik berkali-kali secara membabi-buta, sehingga website tetap stabil dan aman.
Menahan serangan tebak password massal dan tombol yang diklik berkali-kali secara membabi-buta, sehingga website tetap stabil dan aman.
Tantangan & Kebutuhan Pengguna
Menyimpan counter rate limit hanya di memori proses Node.js tidak efektif ketika aplikasi dijalankan pada beberapa container atau worker paralel, karena penyerang dapat membagi request ke banyak instance. Di sisi lain, menambahkan infrastruktur Redis eksternal sering kali berlebihan untuk arsitektur monolit modular, dan ketergantungan mutlak pada database berisiko menghentikan seluruh aplikasi bila jaringan database mengalami fluktuasi sesaat.
Sistem pertahanan laju request harus berbagi state secara terpusat di kondisi normal, tetapi tetap mampu bertahan di memori lokal saat lapisan penyimpanan utama mengalami gangguan.
Solusi & Cara Kerja
Modul `common/lib/rate-limit.ts` menyimpan bucket perhitungan di tabel `rate_limit_buckets` menggunakan transaksi Prisma (`upsert` & `increment`). Setiap aksi sensitif—mulai dari login, setup MFA, hingga eksekusi `requirePermission` (120 request/menit untuk `read`, 30 request/menit untuk mutasi)—diperiksa terhadap batas kuotanya. Untuk menjaga ukuran tabel tetap ramping tanpa cronjob eksternal, sistem menjalankan probabilistic pruning (peluang 2% di setiap request) untuk menghapus bucket yang sudah kedaluwarsa. Jika query database melempar error koneksi, blok `catch` secara transparan mengalihkan pengecekan ke `assertMemoryRateLimit` yang dibatasi maksimal 10.000 kunci.
Berikut adalah implementasi transaksi rate limiter terdistribusi beserta fallback in-memory dari `common/lib/rate-limit.ts`:
Detail & Hal yang Diperhatikan
- Pencegahan Memory Leak pada Fallback: Map in-memory memiliki batas keras `MAX_BUCKETS = 10_000` dan otomatis menyapu entri yang sudah kedaluwarsa saat batas tercapai.
- Kalkulasi Retry-After Presisi: Error `RateLimitError` menghitung sisa detik menuju `resetAt` sehingga pengguna mendapat umpan balik waktu tunggu yang akurat.
- Garbage Collection Tanpa Cron: Pembersihan probabilistik `Math.random() < 0.02` secara asinkron membersihkan tabel `rate_limit_buckets` tanpa memblokir respons utama.
Ekstraksi IP klien pada `common/utils/request-ip.ts` hanya memercayai header `x-forwarded-for` atau `x-real-ip` apabila `TRUST_PROXY=true` diaktifkan, mencegah penyerang memalsukan header IP untuk melewati rate limit.