Browse Source

Add basic measurement against brute force attacks

Peter Justin 9 years ago
parent
commit
0496a2b5a3
3 changed files with 35 additions and 5 deletions
  1. 11 1
      flaskbb/auth/views.py
  2. 4 0
      flaskbb/exceptions.py
  3. 20 4
      flaskbb/user/models.py

+ 11 - 1
flaskbb/auth/views.py

@@ -9,6 +9,8 @@
     :copyright: (c) 2014 by the FlaskBB Team.
     :license: BSD, see LICENSE for more details.
 """
+from datetime import datetime, timedelta
+
 from flask import Blueprint, flash, redirect, url_for, request, current_app
 from flask_login import (current_user, login_user, login_required,
                          logout_user, confirm_login, login_fresh)
@@ -16,7 +18,7 @@ from flask_babelplus import gettext as _
 
 from flaskbb.utils.helpers import render_template, redirect_or_next
 from flaskbb.email import send_reset_token
-from flaskbb.exceptions import AuthenticationError
+from flaskbb.exceptions import AuthenticationError, LoginAttemptsExceeded
 from flaskbb.auth.forms import (LoginForm, LoginRecaptchaForm, ReauthForm,
                                 ForgotPasswordForm, ResetPasswordForm,
                                 RegisterRecaptchaForm, RegisterForm)
@@ -41,6 +43,14 @@ 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")
 
     return render_template("auth/login.html", form=form)
 

+ 4 - 0
flaskbb/exceptions.py

@@ -21,3 +21,7 @@ class AuthorizationRequired(FlaskBBError, Forbidden):
 
 class AuthenticationError(FlaskBBError):
     description = "Invalid username and password combination."
+
+
+class LoginAttemptsExceeded(FlaskBBError):
+    description = "The user has entered the wrong password too many times."

+ 20 - 4
flaskbb/user/models.py

@@ -9,7 +9,7 @@
     :license: BSD, see LICENSE for more details.
 """
 import os
-from datetime import datetime
+from datetime import datetime, timedelta
 
 from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
 from itsdangerous import SignatureExpired
@@ -19,7 +19,7 @@ from flask_login import UserMixin, AnonymousUserMixin
 
 from flaskbb._compat import max_integer
 from flaskbb.extensions import db, cache
-from flaskbb.exceptions import AuthenticationError
+from flaskbb.exceptions import AuthenticationError, LoginAttemptsExceeded
 from flaskbb.utils.settings import flaskbb_config
 from flaskbb.utils.database import CRUDMixin
 from flaskbb.forum.models import (Post, Topic, topictracker, TopicsRead,
@@ -179,7 +179,9 @@ class User(db.Model, UserMixin, CRUDMixin):
     @property
     def topics_per_day(self):
         """Returns the topics per day count."""
-        return round((float(self.topic_count) / float(self.days_registered)), 1)
+        return round(
+            (float(self.topic_count) / float(self.days_registered)), 1
+        )
 
     # Methods
     def __repr__(self):
@@ -213,7 +215,9 @@ class User(db.Model, UserMixin, CRUDMixin):
     @classmethod
     def authenticate(cls, login, password):
         """A classmethod for authenticating users.
-        It returns true if the user exists and has entered a correct password.
+        It returns the user object if the user/password combination is ok.
+        If the user has entered too often a wrong password, he will be locked
+        out of his account for a specified time.
 
         :param login: This can be either a username or a email address.
         :param password: The password that is connected to username and email.
@@ -222,7 +226,19 @@ class User(db.Model, UserMixin, CRUDMixin):
                                        User.email == login)).first()
 
         if user:
+            # check for the login attempts first
+            login_timeout = datetime.utcnow() - timedelta(
+                    minutes=flaskbb_config["LOGIN_TIMEOUT"]
+            )
+            if user.login_attempts >= flaskbb_config["LOGIN_ATTEMPTS"] and \
+                    user.last_failed_login > login_timeout:
+                raise LoginAttemptsExceeded
+
             if user.check_password(password):
+                if user.login_attempts >= flaskbb_config["LOGIN_ATTEMPTS"]:
+                    # reset them after a successful login attempt
+                    user.login_attempts = 0
+                    user.save()
                 return user
 
             # user exists, wrong password