From d0113dd2ccbca8d1d70c6c35aa092f6b7b815a1b Mon Sep 17 00:00:00 2001 From: Spitap Date: Wed, 31 Aug 2022 21:13:15 +0200 Subject: [PATCH] Fixes and applied comments --- mfa/recovery.py | 60 +++++++++------- mfa/templates/MFA.html | 4 +- mfa/templates/RECOVERY/Add.html | 73 +++++++++---------- mfa/templates/RECOVERY/Auth.html | 104 ++++------------------------ mfa/templates/RECOVERY/recheck.html | 29 +++----- mfa/templates/TOTP/recheck.html | 28 +------- mfa/totp.py | 3 +- mfa/urls.py | 2 +- mfa/views.py | 6 +- 9 files changed, 98 insertions(+), 211 deletions(-) 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' %} -
    -
    -
    - Recovery Code -
    -
    - -
    - - - {% csrf_token %} - {% if invalid %} -
    - Sorry, The provided token is not valid. -
    - {% endif %} -
    -
    -
    -

    Enter enter your next recovery code

    -
    -
    - -
    -
    -
    -
    - - - - - -
    -
    - -
    - - -
    -
    -
    -
    -
    -
    -
    - {% if request.session.mfa_methods|length > 1 %} - Select Another Method - {% endif %} -
    -
    -
    -
    -
    -
    - -{% 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(); - } - }
    @@ -39,17 +28,17 @@
    - One Time Password + Recovery code
    -
    + {% csrf_token %} {% if invalid %}
    - Sorry, The provided token is not valid. + Sorry, The provided code is not valid.
    {% endif %} {% if quota %} @@ -60,7 +49,7 @@
    -

    Enter the 6-digits on your authenticator. Or input a recovery code

    +

    Enter the 11-digits on your authenticator. Or input a recovery code

    @@ -71,14 +60,14 @@ - +
    - +
    diff --git a/mfa/templates/TOTP/recheck.html b/mfa/templates/TOTP/recheck.html index 7261939..0a6328e 100644 --- a/mfa/templates/TOTP/recheck.html +++ b/mfa/templates/TOTP/recheck.html @@ -1,14 +1,4 @@
    @@ -60,7 +39,7 @@
    -

    Enter the 6-digits on your authenticator. Or input a recovery code

    +

    Enter the 6-digits on your authenticator

    @@ -71,15 +50,14 @@ - +
    - -
    +
    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)