Compare commits
1 Commits
TouchID
...
TouchID_4_
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b39fa1a99b |
1
.github/FUNDING.yml
vendored
1
.github/FUNDING.yml
vendored
@@ -1 +0,0 @@
|
||||
tidelift: "pypi/django-mfa2"
|
||||
11
.github/dependabot.yml
vendored
11
.github/dependabot.yml
vendored
@@ -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"
|
||||
30
CHANGELOG.md
30
CHANGELOG.md
@@ -1,35 +1,5 @@
|
||||
# Change Log
|
||||
|
||||
## 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
|
||||
|
||||
42
README.md
42
README.md
@@ -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
|
||||
[](https://badge.fury.io/py/django-mfa2)
|
||||
[](https://pepy.tech/project/django-mfa2)
|
||||
|
||||
### Conda Stats
|
||||
[](https://anaconda.org/conda-forge/django-mfa2)
|
||||
[](https://anaconda.org/conda-forge/django-mfa2)
|
||||
[](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.
|
||||
|
||||
@@ -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'),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -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":
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
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
|
||||
@@ -24,7 +24,7 @@ def recheck(request):
|
||||
|
||||
|
||||
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()
|
||||
|
||||
@@ -1 +1 @@
|
||||
__version__="2.1.0"
|
||||
__version__="1.6.0"
|
||||
|
||||
@@ -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 ))
|
||||
]
|
||||
|
||||
@@ -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),
|
||||
),
|
||||
]
|
||||
@@ -2,9 +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)
|
||||
|
||||
@@ -2,15 +2,18 @@
|
||||
{% block head %}
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<br/>
|
||||
<br/>
|
||||
<div class="container">
|
||||
<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">
|
||||
@@ -25,9 +28,10 @@
|
||||
<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>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12">
|
||||
<div class="form-group">
|
||||
@@ -36,16 +40,16 @@
|
||||
<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>
|
||||
</div>
|
||||
</fieldset>
|
||||
</FORM>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
<table class="table table-striped">
|
||||
|
||||
@@ -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,16 +61,16 @@
|
||||
</script>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<br/>
|
||||
<br/>
|
||||
<div class="container">
|
||||
<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 can’t use a barcode,
|
||||
<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 can’t use a barcode,
|
||||
<a href="javascript:void(0)" onclick="showKey()">enter this text</a> instead. </p>
|
||||
</div>
|
||||
|
||||
@@ -93,7 +93,7 @@
|
||||
<input style="display: inline;width: 95%" maxlength="6" size="6" class="form-control" id="answer" placeholder="e.g 785481"/>
|
||||
|
||||
</div>
|
||||
<div class="row" style="padding-top: 10px;">
|
||||
<div class="row">
|
||||
<div class="col-md-6" style="padding-left: 0px">
|
||||
<button class="btn btn-success" onclick="verify()">Enable</button>
|
||||
</div>
|
||||
@@ -104,6 +104,6 @@
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% include "modal.html" %}
|
||||
</div>
|
||||
{% include "modal.html" %}
|
||||
{% endblock %}
|
||||
@@ -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":
|
||||
@@ -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",{})
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
django >= 1.7
|
||||
jsonfield
|
||||
simplejson
|
||||
pyotp
|
||||
python-u2flib-server
|
||||
ua-parser
|
||||
user-agents
|
||||
python-jose
|
||||
fido2 == 0.8.1
|
||||
jsonLookup
|
||||
jsonfield
|
||||
simplejson
|
||||
pyotp
|
||||
python-u2flib-server
|
||||
ua-parser
|
||||
user-agents
|
||||
python-jose
|
||||
fido2 == 0.7
|
||||
jsonLookup
|
||||
|
||||
|
||||
16
setup.py
16
setup.py
@@ -4,7 +4,7 @@ from setuptools import find_packages, setup
|
||||
|
||||
setup(
|
||||
name='django-mfa2',
|
||||
version='2.1.0',
|
||||
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',
|
||||
@@ -27,25 +27,29 @@ setup(
|
||||
'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
|
||||
classifiers=[
|
||||
"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",
|
||||
]
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user