diff --git a/config/rateLimit/rateLimiter.js b/config/rateLimit/rateLimiter.js index b26bfec77..10d4444ff 100644 --- a/config/rateLimit/rateLimiter.js +++ b/config/rateLimit/rateLimiter.js @@ -134,6 +134,13 @@ module.exports = { return true; } + // Removing an entrance from a user's exploration list is a low-risk + // self-service action (tokenAuth already ensures ownership). Skip the + // restrictive DELETE rate limit so users can freely manage their list. + if (/^\/api\/v1\/entrances\/\d+\/cavers\/\d+$/.test(req.path)) { + return true; + } + // Third-party origins are always limited (handled by generalRateLimit) if (req.token && req.headers.origin !== sails.config.custom.baseUrl) { sails.log.error( diff --git a/test/integration/2_utils/rateLimiter.test.js b/test/integration/2_utils/rateLimiter.test.js index 3ac8cbdca..f225bc1ad 100644 --- a/test/integration/2_utils/rateLimiter.test.js +++ b/test/integration/2_utils/rateLimiter.test.js @@ -291,6 +291,22 @@ describe('Rate Limiter', () => { await agent.delete('/test').expect(200); } }); + + it('should skip rate limiting for explored entrance DELETE route', async () => { + const rateLimiter = freshRateLimiter(); + const app = express(); + app.use(rateLimiter.deleteRateLimit); + app.delete( + '/api/v1/entrances/:entranceId/cavers/:caverId', + (req, res) => res.status(200).send('ok') + ); + + const agent = supertest.agent(app); + // Send more than the user delete limit — all should pass + for (let i = 0; i < TEST_USER_DELETE_LIMIT + 5; i += 1) { + await agent.delete('/api/v1/entrances/42/cavers/7').expect(200); + } + }); }); }); });