Fixed generation issue, warning when user uses its last backup code
This commit is contained in:
@@ -1,19 +1,22 @@
|
|||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
from django.views.decorators.cache import never_cache
|
from django.views.decorators.cache import never_cache
|
||||||
|
from django.template.context_processors import csrf
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
from .Common import get_redirect_url
|
from .Common import get_redirect_url
|
||||||
from .models import *
|
from .models import *
|
||||||
import simplejson
|
import simplejson
|
||||||
import random
|
import random
|
||||||
import string
|
import string
|
||||||
|
import datetime
|
||||||
|
|
||||||
#TODO :
|
#TODO :
|
||||||
# - Show authtificator panel on login everytime if RECOVERY is not deactivated
|
# - Show authtificator panel on login everytime if RECOVERY is not deactivated
|
||||||
# - Generation abuse checks
|
# - Generation abuse checks
|
||||||
|
|
||||||
def token_left(request):
|
def token_left(request, username=None):
|
||||||
uk = User_Keys.objects.filter(username=request.user.username, key_type="RECOVERY", enabled=True)
|
if not username and request:
|
||||||
|
username = request.user.username
|
||||||
|
uk = User_Keys.objects.filter(username=username, key_type = "RECOVERY")
|
||||||
keyLeft=0
|
keyLeft=0
|
||||||
for key in uk:
|
for key in uk:
|
||||||
keyEnabled = key.properties["enabled"]
|
keyEnabled = key.properties["enabled"]
|
||||||
@@ -58,9 +61,10 @@ def verify_login(request, username,token):
|
|||||||
if token == secret_keys[i] and key.properties["enabled"][i]:
|
if token == secret_keys[i] and key.properties["enabled"][i]:
|
||||||
key.properties["enabled"][i] = False
|
key.properties["enabled"][i] = False
|
||||||
key.save()
|
key.save()
|
||||||
if token_left(request) == 0:
|
lastToken = False
|
||||||
newTokens(username)
|
if token_left(None, username) == 0:
|
||||||
return [True, key.id]
|
lastToken = True
|
||||||
|
return [True, key.id, lastToken]
|
||||||
return [False]
|
return [False]
|
||||||
|
|
||||||
def getTokens(request):
|
def getTokens(request):
|
||||||
@@ -73,6 +77,35 @@ def getTokens(request):
|
|||||||
enable.append(key.properties["enabled"][i])
|
enable.append(key.properties["enabled"][i])
|
||||||
return HttpResponse(simplejson.dumps({"keys":tokens, "enable":enable}))
|
return HttpResponse(simplejson.dumps({"keys":tokens, "enable":enable}))
|
||||||
|
|
||||||
|
@never_cache
|
||||||
|
def auth(request):
|
||||||
|
from .views import login
|
||||||
|
context=csrf(request)
|
||||||
|
if request.method=="POST":
|
||||||
|
tokenLength = len(request.POST["otp"])
|
||||||
|
if tokenLength == 10 and "RECOVERY" not in settings.MFA_UNALLOWED_METHODS:
|
||||||
|
#Backup code check
|
||||||
|
resBackup=verify_login(request, request.session["base_username"], token=request.POST["otp"])
|
||||||
|
if resBackup[0]:
|
||||||
|
mfa = {"verified": True, "method": "RECOVERY","id":resBackup[1], "lastBackup":resBackup[2]}
|
||||||
|
if getattr(settings, "MFA_RECHECK", False):
|
||||||
|
mfa["next_check"] = datetime.datetime.timestamp((datetime.datetime.now()
|
||||||
|
+ datetime.timedelta(
|
||||||
|
seconds=random.randint(settings.MFA_RECHECK_MIN, settings.MFA_RECHECK_MAX))))
|
||||||
|
request.session["mfa"] = mfa
|
||||||
|
if resBackup[2]:
|
||||||
|
#If the last bakup code has just been used, we return a response insead of redirecting to login
|
||||||
|
context["lastBackup"] = True
|
||||||
|
return render(request,"TOTP/Auth.html", context)
|
||||||
|
return login(request)
|
||||||
|
elif request.method=="GET":
|
||||||
|
mfa = request.session["mfa"]
|
||||||
|
if mfa and mfa["verified"] and mfa["method"] == "RECOVERY" and "lastBackup":
|
||||||
|
return login(request)
|
||||||
|
|
||||||
|
context["invalid"]=True
|
||||||
|
return render(request,"TOTP/Auth.html", context)
|
||||||
|
|
||||||
@never_cache
|
@never_cache
|
||||||
def start(request):
|
def start(request):
|
||||||
"""Start Managing recovery tokens"""
|
"""Start Managing recovery tokens"""
|
||||||
|
|||||||
@@ -1,4 +1,14 @@
|
|||||||
<script type="application/javascript">
|
<script type="application/javascript">
|
||||||
|
$(document).ready(function showWarningLastBackup() {
|
||||||
|
{% if lastBackup %}
|
||||||
|
$("#modal-title").html("Last backup code used !")
|
||||||
|
$("#modal-body").html("Don't forget to regenerate new backup code after login !")
|
||||||
|
$('#modal-footer').html(`<FORM METHOD="GET" ACTION="{% url 'recovery_auth' %}" Id="confirmLogin" onSubmit="" name="recoveryLastBackupConfirm">
|
||||||
|
<input type='submit'class='btn btn-lg btn-success btn-block' value='Continue'>`)
|
||||||
|
$("#popUpModal").modal('show')
|
||||||
|
{% endif %}
|
||||||
|
return
|
||||||
|
});
|
||||||
function send_totp() {
|
function send_totp() {
|
||||||
$.ajax({"url":"{% url 'totp_recheck' %}", method:"POST",dataType:"JSON",
|
$.ajax({"url":"{% url 'totp_recheck' %}", method:"POST",dataType:"JSON",
|
||||||
data:{"csrfmiddlewaretoken":"{{ csrf_token }}","otp":$("#otp").val()},
|
data:{"csrfmiddlewaretoken":"{{ csrf_token }}","otp":$("#otp").val()},
|
||||||
@@ -10,7 +20,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
function tryToAuth() {
|
||||||
|
if ($("#otp").val().length == 6) {
|
||||||
|
document.getElementById("formLogin").submit();
|
||||||
|
}
|
||||||
|
else if ($("#otp").val().length == 10) {
|
||||||
|
const form = document.getElementById("formLogin");
|
||||||
|
form.setAttribute("ACTION", "{% url 'recovery_auth' %}")
|
||||||
|
form.submit();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<div class='container'>
|
<div class='container'>
|
||||||
@@ -58,7 +77,7 @@
|
|||||||
|
|
||||||
<div class="form-group d-grid gap-2">
|
<div class="form-group d-grid gap-2">
|
||||||
|
|
||||||
<input type="{% if mode == "auth" %}submit{% elif mode == 'recheck' %}button{% endif %}" {% if mode == "recheck" %}onclick="send_totp()" {% endif %} class="btn btn-lg btn-success btn-block" value="Sign in">
|
<input type="button" onclick="{% if mode == "recheck" %} send_totp() {% else %} tryToAuth() {% endif %}" class="btn btn-lg btn-success btn-block" value="Sign in">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
@@ -76,3 +95,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% include "modal.html" %}
|
||||||
11
mfa/totp.py
11
mfa/totp.py
@@ -53,17 +53,6 @@ def auth(request):
|
|||||||
seconds=random.randint(settings.MFA_RECHECK_MIN, settings.MFA_RECHECK_MAX))))
|
seconds=random.randint(settings.MFA_RECHECK_MIN, settings.MFA_RECHECK_MAX))))
|
||||||
request.session["mfa"] = mfa
|
request.session["mfa"] = mfa
|
||||||
return login(request)
|
return login(request)
|
||||||
elif tokenLength == 10 and "RECOVERY" not in settings.MFA_UNALLOWED_METHODS:
|
|
||||||
#Backup code check
|
|
||||||
resBackup=recovery.verify_login(request, request.session["base_username"], token=request.POST["otp"])
|
|
||||||
if resBackup[0]:
|
|
||||||
mfa = {"verified": True, "method": "RECOVERY","id":resBackup[1]}
|
|
||||||
if getattr(settings, "MFA_RECHECK", False):
|
|
||||||
mfa["next_check"] = datetime.datetime.timestamp((datetime.datetime.now()
|
|
||||||
+ datetime.timedelta(
|
|
||||||
seconds=random.randint(settings.MFA_RECHECK_MIN, settings.MFA_RECHECK_MAX))))
|
|
||||||
request.session["mfa"] = mfa
|
|
||||||
return login(request)
|
|
||||||
context["invalid"]=True
|
context["invalid"]=True
|
||||||
return render(request,"TOTP/Auth.html", context)
|
return render(request,"TOTP/Auth.html", context)
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ urlpatterns = [
|
|||||||
url(r'recovery/start', recovery.start, name="manage_recovery_codes"),
|
url(r'recovery/start', recovery.start, name="manage_recovery_codes"),
|
||||||
url(r'recovery/getTokens', recovery.getTokens, name="get_recovery_tokens"),
|
url(r'recovery/getTokens', recovery.getTokens, name="get_recovery_tokens"),
|
||||||
url(r'recovery/genTokens', recovery.genTokens, name="regen_recovery_tokens"),
|
url(r'recovery/genTokens', recovery.genTokens, name="regen_recovery_tokens"),
|
||||||
|
url(r'recovery/auth', recovery.auth, name="recovery_auth"),
|
||||||
|
|
||||||
url(r'email/start/', Email.start , name="start_email"),
|
url(r'email/start/', Email.start , name="start_email"),
|
||||||
url(r'email/auth/', Email.auth , name="email_auth"),
|
url(r'email/auth/', Email.auth , name="email_auth"),
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ def reset_cookie(request):
|
|||||||
response=HttpResponseRedirect(settings.LOGIN_URL)
|
response=HttpResponseRedirect(settings.LOGIN_URL)
|
||||||
response.delete_cookie("base_username")
|
response.delete_cookie("base_username")
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def login(request):
|
def login(request):
|
||||||
from django.contrib import auth
|
from django.contrib import auth
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|||||||
Reference in New Issue
Block a user