Add TOTP
This commit is contained in:
parent
fcff91d027
commit
c95e097f34
6 changed files with 161 additions and 12 deletions
139
poetry.lock
generated
139
poetry.lock
generated
|
@ -97,7 +97,7 @@ unicode_backport = ["unicodedata2"]
|
|||
name = "colorama"
|
||||
version = "0.4.4"
|
||||
description = "Cross-platform colored terminal text."
|
||||
category = "dev"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
|
||||
|
@ -200,13 +200,83 @@ python-versions = ">=3.6"
|
|||
Django = ">=2.2"
|
||||
sqlparse = ">=0.2.0"
|
||||
|
||||
[[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]]
|
||||
name = "django-otp"
|
||||
version = "1.1.3"
|
||||
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]]
|
||||
name = "django-two-factor-auth"
|
||||
version = "1.13"
|
||||
description = ""
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
develop = false
|
||||
|
||||
[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)"]
|
||||
sms = ["twilio (>=6.0)"]
|
||||
yubikey = ["django-otp-yubikey"]
|
||||
phonenumbers = ["phonenumbers (>=7.0.9,<8.99)"]
|
||||
phonenumberslite = ["phonenumberslite (>=7.0.9,<8.99)"]
|
||||
|
||||
[package.source]
|
||||
type = "git"
|
||||
url = "https://github.com/Bouke/django-two-factor-auth.git"
|
||||
reference = "ffe4422e"
|
||||
resolved_reference = "ffe4422e6f68bfb84ad2b44ca83c15abb0af5e7c"
|
||||
|
||||
[[package]]
|
||||
name = "filelock"
|
||||
version = "3.4.0"
|
||||
version = "3.4.2"
|
||||
description = "A platform independent file lock."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.extras]
|
||||
docs = ["furo (>=2021.8.17b43)", "sphinx (>=4.1)", "sphinx-autodoc-typehints (>=1.12)"]
|
||||
|
@ -340,6 +410,14 @@ python-versions = ">=3.6"
|
|||
[package.dependencies]
|
||||
pyparsing = ">=2.0.2,<3.0.5 || >3.0.5"
|
||||
|
||||
[[package]]
|
||||
name = "phonenumberslite"
|
||||
version = "8.12.40"
|
||||
description = "Python version of Google's common library for parsing, formatting, storing and validating international phone numbers."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "pillow"
|
||||
version = "8.4.0"
|
||||
|
@ -350,11 +428,11 @@ python-versions = ">=3.6"
|
|||
|
||||
[[package]]
|
||||
name = "platformdirs"
|
||||
version = "2.4.0"
|
||||
version = "2.4.1"
|
||||
description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.extras]
|
||||
docs = ["Sphinx (>=4)", "furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)"]
|
||||
|
@ -559,6 +637,24 @@ category = "dev"
|
|||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[[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]]
|
||||
name = "rcssmin"
|
||||
version = "1.1.0"
|
||||
|
@ -621,7 +717,7 @@ python-versions = ">=3.6.*"
|
|||
name = "six"
|
||||
version = "1.16.0"
|
||||
description = "Python 2 and 3 compatibility utilities"
|
||||
category = "dev"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||
|
||||
|
@ -748,7 +844,7 @@ multidict = ">=4.0"
|
|||
[metadata]
|
||||
lock-version = "1.1"
|
||||
python-versions = "^3.10"
|
||||
content-hash = "8ce7193640d549da552b67f5d485214f3e74931c801116164ad21614be32846e"
|
||||
content-hash = "30053d4662f7e86ae956249a199beeaed34b338fe420d09ad2db0ae4f9d2d8bd"
|
||||
|
||||
[metadata.files]
|
||||
asgiref = [
|
||||
|
@ -898,9 +994,22 @@ django-debug-toolbar = [
|
|||
{file = "django-debug-toolbar-3.2.4.tar.gz", hash = "sha256:644bbd5c428d3283aa9115722471769cac1bec189edf3a0c855fd8ff870375a9"},
|
||||
{file = "django_debug_toolbar-3.2.4-py3-none-any.whl", hash = "sha256:6b633b6cfee24f232d73569870f19aa86c819d750e7f3e833f2344a9eb4b4409"},
|
||||
]
|
||||
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-otp = [
|
||||
{file = "django-otp-1.1.3.tar.gz", hash = "sha256:f002c71d4ea7f514590be00492980d3c87397b73dc20542e1c4fc00b66f2dda1"},
|
||||
{file = "django_otp-1.1.3-py3-none-any.whl", hash = "sha256:8637be826c0465d0fd1710e4472efe9fc83883853a2141fefdbace9358d20003"},
|
||||
]
|
||||
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-two-factor-auth = []
|
||||
filelock = [
|
||||
{file = "filelock-3.4.0-py3-none-any.whl", hash = "sha256:2e139a228bcf56dd8b2274a65174d005c4a6b68540ee0bdbb92c76f43f29f7e8"},
|
||||
{file = "filelock-3.4.0.tar.gz", hash = "sha256:93d512b32a23baf4cac44ffd72ccf70732aeff7b8050fcaf6d3ec406d954baf4"},
|
||||
{file = "filelock-3.4.2-py3-none-any.whl", hash = "sha256:cf0fc6a2f8d26bd900f19bf33915ca70ba4dd8c56903eeb14e1e7a2fd7590146"},
|
||||
{file = "filelock-3.4.2.tar.gz", hash = "sha256:38b4f4c989f9d06d44524df1b24bd19e167d851f19b50bf3e3559952dddc5b80"},
|
||||
]
|
||||
gunicorn = [
|
||||
{file = "gunicorn-20.1.0-py3-none-any.whl", hash = "sha256:9dcc4547dbb1cb284accfb15ab5667a0e5d1881cc443e0677b4882a4067a807e"},
|
||||
|
@ -1083,6 +1192,10 @@ packaging = [
|
|||
{file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"},
|
||||
{file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"},
|
||||
]
|
||||
phonenumberslite = [
|
||||
{file = "phonenumberslite-8.12.40-py2.py3-none-any.whl", hash = "sha256:e6fe6cad1091f8928e34a98570cade4758f4cf4e70e9e32ff7eca517ce98e273"},
|
||||
{file = "phonenumberslite-8.12.40.tar.gz", hash = "sha256:153885eefec397058c8ce91fb987f55545b2cfa945f22a904584ddf162aa82b1"},
|
||||
]
|
||||
pillow = [
|
||||
{file = "Pillow-8.4.0-cp310-cp310-macosx_10_10_universal2.whl", hash = "sha256:81f8d5c81e483a9442d72d182e1fb6dcb9723f289a57e8030811bac9ea3fef8d"},
|
||||
{file = "Pillow-8.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3f97cfb1e5a392d75dd8b9fd274d205404729923840ca94ca45a0af57e13dbe6"},
|
||||
|
@ -1127,8 +1240,8 @@ pillow = [
|
|||
{file = "Pillow-8.4.0.tar.gz", hash = "sha256:b8e2f83c56e141920c39464b852de3719dfbfb6e3c99a2d8da0edf4fb33176ed"},
|
||||
]
|
||||
platformdirs = [
|
||||
{file = "platformdirs-2.4.0-py3-none-any.whl", hash = "sha256:8868bbe3c3c80d42f20156f22e7131d2fb321f5bc86a2a345375c6481a67021d"},
|
||||
{file = "platformdirs-2.4.0.tar.gz", hash = "sha256:367a5e80b3d04d2428ffa76d33f124cf11e8fff2acdaa9b43d545f5c7d661ef2"},
|
||||
{file = "platformdirs-2.4.1-py3-none-any.whl", hash = "sha256:1d7385c7db91728b83efd0ca99a5afb296cab9d0ed8313a45ed8ba17967ecfca"},
|
||||
{file = "platformdirs-2.4.1.tar.gz", hash = "sha256:440633ddfebcc36264232365d7840a970e75e1018d15b4327d11f91909045fda"},
|
||||
]
|
||||
pluggy = [
|
||||
{file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
|
||||
|
@ -1227,6 +1340,10 @@ pyyaml = [
|
|||
{file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"},
|
||||
{file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"},
|
||||
]
|
||||
qrcode = [
|
||||
{file = "qrcode-6.1-py2.py3-none-any.whl", hash = "sha256:3996ee560fc39532910603704c82980ff6d4d5d629f9c3f25f34174ce8606cf5"},
|
||||
{file = "qrcode-6.1.tar.gz", hash = "sha256:505253854f607f2abf4d16092c61d4e9d511a3b4392e60bff957a68592b04369"},
|
||||
]
|
||||
rcssmin = [
|
||||
{file = "rcssmin-1.1.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:2211a5c91ea14a5937b57904c9121f8bfef20987825e55368143da7d25446e3b"},
|
||||
{file = "rcssmin-1.1.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:7085d1b51dd2556f3aae03947380f6e9e1da29fb1eeadfa6766b7f105c54c9ff"},
|
||||
|
|
|
@ -22,6 +22,7 @@ django-debug-toolbar = "^3.2"
|
|||
whitenoise = {extras = ["brotli"], version = "^5.2.0"}
|
||||
rcssmin = "^1.0.6"
|
||||
django-csp = "^3.7"
|
||||
django-two-factor-auth = {extras = ["phonenumberslite"], git = "https://github.com/Bouke/django-two-factor-auth.git", rev = "ffe4422e"}
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
pre-commit = "^2.7"
|
||||
|
|
14
src/articles/static/login.css
Normal file
14
src/articles/static/login.css
Normal file
|
@ -0,0 +1,14 @@
|
|||
.d-none {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.float-right {
|
||||
float: right;
|
||||
}
|
||||
|
||||
td, th, tr, tbody, tr:nth-child(2n) {
|
||||
background-color: inherit;
|
||||
border: none;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
5
src/articles/templates/two_factor/_base.html
Normal file
5
src/articles/templates/two_factor/_base.html
Normal file
|
@ -0,0 +1,5 @@
|
|||
{% extends "articles/base.html" %}
|
||||
{% load static %}
|
||||
{% block append_css %}
|
||||
<link rel="stylesheet" href="{% static "login.css" %}">
|
||||
{% endblock %}
|
|
@ -71,6 +71,10 @@ INSTALLED_APPS = [
|
|||
"anymail",
|
||||
"django_cleanup.apps.CleanupConfig",
|
||||
"debug_toolbar",
|
||||
"django_otp",
|
||||
"django_otp.plugins.otp_static",
|
||||
"django_otp.plugins.otp_totp",
|
||||
"two_factor",
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
|
@ -82,6 +86,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",
|
||||
"csp.middleware.CSPMiddleware",
|
||||
|
@ -207,7 +212,12 @@ SHORTPIXEL_RESIZE_HEIGHT = int(os.getenv("SHORTPIXEL_RESIZE_HEIGHT", 10000))
|
|||
|
||||
GOATCOUNTER_DOMAIN = os.getenv("GOATCOUNTER_DOMAIN")
|
||||
|
||||
LOGIN_URL = "admin:login"
|
||||
LOGIN_URL = "two_factor:login"
|
||||
LOGIN_REDIRECT_URL = "two_factor:profile"
|
||||
LOGOUT_REDIRECT_URL = "articles-list"
|
||||
TWO_FACTOR_REMEMBER_COOKIE_AGE = 86400 * 30
|
||||
TWO_FACTOR_REMEMBER_COOKIE_SECURE = True
|
||||
TWO_FACTOR_REMEMBER_COOKIE_SAMESITE = "Strict"
|
||||
|
||||
SECURE_REFERRER_POLICY = "strict-origin-when-cross-origin"
|
||||
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
|
||||
|
|
|
@ -18,10 +18,12 @@ from django.conf.urls.static import static
|
|||
from django.contrib import admin
|
||||
from django.urls import include, path
|
||||
from django.views.generic import TemplateView
|
||||
from two_factor.urls import urlpatterns as tf_urls
|
||||
|
||||
from blog import settings
|
||||
|
||||
urlpatterns = [
|
||||
path("", include(tf_urls)),
|
||||
path(
|
||||
"robots.txt",
|
||||
TemplateView.as_view(
|
||||
|
|
Reference in a new issue