From 0fc7c5c7122f19af19302f96bfdd29c93aa3ea1d Mon Sep 17 00:00:00 2001 From: Mark Veidemanis Date: Mon, 28 Nov 2022 19:45:22 +0000 Subject: [PATCH] Implement more advanced 2FA library --- app/settings.py | 16 ++++- app/urls.py | 11 ++-- core/admin.py | 3 +- core/templates/base.html | 9 ++- core/templates/cancel.html | 2 +- core/templates/partials/product-list.html | 2 +- core/templates/registration/login.html | 2 +- .../registration/registration_closed.html | 2 +- core/templates/registration/signup.html | 2 +- core/templates/subscriptioncancel.html | 2 +- core/templates/two_factor/_base.html | 1 + core/templates/two_factor/_base_focus.html | 16 +++++ .../templates/two_factor/_wizard_actions.html | 16 +++++ core/templates/two_factor/_wizard_forms.html | 6 ++ .../two_factor/core/backup_tokens.html | 28 +++++++++ core/templates/two_factor/core/login.html | 52 +++++++++++++++ .../two_factor/core/otp_required.html | 22 +++++++ .../two_factor/core/phone_register.html | 24 +++++++ core/templates/two_factor/core/setup.html | 56 +++++++++++++++++ .../two_factor/core/setup_complete.html | 24 +++++++ .../templates/two_factor/profile/disable.html | 14 +++++ .../templates/two_factor/profile/profile.html | 63 +++++++++++++++++++ .../two_factor/twilio/press_a_key.xml | 7 +++ .../two_factor/twilio/sms_message.html | 5 ++ core/templates/two_factor/twilio/token.xml | 12 ++++ core/views/accounts.py | 11 ++-- core/views/base.py | 2 +- core/views/positions.py | 9 +-- core/views/strategies.py | 14 ++--- core/views/trades.py | 11 ++-- docker/prod/requirements.prod.txt | 3 + 31 files changed, 406 insertions(+), 41 deletions(-) create mode 100644 core/templates/two_factor/_base.html create mode 100644 core/templates/two_factor/_base_focus.html create mode 100644 core/templates/two_factor/_wizard_actions.html create mode 100644 core/templates/two_factor/_wizard_forms.html create mode 100644 core/templates/two_factor/core/backup_tokens.html create mode 100644 core/templates/two_factor/core/login.html create mode 100644 core/templates/two_factor/core/otp_required.html create mode 100644 core/templates/two_factor/core/phone_register.html create mode 100644 core/templates/two_factor/core/setup.html create mode 100644 core/templates/two_factor/core/setup_complete.html create mode 100644 core/templates/two_factor/profile/disable.html create mode 100644 core/templates/two_factor/profile/profile.html create mode 100644 core/templates/two_factor/twilio/press_a_key.xml create mode 100644 core/templates/two_factor/twilio/sms_message.html create mode 100644 core/templates/two_factor/twilio/token.xml diff --git a/app/settings.py b/app/settings.py index ae3591e..6d65fbc 100644 --- a/app/settings.py +++ b/app/settings.py @@ -44,8 +44,14 @@ INSTALLED_APPS = [ # "django_tables2_bulma_template", "django_otp", "django_otp.plugins.otp_totp", + # "django_otp.plugins.otp_email", # 'django_otp.plugins.otp_hotp', "django_otp.plugins.otp_static", + "two_factor", + # "two_factor.plugins.phonenumber", + # "two_factor.plugins.email", + # "two_factor.plugins.yubikey", + # "otp_yubikey", ] CRISPY_TEMPLATE_PACK = "bulma" CRISPY_ALLOWED_TEMPLATE_PACKS = ("bulma",) @@ -135,9 +141,15 @@ DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" AUTH_USER_MODEL = "core.User" -LOGIN_REDIRECT_URL = "home" + LOGOUT_REDIRECT_URL = "home" -LOGIN_URL = "/accounts/login/" + +LOGIN_REDIRECT_URL = "home" +# LOGIN_URL = "/accounts/login/" + +# 2FA +LOGIN_URL = "two_factor:login" +# LOGIN_REDIRECT_URL = 'two_factor:profile' # ALLOWED_PAYMENT_METHODS = ["bacs_debit", "card"] ALLOWED_PAYMENT_METHODS = ["card"] diff --git a/app/urls.py b/app/urls.py index 518a0dc..ff882e0 100644 --- a/app/urls.py +++ b/app/urls.py @@ -16,10 +16,10 @@ Including another URLconf from django.conf import settings from django.conf.urls.static import static from django.contrib import admin -from django.contrib.auth.views import LoginView +from django.contrib.auth.views import LogoutView from django.urls import include, path from django.views.generic import TemplateView -from django_otp.forms import OTPAuthenticationForm +from two_factor.urls import urlpatterns as tf_urls from core.views import ( accounts, @@ -50,11 +50,10 @@ urlpatterns = [ path("cancel/", TemplateView.as_view(template_name="cancel.html"), name="cancel"), path("portal", base.Portal.as_view(), name="portal"), path("sapp/", admin.site.urls), - path( - "accounts/login/", LoginView.as_view(authentication_form=OTPAuthenticationForm) - ), - path("accounts/", include("django.contrib.auth.urls")), + # 2FA login urls + path("", include(tf_urls)), path("accounts/signup/", base.Signup.as_view(), name="signup"), + path("accounts/logout/", LogoutView.as_view(), name="logout"), path("hooks//", hooks.HookList.as_view(), name="hooks"), path("hooks//create/", hooks.HookCreate.as_view(), name="hook_create"), path( diff --git a/core/admin.py b/core/admin.py index 52b3b2d..2dff02f 100644 --- a/core/admin.py +++ b/core/admin.py @@ -1,11 +1,10 @@ from django.contrib import admin from django.contrib.auth.admin import UserAdmin -from django_otp.admin import OTPAdminSite from .forms import CustomUserCreationForm from .models import Plan, Session, User -admin.site.__class__ = OTPAdminSite +# admin.site.__class__ = OTPAdminSite # otp_admin_site = OTPAdminSite(OTPAdminSite.name) # for model_cls, model_admin in admin.site._registry.items(): diff --git a/core/templates/base.html b/core/templates/base.html index 818e50d..fc1c637 100644 --- a/core/templates/base.html +++ b/core/templates/base.html @@ -268,13 +268,14 @@ Sign up - + Log in {% endif %} {% if user.is_authenticated %} - Logout + Security + Logout {% endif %} @@ -315,7 +316,9 @@ {% endblock %}
- {% block content %} + {% block content_wrapper %} + {% block content %} + {% endblock %} {% endblock %}
diff --git a/core/templates/cancel.html b/core/templates/cancel.html index 426cde8..68aa21a 100644 --- a/core/templates/cancel.html +++ b/core/templates/cancel.html @@ -3,7 +3,7 @@ {% block content %}
-

Forgot to add something to your cart? Shop around then come back to pay!

+

Forgot to add something to your cart? Shop around then come back to pay!

{% endblock %} diff --git a/core/templates/partials/product-list.html b/core/templates/partials/product-list.html index 9d845b3..a35839e 100644 --- a/core/templates/partials/product-list.html +++ b/core/templates/partials/product-list.html @@ -12,7 +12,7 @@
-

+

{{ plan.name }} £{{ plan.cost }} {% if plan in user_plans %} diff --git a/core/templates/registration/login.html b/core/templates/registration/login.html index 8bde98d..3bd0432 100644 --- a/core/templates/registration/login.html +++ b/core/templates/registration/login.html @@ -7,7 +7,7 @@

-
+
{% csrf_token %} {{ form|crispy }} diff --git a/core/templates/registration/registration_closed.html b/core/templates/registration/registration_closed.html index f0a7ca1..ba51237 100644 --- a/core/templates/registration/registration_closed.html +++ b/core/templates/registration/registration_closed.html @@ -7,7 +7,7 @@
-
+

Registration closed.

diff --git a/core/templates/registration/signup.html b/core/templates/registration/signup.html index 7beef31..76e8ca8 100644 --- a/core/templates/registration/signup.html +++ b/core/templates/registration/signup.html @@ -7,7 +7,7 @@
-
+
{% csrf_token %} {{ form|crispy }} diff --git a/core/templates/subscriptioncancel.html b/core/templates/subscriptioncancel.html index fe63b0e..917f826 100644 --- a/core/templates/subscriptioncancel.html +++ b/core/templates/subscriptioncancel.html @@ -3,7 +3,7 @@ {% block content %}
-

Subscription {{ plan }} cancelled!

+

Subscription {{ plan }} cancelled!

{% endblock %} diff --git a/core/templates/two_factor/_base.html b/core/templates/two_factor/_base.html new file mode 100644 index 0000000..838aa18 --- /dev/null +++ b/core/templates/two_factor/_base.html @@ -0,0 +1 @@ +{% extends 'base.html' %} \ No newline at end of file diff --git a/core/templates/two_factor/_base_focus.html b/core/templates/two_factor/_base_focus.html new file mode 100644 index 0000000..104ce14 --- /dev/null +++ b/core/templates/two_factor/_base_focus.html @@ -0,0 +1,16 @@ +{% extends "two_factor/_base.html" %} + +{% block content_wrapper %} +
+
+
+
+
+ {% block content %}{% endblock content %} +
+
+
+
+
+{% endblock %} + diff --git a/core/templates/two_factor/_wizard_actions.html b/core/templates/two_factor/_wizard_actions.html new file mode 100644 index 0000000..08099f8 --- /dev/null +++ b/core/templates/two_factor/_wizard_actions.html @@ -0,0 +1,16 @@ +{% load i18n %} + +
+ {% if cancel_url %} + {% trans "Cancel" %} + {% endif %} + {% if wizard.steps.prev %} + + {% else %} + + {% endif %} + +
\ No newline at end of file diff --git a/core/templates/two_factor/_wizard_forms.html b/core/templates/two_factor/_wizard_forms.html new file mode 100644 index 0000000..7a9eb9e --- /dev/null +++ b/core/templates/two_factor/_wizard_forms.html @@ -0,0 +1,6 @@ +{% load crispy_forms_tags %} + + + {{ wizard.management_form|crispy }} + {{ wizard.form|crispy }} +
diff --git a/core/templates/two_factor/core/backup_tokens.html b/core/templates/two_factor/core/backup_tokens.html new file mode 100644 index 0000000..8cb45fa --- /dev/null +++ b/core/templates/two_factor/core/backup_tokens.html @@ -0,0 +1,28 @@ +{% extends "two_factor/_base_focus.html" %} +{% load i18n %} + +{% block content %} +

{% block title %}{% trans "Backup Tokens" %}{% endblock %}

+

{% blocktrans trimmed %}Backup tokens can be used when your primary and backup + phone numbers aren't available. The backup tokens below can be used + for login verification. If you've used up all your backup tokens, you + can generate a new set of backup tokens. Only the backup tokens shown + below will be valid.{% endblocktrans %}

+ + {% if device.token_set.count %} +
    + {% for token in device.token_set.all %} +
  • {{ token.token }}
  • + {% endfor %} +
+

{% blocktrans %}Print these tokens and keep them somewhere safe.{% endblocktrans %}

+ {% else %} +

{% trans "You don't have any backup codes yet." %}

+ {% endif %} + + {% csrf_token %}{{ form }} + {% trans "Back to Account Security" %} + + +{% endblock %} diff --git a/core/templates/two_factor/core/login.html b/core/templates/two_factor/core/login.html new file mode 100644 index 0000000..9ab95e9 --- /dev/null +++ b/core/templates/two_factor/core/login.html @@ -0,0 +1,52 @@ +{% extends "two_factor/_base_focus.html" %} +{% load i18n %} + +{% block content %} +

{% block title %}{% trans "Login" %}{% endblock %}

+ + {% if wizard.steps.current == 'auth' %} +

{% blocktrans %}Enter your credentials.{% endblocktrans %}

+ {% elif wizard.steps.current == 'token' %} + {% if device.method == 'call' %} +

{% blocktrans trimmed %}We are calling your phone right now, please enter the + digits you hear.{% endblocktrans %}

+ {% elif device.method == 'sms' %} +

{% blocktrans trimmed %}We sent you a text message, please enter the tokens we + sent.{% endblocktrans %}

+ {% else %} +

{% blocktrans trimmed %}Please enter the tokens generated by your token + generator.{% endblocktrans %}

+ {% endif %} + {% elif wizard.steps.current == 'backup' %} +

{% blocktrans trimmed %}Use this form for entering backup tokens for logging in. + These tokens have been generated for you to print and keep safe. Please + enter one of these backup tokens to login to your account.{% endblocktrans %}

+ {% endif %} + +
{% csrf_token %} + {% include "two_factor/_wizard_forms.html" %} + + {# hidden submit button to enable [enter] key #} + + + {% if other_devices %} +

{% trans "Or, alternatively, use one of your backup phones:" %}

+

+ {% for other in other_devices %} + + {% endfor %}

+ {% endif %} + {% if backup_tokens %} +

{% trans "As a last resort, you can use a backup token:" %}

+

+ +

+ {% endif %} + + {% include "two_factor/_wizard_actions.html" %} +
+{% endblock %} diff --git a/core/templates/two_factor/core/otp_required.html b/core/templates/two_factor/core/otp_required.html new file mode 100644 index 0000000..f9360d9 --- /dev/null +++ b/core/templates/two_factor/core/otp_required.html @@ -0,0 +1,22 @@ +{% extends "two_factor/_base_focus.html" %} +{% load i18n %} + +{% block content %} +

{% block title %}{% trans "Permission Denied" %}{% endblock %}

+ +

{% blocktrans trimmed %}The page you requested, enforces users to verify using + two-factor authentication for security reasons. You need to enable these + security features in order to access this page.{% endblocktrans %}

+ +

{% blocktrans trimmed %}Two-factor authentication is not enabled for your + account. Enable two-factor authentication for enhanced account + security.{% endblocktrans %}

+ + +{% endblock %} diff --git a/core/templates/two_factor/core/phone_register.html b/core/templates/two_factor/core/phone_register.html new file mode 100644 index 0000000..11182ff --- /dev/null +++ b/core/templates/two_factor/core/phone_register.html @@ -0,0 +1,24 @@ +{% extends "two_factor/_base_focus.html" %} +{% load i18n %} + +{% block content %} +

{% block title %}{% trans "Add Backup Phone" %}{% endblock %}

+ + {% if wizard.steps.current == 'setup' %} +

{% blocktrans trimmed %}You'll be adding a backup phone number to your + account. This number will be used if your primary method of + registration is not available.{% endblocktrans %}

+ {% elif wizard.steps.current == 'validation' %} +

{% blocktrans trimmed %}We've sent a token to your phone number. Please + enter the token you've received.{% endblocktrans %}

+ {% endif %} + +
{% csrf_token %} + {% include "two_factor/_wizard_forms.html" %} + + {# hidden submit button to enable [enter] key #} + + + {% include "two_factor/_wizard_actions.html" %} +
+{% endblock %} diff --git a/core/templates/two_factor/core/setup.html b/core/templates/two_factor/core/setup.html new file mode 100644 index 0000000..bd5c93d --- /dev/null +++ b/core/templates/two_factor/core/setup.html @@ -0,0 +1,56 @@ +{% extends "two_factor/_base_focus.html" %} +{% load i18n %} + +{% block content %} +

{% block title %}{% trans "Enable Two-Factor Authentication" %}{% endblock %}

+ {% if wizard.steps.current == 'welcome' %} +

{% blocktrans trimmed %}You are about to take your account security to the + next level. Follow the steps in this wizard to enable two-factor + authentication.{% endblocktrans %}

+ {% elif wizard.steps.current == 'method' %} +

{% blocktrans trimmed %}Please select which authentication method you would + like to use.{% endblocktrans %}

+ {% elif wizard.steps.current == 'generator' %} +

{% blocktrans trimmed %}To start using a token generator, please use your + smartphone to scan the QR code below. For example, use Google + Authenticator. Then, enter the token generated by the app. + {% endblocktrans %}

+

QR Code

+ {% elif wizard.steps.current == 'sms' %} +

{% blocktrans trimmed %}Please enter the phone number you wish to receive the + text messages on. This number will be validated in the next step. + {% endblocktrans %}

+ {% elif wizard.steps.current == 'call' %} +

{% blocktrans trimmed %}Please enter the phone number you wish to be called on. + This number will be validated in the next step. {% endblocktrans %}

+ {% elif wizard.steps.current == 'validation' %} + {% if challenge_succeeded %} + {% if device.method == 'call' %} +

{% blocktrans trimmed %}We are calling your phone right now, please enter the + digits you hear.{% endblocktrans %}

+ {% elif device.method == 'sms' %} +

{% blocktrans trimmed %}We sent you a text message, please enter the tokens we + sent.{% endblocktrans %}

+ {% endif %} + {% else %} + + {% endif %} + {% elif wizard.steps.current == 'yubikey' %} +

{% blocktrans trimmed %}To identify and verify your YubiKey, please insert a + token in the field below. Your YubiKey will be linked to your + account.{% endblocktrans %}

+ {% endif %} + +
{% csrf_token %} + {% include "two_factor/_wizard_forms.html" %} + + {# hidden submit button to enable [enter] key #} + + + {% include "two_factor/_wizard_actions.html" %} +
+{% endblock %} diff --git a/core/templates/two_factor/core/setup_complete.html b/core/templates/two_factor/core/setup_complete.html new file mode 100644 index 0000000..17415d6 --- /dev/null +++ b/core/templates/two_factor/core/setup_complete.html @@ -0,0 +1,24 @@ +{% extends "two_factor/_base_focus.html" %} +{% load i18n %} + +{% block content %} +

{% block title %}{% trans "Enable Two-Factor Authentication" %}{% endblock %}

+ +

{% blocktrans trimmed %}Congratulations, you've successfully enabled two-factor + authentication.{% endblocktrans %}

+ + {% if not phone_methods %} +

{% trans "Back to Account Security" %}

+ {% else %} +

{% blocktrans trimmed %}However, it might happen that you don't have access to + your primary token device. To enable account recovery, add a phone + number.{% endblocktrans %}

+ + {% trans "Back to Account Security" %} +

{% trans "Add Phone Number" %}

+ {% endif %} + +{% endblock %} diff --git a/core/templates/two_factor/profile/disable.html b/core/templates/two_factor/profile/disable.html new file mode 100644 index 0000000..a313dcc --- /dev/null +++ b/core/templates/two_factor/profile/disable.html @@ -0,0 +1,14 @@ +{% extends "two_factor/_base_focus.html" %} +{% load i18n %} + +{% block content %} +

{% block title %}{% trans "Disable Two-factor Authentication" %}{% endblock %}

+

{% blocktrans trimmed %}You are about to disable two-factor authentication. This + weakens your account security, are you sure?{% endblocktrans %}

+
+ {% csrf_token %} + {{ form }}
+ +
+{% endblock %} diff --git a/core/templates/two_factor/profile/profile.html b/core/templates/two_factor/profile/profile.html new file mode 100644 index 0000000..44a0049 --- /dev/null +++ b/core/templates/two_factor/profile/profile.html @@ -0,0 +1,63 @@ +{% extends "two_factor/_base.html" %} +{% load i18n %} + +{% block content %} +

{% block title %}{% trans "Account Security" %}{% endblock %}

+ + {% if default_device %} + {% if default_device_type == 'TOTPDevice' %} +

{% trans "Tokens will be generated by your token generator." %}

+ {% elif default_device_type == 'PhoneDevice' %} +

{% blocktrans with primary=default_device.generate_challenge_button_title %}Primary method: {{ primary }}{% endblocktrans %}

+ {% elif default_device_type == 'RemoteYubikeyDevice' %} +

{% blocktrans %}Tokens will be generated by your YubiKey.{% endblocktrans %}

+ {% endif %} + + {% if available_phone_methods %} +

{% trans "Backup Phone Numbers" %}

+

{% blocktrans trimmed %}If your primary method is not available, we are able to + send backup tokens to the phone numbers listed below.{% endblocktrans %}

+
    + {% for phone in backup_phones %} +
  • + {{ phone.generate_challenge_button_title }} +
    + {% csrf_token %} + +
    +
  • + {% endfor %} +
+

{% trans "Add Phone Number" %}

+ {% endif %} + +

{% trans "Backup Tokens" %}

+

+ {% blocktrans trimmed %}If you don't have any device with you, you can access + your account using backup tokens.{% endblocktrans %} + {% blocktrans trimmed count counter=backup_tokens %} + You have only one backup token remaining. + {% plural %} + You have {{ counter }} backup tokens remaining. + {% endblocktrans %} +

+

{% trans "Show Codes" %}

+ +

{% trans "Disable Two-Factor Authentication" %}

+

{% blocktrans trimmed %}However we strongly discourage you to do so, you can + also disable two-factor authentication for your account.{% endblocktrans %}

+

+ {% trans "Disable Two-Factor Authentication" %}

+ {% else %} +

{% blocktrans trimmed %}Two-factor authentication is not enabled for your + account. Enable two-factor authentication for enhanced account + security.{% endblocktrans %}

+

+ {% trans "Enable Two-Factor Authentication" %} +

+ {% endif %} +{% endblock %} diff --git a/core/templates/two_factor/twilio/press_a_key.xml b/core/templates/two_factor/twilio/press_a_key.xml new file mode 100644 index 0000000..85a3619 --- /dev/null +++ b/core/templates/two_factor/twilio/press_a_key.xml @@ -0,0 +1,7 @@ +{% load i18n %} + + + {% blocktrans %}Hi, this is {{ site_name }} calling. Press any key to continue.{% endblocktrans %} + + {% trans "You didn’t press any keys. Good bye." %} + diff --git a/core/templates/two_factor/twilio/sms_message.html b/core/templates/two_factor/twilio/sms_message.html new file mode 100644 index 0000000..b680488 --- /dev/null +++ b/core/templates/two_factor/twilio/sms_message.html @@ -0,0 +1,5 @@ +{% load i18n %} +{% blocktrans trimmed %} + Your OTP token is {{ token }} +{% endblocktrans %} + diff --git a/core/templates/two_factor/twilio/token.xml b/core/templates/two_factor/twilio/token.xml new file mode 100644 index 0000000..4eafffe --- /dev/null +++ b/core/templates/two_factor/twilio/token.xml @@ -0,0 +1,12 @@ +{% load i18n %} + + {% trans "Your token is:" %} + +{% for digit in token %} {{ digit }} + +{% endfor %} {% trans "Repeat:" %} + +{% for digit in token %} {{ digit }} + +{% endfor %} {% trans "Good bye." %} + diff --git a/core/views/accounts.py b/core/views/accounts.py index 2afc901..6965b9d 100644 --- a/core/views/accounts.py +++ b/core/views/accounts.py @@ -4,6 +4,7 @@ from django.contrib.auth.mixins import LoginRequiredMixin from django.http import HttpResponseBadRequest from django.shortcuts import render from django.views import View +from two_factor.views.mixins import OTPRequiredMixin from core.forms import AccountForm from core.models import Account @@ -13,7 +14,7 @@ from core.views import ObjectCreate, ObjectDelete, ObjectList, ObjectUpdate log = logs.get_logger(__name__) -class AccountInfo(LoginRequiredMixin, View): +class AccountInfo(LoginRequiredMixin, OTPRequiredMixin, View): VIEWABLE_FIELDS_MODEL = [ "name", "exchange", @@ -69,7 +70,7 @@ class AccountInfo(LoginRequiredMixin, View): return render(request, template_name, context) -class AccountList(LoginRequiredMixin, ObjectList): +class AccountList(LoginRequiredMixin, OTPRequiredMixin, ObjectList): list_template = "partials/account-list.html" model = Account page_title = "List of accounts" @@ -80,7 +81,7 @@ class AccountList(LoginRequiredMixin, ObjectList): submit_url_name = "account_create" -class AccountCreate(LoginRequiredMixin, ObjectCreate): +class AccountCreate(LoginRequiredMixin, OTPRequiredMixin, ObjectCreate): model = Account form_class = AccountForm @@ -103,7 +104,7 @@ class AccountCreate(LoginRequiredMixin, ObjectCreate): # ) -class AccountUpdate(LoginRequiredMixin, ObjectUpdate): +class AccountUpdate(LoginRequiredMixin, OTPRequiredMixin, ObjectUpdate): model = Account form_class = AccountForm @@ -113,7 +114,7 @@ class AccountUpdate(LoginRequiredMixin, ObjectUpdate): submit_url_name = "account_update" -class AccountDelete(LoginRequiredMixin, ObjectDelete): +class AccountDelete(LoginRequiredMixin, OTPRequiredMixin, ObjectDelete): model = Account list_url_name = "accounts" diff --git a/core/views/base.py b/core/views/base.py index f8107e9..7464876 100644 --- a/core/views/base.py +++ b/core/views/base.py @@ -94,7 +94,7 @@ class Signup(CreateView): def get(self, request, *args, **kwargs): if not settings.REGISTRATION_OPEN: return render(request, "registration/registration_closed.html") - super().get(request, *args, **kwargs) + return super().get(request, *args, **kwargs) class Portal(LoginRequiredMixin, View): diff --git a/core/views/positions.py b/core/views/positions.py index 0fc858c..5a2c599 100644 --- a/core/views/positions.py +++ b/core/views/positions.py @@ -5,6 +5,7 @@ from django.http import HttpResponseBadRequest from django.shortcuts import render from django.views import View from rest_framework.parsers import FormParser +from two_factor.views.mixins import OTPRequiredMixin from core.exchanges import GenericAPIError from core.models import Account @@ -27,14 +28,14 @@ def get_positions(user, account_id=None): return items -class Positions(LoginRequiredMixin, View): +class Positions(LoginRequiredMixin, OTPRequiredMixin, View): allowed_types = ["modal", "widget", "window", "page"] window_content = "window-content/objects.html" list_template = "partials/position-list.html" page_title = "Live positions from all exchanges" page_subtitle = "Manual trades are editable under 'Bot Trades' tab." - async def get(self, request, type, account_id=None): + def get(self, request, type, account_id=None): if type not in self.allowed_types: return HttpResponseBadRequest template_name = f"wm/{type}.html" @@ -55,12 +56,12 @@ class Positions(LoginRequiredMixin, View): return render(request, template_name, context) -class PositionAction(LoginRequiredMixin, View): +class PositionAction(LoginRequiredMixin, OTPRequiredMixin, View): allowed_types = ["modal", "widget", "window", "page"] window_content = "window-content/view-position.html" parser_classes = [FormParser] - async def get(self, request, type, account_id, symbol): + def get(self, request, type, account_id, symbol): """ Get live information for a trade. """ diff --git a/core/views/strategies.py b/core/views/strategies.py index e62b87c..5bbe5a9 100644 --- a/core/views/strategies.py +++ b/core/views/strategies.py @@ -1,17 +1,17 @@ from django.contrib.auth.mixins import LoginRequiredMixin +# from django.urls import reverse +from two_factor.views.mixins import OTPRequiredMixin + from core.forms import StrategyForm from core.models import Strategy from core.util import logs from core.views import ObjectCreate, ObjectDelete, ObjectList, ObjectUpdate -# from django.urls import reverse - - log = logs.get_logger(__name__) -class StrategyList(LoginRequiredMixin, ObjectList): +class StrategyList(LoginRequiredMixin, OTPRequiredMixin, ObjectList): list_template = "partials/strategy-list.html" model = Strategy page_title = "List of strategies" @@ -22,7 +22,7 @@ class StrategyList(LoginRequiredMixin, ObjectList): submit_url_name = "strategy_create" -class StrategyCreate(LoginRequiredMixin, ObjectCreate): +class StrategyCreate(LoginRequiredMixin, OTPRequiredMixin, ObjectCreate): model = Strategy form_class = StrategyForm list_url_name = "strategies" @@ -31,7 +31,7 @@ class StrategyCreate(LoginRequiredMixin, ObjectCreate): submit_url_name = "strategy_create" -class StrategyUpdate(LoginRequiredMixin, ObjectUpdate): +class StrategyUpdate(LoginRequiredMixin, OTPRequiredMixin, ObjectUpdate): model = Strategy form_class = StrategyForm @@ -41,7 +41,7 @@ class StrategyUpdate(LoginRequiredMixin, ObjectUpdate): submit_url_name = "strategy_update" -class StrategyDelete(LoginRequiredMixin, ObjectDelete): +class StrategyDelete(LoginRequiredMixin, OTPRequiredMixin, ObjectDelete): model = Strategy list_url_name = "strategies" diff --git a/core/views/trades.py b/core/views/trades.py index 1f0130c..f4a8a99 100644 --- a/core/views/trades.py +++ b/core/views/trades.py @@ -1,6 +1,7 @@ from django.contrib.auth.mixins import LoginRequiredMixin from django.shortcuts import render from django.views import View +from two_factor.views.mixins import OTPRequiredMixin from core.forms import TradeForm from core.models import Trade @@ -16,7 +17,7 @@ from core.views import ( log = logs.get_logger(__name__) -class TradeList(LoginRequiredMixin, ObjectList): +class TradeList(LoginRequiredMixin, OTPRequiredMixin, ObjectList): list_template = "partials/trade-list.html" model = Trade page_title = ( @@ -32,7 +33,7 @@ class TradeList(LoginRequiredMixin, ObjectList): delete_all_url_name = "trade_delete_all" -class TradeCreate(LoginRequiredMixin, ObjectCreate): +class TradeCreate(LoginRequiredMixin, OTPRequiredMixin, ObjectCreate): model = Trade form_class = TradeForm list_url_name = "trades" @@ -45,7 +46,7 @@ class TradeCreate(LoginRequiredMixin, ObjectCreate): log.debug(f"Posting trade {obj}") -class TradeUpdate(LoginRequiredMixin, ObjectUpdate): +class TradeUpdate(LoginRequiredMixin, OTPRequiredMixin, ObjectUpdate): model = Trade form_class = TradeForm list_url_name = "trades" @@ -54,14 +55,14 @@ class TradeUpdate(LoginRequiredMixin, ObjectUpdate): submit_url_name = "trade_update" -class TradeDelete(LoginRequiredMixin, ObjectDelete): +class TradeDelete(LoginRequiredMixin, OTPRequiredMixin, ObjectDelete): model = Trade list_url_name = "trades" list_url_args = ["type"] -class TradeDeleteAll(LoginRequiredMixin, ObjectNameMixin, View): +class TradeDeleteAll(LoginRequiredMixin, OTPRequiredMixin, ObjectNameMixin, View): template_name = "partials/notify.html" model = Trade diff --git a/docker/prod/requirements.prod.txt b/docker/prod/requirements.prod.txt index 2b9726d..7bee8ee 100644 --- a/docker/prod/requirements.prod.txt +++ b/docker/prod/requirements.prod.txt @@ -14,6 +14,9 @@ django-debug-toolbar django-debug-toolbar-template-profiler orjson django-otp +django-two-factor-auth +django-otp-yubikey +phonenumbers qrcode pydantic alpaca-py