diff --git a/package.json b/package.json index e2c0d2fc..437e4b93 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "dompurify": "^2.4.1", "email-templates": "^10.0.1", "express": "^4.18.1", + "express-rate-limit": "^6.7.0", "http-signature": "^1.3.6", "https-proxy-agent": "^5.0.1", "ical.js": "^1.5.0", diff --git a/server/api/controller/geolocation.js b/server/api/controller/geolocation.js new file mode 100644 index 00000000..fd0aa220 --- /dev/null +++ b/server/api/controller/geolocation.js @@ -0,0 +1,38 @@ +const rateLimit = require('express-rate-limit'); +const log = require('../../log') +let curReq + +const geolocationController = { + rateLimiter: rateLimit({ + windowMs: 15 * 60 * 1000, // 15 minutes + max: 100, // Limit each IP to 100 requests per `window` (here, per 15 minutes) + standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers + legacyHeaders: false, // Disable the `X-RateLimit-*` headers + }), + + /** + * Limit api usage + * From https://operations.osmfoundation.org/policies/nominatim/ + * [Requirements] No heavy uses (an absolute maximum of 1 request per second). + * [Websites and Apps] Note that the usage limits above apply per website/application: the sum of traffic by all your users should not exceed the limits. + */ + apiLimit (req, res, next) { + prevReq = curReq + curReq = Date.now() + deltaTime = (curReq - prevReq) + + if (typeof prevReq === 'undefined' || deltaTime > 1000) { + geolocationController.rateLimiter(req, res, next) + } else { + log.warn('More than 1 request per second to geolocation api come from ' + req.ip) + + setTimeout(() => { + geolocationController.rateLimiter(req, res, next) + }, 1000 - deltaTime) + } + + } + +} + +module.exports = geolocationController \ No newline at end of file diff --git a/server/api/index.js b/server/api/index.js index 49d0e61d..32e15198 100644 --- a/server/api/index.js +++ b/server/api/index.js @@ -19,6 +19,7 @@ const resourceController = require('./controller/resource') const oauthController = require('./controller/oauth') const announceController = require('./controller/announce') const pluginController = require('./controller/plugins') +const geolocationController = require('./controller/geolocation') const helpers = require('../helpers') const storage = require('./storage') @@ -65,7 +66,6 @@ module.exports = () => { api.get('/ping', (_req, res) => res.sendStatus(200)) api.get('/user', isAuth, (req, res) => res.json(req.user)) - api.post('/user/recover', userController.forgotPassword) api.post('/user/check_recover_code', userController.checkRecoverCode) api.post('/user/recover_password', userController.updatePasswordWithRecoverCode) @@ -173,8 +173,8 @@ module.exports = () => { api.put('/place', isAdmin, placeController.updatePlace) // - GEOCODING - api.get('/placeOSM/Nominatim/:place_details', helpers.isGeocodingEnabled, placeController._nominatim) - api.get('/placeOSM/Photon/:place_details', helpers.isGeocodingEnabled, placeController._photon) + api.get('/placeOSM/Nominatim/:place_details', helpers.isGeocodingEnabled, geolocationController.apiLimit, placeController._nominatim) + api.get('/placeOSM/Photon/:place_details', helpers.isGeocodingEnabled, geolocationController.apiLimit, placeController._photon) // - TAGS api.get('/tags', isAdmin, tagController.getAll) diff --git a/yarn.lock b/yarn.lock index 7de9e8e5..2445ccf5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5160,6 +5160,11 @@ expect@^29.3.1: jest-message-util "^29.3.1" jest-util "^29.3.1" +express-rate-limit@^6.7.0: + version "6.7.0" + resolved "https://registry.yarnpkg.com/express-rate-limit/-/express-rate-limit-6.7.0.tgz#6aa8a1bd63dfe79702267b3af1161a93afc1d3c2" + integrity sha512-vhwIdRoqcYB/72TK3tRZI+0ttS8Ytrk24GfmsxDXK9o9IhHNO5bXRiXQSExPQ4GbaE5tvIS7j1SGrxsuWs+sGA== + express@^4.18.1: version "4.18.2" resolved "https://registry.yarnpkg.com/express/-/express-4.18.2.tgz#3fabe08296e930c796c19e3c516979386ba9fd59" @@ -12636,10 +12641,8 @@ watchpack@^1.7.4: resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.7.5.tgz#1267e6c55e0b9b5be44c2023aed5437a2c26c453" integrity sha512-9P3MWk6SrKjHsGkLT2KHXdQ/9SNkyoJbabxnKOoJepsvJjJG8uYTR3yTPxPQvNDI3w4Nz1xnE0TLHK4RIVe/MQ== dependencies: - chokidar "^3.4.1" graceful-fs "^4.1.2" neo-async "^2.5.0" - watchpack-chokidar2 "^2.0.1" optionalDependencies: chokidar "^3.4.1" watchpack-chokidar2 "^2.0.1"