1
0
Fork 0

improve security and fix error msg

- Instead of passing the user as a hidden form element, we use a session variable.
- Introduces a 60 second limit on completing the login, and an exponentially increasing delay to attempt to login with 2FA if the code is entered incorrectly.
- use proper Django form error when incorrect otp value entered
This commit is contained in:
Hugh Rundle 2022-09-18 16:32:42 +10:00
parent 9d12b7caff
commit 6db4fb39ed
5 changed files with 58 additions and 55 deletions

View file

@ -1,4 +1,5 @@
""" class views for login/register views """
from datetime import datetime
from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.decorators import login_required
from django.shortcuts import redirect
@ -29,7 +30,7 @@ class Login(View):
}
return TemplateResponse(request, "landing/login.html", data)
#pylint: disable=too-many-return-statements
# pylint: disable=too-many-return-statements
@sensitive_variables("password")
@method_decorator(sensitive_post_parameters("password"))
def post(self, request):
@ -54,12 +55,9 @@ class Login(View):
if user is not None:
# if 2fa is set, don't log them in until they enter the right code
if user.two_factor_auth is True:
form = forms.Confirm2FAForm(request.GET, user)
return TemplateResponse(
request,
"two_factor_auth/two_factor_login.html",
{"form": form, "2fa_user": user},
)
request.session["2fa_user"] = user.username
request.session["2fa_auth_time"] = datetime.now()
return redirect("login-with-2fa")
# otherwise, successful login
login(request, user)

View file

@ -1,4 +1,5 @@
""" class views for 2FA management """
from datetime import datetime, timedelta
import time
import pyotp
import qrcode
@ -94,17 +95,31 @@ class Disable2FA(View):
class LoginWith2FA(View):
"""Check 2FA code matches before allowing login"""
def get(self, request):
"""Display 2FA form"""
data = {"form": forms.Confirm2FAForm()}
return TemplateResponse(request, "two_factor_auth/two_factor_login.html", data)
def post(self, request):
"""Check 2FA code and allow/disallow login"""
user = models.User.objects.get(username=request.POST.get("2fa_user"))
user = models.User.objects.get(username=request.session["2fa_user"])
elapsed_time = datetime.now() - request.session["2fa_auth_time"]
form = forms.Confirm2FAForm(request.POST, instance=user)
# don't allow the login credentials to last too long before completing login
if elapsed_time > timedelta(seconds=60):
request.session["2fa_user"] = None
request.session["2fa_auth_time"] = 0
return redirect("/")
if not form.is_valid():
time.sleep(2) # make life slightly harder for bots
data = {
"form": form,
"2fa_user": user,
"error": "Code does not match, try again",
}
# make life harder for bots
# humans are unlikely to get it wrong more than twice
if not request.session["2fa_attempts"]:
request.session["2fa_attempts"] = 0
request.session["2fa_attempts"] = request.session["2fa_attempts"] + 1
time.sleep(2 ** request.session["2fa_attempts"])
data = {"form": form, "2fa_user": user}
return TemplateResponse(
request, "two_factor_auth/two_factor_login.html", data
)