Compare commits
15 Commits
v2.1.0b1
...
passwordle
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c1fbdab069 | ||
|
|
958775418d | ||
|
|
ef8287666c | ||
|
|
19563fe2b8 | ||
|
|
0e7c0911ca | ||
|
|
ebfdaf7504 | ||
|
|
c78e600b37 | ||
|
|
e1e931285f | ||
|
|
2709e70f88 | ||
|
|
25fe80d76d | ||
|
|
ba76f842bb | ||
|
|
87e83b3bbe | ||
|
|
a94ad50b93 | ||
|
|
75cf1e6130 | ||
|
|
911be9e106 |
14
CHANGELOG.md
14
CHANGELOG.md
@@ -1,5 +1,19 @@
|
||||
# Change Log
|
||||
|
||||
## 2.2.0 (Not released)
|
||||
* Added: MFA_REDIRECT_AFTER_REGISTRATION settings parameter
|
||||
|
||||
## 2.1.2
|
||||
* Fixed: Getting timestamp on Python 3.7 as ("%s") is raising an exception
|
||||
* Upgraded to FIDO 0.9.1
|
||||
|
||||
|
||||
## 2.1.1
|
||||
* Fixed: FIDO2 version in requirments.txt file.
|
||||
|
||||
## 2.1.0
|
||||
* Added Support for Touch ID for Mac OSx and iOS 14 on Safari
|
||||
|
||||
## 2.0.5
|
||||
* Fixed issue in __version__
|
||||
|
||||
|
||||
14
README.md
14
README.md
@@ -17,17 +17,17 @@ Web Authencation API (WebAuthn) is state-of-the art techology that is expected t
|
||||
For FIDO2, the following are supported
|
||||
* **security keys** (Firefox 60+, Chrome 67+, Edge 18+, Safari 13 on Mac OS, Chrome on Andriod, Safari on iOS 13.3+),
|
||||
* **Windows Hello** (Firefox 67+, Chrome 72+ , Edge) ,
|
||||
* **Apple's Touch ID** (Chrome 70+ on Mac OS X ),
|
||||
* **Apple's Touch ID/Face ID** (Chrome 70+ on Mac OS X, Safari on macOS Big Sur, Safari on iOS 14.0+ ),
|
||||
* **android-safetynet** (Chrome 70+, Firefox 68+)
|
||||
* **NFC devices using PCSC** (Not Tested, but as supported in fido2)
|
||||
|
||||
In English :), It allows you to verify the user by security keys on PC, Laptops or Mobiles, Windows Hello (Fingerprint, PIN) on Windows 10 Build 1903+ (May 2019 Update) Touch ID on Macbooks (Chrome) and Fingerprint/Face/Iris/PIN on Andriod Phones.
|
||||
In English :), It allows you to verify the user by security keys on PC, Laptops or Mobiles, Windows Hello (Fingerprint, PIN) on Windows 10 Build 1903+ (May 2019 Update) Touch/Face ID on Macbooks (Chrome, Safari), Touch/Face ID on iPhone and iPad and Fingerprint/Face/Iris/PIN on Android Phones.
|
||||
|
||||
Trusted device is a mode for the user to add a device that doesn't support security keys like iOS and andriod without fingerprints or NFC.
|
||||
Trusted device is a mode for the user to add a device that doesn't support security keys like Android without fingerprints or NFC.
|
||||
|
||||
**Note**: `U2F and FIDO2 can only be served under secure context (https)`
|
||||
|
||||
Package tested with Django 1.8, Django 2.1 on Python 2.7 and Python 3.5+ but it was not checked with any version in between but open for issues.
|
||||
Package tested with Django 1.8, Django 2.2 on Python 2.7 and Python 3.5+ but it was not checked with any version in between but open for issues.
|
||||
|
||||
Depends on
|
||||
|
||||
@@ -36,7 +36,7 @@ Depends on
|
||||
* ua-parser
|
||||
* user-agents
|
||||
* python-jose
|
||||
* fido2==0.8.1
|
||||
* fido2==0.9.0
|
||||
|
||||
# Installation
|
||||
1. using pip
|
||||
@@ -68,6 +68,8 @@ Depends on
|
||||
MFA_UNALLOWED_METHODS=() # Methods that shouldn't be allowed for the user
|
||||
MFA_LOGIN_CALLBACK="" # A function that should be called by username to login the user in session
|
||||
MFA_RECHECK=True # Allow random rechecking of the user
|
||||
MFA_REDIRECT_AFTER_REGISTRATION="mfa_home" # Allows Changing the page after successful registeration
|
||||
MFA_SUCCESS_REGISTRATION_MSG = "Go to Security Home" # The text of the link
|
||||
MFA_RECHECK_MIN=10 # Minimum interval in seconds
|
||||
MFA_RECHECK_MAX=30 # Maximum in seconds
|
||||
MFA_QUICKLOGIN=True # Allow quick login for returning users by provide only their 2FA
|
||||
@@ -91,6 +93,8 @@ Depends on
|
||||
**Notes**:
|
||||
* Starting version 1.1, ~~FIDO_LOGIN_URL~~ isn't required for FIDO2 anymore.
|
||||
* Starting version 1.7.0, Key owners can be specified.
|
||||
* Starting version 2.2.0
|
||||
* Added: `MFA_SUCCESS_REGISTRATION_MSG` & `MFA_REDIRECT_AFTER_REGISTRATION`
|
||||
1. Break your login function
|
||||
|
||||
Usually your login function will check for username and password, log the user in if the username and password are correct and create the user session, to support mfa, this has to change
|
||||
|
||||
@@ -9,7 +9,7 @@ Usually your login function will check for username and password, log the user i
|
||||
* if user has mfa then redirect to mfa page
|
||||
* if user doesn't have mfa then call your function to create the user session
|
||||
|
||||
<code>
|
||||
```python
|
||||
def login(request): # this function handles the login form POST
|
||||
user = auth.authenticate(username=username, password=password)
|
||||
if user is not None: # if the user object exist
|
||||
@@ -19,5 +19,5 @@ Usually your login function will check for username and password, log the user i
|
||||
return res
|
||||
return log_user_in(request,username=user.username)
|
||||
#log_user_in is a function that handles creatung user session, it should be in the setting file as MFA_CALLBACK
|
||||
</code>
|
||||
```
|
||||
|
||||
|
||||
@@ -28,3 +28,7 @@ def create_session(request,username):
|
||||
def logoutView(request):
|
||||
logout(request)
|
||||
return render(request,"logout.html",{})
|
||||
|
||||
def register(request):
|
||||
if request.method == "GET":
|
||||
return
|
||||
@@ -77,10 +77,8 @@ WSGI_APPLICATION = 'example.wsgi.application'
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.mysql',
|
||||
'NAME': 'mfa',
|
||||
'USER': 'root',
|
||||
'PASSWORD': 'password',
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': 'test_db',
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,7 +139,9 @@ MFA_RECHECK=True # Allow random rechecking of the user
|
||||
MFA_RECHECK_MIN=10 # Minimum interval in seconds
|
||||
MFA_RECHECK_MAX=30 # Maximum in seconds
|
||||
MFA_QUICKLOGIN=True # Allow quick login for returning users by provide only their 2FA
|
||||
MFA_HIDE_DISABLE=('FIDO2',) # Can the user disable his key (Added in 1.2.0).
|
||||
MFA_HIDE_DISABLE=('',) # Can the user disable his key (Added in 1.2.0).
|
||||
MFA_REDIRECT_AFTER_REGISTRATION="registered"
|
||||
MFA_SUCCESS_REGISTRATION_MSG="Go to Home"
|
||||
|
||||
TOKEN_ISSUER_NAME="PROJECT_NAME" #TOTP Issuer name
|
||||
|
||||
|
||||
@@ -12,6 +12,9 @@
|
||||
</ol>
|
||||
|
||||
<!-- Page Content -->
|
||||
{% if registered %}
|
||||
<div class="alert alert-success">Registered Successfully</div>
|
||||
{% endif %}
|
||||
<h1>Welcome {{ request.user.username }}!</h1>
|
||||
<hr>
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
|
||||
<!-- Custom styles for this template-->
|
||||
<link href="{% static 'css/sb-admin.css'%}" rel="stylesheet">
|
||||
|
||||
</head>
|
||||
|
||||
<body class="bg-dark">
|
||||
@@ -29,7 +28,6 @@
|
||||
{% if invalid %}
|
||||
<div class="alert alert-danger">Invalid Username or password</div>
|
||||
{% endif %}
|
||||
<form action="{% url 'login' %}" method="post">
|
||||
{% csrf_token %}
|
||||
<div class="form-group">
|
||||
<div class="form-label-group">
|
||||
@@ -37,14 +35,9 @@
|
||||
<label for="inputUsername">Username</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="form-label-group">
|
||||
<input type="password" id="inputPassword" name="password" class="form-control" placeholder="Password" required="required">
|
||||
<label for="inputPassword">Password</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-primary btn-block" type="submit">Login</button>
|
||||
|
||||
<button class="btn btn-primary btn-block" type="button" onclick="authen()">Login</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@@ -56,7 +49,7 @@
|
||||
|
||||
<!-- Core plugin JavaScript-->
|
||||
<script src="{% static 'vendor/jquery-easing/jquery.easing.min.js'%}"></script>
|
||||
|
||||
{% include 'FIDO2/login.html' %}
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
@@ -22,5 +22,6 @@ urlpatterns = [
|
||||
path('auth/login',auth.loginView,name="login"),
|
||||
path('auth/logout',auth.logoutView,name="logout"),
|
||||
|
||||
re_path('^$',views.home,name='home')
|
||||
re_path('^$',views.home,name='home'),
|
||||
path('registered/',views.registered,name='registered')
|
||||
]
|
||||
|
||||
@@ -5,3 +5,7 @@ from django.contrib.auth.decorators import login_required
|
||||
@login_required()
|
||||
def home(request):
|
||||
return render(request,"home.html",{})
|
||||
|
||||
@login_required()
|
||||
def registered(request):
|
||||
return render(request,"home.html",{"registered":True})
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
django==2.0
|
||||
django-sslserver
|
||||
django >= 2.2
|
||||
django_ssl
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
from django.conf import settings
|
||||
from django.core.mail import EmailMessage
|
||||
try:
|
||||
from django.urls import reverse
|
||||
except:
|
||||
from django.core.urlresolver import reverse
|
||||
|
||||
def send(to,subject,body):
|
||||
from_email_address = settings.EMAIL_HOST_USER
|
||||
@@ -9,3 +13,7 @@ def send(to,subject,body):
|
||||
email = EmailMessage(subject,body,From,to)
|
||||
email.content_subtype = "html"
|
||||
return email.send(False)
|
||||
|
||||
def get_redirect_url():
|
||||
return {"redirect_html": reverse(getattr(settings, 'MFA_REDIRECT_AFTER_REGISTRATION', 'mfa_home')),
|
||||
"reg_success_msg":getattr(settings,"MFA_SUCCESS_REGISTRATION_MSG")}
|
||||
|
||||
14
mfa/Email.py
14
mfa/Email.py
@@ -7,7 +7,9 @@ from .models import *
|
||||
#from django.template.context import RequestContext
|
||||
from .views import login
|
||||
from .Common import send
|
||||
|
||||
def sendEmail(request,username,secret):
|
||||
"""Send Email to the user after rendering `mfa_email_token_template`"""
|
||||
from django.contrib.auth import get_user_model
|
||||
User = get_user_model()
|
||||
key = getattr(User, 'USERNAME_FIELD', 'username')
|
||||
@@ -18,9 +20,10 @@ def sendEmail(request,username,secret):
|
||||
|
||||
@never_cache
|
||||
def start(request):
|
||||
"""Start adding email as a 2nd factor"""
|
||||
context = csrf(request)
|
||||
if request.method == "POST":
|
||||
if request.session["email_secret"] == request.POST["otp"]:
|
||||
if request.session["email_secret"] == request.POST["otp"]: #if successful
|
||||
uk=User_Keys()
|
||||
uk.username=request.user.username
|
||||
uk.key_type="Email"
|
||||
@@ -31,23 +34,24 @@ def start(request):
|
||||
from django.core.urlresolvers import reverse
|
||||
except:
|
||||
from django.urls import reverse
|
||||
return HttpResponseRedirect(reverse('mfa_home'))
|
||||
return HttpResponseRedirect(reverse(getattr(settings,'MFA_REDIRECT_AFTER_REGISTRATION','mfa_home')))
|
||||
context["invalid"] = True
|
||||
else:
|
||||
request.session["email_secret"] = str(randint(0,100000))
|
||||
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)
|
||||
@never_cache
|
||||
def auth(request):
|
||||
"""Authenticating the user by email."""
|
||||
context=csrf(request)
|
||||
if request.method=="POST":
|
||||
if request.session["email_secret"]==request.POST["otp"].strip():
|
||||
uk = User_Keys.objects.get(username=request.session["base_username"], key_type="Email")
|
||||
mfa = {"verified": True, "method": "Email","id":uk.id}
|
||||
if getattr(settings, "MFA_RECHECK", False):
|
||||
mfa["next_check"] = int((datetime.datetime.now() + datetime.timedelta(
|
||||
seconds = random.randint(settings.MFA_RECHECK_MIN, settings.MFA_RECHECK_MAX))).strftime("%s"))
|
||||
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
|
||||
|
||||
from django.utils import timezone
|
||||
|
||||
38
mfa/FIDO2.py
38
mfa/FIDO2.py
@@ -14,9 +14,12 @@ from fido2.utils import websafe_decode,websafe_encode
|
||||
from fido2.ctap2 import AttestedCredentialData
|
||||
from .views import login, reset_cookie
|
||||
import datetime
|
||||
from .Common import get_redirect_url
|
||||
from django.utils import timezone
|
||||
|
||||
|
||||
def recheck(request):
|
||||
"""Starts FIDO2 recheck"""
|
||||
context = csrf(request)
|
||||
context["mode"] = "recheck"
|
||||
request.session["mfa_recheck"] = True
|
||||
@@ -24,9 +27,13 @@ def recheck(request):
|
||||
|
||||
|
||||
def getServer():
|
||||
"""Get Server Info from settings and returns a Fido2Server"""
|
||||
rp = PublicKeyCredentialRpEntity(settings.FIDO_SERVER_ID, settings.FIDO_SERVER_NAME)
|
||||
return Fido2Server(rp)
|
||||
|
||||
|
||||
def begin_registeration(request):
|
||||
"""Starts registering a new FIDO Device, called from API"""
|
||||
server = getServer()
|
||||
registration_data, state = server.register_begin({
|
||||
u'id': request.user.username.encode("utf8"),
|
||||
@@ -36,8 +43,11 @@ def begin_registeration(request):
|
||||
request.session['fido_state'] = state
|
||||
|
||||
return HttpResponse(cbor.encode(registration_data), content_type = 'application/octet-stream')
|
||||
|
||||
|
||||
@csrf_exempt
|
||||
def complete_reg(request):
|
||||
"""Completes the registeration, called by API"""
|
||||
try:
|
||||
data = cbor.decode(request.body)
|
||||
|
||||
@@ -64,32 +74,41 @@ def complete_reg(request):
|
||||
except:
|
||||
pass
|
||||
return HttpResponse(simplejson.dumps({'status': 'ERR', "message": "Error on server, please try again later"}))
|
||||
|
||||
|
||||
def start(request):
|
||||
"""Start Registeration a new FIDO Token"""
|
||||
context = csrf(request)
|
||||
context.update(get_redirect_url())
|
||||
context["mfa_invoke"] = True
|
||||
return render(request, "FIDO2/Add.html", context)
|
||||
|
||||
|
||||
def getUserCredentials(username):
|
||||
credentials = []
|
||||
for uk in User_Keys.objects.filter(username = username, key_type = "FIDO2"):
|
||||
credentials.append(AttestedCredentialData(websafe_decode(uk.properties["device"])))
|
||||
return credentials
|
||||
|
||||
|
||||
def auth(request):
|
||||
context = csrf(request)
|
||||
return render(request, "FIDO2/Auth.html", context)
|
||||
|
||||
|
||||
def authenticate_begin(request):
|
||||
server = getServer()
|
||||
credentials=getUserCredentials(request.session.get("base_username",request.user.username))
|
||||
auth_data, state = server.authenticate_begin(credentials)
|
||||
#credentials = getUserCredentials(request.session.get("base_username", request.user.username))
|
||||
auth_data, state = server.authenticate_begin()
|
||||
request.session['fido_state'] = state
|
||||
return HttpResponse(cbor.encode(auth_data), content_type = "application/octet-stream")
|
||||
|
||||
|
||||
@csrf_exempt
|
||||
def authenticate_complete(request):
|
||||
try:
|
||||
credentials = []
|
||||
username=request.session.get("base_username",request.user.username)
|
||||
username = request.session.get("base_username", request.POST.get("username",request.user.username))
|
||||
server = getServer()
|
||||
credentials = getUserCredentials(username)
|
||||
data = cbor.decode(request.body)
|
||||
@@ -107,7 +126,8 @@ def authenticate_complete(request):
|
||||
signature
|
||||
)
|
||||
except ValueError:
|
||||
return HttpResponse(simplejson.dumps({'status': "ERR", "message": "Wrong challenge received, make sure that this is your security and try again."}),
|
||||
return HttpResponse(simplejson.dumps({'status': "ERR",
|
||||
"message": "Wrong challenge received, make sure that this is your security and try again."}),
|
||||
content_type = "application/json")
|
||||
except Exception as excep:
|
||||
try:
|
||||
@@ -133,8 +153,8 @@ def authenticate_complete(request):
|
||||
k.save()
|
||||
mfa = {"verified": True, "method": "FIDO2", 'id': k.id}
|
||||
if getattr(settings, "MFA_RECHECK", False):
|
||||
mfa["next_check"] = int((datetime.datetime.now()+ datetime.timedelta(
|
||||
seconds=random.randint(settings.MFA_RECHECK_MIN, settings.MFA_RECHECK_MAX))).strftime("%s"))
|
||||
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
|
||||
try:
|
||||
authenticated = request.user.is_authenticated
|
||||
@@ -143,8 +163,10 @@ def authenticate_complete(request):
|
||||
if not authenticated:
|
||||
res = login(request)
|
||||
if not "location" in res: return reset_cookie(request)
|
||||
return HttpResponse(simplejson.dumps({'status':"OK","redirect":res["location"]}),content_type="application/json")
|
||||
return HttpResponse(simplejson.dumps({'status': "OK", "redirect": res["location"]}),
|
||||
content_type = "application/json")
|
||||
return HttpResponse(simplejson.dumps({'status': "OK"}),
|
||||
content_type = "application/json")
|
||||
except Exception as exp:
|
||||
return HttpResponse(simplejson.dumps({'status': "ERR","message":exp.message}),content_type="application/json")
|
||||
return HttpResponse(simplejson.dumps({'status': "ERR", "message": exp.message}),
|
||||
content_type = "application/json")
|
||||
|
||||
@@ -57,9 +57,9 @@ def validate(request,username):
|
||||
key.save()
|
||||
mfa = {"verified": True, "method": "U2F","id":key.id}
|
||||
if getattr(settings, "MFA_RECHECK", False):
|
||||
mfa["next_check"] = int((datetime.datetime.now()
|
||||
mfa["next_check"] = datetime.datetime.timestamp((datetime.datetime.now()
|
||||
+ datetime.timedelta(
|
||||
seconds=random.randint(settings.MFA_RECHECK_MIN, settings.MFA_RECHECK_MAX))).strftime("%s"))
|
||||
seconds=random.randint(settings.MFA_RECHECK_MIN, settings.MFA_RECHECK_MAX))))
|
||||
request.session["mfa"] = mfa
|
||||
return True
|
||||
|
||||
@@ -76,6 +76,7 @@ def start(request):
|
||||
request.session['_u2f_enroll_'] = enroll.json
|
||||
context=csrf(request)
|
||||
context["token"]=simplejson.dumps(enroll.data_for_client)
|
||||
context.update(get_redirect_url())
|
||||
return render(request,"U2F/Add.html",context)
|
||||
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
__version__="2.1.0b1"
|
||||
__version__="2.2.0b1"
|
||||
|
||||
@@ -2,9 +2,10 @@ from django.db import models
|
||||
from jsonfield import JSONField
|
||||
from jose import jwt
|
||||
from django.conf import settings
|
||||
from jsonLookup import shasLookup, hasLookup
|
||||
JSONField.register_lookup(shasLookup)
|
||||
JSONField.register_lookup(hasLookup)
|
||||
#from jsonLookup import shasLookup, hasLookup
|
||||
# JSONField.register_lookup(shasLookup)
|
||||
# JSONField.register_lookup(hasLookup)
|
||||
|
||||
|
||||
class User_Keys(models.Model):
|
||||
username=models.CharField(max_length = 50)
|
||||
@@ -20,9 +21,12 @@ class User_Keys(models.Model):
|
||||
if self.key_type == "Trusted Device" and self.properties.get("signature","") == "":
|
||||
self.properties["signature"]= jwt.encode({"username": self.username, "key": self.properties["key"]}, settings.SECRET_KEY)
|
||||
super(User_Keys, self).save(force_insert=force_insert, force_update=force_update, using=using, update_fields=update_fields)
|
||||
|
||||
def __unicode__(self):
|
||||
return "%s -- %s"%(self.username,self.key_type)
|
||||
|
||||
def __str__(self):
|
||||
return self.__unicode__()
|
||||
|
||||
class Meta:
|
||||
app_label='mfa'
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
}).then(function (res)
|
||||
{
|
||||
if (res["status"] =='OK')
|
||||
$("#res").html("<div class='alert alert-success'>Registered Successfully, <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
|
||||
$("#res").html("<div class='alert alert-danger'>Registeration 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>")
|
||||
|
||||
|
||||
71
mfa/templates/FIDO2/fido2_auth.html
Normal file
71
mfa/templates/FIDO2/fido2_auth.html
Normal file
@@ -0,0 +1,71 @@
|
||||
{% load static %}
|
||||
<script type="application/javascript" src="{% static 'mfa/js/cbor.js' %}"></script>
|
||||
<script type="application/javascript" src="{% static 'mfa/js/ua-parser.min.js' %}"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
function authen()
|
||||
{
|
||||
fetch('{% url 'fido2_begin_auth' %}', {
|
||||
method: 'GET',
|
||||
}).then(function(response) {
|
||||
if(response.ok) return response.arrayBuffer();
|
||||
throw new Error('No credential available to authenticate!');
|
||||
}).then(CBOR.decode).then(function(options) {
|
||||
console.log(options)
|
||||
return navigator.credentials.get(options);
|
||||
}).then(function(assertion) {
|
||||
res=CBOR.encode({
|
||||
"credentialId": new Uint8Array(assertion.rawId),
|
||||
"authenticatorData": new Uint8Array(assertion.response.authenticatorData),
|
||||
"clientDataJSON": new Uint8Array(assertion.response.clientDataJSON),
|
||||
"signature": new Uint8Array(assertion.response.signature)
|
||||
});
|
||||
|
||||
return fetch('{% url 'fido2_complete_auth' %}', {
|
||||
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/cbor'},
|
||||
body:res,
|
||||
|
||||
}).then(function (response) {if (response.ok) return res = response.json()}).then(function (res) {
|
||||
if (res.status=="OK")
|
||||
{
|
||||
$("#msgdiv").addClass("alert alert-success").removeClass("alert-danger")
|
||||
$("#msgdiv").html("Verified....please wait")
|
||||
{% if mode == "auth" %}
|
||||
window.location.href=res.redirect;
|
||||
{% elif mode == "recheck" %}
|
||||
mfa_success_function();
|
||||
{% endif %}
|
||||
}
|
||||
else {
|
||||
$("#msgdiv").addClass("alert alert-danger").removeClass("alert-success")
|
||||
$("#msgdiv").html("Verification Failed as " + res.message + ", <a href='javascript:void(0)' onclick='authen())'> try again</a> or <a href='javascript:void(0)' onclick='history.back()'> Go Back</a>")
|
||||
|
||||
{% if mode == "auth" %}
|
||||
|
||||
{% elif mode == "recheck" %}
|
||||
|
||||
mfa_failed_function();
|
||||
{% endif %}
|
||||
}
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
$(document).ready(function () {
|
||||
if (location.protocol != 'https:') {
|
||||
$("#main_paragraph").addClass("alert alert-danger")
|
||||
$("#main_paragraph").html("FIDO2 must work under secure context")
|
||||
} else {
|
||||
{% if mfa_invoke %}
|
||||
ua=new UAParser().getResult()
|
||||
if (ua.browser.name == "Safari")
|
||||
$("#res").html("<button class='btn btn-success' onclick='authen()'>Authenticate...</button>")
|
||||
else
|
||||
authen()
|
||||
{% endif %}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
1
mfa/templates/FIDO2/login.html
Normal file
1
mfa/templates/FIDO2/login.html
Normal file
@@ -0,0 +1 @@
|
||||
{% include 'FIDO2/fido2_auth.html' %}
|
||||
@@ -1,6 +1,4 @@
|
||||
{% load static %}
|
||||
<script type="application/javascript" src="{% static 'mfa/js/cbor.js' %}"></script>
|
||||
<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">
|
||||
@@ -47,71 +45,4 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
function authen()
|
||||
{
|
||||
fetch('{% url 'fido2_begin_auth' %}', {
|
||||
method: 'GET',
|
||||
}).then(function(response) {
|
||||
if(response.ok) return response.arrayBuffer();
|
||||
throw new Error('No credential available to authenticate!');
|
||||
}).then(CBOR.decode).then(function(options) {
|
||||
console.log(options)
|
||||
return navigator.credentials.get(options);
|
||||
}).then(function(assertion) {
|
||||
res=CBOR.encode({
|
||||
"credentialId": new Uint8Array(assertion.rawId),
|
||||
"authenticatorData": new Uint8Array(assertion.response.authenticatorData),
|
||||
"clientDataJSON": new Uint8Array(assertion.response.clientDataJSON),
|
||||
"signature": new Uint8Array(assertion.response.signature)
|
||||
});
|
||||
|
||||
return fetch('{% url 'fido2_complete_auth' %}', {
|
||||
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/cbor'},
|
||||
body:res,
|
||||
|
||||
}).then(function (response) {if (response.ok) return res = response.json()}).then(function (res) {
|
||||
if (res.status=="OK")
|
||||
{
|
||||
$("#msgdiv").addClass("alert alert-success").removeClass("alert-danger")
|
||||
$("#msgdiv").html("Verified....please wait")
|
||||
{% if mode == "auth" %}
|
||||
window.location.href=res.redirect;
|
||||
{% elif mode == "recheck" %}
|
||||
mfa_success_function();
|
||||
{% endif %}
|
||||
}
|
||||
else {
|
||||
$("#msgdiv").addClass("alert alert-danger").removeClass("alert-success")
|
||||
$("#msgdiv").html("Verification Failed as " + res.message + ", <a href='javascript:void(0)' onclick='authen())'> try again</a> or <a href='javascript:void(0)' onclick='history.back()'> Go Back</a>")
|
||||
|
||||
{% if mode == "auth" %}
|
||||
|
||||
{% elif mode == "recheck" %}
|
||||
|
||||
mfa_failed_function();
|
||||
{% endif %}
|
||||
}
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
$(document).ready(function () {
|
||||
if (location.protocol != 'https:') {
|
||||
$("#main_paragraph").addClass("alert alert-danger")
|
||||
$("#main_paragraph").html("FIDO2 must work under secure context")
|
||||
} else {
|
||||
ua=new UAParser().getResult()
|
||||
if (ua.browser.name == "Safari")
|
||||
$("#res").html("<button class='btn btn-success' onclick='authen()'>Authenticate...</button>")
|
||||
else
|
||||
authen()
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
{% include 'FIDO2/fido2_auth.html' %}
|
||||
@@ -43,7 +43,7 @@
|
||||
else
|
||||
{
|
||||
alert("Your authenticator is added successfully.")
|
||||
window.location.href="{% url 'mfa_home' %}"
|
||||
window.location.href="{{ redirect_html }}"
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
if (data == "OK")
|
||||
{
|
||||
alert("Your device is added successfully.")
|
||||
window.location.href="{% url 'mfa_home' %}"
|
||||
window.location.href="{{ redirect_html }}"
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -40,9 +40,9 @@ def auth(request):
|
||||
if res[0]:
|
||||
mfa = {"verified": True, "method": "TOTP","id":res[1]}
|
||||
if getattr(settings, "MFA_RECHECK", False):
|
||||
mfa["next_check"] = int((datetime.datetime.now()
|
||||
mfa["next_check"] = datetime.datetime.timestamp((datetime.datetime.now()
|
||||
+ datetime.timedelta(
|
||||
seconds=random.randint(settings.MFA_RECHECK_MIN, settings.MFA_RECHECK_MAX))).strftime("%s"))
|
||||
seconds=random.randint(settings.MFA_RECHECK_MIN, settings.MFA_RECHECK_MAX))))
|
||||
request.session["mfa"] = mfa
|
||||
return login(request)
|
||||
context["invalid"]=True
|
||||
@@ -72,4 +72,5 @@ def verify(request):
|
||||
|
||||
@never_cache
|
||||
def start(request):
|
||||
return render(request,"TOTP/Add.html",{})
|
||||
"""Start Adding Time One Time Password (TOTP)"""
|
||||
return render(request,"TOTP/Add.html",get_redirect_url())
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
django >= 1.7
|
||||
django >= 2.0
|
||||
jsonfield
|
||||
simplejson
|
||||
pyotp
|
||||
@@ -6,5 +6,5 @@ python-u2flib-server
|
||||
ua-parser
|
||||
user-agents
|
||||
python-jose
|
||||
fido2 == 0.8.1
|
||||
fido2 == 0.9.1
|
||||
jsonLookup
|
||||
|
||||
8
setup.py
8
setup.py
@@ -4,7 +4,7 @@ from setuptools import find_packages, setup
|
||||
|
||||
setup(
|
||||
name='django-mfa2',
|
||||
version='2.1.0b1',
|
||||
version='2.2.0b2 ',
|
||||
description='Allows user to add 2FA to their accounts',
|
||||
long_description=open("README.md").read(),
|
||||
long_description_content_type="text/markdown",
|
||||
@@ -24,18 +24,16 @@ setup(
|
||||
'ua-parser',
|
||||
'user-agents',
|
||||
'python-jose',
|
||||
# 'fido2 == 0.8.1',
|
||||
'fido2 == 0.9.1',
|
||||
'jsonLookup'
|
||||
],
|
||||
dependency_links =["https://github.com/Yubico/python-fido2/tarball/master"],
|
||||
python_requires=">=3.5",
|
||||
include_package_data=True,
|
||||
zip_safe=False, # because we're including static files
|
||||
classifiers=[
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
"Development Status :: 4 - Beta",
|
||||
"Environment :: Web Environment",
|
||||
"Framework :: Django",
|
||||
"Framework :: Django :: 1.11",
|
||||
"Framework :: Django :: 2.0",
|
||||
"Framework :: Django :: 2.1",
|
||||
"Framework :: Django :: 2.2",
|
||||
|
||||
Reference in New Issue
Block a user