diff --git a/server/Server.js b/server/Server.js index 6feabee8..01b1af12 100644 --- a/server/Server.js +++ b/server/Server.js @@ -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 diff --git a/server/libs/expressRateLimit/LICENSE b/server/libs/expressRateLimit/LICENSE deleted file mode 100644 index f4bb9cc3..00000000 --- a/server/libs/expressRateLimit/LICENSE +++ /dev/null @@ -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. \ No newline at end of file diff --git a/server/libs/expressRateLimit/index.js b/server/libs/expressRateLimit/index.js deleted file mode 100644 index 6df27ff5..00000000 --- a/server/libs/expressRateLimit/index.js +++ /dev/null @@ -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; diff --git a/server/libs/expressRateLimit/memory-store.js b/server/libs/expressRateLimit/memory-store.js deleted file mode 100644 index 60938dbc..00000000 --- a/server/libs/expressRateLimit/memory-store.js +++ /dev/null @@ -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;