Skip to content

Commit

Permalink
Merge 7cd689c into 1cbb13d
Browse files Browse the repository at this point in the history
  • Loading branch information
gregkster committed Sep 20, 2018
2 parents 1cbb13d + 7cd689c commit 0f71161
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 7 deletions.
12 changes: 10 additions & 2 deletions README.rst
Expand Up @@ -26,8 +26,7 @@ Django-mfa(Multi-factor Authentication) is a simple django package to add extra

We welcome your feedback on this package. If you run into problems, please raise an issue or contribute to the project by forking the repository and sending some pull requests.

This Package is compatible with Django versions >=1.6.0,<=1.11. Documentation is available at readthedocs(http://django-mfa.readthedocs.io/en/latest/)

This Package is compatible with Django versions >=1.10 (including at least Django 2.0.7) Documentation is available at readthedocs(http://django-mfa.readthedocs.io/en/latest/)

Quick start
-----------
Expand Down Expand Up @@ -64,6 +63,15 @@ Settings
'....................................',
]

3. Optional issuer name. This name will be shown in the Authenticator App along with the username

MFA_ISSUER_NAME = "Cool Django App"

4. Optionally enable remember-my-browser. If enabled, the browser will be trusted for specified number of days after the user enters the code once.

MFA_REMEMBER_MY_BROWSER = True
MFA_REMEMBER_DAYS = 90
MFA_COOKIE_SALT = "uoiqwueroqwer" # random

Urls
~~~~
Expand Down
3 changes: 2 additions & 1 deletion django_mfa/middleware.py
Expand Up @@ -2,6 +2,7 @@
from django.shortcuts import resolve_url
from django.contrib.auth import REDIRECT_FIELD_NAME as redirect_field_name
from .models import is_mfa_enabled
from .views import verify_rmb_cookie

try:
from django.utils.deprecation import MiddlewareMixin
Expand All @@ -13,7 +14,7 @@
class MfaMiddleware(MiddlewareMixin):

def process_request(self, request):
if request.user.is_authenticated and is_mfa_enabled(request.user):
if request.user.is_authenticated and not verify_rmb_cookie(request) and is_mfa_enabled(request.user):
if not request.session.get('verfied_otp'):
current_path = request.path
if current_path != reverse("mfa:verify_otp"):
Expand Down
62 changes: 58 additions & 4 deletions django_mfa/views.py
Expand Up @@ -4,10 +4,11 @@
import re

from django.conf import settings
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.urls import reverse
from django.http import HttpResponseRedirect
from django.shortcuts import render
from django.shortcuts import render, redirect
from django_mfa.models import is_mfa_enabled, UserOTP

from . import totp
Expand All @@ -31,7 +32,13 @@ def configure_mfa(request):
)
base_32_secret_utf8 = base_32_secret.decode("utf-8")
totp_obj = totp.TOTP(base_32_secret_utf8)
qr_code = re.sub(r'=+$', '', totp_obj.provisioning_uri(request.user.email))

try:
issuer_name = settings.MFA_ISSUER_NAME
except:
issuer_name = None
qr_code = re.sub(r'=+$', '', totp_obj.provisioning_uri(request.user.username, issuer_name=issuer_name))

return render(request, 'django_mfa/configure.html', {"qr_code": qr_code, "secret_key": base_32_secret_utf8})


Expand All @@ -49,13 +56,57 @@ def enable_mfa(request):
UserOTP.objects.get_or_create(otp_type=request.POST["otp_type"],
user=request.user,
secret_key=request.POST['secret_key'])
messages.success(request, "You have successfully enabled multi-factor authentication on your account.")
response = redirect(settings.LOGIN_REDIRECT_URL)
return response
else:
totp_obj = totp.TOTP(base_32_secret)
qr_code = totp_obj.provisioning_uri(request.user.email)

return render(request, 'django_mfa/configure.html', {"is_verified": is_verified, "qr_code": qr_code, "secret_key": base_32_secret})


MFA_COOKIE_PREFIX="RMB_"
# update Remember-My-Browser cookie
def update_rmb_cookie(request, response):
try:
remember_my_browser = settings.MFA_REMEMBER_MY_BROWSER
remember_days = settings.MFA_REMEMBER_DAYS
cookie_salt = settings.MFA_COOKIE_SALT
except:
remember_my_browser = False
if remember_my_browser:
# better not to reveal the username. Revealing the number seems harmless
cookie_name = MFA_COOKIE_PREFIX + str(request.user.pk)
response.set_signed_cookie(cookie_name, True, salt=cookie_salt, max_age=remember_days*24*3600,
secure=(not settings.DEBUG), httponly=True)
return response


# verify Remember-My-Browser cookie
# returns True if browser is trusted and no code verification needed
def verify_rmb_cookie(request):
try:
remember_my_browser = settings.MFA_REMEMBER_MY_BROWSER
max_cookie_age = settings.MFA_REMEMBER_DAYS*24*3600
cookie_salt = settings.MFA_COOKIE_SALT
except:
return False
if not remember_my_browser:
return False
else:
cookie_name = MFA_COOKIE_PREFIX + str(request.user.pk)
cookie_value = request.get_signed_cookie(cookie_name, False, max_age=max_cookie_age, salt=cookie_salt)
# if the cookie value is True and the signature is good than the browser can be trusted
return cookie_value


def delete_rmb_cookie(request, response):
cookie_name = MFA_COOKIE_PREFIX + str(request.user.pk)
response.delete_cookie(cookie_name)
return response


@login_required
def disable_mfa(request):
user = request.user
Expand All @@ -64,7 +115,9 @@ def disable_mfa(request):
if request.method == "POST":
user_mfa = user.userotp
user_mfa.delete()
return HttpResponseRedirect(reverse("mfa:configure_mfa"))
messages.success(request, "You have successfully disabled multi-factor authentication on your account.")
response = redirect(settings.LOGIN_REDIRECT_URL)
return delete_rmb_cookie(request, response)
return render(request, 'django_mfa/disable_mfa.html')


Expand All @@ -88,7 +141,8 @@ def verify_otp(request):

if is_verified:
request.session['verfied_otp'] = True
return HttpResponseRedirect(request.POST.get("next", settings.LOGIN_REDIRECT_URL))
response = redirect(request.POST.get("next", settings.LOGIN_REDIRECT_URL))
return update_rmb_cookie(request, response)
ctx['error_message'] = "Your code is expired or invalid."

ctx['next'] = request.GET.get('next', settings.LOGIN_REDIRECT_URL)
Expand Down

0 comments on commit 0f71161

Please sign in to comment.