Browse Source

Add protection against brute force logins

sh4nks 9 years ago
parent
commit
2449235e14
4 changed files with 29 additions and 17 deletions
  1. 11 9
      flaskbb/auth/views.py
  2. 4 0
      flaskbb/exceptions.py
  3. 1 1
      flaskbb/user/models.py
  4. 13 7
      flaskbb/utils/helpers.py

+ 11 - 9
flaskbb/auth/views.py

@@ -16,7 +16,8 @@ from flask_login import (current_user, login_user, login_required,
                          logout_user, confirm_login, login_fresh)
 from flask_babelplus import gettext as _
 
-from flaskbb.utils.helpers import render_template, redirect_or_next
+from flaskbb.utils.helpers import (render_template, redirect_or_next,
+                                   format_timedelta)
 from flaskbb.email import send_reset_token, send_activation_token
 from flaskbb.exceptions import AuthenticationError, LoginAttemptsExceeded
 from flaskbb.auth.forms import (LoginForm, ReauthForm, ForgotPasswordForm,
@@ -44,14 +45,15 @@ def login():
             return redirect_or_next(url_for("forum.index"))
         except AuthenticationError:
             flash(_("Wrong Username or Password."), "danger")
-        except LoginAttemptsExceeded:
-            #timeout = (user.last_failed_login +
-            #           timedelta(minutes=flaskbb_config["LOGIN_TIMEOUT"]))
-            #timeout_left = datetime.utcnow() - timeout
-
-            flash(_("Your account has been locked for %(minutes)s minutes "
-                    "for too many failed login attempts.",
-                    minutes=flaskbb_config["LOGIN_TIMEOUT"]), "warning")
+        except LoginAttemptsExceeded as e:
+            user = e.user
+            timeout_till = (user.last_failed_login +
+                            timedelta(minutes=flaskbb_config["LOGIN_TIMEOUT"]))
+            timeout_left = timeout_till - datetime.utcnow()
+
+            flash(_("Your account has been temporarily locked due to failed "
+                    "login attempts. Try again in %(timeout_left)s.",
+                    timeout_left=format_timedelta(timeout_left)), "warning")
 
     return render_template("auth/login.html", form=form)
 

+ 4 - 0
flaskbb/exceptions.py

@@ -25,3 +25,7 @@ class AuthenticationError(FlaskBBError):
 
 class LoginAttemptsExceeded(FlaskBBError):
     description = "The user has entered the wrong password too many times."
+
+    def __init__(self, user):
+        super(LoginAttemptsExceeded, self).__init__()
+        self.user = user

+ 1 - 1
flaskbb/user/models.py

@@ -245,7 +245,7 @@ class User(db.Model, UserMixin, CRUDMixin):
             )
             if user.login_attempts >= flaskbb_config["LOGIN_ATTEMPTS"] and \
                     user.last_failed_login > login_timeout:
-                raise LoginAttemptsExceeded
+                raise LoginAttemptsExceeded(user)
 
             if user.check_password(password):
                 if user.login_attempts >= flaskbb_config["LOGIN_ATTEMPTS"]:

+ 13 - 7
flaskbb/utils/helpers.py

@@ -19,7 +19,7 @@ from datetime import datetime, timedelta
 import requests
 import unidecode
 from flask import session, url_for, flash, redirect, request
-from babel.dates import format_timedelta
+from babel.dates import format_timedelta as babel_format_timedelta
 from flask_babelplus import lazy_gettext as _
 from flask_themes2 import render_theme_template
 from flask_login import current_user
@@ -367,6 +367,17 @@ def format_date(value, format='%Y-%m-%d'):
     return value.strftime(format)
 
 
+def format_timedelta(delta, **kwargs):
+    """Wrapper around babel's format_timedelta to make it user language
+    aware.
+    """
+    locale = flaskbb_config.get("DEFAULT_LANGUAGE", "en")
+    if current_user.is_authenticated and current_user.language is not None:
+        locale = current_user.language
+
+    return babel_format_timedelta(delta, locale=locale, **kwargs)
+
+
 def time_since(time):  # pragma: no cover
     """Returns a string representing time since e.g.
     3 days ago, 5 hours ago.
@@ -374,12 +385,7 @@ def time_since(time):  # pragma: no cover
     :param time: A datetime object
     """
     delta = time - datetime.utcnow()
-
-    locale = "en"
-    if current_user.is_authenticated and current_user.language is not None:
-        locale = current_user.language
-
-    return format_timedelta(delta, add_direction=True, locale=locale)
+    return format_timedelta(delta, add_direction=True)
 
 
 def format_quote(username, content):