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); });