Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
19563fe2b8 | ||
|
|
0e7c0911ca | ||
|
|
ebfdaf7504 | ||
|
|
c78e600b37 | ||
|
|
e1e931285f | ||
|
|
2709e70f88 | ||
|
|
25fe80d76d | ||
|
|
ba76f842bb | ||
|
|
87e83b3bbe | ||
|
|
a94ad50b93 | ||
|
|
75cf1e6130 | ||
|
|
911be9e106 |
11
CHANGELOG.md
11
CHANGELOG.md
@@ -1,5 +1,16 @@
|
|||||||
# Change Log
|
# Change Log
|
||||||
|
|
||||||
|
## 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
|
## 2.0.5
|
||||||
* Fixed issue in __version__
|
* Fixed issue in __version__
|
||||||
|
|
||||||
|
|||||||
10
README.md
10
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
|
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+),
|
* **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) ,
|
* **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+)
|
* **android-safetynet** (Chrome 70+, Firefox 68+)
|
||||||
* **NFC devices using PCSC** (Not Tested, but as supported in fido2)
|
* **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)`
|
**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
|
Depends on
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ Depends on
|
|||||||
* ua-parser
|
* ua-parser
|
||||||
* user-agents
|
* user-agents
|
||||||
* python-jose
|
* python-jose
|
||||||
* fido2==0.8.1
|
* fido2==0.9.0
|
||||||
|
|
||||||
# Installation
|
# Installation
|
||||||
1. using pip
|
1. using pip
|
||||||
|
|||||||
@@ -46,8 +46,8 @@ def auth(request):
|
|||||||
uk = User_Keys.objects.get(username=request.session["base_username"], key_type="Email")
|
uk = User_Keys.objects.get(username=request.session["base_username"], key_type="Email")
|
||||||
mfa = {"verified": True, "method": "Email","id":uk.id}
|
mfa = {"verified": True, "method": "Email","id":uk.id}
|
||||||
if getattr(settings, "MFA_RECHECK", False):
|
if getattr(settings, "MFA_RECHECK", False):
|
||||||
mfa["next_check"] = int((datetime.datetime.now() + datetime.timedelta(
|
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
|
request.session["mfa"] = mfa
|
||||||
|
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|||||||
24
mfa/FIDO2.py
24
mfa/FIDO2.py
@@ -16,6 +16,7 @@ from .views import login,reset_cookie
|
|||||||
import datetime
|
import datetime
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
|
|
||||||
def recheck(request):
|
def recheck(request):
|
||||||
context = csrf(request)
|
context = csrf(request)
|
||||||
context["mode"] = "recheck"
|
context["mode"] = "recheck"
|
||||||
@@ -26,6 +27,8 @@ def recheck(request):
|
|||||||
def getServer():
|
def getServer():
|
||||||
rp = PublicKeyCredentialRpEntity(settings.FIDO_SERVER_ID, settings.FIDO_SERVER_NAME)
|
rp = PublicKeyCredentialRpEntity(settings.FIDO_SERVER_ID, settings.FIDO_SERVER_NAME)
|
||||||
return Fido2Server(rp)
|
return Fido2Server(rp)
|
||||||
|
|
||||||
|
|
||||||
def begin_registeration(request):
|
def begin_registeration(request):
|
||||||
server = getServer()
|
server = getServer()
|
||||||
registration_data, state = server.register_begin({
|
registration_data, state = server.register_begin({
|
||||||
@@ -36,6 +39,8 @@ def begin_registeration(request):
|
|||||||
request.session['fido_state'] = state
|
request.session['fido_state'] = state
|
||||||
|
|
||||||
return HttpResponse(cbor.encode(registration_data), content_type = 'application/octet-stream')
|
return HttpResponse(cbor.encode(registration_data), content_type = 'application/octet-stream')
|
||||||
|
|
||||||
|
|
||||||
@csrf_exempt
|
@csrf_exempt
|
||||||
def complete_reg(request):
|
def complete_reg(request):
|
||||||
try:
|
try:
|
||||||
@@ -64,20 +69,25 @@ def complete_reg(request):
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
return HttpResponse(simplejson.dumps({'status': 'ERR', "message": "Error on server, please try again later"}))
|
return HttpResponse(simplejson.dumps({'status': 'ERR', "message": "Error on server, please try again later"}))
|
||||||
|
|
||||||
|
|
||||||
def start(request):
|
def start(request):
|
||||||
context = csrf(request)
|
context = csrf(request)
|
||||||
return render(request, "FIDO2/Add.html", context)
|
return render(request, "FIDO2/Add.html", context)
|
||||||
|
|
||||||
|
|
||||||
def getUserCredentials(username):
|
def getUserCredentials(username):
|
||||||
credentials = []
|
credentials = []
|
||||||
for uk in User_Keys.objects.filter(username = username, key_type = "FIDO2"):
|
for uk in User_Keys.objects.filter(username = username, key_type = "FIDO2"):
|
||||||
credentials.append(AttestedCredentialData(websafe_decode(uk.properties["device"])))
|
credentials.append(AttestedCredentialData(websafe_decode(uk.properties["device"])))
|
||||||
return credentials
|
return credentials
|
||||||
|
|
||||||
|
|
||||||
def auth(request):
|
def auth(request):
|
||||||
context = csrf(request)
|
context = csrf(request)
|
||||||
return render(request, "FIDO2/Auth.html", context)
|
return render(request, "FIDO2/Auth.html", context)
|
||||||
|
|
||||||
|
|
||||||
def authenticate_begin(request):
|
def authenticate_begin(request):
|
||||||
server = getServer()
|
server = getServer()
|
||||||
credentials = getUserCredentials(request.session.get("base_username", request.user.username))
|
credentials = getUserCredentials(request.session.get("base_username", request.user.username))
|
||||||
@@ -85,6 +95,7 @@ def authenticate_begin(request):
|
|||||||
request.session['fido_state'] = state
|
request.session['fido_state'] = state
|
||||||
return HttpResponse(cbor.encode(auth_data), content_type = "application/octet-stream")
|
return HttpResponse(cbor.encode(auth_data), content_type = "application/octet-stream")
|
||||||
|
|
||||||
|
|
||||||
@csrf_exempt
|
@csrf_exempt
|
||||||
def authenticate_complete(request):
|
def authenticate_complete(request):
|
||||||
try:
|
try:
|
||||||
@@ -107,7 +118,8 @@ def authenticate_complete(request):
|
|||||||
signature
|
signature
|
||||||
)
|
)
|
||||||
except ValueError:
|
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")
|
content_type = "application/json")
|
||||||
except Exception as excep:
|
except Exception as excep:
|
||||||
try:
|
try:
|
||||||
@@ -133,8 +145,8 @@ def authenticate_complete(request):
|
|||||||
k.save()
|
k.save()
|
||||||
mfa = {"verified": True, "method": "FIDO2", 'id': k.id}
|
mfa = {"verified": True, "method": "FIDO2", 'id': k.id}
|
||||||
if getattr(settings, "MFA_RECHECK", False):
|
if getattr(settings, "MFA_RECHECK", False):
|
||||||
mfa["next_check"] = int((datetime.datetime.now()+ datetime.timedelta(
|
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
|
request.session["mfa"] = mfa
|
||||||
try:
|
try:
|
||||||
authenticated = request.user.is_authenticated
|
authenticated = request.user.is_authenticated
|
||||||
@@ -143,8 +155,10 @@ def authenticate_complete(request):
|
|||||||
if not authenticated:
|
if not authenticated:
|
||||||
res = login(request)
|
res = login(request)
|
||||||
if not "location" in res: return reset_cookie(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"}),
|
return HttpResponse(simplejson.dumps({'status': "OK"}),
|
||||||
content_type = "application/json")
|
content_type = "application/json")
|
||||||
except Exception as exp:
|
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()
|
key.save()
|
||||||
mfa = {"verified": True, "method": "U2F","id":key.id}
|
mfa = {"verified": True, "method": "U2F","id":key.id}
|
||||||
if getattr(settings, "MFA_RECHECK", False):
|
if getattr(settings, "MFA_RECHECK", False):
|
||||||
mfa["next_check"] = int((datetime.datetime.now()
|
mfa["next_check"] = datetime.datetime.timestamp((datetime.datetime.now()
|
||||||
+ datetime.timedelta(
|
+ 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
|
request.session["mfa"] = mfa
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
__version__="2.1.0b1"
|
__version__="2.1.0"
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ from jsonLookup import shasLookup, hasLookup
|
|||||||
JSONField.register_lookup(shasLookup)
|
JSONField.register_lookup(shasLookup)
|
||||||
JSONField.register_lookup(hasLookup)
|
JSONField.register_lookup(hasLookup)
|
||||||
|
|
||||||
|
|
||||||
class User_Keys(models.Model):
|
class User_Keys(models.Model):
|
||||||
username=models.CharField(max_length = 50)
|
username=models.CharField(max_length = 50)
|
||||||
properties=JSONField(null = True)
|
properties=JSONField(null = True)
|
||||||
@@ -20,9 +21,12 @@ class User_Keys(models.Model):
|
|||||||
if self.key_type == "Trusted Device" and self.properties.get("signature","") == "":
|
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)
|
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)
|
super(User_Keys, self).save(force_insert=force_insert, force_update=force_update, using=using, update_fields=update_fields)
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return "%s -- %s"%(self.username,self.key_type)
|
return "%s -- %s"%(self.username,self.key_type)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.__unicode__()
|
return self.__unicode__()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
app_label='mfa'
|
app_label='mfa'
|
||||||
|
|||||||
@@ -40,9 +40,9 @@ def auth(request):
|
|||||||
if res[0]:
|
if res[0]:
|
||||||
mfa = {"verified": True, "method": "TOTP","id":res[1]}
|
mfa = {"verified": True, "method": "TOTP","id":res[1]}
|
||||||
if getattr(settings, "MFA_RECHECK", False):
|
if getattr(settings, "MFA_RECHECK", False):
|
||||||
mfa["next_check"] = int((datetime.datetime.now()
|
mfa["next_check"] = datetime.datetime.timestamp((datetime.datetime.now()
|
||||||
+ datetime.timedelta(
|
+ 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
|
request.session["mfa"] = mfa
|
||||||
return login(request)
|
return login(request)
|
||||||
context["invalid"]=True
|
context["invalid"]=True
|
||||||
|
|||||||
@@ -6,5 +6,5 @@ python-u2flib-server
|
|||||||
ua-parser
|
ua-parser
|
||||||
user-agents
|
user-agents
|
||||||
python-jose
|
python-jose
|
||||||
fido2 == 0.8.1
|
fido2 == 0.9.0
|
||||||
jsonLookup
|
jsonLookup
|
||||||
|
|||||||
7
setup.py
7
setup.py
@@ -4,7 +4,7 @@ from setuptools import find_packages, setup
|
|||||||
|
|
||||||
setup(
|
setup(
|
||||||
name='django-mfa2',
|
name='django-mfa2',
|
||||||
version='2.1.0b1',
|
version='2.1.2b1',
|
||||||
description='Allows user to add 2FA to their accounts',
|
description='Allows user to add 2FA to their accounts',
|
||||||
long_description=open("README.md").read(),
|
long_description=open("README.md").read(),
|
||||||
long_description_content_type="text/markdown",
|
long_description_content_type="text/markdown",
|
||||||
@@ -24,15 +24,14 @@ setup(
|
|||||||
'ua-parser',
|
'ua-parser',
|
||||||
'user-agents',
|
'user-agents',
|
||||||
'python-jose',
|
'python-jose',
|
||||||
# 'fido2 == 0.8.1',
|
'fido2 == 0.9.1',
|
||||||
'jsonLookup'
|
'jsonLookup'
|
||||||
],
|
],
|
||||||
dependency_links =["https://github.com/Yubico/python-fido2/tarball/master"],
|
|
||||||
python_requires=">=3.5",
|
python_requires=">=3.5",
|
||||||
include_package_data=True,
|
include_package_data=True,
|
||||||
zip_safe=False, # because we're including static files
|
zip_safe=False, # because we're including static files
|
||||||
classifiers=[
|
classifiers=[
|
||||||
"Development Status :: 5 - Production/Stable",
|
"Development Status :: 4 - Beta",
|
||||||
"Environment :: Web Environment",
|
"Environment :: Web Environment",
|
||||||
"Framework :: Django",
|
"Framework :: Django",
|
||||||
"Framework :: Django :: 1.11",
|
"Framework :: Django :: 1.11",
|
||||||
|
|||||||
Reference in New Issue
Block a user