mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-27 11:18:14 +01:00 
			
		
		
		
	Remove old login rate limiter
This commit is contained in:
		
							parent
							
								
									a5c200ac79
								
							
						
					
					
						commit
						d7aba5629e
					
				| @ -5,7 +5,6 @@ const http = require('http') | ||||
| const util = require('util') | ||||
| const fs = require('./libs/fsExtra') | ||||
| const fileUpload = require('./libs/expressFileupload') | ||||
| const rateLimit = require('./libs/expressRateLimit') | ||||
| const cookieParser = require("cookie-parser") | ||||
| 
 | ||||
| const { version } = require('../package.json') | ||||
| @ -287,8 +286,6 @@ class Server { | ||||
|     ] | ||||
|     dyanimicRoutes.forEach((route) => router.get(route, (req, res) => res.sendFile(Path.join(distPath, 'index.html')))) | ||||
| 
 | ||||
|     // router.post('/login', passport.authenticate('local', this.auth.login), this.auth.loginResult.bind(this))
 | ||||
|     // router.post('/logout', this.authMiddleware.bind(this), this.logout.bind(this))
 | ||||
|     router.post('/init', (req, res) => { | ||||
|       if (Database.hasRootUser) { | ||||
|         Logger.error(`[Server] attempt to init server when server already has a root user`) | ||||
| @ -401,30 +398,6 @@ class Server { | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   // First time login rate limit is hit
 | ||||
|   loginLimitReached(req, res, options) { | ||||
|     Logger.error(`[Server] Login rate limit (${options.max}) was hit for ip ${req.ip}`) | ||||
|     options.message = 'Too many attempts. Login temporarily locked.' | ||||
|   } | ||||
| 
 | ||||
|   getLoginRateLimiter() { | ||||
|     return rateLimit({ | ||||
|       windowMs: Database.serverSettings.rateLimitLoginWindow, // 5 minutes
 | ||||
|       max: Database.serverSettings.rateLimitLoginRequests, | ||||
|       skipSuccessfulRequests: true, | ||||
|       onLimitReached: this.loginLimitReached | ||||
|     }) | ||||
|   } | ||||
| 
 | ||||
|   logout(req, res) { | ||||
|     if (req.body.socketId) { | ||||
|       Logger.info(`[Server] User ${req.user ? req.user.username : 'Unknown'} is logging out with socket ${req.body.socketId}`) | ||||
|       SocketAuthority.logout(req.body.socketId) | ||||
|     } | ||||
| 
 | ||||
|     res.sendStatus(200) | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Gracefully stop server | ||||
|    * Stops watcher and socket server | ||||
|  | ||||
| @ -1,20 +0,0 @@ | ||||
| # MIT License | ||||
| 
 | ||||
| Copyright 2021 Nathan Friedly | ||||
| 
 | ||||
| Permission is hereby granted, free of charge, to any person obtaining a copy of | ||||
| this software and associated documentation files (the "Software"), to deal in | ||||
| the Software without restriction, including without limitation the rights to | ||||
| use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of | ||||
| the Software, and to permit persons to whom the Software is furnished to do so, | ||||
| subject to the following conditions: | ||||
| 
 | ||||
| The above copyright notice and this permission notice shall be included in all | ||||
| copies or substantial portions of the Software. | ||||
| 
 | ||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS | ||||
| FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR | ||||
| COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER | ||||
| IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | ||||
| CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||||
| @ -1,196 +0,0 @@ | ||||
| "use strict"; | ||||
| 
 | ||||
| //
 | ||||
| // modified for use in audiobookshelf
 | ||||
| // Source: https://github.com/nfriedly/express-rate-limit
 | ||||
| //
 | ||||
| 
 | ||||
| const MemoryStore = require("./memory-store"); | ||||
| 
 | ||||
| function RateLimit(options) { | ||||
|   options = Object.assign( | ||||
|     { | ||||
|       windowMs: 60 * 1000, // milliseconds - how long to keep records of requests in memory
 | ||||
|       max: 5, // max number of recent connections during `window` milliseconds before sending a 429 response
 | ||||
|       message: "Too many requests, please try again later.", | ||||
|       statusCode: 429, // 429 status = Too Many Requests (RFC 6585)
 | ||||
|       headers: true, //Send custom rate limit header with limit and remaining
 | ||||
|       draft_polli_ratelimit_headers: false, //Support for the new RateLimit standardization headers
 | ||||
|       // ability to manually decide if request was successful. Used when `skipSuccessfulRequests` and/or `skipFailedRequests` are set to `true`
 | ||||
|       requestWasSuccessful: function (req, res) { | ||||
|         return res.statusCode < 400; | ||||
|       }, | ||||
|       skipFailedRequests: false, // Do not count failed requests
 | ||||
|       skipSuccessfulRequests: false, // Do not count successful requests
 | ||||
|       // allows to create custom keys (by default user IP is used)
 | ||||
|       keyGenerator: function (req /*, res*/) { | ||||
|         if (!req.ip) { | ||||
|           console.error( | ||||
|             "express-rate-limit: req.ip is undefined - you can avoid this by providing a custom keyGenerator function, but it may be indicative of a larger issue." | ||||
|           ); | ||||
|         } | ||||
|         return req.ip; | ||||
|       }, | ||||
|       skip: function (/*req, res*/) { | ||||
|         return false; | ||||
|       }, | ||||
|       handler: function (req, res /*, next, optionsUsed*/) { | ||||
|         res.status(options.statusCode).send(options.message); | ||||
|       }, | ||||
|       onLimitReached: function (/*req, res, optionsUsed*/) { }, | ||||
|       requestPropertyName: "rateLimit", // Parameter name appended to req object
 | ||||
|     }, | ||||
|     options | ||||
|   ); | ||||
| 
 | ||||
|   // store to use for persisting rate limit data
 | ||||
|   options.store = options.store || new MemoryStore(options.windowMs); | ||||
| 
 | ||||
|   // ensure that the store has the incr method
 | ||||
|   if ( | ||||
|     typeof options.store.incr !== "function" || | ||||
|     typeof options.store.resetKey !== "function" || | ||||
|     (options.skipFailedRequests && | ||||
|       typeof options.store.decrement !== "function") | ||||
|   ) { | ||||
|     throw new Error("The store is not valid."); | ||||
|   } | ||||
| 
 | ||||
|   ["global", "delayMs", "delayAfter"].forEach((key) => { | ||||
|     // note: this doesn't trigger if delayMs or delayAfter are set to 0, because that essentially disables them
 | ||||
|     if (options[key]) { | ||||
|       throw new Error( | ||||
|         `The ${key} option was removed from express-rate-limit v3.` | ||||
|       ); | ||||
|     } | ||||
|   }); | ||||
| 
 | ||||
|   function rateLimit(req, res, next) { | ||||
|     Promise.resolve(options.skip(req, res)) | ||||
|       .then((skip) => { | ||||
|         if (skip) { | ||||
|           return next(); | ||||
|         } | ||||
| 
 | ||||
|         const key = options.keyGenerator(req, res); | ||||
| 
 | ||||
|         options.store.incr(key, function (err, current, resetTime) { | ||||
|           if (err) { | ||||
|             return next(err); | ||||
|           } | ||||
| 
 | ||||
|           const maxResult = | ||||
|             typeof options.max === "function" | ||||
|               ? options.max(req, res) | ||||
|               : options.max; | ||||
| 
 | ||||
|           Promise.resolve(maxResult) | ||||
|             .then((max) => { | ||||
|               req[options.requestPropertyName] = { | ||||
|                 limit: max, | ||||
|                 current: current, | ||||
|                 remaining: Math.max(max - current, 0), | ||||
|                 resetTime: resetTime, | ||||
|               }; | ||||
| 
 | ||||
|               if (options.headers && !res.headersSent) { | ||||
|                 res.setHeader("X-RateLimit-Limit", max); | ||||
|                 res.setHeader( | ||||
|                   "X-RateLimit-Remaining", | ||||
|                   req[options.requestPropertyName].remaining | ||||
|                 ); | ||||
|                 if (resetTime instanceof Date) { | ||||
|                   // if we have a resetTime, also provide the current date to help avoid issues with incorrect clocks
 | ||||
|                   res.setHeader("Date", new Date().toUTCString()); | ||||
|                   res.setHeader( | ||||
|                     "X-RateLimit-Reset", | ||||
|                     Math.ceil(resetTime.getTime() / 1000) | ||||
|                   ); | ||||
|                 } | ||||
|               } | ||||
|               if (options.draft_polli_ratelimit_headers && !res.headersSent) { | ||||
|                 res.setHeader("RateLimit-Limit", max); | ||||
|                 res.setHeader( | ||||
|                   "RateLimit-Remaining", | ||||
|                   req[options.requestPropertyName].remaining | ||||
|                 ); | ||||
|                 if (resetTime) { | ||||
|                   const deltaSeconds = Math.ceil( | ||||
|                     (resetTime.getTime() - Date.now()) / 1000 | ||||
|                   ); | ||||
|                   res.setHeader("RateLimit-Reset", Math.max(0, deltaSeconds)); | ||||
|                 } | ||||
|               } | ||||
| 
 | ||||
|               if ( | ||||
|                 options.skipFailedRequests || | ||||
|                 options.skipSuccessfulRequests | ||||
|               ) { | ||||
|                 let decremented = false; | ||||
|                 const decrementKey = () => { | ||||
|                   if (!decremented) { | ||||
|                     options.store.decrement(key); | ||||
|                     decremented = true; | ||||
|                   } | ||||
|                 }; | ||||
| 
 | ||||
|                 if (options.skipFailedRequests) { | ||||
|                   res.on("finish", function () { | ||||
|                     if (!options.requestWasSuccessful(req, res)) { | ||||
|                       decrementKey(); | ||||
|                     } | ||||
|                   }); | ||||
| 
 | ||||
|                   res.on("close", () => { | ||||
|                     if (!res.finished) { | ||||
|                       decrementKey(); | ||||
|                     } | ||||
|                   }); | ||||
| 
 | ||||
|                   res.on("error", () => decrementKey()); | ||||
|                 } | ||||
| 
 | ||||
|                 if (options.skipSuccessfulRequests) { | ||||
|                   res.on("finish", function () { | ||||
|                     if (options.requestWasSuccessful(req, res)) { | ||||
|                       options.store.decrement(key); | ||||
|                     } | ||||
|                   }); | ||||
|                 } | ||||
|               } | ||||
| 
 | ||||
|               if (max && current === max + 1) { | ||||
|                 options.onLimitReached(req, res, options); | ||||
|               } | ||||
| 
 | ||||
|               if (max && current > max) { | ||||
|                 if (options.headers && !res.headersSent) { | ||||
|                   res.setHeader( | ||||
|                     "Retry-After", | ||||
|                     Math.ceil(options.windowMs / 1000) | ||||
|                   ); | ||||
|                 } | ||||
|                 return options.handler(req, res, next, options); | ||||
|               } | ||||
| 
 | ||||
|               next(); | ||||
| 
 | ||||
|               return null; | ||||
|             }) | ||||
|             .catch(next); | ||||
|         }); | ||||
| 
 | ||||
|         return null; | ||||
|       }) | ||||
|       .catch(next); | ||||
|   } | ||||
| 
 | ||||
|   rateLimit.resetKey = options.store.resetKey.bind(options.store); | ||||
| 
 | ||||
|   // Backward compatibility function
 | ||||
|   rateLimit.resetIp = rateLimit.resetKey; | ||||
| 
 | ||||
|   return rateLimit; | ||||
| } | ||||
| 
 | ||||
| module.exports = RateLimit; | ||||
| @ -1,47 +0,0 @@ | ||||
| "use strict"; | ||||
| 
 | ||||
| function calculateNextResetTime(windowMs) { | ||||
|   const d = new Date(); | ||||
|   d.setMilliseconds(d.getMilliseconds() + windowMs); | ||||
|   return d; | ||||
| } | ||||
| 
 | ||||
| function MemoryStore(windowMs) { | ||||
|   let hits = {}; | ||||
|   let resetTime = calculateNextResetTime(windowMs); | ||||
| 
 | ||||
|   this.incr = function (key, cb) { | ||||
|     if (hits[key]) { | ||||
|       hits[key]++; | ||||
|     } else { | ||||
|       hits[key] = 1; | ||||
|     } | ||||
| 
 | ||||
|     cb(null, hits[key], resetTime); | ||||
|   }; | ||||
| 
 | ||||
|   this.decrement = function (key) { | ||||
|     if (hits[key]) { | ||||
|       hits[key]--; | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   // export an API to allow hits all IPs to be reset
 | ||||
|   this.resetAll = function () { | ||||
|     hits = {}; | ||||
|     resetTime = calculateNextResetTime(windowMs); | ||||
|   }; | ||||
| 
 | ||||
|   // export an API to allow hits from one IP to be reset
 | ||||
|   this.resetKey = function (key) { | ||||
|     delete hits[key]; | ||||
|   }; | ||||
| 
 | ||||
|   // simply reset ALL hits every windowMs
 | ||||
|   const interval = setInterval(this.resetAll, windowMs); | ||||
|   if (interval.unref) { | ||||
|     interval.unref(); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| module.exports = MemoryStore; | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user