Files
openmaic/OpenMAIC/tests/server/access-code-rate-limit.test.ts
2026-08-16 14:58:47 +08:00

69 lines
2.3 KiB
TypeScript

import { describe, expect, test } from 'vitest';
import {
AccessCodeRateLimiter,
resolveAccessCodeRateLimitConfig,
resolveAccessCodeRateLimitKey,
} from '@/lib/server/access-code-rate-limit';
describe('ACCESS_CODE login rate limiting', () => {
test('blocks after the configured attempt count and reopens after the window', () => {
const limiter = new AccessCodeRateLimiter();
const config = { maxAttempts: 2, windowMs: 10_000, maxClients: 100 };
expect(limiter.consume('client-a', config, 1_000)).toMatchObject({
allowed: true,
remaining: 1,
});
expect(limiter.consume('client-a', config, 2_000)).toMatchObject({
allowed: true,
remaining: 0,
});
expect(limiter.consume('client-a', config, 3_000)).toMatchObject({
allowed: false,
retryAfterSeconds: 8,
});
expect(limiter.consume('client-a', config, 11_000)).toMatchObject({
allowed: true,
remaining: 1,
});
});
test('keeps the in-process client map bounded', () => {
const limiter = new AccessCodeRateLimiter();
const config = { maxAttempts: 3, windowMs: 10_000, maxClients: 2 };
limiter.consume('client-a', config, 1_000);
limiter.consume('client-b', config, 1_001);
limiter.consume('client-c', config, 1_002);
expect(limiter.size).toBe(2);
expect(limiter.consume('client-a', config, 1_003)).toMatchObject({
allowed: true,
remaining: 2,
});
expect(limiter.size).toBe(2);
});
test('trusts proxy IP headers only through explicit opt-in', () => {
const request = new Request('https://ops.example/api/access-code/verify', {
headers: {
'x-forwarded-for': '203.0.113.8, 10.0.0.2',
'x-real-ip': '203.0.113.9',
},
});
expect(resolveAccessCodeRateLimitKey(request, false)).toBe('shared:unknown-client');
expect(resolveAccessCodeRateLimitKey(request, true)).toBe('ip:203.0.113.8');
});
test('falls back safely for invalid configuration', () => {
expect(
resolveAccessCodeRateLimitConfig({
ACCESS_CODE_RATE_LIMIT_MAX_ATTEMPTS: '-1',
ACCESS_CODE_RATE_LIMIT_WINDOW_SECONDS: 'not-a-number',
ACCESS_CODE_RATE_LIMIT_MAX_CLIENTS: '0',
}),
).toEqual({ maxAttempts: 10, windowMs: 900_000, maxClients: 10_000 });
});
});