Fixes and applied comments

This commit is contained in:
Spitap
2022-08-31 21:13:15 +02:00
parent cf4f6ed224
commit d0113dd2cc
9 changed files with 98 additions and 211 deletions

View File

@@ -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):

View File

@@ -60,10 +60,10 @@
{% if not 'U2F' in UNALLOWED_AUTHEN_METHODS %}
<li><a class="dropdown-item" href="{% url 'start_u2f' %}">Security Key</a></li>
{% endif %}
{% if not 'FIDO2' in UNALLOWED_AUTHEN_METHODS %}
{% if not 'FIDO2' in UNALLOWED_AUTHEN_METHODS %}
<li><a class="dropdown-item" href="{% url 'start_fido2' %}">FIDO2 Security Key</a></li>
{% endif %}
{% if not 'Trusted_Devices' in UNALLOWED_AUTHEN_METHODS %}
{% if not 'Trusted_Devices' in UNALLOWED_AUTHEN_METHODS %}
<li><a class="dropdown-item" href="{% url 'start_td' %}">Trusted Device</a></li>
{% endif %}
</ul>

View File

@@ -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;
}
</style>
.toolbtn {
border-radius: 7px;
}
.toolbtn:hover {
background-color: gray;
transition: 0.2s;
}
.toolbtn:active {
background-color: green;
transition: 0.2s;
}
</style>
<script src="{% static 'mfa/js/qrious.min.js' %}" type="text/javascript"></script>
<script type="text/javascript">
var clearCodes;
$(document).ready(function checkTokenLeft() {
$.ajax({"url":"{% url 'get_recovery_token_left' %}", dataType:"JSON",
success:function (data) {
@@ -93,18 +71,35 @@
<div class='row'><div class='offset-4 col-md-4' style='background-color:#f0f0f0;padding: 10px'>
<div class='row'>
<div class="col-6 offset-6">
<span onclick='window.location.href="{% url 'download_recovery' %}"' class='fa fa-download' title="Download"
<span onclick='download_recovery()' class='fa fa-download toolbtn' title="Download"
style='cursor:pointer'></span>&nbsp;&nbsp;
<span class='fa fa-clipboard' title="Copy" onclick="copy()" style='cursor:pointer'></span>
<span class='fa fa-clipboard toolbtn' title="Copy" onclick="copy()" style='cursor:pointer'></span>
</div></div><div id='recovery_codes'><pre>`;
for (let i = 0; i < data.keys.length; i++) {
htmlkey +="- " +data.keys[i] + "\n"
}
document.getElementById('tokens').innerHTML = htmlkey+"</pre></div></div></div>"
$("#popUpModal").modal('hide')
clearCodes = data.keys
}
})
}
function download_recovery() {
var element = document.createElement('a');
var text = "";
for(let i = 0; i < clearCodes.length; i++)
{
text = text + clearCodes[i]
if (i < clearCodes.length - 1) { text = text + "\n"}
}
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
element.setAttribute('download', 'Recovery Codes.txt');
element.style.display = 'none';
document.body.appendChild(element);
element.click();
console.log(element.innerHTML)
document.body.removeChild(element);
}
</script>
{% endblock %}
{% block content %}

View File

@@ -1,94 +1,14 @@
<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() {
$.ajax({"url":"{% url 'totp_recheck' %}", method:"POST",dataType:"JSON",
data:{"csrfmiddlewaretoken":"{{ csrf_token }}","otp":$("#otp").val()},
success:function (data) {
if (data["recheck"])
mfa_success_function();
else {
mfa_failed_function();
}
}
})
{% extends "mfa_auth_base.html" %}
{% block head %}
<style>
.row{
margin-left: 15px;
}
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();
}
}
</script>
<div class='container'>
<div class="row">
</style>
{% endblock %}
{% block content %}
<br/>
<br/>
{% include "RECOVERY/recheck.html" with mode='auth' %}
<div class="col-sm-10 col-sm-offset-1 col-xs-12 col-md-10 col-md-offset-1 col-lg-8 col-lg-offset-2">
<div class="panel panel-default card">
<div class="panel-heading card-header">
<strong>Recovery Code</strong>
</div>
<div class="panel-body card-body">
<FORM METHOD="POST" ACTION="{% url 'recovery_auth' %}" Id="formLogin" onSubmit="" name="FrontPage_Form1">
{% csrf_token %}
{% if invalid %}
<div class="alert alert-danger">
Sorry, The provided token is not valid.
</div>
{% endif %}
<fieldset>
<div class="row">
<div class="col-sm-12 col-md-12">
<p>Enter enter your next recovery code</p>
</div>
</div>
<div class="row">
<div class="col-sm-12 col-md-12">
<div class="form-group">
<div class="input-group mb-3">
<span class="input-group-addon input-group-text">
<i class="glyphicon glyphicon-lock bi bi-lock"></i>
</span>
<input class="form-control" size="11" value="" placeholder="e.g npXiX-7dZgK" name="otp" type="text" id="otp" autofocus>
</div>
</div>
<div class="form-group d-grid gap-2">
<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>
</fieldset>
</FORM>
</div>
<div class="row">
<div class="col-md-12 mb-3" style="padding-left: 25px">
{% if request.session.mfa_methods|length > 1 %}
<a href="{% url 'mfa_methods_list' %}">Select Another Method</a>
{% endif %}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% include "modal.html" %}
{% endblock %}

View File

@@ -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();
}
}
</script>
<div class='container'>
<div class="row">
@@ -39,17 +28,17 @@
<div class="col-sm-10 col-sm-offset-1 col-xs-12 col-md-10 col-md-offset-1 col-lg-8 col-lg-offset-2">
<div class="panel panel-default card">
<div class="panel-heading card-header">
<strong> One Time Password</strong>
<strong> Recovery code</strong>
</div>
<div class="panel-body card-body">
<FORM METHOD="POST" ACTION="{% url 'totp_auth' %}" Id="formLogin" onSubmit="" name="FrontPage_Form1">
<FORM METHOD="POST" ACTION="{% url 'recovery_auth' %}" Id="formLogin" onSubmit="" name="FrontPage_Form1">
{% csrf_token %}
{% if invalid %}
<div class="alert alert-danger">
Sorry, The provided token is not valid.
Sorry, The provided code is not valid.
</div>
{% endif %}
{% if quota %}
@@ -60,7 +49,7 @@
<fieldset>
<div class="row">
<div class="col-sm-12 col-md-12">
<p>Enter the 6-digits on your authenticator. Or input a recovery code</p>
<p>Enter the 11-digits on your authenticator. Or input a recovery code</p>
</div>
</div>
@@ -71,14 +60,14 @@
<span class="input-group-addon input-group-text">
<i class="glyphicon glyphicon-lock bi bi-lock"></i>
</span>
<input class="form-control" size="6" MaxLength="11" value="" placeholder="e.g 55552" name="otp" type="text" id="otp" autofocus>
<input class="form-control" size="11" MaxLength="11" value="" placeholder="e.g abcde-fghij" name="recovery" type="text" id="recovery" autofocus>
</div>
</div>
<div class="form-group d-grid gap-2">
<input type="button" onclick="{% if mode == "recheck" %} send_totp() {% else %} tryToAuth() {% endif %}" class="btn btn-lg btn-success btn-block" value="Sign in">
<input type="{% if mode == "auth" %}submit{% elif mode == 'recheck' %}button{% endif %}" {% if mode == "recheck" %}onclick="send_recovery()" {% endif %} class="btn btn-lg btn-success btn-block" value="Sign in">
</div>
</div>
</fieldset>

View File

@@ -1,14 +1,4 @@
<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() {
$.ajax({"url":"{% url 'totp_recheck' %}", method:"POST",dataType:"JSON",
data:{"csrfmiddlewaretoken":"{{ csrf_token }}","otp":$("#otp").val()},
@@ -21,17 +11,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();
}
}
</script>
<div class='container'>
<div class="row">
@@ -60,7 +39,7 @@
<fieldset>
<div class="row">
<div class="col-sm-12 col-md-12">
<p>Enter the 6-digits on your authenticator. Or input a recovery code</p>
<p>Enter the 6-digits on your authenticator</p>
</div>
</div>
@@ -71,15 +50,14 @@
<span class="input-group-addon input-group-text">
<i class="glyphicon glyphicon-lock bi bi-lock"></i>
</span>
<input class="form-control" size="6" MaxLength="11" value="" placeholder="e.g 55552" name="otp" type="text" id="otp" autofocus>
<input class="form-control" size="6" MaxLength="6" value="" placeholder="e.g 55552" name="otp" type="text" id="otp" autofocus>
</div>
</div>
<div class="form-group d-grid gap-2">
<input type="button" onclick="{% if mode == "recheck" %} send_totp() {% else %} tryToAuth() {% endif %}" class="btn btn-lg btn-success btn-block" value="Sign in">
</div>
<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"> </div>
</div>
</fieldset>
</FORM>

View File

@@ -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")

View File

@@ -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"),

View File

@@ -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)