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:
parent
9d12b7caff
commit
6db4fb39ed
5 changed files with 58 additions and 55 deletions
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue