diff --git a/__tests__/routes/public/rateLimit.ts b/__tests__/routes/public/rateLimit.ts index 03e71d1759..3fde91da4b 100644 --- a/__tests__/routes/public/rateLimit.ts +++ b/__tests__/routes/public/rateLimit.ts @@ -53,5 +53,27 @@ describe('Public API Rate Limiting', () => { expect(remaining2).toBeLessThan(remaining1); }); + + it('should return 429 when user rate limit is exceeded', async () => { + const token = await createTokenForUser(state.con, '5'); + + // Limit is 60/min/user. Fire until we hit the limit. + let lastRes; + for (let i = 0; i < 61; i++) { + lastRes = await request(state.app.server) + .get('/public/v1/feeds/foryou') + .set('Authorization', `Bearer ${token}`); + + if (lastRes.status === 429 && lastRes.body.statusCode === 429) { + break; + } + } + + expect(lastRes!.status).toBe(429); + expect(lastRes!.body).toMatchObject({ + error: 'rate_limit_exceeded', + message: expect.stringMatching(/rate limit/i), + }); + }); }); }); diff --git a/src/routes/public/index.ts b/src/routes/public/index.ts index ed64f4faf8..216c8767bd 100644 --- a/src/routes/public/index.ts +++ b/src/routes/public/index.ts @@ -137,11 +137,17 @@ export default async function ( max: IP_RATE_LIMIT_PER_MINUTE, timeWindow: '1 minute', keyGenerator: (request: FastifyRequest) => request.ip, - errorResponseBuilder: () => ({ - error: 'rate_limit_exceeded', - message: 'Too many requests from this IP. Please slow down.', - retryAfter: 60, - }), + errorResponseBuilder: () => { + // @fastify/rate-limit throws the return value of this builder. Return + // a proper Error so the global setErrorHandler can read err.name and + // err.statusCode (plain objects lose both). + const err = new Error( + 'Too many requests from this IP. Please slow down.', + ); + err.name = 'rate_limit_exceeded'; + (err as Error & { statusCode: number }).statusCode = 429; + return err; + }, skipOnError: false, addHeadersOnExceeding: { 'x-ratelimit-limit': false, @@ -177,11 +183,14 @@ export default async function ( hook: 'preHandler', keyGenerator: (request: FastifyRequest) => request.apiUserId, skip: (request: FastifyRequest) => !request.apiUserId, - errorResponseBuilder: () => ({ - error: 'rate_limit_exceeded', - message: 'User rate limit exceeded. Please slow down.', - retryAfter: 60, - }), + errorResponseBuilder: () => { + // See IP rate limiter above. Return a proper Error so name/statusCode + // survive the throw and reach the global setErrorHandler. + const err = new Error('User rate limit exceeded. Please slow down.'); + err.name = 'rate_limit_exceeded'; + (err as Error & { statusCode: number }).statusCode = 429; + return err; + }, skipOnError: false, addHeadersOnExceeding: { 'x-ratelimit-limit': true,