diff --git a/logs_collector/collector/templates/collector/tickets.html b/logs_collector/collector/templates/collector/tickets.html
index 79f8a71..5392c93 100644
--- a/logs_collector/collector/templates/collector/tickets.html
+++ b/logs_collector/collector/templates/collector/tickets.html
@@ -1,7 +1,7 @@
{% extends 'collector/base.html' %}
{% load static %}
{% load collector_extras %}
-{% block title %}
{% endblock title %}
+{% block title %} {{ title }} {% endblock title %}
{% block main %}
{% csrf_token %}
diff --git a/logs_collector/collector/views.py b/logs_collector/collector/views.py
index 8de0156..0ff5bd4 100644
--- a/logs_collector/collector/views.py
+++ b/logs_collector/collector/views.py
@@ -15,6 +15,8 @@ from rest_framework import filters
from django_filters.rest_framework import DjangoFilterBackend
+from two_factor.views import OTPRequiredMixin
+
from .models import Archive, Ticket, Platform
from .forms import TicketForm
from .filters import ArchiveFilter, TicketFilter
@@ -29,7 +31,11 @@ from .serializers import (
)
-class ArchiveHandlerView(LoginRequiredMixin, SingleObjectMixin, generic.View):
+class ArchiveHandlerView(
+ OTPRequiredMixin,
+ LoginRequiredMixin,
+ SingleObjectMixin,
+ generic.View):
model = Archive
slug_field = 'file'
slug_url_kwarg = 'path'
@@ -67,7 +73,7 @@ class UpdateTicket(LoginRequiredMixin, PageTitleViewMixin, generic.UpdateView):
return super().form_valid(form)
-class ListAllTickets(PageTitleViewMixin, generic.ListView):
+class ListAllTickets(LoginRequiredMixin, PageTitleViewMixin, generic.ListView):
model = Ticket
template_name = 'collector/tickets.html'
context_object_name = 'tickets'
diff --git a/logs_collector/logs_collector/settings.py b/logs_collector/logs_collector/settings.py
index 4ce9933..862c927 100644
--- a/logs_collector/logs_collector/settings.py
+++ b/logs_collector/logs_collector/settings.py
@@ -36,12 +36,18 @@ INSTALLED_APPS = [
'django.contrib.messages',
'django.contrib.staticfiles',
'collector.apps.CollectorConfig', # main app
+ 'account.apps.AccountConfig', # account app
'rest_framework',
'rest_framework_simplejwt',
'django_filters',
'drf_spectacular',
"crispy_forms",
"crispy_bootstrap5",
+ 'django_otp',
+ 'django_otp.plugins.otp_static',
+ 'django_otp.plugins.otp_totp',
+ 'two_factor.plugins.phonenumber', # <- if you want phone number capability
+ 'two_factor',
'django_cleanup.apps.CleanupConfig', # required bottom
]
@@ -51,6 +57,7 @@ MIDDLEWARE = [
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
+ 'django_otp.middleware.OTPMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
@@ -60,7 +67,7 @@ ROOT_URLCONF = 'logs_collector.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
- 'DIRS': [],
+ 'DIRS': [BASE_DIR / 'templates'],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
@@ -216,3 +223,7 @@ SIMPLE_JWT = {
"SLIDING_TOKEN_OBTAIN_SERIALIZER": "rest_framework_simplejwt.serializers.TokenObtainSlidingSerializer", # noqa:E501
"SLIDING_TOKEN_REFRESH_SERIALIZER": "rest_framework_simplejwt.serializers.TokenRefreshSlidingSerializer", # noqa:E501
}
+
+LOGIN_URL = 'two_factor:login'
+LOGIN_REDIRECT_URL = 'collector:index'
+LOGOUT_REDIRECT_URL = 'two_factor:login'
diff --git a/logs_collector/logs_collector/urls.py b/logs_collector/logs_collector/urls.py
index 3d237e2..51c4d90 100644
--- a/logs_collector/logs_collector/urls.py
+++ b/logs_collector/logs_collector/urls.py
@@ -1,60 +1,28 @@
-"""
-URL configuration for logs_collector project.
-
-The `urlpatterns` list routes URLs to views. For more information please see:
- https://docs.djangoproject.com/en/4.2/topics/http/urls/
-Examples:
-Function views
- 1. Add an import: from my_app import views
- 2. Add a URL to urlpatterns: path('', views.home, name='home')
-Class-based views
- 1. Add an import: from other_app.views import Home
- 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
-Including another URLconf
- 1. Import the include() function: from django.urls import include, path
- 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
-"""
from django.conf.urls.static import static
from django.contrib import admin
from django.urls import path, include
-from rest_framework_simplejwt.views import (
- TokenObtainPairView,
- TokenRefreshView,
- TokenVerifyView
-)
-
from drf_spectacular.views import (
SpectacularAPIView,
SpectacularRedocView,
SpectacularSwaggerView
)
+from two_factor.urls import urlpatterns as tf_urls
+
from logs_collector import settings
+from account.utils import AdminSiteOTPRequiredMixinRedirectSetup
+
+
+# ? 2FA patch (Admin site protection)
+admin.site.__class__ = AdminSiteOTPRequiredMixinRedirectSetup
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('collector.urls', namespace='collector')),
-]
-
-urlpatterns += [
- # JWT AUTH:
- path(
- 'api/v1/auth/token/',
- TokenObtainPairView.as_view(),
- name='token_obtain_pair'
- ),
- path(
- 'api/v1/auth/token/refresh/',
- TokenRefreshView.as_view(),
- name='token_refresh'
- ),
- path(
- 'api/v1/auth/token/verify/',
- TokenVerifyView.as_view(),
- name='token_verify'
- ),
+ path('', include(tf_urls)),
+ path('', include('account.urls', namespace='account'))
]
urlpatterns += [
diff --git a/logs_collector/templates/theme_swither.html b/logs_collector/templates/theme_swither.html
new file mode 100644
index 0000000..a81fedd
--- /dev/null
+++ b/logs_collector/templates/theme_swither.html
@@ -0,0 +1,91 @@
+
+
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
diff --git a/logs_collector/templates/two_factor/_base.html b/logs_collector/templates/two_factor/_base.html
new file mode 100644
index 0000000..b494774
--- /dev/null
+++ b/logs_collector/templates/two_factor/_base.html
@@ -0,0 +1,42 @@
+{% load static %}
+
+
+
+ {% include 'collector/includes/metalinks.html' %}
+
Collector - {% block title %}{% endblock %}
+ {% block extra_media %}{% endblock %}
+
+
+
+
+
+ {% block 'navigation' %}{% endblock 'navigation' %}
+
+
+
+
+
+ {% block content_wrapper %}
+
+
{% block content %}{% endblock %}
+
+ {% endblock %}
+
+
+
+
+
+
+
+
+
diff --git a/logs_collector/templates/two_factor/_base_focus.html b/logs_collector/templates/two_factor/_base_focus.html
new file mode 100644
index 0000000..bb2b776
--- /dev/null
+++ b/logs_collector/templates/two_factor/_base_focus.html
@@ -0,0 +1,11 @@
+{% extends "two_factor/_base.html" %}
+
+{% block content_wrapper %}
+
+
+
+ {% block content %}{% endblock %}
+
+
+
+{% endblock %}
diff --git a/logs_collector/templates/two_factor/_wizard_actions.html b/logs_collector/templates/two_factor/_wizard_actions.html
new file mode 100644
index 0000000..fac484e
--- /dev/null
+++ b/logs_collector/templates/two_factor/_wizard_actions.html
@@ -0,0 +1,14 @@
+{% load i18n %}
+
+{% if cancel_url %}
+
{% trans "Cancel" %}
+{% endif %}
+{% if wizard.steps.prev %}
+
+{% else %}
+
+{% endif %}
+
diff --git a/logs_collector/templates/two_factor/_wizard_forms.html b/logs_collector/templates/two_factor/_wizard_forms.html
new file mode 100644
index 0000000..b5fd2bf
--- /dev/null
+++ b/logs_collector/templates/two_factor/_wizard_forms.html
@@ -0,0 +1,5 @@
+{% load crispy_forms_tags %}
+
+ {{ wizard.management_form }}
+ {{ wizard.form|crispy }}
+
diff --git a/logs_collector/templates/two_factor/core/backup_tokens.html b/logs_collector/templates/two_factor/core/backup_tokens.html
new file mode 100644
index 0000000..66af9d2
--- /dev/null
+++ b/logs_collector/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 %}
+
+
+{% endblock %}
diff --git a/logs_collector/templates/two_factor/core/login.html b/logs_collector/templates/two_factor/core/login.html
new file mode 100644
index 0000000..2e5ce34
--- /dev/null
+++ b/logs_collector/templates/two_factor/core/login.html
@@ -0,0 +1,57 @@
+{% extends "two_factor/_base_focus.html" %}
+{% load i18n %}
+{% load two_factor_tags %}
+
+{% block extra_media %}
+ {{ form.media }}
+{% endblock %}
+
+{% block content %}
+
{% block title %}{% trans "Login" %}{% endblock %}
+
+ {% if wizard.steps.current == 'auth' %}
+
{% blocktrans %}Enter your credentials.{% endblocktrans %}
+ {% elif wizard.steps.current == 'token' %}
+
{{ device|as_verbose_action }}
+ {% 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 %}
+
+
+
+ {% block 'backup_tokens' %}
+ {% if backup_tokens %}
+
+
+ {% endif %}
+ {% endblock %}
+{% endblock %}
diff --git a/logs_collector/templates/two_factor/core/otp_required.html b/logs_collector/templates/two_factor/core/otp_required.html
new file mode 100644
index 0000000..242942a
--- /dev/null
+++ b/logs_collector/templates/two_factor/core/otp_required.html
@@ -0,0 +1,20 @@
+{% 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 %}
+
+ {% trans "Go back" %}
+
+ {% trans "Enable Two-Factor Authentication" %}
+
+{% endblock %}
diff --git a/logs_collector/templates/two_factor/core/phone_register.html b/logs_collector/templates/two_factor/core/phone_register.html
new file mode 100644
index 0000000..3bbecc1
--- /dev/null
+++ b/logs_collector/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 %}
+
+
+{% endblock %}
diff --git a/logs_collector/templates/two_factor/core/setup.html b/logs_collector/templates/two_factor/core/setup.html
new file mode 100644
index 0000000..1596fee
--- /dev/null
+++ b/logs_collector/templates/two_factor/core/setup.html
@@ -0,0 +1,64 @@
+{% extends "two_factor/_base_focus.html" %}
+{% load i18n %}
+
+{% block extra_media %}
+ {{ form.media }}
+{% endblock %}
+
+{% 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.{% endblocktrans %}
+
+
{% blocktrans trimmed %}Alternatively you can use the following secret to
+ setup TOTP in your authenticator or password manager manually.{% endblocktrans %}
+
{% translate "TOTP Secret:" %} {{ secret_key }}
+
{% blocktrans %}Then, enter the token generated by the app.{% endblocktrans %}
+
+ {% 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 %}
+
{% 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 %}
+ {% 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 %}
+
+
+{% endblock %}
diff --git a/logs_collector/templates/two_factor/core/setup_complete.html b/logs_collector/templates/two_factor/core/setup_complete.html
new file mode 100644
index 0000000..64b2088
--- /dev/null
+++ b/logs_collector/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/logs_collector/templates/two_factor/profile/disable.html b/logs_collector/templates/two_factor/profile/disable.html
new file mode 100644
index 0000000..d1a92b8
--- /dev/null
+++ b/logs_collector/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 %}
+
+{% endblock %}
diff --git a/logs_collector/templates/two_factor/profile/profile.html b/logs_collector/templates/two_factor/profile/profile.html
new file mode 100644
index 0000000..0527ec8
--- /dev/null
+++ b/logs_collector/templates/two_factor/profile/profile.html
@@ -0,0 +1,62 @@
+{% extends "two_factor/_base.html" %}
+{% load i18n %}
+{% load two_factor_tags %}
+
+{% block 'navigation' %}
+ {% include 'collector/includes/navigation.html' %}
+{% endblock 'navigation' %}
+
+{% block content %}
+
{% block title %}{% trans "Account Security" %}{% endblock %}
+
+ {% if default_device %}
+
{% blocktrans with primary=default_device|as_action %}Primary method: {{ primary }}{% endblocktrans %}
+
+ {% 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|as_action }}
+
+
+ {% 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/logs_collector/templates/two_factor/twilio/press_a_key.xml b/logs_collector/templates/two_factor/twilio/press_a_key.xml
new file mode 100644
index 0000000..85a3619
--- /dev/null
+++ b/logs_collector/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/logs_collector/templates/two_factor/twilio/sms_message.html b/logs_collector/templates/two_factor/twilio/sms_message.html
new file mode 100644
index 0000000..822cfde
--- /dev/null
+++ b/logs_collector/templates/two_factor/twilio/sms_message.html
@@ -0,0 +1,5 @@
+{% load i18n %}
+{% blocktrans trimmed %}
+ Your OTP token is {{ token }}
+{% endblocktrans %}
+
diff --git a/logs_collector/templates/two_factor/twilio/token.xml b/logs_collector/templates/two_factor/twilio/token.xml
new file mode 100644
index 0000000..4eafffe
--- /dev/null
+++ b/logs_collector/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/poetry.lock b/poetry.lock
index 7afdfcf..13b9e8a 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -35,6 +35,17 @@ docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-
tests = ["attrs[tests-no-zope]", "zope-interface"]
tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"]
+[[package]]
+name = "colorama"
+version = "0.4.6"
+description = "Cross-platform colored terminal text."
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
+files = [
+ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
+ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
+]
+
[[package]]
name = "crispy-bootstrap5"
version = "0.7"
@@ -128,6 +139,82 @@ files = [
[package.dependencies]
Django = ">=3.2"
+[[package]]
+name = "django-formtools"
+version = "2.4.1"
+description = "A set of high-level abstractions for Django forms"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "django-formtools-2.4.1.tar.gz", hash = "sha256:21f8d5dac737f1e636fa8a0a10969c1c32f525a6dfa27c29592827ba70d9643a"},
+ {file = "django_formtools-2.4.1-py3-none-any.whl", hash = "sha256:49ea8a64ddef4728a558bf5f8f622c0f4053b979edcf193bf00dd80432ab2f12"},
+]
+
+[package.dependencies]
+Django = ">=3.2"
+
+[[package]]
+name = "django-otp"
+version = "1.2.2"
+description = "A pluggable framework for adding two-factor authentication to Django using one-time passwords."
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "django_otp-1.2.2-py3-none-any.whl", hash = "sha256:90765d5dac238a719f9550ac05681dd6307f513a81a10b6adb879b4abc6bc1a3"},
+ {file = "django_otp-1.2.2.tar.gz", hash = "sha256:007a6354dabb3a1a54574bf73abf045ebbde0bb8734a38e2ed7845ba450f345e"},
+]
+
+[package.dependencies]
+django = ">=3.2"
+
+[package.extras]
+qrcode = ["qrcode"]
+
+[[package]]
+name = "django-phonenumber-field"
+version = "6.4.0"
+description = "An international phone number field for django models."
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "django-phonenumber-field-6.4.0.tar.gz", hash = "sha256:72a3e7a3e7493bf2a12c07a3bc77ce89813acc16592bf04d0eee3b5a452097ed"},
+ {file = "django_phonenumber_field-6.4.0-py3-none-any.whl", hash = "sha256:a31b4f05ac0ff898661516c84940f83adb5cdcf0ae4b9b1d31bb8ad3ff345b58"},
+]
+
+[package.dependencies]
+Django = ">=3.2"
+
+[package.extras]
+phonenumbers = ["phonenumbers (>=7.0.2)"]
+phonenumberslite = ["phonenumberslite (>=7.0.2)"]
+
+[[package]]
+name = "django-two-factor-auth"
+version = "1.15.3"
+description = "Complete Two-Factor Authentication for Django"
+optional = false
+python-versions = "*"
+files = [
+ {file = "django-two-factor-auth-1.15.3.tar.gz", hash = "sha256:311b7f0c5ee47ae5c3734f7810f90c9390b3aef556f58a767b0d80d6b54013fb"},
+ {file = "django_two_factor_auth-1.15.3-py3-none-any.whl", hash = "sha256:a5752732225304ba0461ec6f5347def517bdde98685e90bb7879aa066c91f3a4"},
+]
+
+[package.dependencies]
+Django = ">=3.2"
+django-formtools = "*"
+django-otp = ">=0.8.0"
+django-phonenumber-field = ">=1.1.0,<7"
+phonenumberslite = {version = ">=7.0.9,<8.99", optional = true, markers = "extra == \"phonenumberslite\""}
+qrcode = ">=4.0.0,<7.99"
+
+[package.extras]
+call = ["twilio (>=6.0)"]
+phonenumbers = ["phonenumbers (>=7.0.9,<8.99)"]
+phonenumberslite = ["phonenumberslite (>=7.0.9,<8.99)"]
+sms = ["twilio (>=6.0)"]
+webauthn = ["pydantic (>=1.9.0,<1.99)", "webauthn (>=1.6.0,<1.99)"]
+yubikey = ["django-otp-yubikey"]
+
[[package]]
name = "djangorestframework"
version = "3.14.0"
@@ -278,6 +365,17 @@ files = [
{file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"},
]
+[[package]]
+name = "phonenumberslite"
+version = "8.13.18"
+description = "Python version of Google's common library for parsing, formatting, storing and validating international phone numbers."
+optional = false
+python-versions = "*"
+files = [
+ {file = "phonenumberslite-8.13.18-py2.py3-none-any.whl", hash = "sha256:40cef03b24f2bc5711fed2b53b72770ff58f6b7dbfff749822c91078d6e82481"},
+ {file = "phonenumberslite-8.13.18.tar.gz", hash = "sha256:a321f0decf3e4e080f005fda3fba5a791d9d14a3ca217974345ff452923c31e2"},
+]
+
[[package]]
name = "pycodestyle"
version = "2.10.0"
@@ -317,6 +415,17 @@ dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pyte
docs = ["sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"]
tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"]
+[[package]]
+name = "pypng"
+version = "0.20220715.0"
+description = "Pure Python library for saving and loading PNG images"
+optional = false
+python-versions = "*"
+files = [
+ {file = "pypng-0.20220715.0-py3-none-any.whl", hash = "sha256:4a43e969b8f5aaafb2a415536c1a8ec7e341cd6a3f957fd5b5f32a4cfeed902c"},
+ {file = "pypng-0.20220715.0.tar.gz", hash = "sha256:739c433ba96f078315de54c0db975aee537cbc3e1d0ae4ed9aab0ca1e427e2c1"},
+]
+
[[package]]
name = "pytz"
version = "2023.3"
@@ -377,6 +486,29 @@ files = [
{file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"},
]
+[[package]]
+name = "qrcode"
+version = "7.4.2"
+description = "QR Code image generator"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "qrcode-7.4.2-py3-none-any.whl", hash = "sha256:581dca7a029bcb2deef5d01068e39093e80ef00b4a61098a2182eac59d01643a"},
+ {file = "qrcode-7.4.2.tar.gz", hash = "sha256:9dd969454827e127dbd93696b20747239e6d540e082937c90f14ac95b30f5845"},
+]
+
+[package.dependencies]
+colorama = {version = "*", markers = "platform_system == \"Windows\""}
+pypng = "*"
+typing-extensions = "*"
+
+[package.extras]
+all = ["pillow (>=9.1.0)", "pytest", "pytest-cov", "tox", "zest.releaser[recommended]"]
+dev = ["pytest", "pytest-cov", "tox"]
+maintainer = ["zest.releaser[recommended]"]
+pil = ["pillow (>=9.1.0)"]
+test = ["coverage", "pytest"]
+
[[package]]
name = "referencing"
version = "0.30.2"
@@ -550,4 +682,4 @@ files = [
[metadata]
lock-version = "2.0"
python-versions = "^3.10"
-content-hash = "fd9080bb86f6f84b684d431507eaaf41306e13fbb4f88f07f2a7c82f8823b728"
+content-hash = "69aa50d072f03697f4d11a333694d01f2528e433185216e3dc8d32b2debe7432"
diff --git a/pyproject.toml b/pyproject.toml
index bef25e9..d568974 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -18,6 +18,7 @@ crispy-bootstrap5 = "^0.7"
markdown = "^3.4.4"
django-filter = "^23.2"
drf-spectacular = "^0.26.4"
+django-two-factor-auth = {extras = ["phonenumberslite"], version = "^1.15.3"}
[tool.poetry.group.dev.dependencies]
flake8 = "^6.0.0"