Fixes for flask_limiter > 4

This commit is contained in:
Ozzie Isaacs
2026-02-14 10:08:32 +01:00
parent d47af66499
commit 8bdd95fc45
6 changed files with 56 additions and 33 deletions
+2 -2
View File
@@ -25,7 +25,7 @@ import sys
import os import os
import mimetypes import mimetypes
from flask import Flask, request from flask import Flask
from flask.sessions import SecureCookieSessionInterface from flask.sessions import SecureCookieSessionInterface
from .MyLoginManager import MyLoginManager from .MyLoginManager import MyLoginManager
from flask_principal import Principal from flask_principal import Principal
@@ -111,7 +111,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=False) limiter = Limiter(key_func=True, headers_enabled=True, default_limits=[], swallow_errors=False)
else: else:
limiter = None limiter = None
+25 -2
View File
@@ -18,7 +18,9 @@
import traceback import traceback
from flask import render_template from flask import render_template, request, flash, abort
from flask_limiter import RateLimitExceeded
from flask_babel import gettext as _
from werkzeug.exceptions import default_exceptions from werkzeug.exceptions import default_exceptions
try: try:
from werkzeug.exceptions import FailedDependency from werkzeug.exceptions import FailedDependency
@@ -26,7 +28,10 @@ except ImportError:
from werkzeug.exceptions import UnprocessableEntity as FailedDependency from werkzeug.exceptions import UnprocessableEntity as FailedDependency
from . import config, app, logger, services from . import config, app, logger, services
from .render_template import render_title_template
from .web import render_login
from .usermanagement import auth
from cps.string_helper import strip_whitespaces
log = logger.create() log = logger.create()
@@ -85,3 +90,21 @@ def init_errorhandler():
log.debug('LDAP server not accessible while trying to login to opds feed') log.debug('LDAP server not accessible while trying to login to opds feed')
return error_http(FailedDependency()) return error_http(FailedDependency())
@app.errorhandler(RateLimitExceeded)
def handle_rate_limit(__):
log.error("Rate limit exceeded {}".format(request.endpoint))
if "register" in request.endpoint:
flash(_(u"Please wait one minute to register next user"), category="error")
return render_title_template('register.html', config=config, title=_("Register"), page="register")
elif "login" in request.endpoint:
form = request.form.to_dict()
username = strip_whitespaces(form.get('username', "")).lower().replace("\n", "").replace("\r", "")
flash(_("Please wait one minute before next login"), category="error")
return render_login(username, form.get("password", ""))
elif "opds" in request.endpoint:
return auth.auth_error_callback(429)
else:
return abort(429)
+7 -7
View File
@@ -154,13 +154,13 @@ def requires_kobo_auth(f):
def inner(*args, **kwargs): def inner(*args, **kwargs):
auth_token = get_auth_token() auth_token = get_auth_token()
if auth_token is not None: if auth_token is not None:
try: #try:
limiter.check() # limiter.check()
except RateLimitExceeded: #except RateLimitExceeded:
return abort(429) # return abort(429)
except (ConnectionError, Exception) as e: #except (ConnectionError, Exception) as e:
log.error("Connection error to limiter backend: %s", e) # log.error("Connection error to limiter backend: %s", e)
return abort(429) # return abort(429)
user = ( user = (
ub.session.query(ub.User) ub.session.query(ub.User)
.join(ub.RemoteAuthToken) .join(ub.RemoteAuthToken)
+2 -2
View File
@@ -66,8 +66,8 @@ def main():
app.register_blueprint(tasks) app.register_blueprint(tasks)
app.register_blueprint(web) app.register_blueprint(web)
app.register_blueprint(basic) app.register_blueprint(basic)
app.register_blueprint(opds)
limiter.limit("3/minute", key_func=request_username)(opds) limiter.limit("3/minute", key_func=request_username)(opds)
app.register_blueprint(opds)
app.register_blueprint(jinjia) app.register_blueprint(jinjia)
app.register_blueprint(about) app.register_blueprint(about)
app.register_blueprint(shelf) app.register_blueprint(shelf)
@@ -77,9 +77,9 @@ def main():
app.register_blueprint(gdrive) app.register_blueprint(gdrive)
app.register_blueprint(editbook) app.register_blueprint(editbook)
if kobo_available: if kobo_available:
limiter.limit("3/minute", key_func=get_remote_address)(kobo)
app.register_blueprint(kobo) app.register_blueprint(kobo)
app.register_blueprint(kobo_auth) app.register_blueprint(kobo_auth)
limiter.limit("3/minute", key_func=get_remote_address)(kobo)
if oauth_available: if oauth_available:
app.register_blueprint(oauth) app.register_blueprint(oauth)
success = web_server.start() success = web_server.start()
+1 -1
View File
@@ -48,7 +48,7 @@ def verify_password(username, password):
if error is not None: if error is not None:
log.error(error) log.error(error)
else: else:
limiter.check() # limiter.check()
if check_password_hash(str(user.password), password): if check_password_hash(str(user.password), password):
[limiter.limiter.storage.clear(k.key) for k in limiter.current_limits] [limiter.limiter.storage.clear(k.key) for k in limiter.current_limits]
return user return user
+19 -19
View File
@@ -30,7 +30,7 @@ from flask import session as flask_session
from flask_babel import gettext as _ from flask_babel import gettext as _
from flask_babel import get_locale from flask_babel import get_locale
from .cw_login import login_user, logout_user, current_user from .cw_login import login_user, logout_user, current_user
from flask_limiter import RateLimitExceeded # from flask_limiter import RateLimitExceeded
from flask_limiter.util import get_remote_address from flask_limiter.util import get_remote_address
from sqlalchemy.exc import IntegrityError, InvalidRequestError, OperationalError from sqlalchemy.exc import IntegrityError, InvalidRequestError, OperationalError
from sqlalchemy.sql.expression import text, func, false, not_, and_, or_ from sqlalchemy.sql.expression import text, func, false, not_, and_, or_
@@ -1285,15 +1285,15 @@ def register_post():
if not config.config_public_reg: if not config.config_public_reg:
abort(404) abort(404)
to_save = request.form.to_dict() to_save = request.form.to_dict()
try: #try:
limiter.check() # limiter.check()
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: #except (ConnectionError, Exception) as e:
log.error("Connection error to limiter backend: %s", e) # log.error("Connection error to limiter backend: %s", e)
flash(_("Connection error to limiter backend, please contact your administrator"), category="error") # flash(_("Connection error to limiter backend, please contact your administrator"), 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")
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():
@@ -1388,15 +1388,15 @@ def login():
def login_post(): def login_post():
form = request.form.to_dict() form = request.form.to_dict()
username = strip_whitespaces(form.get('username', "")).lower().replace("\n","").replace("\r","") username = strip_whitespaces(form.get('username', "")).lower().replace("\n","").replace("\r","")
try: #try:
limiter.check() # limiter.check()
except RateLimitExceeded: #except RateLimitExceeded:
flash(_("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", "")) # return render_login(username, form.get("password", ""))
except (ConnectionError, Exception) as e: #except (ConnectionError, Exception) as e:
log.error("Connection error to limiter backend: %s", e) # log.error("Connection error to limiter backend: %s", e)
flash(_("Connection error to limiter backend, please contact your administrator"), category="error") # 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'))
if config.config_login_type == constants.LOGIN_LDAP and not services.ldap: if config.config_login_type == constants.LOGIN_LDAP and not services.ldap: