Merge branch 'two-factor-auth' into 'development'

Draft: Two factor auth

See merge request sonny/newsreader!38
This commit is contained in:
sonny 2021-10-07 17:20:40 +00:00
commit 9388784ea1
13 changed files with 251 additions and 12 deletions

View file

@ -1,4 +1,4 @@
FROM python:3.7-buster FROM python:3.9-slim
RUN pip install poetry RUN pip install poetry

116
poetry.lock generated
View file

@ -192,7 +192,7 @@ importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
name = "colorama" name = "colorama"
version = "0.4.4" version = "0.4.4"
description = "Cross-platform colored terminal text." description = "Cross-platform colored terminal text."
category = "dev" category = "main"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
@ -298,6 +298,17 @@ python-versions = "*"
[package.dependencies] [package.dependencies]
six = ">=1.2" six = ">=1.2"
[[package]]
name = "django-formtools"
version = "2.3"
description = "A set of high-level abstractions for Django forms"
category = "main"
optional = false
python-versions = ">=3.6"
[package.dependencies]
Django = ">=2.2"
[[package]] [[package]]
name = "django-ipware" name = "django-ipware"
version = "4.0.0" version = "4.0.0"
@ -306,6 +317,35 @@ category = "main"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
[[package]]
name = "django-otp"
version = "1.0.6"
description = "A pluggable framework for adding two-factor authentication to Django using one-time passwords."
category = "main"
optional = false
python-versions = "*"
[package.dependencies]
django = ">=2.2"
[package.extras]
qrcode = ["qrcode"]
[[package]]
name = "django-phonenumber-field"
version = "5.2.0"
description = "An international phone number field for django models."
category = "main"
optional = false
python-versions = ">=3.6"
[package.dependencies]
Django = ">=2.2"
[package.extras]
phonenumbers = ["phonenumbers (>=7.0.2)"]
phonenumberslite = ["phonenumberslite (>=7.0.2)"]
[[package]] [[package]]
name = "django-registration-redux" name = "django-registration-redux"
version = "2.9" version = "2.9"
@ -329,6 +369,29 @@ pytz = "*"
[package.extras] [package.extras]
rest_framework = ["djangorestframework (>=3.0.0)"] rest_framework = ["djangorestframework (>=3.0.0)"]
[[package]]
name = "django-two-factor-auth"
version = "1.13.1"
description = "Complete Two-Factor Authentication for Django"
category = "main"
optional = false
python-versions = "*"
[package.dependencies]
Django = ">=2.2"
django-formtools = "*"
django-otp = ">=0.8.0"
django-phonenumber-field = ">=1.1.0,<6"
phonenumberslite = {version = ">=7.0.9,<8.99", optional = true, markers = "extra == \"phonenumberslite\""}
qrcode = ">=4.0.0,<6.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)"]
yubikey = ["django-otp-yubikey"]
[[package]] [[package]]
name = "djangorestframework" name = "djangorestframework"
version = "3.12.4" version = "3.12.4"
@ -575,6 +638,14 @@ python-versions = ">=3.6"
[package.dependencies] [package.dependencies]
pyparsing = ">=2.0.2" pyparsing = ">=2.0.2"
[[package]]
name = "phonenumberslite"
version = "8.12.31"
description = "Python version of Google's common library for parsing, formatting, storing and validating international phone numbers."
category = "main"
optional = false
python-versions = "*"
[[package]] [[package]]
name = "psycopg2-binary" name = "psycopg2-binary"
version = "2.9.1" version = "2.9.1"
@ -655,6 +726,24 @@ category = "main"
optional = false optional = false
python-versions = "*" python-versions = "*"
[[package]]
name = "qrcode"
version = "6.1"
description = "QR Code image generator"
category = "main"
optional = false
python-versions = "*"
[package.dependencies]
colorama = {version = "*", markers = "platform_system == \"Windows\""}
six = "*"
[package.extras]
dev = ["tox", "pytest", "mock"]
maintainer = ["zest.releaser"]
pil = ["pillow"]
test = ["pytest", "pytest-cov", "mock"]
[[package]] [[package]]
name = "requests" name = "requests"
version = "2.26.0" version = "2.26.0"
@ -1007,10 +1096,22 @@ django-extensions = [
{file = "django-extensions-2.2.9.tar.gz", hash = "sha256:2f81b618ba4d1b0e58603e25012e5c74f88a4b706e0022a3b21f24f0322a6ce6"}, {file = "django-extensions-2.2.9.tar.gz", hash = "sha256:2f81b618ba4d1b0e58603e25012e5c74f88a4b706e0022a3b21f24f0322a6ce6"},
{file = "django_extensions-2.2.9-py2.py3-none-any.whl", hash = "sha256:b19182d101a441fe001c5753553a901e2ef3ff60e8fbbe38881eb4a61fdd17c4"}, {file = "django_extensions-2.2.9-py2.py3-none-any.whl", hash = "sha256:b19182d101a441fe001c5753553a901e2ef3ff60e8fbbe38881eb4a61fdd17c4"},
] ]
django-formtools = [
{file = "django-formtools-2.3.tar.gz", hash = "sha256:9663b6eca64777b68d6d4142efad8597fe9a685924673b25aa8a1dcff4db00c3"},
{file = "django_formtools-2.3-py3-none-any.whl", hash = "sha256:4699937e19ee041d803943714fe0c1c7ad4cab802600eb64bbf4cdd0a1bfe7d9"},
]
django-ipware = [ django-ipware = [
{file = "django-ipware-4.0.0.tar.gz", hash = "sha256:1294f916f3b3475e40e1b0ec1bd320aa2397978eae672721c81cbc2ed517e9ee"}, {file = "django-ipware-4.0.0.tar.gz", hash = "sha256:1294f916f3b3475e40e1b0ec1bd320aa2397978eae672721c81cbc2ed517e9ee"},
{file = "django_ipware-4.0.0-py2.py3-none-any.whl", hash = "sha256:116bd0d7940f09bf7ffd465943992e23d87e772a9d6c0d3a57b74040589a383b"}, {file = "django_ipware-4.0.0-py2.py3-none-any.whl", hash = "sha256:116bd0d7940f09bf7ffd465943992e23d87e772a9d6c0d3a57b74040589a383b"},
] ]
django-otp = [
{file = "django-otp-1.0.6.tar.gz", hash = "sha256:0d56dd2a7fbb6ee6e54557e036ca64add0bd3596f471794bad673b7637d5e935"},
{file = "django_otp-1.0.6-py3-none-any.whl", hash = "sha256:01b5888f0bde5125e139433aacb947e52d5c406fa56c9db43c3e8d75b5c323c4"},
]
django-phonenumber-field = [
{file = "django-phonenumber-field-5.2.0.tar.gz", hash = "sha256:52b2e5970133ec5ab701218b802f7ab237229854dc95fd239b7e9e77dc43731d"},
{file = "django_phonenumber_field-5.2.0-py3-none-any.whl", hash = "sha256:5547fb2b2cc690a306ba77a5038419afc8fa8298a486fb7895008e9067cc7e75"},
]
django-registration-redux = [ django-registration-redux = [
{file = "django-registration-redux-2.9.tar.gz", hash = "sha256:e3d123354a1b8cbfa005d60f1ebb89ae8541f3eaffd6174d9f2aff529b57e430"}, {file = "django-registration-redux-2.9.tar.gz", hash = "sha256:e3d123354a1b8cbfa005d60f1ebb89ae8541f3eaffd6174d9f2aff529b57e430"},
{file = "django_registration_redux-2.9-py2.py3-none-any.whl", hash = "sha256:e94b8a945e1cbfa9ec6c32b549597270405328d4e26651985d287d0211120691"}, {file = "django_registration_redux-2.9-py2.py3-none-any.whl", hash = "sha256:e94b8a945e1cbfa9ec6c32b549597270405328d4e26651985d287d0211120691"},
@ -1019,6 +1120,10 @@ django-timezone-field = [
{file = "django-timezone-field-4.2.1.tar.gz", hash = "sha256:97780cde658daa5094ae515bb55ca97c1352928ab554041207ad515dee3fe971"}, {file = "django-timezone-field-4.2.1.tar.gz", hash = "sha256:97780cde658daa5094ae515bb55ca97c1352928ab554041207ad515dee3fe971"},
{file = "django_timezone_field-4.2.1-py3-none-any.whl", hash = "sha256:6dc782e31036a58da35b553bd00c70f112d794700025270d8a6a4c1d2e5b26c6"}, {file = "django_timezone_field-4.2.1-py3-none-any.whl", hash = "sha256:6dc782e31036a58da35b553bd00c70f112d794700025270d8a6a4c1d2e5b26c6"},
] ]
django-two-factor-auth = [
{file = "django-two-factor-auth-1.13.1.tar.gz", hash = "sha256:a20e03d256fd9fd668988545f052cedcc47e5a981888562e5e27d0bb83deae89"},
{file = "django_two_factor_auth-1.13.1-py2.py3-none-any.whl", hash = "sha256:d270d4288731233621a9462a89a8dfed2dcb86fa354125c816a89772d55f9e29"},
]
djangorestframework = [ djangorestframework = [
{file = "djangorestframework-3.12.4-py3-none-any.whl", hash = "sha256:6d1d59f623a5ad0509fe0d6bfe93cbdfe17b8116ebc8eda86d45f6e16e819aaf"}, {file = "djangorestframework-3.12.4-py3-none-any.whl", hash = "sha256:6d1d59f623a5ad0509fe0d6bfe93cbdfe17b8116ebc8eda86d45f6e16e819aaf"},
{file = "djangorestframework-3.12.4.tar.gz", hash = "sha256:f747949a8ddac876e879190df194b925c177cdeb725a099db1460872f7c0a7f2"}, {file = "djangorestframework-3.12.4.tar.gz", hash = "sha256:f747949a8ddac876e879190df194b925c177cdeb725a099db1460872f7c0a7f2"},
@ -1159,6 +1264,10 @@ packaging = [
{file = "packaging-21.0-py3-none-any.whl", hash = "sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14"}, {file = "packaging-21.0-py3-none-any.whl", hash = "sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14"},
{file = "packaging-21.0.tar.gz", hash = "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7"}, {file = "packaging-21.0.tar.gz", hash = "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7"},
] ]
phonenumberslite = [
{file = "phonenumberslite-8.12.31-py2.py3-none-any.whl", hash = "sha256:c593d2716dee6726f30d8e13c2fabf4b6d15551adfeb6a424c893c65686fb829"},
{file = "phonenumberslite-8.12.31.tar.gz", hash = "sha256:19ba2c15b0926707e670e58faafe80957344db4bae1479d74fa4ec34b3d8632a"},
]
psycopg2-binary = [ psycopg2-binary = [
{file = "psycopg2-binary-2.9.1.tar.gz", hash = "sha256:b0221ca5a9837e040ebf61f48899926b5783668b7807419e4adae8175a31f773"}, {file = "psycopg2-binary-2.9.1.tar.gz", hash = "sha256:b0221ca5a9837e040ebf61f48899926b5783668b7807419e4adae8175a31f773"},
{file = "psycopg2_binary-2.9.1-cp36-cp36m-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:c250a7ec489b652c892e4f0a5d122cc14c3780f9f643e1a326754aedf82d9a76"}, {file = "psycopg2_binary-2.9.1-cp36-cp36m-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:c250a7ec489b652c892e4f0a5d122cc14c3780f9f643e1a326754aedf82d9a76"},
@ -1217,6 +1326,10 @@ pytz = [
{file = "pytz-2021.1-py2.py3-none-any.whl", hash = "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798"}, {file = "pytz-2021.1-py2.py3-none-any.whl", hash = "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798"},
{file = "pytz-2021.1.tar.gz", hash = "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da"}, {file = "pytz-2021.1.tar.gz", hash = "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da"},
] ]
qrcode = [
{file = "qrcode-6.1-py2.py3-none-any.whl", hash = "sha256:3996ee560fc39532910603704c82980ff6d4d5d629f9c3f25f34174ce8606cf5"},
{file = "qrcode-6.1.tar.gz", hash = "sha256:505253854f607f2abf4d16092c61d4e9d511a3b4392e60bff957a68592b04369"},
]
requests = [ requests = [
{file = "requests-2.26.0-py2.py3-none-any.whl", hash = "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24"}, {file = "requests-2.26.0-py2.py3-none-any.whl", hash = "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24"},
{file = "requests-2.26.0.tar.gz", hash = "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"}, {file = "requests-2.26.0.tar.gz", hash = "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"},
@ -1260,6 +1373,7 @@ sentry-sdk = [
sgmllib3k = [ sgmllib3k = [
{file = "sgmllib3k-1.0.0.tar.gz", hash = "sha256:7868fb1c8bfa764c1ac563d3cf369c381d1325d36124933a726f29fcdaa812e9"}, {file = "sgmllib3k-1.0.0.tar.gz", hash = "sha256:7868fb1c8bfa764c1ac563d3cf369c381d1325d36124933a726f29fcdaa812e9"},
] ]
six = [ six = [
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},

View file

@ -26,6 +26,7 @@ python-dotenv = "^0.12.0"
sentry-sdk = {version = "^1.0.0", optional = true} sentry-sdk = {version = "^1.0.0", optional = true}
ftfy = "^5.8" ftfy = "^5.8"
requests_oauthlib = "^1.3.0" requests_oauthlib = "^1.3.0"
django-two-factor-auth = {extras = ["phonenumberslite"], version = "^1.13.1"}
[tool.poetry.extras] [tool.poetry.extras]
sentry = ["sentry_sdk"] sentry = ["sentry_sdk"]

View file

@ -1,17 +1,76 @@
{% extends "components/form/form.html" %} {% extends "components/form/form.html" %}
{% load i18n %} {% load i18n %}
{# TODO incorporate formtools wizard #}
{# TODO add support for other devices and backup tokens #}
{# see two_factor/templates/two_factor/core/login.html #}
{% block intro %}
<div class="form__intro">
{% if wizard.steps.current == 'token' %}
{% if device.method == 'call' %}
<p>
{% blocktrans trimmed %}
We are calling your phone right now, please enter the digits you hear.
{% endblocktrans %}
</p>
{% elif device.method == 'sms' %}
<p>
{% blocktrans trimmed %}
We sent you a text message, please enter the tokens we sent.
{% endblocktrans %}
</p>
{% else %}
<p>
{% blocktrans trimmed %}
Please enter the tokens generated by your token generator.
{% endblocktrans %}
</p>
{% endif %}
{% elif wizard.steps.current == 'backup' %}
<p>
{% 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 %}
</div>
{% endblock intro %}
{# TODO test this #}
{% block fields %}
{{ wizard.management_form }}
{{ block.super }}
{% endblock fields %}
{% block actions %} {% block actions %}
<section class="section form__section--last"> <section class="section form__section--last">
<fieldset class="fieldset form__fieldset"> <fieldset class="fieldset form__fieldset">
{% include "components/form/cancel-button.html" %} {% if cancel_url %}
{% include "components/form/cancel-button.html" %}
{% endif %}
{% if wizard.steps.prev %}
<button name="wizard_goto_step" type="submit" value="{{ wizard.steps.prev }}" class="link button">
{% trans "Back" %}
</button>
{% else %}
<button disabled name="" type="button" class="link button">
{% trans "Back" %}
</button>
{% endif %}
{% include "components/form/confirm-button.html" %} {% include "components/form/confirm-button.html" %}
</fieldset> </fieldset>
<fieldset class="fieldset form__fieldset"> {% if wizard.steps.index == wizard.steps.first %}
<a class="link" href="{% url 'accounts:password-reset' %}"> <fieldset class="fieldset form__fieldset">
<small class="small">{% trans "I forgot my password" %}</small> <a class="link" href="{% url 'accounts:password-reset' %}">
</a> <small class="small">{% trans "I forgot my password" %}</small>
</fieldset> </a>
</fieldset>
{% endif %}
</section> </section>
{% endblock actions %} {% endblock actions %}

View file

@ -2,6 +2,6 @@
{% block content %} {% block content %}
<main id="login--page" class="main"> <main id="login--page" class="main">
{% include "accounts/components/login-form.html" with form=form title="Login" confirm_text="Login" %} {% include "accounts/components/login-form.html" with title="Login" confirm_text="Next" %}
</main> </main>
{% endblock %} {% endblock %}

View file

@ -1,6 +1,17 @@
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.urls import include, path from django.urls import include, path
from two_factor.views import (
BackupTokensView,
DisableView,
LoginView,
PhoneDeleteView,
PhoneSetupView,
ProfileView,
QRGeneratorView,
SetupCompleteView,
)
from newsreader.accounts.views import ( from newsreader.accounts.views import (
ActivationCompleteView, ActivationCompleteView,
ActivationResendView, ActivationResendView,
@ -21,6 +32,7 @@ from newsreader.accounts.views import (
RegistrationCompleteView, RegistrationCompleteView,
RegistrationView, RegistrationView,
SettingsView, SettingsView,
SetupView,
TwitterAuthRedirectView, TwitterAuthRedirectView,
TwitterRevokeRedirectView, TwitterRevokeRedirectView,
TwitterTemplateView, TwitterTemplateView,
@ -67,8 +79,28 @@ settings_patterns = [
path("", login_required(SettingsView.as_view()), name="home"), path("", login_required(SettingsView.as_view()), name="home"),
] ]
two_factor = [
path("accounts/setup/", SetupView.as_view(), name="setup"),
path("accounts/qrcode/", QRGeneratorView.as_view(), name="qr"),
path(
"accounts/setup/complete/", SetupCompleteView.as_view(), name="setup_complete"
),
path("accounts/backup/tokens/", BackupTokensView.as_view(), name="backup_tokens"),
path(
"accounts/backup/phone/register/", PhoneSetupView.as_view(), name="phone_create"
),
path(
"accounts/backup/phone/unregister/<int:pk>/",
PhoneDeleteView.as_view(),
name="phone_delete",
),
path("accounts/profile/", ProfileView.as_view(), name="profile"),
path("accounts/disable/", DisableView.as_view(), name="disable"),
]
urlpatterns = [ urlpatterns = [
# Auth # Auth
path("", include((two_factor, "two_factor"))),
path("login/", LoginView.as_view(), name="login"), path("login/", LoginView.as_view(), name="login"),
path("logout/", LogoutView.as_view(), name="logout"), path("logout/", LogoutView.as_view(), name="logout"),
# Register # Register

View file

@ -1,4 +1,4 @@
from newsreader.accounts.views.auth import LoginView, LogoutView from newsreader.accounts.views.auth import LoginView, LogoutView, SetupView
from newsreader.accounts.views.favicon import FaviconRedirectView from newsreader.accounts.views.favicon import FaviconRedirectView
from newsreader.accounts.views.integrations import ( from newsreader.accounts.views.integrations import (
IntegrationsView, IntegrationsView,

View file

@ -1,11 +1,30 @@
from django.contrib.auth import views as django_views from django.contrib.auth import views as django_views
from django.shortcuts import redirect
from django.urls import reverse_lazy from django.urls import reverse_lazy
from two_factor.views.core import LoginView as TwoFactorLoginView
from two_factor.views.core import SetupView as TwoFactorSetupView
class LoginView(django_views.LoginView):
class LoginView(TwoFactorLoginView):
redirect_authenticated_user = True
template_name = "accounts/views/login.html" template_name = "accounts/views/login.html"
success_url = reverse_lazy("index")
def done(self, form_list, **kwargs):
response = super().done(form_list, **kwargs)
user = self.get_user()
if not user.phonedevice_set.exists():
return redirect("accounts:two_factor:setup")
return response
class LogoutView(django_views.LogoutView): class LogoutView(django_views.LogoutView):
next_page = reverse_lazy("accounts:login") next_page = reverse_lazy("accounts:login")
class SetupView(TwoFactorSetupView):
success_url = "accounts:two_factor:setup_complete"
qrcode_url = "accounts:two_factor:qr"

View file

@ -42,6 +42,10 @@ INSTALLED_APPS = [
"django_celery_beat", "django_celery_beat",
"registration", "registration",
"axes", "axes",
"django_otp",
"django_otp.plugins.otp_static",
"django_otp.plugins.otp_totp",
"two_factor",
# app modules # app modules
"newsreader.accounts", "newsreader.accounts",
"newsreader.utils", "newsreader.utils",
@ -61,6 +65,7 @@ MIDDLEWARE = [
"django.middleware.common.CommonMiddleware", "django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware", "django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware",
"django_otp.middleware.OTPMiddleware",
"django.contrib.messages.middleware.MessageMiddleware", "django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware",
"axes.middleware.AxesMiddleware", "axes.middleware.AxesMiddleware",
@ -182,6 +187,7 @@ AUTH_PASSWORD_VALIDATORS = [
# Authentication user model # Authentication user model
AUTH_USER_MODEL = "accounts.User" AUTH_USER_MODEL = "accounts.User"
LOGIN_URL = "accounts:login"
LOGIN_REDIRECT_URL = "/" LOGIN_REDIRECT_URL = "/"
# Internationalization # Internationalization
@ -253,6 +259,7 @@ SWAGGER_SETTINGS = {
# https://docs.celeryproject.org/en/stable/userguide/configuration.html # https://docs.celeryproject.org/en/stable/userguide/configuration.html
CELERY_WORKER_HIJACK_ROOT_LOGGER = False CELERY_WORKER_HIJACK_ROOT_LOGGER = False
# Registration
REGISTRATION_OPEN = True REGISTRATION_OPEN = True
REGISTRATION_AUTO_LOGIN = True REGISTRATION_AUTO_LOGIN = True
ACCOUNT_ACTIVATION_DAYS = 7 ACCOUNT_ACTIVATION_DAYS = 7

View file

@ -2,6 +2,8 @@ from .base import * # isort:skip
from .version import get_current_version from .version import get_current_version
LOGGING.update({"loggers": {"two_factor": {"handlers": ["console"], "level": "INFO"}}})
SECRET_KEY = "mv4&5#+)-=abz3^&1r^nk_ca6y54--p(4n4cg%z*g&rb64j%wl" SECRET_KEY = "mv4&5#+)-=abz3^&1r^nk_ca6y54--p(4n4cg%z*g&rb64j%wl"
MIDDLEWARE += ["debug_toolbar.middleware.DebugToolbarMiddleware"] MIDDLEWARE += ["debug_toolbar.middleware.DebugToolbarMiddleware"]

View file

@ -2,6 +2,8 @@ from .base import * # isort:skip
from .version import get_current_version from .version import get_current_version
LOGGING.update({"loggers": {"two_factor": {"handlers": ["console"], "level": "INFO"}}})
SECRET_KEY = "=q(ztyo)b6noom#a164g&s9vcj1aawa^g#ing_ir99=_zl4g&$" SECRET_KEY = "=q(ztyo)b6noom#a164g&s9vcj1aawa^g#ing_ir99=_zl4g&$"
INSTALLED_APPS += ["debug_toolbar", "django_extensions"] INSTALLED_APPS += ["debug_toolbar", "django_extensions"]

View file

@ -5,6 +5,7 @@ from django.urls import include, path
from drf_yasg import openapi from drf_yasg import openapi
from drf_yasg.views import get_schema_view from drf_yasg.views import get_schema_view
from two_factor.admin import AdminSiteOTPRequired
from newsreader.accounts.urls import urlpatterns as login_urls from newsreader.accounts.urls import urlpatterns as login_urls
from newsreader.news.core.views import NewsView from newsreader.news.core.views import NewsView
@ -17,6 +18,8 @@ api_patterns = [path("api/", include((news_endpoints, "news")))]
schema_info = openapi.Info(title="Newsreader API", default_version="v1") schema_info = openapi.Info(title="Newsreader API", default_version="v1")
schema_view = get_schema_view(schema_info, patterns=api_patterns) schema_view = get_schema_view(schema_info, patterns=api_patterns)
admin.site.__class__ = AdminSiteOTPRequired
urlpatterns = [ urlpatterns = [
path("", login_required(NewsView.as_view()), name="index"), path("", login_required(NewsView.as_view()), name="index"),
path("", include((news_patterns, "news"))), path("", include((news_patterns, "news"))),