2026-05-30 23:20:15 -05:00
|
|
|
import { beforeEach, describe, expect, test } from "bun:test";
|
2026-03-06 20:50:02 -06:00
|
|
|
import { getConfig } from "./config";
|
2026-05-30 23:20:15 -05:00
|
|
|
import {
|
|
|
|
|
clearRequestRateLimitsForTests,
|
|
|
|
|
consumeLoginPageRateLimit,
|
|
|
|
|
consumeUnauthorizedRateLimit,
|
|
|
|
|
isRateLimited,
|
|
|
|
|
recordFailedAttempt,
|
|
|
|
|
recordSuccessfulAttempt,
|
|
|
|
|
} from "./rateLimit";
|
|
|
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
|
clearRequestRateLimitsForTests();
|
|
|
|
|
});
|
2026-03-05 00:04:26 -06:00
|
|
|
|
|
|
|
|
describe("rateLimit", () => {
|
|
|
|
|
test("locks out after repeated failures", () => {
|
2026-03-06 20:50:02 -06:00
|
|
|
const maxAttempts = Math.max(1, Number(getConfig().security.loginMaxAttempts) || 5);
|
2026-03-05 00:04:26 -06:00
|
|
|
const ip = `10.0.0.${Date.now()}`;
|
2026-03-06 20:50:02 -06:00
|
|
|
for (let i = 0; i < maxAttempts; i += 1) {
|
2026-03-05 00:04:26 -06:00
|
|
|
recordFailedAttempt(ip);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const result = isRateLimited(ip);
|
|
|
|
|
expect(result.limited).toBe(true);
|
|
|
|
|
expect(result.retryAfter).toBeGreaterThan(0);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("successful attempt clears lock state", () => {
|
2026-03-06 20:50:02 -06:00
|
|
|
const maxAttempts = Math.max(1, Number(getConfig().security.loginMaxAttempts) || 5);
|
2026-03-05 00:04:26 -06:00
|
|
|
const ip = `10.0.1.${Date.now()}`;
|
2026-03-06 20:50:02 -06:00
|
|
|
for (let i = 0; i < maxAttempts; i += 1) {
|
2026-03-05 00:04:26 -06:00
|
|
|
recordFailedAttempt(ip);
|
|
|
|
|
}
|
|
|
|
|
expect(isRateLimited(ip).limited).toBe(true);
|
|
|
|
|
|
|
|
|
|
recordSuccessfulAttempt(ip);
|
|
|
|
|
expect(isRateLimited(ip).limited).toBe(false);
|
|
|
|
|
});
|
2026-05-30 23:20:15 -05:00
|
|
|
|
|
|
|
|
test("limits repeated login page requests by IP", () => {
|
|
|
|
|
const ip = `10.0.2.${Date.now()}`;
|
|
|
|
|
for (let i = 0; i < 60; i += 1) {
|
|
|
|
|
expect(consumeLoginPageRateLimit(ip).limited).toBe(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const result = consumeLoginPageRateLimit(ip);
|
|
|
|
|
expect(result.limited).toBe(true);
|
|
|
|
|
expect(result.retryAfter).toBeGreaterThan(0);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("limits repeated unauthorized responses by IP", () => {
|
|
|
|
|
const ip = `10.0.3.${Date.now()}`;
|
|
|
|
|
for (let i = 0; i < 120; i += 1) {
|
|
|
|
|
expect(consumeUnauthorizedRateLimit(ip).limited).toBe(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const result = consumeUnauthorizedRateLimit(ip);
|
|
|
|
|
expect(result.limited).toBe(true);
|
|
|
|
|
expect(result.retryAfter).toBeGreaterThan(0);
|
|
|
|
|
});
|
2026-03-05 00:04:26 -06:00
|
|
|
});
|