| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149 |
- import assert from 'node:assert/strict';
- import { test } from 'node:test';
- import {
- AuthenticationRateLimiter,
- authenticateWithRateLimit,
- defaultAuthenticationRateLimits,
- normalizeAuthenticationAccount,
- normalizeAuthenticationIp
- } from '../src/auth-rate-limit.js';
- test('authentication rate limiter uses production thresholds and normalizes identities', () => {
- assert.deepEqual(defaultAuthenticationRateLimits, {
- windowMs: 600_000,
- blockMs: 600_000,
- combinationLimit: 10,
- accountLimit: 30,
- ipLimit: 100,
- maxEntries: 50_000,
- cleanupIntervalMs: 60_000
- });
- assert.equal(normalizeAuthenticationIp('::ffff:192.0.2.10'), '192.0.2.10');
- assert.equal(normalizeAuthenticationIp('[2001:DB8::1]'), '2001:db8::1');
- assert.equal(normalizeAuthenticationAccount(' Admin@Example.COM '), 'admin@example.com');
- });
- test('authentication rate limiter blocks an IP and account combination for the configured duration', () => {
- let now = 1_000;
- const limiter = new AuthenticationRateLimiter({
- now: () => now,
- windowMs: 10_000,
- blockMs: 20_000,
- combinationLimit: 3,
- accountLimit: 10,
- ipLimit: 20
- });
- const identity = { ip: '192.0.2.1', account: 'user@example.com' };
- limiter.recordFailure(identity);
- limiter.recordFailure(identity);
- assert.equal(limiter.isBlocked(identity), false);
- assert.equal(limiter.recordFailure(identity), true);
- assert.equal(limiter.isBlocked(identity), true);
- now += 19_999;
- assert.equal(limiter.isBlocked(identity), true);
- now += 1;
- assert.equal(limiter.isBlocked(identity), false);
- });
- test('authentication rate limiter aggregates failures by account across IPs', () => {
- const limiter = new AuthenticationRateLimiter({
- combinationLimit: 10,
- accountLimit: 3,
- ipLimit: 20
- });
- const account = 'user@example.com';
- limiter.recordFailure({ ip: '192.0.2.1', account });
- limiter.recordFailure({ ip: '192.0.2.2', account });
- limiter.recordFailure({ ip: '192.0.2.3', account });
- assert.equal(limiter.isBlocked({ ip: '192.0.2.99', account }), true);
- assert.equal(limiter.isBlocked({ ip: '192.0.2.99', account: 'other@example.com' }), false);
- });
- test('authentication rate limiter aggregates failures by IP across accounts', () => {
- const limiter = new AuthenticationRateLimiter({
- combinationLimit: 10,
- accountLimit: 10,
- ipLimit: 3
- });
- const ip = '192.0.2.1';
- limiter.recordFailure({ ip, account: 'one@example.com' });
- limiter.recordFailure({ ip, account: 'two@example.com' });
- limiter.recordFailure({ ip, account: 'three@example.com' });
- assert.equal(limiter.isBlocked({ ip, account: 'four@example.com' }), true);
- assert.equal(limiter.isBlocked({ ip: '192.0.2.2', account: 'four@example.com' }), false);
- });
- test('successful authentication clears account records without clearing unrelated IP failures', () => {
- const limiter = new AuthenticationRateLimiter({
- combinationLimit: 5,
- accountLimit: 5,
- ipLimit: 3
- });
- const ip = '192.0.2.1';
- const account = 'user@example.com';
- limiter.recordFailure({ ip, account });
- limiter.recordFailure({ ip, account });
- const result = authenticateWithRateLimit({
- limiter,
- ip,
- account,
- authenticate: () => ({ id: 1 })
- });
- assert.deepEqual(result, { id: 1 });
- limiter.recordFailure({ ip, account: 'second@example.com' });
- assert.equal(limiter.isBlocked({ ip, account: 'third@example.com' }), true);
- assert.equal(limiter.isBlocked({ ip: '192.0.2.2', account }), false);
- });
- test('rate-limited authentication skips credential verification while keeping storage bounded', () => {
- let now = 1_000;
- const limiter = new AuthenticationRateLimiter({
- now: () => now,
- windowMs: 100,
- blockMs: 100,
- combinationLimit: 1,
- accountLimit: 100,
- ipLimit: 100,
- maxEntries: 5,
- cleanupIntervalMs: 1
- });
- let verifications = 0;
- assert.equal(authenticateWithRateLimit({
- limiter,
- ip: '192.0.2.1',
- account: 'blocked@example.com',
- authenticate: () => {
- verifications += 1;
- return null;
- }
- }), null);
- assert.equal(authenticateWithRateLimit({
- limiter,
- ip: '192.0.2.1',
- account: 'blocked@example.com',
- authenticate: () => {
- verifications += 1;
- return { id: 1 };
- }
- }), null);
- assert.equal(verifications, 1);
- for (let index = 0; index < 20; index += 1) {
- limiter.recordFailure({ ip: `198.51.100.${index}`, account: `user-${index}@example.com` });
- assert.ok(limiter.entryCount <= 5);
- }
- now += 201;
- limiter.isBlocked({ ip: '203.0.113.1', account: 'fresh@example.com' });
- assert.equal(limiter.entryCount, 0);
- });
|