mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-10-31 16:13:15 +00:00
The admin login limit keys on the client IP taken from X-Forwarded-For. With no reverse proxy in front, that header is whatever the client sends (Next only fills it from the socket when the request carries none), so each forged address got a fresh bucket of five guesses. Route handlers cannot see the socket address, so the limiter now also stops at 50 attempts per 15 minutes across all clients. `.env.example` says why a proxy that appends the header is still needed for trustworthy per-IP limits and audit addresses.
45 lines
1.4 KiB
TypeScript
45 lines
1.4 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
|
|
// The per-IP key is X-Forwarded-For, which a client writes itself when the
|
|
// app is reachable without a reverse proxy: a fresh forged address used to
|
|
// mean a fresh bucket of five guesses, without end.
|
|
|
|
async function load() {
|
|
vi.resetModules();
|
|
return import('@/lib/admin/rate-limit');
|
|
}
|
|
|
|
describe('admin login rate limit', () => {
|
|
beforeEach(() => {
|
|
vi.useFakeTimers();
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.useRealTimers();
|
|
});
|
|
|
|
it('allows five attempts per address', async () => {
|
|
const { checkRateLimit } = await load();
|
|
for (let i = 0; i < 5; i++) expect(checkRateLimit('203.0.113.1').allowed).toBe(true);
|
|
expect(checkRateLimit('203.0.113.1').allowed).toBe(false);
|
|
expect(checkRateLimit('203.0.113.2').allowed).toBe(true);
|
|
});
|
|
|
|
it('stops rotating forged addresses at the global ceiling', async () => {
|
|
const { checkRateLimit } = await load();
|
|
let allowed = 0;
|
|
for (let i = 0; i < 200; i++) {
|
|
if (checkRateLimit(`198.51.100.${i}`).allowed) allowed++;
|
|
}
|
|
expect(allowed).toBe(50);
|
|
expect(checkRateLimit('192.0.2.77').allowed).toBe(false);
|
|
});
|
|
|
|
it('opens again after the window', async () => {
|
|
const { checkRateLimit } = await load();
|
|
for (let i = 0; i < 60; i++) checkRateLimit(`198.51.100.${i}`);
|
|
vi.advanceTimersByTime(15 * 60 * 1000 + 1);
|
|
expect(checkRateLimit('192.0.2.77').allowed).toBe(true);
|
|
});
|
|
});
|