Compare commits

...

33 Commits

Author SHA1 Message Date
Mohamed ElKalioby
19563fe2b8 Fixes #37 2021-02-26 13:55:49 +03:00
Mohamed ElKalioby
0e7c0911ca Merge branch 'master' of https://github.com/mkalioby/django-mfa2 2021-02-26 13:26:31 +03:00
Mohamed ElKalioby
ebfdaf7504 Merge branch 'TouchID' 2021-02-26 13:25:56 +03:00
Mohamed El-Kalioby
c78e600b37 Update CHANGELOG.md 2021-01-21 09:17:28 +03:00
Mohamed El-Kalioby
e1e931285f Update requirements.txt
Fix FIDO2 version in the requirements file
2021-01-21 09:13:52 +03:00
Mohamed El-Kalioby
2709e70f88 Removed Touch ID Beta statment 2021-01-20 17:11:08 +03:00
Mohamed El-Kalioby
25fe80d76d Merge branch 'master' of github.com:mkalioby/django-mfa2 2021-01-20 17:10:08 +03:00
Mohamed El-Kalioby
ba76f842bb Fixed README 2021-01-20 17:08:43 +03:00
Mohamed ElKalioby
87e83b3bbe Updated Changed Log 2021-01-20 16:06:34 +03:00
Mohamed ElKalioby
a94ad50b93 Adding Latest mfa 2021-01-20 16:05:08 +03:00
Mohamed El-Kalioby
75cf1e6130 Update README.md 2021-01-18 19:57:54 +03:00
Mohamed El-Kalioby
911be9e106 Update README.md 2021-01-18 19:56:19 +03:00
Mohamed El-Kalioby
a577c80cc3 Final touches 2021-01-18 19:31:23 +03:00
Mohamed El-Kalioby
66cf103bca Fixes 2021-01-18 19:12:12 +03:00
Mohamed El-Kalioby
53f936a2c6 Testing Touch ID 2021-01-18 18:53:11 +03:00
Mohamed El-Kalioby
6f4c3e0486 closes #31 2020-12-09 07:45:20 +03:00
Mohamed El-Kalioby
b66ae97a0d Bumped version to 2.0.5 2020-12-08 17:38:19 +03:00
Mohamed El-Kalioby
2ced125e9f closes #30 2020-12-08 17:29:14 +03:00
Mohamed El-Kalioby
fb2f52c8ea Adding Thanks to swainn 2020-11-14 12:51:26 +03:00
Mohamed El-Kalioby
91d66e6525 Fix Styles 2020-11-14 12:34:02 +03:00
Mohamed El-Kalioby
8fe7531bf4 Added Conda Stats 2020-11-14 12:32:49 +03:00
Mohamed El-Kalioby
b2764880a8 Added Conda forge installation method 2020-11-14 12:24:30 +03:00
Mohamed El-Kalioby
0668e1a34a Added a yml file 2020-11-12 08:40:58 +03:00
Mohamed El-Kalioby
4e063a2268 Update README.md 2020-11-12 08:24:47 +03:00
Mohamed ElKalioby
049b174a20 Merge branch 'master' of https://github.com/mkalioby/django-mfa2 2020-11-10 09:05:01 +03:00
Mohamed ElKalioby
becfc9e238 Upgraded to 2.0.3 2020-11-10 09:04:08 +03:00
Mohamed El-Kalioby
dda82e5e2e Create dependabot.yml 2020-11-03 10:40:29 +03:00
Mohamed El-Kalioby
9455c501d4 Added Downloads Count to README.md 2020-11-03 10:04:28 +03:00
Mohamed ElKalioby
4a772ee626 Updated to v2.0.1 2020-09-10 09:13:38 +03:00
Mohamed El-Kalioby
5b97d5aa6d Merge pull request #24 from Aquaveo/migration-fix
Fix for Issue #23
2020-09-10 09:06:08 +03:00
nswain
f3483868ed Use the ORM to perform migration query. 2020-09-09 17:32:09 -06:00
nswain
f5218bdbdb Simplify migration script. 2020-09-09 13:16:28 -06:00
nswain
d9bc0dd5fd Fix migration script. 2020-09-09 13:09:42 -06:00
18 changed files with 198 additions and 61 deletions

1
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1 @@
tidelift: "pypi/django-mfa2"

11
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,11 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "pip" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "daily"

View File

