Add rate limiting to login page

This commit is contained in:
JonnyWong16 2020-11-08 15:36:40 -08:00
parent 199119cafb
commit 3e0b240154
No known key found for this signature in database
GPG key ID: B1F1F9807184697A
4 changed files with 44 additions and 7 deletions

View file

@ -204,7 +204,7 @@ ${next.modalIncludes()}
</div>
</div>
<div class="modal-footer">
<span id="incorrect-login" style="padding-right: 25px; display: none;">Incorrect username or password.</span>
<span id="sign-in-alert" style="padding-right: 25px; display: none;"></span>
<button id="sign-in" type="submit" class="btn btn-bright login-button"><i class="fa fa-sign-in"></i>&nbsp; Sign In</button>
</div>
<input type="hidden" id="admin_login" name="admin_login" value="1" />
@ -446,12 +446,16 @@ ${next.modalIncludes()}
data: $(this).serialize(),
dataType: 'json',
statusCode: {
200: function() {
200: function(xhr, status) {
window.location = "${http_root}";
},
401: function() {
$('#incorrect-login').show();
$('#username').focus();
401: function(xhr, status) {
$('#sign-in-alert').text('Incorrect username or password.').show();
$('#username').focus();
},
429: function(xhr, status) {
var retry = Math.ceil(xhr.getResponseHeader('Retry-After') / 60)
$('#sign-in-alert').text('Too many login attempts. Try again in ' + retry + ' minute(s).').show();
}
},
complete: function() {

View file

@ -159,16 +159,20 @@
data: data,
dataType: 'json',
statusCode: {
200: function() {
200: function(xhr, status) {
window.location = "${redirect_uri or http_root}";
},
401: function() {
401: function(xhr, status) {
if (plex) {
$('#sign-in-alert').text('Invalid Plex Login.').show();
} else {
$('#sign-in-alert').text('Incorrect username or password.').show();
$('#username').focus();
}
},
429: function(xhr, status) {
var retry = Math.ceil(xhr.getResponseHeader('Retry-After') / 60)
$('#sign-in-alert').text('Too many login attempts. Try again in ' + retry + ' minute(s).').show();
}
},
complete: function() {

View file

@ -134,6 +134,9 @@ _CONFIG_DEFINITIONS = {
'HTTP_USERNAME': (str, 'General', ''),
'HTTP_PLEX_ADMIN': (int, 'General', 0),
'HTTP_BASE_URL': (str, 'General', ''),
'HTTP_RATE_LIMIT_ATTEMPTS': (int, 'General', 10),
'HTTP_RATE_LIMIT_ATTEMPTS_INTERVAL': (int, 'General', 300),
'HTTP_RATE_LIMIT_LOCKOUT_TIME': (int, 'General', 300),
'INTERFACE': (str, 'General', 'default'),
'IMGUR_CLIENT_ID': (str, 'Monitoring', ''),
'JOURNAL_MODE': (str, 'Advanced', 'WAL'),

View file

@ -33,11 +33,13 @@ import plexpy
if plexpy.PYTHON2:
import logger
from database import MonitorDatabase
from helpers import timestamp
from users import Users, refresh_users
from plextv import PlexTV
else:
from plexpy import logger
from plexpy.database import MonitorDatabase
from plexpy.helpers import timestamp
from plexpy.users import Users, refresh_users
from plexpy.plextv import PlexTV
@ -246,6 +248,20 @@ def all_of(*conditions):
return check
def check_rate_limit(ip_address):
monitor_db = MonitorDatabase()
result = monitor_db.select('SELECT timestamp FROM user_login '
'WHERE ip_address = ? AND success = 0 '
'AND timestamp >= (SELECT MAX(timestamp) FROM user_login WHERE success = 1) '
'AND timestamp > (SELECT MAX(timestamp) - ? FROM user_login) '
'ORDER BY timestamp DESC',
[ip_address, plexpy.CONFIG.HTTP_RATE_LIMIT_ATTEMPTS_INTERVAL])
if len(result) >= plexpy.CONFIG.HTTP_RATE_LIMIT_ATTEMPTS:
last_timestamp = result[0]['timestamp']
return max(last_timestamp - (timestamp() - plexpy.CONFIG.HTTP_RATE_LIMIT_LOCKOUT_TIME), 0)
# Controller to provide login and logout actions
class AuthController(object):
@ -325,6 +341,16 @@ class AuthController(object):
cherrypy.response.status = 405
return {'status': 'error', 'message': 'Sign in using POST.'}
ip_address = cherrypy.request.remote.ip
rate_limit = check_rate_limit(ip_address)
if rate_limit:
logger.debug("Tautulli WebAuth :: Too many incorrect login attempts from '%s'." % ip_address)
error_message = {'status': 'error', 'message': 'Too many login attempts.'}
cherrypy.response.status = 429
cherrypy.response.headers['Retry-After'] = rate_limit
return error_message
error_message = {'status': 'error', 'message': 'Invalid credentials.'}
valid_login, user_details, user_group = check_credentials(username=username,