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 util = require('util')
 | 
				
			||||||
const fs = require('./libs/fsExtra')
 | 
					const fs = require('./libs/fsExtra')
 | 
				
			||||||
const fileUpload = require('./libs/expressFileupload')
 | 
					const fileUpload = require('./libs/expressFileupload')
 | 
				
			||||||
const rateLimit = require('./libs/expressRateLimit')
 | 
					 | 
				
			||||||
const cookieParser = require("cookie-parser")
 | 
					const cookieParser = require("cookie-parser")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const { version } = require('../package.json')
 | 
					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'))))
 | 
					    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) => {
 | 
					    router.post('/init', (req, res) => {
 | 
				
			||||||
      if (Database.hasRootUser) {
 | 
					      if (Database.hasRootUser) {
 | 
				
			||||||
        Logger.error(`[Server] attempt to init server when server already has a root user`)
 | 
					        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
 | 
					   * Gracefully stop server
 | 
				
			||||||
   * Stops watcher and socket 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