@@ -1,5 +1,34 @@
# 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
* Fixed issue in __version__
## 2.0.4
* Fixed: Closes #30
## 2.0.3
* Fixed: __version__ to show correct version
## 2.0.2
* Added: A missing migration
thnks to @swainn
## 2.0.1
* Fixed: issue in migration between Postgres and SQLite
thnks to @swainn and @willingham
## 2.0 ## 2.0
* Dropped support to djangp-1.8 and Python 2.7 * Dropped support to djangp-1.8 and Python 2.7
* Added: never-cache decorator * Added: never-cache decorator

View File

@@ -1,7 +1,14 @@
# django-mfa2 # django-mfa2
A Django app that handles MFA, it supports TOTP, U2F, FIDO2 U2F (Web Authn), Email Tokens , and Trusted Devices A Django app that handles MFA, it supports TOTP, U2F, FIDO2 U2F (Web Authn), Email Tokens , and Trusted Devices
### Pip Stats
[![PyPI version](https://badge.fury.io/py/django-mfa2.svg)](https://badge.fury.io/py/django-mfa2) [![PyPI version](https://badge.fury.io/py/django-mfa2.svg)](https://badge.fury.io/py/django-mfa2)
[![Downloads Count](https://static.pepy.tech/personalized-badge/django-mfa2?period=total&units=international_system&left_color=black&right_color=green&left_text=Downloads)](https://pepy.tech/project/django-mfa2)
### Conda Stats
[![Conda Recipe](https://img.shields.io/badge/recipe-django--mfa2-green.svg)](https://anaconda.org/conda-forge/django-mfa2)
[![Conda Downloads](https://img.shields.io/conda/dn/conda-forge/django-mfa2.svg)](https://anaconda.org/conda-forge/django-mfa2)
[![Conda Version](https://img.shields.io/conda/vn/conda-forge/django-mfa2.svg)](https://anaconda.org/conda-forge/django-mfa2)
Web Authencation API (WebAuthn) is state-of-the art techology that is expected to replace passwords. Web Authencation API (WebAuthn) is state-of-the art techology that is expected to replace passwords.
@@ -10,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
@@ -29,11 +36,23 @@ Depends on
* ua-parser * ua-parser
* user-agents * user-agents
* python-jose * python-jose
* fido2==0.7 * fido2==0.9.0
# Installation # Installation
1. `pip install django-mfa2` 1. using pip
`pip install django-mfa2`
2. Using Conda forge
`conda config --add channels conda-forge`
`conda install django-mfa2`
For more info, see the conda-forge repo (https://github.com/conda-forge/django-mfa2-feedstock)
Thanks for [swainn](https://github.com/swainn) for adding package to conda-forge
# Usage
1. in your settings.py add the application to your installed apps 1. in your settings.py add the application to your installed apps
```python ```python
INSTALLED_APPS=( INSTALLED_APPS=(
@@ -158,3 +177,8 @@ function some_func() {
* [d3cline](https://github.com/d3cline) * [d3cline](https://github.com/d3cline)
* [swainn](https://github.com/swainn) * [swainn](https://github.com/swainn)
* [unramk](https://github.com/unramk) * [unramk](https://github.com/unramk)
* [willingham](https://github.com/willingham)
# Security contact information
To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure.

View File

@@ -77,8 +77,10 @@ WSGI_APPLICATION = 'example.wsgi.application'
DATABASES = { DATABASES = {
'default': { 'default': {
'ENGINE': 'django.db.backends.sqlite3', 'ENGINE': 'django.db.backends.mysql',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 'NAME': 'mfa',
'USER': 'root',
'PASSWORD': 'password',
} }
} }

View File

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

View File

@@ -4,28 +4,31 @@ from fido2.ctap2 import AttestationObject, AuthenticatorData
from django.template.context_processors import csrf from django.template.context_processors import csrf
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from django.shortcuts import render from django.shortcuts import render
#from django.template.context import RequestContext # from django.template.context import RequestContext
import simplejson import simplejson
from fido2 import cbor from fido2 import cbor
from django.http import HttpResponse from django.http import HttpResponse
from django.conf import settings from django.conf import settings
from .models import * from .models import *
from fido2.utils import websafe_decode,websafe_encode from fido2.utils import websafe_decode, websafe_encode
from fido2.ctap2 import AttestedCredentialData from fido2.ctap2 import AttestedCredentialData
from .views import login,reset_cookie 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"
request.session["mfa_recheck"]=True request.session["mfa_recheck"] = True
return render(request,"FIDO2/recheck.html", context) return render(request, "FIDO2/recheck.html", context)
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({
@@ -35,7 +38,9 @@ def begin_registeration(request):
}, getUserCredentials(request.user.username)) }, getUserCredentials(request.user.username))
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:
@@ -50,10 +55,10 @@ def complete_reg(request):
att_obj att_obj
) )
encoded = websafe_encode(auth_data.credential_data) encoded = websafe_encode(auth_data.credential_data)
uk=User_Keys() uk = User_Keys()
uk.username = request.user.username uk.username = request.user.username
uk.properties = {"device":encoded,"type":att_obj.fmt,} uk.properties = {"device": encoded, "type": att_obj.fmt, }
uk.owned_by_enterprise=getattr(settings,"MFA_OWNED_BY_ENTERPRISE",False) uk.owned_by_enterprise = getattr(settings, "MFA_OWNED_BY_ENTERPRISE", False)
uk.key_type = "FIDO2" uk.key_type = "FIDO2"
uk.save() uk.save()
return HttpResponse(simplejson.dumps({'status': 'OK'})) return HttpResponse(simplejson.dumps({'status': 'OK'}))
@@ -63,10 +68,13 @@ def complete_reg(request):
client.captureException() client.captureException()
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 = []
@@ -74,24 +82,27 @@ def getUserCredentials(username):
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))
auth_data, state = server.authenticate_begin(credentials) auth_data, state = server.authenticate_begin(credentials)
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:
credentials = [] credentials = []
username=request.session.get("base_username",request.user.username) username = request.session.get("base_username", request.user.username)
server=getServer() server = getServer()
credentials=getUserCredentials(username) credentials = getUserCredentials(username)
data = cbor.decode(request.body) data = cbor.decode(request.body)
credential_id = data['credentialId'] credential_id = data['credentialId']
client_data = ClientData(data['clientDataJSON']) client_data = ClientData(data['clientDataJSON'])
@@ -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:
@@ -119,32 +131,34 @@ def authenticate_complete(request):
"message": excep.message}), "message": excep.message}),
content_type = "application/json") content_type = "application/json")
if request.session.get("mfa_recheck",False): if request.session.get("mfa_recheck", False):
import time import time
request.session["mfa"]["rechecked_at"]=time.time() request.session["mfa"]["rechecked_at"] = time.time()
return HttpResponse(simplejson.dumps({'status': "OK"}), return HttpResponse(simplejson.dumps({'status': "OK"}),
content_type="application/json") content_type = "application/json")
else: else:
import random import random
keys = User_Keys.objects.filter(username=username, key_type="FIDO2", enabled=1) keys = User_Keys.objects.filter(username = username, key_type = "FIDO2", enabled = 1)
for k in keys: for k in keys:
if AttestedCredentialData(websafe_decode(k.properties["device"])).credential_id == cred.credential_id: if AttestedCredentialData(websafe_decode(k.properties["device"])).credential_id == cred.credential_id:
k.last_used = timezone.now() k.last_used = timezone.now()
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
except: except:
authenticated = request.user.is_authenticated() authenticated = request.user.is_authenticated()
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")

View File

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

View File

@@ -1 +1 @@
__version__="1.6.0" __version__="2.1.0"

View File

@@ -4,6 +4,12 @@ from __future__ import unicode_literals
from django.db import models, migrations from django.db import models, migrations
from django.conf import settings from django.conf import settings
def update_owned_by_enterprise(apps, schema_editor):
user_keys = apps.get_model('mfa', 'user_keys')
user_keys.objects.filter(key_type='FIDO2').update(owned_by_enterprise=getattr(settings,"MFA_OWNED_BY_ENTERPRISE",False))
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
@@ -16,5 +22,5 @@ class Migration(migrations.Migration):
name='owned_by_enterprise', name='owned_by_enterprise',
field=models.NullBooleanField(default=None), field=models.NullBooleanField(default=None),
), ),
migrations.RunSQL("update mfa_user_keys set owned_by_enterprise = %s where key_type='FIDO2'"%(1 if getattr(settings,"MFA_OWNED_BY_ENTERPRISE",False) else 0 )) migrations.RunPython(update_owned_by_enterprise)
] ]

View File

@@ -0,0 +1,18 @@
# Generated by Django 2.0 on 2020-11-10 05:57
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('mfa', '0009_user_keys_owned_by_enterprise'),
]
operations = [
migrations.AlterField(
model_name='user_keys',
name='key_type',
field=models.CharField(default='TOTP', max_length=25),
),
]

View File

@@ -2,8 +2,10 @@ from django.db import models
from jsonfield import JSONField from jsonfield import JSONField
from jose import jwt from jose import jwt
from django.conf import settings from django.conf import settings
from jsonLookup import shasLookup from jsonLookup import shasLookup, hasLookup
JSONField.register_lookup(shasLookup) JSONField.register_lookup(shasLookup)
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)
@@ -19,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'

9
mfa/static/mfa/js/ua-parser.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -2,6 +2,7 @@
{% load static %} {% load static %}
{% block head %} {% block head %}
<script type="application/javascript" src="{% static 'mfa/js/cbor.js'%}"></script> <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="application/javascript"> <script type="application/javascript">
function begin_reg(){ function begin_reg(){
fetch('{% url 'fido2_begin_reg' %}',{}).then(function(response) { fetch('{% url 'fido2_begin_reg' %}',{}).then(function(response) {
@@ -40,7 +41,17 @@
$("#res").html("<div class='alert alert-danger'>Registeration Failed as " +reason +", <a href='javascript:void(0)' onclick='begin_reg()'> try again </a> or <a href='{% url 'mfa_home' %}'> Go to Security Home</a></div>") $("#res").html("<div class='alert alert-danger'>Registeration Failed as " +reason +", <a href='javascript:void(0)' onclick='begin_reg()'> try again </a> or <a href='{% url 'mfa_home' %}'> Go to Security Home</a></div>")
}) })
} }
$(document).ready(setTimeout(begin_reg,500)) $(document).ready(function (){
ua=new UAParser().getResult()
if (ua.browser.name == "Safari")
{
$("#res").html("<button class='btn btn-success' onclick='begin_reg()'>Start...</button>")
}
else
{
setTimeout(begin_reg, 500)
}
})
</script> </script>
{% endblock %} {% endblock %}
@@ -55,8 +66,8 @@
<div class="panel-body"> <div class="panel-body">
<div class="row alert alert-pr" id="res"> <div class="row alert alert-pr" id="res" align="center">
<p style="color: green">Your broswer should ask you to confirm you indentity.</p> <p style="color: green">Your browser should ask you to confirm you identity.</p>
</div> </div>
</div> </div>

View File

@@ -1,5 +1,6 @@
{% load static %} {% load static %}
<script type="application/javascript" src="{% static 'mfa/js/cbor.js' %}"></script> <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="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">
@@ -17,7 +18,9 @@
<br/> <br/>
{% endif %} {% endif %}
<div id="res">
<p style="color: green">please press the button on your security key to prove it is you.</p> <p style="color: green">please press the button on your security key to prove it is you.</p>
</div>
<div id="msgdiv"></div> <div id="msgdiv"></div>
{% if mode == "auth" %} {% if mode == "auth" %}
<form id="u2f_login" action="{% url 'fido2_complete_auth' %}" method="post" enctype="multipart/form-data"> <form id="u2f_login" action="{% url 'fido2_complete_auth' %}" method="post" enctype="multipart/form-data">
@@ -101,6 +104,10 @@
$("#main_paragraph").addClass("alert alert-danger") $("#main_paragraph").addClass("alert alert-danger")
$("#main_paragraph").html("FIDO2 must work under secure context") $("#main_paragraph").html("FIDO2 must work under secure context")
} else { } else {
ua=new UAParser().getResult()
if (ua.browser.name == "Safari")
$("#res").html("<button class='btn btn-success' onclick='authen()'>Authenticate...</button>")
else
authen() authen()
} }
}); });

View File

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

View File

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

View File

@@ -4,7 +4,7 @@ from setuptools import find_packages, setup
setup( setup(
name='django-mfa2', name='django-mfa2',
version='2.0.0', 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",
@@ -16,7 +16,7 @@ setup(
license='MIT', license='MIT',
packages=find_packages(), packages=find_packages(),
install_requires=[ install_requires=[
'django >= 1.7', 'django >= 2.0',
'jsonfield', 'jsonfield',
'simplejson', 'simplejson',
'pyotp', 'pyotp',
@@ -24,14 +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'
], ],
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",