Added ratelimiterbackends

pull/2948/merge
Ozzie Isaacs 3 months ago
parent 117c92233d
commit 3c4ed0de1a

@ -103,7 +103,7 @@ web_server = WebServer()
updater_thread = Updater() updater_thread = Updater()
if limiter_present: if limiter_present:
limiter = Limiter(key_func=True, headers_enabled=True, auto_check=False, swallow_errors=True) limiter = Limiter(key_func=True, headers_enabled=True, auto_check=False, swallow_errors=False)
else: else:
limiter = None limiter = None
@ -196,8 +196,18 @@ def create_app():
config.config_use_goodreads) config.config_use_goodreads)
config.store_calibre_uuid(calibre_db, db.Library_Id) config.store_calibre_uuid(calibre_db, db.Library_Id)
# Configure rate limiter # Configure rate limiter
# https://limits.readthedocs.io/en/stable/storage.html
app.config.update(RATELIMIT_ENABLED=config.config_ratelimiter) app.config.update(RATELIMIT_ENABLED=config.config_ratelimiter)
limiter.init_app(app) if config.config_limiter_uri != "":
app.config.update(RATELIMIT_STORAGE_URI=config.config_limiter_uri)
if config.config_limiter_options != "":
app.config.update(RATELIMIT_STORAGE_OPTIONS=config.config_limiter_options)
try:
limiter.init_app(app)
except Exception as e:
log.error('Wrong Flask Limiter configuration, falling back to default: {}'.format(e))
app.config.update(RATELIMIT_STORAGE_URI=None)
limiter.init_app(app)
# Register scheduled tasks # Register scheduled tasks
from .schedule import register_scheduled_tasks, register_startup_tasks from .schedule import register_scheduled_tasks, register_startup_tasks

@ -1706,7 +1706,7 @@ def _db_configuration_update_helper():
return _db_configuration_result('{}'.format(ex), gdrive_error) return _db_configuration_result('{}'.format(ex), gdrive_error)
if db_change or not db_valid or not config.db_configured \ if db_change or not db_valid or not config.db_configured \
or config.config_calibre_dir != to_save["config_calibre_dir"]: or config.config_calibre_dir != to_save["config_calibre_dir"]:
if not os.path.exists(metadata_db) or not to_save['config_calibre_dir']: if not os.path.exists(metadata_db) or not to_save['config_calibre_dir']:
return _db_configuration_result(_('DB Location is not Valid, Please Enter Correct Path'), gdrive_error) return _db_configuration_result(_('DB Location is not Valid, Please Enter Correct Path'), gdrive_error)
else: else:
@ -1830,6 +1830,8 @@ def _configuration_update_helper():
return _configuration_result(_('Password length has to be between 1 and 40')) return _configuration_result(_('Password length has to be between 1 and 40'))
reboot_required |= _config_int(to_save, "config_session") reboot_required |= _config_int(to_save, "config_session")
reboot_required |= _config_checkbox(to_save, "config_ratelimiter") reboot_required |= _config_checkbox(to_save, "config_ratelimiter")
reboot_required |= _config_string(to_save, "config_limiter_uri")
reboot_required |= _config_string(to_save, "config_limiter_options")
# Rarfile Content configuration # Rarfile Content configuration
_config_string(to_save, "config_rarfile_location") _config_string(to_save, "config_rarfile_location")

@ -168,6 +168,8 @@ class _Settings(_Base):
config_password_special = Column(Boolean, default=True) config_password_special = Column(Boolean, default=True)
config_session = Column(Integer, default=1) config_session = Column(Integer, default=1)
config_ratelimiter = Column(Boolean, default=True) config_ratelimiter = Column(Boolean, default=True)
config_limiter_uri = Column(String, default="")
config_limiter_options = Column(String, default="")
def __repr__(self): def __repr__(self):
return self.__class__.__name__ return self.__class__.__name__

