diff --git a/.gitignore b/.gitignore index e996682..3a7bc4a 100644 --- a/.gitignore +++ b/.gitignore @@ -284,4 +284,6 @@ fabric.properties # Test blog posts /markdownblog/mdfiles/* /envvars.env -/markdownblog/static \ No newline at end of file +/markdownblog/static + +/*.pem \ No newline at end of file diff --git a/markdownblog/blog/views.py b/markdownblog/blog/views.py index 27ac043..c967c01 100644 --- a/markdownblog/blog/views.py +++ b/markdownblog/blog/views.py @@ -10,6 +10,7 @@ from django.shortcuts import render, redirect from blog.models import Topic, Tag, Blogpost from django.template import Template, Context from django.views.decorators.csrf import csrf_exempt +from django_2fa.decorators import mfa_login_required def render_md_file(path) -> Template: @@ -53,6 +54,7 @@ def index(request) -> HttpResponse: @login_required +@mfa_login_required def edit(request, id) -> HttpResponse: blogpost = Blogpost.objects.get(pk=id) mdfile_content = open(blogpost.mdfile, "r").read() @@ -94,6 +96,7 @@ def edit(request, id) -> HttpResponse: @login_required @csrf_exempt +@mfa_login_required def order(request) -> HttpResponse: if request.method == "POST": root_id = int(request.POST['rootID']) if request.POST['rootID'] != 'root_list' else None @@ -114,6 +117,7 @@ def order(request) -> HttpResponse: @login_required +@mfa_login_required def addpost(request) -> HttpResponse: context = {'alltopics': Topic.objects.all().order_by('name').values(), 'markdown': '', 'roottopics': Topic.objects.all().filter(rootTopic=None), @@ -154,6 +158,7 @@ def addpost(request) -> HttpResponse: # @login_required +# @mfa_login_required # def createmocks(request, objtype, n) -> HttpResponse: # topics = TopicFactory.create_batch(n) # @@ -171,6 +176,7 @@ def addpost(request) -> HttpResponse: @login_required +@mfa_login_required def addtopic(request): context = {'roottopics': Topic.objects.all().filter(rootTopic=None), 'allposts': Blogpost.objects.all()} diff --git a/markdownblog/markdownblog/settings.py b/markdownblog/markdownblog/settings.py index 084b2cb..054a83d 100644 --- a/markdownblog/markdownblog/settings.py +++ b/markdownblog/markdownblog/settings.py @@ -32,6 +32,7 @@ INSTALLED_APPS = [ 'blog', 'markdownblog', 'fontawesomefree', + 'django_2fa', 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', @@ -48,6 +49,7 @@ MIDDLEWARE = [ 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', + 'django_2fa.middleware.MFAProctectMiddleware', ] ROOT_URLCONF = 'markdownblog.urls' @@ -128,7 +130,19 @@ STATIC_URL = 'static/' DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' +LOGIN_URL = '/accounts/login' + LOGIN_REDIRECT_URL = "/" LOGOUT_REDIRECT_URL = "/" CSRF_HEADER_NAME = "X-CSRFToken" + +# multifactor auth +MFA_URL = '/accounts/2fa/login' + +SALT_KEY = os.environ['SALT_KEY'] + +MFA_ISSUER_NAME = 'MDBlog' + +# Configure mail here +EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' diff --git a/markdownblog/markdownblog/static/base.css b/markdownblog/markdownblog/static/base.css index 3cfe968..705328e 100644 --- a/markdownblog/markdownblog/static/base.css +++ b/markdownblog/markdownblog/static/base.css @@ -24,4 +24,8 @@ header, main, footer { header, main, footer { padding-left: 0; } +} + +.mt-3 { + margin-top: 3em; } \ No newline at end of file diff --git a/markdownblog/markdownblog/templates/2fa/add-device.html b/markdownblog/markdownblog/templates/2fa/add-device.html new file mode 100644 index 0000000..87e930f --- /dev/null +++ b/markdownblog/markdownblog/templates/2fa/add-device.html @@ -0,0 +1,57 @@ +{% extends 'base/base.html' %} +{% load i18n static %} +{% block title %} + Add new 2FA device +{% endblock %} +{% block includehere %} +{% endblock %} +{% block content %} +
+

{% translate 'Add new 2FA device' %}

+
+ {% if form.errors and not form.non_field_errors %} +

+ {% if form.errors.items|length == 1 %}{% translate "Please correct the error below." %}{% else %} + {% translate "Please correct the errors below." %}{% endif %} +

