mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-01-08 00:08: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