fix search feature
Build & Push / Pipeline Tests (push) Failing after 1m14s
Build & Push / Build & Push Docker Image (push) Has been skipped
Test / Type Check (all packages) (push) Successful in 53s
Test / API Unit Tests (push) Failing after 50s
Test / Homepage Unit Tests (push) Successful in 45s
Test / Carplace Unit Tests (push) Successful in 44s
Test / Admin Unit Tests (push) Successful in 42s
Test / Dashboard Unit Tests (push) Successful in 43s
Test / API Integration Tests (push) Successful in 1m4s

This commit is contained in:
root
2026-07-26 01:18:05 -04:00
parent f6fcd7ce54
commit 2b422ca197
23 changed files with 880 additions and 51 deletions
@@ -31,6 +31,8 @@ describe('rateLimiter middleware configuration', () => {
expect(authLimiter.max).toBe(20)
expect(authLimiter.windowMs).toBe(15 * 60 * 1000)
expect(authLimiter.skip({ method: 'OPTIONS' } as any)).toBe(true)
expect(authLimiter.skip({ method: 'POST' } as any)).toBe(false)
expect(authLimiter.skipSuccessfulRequests).toBe(true)
expect(authLimiter.message).toMatchObject({ error: 'too_many_requests', statusCode: 429 })
expect(rateLimit).toHaveBeenCalled()
@@ -63,7 +65,18 @@ describe('rateLimiter middleware configuration', () => {
expect(publicLimiter.max).toBe(60)
expect(publicLimiter.message.message).toBe('Rate limit exceeded')
expect(publicLimiter.skip({ method: 'OPTIONS' } as any)).toBe(true)
expect(adminLimiter.max).toBe(100)
expect(adminLimiter.message.message).toBe('Too many admin requests')
expect(adminLimiter.skip({ method: 'OPTIONS' } as any)).toBe(true)
})
it('uses a higher API cap and skips preflight requests before authenticated actor limits', async () => {
const { apiLimiter, actorLimiter } = await import('./rateLimiter')
expect(apiLimiter.max).toBe(300)
expect(apiLimiter.skip({ method: 'OPTIONS' } as any)).toBe(true)
expect(apiLimiter.skip({ method: 'GET' } as any)).toBe(false)
expect(actorLimiter.skip({ method: 'OPTIONS' } as any)).toBe(true)
})
})
+8 -1
View File
@@ -52,6 +52,7 @@ function getAuthenticatedActorKey(req: Request): string | null {
// req.ip is already the real client IP when app.set('trust proxy', 1) is configured
const getClientIpKey = (req: Request) => ipKeyGenerator(req.ip ?? '')
const skipPreflightRequest = (req: Request) => req.method === 'OPTIONS'
// Strict limiter for auth endpoints — prevents brute-force and credential stuffing.
// Successful requests (e.g. GET /me profile reads) are skipped so only failed
@@ -61,6 +62,7 @@ export const authLimiter = rateLimit({
max: 20,
standardHeaders: 'draft-7',
legacyHeaders: false,
skip: skipPreflightRequest,
skipSuccessfulRequests: true,
keyGenerator: (req) => getClientIpKey(req),
message: { error: 'too_many_requests', message: 'Too many attempts, please try again later', statusCode: 429 },
@@ -69,9 +71,10 @@ export const authLimiter = rateLimit({
// Standard limiter for general authenticated API endpoints
export const apiLimiter = rateLimit({
windowMs: 60 * 1000, // 1 minute
max: 120,
max: 300,
standardHeaders: 'draft-7',
legacyHeaders: false,
skip: skipPreflightRequest,
keyGenerator: (req) => {
const ip = getClientIpKey(req)
const actorKey = getAuthenticatedActorKey(req)
@@ -88,6 +91,7 @@ export const publicLimiter = rateLimit({
max: 60,
standardHeaders: 'draft-7',
legacyHeaders: false,
skip: skipPreflightRequest,
keyGenerator: (req) => getClientIpKey(req),
message: { error: 'too_many_requests', message: 'Rate limit exceeded', statusCode: 429 },
})
@@ -99,6 +103,7 @@ export const webhookLimiter = rateLimit({
max: 30,
standardHeaders: 'draft-7',
legacyHeaders: false,
skip: skipPreflightRequest,
keyGenerator: (req) => getClientIpKey(req),
message: { error: 'too_many_requests', message: 'Webhook rate limit exceeded', statusCode: 429 },
})
@@ -109,6 +114,7 @@ export const adminLimiter = rateLimit({
max: 100,
standardHeaders: 'draft-7',
legacyHeaders: false,
skip: skipPreflightRequest,
keyGenerator: (req) => {
const ip = getClientIpKey(req)
return `${ip}:${getAuthenticatedActorKey(req) || 'anonymous'}`
@@ -124,6 +130,7 @@ export const actorLimiter = rateLimit({
max: 240,
standardHeaders: 'draft-7',
legacyHeaders: false,
skip: skipPreflightRequest,
keyGenerator: (req) => {
const ip = getClientIpKey(req)
const actorKey = getAuthenticatedActorKey(req)