Compare commits

..

1 Commits

Author SHA1 Message Date
Mohamed El-Kalioby
b39fa1a99b Adding Touch ID to Django 1.8 2021-01-30 17:10:33 +03:00
22 changed files with 160 additions and 281 deletions

1
.github/FUNDING.yml vendored
View File

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

View File

@@ -1,11 +0,0 @@
# 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,43 +1,5 @@
# 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
* Dropped support to djangp-1.8 and Python 2.7
* Added: never-cache decorator
* Fixes to Make Email Method More Robust
* Addresses several structure and style issues with TOTP and Email dialogs
* Updated to fido2 0.8.1
Thanks to @swainn
## v1.9.1
* Fixed: is_authenticated #13
* Fixed: is_anonymous #6

View File

@@ -1,14 +1,7 @@
# django-mfa2
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)
[![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.
@@ -17,17 +10,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/Face ID** (Chrome 70+ on Mac OS X, Safari on macOS Big Sur, Safari on iOS 14.0+ ),
* **Apple's Touch ID** (Chrome 70+ on Mac OS X ),
* **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/Face ID on Macbooks (Chrome, Safari), Touch/Face ID on iPhone and iPad and Fingerprint/Face/Iris/PIN on Android 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 ID on Macbooks (Chrome) and Fingerprint/Face/Iris/PIN on Andriod Phones.
Trusted device is a mode for the user to add a device that doesn't support security keys like Android without fingerprints or NFC.
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.
**Note**: `U2F and FIDO2 can only be served under secure context (https)`
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.
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.
Depends on
@@ -36,23 +29,11 @@ Depends on
* ua-parser
* user-agents
* python-jose
* fido2==0.9.0
* fido2==0.7
# Installation
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. `pip install django-mfa2`
1. in your settings.py add the application to your installed apps
```python
INSTALLED_APPS=(
@@ -72,7 +53,7 @@ Depends on
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_OWNED_BY_ENTERPRISE = FALSE # Who owns security keys
MFA_OWNED_BY_ENTERPRISE = FALSE # Who ownes security keys
TOKEN_ISSUER_NAME="PROJECT_NAME" #TOTP Issuer name
@@ -175,10 +156,3 @@ function some_func() {
# Contributors
* [mahmoodnasr](https://github.com/mahmoodnasr)
* [d3cline](https://github.com/d3cline)
* [swainn](https://github.com/swainn)
* [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,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': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}

View File

@@ -2,10 +2,7 @@ from django.conf import settings
from django.core.mail import EmailMessage
def send(to,subject,body):
from_email_address = settings.EMAIL_HOST_USER
if '@' not in from_email_address:
from_email_address = settings.DEFAULT_FROM_EMAIL
From = "%s <%s>" % (settings.EMAIL_FROM, from_email_address)
From = "%s <%s>" % (settings.EMAIL_FROM, settings.EMAIL_HOST_USER)
email = EmailMessage(subject,body,From,to)
email.content_subtype = "html"
return email.send(False)

View File

@@ -1,5 +1,4 @@
from django.shortcuts import render
from django.views.decorators.cache import never_cache
from django.template.context_processors import csrf
import datetime,random
from random import randint
@@ -14,9 +13,8 @@ def sendEmail(request,username,secret):
kwargs = {key: username}
user = User.objects.get(**kwargs)
res=render(request,"mfa_email_token_template.html",{"request":request,"user":user,'otp':secret})
return send([user.email],"OTP", res.content.decode())
return send([user.email],"OTP", str(res.content))
@never_cache
def start(request):
context = csrf(request)
if request.method == "POST":
@@ -38,7 +36,6 @@ def start(request):
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):
context=csrf(request)
if request.method=="POST":
@@ -46,8 +43,8 @@ def auth(request):
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"] = datetime.datetime.timestamp(datetime.datetime.now() + datetime.timedelta(
seconds = random.randint(settings.MFA_RECHECK_MIN, settings.MFA_RECHECK_MAX)))
mfa["next_check"] = int((datetime.datetime.now() + datetime.timedelta(
seconds = random.randint(settings.MFA_RECHECK_MIN, settings.MFA_RECHECK_MAX))).strftime("%s"))
request.session["mfa"] = mfa
from django.utils import timezone

View File

@@ -1,34 +1,31 @@
from fido2.client import ClientData
from fido2.server import Fido2Server, PublicKeyCredentialRpEntity
from fido2.server import Fido2Server, RelyingParty
from fido2.ctap2 import AttestationObject, AuthenticatorData
from django.template.context_processors import csrf
from django.views.decorators.csrf import csrf_exempt
from django.shortcuts import render
# from django.template.context import RequestContext
#from django.template.context import RequestContext
import simplejson
from fido2 import cbor
from django.http import HttpResponse
from django.conf import settings
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 .views import login, reset_cookie
from .views import login,reset_cookie
import datetime
from django.utils import timezone
def recheck(request):
context = csrf(request)
context["mode"] = "recheck"
request.session["mfa_recheck"] = True
return render(request, "FIDO2/recheck.html", context)
context["mode"]="recheck"
request.session["mfa_recheck"]=True
return render(request,"FIDO2/recheck.html", context)
def getServer():
rp = PublicKeyCredentialRpEntity(settings.FIDO_SERVER_ID, settings.FIDO_SERVER_NAME)
rp = RelyingParty(settings.FIDO_SERVER_ID, settings.FIDO_SERVER_NAME)
return Fido2Server(rp)
def begin_registeration(request):
server = getServer()
registration_data, state = server.register_begin({
@@ -38,9 +35,7 @@ def begin_registeration(request):
}, getUserCredentials(request.user.username))
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
def complete_reg(request):
try:
@@ -55,10 +50,10 @@ def complete_reg(request):
att_obj
)
encoded = websafe_encode(auth_data.credential_data)
uk = User_Keys()
uk=User_Keys()
uk.username = request.user.username
uk.properties = {"device": encoded, "type": att_obj.fmt, }
uk.owned_by_enterprise = getattr(settings, "MFA_OWNED_BY_ENTERPRISE", False)
uk.properties = {"device":encoded,"type":att_obj.fmt,}
uk.owned_by_enterprise=getattr(settings,"MFA_OWNED_BY_ENTERPRISE",False)
uk.key_type = "FIDO2"
uk.save()
return HttpResponse(simplejson.dumps({'status': 'OK'}))
@@ -68,13 +63,10 @@ def complete_reg(request):
client.captureException()
except:
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):
context = csrf(request)
return render(request, "FIDO2/Add.html", context)
return render(request,"FIDO2/Add.html", context)
def getUserCredentials(username):
credentials = []
@@ -82,27 +74,24 @@ def getUserCredentials(username):
credentials.append(AttestedCredentialData(websafe_decode(uk.properties["device"])))
return credentials
def auth(request):
context = csrf(request)
return render(request, "FIDO2/Auth.html", context)
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))
credentials=getUserCredentials(request.session.get("base_username",request.user.username))
auth_data, state = server.authenticate_begin(credentials)
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
def authenticate_complete(request):
try:
credentials = []
username = request.session.get("base_username", request.user.username)
server = getServer()
credentials = getUserCredentials(username)
username=request.session.get("base_username",request.user.username)
server=getServer()
credentials=getUserCredentials(username)
data = cbor.decode(request.body)
credential_id = data['credentialId']
client_data = ClientData(data['clientDataJSON'])
@@ -118,8 +107,7 @@ 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:
@@ -131,34 +119,32 @@ def authenticate_complete(request):
"message": excep.message}),
content_type = "application/json")
if request.session.get("mfa_recheck", False):
if request.session.get("mfa_recheck",False):
import time
request.session["mfa"]["rechecked_at"] = time.time()
request.session["mfa"]["rechecked_at"]=time.time()
return HttpResponse(simplejson.dumps({'status': "OK"}),
content_type = "application/json")
content_type="application/json")
else:
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:
if AttestedCredentialData(websafe_decode(k.properties["device"])).credential_id == cred.credential_id:
k.last_used = timezone.now()
k.save()
mfa = {"verified": True, "method": "FIDO2", 'id': k.id}
mfa = {"verified": True, "method": "FIDO2",'id':k.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))))
mfa["next_check"] = int((datetime.datetime.now()+ datetime.timedelta(
seconds=random.randint(settings.MFA_RECHECK_MIN, settings.MFA_RECHECK_MAX))).strftime("%s"))
request.session["mfa"] = mfa
try:
authenticated = request.user.is_authenticated
authenticated=request.user.is_authenticated
except:
authenticated = request.user.is_authenticated()
if not authenticated:
res = login(request)
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")

View File

@@ -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"] = datetime.datetime.timestamp((datetime.datetime.now()
mfa["next_check"] = int((datetime.datetime.now()
+ datetime.timedelta(
seconds=random.randint(settings.MFA_RECHECK_MIN, settings.MFA_RECHECK_MAX))))
seconds=random.randint(settings.MFA_RECHECK_MIN, settings.MFA_RECHECK_MAX))).strftime("%s"))
request.session["mfa"] = mfa
return True

View File

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

View File

@@ -4,12 +4,6 @@ from __future__ import unicode_literals
from django.db import models, migrations
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):
dependencies = [
@@ -22,5 +16,5 @@ class Migration(migrations.Migration):
name='owned_by_enterprise',
field=models.NullBooleanField(default=None),
),
migrations.RunPython(update_owned_by_enterprise)
migrations.RunSQL("update mfa_user_keys set owned_by_enterprise = %s where key_type='FIDO2'"%(True if getattr(settings,"MFA_OWNED_BY_ENTERPRISE",False) else False ))
]

View File

@@ -1,18 +0,0 @@
# 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,10 +2,8 @@ from django.db import models
from jsonfield import JSONField
from jose import jwt
from django.conf import settings
from jsonLookup import shasLookup, hasLookup
from jsonLookup import shasLookup
JSONField.register_lookup(shasLookup)
JSONField.register_lookup(hasLookup)
class User_Keys(models.Model):
username=models.CharField(max_length = 50)
@@ -21,12 +19,9 @@ 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'

View File

@@ -2,50 +2,54 @@
{% block head %}
{% endblock %}
{% block content %}
<br/>
<br/>
<div class="container">
<div class="panel panel-default">
<div class="panel-heading">
<strong> Activate Token by email</strong>
</div>
<div class="panel-body">
<br/>
<br/>
<div class="panel panel-default">
<div class="panel-heading">
<strong> Activate Token by email</strong>
</div>
<div class="panel-body">
<FORM METHOD="POST" ACTION="{% url 'start_email' %}" Id="formLogin" onSubmit="" name="FrontPage_Form1">
{% csrf_token %}
{% if invalid %}
<div class="alert alert-danger">
Sorry, The provided token is not valid.
</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 code sent to your email.</p>
</div>
{% csrf_token %}
{% if invalid %}
<div class="alert alert-danger">
Sorry, The provided token is not valid.
</div>
<div class="row">
<div class="col-sm-12 col-md-12">
<div class="form-group">
<div class="input-group">
<span class="input-group-addon">
<i class="glyphicon glyphicon-lock"></i>
</span>
<input class="form-control" size="6" MaxLength="6" value="" placeholder="e.g 55552" name="otp" type="text" id="otp" autofocus>
</div>
</div>
<div class="form-group">
<input type="submit" class="btn btn-lg btn-success btn-block" value="Verify">
</div>
</div>
{% endif %}
{% if quota %}
<div class="alert alert-warning">
{{ quota }}
</div>
</fieldset>
{% endif %}
<fieldset>
<div class="row">
<div class="col-sm-12 col-md-12">
<p>Enter the 6-digits sent to your email.</p>
</div>
</div>
<div class="row">
<div class="col-sm-12 col-md-12">
<div class="form-group">
<div class="input-group">
<span class="input-group-addon">
<i class="glyphicon glyphicon-lock"></i>
</span>
<input class="form-control" size="6" MaxLength="6" value="" placeholder="e.g 55552" name="otp" type="text" id="otp" autofocus>
</div>
</div>
<div class="form-group">
<input type="submit" class="btn btn-lg btn-success btn-block" value="Verify">
</div>
</div>
</fieldset>
</FORM>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -39,7 +39,7 @@
<fieldset>
<div class="row">
<div class="col-sm-12 col-md-12">
<p>Enter the code sent to your email.</p>
<p>Enter the 6-digits sent to your email.</p>
</div>
</div>

View File

@@ -42,10 +42,10 @@
})
}
$(document).ready(function (){
ua=new UAParser().getResult()
if (ua.browser.name == "Safari")
ua=new UAParser()
if (ua.getBrowser().name == "Safari")
{
$("#res").html("<button class='btn btn-success' onclick='begin_reg()'>Start...</button>")
$("#res").html("<button class='btn btn-primary' onclick='begin_reg()'>Start...</button>")
}
else
{
@@ -66,7 +66,7 @@
<div class="panel-body">
<div class="row alert alert-pr" id="res" align="center">
<div class="row alert alert-pr" id="res">
<p style="color: green">Your browser should ask you to confirm you identity.</p>
</div>

View File

@@ -1,6 +1,6 @@
{% 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="application/javascript" src="{% static 'mfa/js/us-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">
@@ -104,8 +104,8 @@
$("#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")
ua=UAParser()
if (ua.getBrowser().name == "Safari")
$("#res").html("<button class='btn btn-success' onclick='authen()'>Authenticate...</button>")
else
authen()

View File

@@ -66,7 +66,7 @@
{% endif %}
</ul>
</div>
</div>
<br/>
<br/>
<table class="table table-striped">

View File

@@ -2,7 +2,7 @@
{% extends "base.html" %}
{% load static %}
{% block head %}
<style>
<style>
#two-factor-steps {
border: 1px solid #ccc;
border-radius: 3px;
@@ -12,8 +12,8 @@
margin: 0px;
}
</style>
<script src="{% static 'mfa/js/qrious.min.js' %}" type="text/javascript"></script>
<script type="text/javascript">
<script src="{% static 'mfa/js/qrious.min.js' %}" type="text/javascript"></script>
<script type="text/javascript">
var key="";
$(document).ready(function addToken() {
$.ajax({
@@ -61,49 +61,49 @@
</script>
{% endblock %}
{% block content %}
<br/>
<br/>
<div class="container">
<div class="col-md-6 col-md-offset-3" id="two-factor-steps">
<div class="row" align="center">
<h4>Adding Authenticator</h4>
</div>
<div class="row">
<br/>
<br/>
<div class="container">
<div class="col-md-6 col-md-offset-3" id="two-factor-steps">
<div class="row" align="center">
<h4>Adding Authenticator</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 cant use a barcode,
<a href="javascript:void(0)" onclick="showKey()">enter this text</a> instead. </p>
<p>Scan the image below with the two-factor authentication app on your <a href="javascript:void(0)" onclick="showTOTP()">phone/PC</a> phone/PC. If you cant use a barcode,
<a href="javascript:void(0)" onclick="showKey()">enter this text</a> instead. </p>
</div>
<div class="row">
<div align="center" style="display: none" id="second_step">
<div align="center" style="display: none" id="second_step">
<img id="qr"/>
<img id="qr"/>
</div>
<div class="row">
<div class="row">
<p><b>Enter the six-digit code from the application</b></p>
<p style="color: #333333;font-size: 10px">After scanning the barcode image, the app will display a six-digit code that you can enter below. </p>
</div>
</div>
<div class="row">
<input style="display: inline;width: 95%" maxlength="6" size="6" class="form-control" id="answer" placeholder="e.g 785481"/>
<input style="display: inline;width: 95%" maxlength="6" size="6" class="form-control" id="answer" placeholder="e.g 785481"/>
</div>
<div class="row">
<div class="col-md-6" style="padding-left: 0px">
<button class="btn btn-success" onclick="verify()">Enable</button>
</div>
<div class="row" style="padding-top: 10px;">
<div class="col-md-6" 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' %}"><button class="btn btn-default">Cancel</button></a>
</div>
<div class="col-md-6" align="right" style="padding-right: 30px">
<a href="{% url 'mfa_home' %}"><button class="btn btn-default">Cancel</button></a>
</div>
</div>
</div>
</div>
</div>
{% include "modal.html" %}
</div>
{% include "modal.html" %}
{% endblock %}

View File

@@ -1,5 +1,4 @@
from django.shortcuts import render
from django.views.decorators.cache import never_cache
from django.http import HttpResponse
from .models import *
from django.template.context_processors import csrf
@@ -32,7 +31,6 @@ def recheck(request):
return HttpResponse(simplejson.dumps({"recheck": False}), content_type="application/json")
return render(request,"TOTP/recheck.html", context)
@never_cache
def auth(request):
context=csrf(request)
if request.method=="POST":
@@ -40,9 +38,9 @@ def auth(request):
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()
mfa["next_check"] = int((datetime.datetime.now()
+ datetime.timedelta(
seconds=random.randint(settings.MFA_RECHECK_MIN, settings.MFA_RECHECK_MAX))))
seconds=random.randint(settings.MFA_RECHECK_MIN, settings.MFA_RECHECK_MAX))).strftime("%s"))
request.session["mfa"] = mfa
return login(request)
context["invalid"]=True
@@ -70,6 +68,5 @@ def verify(request):
return HttpResponse("Success")
else: return HttpResponse("Error")
@never_cache
def start(request):
return render(request,"TOTP/Add.html",{})

View File

@@ -1,10 +1,11 @@
django >= 1.7
jsonfield
simplejson
pyotp
python-u2flib-server
ua-parser
user-agents
python-jose
fido2 == 0.9.0
jsonLookup
jsonfield
simplejson
pyotp
python-u2flib-server
ua-parser
user-agents
python-jose
fido2 == 0.7
jsonLookup

View File

@@ -4,7 +4,7 @@ from setuptools import find_packages, setup
setup(
name='django-mfa2',
version='2.1.2b1',
version='1.10.0,
description='Allows user to add 2FA to their accounts',
long_description=open("README.md").read(),
long_description_content_type="text/markdown",
@@ -16,7 +16,7 @@ setup(
license='MIT',
packages=find_packages(),
install_requires=[
'django >= 2.0',
'django >= 1.7',
'jsonfield',
'simplejson',
'pyotp',
@@ -24,28 +24,32 @@ setup(
'ua-parser',
'user-agents',
'python-jose',
'fido2 == 0.9.1',
'fido2 == 0.9',
'jsonLookup'
],
python_requires=">=3.5",
python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*",
include_package_data=True,
zip_safe=False, # because we're including static files
zip_safe=False, # because we're including static files
classifiers=[
"Development Status :: 4 - Beta",
"Development Status :: 5 - Production/Stable",
"Environment :: Web Environment",
"Framework :: Django",
"Framework :: Django :: 1.7",
"Framework :: Django :: 1.8",
"Framework :: Django :: 1.9",
"Framework :: Django :: 1.10",
"Framework :: Django :: 1.11",
"Framework :: Django :: 2.0",
"Framework :: Django :: 2.1",
"Framework :: Django :: 2.2",
"Intended Audience :: Developers",
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 2",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.4",
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Topic :: Software Development :: Libraries :: Python Modules",
]
)