diff --git a/mfa/recovery.py b/mfa/recovery.py
index e5d81b2..f4f1f28 100644
--- a/mfa/recovery.py
+++ b/mfa/recovery.py
@@ -2,7 +2,7 @@ from django.shortcuts import render
from django.views.decorators.cache import never_cache
from django.template.context_processors import csrf
from django.contrib.auth.hashers import make_password, PBKDF2PasswordHasher
-from django.http import HttpResponse,FileResponse,HttpResponseNotFound
+from django.http import HttpResponse,HttpResponseNotFound
from .Common import get_redirect_url
from .models import *
import simplejson
@@ -42,28 +42,25 @@ def genTokens(request):
uk.username = request.user.username
uk.properties={"secret_keys":hashedKeys, "salt":salt}
uk.key_type="RECOVERY"
- uk.enabled = False
+ uk.enabled = True
uk.save()
- request.session["recovery_keys"]=clearKeys
return HttpResponse(simplejson.dumps({"keys":clearKeys}))
-def download_codes(request):
- if not "recovery_keys" in request.session:
- return HttpResponseNotFound("This page isn't valid anymore.")
- response = HttpResponse('\n'.join(request.session["recovery_keys"]),content_type='text/text')
- response['Content-Disposition'] = 'attachment; filename = Recovery Codes.txt'
- return response
-def verify_login(request, username,token):
- key = User_Keys.objects.filter(username=username, key_type = "RECOVERY")
- secret_keys = key.properties["secret_keys"]
- salt = key.properties["salt"]
- hashedToken = make_password(token, salt, "pbkdf2_sha256_custom")
- if hashedToken == secret_keys[0]:
- secret_keys.pop(0)
- key.properties["secret_keys"] = secret_keys
- key.save()
- return [True, key.id, len(secret_keys) == 0]
+def verify_login(request, username, token):
+ for key in User_Keys.objects.filter(username=username, key_type = "RECOVERY"):
+ secret_keys = key.properties["secret_keys"]
+ salt = key.properties["salt"]
+ hashedToken = make_password(token, salt, "pbkdf2_sha256_custom")
+ for i in range(len(secret_keys)):
+ if hashedToken == secret_keys[i]:
+ secret_keys.pop(i)
+ key.properties["secret_keys"] = secret_keys
+ key.save()
+ return [True, key.id, len(secret_keys) == 0]
+ if len(secret_keys) == 0:
+ #Show a message ?
+ return [False]
return [False]
def getTokenLeft(request):
@@ -73,15 +70,27 @@ def getTokenLeft(request):
keyLeft += len(key.properties["secret_keys"])
return HttpResponse(simplejson.dumps({"left":keyLeft}))
+def recheck(request):
+ context = csrf(request)
+ context["mode"]="recheck"
+ if request.method == "POST":
+ if verify_login(request,request.user.username, token=request.POST["recovery"])[0]:
+ import time
+ request.session["mfa"]["rechecked_at"] = time.time()
+ return HttpResponse(simplejson.dumps({"recheck": True}), content_type="application/json")
+ else:
+ return HttpResponse(simplejson.dumps({"recheck": False}), content_type="application/json")
+ return render(request,"RECOVERY/recheck.html", context)
+
@never_cache
def auth(request):
from .views import login
context=csrf(request)
if request.method=="POST":
- tokenLength = len(request.POST["otp"])
+ tokenLength = len(request.POST["recovery"])
if tokenLength == 11 and "RECOVERY" not in settings.MFA_UNALLOWED_METHODS:
#Backup code check
- resBackup=verify_login(request, request.session["base_username"], token=request.POST["otp"])
+ resBackup=verify_login(request, request.session["base_username"], token=request.POST["recovery"])
if resBackup[0]:
mfa = {"verified": True, "method": "RECOVERY","id":resBackup[1], "lastBackup":resBackup[2]}
if getattr(settings, "MFA_RECHECK", False):
@@ -92,15 +101,16 @@ def auth(request):
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 render(request,"RECOVERY/Auth.html", context)
return login(request)
+ context["invalid"]=True
+
elif request.method=="GET":
- mfa = request.session["mfa"]
+ mfa = request.session.get("mfa")
if mfa and mfa["verified"] and mfa["lastBackup"]:
return login(request)
- context["invalid"]=True
- return render(request,"TOTP/Auth.html", context)
+ return render(request,"RECOVERY/Auth.html", context)
@never_cache
def start(request):
diff --git a/mfa/templates/MFA.html b/mfa/templates/MFA.html
index 8ef7a94..cd57c11 100644
--- a/mfa/templates/MFA.html
+++ b/mfa/templates/MFA.html
@@ -60,10 +60,10 @@
{% if not 'U2F' in UNALLOWED_AUTHEN_METHODS %}
Security Key
{% endif %}
- {% if not 'FIDO2' in UNALLOWED_AUTHEN_METHODS %}
+ {% if not 'FIDO2' in UNALLOWED_AUTHEN_METHODS %}
FIDO2 Security Key
{% endif %}
- {% if not 'Trusted_Devices' in UNALLOWED_AUTHEN_METHODS %}
+ {% if not 'Trusted_Devices' in UNALLOWED_AUTHEN_METHODS %}
Trusted Device
{% endif %}
diff --git a/mfa/templates/RECOVERY/Add.html b/mfa/templates/RECOVERY/Add.html
index 9f240f6..a5c8d71 100644
--- a/mfa/templates/RECOVERY/Add.html
+++ b/mfa/templates/RECOVERY/Add.html
@@ -21,46 +21,24 @@
.tooltip {
position: relative;
display: inline-block;
-}
-
-.tooltip .tooltiptext {
- visibility: hidden;
- width: 140px;
- background-color: #555;
- color: #fff;
- text-align: center;
- border-radius: 6px;
- padding: 5px;
- position: absolute;
- z-index: 1;
- bottom: 150%;
- left: 50%;
- margin-left: -75px;
- opacity: 0;
- transition: opacity 0.3s;
-}
-
-.tooltip .tooltiptext::after {
- content: "";
- position: absolute;
- top: 100%;
- left: 50%;
- margin-left: -5px;
- border-width: 5px;
- border-style: solid;
- border-color: #555 transparent transparent transparent;
-}
-
-.tooltip:hover .tooltiptext {
- visibility: visible;
- opacity: 1;
-}
- .return{
- margin: 1px;
}
-
+
+ .toolbtn {
+ border-radius: 7px;
+ }
+ .toolbtn:hover {
+ background-color: gray;
+ transition: 0.2s;
+ }
+ .toolbtn:active {
+ background-color: green;
+ transition: 0.2s;
+ }
+
+
{% endblock %}
{% block content %}
diff --git a/mfa/templates/RECOVERY/Auth.html b/mfa/templates/RECOVERY/Auth.html
index 77629dd..82b80a3 100644
--- a/mfa/templates/RECOVERY/Auth.html
+++ b/mfa/templates/RECOVERY/Auth.html
@@ -1,94 +1,14 @@
-
-
-
+
+{% endblock %}
+{% block content %}
+
+
+{% include "RECOVERY/recheck.html" with mode='auth' %}
-
-
-
-{% include "modal.html" %}
\ No newline at end of file
+ {% endblock %}
diff --git a/mfa/templates/RECOVERY/recheck.html b/mfa/templates/RECOVERY/recheck.html
index 7261939..b21f782 100644
--- a/mfa/templates/RECOVERY/recheck.html
+++ b/mfa/templates/RECOVERY/recheck.html
@@ -9,9 +9,9 @@
{% endif %}
return
});
- function send_totp() {
- $.ajax({"url":"{% url 'totp_recheck' %}", method:"POST",dataType:"JSON",
- data:{"csrfmiddlewaretoken":"{{ csrf_token }}","otp":$("#otp").val()},
+ function send_recovery() {
+ $.ajax({"url":"{% url 'recovery_recheck' %}", method:"POST",dataType:"JSON",
+ data:{"csrfmiddlewaretoken":"{{ csrf_token }}","recovery":$("#recovery").val()},
success:function (data) {
if (data["recheck"])
mfa_success_function();
@@ -21,17 +21,6 @@
}
})
}
- function tryToAuth() {
- const otp_length = $("#otp").val().length
- if (otp_length == 6) {
- document.getElementById("formLogin").submit();
- }
- else if (otp_length == 11) {
- const form = document.getElementById("formLogin");
- form.setAttribute("ACTION", "{% url 'recovery_auth' %}")
- form.submit();
- }
- }
diff --git a/mfa/totp.py b/mfa/totp.py
index 5f9f50e..3d42f23 100644
--- a/mfa/totp.py
+++ b/mfa/totp.py
@@ -5,7 +5,6 @@ from .Common import get_redirect_url
from .models import *
from django.template.context_processors import csrf
import simplejson
-from django.template.context import RequestContext
from django.conf import settings
import pyotp
from .views import login
@@ -27,7 +26,7 @@ def recheck(request):
context = csrf(request)
context["mode"]="recheck"
if request.method == "POST":
- if verify_login(request,request.user.username, token=request.POST["otp"]):
+ if verify_login(request,request.user.username, token=request.POST["otp"])[0]:
import time
request.session["mfa"]["rechecked_at"] = time.time()
return HttpResponse(simplejson.dumps({"recheck": True}), content_type="application/json")
diff --git a/mfa/urls.py b/mfa/urls.py
index beb13fd..6d5e7dc 100644
--- a/mfa/urls.py
+++ b/mfa/urls.py
@@ -16,7 +16,7 @@ urlpatterns = [
url(r'recovery/getTokenLeft', recovery.getTokenLeft, name="get_recovery_token_left"),
url(r'recovery/genTokens', recovery.genTokens, name="regen_recovery_tokens"),
url(r'recovery/auth', recovery.auth, name="recovery_auth"),
- url(r'recovery/download_codes', recovery.download_codes, name="download_recovery"),
+ url(r'recovery/recheck', recovery.recheck, name="recovery_recheck"),
url(r'email/start/', Email.start , name="start_email"),
url(r'email/auth/', Email.auth , name="email_auth"),
diff --git a/mfa/views.py b/mfa/views.py
index 23c56e7..7eecc6c 100644
--- a/mfa/views.py
+++ b/mfa/views.py
@@ -32,7 +32,7 @@ def verify(request,username):
request.session["base_username"] = username
#request.session["base_password"] = password
keys=User_Keys.objects.filter(username=username,enabled=1)
- methods=list(set([k.key_type for k in keys if k.key_type != "RECOVERY"]))
+ methods=list(set([k.key_type for k in keys]))
if "Trusted Device" in methods and not request.session.get("checked_trusted_device",False):
if TrustedDevice.verify(request):
@@ -40,10 +40,6 @@ def verify(request,username):
methods.remove("Trusted Device")
request.session["mfa_methods"] = methods
- if "TOTP" not in methods and "RECOVERY" not in settings.MFA_UNALLOWED_METHODS:
- #Add the "totp" option if user doesn't have totp auth (case with fido auth and backup code for instace)
- methods.append("TOTP")
-
if len(methods)==1:
return HttpResponseRedirect(reverse(methods[0].lower()+"_auth"))
return show_methods(request)