+ {% endif %} +
+ {% csrf_token %} + + {% if form.non_field_errors %} +
+ {{ form.non_field_errors }} +
+ {% endif %} +
+
+ {{ form.name.errors }} + {{ form.name }} + {{ form.name.label_tag }} +
+
+
+
+ {{ form.device_type.errors }} + {{ form.device_type }} + {{ form.device_type.label_tag }} +
+
+ {% for hidden in form.hidden_fields %}{{ hidden }}{% endfor %} +
+
+ +
+
+
+
+ +{% endblock %} + diff --git a/markdownblog/markdownblog/templates/2fa/complete-fido.html b/markdownblog/markdownblog/templates/2fa/complete-fido.html new file mode 100644 index 0000000..a123371 --- /dev/null +++ b/markdownblog/markdownblog/templates/2fa/complete-fido.html @@ -0,0 +1,41 @@ +{% extends "base/base.html" %} +{% load i18n static %} +{% block title %}{% translate "Complete setup for 2FA Device" %}{% endblock %} +{% block content %} +
+

{% translate 'Hardware Key Setup' %}

+
+

{% translate 'Insert your hardware key to complete setup for:' %} {{ device.name }}

+
+ + + +{% endblock %} \ No newline at end of file diff --git a/markdownblog/markdownblog/templates/2fa/devices.html b/markdownblog/markdownblog/templates/2fa/devices.html new file mode 100644 index 0000000..51a98ce --- /dev/null +++ b/markdownblog/markdownblog/templates/2fa/devices.html @@ -0,0 +1,39 @@ +{% extends 'base/base.html' %} +{% load i18n static %} +{% block title %} + Manage 2FA devices +{% endblock %} +{% block includehere %} +{% endblock %} +{% block content %} +
+

{% translate 'Manage 2FA devices' %}

+
+ + Add Device + + +
+ +{% endblock %} \ No newline at end of file diff --git a/markdownblog/markdownblog/templates/2fa/verify.html b/markdownblog/markdownblog/templates/2fa/verify.html new file mode 100644 index 0000000..3570279 --- /dev/null +++ b/markdownblog/markdownblog/templates/2fa/verify.html @@ -0,0 +1,76 @@ +{% extends "base/base.html" %} +{% load i18n static %} +{% block title %}{% translate "Verify authenticator app" %}{% endblock %} +{% block content %} +
+ {% if complete_setup %} +

{% translate 'Verify your authorization device.' %}

+ {% else %} +

{% translate 'Please authenticate with your 2FA token' %}

+ {% endif %} +
+ {% if form.errors and not form.non_field_errors %} +

+ {% if form.errors.items|length == 1 %}{% translate "Please correct the error below." %}{% else %} + {% translate "Please correct the errors below." %}{% endif %} +

+ {% endif %} +
+
+ {% csrf_token %} + + {% if form.non_field_errors %} +
+ {{ form.non_field_errors }} +
+ {% endif %} +
+

{% translate '2FA Factor:' %} {{ device.name }}

+

+ {% if device.device_type == 'email' %} + {% translate 'Check your e-mail for your authorization code.' %} + {% elif device.device_type == 'app' %} + {% if not complete_setup %} + {% translate 'Enter code from your authenicator application.' %}{% endif %} + {% else %} + {% translate 'Use your hardware token to complete sign in.' %} + {% endif %} +

+
+
+ {% if complete_setup and device.device_type == 'app' %} +

+ {% translate 'Scan QR below in your authenicator app, then enter a code to verify your device.' %} +
+ +

{% translate 'Provision URL:' %}
+

{{ device.provision_url }}
+

+ {% endif %} +
+ {{ form.mfa_code.errors }} + {{ form.mfa_code }} + {{ form.mfa_code.label_tag }} +
+
+
+
+ +
+
+
+
+
+ {% if complete_setup and device.device_type == 'app' %} + + + {% endif %} +{% endblock %} diff --git a/markdownblog/markdownblog/templates/base/base.html b/markdownblog/markdownblog/templates/base/base.html index dd49ed4..10dce4c 100644 --- a/markdownblog/markdownblog/templates/base/base.html +++ b/markdownblog/markdownblog/templates/base/base.html @@ -61,6 +61,8 @@
  • admin_panel_settingsAdmin panel
  • +
  • + vpn_keyManage 2FA
  • logoutLogout
  • {% if debug %} diff --git a/markdownblog/markdownblog/urls.py b/markdownblog/markdownblog/urls.py index 29fdaed..3bf1972 100644 --- a/markdownblog/markdownblog/urls.py +++ b/markdownblog/markdownblog/urls.py @@ -1,8 +1,11 @@ from django.contrib import admin from django.urls import path, include +import django_2fa.urls + urlpatterns = [ path('', include('blog.urls')), path('admin/', admin.site.urls), - path('accounts/', include('django.contrib.auth.urls')) + path('accounts/', include('django.contrib.auth.urls')), + path('accounts/2fa/', include(django_2fa.urls)), ] diff --git a/markdownblog/requirements.txt b/markdownblog/requirements.txt index ec17783..8f38469 100644 --- a/markdownblog/requirements.txt +++ b/markdownblog/requirements.txt @@ -3,4 +3,5 @@ psycopg2-binary==2.9.3 factory-boy==3.2.1 markdown2==2.4.3 fontawesomefree==6.1.1 -pygments==2.12.0 \ No newline at end of file +pygments==2.12.0 +django-2fa==0.9.0 \ No newline at end of file