@ -159,7 +159,7 @@ EXTENSIONS_UPLOAD = {'txt', 'pdf', 'epub', 'kepub', 'mobi', 'azw', 'azw3', 'cbr'
_extension = "" _extension = ""
if sys.platform == "win32": if sys.platform == "win32":
_extension = ".exe" _extension = ".exe"
SUPPORTED_CALIBRE_BINARIES = {binary:binary + _extension for binary in ["ebook-convert", "calibredb", "ebook-meta"]} SUPPORTED_CALIBRE_BINARIES = {binary:binary + _extension for binary in ["ebook-convert", "calibredb"]}
def has_flag(value, bit_flag): def has_flag(value, bit_flag):

@ -156,6 +156,9 @@ def requires_kobo_auth(f):
limiter.check() limiter.check()
except RateLimitExceeded: except RateLimitExceeded:
return abort(429) return abort(429)
except (ConnectionError, Exception) as e:
log.error("Connection error to limiter backend: %s", e)
return abort(429)
user = ( user = (
ub.session.query(ub.User) ub.session.query(ub.User)
.join(ub.RemoteAuthToken) .join(ub.RemoteAuthToken)

@ -372,6 +372,16 @@
<input type="checkbox" id="config_ratelimiter" name="config_ratelimiter" {% if config.config_ratelimiter %}checked{% endif %}> <input type="checkbox" id="config_ratelimiter" name="config_ratelimiter" {% if config.config_ratelimiter %}checked{% endif %}>
<label for="config_ratelimiter">{{_('Limit failed login attempts')}}</label> <label for="config_ratelimiter">{{_('Limit failed login attempts')}}</label>
</div> </div>
<div data-related="ratelimiter_settings">
<div class="form-group" style="margin-left:10px;">
<label for="config_calibre">{{_('Configure Backend for Limiter')}}</label>
<input type="text" class="form-control" id="config_limiter_uri" name="config_limiter_uri" value="{% if config.config_limiter_uri != None %}{{ config.config_limiter_uri }}{% endif %}" autocomplete="off">
</div>
<div class="form-group" style="margin-left:10px;">
<label for="config_calibre">{{_('Options for Limiter')}}</label>
<input type="text" class="form-control" id="config_limiter_options" name="config_limiter_options" value="{% if config.config_limiter_options != None %}{{ config.config_limiter_options }}{% endif %}" autocomplete="off">
</div>
</div>
<div class="form-group"> <div class="form-group">
<label for="config_session">{{_('Session protection')}}</label> <label for="config_session">{{_('Session protection')}}</label>
<select name="config_session" id="config_session" class="form-control"> <select name="config_session" id="config_session" class="form-control">

@ -1272,6 +1272,10 @@ def register_post():
except RateLimitExceeded: except RateLimitExceeded:
flash(_(u"Please wait one minute to register next user"), category="error") flash(_(u"Please wait one minute to register next user"), category="error")
return render_title_template('register.html', config=config, title=_("Register"), page="register") return render_title_template('register.html', config=config, title=_("Register"), page="register")
except (ConnectionError, Exception) as e:
log.error("Connection error to limiter backend: %s", e)
flash(_("Connection error to limiter backend, please contact your administrator"), category="error")
return render_title_template('register.html', config=config, title=_("Register"), page="register")
if current_user is not None and current_user.is_authenticated: if current_user is not None and current_user.is_authenticated:
return redirect(url_for('web.index')) return redirect(url_for('web.index'))
if not config.get_mail_server_configured(): if not config.get_mail_server_configured():
@ -1370,7 +1374,11 @@ def login_post():
try: try:
limiter.check() limiter.check()
except RateLimitExceeded: except RateLimitExceeded:
flash(_(u"Please wait one minute before next login"), category="error") flash(_("Please wait one minute before next login"), category="error")
return render_login(username, form.get("password", ""))
except (ConnectionError, Exception) as e:
log.error("Connection error to limiter backend: %s", e)
flash(_("Connection error to limiter backend, please contact your administrator"), category="error")
return render_login(username, form.get("password", "")) return render_login(username, form.get("password", ""))
if current_user is not None and current_user.is_authenticated: if current_user is not None and current_user.is_authenticated:
return redirect(url_for('web.index')) return redirect(url_for('web.index'))

Loading…
Cancel
Save