Merged v2.6.0
This commit is contained in:
@@ -34,10 +34,16 @@ def start(request):
|
||||
from django.core.urlresolvers import reverse
|
||||
except:
|
||||
from django.urls import reverse
|
||||
return HttpResponseRedirect(reverse(getattr(settings,'MFA_REDIRECT_AFTER_REGISTRATION','mfa_home')))
|
||||
if getattr(settings, 'MFA_ENFORCE_RECOVERY_METHOD', False) and not User_Keys.objects.filter(
|
||||
key_type="RECOVERY", username=request.user.username).exists():
|
||||
request.session["mfa_reg"] = {"method": "Email",
|
||||
"name": getattr(settings, "MFA_RENAME_METHODS", {}).get("Email", "Email")}
|
||||
else:
|
||||
return HttpResponseRedirect(reverse(getattr(settings,'MFA_REDIRECT_AFTER_REGISTRATION','mfa_home')))
|
||||
context["invalid"] = True
|
||||
else:
|
||||
request.session["email_secret"] = str(randint(0,100000)) #generate a random integer
|
||||
|
||||
if sendEmail(request, request.user.username, request.session["email_secret"]):
|
||||
context["sent"] = True
|
||||
return render(request,"Email/Add.html", context)
|
||||
|
||||
12
mfa/FIDO2.py
12
mfa/FIDO2.py
@@ -66,7 +66,11 @@ def complete_reg(request):
|
||||
uk.owned_by_enterprise = getattr(settings, "MFA_OWNED_BY_ENTERPRISE", False)
|
||||
uk.key_type = "FIDO2"
|
||||
uk.save()
|
||||
return HttpResponse(simplejson.dumps({'status': 'OK'}))
|
||||
if getattr(settings, 'MFA_ENFORCE_RECOVERY_METHOD', False) and not User_Keys.objects.filter(key_type = "RECOVERY", username=request.user.username).exists():
|
||||
request.session["mfa_reg"] = {"method":"FIDO2","name": getattr(settings, "MFA_RENAME_METHODS", {}).get("FIDO2", "FIDO2")}
|
||||
return HttpResponse(simplejson.dumps({'status': 'RECOVERY'}))
|
||||
else:
|
||||
return HttpResponse(simplejson.dumps({'status': 'OK'}))
|
||||
except Exception as exp:
|
||||
import traceback
|
||||
print(traceback.format_exc())
|
||||
@@ -79,9 +83,11 @@ def complete_reg(request):
|
||||
|
||||
|
||||
def start(request):
|
||||
"""Start Registeration a new FIDO Token"""
|
||||
"""Start Registration a new FIDO Token"""
|
||||
context = csrf(request)
|
||||
context.update(get_redirect_url())
|
||||
context["method"] = {"name":getattr(settings,"MFA_RENAME_METHODS",{}).get("FIDO2","FIDO2 Security Key")}
|
||||
context["RECOVERY_METHOD"]=getattr(settings,"MFA_RENAME_METHODS",{}).get("RECOVERY","Recovery codes")
|
||||
return render(request, "FIDO2/Add.html", context)
|
||||
|
||||
|
||||
@@ -137,7 +143,7 @@ def authenticate_complete(request):
|
||||
except:
|
||||
pass
|
||||
return HttpResponse(simplejson.dumps({'status': "ERR",
|
||||
"message": excep.message}),
|
||||
"message": str(excep)}),
|
||||
content_type = "application/json")
|
||||
|
||||
if request.session.get("mfa_recheck", False):
|
||||
|
||||
35
mfa/U2F.py
35
mfa/U2F.py
@@ -52,25 +52,29 @@ def validate(request,username):
|
||||
|
||||
challenge = request.session.pop('_u2f_challenge_')
|
||||
device, c, t = complete_authentication(challenge, data, [settings.U2F_APPID])
|
||||
try:
|
||||
key=User_Keys.objects.get(username=username,properties__icontains='"publicKey": "%s"'%device["publicKey"])
|
||||
key.last_used=timezone.now()
|
||||
key.save()
|
||||
mfa = {"verified": True, "method": "U2F","id":key.id}
|
||||
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 True
|
||||
except:
|
||||
return False
|
||||
|
||||
|
||||
key=User_Keys.objects.get(username=username,properties__shas="$.device.publicKey=%s"%device["publicKey"])
|
||||
key.last_used=timezone.now()
|
||||
key.save()
|
||||
mfa = {"verified": True, "method": "U2F","id":key.id}
|
||||
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 True
|
||||
|
||||
def auth(request):
|
||||
context=csrf(request)
|
||||
s=sign(request.session["base_username"])
|
||||
request.session["_u2f_challenge_"]=s[0]
|
||||
context["token"]=s[1]
|
||||
|
||||
return render(request,"U2F/Auth.html")
|
||||
context["method"] = {"name": getattr(settings, "MFA_RENAME_METHODS", {}).get("U2F", "Classical Security Key")}
|
||||
return render(request,"U2F/Auth.html",context)
|
||||
|
||||
def start(request):
|
||||
enroll = begin_registration(settings.U2F_APPID, [])
|
||||
@@ -78,6 +82,8 @@ def start(request):
|
||||
context=csrf(request)
|
||||
context["token"]=simplejson.dumps(enroll.data_for_client)
|
||||
context.update(get_redirect_url())
|
||||
context["method"] = {"name": getattr(settings, "MFA_RENAME_METHODS", {}).get("U2F", "Classical Security Key")}
|
||||
context["RECOVERY_METHOD"] = getattr(settings, "MFA_RENAME_METHODS", {}).get("RECOVERY", "Recovery codes")
|
||||
return render(request,"U2F/Add.html",context)
|
||||
|
||||
|
||||
@@ -98,6 +104,11 @@ def bind(request):
|
||||
uk.properties = {"device":simplejson.loads(device.json),"cert":cert_hash}
|
||||
uk.key_type = "U2F"
|
||||
uk.save()
|
||||
if getattr(settings, 'MFA_ENFORCE_RECOVERY_METHOD', False) and not User_Keys.objects.filter(key_type="RECOVERY",
|
||||
username=request.user.username).exists():
|
||||
request.session["mfa_reg"] = {"method": "U2F",
|
||||
"name": getattr(settings, "MFA_RENAME_METHODS", {}).get("U2F", "Classical Security Key")}
|
||||
return HttpResponse('RECOVERY')
|
||||
return HttpResponse("OK")
|
||||
|
||||
def sign(username):
|
||||
|
||||
122
mfa/recovery.py
Normal file
122
mfa/recovery.py
Normal file
@@ -0,0 +1,122 @@
|
||||
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
|
||||
from .Common import get_redirect_url
|
||||
from .models import *
|
||||
import simplejson
|
||||
import random
|
||||
import string
|
||||
import datetime
|
||||
from django.utils import timezone
|
||||
|
||||
USER_FRIENDLY_NAME = "Recovery Codes"
|
||||
|
||||
class Hash(PBKDF2PasswordHasher):
|
||||
algorithm = 'pbkdf2_sha256_custom'
|
||||
iterations = getattr(settings,"RECOVERY_ITERATION",1)
|
||||
|
||||
def delTokens(request):
|
||||
#Only when all MFA have been deactivated, or to generate new !
|
||||
#We iterate only to clean if any error happend and multiple entry of RECOVERY created for one user
|
||||
for key in User_Keys.objects.filter(username=request.user.username, key_type = "RECOVERY"):
|
||||
if key.username == request.user.username:
|
||||
key.delete()
|
||||
|
||||
def randomGen(n):
|
||||
return ''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits) for _ in range(n))
|
||||
|
||||
@never_cache
|
||||
def genTokens(request):
|
||||
#Delete old ones
|
||||
delTokens(request)
|
||||
#Then generate new one
|
||||
salt = randomGen(15)
|
||||
hashedKeys = []
|
||||
clearKeys = []
|
||||
for i in range(5):
|
||||
token = randomGen(5) + "-" + randomGen(5)
|
||||
hashedToken = make_password(token, salt, 'pbkdf2_sha256_custom')
|
||||
hashedKeys.append(hashedToken)
|
||||
clearKeys.append(token)
|
||||
uk=User_Keys()
|
||||
|
||||
uk.username = request.user.username
|
||||
uk.properties={"secret_keys":hashedKeys, "salt":salt}
|
||||
uk.key_type="RECOVERY"
|
||||
uk.enabled = True
|
||||
uk.save()
|
||||
return HttpResponse(simplejson.dumps({"keys":clearKeys}))
|
||||
|
||||
|
||||
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,token in enumerate(secret_keys):
|
||||
if hashedToken == token:
|
||||
secret_keys.pop(i)
|
||||
key.properties["secret_keys"] = secret_keys
|
||||
key.last_used= timezone.now()
|
||||
key.save()
|
||||
return [True, key.id, len(secret_keys) == 0]
|
||||
return [False]
|
||||
|
||||
def getTokenLeft(request):
|
||||
uk = User_Keys.objects.filter(username=request.user.username, key_type = "RECOVERY")
|
||||
keyLeft=0
|
||||
for key in uk:
|
||||
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["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["recovery"])
|
||||
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,"RECOVERY/Auth.html", context)
|
||||
return login(request)
|
||||
context["invalid"]=True
|
||||
|
||||
elif request.method=="GET":
|
||||
mfa = request.session.get("mfa")
|
||||
if mfa and mfa["verified"] and mfa["lastBackup"]:
|
||||
return login(request)
|
||||
|
||||
return render(request,"RECOVERY/Auth.html", context)
|
||||
|
||||
@never_cache
|
||||
def start(request):
|
||||
"""Start Managing recovery tokens"""
|
||||
context = get_redirect_url()
|
||||
if "mfa_reg" in request.session:
|
||||
context["mfa_redirect"] = request.session["mfa_reg"]["name"]
|
||||
return render(request,"RECOVERY/Add.html",context)
|
||||
@@ -32,9 +32,14 @@
|
||||
}).then(function (res)
|
||||
{
|
||||
if (res["status"] =='OK')
|
||||
$("#res").html("<div class='alert alert-success'>Registered Successfully, <a href='{{redirect_html}}'> {{reg_success_msg}}</a></div>")
|
||||
else
|
||||
$("#res").html("<div class='alert alert-danger'>Registration Failed as " + res["message"] + ", <a href='javascript:void(0)' onclick='begin_reg()'> try again or <a href='{% url 'mfa_home' %}'> Go to Security Home</a></div>")
|
||||
$("#res").html("<div class='alert alert-success'>Registered Successfully, <a href='{{redirect_html}}'> {{reg_success_msg}}</a></div>")
|
||||
else if (res['status'] = "RECOVERY")
|
||||
{
|
||||
setTimeout(function (){location.href="{% url 'manage_recovery_codes' %}"},2500)
|
||||
$("#res").html("<div class='alert alert-success'>Registered Successfully, but <a href='{% url 'manage_recovery_codes' %}'>redirecting to {{ RECOVERY_METHOD }} method</a></div>")
|
||||
}
|
||||
else
|
||||
$("#res").html("<div class='alert alert-danger'>Registration Failed as " + res["message"] + ", <a href='javascript:void(0)' onclick='begin_reg()'> try again or <a href='{% url 'mfa_home' %}'> Go to Security Home</a></div>")
|
||||
|
||||
|
||||
}, function(reason) {
|
||||
@@ -61,7 +66,7 @@
|
||||
<div class="container">
|
||||
<div class="panel panel-default card">
|
||||
<div class="panel-heading card-header">
|
||||
<strong> FIDO2 Security Key</strong>
|
||||
<strong> Adding a New {{ method.name }}</strong>
|
||||
</div>
|
||||
<div class="panel-body card-body">
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<script type="application/javascript" src="{% static 'mfa/js/ua-parser.min.js' %}"></script>
|
||||
<div class="row">
|
||||
|
||||
<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="col-sm-10 col-sm-offset-1 col-xs-12 col-md-10 col-md-offset-1 col-lg-8 col-lg-offset-2 offset-2 col-8">
|
||||
<div class="panel panel-default card">
|
||||
<div class="panel-heading card-header">
|
||||
<strong> Security Key</strong>
|
||||
@@ -35,10 +35,10 @@
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12 mb-3" style="padding-left: 15px">
|
||||
<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>
|
||||
<a href="{% url 'mfa_methods_list' %}">Select Another Method</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -45,31 +45,31 @@
|
||||
<br/>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div align="center">
|
||||
<div class="offset-5 col-2" style="text-align: center">
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-success dropdown-toggle" data-toggle="dropdown" data-bs-toggle="dropdown">
|
||||
Add Method <span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
{% if not 'TOTP' in UNALLOWED_AUTHEN_METHODS %}
|
||||
<li><a class="dropdown-item" href="{% url 'start_new_otop' %}">Authenticator app</a></li>
|
||||
<li><a class="dropdown-item" href="{% url 'start_new_otop' %}">{% if 'TOTP' in RENAME_METHODS.keys %}{{ RENAME_METHODS.TOTP }}{% else %}Authenticator app{% endif %}</a></li>
|
||||
{% endif %}
|
||||
{% if not 'Email' in UNALLOWED_AUTHEN_METHODS %}
|
||||
<li><a class="dropdown-item" href="{% url 'start_email' %}">Email Token</a></li>
|
||||
<li><a class="dropdown-item" href="{% url 'start_email' %}">{% if 'Email' in RENAME_METHODS.keys %}{{ RENAME_METHODS.Email }}{% else %}Email Token{% endif %}</a></li>
|
||||
{% endif %}
|
||||
{% if not 'U2F' in UNALLOWED_AUTHEN_METHODS %}
|
||||
<li><a class="dropdown-item" href="{% url 'start_u2f' %}">Security Key</a></li>
|
||||
<li><a class="dropdown-item" href="{% url 'start_u2f' %}">{% if 'U2F' in RENAME_METHODS.keys %}{{ RENAME_METHODS.U2F }}{% else %}Security Key{% endif %}</a></li>
|
||||
{% endif %}
|
||||
{% if not 'FIDO2' in UNALLOWED_AUTHEN_METHODS %}
|
||||
<li><a class="dropdown-item" href="{% url 'start_fido2' %}">FIDO2 Security Key</a></li>
|
||||
{% if not 'FIDO2' in UNALLOWED_AUTHEN_METHODS %}
|
||||
<li><a class="dropdown-item" href="{% url 'start_fido2' %}">{% if 'FIDO2' in RENAME_METHODS.keys %}{{ RENAME_METHODS.FIDO2 }}{% else %}FIDO2 Security Key{% endif %}</a></li>
|
||||
{% endif %}
|
||||
{% if not 'Trusted_Devices' in UNALLOWED_AUTHEN_METHODS %}
|
||||
<li><a class="dropdown-item" href="{% url 'start_td' %}">Trusted Device</a></li>
|
||||
{% if not 'Trusted_Devices' in UNALLOWED_AUTHEN_METHODS %}
|
||||
<li><a class="dropdown-item" href="{% url 'start_td' %}">{% if 'Trusted_Devices' in RENAME_METHODS.keys %}{{ RENAME_METHODS.Trusted_Devices }}{% else %}Trusted Device{% endif %}</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<br/>
|
||||
</div>
|
||||
<br/>
|
||||
<table class="table table-striped">
|
||||
<tr>
|
||||
@@ -82,28 +82,42 @@
|
||||
<th>Status</th>
|
||||
<th>Delete</th>
|
||||
</tr>
|
||||
{% for key in keys %}
|
||||
<tr>
|
||||
{% if keys %}
|
||||
{% for key in keys %}
|
||||
<tr>
|
||||
|
||||
<td>{{ key.key_type }}</td>
|
||||
<td>{{ key.added_on }}</td>
|
||||
<td>{{ key.expires }}</td>
|
||||
<td>{% if key.device %}{{ key.device }}{% endif %}</td>
|
||||
<td>{{ key.last_used }}</td>
|
||||
{% if key.key_type in HIDE_DISABLE %}
|
||||
<td>{% if key.enabled %}On{% else %} Off{% endif %}</td>
|
||||
{% else %}
|
||||
<td><input type="checkbox" id="toggle_{{ key.id }}" {% if key.enabled %}checked{% endif %} data-onstyle="success" data-offstyle="danger" onchange="toggleKey({{ key.id }})" data-toggle="toggle" class="status_chk"></td>
|
||||
{% endif %}
|
||||
<td>{% if key.key_type in HIDE_DISABLE %}
|
||||
----
|
||||
{% else %}
|
||||
<a href="javascript:void(0)" onclick="deleteKey({{ key.id }},'{{ key.key_type }}')"> <span class="fa fa-trash fa-solid fa-trash-can bi bi-trash-fill"></span></a></td>
|
||||
<td>{{ key.name }}</td>
|
||||
<td>{{ key.added_on }}</td>
|
||||
<td>{% if key.expires %}{{ key.expires }}{% else %}N/A{% endif %}</td>
|
||||
<td>{% if key.device %}{{ key.device }}{% endif %}</td>
|
||||
<td>{% if key.last_used %}{{ key.last_used }}{% else %}Never{% endif %}</td>
|
||||
{% if key.key_type in HIDE_DISABLE %}
|
||||
<td>{% if key.enabled %}On{% else %} Off{% endif %}</td>
|
||||
{% else %}
|
||||
<td><input type="checkbox" id="toggle_{{ key.id }}" {% if key.enabled %}checked{% endif %} data-onstyle="success" data-offstyle="danger" onchange="toggleKey({{ key.id }})" data-toggle="toggle" class="status_chk"></td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% empty %}
|
||||
<td>{% if key.key_type in HIDE_DISABLE %}
|
||||
----
|
||||
{% else %}
|
||||
<a href="javascript:void(0)" onclick="deleteKey({{ key.id }},'{{ key.key_type }}')"> <span class="fa fa-trash fa-solid fa-trash-can bi bi-trash-fill"></span></a></td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% if "RECOVERY" not in UNALLOWED_AUTHEN_METHODS %}
|
||||
<tr>
|
||||
|
||||
<td>{{ recovery.name }}</td>
|
||||
<td>{{ recovery.added_on }}</td>
|
||||
<td>N/A</td>
|
||||
<td>N/A</td>
|
||||
<td>{% if recovery.last_used %}{{ recovery.last_used }}{% else %}Never{% endif %}</td>
|
||||
<td>On</td>
|
||||
<td><a href="{% url 'manage_recovery_codes' %}"> <span class="fa fa-wrench fa-solid fa-wrench bi bi-wrench-fill"></span></a></td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<tr><td colspan="7" align="center">You didn't have any keys yet.</td> </tr>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
134
mfa/templates/RECOVERY/Add.html
Normal file
134
mfa/templates/RECOVERY/Add.html
Normal file
@@ -0,0 +1,134 @@
|
||||
|
||||
{% extends "base.html" %}
|
||||
{% load static %}
|
||||
{% block head %}
|
||||
<style>
|
||||
#two-factor-steps {
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 3px;
|
||||
padding: 15px;
|
||||
}
|
||||
.tokenrow{
|
||||
margin-top: 10px;
|
||||
margin-left: 5px;
|
||||
}
|
||||
.row{
|
||||
margin: 3px;
|
||||
}
|
||||
.toolbtn {
|
||||
border-radius: 7px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.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) {
|
||||
tokenLeft = data.left
|
||||
html = ""
|
||||
{% if mfa_redirect %}
|
||||
html += "<div class='alert alert-success'>You have enrolled successfully in {{ mfa_redirect }} method, please generate recovery codes so that you can use in case you lost access to all your verification methods.</div>"
|
||||
{% endif %}
|
||||
if (tokenLeft == 0) {
|
||||
html += "<h6>You don't have any backup code linked to your account, please generate new ones !</h6>"
|
||||
|
||||
}
|
||||
else {
|
||||
html += "<p>You still have "+tokenLeft+" backup code left."
|
||||
}
|
||||
document.getElementById('tokens').innerHTML = html
|
||||
}})
|
||||
});
|
||||
function confirmRegenerateTokens() {
|
||||
htmlModal = "<h6>Caution! you can only view these token now, else you will need to generate new ones.</h6><div align='center'><button onclick='regenerateTokens()' class='btn btn-success'>Regenerate</button></div>"
|
||||
$("#modal-title").html("Regenerate your recovery Codes?")
|
||||
$("#modal-body").html(htmlModal)
|
||||
$("#popUpModal").modal('show')
|
||||
}
|
||||
function copy() {
|
||||
navigator.clipboard.writeText($("#recovery_codes").text());
|
||||
}
|
||||
function regenerateTokens() {
|
||||
$.ajax({
|
||||
"url":"{% url 'regen_recovery_tokens' %}", dataType:"JSON",
|
||||
success:function (data) {
|
||||
let htmlkey=`<p>Here are the recovery codes, you have to save them now as you won't able to view them again.</p>
|
||||
<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='download_recovery()' class='fa fa-download toolbtn' title="Download"></span>
|
||||
<span class='fa fa-clipboard toolbtn' title="Copy" onclick="copy()"></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 %}
|
||||
<br/>
|
||||
<br/>
|
||||
<div class="container d-flex justify-content-center">
|
||||
<div class="col-md-6 col-md-offset-3" id="two-factor-steps">
|
||||
|
||||
<div class="row">
|
||||
|
||||
<h4>Recovery Codes List</h4>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="tokenrow" id="tokens">
|
||||
|
||||
</div>
|
||||
<br/>
|
||||
<br/>
|
||||
<div class="row">
|
||||
|
||||
<div class="col-md-4 col-md-offset-4" style="padding-left: 0px" align="center">
|
||||
|
||||
<button onclick="confirmRegenerateTokens()" class="btn btn-success">Regenerate</button>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="col-md-6" align="right" style="padding-right: 30px">
|
||||
|
||||
<a href="{{redirect_html}}" class="btn btn-default btn-secondary" role="button"> {{reg_success_msg}}</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% include "modal.html" %}
|
||||
{% endblock %}
|
||||
14
mfa/templates/RECOVERY/Auth.html
Normal file
14
mfa/templates/RECOVERY/Auth.html
Normal file
@@ -0,0 +1,14 @@
|
||||
{% extends "mfa_auth_base.html" %}
|
||||
{% block head %}
|
||||
<style>
|
||||
.row{
|
||||
margin-left: 15px;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<br/>
|
||||
<br/>
|
||||
{% include "RECOVERY/recheck.html" with mode='auth' %}
|
||||
|
||||
{% endblock %}
|
||||
85
mfa/templates/RECOVERY/recheck.html
Normal file
85
mfa/templates/RECOVERY/recheck.html
Normal file
@@ -0,0 +1,85 @@
|
||||
<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_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();
|
||||
else {
|
||||
mfa_failed_function();
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
<div class="row">
|
||||
<div class="col-sm-10 col-sm-offset-1 col-xs-12 col-md-10 col-md-offset-1 col-lg-10 col-lg-offset-1">
|
||||
<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 code is not valid, or has already been used.
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if quota %}
|
||||
<div class="alert alert-warning">
|
||||
{{ quota }}
|
||||
</div>
|
||||
{% endif %}
|
||||
<fieldset>
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12">
|
||||
<p>Enter the 11-digits on your authenticator. Or input a 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" 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="{% 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>
|
||||
</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>
|
||||
{% include "modal.html" %}
|
||||
@@ -7,10 +7,22 @@
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 3px;
|
||||
padding: 15px;
|
||||
}
|
||||
}
|
||||
.row{
|
||||
margin: 0px;
|
||||
}
|
||||
.toolbtn {
|
||||
border-radius: 7px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.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">
|
||||
@@ -29,8 +41,17 @@
|
||||
})
|
||||
});
|
||||
function showKey() {
|
||||
const htmlkey = `
|
||||
<div class="row">
|
||||
<div class="col-11">
|
||||
<pre id="totp_secret">`+window.key+`</pre>
|
||||
</div>
|
||||
<div class="col-1">
|
||||
<span onclick=navigator.clipboard.writeText($("#totp_secret").text()) class="fa fa-clipboard toolbtn"></span>
|
||||
</div>
|
||||
</div>`
|
||||
$("#modal-title").html("Your Secret Key")
|
||||
$("#modal-body").html("<pre>"+window.key+"</pre")
|
||||
$("#modal-body").html(htmlkey)
|
||||
$("#popUpModal").modal('show')
|
||||
}
|
||||
function verify() {
|
||||
@@ -38,13 +59,16 @@
|
||||
$.ajax({
|
||||
"url":"{% url 'verify_otop' %}?key="+key+ "&answer="+answer,
|
||||
success:function (data) {
|
||||
if (data == "Error")
|
||||
alert("You entered wrong numbers, please try again")
|
||||
else
|
||||
{
|
||||
alert("Your authenticator is added successfully.")
|
||||
window.location.href="{{ redirect_html }}"
|
||||
}
|
||||
if (data =='Success')
|
||||
$("#res").html("<div class='alert alert-success'>Your authenticator is registered successfully, <a href='{{redirect_html}}'> {{reg_success_msg}}</a></div>")
|
||||
else if (data == "RECOVERY")
|
||||
{
|
||||
setTimeout(function (){location.href="{% url 'manage_recovery_codes' %}"},2500)
|
||||
$("#res").html("<div class='alert alert-success'>Your authenticator is registered successfully, but <a href='{% url 'manage_recovery_codes' %}'>redirecting to {{ RECOVERY_METHOD }} method</a></div>")
|
||||
}
|
||||
else
|
||||
$("#res").html("<div class='alert alert-danger'>The code provided doesn't match the key, please try again or <a href='{% url 'mfa_home' %}'> Go to Security Home</a></div>")
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -66,21 +90,24 @@
|
||||
<div class="container d-flex justify-content-center">
|
||||
<div class="col-md-6 col-md-offset-3" id="two-factor-steps">
|
||||
<div class="row" align="center">
|
||||
<h4>Adding Authenticator</h4>
|
||||
<h4>Adding a new {{ method.name }}</h4>
|
||||
</div>
|
||||
<div class="row">
|
||||
|
||||
<p>Scan the image below with the two-factor authentication app on your <a href="javascript:void(0)" onclick="showTOTP()">phone/PC</a>. If you can’t use a barcode,
|
||||
<a href="javascript:void(0)" onclick="showKey()">enter this text</a> instead. </p>
|
||||
</div>
|
||||
<div id="res">
|
||||
|
||||
<div class="row">
|
||||
</div>
|
||||
<div class="row" style="text-align: center">
|
||||
|
||||
<div align="center" style="display: none" id="second_step">
|
||||
<div align="center" style="display: none;text-align: center;align-content: center" id="second_step">
|
||||
|
||||
<img id="qr"/>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
|
||||
<p><b>Enter the six-digit code from the application</b></p>
|
||||
@@ -88,16 +115,13 @@
|
||||
|
||||
</div>
|
||||
<div class="row">
|
||||
|
||||
|
||||
<div class="offset-md-4 col-md-4">
|
||||
<input style="display: inline;width: 95%" maxlength="6" size="6" class="form-control" id="answer" placeholder="e.g 785481"/>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" style="padding-top: 10px;">
|
||||
<div class="col-md-6" style="padding-left: 0px">
|
||||
<div class="col-md-4 offset-md-4" style="padding-left: 0px">
|
||||
<button class="btn btn-success" onclick="verify()">Enable</button>
|
||||
</div>
|
||||
<div class="col-md-6" align="right" style="padding-right: 30px">
|
||||
<a href="{% url 'mfa_home' %}" class="btn btn-default btn-secondary" role="button">Cancel</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
</script>
|
||||
<div class='container'>
|
||||
@@ -40,7 +39,7 @@
|
||||
<fieldset>
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12">
|
||||
<p>Enter the 6-digits on your authenticator.</p>
|
||||
<p>Enter the 6-digits on your authenticator</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -58,8 +57,7 @@
|
||||
|
||||
<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">
|
||||
</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>
|
||||
@@ -76,3 +74,4 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% include "modal.html" %}
|
||||
@@ -13,7 +13,7 @@
|
||||
</style>
|
||||
<script src="{% static 'mfa/js/u2f-api.js' %}" type="text/javascript"></script>
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function addToken() {
|
||||
function addToken() {
|
||||
data=JSON.parse('{{ token|safe }}')
|
||||
console.log(data)
|
||||
u2f.register(data.appId,data.registerRequests,data.registeredKeys,function (response) {
|
||||
@@ -21,15 +21,24 @@
|
||||
"url":"{% url 'bind_u2f' %}",method:"POST",
|
||||
data:{"csrfmiddlewaretoken":"{{ csrf_token }}","response":JSON.stringify(response)},
|
||||
success:function (data) {
|
||||
if (data == "OK")
|
||||
{
|
||||
alert("Your device is added successfully.")
|
||||
window.location.href="{{ redirect_html }}"
|
||||
}
|
||||
if (data =='OK')
|
||||
$("#res").html("<div class='alert alert-success'>Your device is registered successfully, <a href='{{redirect_html}}'> {{reg_success_msg}}</a></div>")
|
||||
else if (data == "RECOVERY")
|
||||
{
|
||||
setTimeout(function (){location.href="{% url 'manage_recovery_codes' %}"},2500)
|
||||
$("#res").html("<div class='alert alert-success'>Your device is registered successfully, but <a href='{% url 'manage_recovery_codes' %}'>redirecting to {{ RECOVERY_METHOD }} method</a></div>")
|
||||
}
|
||||
else
|
||||
$("#res").html("<div class='alert alert-danger'>Registration failed, please <a href='javascript:void(0)' onclick='addToken()'>try again</a> or <a href='{% url 'mfa_home' %}'> Go to Security Home</a></div>")
|
||||
},
|
||||
error: function (data)
|
||||
{
|
||||
$("#res").html("<div class='alert alert-danger'>Registration failed, please <a href='javascript:void(0)' onclick='addToken()'>try again</a> or <a href='{% url 'mfa_home' %}'> Go to Security Home</a></div>")
|
||||
}
|
||||
})
|
||||
},5000)
|
||||
})
|
||||
}
|
||||
$(document).ready(addToken())
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -37,9 +46,11 @@
|
||||
<br/>
|
||||
<br/>
|
||||
<div class="container">
|
||||
|
||||
<div class="col-md-6 col-md-offset-3" id="two-factor-steps">
|
||||
<div id="res"></div>
|
||||
<div class="row" align="center">
|
||||
<h4>Adding Security Key</h4>
|
||||
<h4>Adding {{ method.name}}</h4>
|
||||
</div>
|
||||
<div class="row">
|
||||
<p style="color: green">Your secure Key should be flashing now, please press on button.</p>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<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> Security Key</strong>
|
||||
<strong> Verify your identity using {{ method.name }}</strong>
|
||||
</div>
|
||||
<div class="panel-body card-body">
|
||||
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
{% extends "mfa_auth_base.html" %}
|
||||
{% block content %}
|
||||
<br/>
|
||||
<br/>
|
||||
<div class='container'>
|
||||
<div class="row">
|
||||
|
||||
<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="col-sm-10 col-sm-offset-1 col-xs-12 col-md-10 col-md-offset-1 col-lg-8 col-lg-offset-2 offset-2 col-8">
|
||||
<div class="panel panel-default card">
|
||||
<div class="panel-heading card-header">
|
||||
<strong> Select Second Verification Method</strong>
|
||||
@@ -15,10 +14,11 @@
|
||||
{% for method in request.session.mfa_methods %}
|
||||
|
||||
<li><a href="{% url "mfa_goto" method %}">
|
||||
{% if method == "TOTP" %}Authenticator App
|
||||
{% elif method == "Email" %}Send OTP by Email
|
||||
{% elif method == "U2F" %}Secure Key
|
||||
{% elif method == "FIDO2" %}FIDO2 Secure Key
|
||||
{% if method == "TOTP" %}{% if 'TOTP' in RENAME_METHODS %}{{ RENAME_METHODS.TOTP }}{% else %}Authenticator App{% endif %}
|
||||
{% elif method == "Email" %}{% if 'Email' in RENAME_METHODS %}{{ RENAME_METHODS.Email }}{% else %}Send OTP by Email{% endif %}
|
||||
{% elif method == "U2F" %}{% if 'U2F' in RENAME_METHODS %}{{ RENAME_METHODS.U2F }}{% else %}Secure Key{% endif %}
|
||||
{% elif method == "FIDO2" %}{% if 'FIDO2' in RENAME_METHODS %}{{ RENAME_METHODS.FIDO2 }}{% else %}FIDO2 Secure Key{% endif %}
|
||||
{% elif method == "RECOVERY" %}{% if 'RECOVERY' in RENAME_METHODS %}{{ RENAME_METHODS.RECOVERY }}{% else %}Recovery Code{% endif %}
|
||||
{% endif %}
|
||||
</a> </li>
|
||||
{% endfor %}
|
||||
|
||||
39
mfa/totp.py
39
mfa/totp.py
@@ -5,13 +5,14 @@ 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
|
||||
import datetime
|
||||
from django.utils import timezone
|
||||
import random
|
||||
|
||||
|
||||
def verify_login(request,username,token):
|
||||
for key in User_Keys.objects.filter(username=username,key_type = "TOTP"):
|
||||
totp = pyotp.TOTP(key.properties["secret_key"])
|
||||
@@ -25,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")
|
||||
@@ -37,15 +38,18 @@ def recheck(request):
|
||||
def auth(request):
|
||||
context=csrf(request)
|
||||
if request.method=="POST":
|
||||
res=verify_login(request,request.session["base_username"],token = request.POST["otp"])
|
||||
if res[0]:
|
||||
mfa = {"verified": True, "method": "TOTP","id":res[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)
|
||||
tokenLength = len(request.POST["otp"])
|
||||
if tokenLength == 6:
|
||||
#TOTO code check
|
||||
res=verify_login(request,request.session["base_username"],token = request.POST["otp"])
|
||||
if res[0]:
|
||||
mfa = {"verified": True, "method": "TOTP","id":res[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
|
||||
return render(request,"TOTP/Auth.html", context)
|
||||
|
||||
@@ -68,10 +72,19 @@ def verify(request):
|
||||
#uk.name="Authenticatior #%s"%User_Keys.objects.filter(username=user.username,type="TOTP")
|
||||
uk.key_type="TOTP"
|
||||
uk.save()
|
||||
return HttpResponse("Success")
|
||||
if getattr(settings, 'MFA_ENFORCE_RECOVERY_METHOD', False) and not User_Keys.objects.filter(key_type="RECOVERY",
|
||||
username=request.user.username).exists():
|
||||
request.session["mfa_reg"] = {"method": "TOTP",
|
||||
"name": getattr(settings, "MFA_RENAME_METHODS", {}).get("TOTP", "TOTP")}
|
||||
return HttpResponse("RECOVERY")
|
||||
else:
|
||||
return HttpResponse("Success")
|
||||
else: return HttpResponse("Error")
|
||||
|
||||
@never_cache
|
||||
def start(request):
|
||||
"""Start Adding Time One Time Password (TOTP)"""
|
||||
return render(request,"TOTP/Add.html",get_redirect_url())
|
||||
context = get_redirect_url()
|
||||
context["RECOVERY_METHOD"] = getattr(settings, "MFA_RENAME_METHODS", {}).get("RECOVERY", "Recovery codes")
|
||||
context["method"] = {"name":getattr(settings,"MFA_RENAME_METHODS",{}).get("TOTP","Authenticator")}
|
||||
return render(request,"TOTP/Add.html",context)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from . import views,totp,U2F,TrustedDevice,helpers,FIDO2,Email
|
||||
from . import views,totp,U2F,TrustedDevice,helpers,FIDO2,Email,recovery
|
||||
#app_name='mfa'
|
||||
|
||||
try:
|
||||
@@ -12,6 +12,12 @@ urlpatterns = [
|
||||
url(r'totp/auth', totp.auth, name="totp_auth"),
|
||||
url(r'totp/recheck', totp.recheck, name="totp_recheck"),
|
||||
|
||||
url(r'recovery/start', recovery.start, name="manage_recovery_codes"),
|
||||
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/recheck', recovery.recheck, name="recovery_recheck"),
|
||||
|
||||
url(r'email/start/', Email.start , name="start_email"),
|
||||
url(r'email/auth/', Email.auth , name="email_auth"),
|
||||
|
||||
|
||||
18
mfa/views.py
18
mfa/views.py
@@ -1,3 +1,5 @@
|
||||
import importlib
|
||||
|
||||
from django.shortcuts import render
|
||||
from django.http import HttpResponse,HttpResponseRedirect
|
||||
from .models import *
|
||||
@@ -16,12 +18,16 @@ from user_agents import parse
|
||||
def index(request):
|
||||
keys=[]
|
||||
context={"keys":User_Keys.objects.filter(username=request.user.username),"UNALLOWED_AUTHEN_METHODS":settings.MFA_UNALLOWED_METHODS
|
||||
,"HIDE_DISABLE":getattr(settings,"MFA_HIDE_DISABLE",[])}
|
||||
,"HIDE_DISABLE":getattr(settings,"MFA_HIDE_DISABLE",[]),'RENAME_METHODS':getattr(settings,'MFA_RENAME_METHODS',{})}
|
||||
for k in context["keys"]:
|
||||
if k.key_type =="Trusted Device" :
|
||||
k.name = getattr(settings,'MFA_RENAME_METHODS',{}).get(k.key_type,k.key_type)
|
||||
if k.key_type =="Trusted Device":
|
||||
setattr(k,"device",parse(k.properties.get("user_agent","-----")))
|
||||
elif k.key_type == "FIDO2":
|
||||
setattr(k,"device",k.properties.get("type","----"))
|
||||
elif k.key_type == "RECOVERY":
|
||||
context["recovery"] = k
|
||||
continue
|
||||
keys.append(k)
|
||||
context["keys"]=keys
|
||||
return render(request,"MFA.html",context)
|
||||
@@ -37,17 +43,23 @@ def verify(request,username):
|
||||
return login(request)
|
||||
methods.remove("Trusted Device")
|
||||
request.session["mfa_methods"] = methods
|
||||
|
||||
if len(methods)==1:
|
||||
return HttpResponseRedirect(reverse(methods[0].lower()+"_auth"))
|
||||
if getattr(settings,"MFA_ALWAYS_GO_TO_LAST_METHOD",False):
|
||||
keys = keys.exclude(last_used__isnull=True).order_by("last_used")
|
||||
if keys.count()>0:
|
||||
return HttpResponseRedirect(reverse(keys[0].key_type.lower() + "_auth"))
|
||||
return show_methods(request)
|
||||
|
||||
def show_methods(request):
|
||||
return render(request,"select_mfa_method.html", {})
|
||||
return render(request,"select_mfa_method.html", {'RENAME_METHODS':getattr(settings,'MFA_RENAME_METHODS',{})})
|
||||
|
||||
def reset_cookie(request):
|
||||
response=HttpResponseRedirect(settings.LOGIN_URL)
|
||||
response.delete_cookie("base_username")
|
||||
return response
|
||||
|
||||
def login(request):
|
||||
from django.contrib import auth
|
||||
from django.conf import settings
|
||||
|
||||
Reference in New Issue
Block a user