Implement more advanced 2FA library
This commit is contained in:
parent
7a64759ceb
commit
0fc7c5c712
|
@ -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"]
|
||||
|
|
11
app/urls.py
11
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/<str:type>/", hooks.HookList.as_view(), name="hooks"),
|
||||
path("hooks/<str:type>/create/", hooks.HookCreate.as_view(), name="hook_create"),
|
||||
path(
|
||||
|
|
|
@ -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():
|
||||
|
|
|
@ -268,13 +268,14 @@
|
|||
<a class="button is-info" href="{% url 'signup' %}">
|
||||
<strong>Sign up</strong>
|
||||
</a>
|
||||
<a class="button is-light" href="{% url 'login' %}">
|
||||
<a class="button is-light" href="{% url 'two_factor:login' %}">
|
||||
Log in
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
{% if user.is_authenticated %}
|
||||
<a class="button is-dark" href="{% url 'logout' %}">Logout</a>
|
||||
<a class="button" href="{% url 'two_factor:profile' %}">Security</a>
|
||||
<a class="button" href="{% url 'logout' %}">Logout</a>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
|
@ -315,7 +316,9 @@
|
|||
{% endblock %}
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
{% block content %}
|
||||
{% block content_wrapper %}
|
||||
{% block content %}
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
<div id="modals-here">
|
||||
</div>
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
{% block content %}
|
||||
|
||||
<section>
|
||||
<p>Forgot to add something to your cart? Shop around then come back to pay!</p>
|
||||
<p class="subtitle">Forgot to add something to your cart? Shop around then come back to pay!</p>
|
||||
</section>
|
||||
|
||||
{% endblock %}
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
</div>
|
||||
<div class="media-content">
|
||||
<div class="content">
|
||||
<p>
|
||||
<p class="subtitle">
|
||||
<strong>{{ plan.name }}</strong> <small>£{{ plan.cost }}</small>
|
||||
{% if plan in user_plans %}
|
||||
<i class="fas fa-check" aria-hidden="true"></i>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<div class="hero-body">
|
||||
<div class="container">
|
||||
<div class="columns is-centered">
|
||||
<div class="column is-5-tablet is-4-desktop is-3-widescreen">
|
||||
<div class="column is-5-tablet is-5-desktop is-4-widescreen">
|
||||
<form method="POST" class="box">
|
||||
{% csrf_token %}
|
||||
{{ form|crispy }}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<div class="hero-body">
|
||||
<div class="container">
|
||||
<div class="columns is-centered">
|
||||
<div class="column is-5-tablet is-4-desktop is-3-widescreen">
|
||||
<div class="column is-5-tablet is-5-desktop is-4-widescreen">
|
||||
<div class="box">
|
||||
<p class="has-text-danger">Registration closed.</p>
|
||||
</div>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<div class="hero-body">
|
||||
<div class="container">
|
||||
<div class="columns is-centered">
|
||||
<div class="column is-5-tablet is-4-desktop is-3-widescreen">
|
||||
<div class="column is-5-tablet is-5-desktop is-4-widescreen">
|
||||
<form method="POST" class="box">
|
||||
{% csrf_token %}
|
||||
{{ form|crispy }}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
{% block content %}
|
||||
|
||||
<section>
|
||||
<p>Subscription {{ plan }} cancelled!</p>
|
||||
<p class="subtitle">Subscription {{ plan }} cancelled!</p>
|
||||
</section>
|
||||
|
||||
{% endblock %}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
{% extends 'base.html' %}
|
|
@ -0,0 +1,16 @@
|
|||
{% extends "two_factor/_base.html" %}
|
||||
|
||||
{% block content_wrapper %}
|
||||
<section class="hero is-fullheight">
|
||||
<div class="hero-body">
|
||||
<div class="container">
|
||||
<div class="columns is-centered">
|
||||
<div class="column box is-5-tablet is-5-desktop is-4-widescreen">
|
||||
{% block content %}{% endblock content %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{% endblock %}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
{% load i18n %}
|
||||
|
||||
<div class="buttons">
|
||||
{% if cancel_url %}
|
||||
<a href="{{ cancel_url }}"
|
||||
class="button">{% trans "Cancel" %}</a>
|
||||
{% endif %}
|
||||
{% if wizard.steps.prev %}
|
||||
<button name="wizard_goto_step" type="submit"
|
||||
value="{{ wizard.steps.prev }}"
|
||||
class="button is-info">{% trans "Back" %}</button>
|
||||
{% else %}
|
||||
<button disabled name="" type="button" class="button is-info">{% trans "Back" %}</button>
|
||||
{% endif %}
|
||||
<button type="submit" class="button is-success">{% trans "Next" %}</button>
|
||||
</div>
|
|
@ -0,0 +1,6 @@
|
|||
{% load crispy_forms_tags %}
|
||||
|
||||
<table class="is-3">
|
||||
{{ wizard.management_form|crispy }}
|
||||
{{ wizard.form|crispy }}
|
||||
</table>
|
|
@ -0,0 +1,28 @@
|
|||
{% extends "two_factor/_base_focus.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<h1 class="title">{% block title %}{% trans "Backup Tokens" %}{% endblock %}</h1>
|
||||
<p class="subtitle">{% 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 %}</p>
|
||||
|
||||
{% if device.token_set.count %}
|
||||
<ul>
|
||||
{% for token in device.token_set.all %}
|
||||
<li>{{ token.token }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<p class="subtitle">{% blocktrans %}Print these tokens and keep them somewhere safe.{% endblocktrans %}</p>
|
||||
{% else %}
|
||||
<p class="subtitle">{% trans "You don't have any backup codes yet." %}</p>
|
||||
{% endif %}
|
||||
|
||||
<form method="post">{% csrf_token %}{{ form }}
|
||||
<a href="{% url 'two_factor:profile'%}"
|
||||
class="float-right button is-info">{% trans "Back to Account Security" %}</a>
|
||||
<button class="button is-success" type="submit">{% trans "Generate Tokens" %}</button>
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -0,0 +1,52 @@
|
|||
{% extends "two_factor/_base_focus.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<h1 class="title">{% block title %}{% trans "Login" %}{% endblock %}</h1>
|
||||
|
||||
{% if wizard.steps.current == 'auth' %}
|
||||
<p class="subtitle">{% blocktrans %}Enter your credentials.{% endblocktrans %}</p>
|
||||
{% elif wizard.steps.current == 'token' %}
|
||||
{% if device.method == 'call' %}
|
||||
<p class="subtitle">{% blocktrans trimmed %}We are calling your phone right now, please enter the
|
||||
digits you hear.{% endblocktrans %}</p>
|
||||
{% elif device.method == 'sms' %}
|
||||
<p class="subtitle">{% blocktrans trimmed %}We sent you a text message, please enter the tokens we
|
||||
sent.{% endblocktrans %}</p>
|
||||
{% else %}
|
||||
<p class="subtitle">{% blocktrans trimmed %}Please enter the tokens generated by your token
|
||||
generator.{% endblocktrans %}</p>
|
||||
{% endif %}
|
||||
{% elif wizard.steps.current == 'backup' %}
|
||||
<p class="subtitle">{% 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 %}</p>
|
||||
{% endif %}
|
||||
|
||||
<form action="" method="post">{% csrf_token %}
|
||||
{% include "two_factor/_wizard_forms.html" %}
|
||||
|
||||
{# hidden submit button to enable [enter] key #}
|
||||
<input type="submit" value="" style="display:none" />
|
||||
|
||||
{% if other_devices %}
|
||||
<p class="subtitle">{% trans "Or, alternatively, use one of your backup phones:" %}</p>
|
||||
<p class="subtitle">
|
||||
{% for other in other_devices %}
|
||||
<button name="challenge_device" value="{{ other.persistent_id }}"
|
||||
class="button is-success" type="submit">
|
||||
{{ other.generate_challenge_button_title }}
|
||||
</button>
|
||||
{% endfor %}</p>
|
||||
{% endif %}
|
||||
{% if backup_tokens %}
|
||||
<p class="subtitle">{% trans "As a last resort, you can use a backup token:" %}</p>
|
||||
<p class="subtitle">
|
||||
<button name="wizard_goto_step" type="submit" value="backup"
|
||||
class="button is-success">{% trans "Use Backup Token" %}</button>
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
{% include "two_factor/_wizard_actions.html" %}
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -0,0 +1,22 @@
|
|||
{% extends "two_factor/_base_focus.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<h1 class="title">{% block title %}{% trans "Permission Denied" %}{% endblock %}</h1>
|
||||
|
||||
<p class="subtitle">{% 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 %}</p>
|
||||
|
||||
<p class="subtitle">{% blocktrans trimmed %}Two-factor authentication is not enabled for your
|
||||
account. Enable two-factor authentication for enhanced account
|
||||
security.{% endblocktrans %}</p>
|
||||
<div class="buttons">
|
||||
|
||||
<a href="javascript:history.go(-1)"
|
||||
class="float-right button is-info">{% trans "Go back" %}</a>
|
||||
<a href="{% url 'two_factor:setup' %}" class="button is-success">
|
||||
{% trans "Enable Two-Factor Authentication" %}</a>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
|
@ -0,0 +1,24 @@
|
|||
{% extends "two_factor/_base_focus.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<h1 class="title">{% block title %}{% trans "Add Backup Phone" %}{% endblock %}</h1>
|
||||
|
||||
{% if wizard.steps.current == 'setup' %}
|
||||
<p class="subtitle">{% 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 %}</p>
|
||||
{% elif wizard.steps.current == 'validation' %}
|
||||
<p class="subtitle">{% blocktrans trimmed %}We've sent a token to your phone number. Please
|
||||
enter the token you've received.{% endblocktrans %}</p>
|
||||
{% endif %}
|
||||
|
||||
<form action="" method="post">{% csrf_token %}
|
||||
{% include "two_factor/_wizard_forms.html" %}
|
||||
|
||||
{# hidden submit button to enable [enter] key #}
|
||||
<input type="submit" value="" style="display:none" />
|
||||
|
||||
{% include "two_factor/_wizard_actions.html" %}
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -0,0 +1,56 @@
|
|||
{% extends "two_factor/_base_focus.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<h1 class="title">{% block title %}{% trans "Enable Two-Factor Authentication" %}{% endblock %}</h1>
|
||||
{% if wizard.steps.current == 'welcome' %}
|
||||
<p class="subtitle">{% 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 %}</p>
|
||||
{% elif wizard.steps.current == 'method' %}
|
||||
<p class="subtitle">{% blocktrans trimmed %}Please select which authentication method you would
|
||||
like to use.{% endblocktrans %}</p>
|
||||
{% elif wizard.steps.current == 'generator' %}
|
||||
<p class="subtitle">{% 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 %}</p>
|
||||
<p class="subtitle"><img src="{{ QR_URL }}" alt="QR Code" class="bg-white"/></p>
|
||||
{% elif wizard.steps.current == 'sms' %}
|
||||
<p class="subtitle">{% 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 %}</p>
|
||||
{% elif wizard.steps.current == 'call' %}
|
||||
<p class="subtitle">{% blocktrans trimmed %}Please enter the phone number you wish to be called on.
|
||||
This number will be validated in the next step. {% endblocktrans %}</p>
|
||||
{% elif wizard.steps.current == 'validation' %}
|
||||
{% if challenge_succeeded %}
|
||||
{% if device.method == 'call' %}
|
||||
<p class="subtitle">{% blocktrans trimmed %}We are calling your phone right now, please enter the
|
||||
digits you hear.{% endblocktrans %}</p>
|
||||
{% elif device.method == 'sms' %}
|
||||
<p class="subtitle">{% blocktrans trimmed %}We sent you a text message, please enter the tokens we
|
||||
sent.{% endblocktrans %}</p>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<p class="alert alert-warning" role="alert">{% blocktrans trimmed %}We've
|
||||
encountered an issue with the selected authentication method. Please
|
||||
go back and verify that you entered your information correctly, try
|
||||
again, or use a different authentication method instead. If the issue
|
||||
persists, contact the site administrator.{% endblocktrans %}</p>
|
||||
{% endif %}
|
||||
{% elif wizard.steps.current == 'yubikey' %}
|
||||
<p class="subtitle">{% 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 %}</p>
|
||||
{% endif %}
|
||||
|
||||
<form action="" method="post">{% csrf_token %}
|
||||
{% include "two_factor/_wizard_forms.html" %}
|
||||
|
||||
{# hidden submit button to enable [enter] key #}
|
||||
<input type="submit" value="" style="display:none" />
|
||||
|
||||
{% include "two_factor/_wizard_actions.html" %}
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -0,0 +1,24 @@
|
|||
{% extends "two_factor/_base_focus.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<h1 class="title">{% block title %}{% trans "Enable Two-Factor Authentication" %}{% endblock %}</h1>
|
||||
|
||||
<p class="subtitle">{% blocktrans trimmed %}Congratulations, you've successfully enabled two-factor
|
||||
authentication.{% endblocktrans %}</p>
|
||||
|
||||
{% if not phone_methods %}
|
||||
<p class="subtitle"><a href="{% url 'two_factor:profile' %}"
|
||||
class="button">{% trans "Back to Account Security" %}</a></p>
|
||||
{% else %}
|
||||
<p class="subtitle">{% 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 %}</p>
|
||||
|
||||
<a href="{% url 'two_factor:profile' %}"
|
||||
class="float-right button">{% trans "Back to Account Security" %}</a>
|
||||
<p class="subtitle"><a href="{% url 'two_factor:phone_create' %}"
|
||||
class="button is-success">{% trans "Add Phone Number" %}</a></p>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
|
@ -0,0 +1,14 @@
|
|||
{% extends "two_factor/_base_focus.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<h1 class="title">{% block title %}{% trans "Disable Two-factor Authentication" %}{% endblock %}</h1>
|
||||
<p class="subtitle">{% blocktrans trimmed %}You are about to disable two-factor authentication. This
|
||||
weakens your account security, are you sure?{% endblocktrans %}</p>
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<table>{{ form }}</table>
|
||||
<button class="button is-danger"
|
||||
type="submit">{% trans "Disable" %}</button>
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -0,0 +1,63 @@
|
|||
{% extends "two_factor/_base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<h1 class="title">{% block title %}{% trans "Account Security" %}{% endblock %}</h1>
|
||||
|
||||
{% if default_device %}
|
||||
{% if default_device_type == 'TOTPDevice' %}
|
||||
<p class="subtitle">{% trans "Tokens will be generated by your token generator." %}</p>
|
||||
{% elif default_device_type == 'PhoneDevice' %}
|
||||
<p class="subtitle">{% blocktrans with primary=default_device.generate_challenge_button_title %}Primary method: {{ primary }}{% endblocktrans %}</p>
|
||||
{% elif default_device_type == 'RemoteYubikeyDevice' %}
|
||||
<p class="subtitle">{% blocktrans %}Tokens will be generated by your YubiKey.{% endblocktrans %}</p>
|
||||
{% endif %}
|
||||
|
||||
{% if available_phone_methods %}
|
||||
<h2 class="title is-4">{% trans "Backup Phone Numbers" %}</h2>
|
||||
<p class="subtitle">{% blocktrans trimmed %}If your primary method is not available, we are able to
|
||||
send backup tokens to the phone numbers listed below.{% endblocktrans %}</p>
|
||||
<ul>
|
||||
{% for phone in backup_phones %}
|
||||
<li>
|
||||
{{ phone.generate_challenge_button_title }}
|
||||
<form method="post" action="{% url 'two_factor:phone_delete' phone.id %}"
|
||||
onsubmit="return confirm({% trans 'Are you sure?' %})">
|
||||
{% csrf_token %}
|
||||
<button class="button is-warning"
|
||||
type="submit">{% trans "Unregister" %}</button>
|
||||
</form>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<p class="subtitle"><a href="{% url 'two_factor:phone_create' %}"
|
||||
class="button is-info">{% trans "Add Phone Number" %}</a></p>
|
||||
{% endif %}
|
||||
|
||||
<h2 class="title is-4">{% trans "Backup Tokens" %}</h2>
|
||||
<p class="subtitle">
|
||||
{% 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 %}
|
||||
</p>
|
||||
<p class="subtitle"><a href="{% url 'two_factor:backup_tokens' %}"
|
||||
class="button is-info">{% trans "Show Codes" %}</a></p>
|
||||
|
||||
<h3 class="title is-5">{% trans "Disable Two-Factor Authentication" %}</h3>
|
||||
<p class="subtitle">{% blocktrans trimmed %}However we strongly discourage you to do so, you can
|
||||
also disable two-factor authentication for your account.{% endblocktrans %}</p>
|
||||
<p class="subtitle"><a class="button is-info" href="{% url 'two_factor:disable' %}">
|
||||
{% trans "Disable Two-Factor Authentication" %}</a></p>
|
||||
{% else %}
|
||||
<p class="subtitle">{% blocktrans trimmed %}Two-factor authentication is not enabled for your
|
||||
account. Enable two-factor authentication for enhanced account
|
||||
security.{% endblocktrans %}</p>
|
||||
<p class="subtitle"><a href="{% url 'two_factor:setup' %}" class="button is-success">
|
||||
{% trans "Enable Two-Factor Authentication" %}</a>
|
||||
</p>
|
||||
{% endif %}
|
||||
{% endblock %}
|
|
@ -0,0 +1,7 @@
|
|||
{% load i18n %}<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<Response>
|
||||
<Gather timeout="15" numDigits="1" finishOnKey="">
|
||||
<Say language="{{ locale }}">{% blocktrans %}Hi, this is {{ site_name }} calling. Press any key to continue.{% endblocktrans %}</Say>
|
||||
</Gather>
|
||||
<Say language="{{ locale }}">{% trans "You didn’t press any keys. Good bye." %}</Say>
|
||||
</Response>
|
|
@ -0,0 +1,5 @@
|
|||
{% load i18n %}
|
||||
{% blocktrans trimmed %}
|
||||
Your OTP token is {{ token }}
|
||||
{% endblocktrans %}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
{% load i18n %}<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<Response>
|
||||
<Say language="{{ locale }}">{% trans "Your token is:" %}</Say>
|
||||
<Pause>
|
||||
{% for digit in token %} <Say language="{{ locale }}">{{ digit }}</Say>
|
||||
<Pause>
|
||||
{% endfor %} <Say language="{{ locale }}">{% trans "Repeat:" %}</Say>
|
||||
<Pause>
|
||||
{% for digit in token %} <Say language="{{ locale }}">{{ digit }}</Say>
|
||||
<Pause>
|
||||
{% endfor %} <Say language="{{ locale }}">{% trans "Good bye." %}</Say>
|
||||
</Response>
|
|
@ -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"
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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.
|
||||
"""
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue