From eb9e8439bedc86599762b7ee3055a2223c89c697 Mon Sep 17 00:00:00 2001
From: Dani Hodovic
Date: Wed, 7 Aug 2024 18:20:01 -0300
Subject: [PATCH] feat: add support for MFA using TOTP and recovery codes
(#109)
---
README.md | 4 +-
.../templates/allauth/layouts/base.html | 1 +
allauth_ui/templates/components/form.html | 17 +-
.../templates/components/form_field.html | 12 +
allauth_ui/templates/mfa/index.html | 73 ++++++
.../mfa/recovery_codes/generate.html | 21 ++
.../templates/mfa/recovery_codes/index.html | 27 ++
.../templates/mfa/totp/activate_form.html | 31 +++
.../templates/mfa/totp/deactivate_form.html | 14 ++
.../usersessions/usersession_list.html | 2 +-
allauth_ui/templatetags/allauth_ui.py | 1 +
poetry.lock | 236 +++++++++++++++---
pyproject.toml | 4 +-
.../example/templates/example/base.html | 1 +
tests/settings.py | 3 +
15 files changed, 401 insertions(+), 46 deletions(-)
create mode 100644 allauth_ui/templates/components/form_field.html
create mode 100644 allauth_ui/templates/mfa/index.html
create mode 100644 allauth_ui/templates/mfa/recovery_codes/generate.html
create mode 100644 allauth_ui/templates/mfa/recovery_codes/index.html
create mode 100644 allauth_ui/templates/mfa/totp/activate_form.html
create mode 100644 allauth_ui/templates/mfa/totp/deactivate_form.html
diff --git a/README.md b/README.md
index ef4791c..dcff17f 100644
--- a/README.md
+++ b/README.md
@@ -22,7 +22,9 @@ defaults for new projects.
## Features
- π± Mobile-friendly design
-- π Configurable themes
+- π [Configurable themes](https://daisyui.com/docs/themes/)
+- π΅οΈ Support for [Allauth User Sessions](https://docs.allauth.org/en/latest/usersessions/index.html)
+- π±Support for [Multi-Factor Authentication](https://docs.allauth.org/en/latest/mfa/index.html)
- π£οΈ Translations
- πͺπΈ Spanish
- π«π· French
diff --git a/allauth_ui/templates/allauth/layouts/base.html b/allauth_ui/templates/allauth/layouts/base.html
index 6ef19aa..61331ae 100644
--- a/allauth_ui/templates/allauth/layouts/base.html
+++ b/allauth_ui/templates/allauth/layouts/base.html
@@ -1,3 +1,4 @@
+{# djlint:off H006 #}
{% load i18n %}
{% load static %}
{% load allauth_ui %}
diff --git a/allauth_ui/templates/components/form.html b/allauth_ui/templates/components/form.html
index 3b0c93e..3997aab 100644
--- a/allauth_ui/templates/components/form.html
+++ b/allauth_ui/templates/components/form.html
@@ -1,4 +1,6 @@
-{% load widget_tweaks slippers %}
+{% load slippers %}
+{% load allauth_ui %}
+{% load widget_tweaks %}
{% var render_fields=render_fields|default:"true" %}
{% var use_default_button=use_default_button|default:"true" %}
+ {% else %}
+ {% translate "An authenticator app is not active." %}
+ {% translate "Activate" %}
+ {% endif %}
+ {% endif %}
+ {% if "webauthn" in MFA_SUPPORTED_TYPES %}
+
+ {% translate "Security Keys" %}
+ {% if authenticators.webauthn|length %}
+
+ {% blocktranslate count count=authenticators.webauthn|length %}You have added {{ count }} security key.{% plural %}You have added {{ count }} security keys.{% endblocktranslate %}
+
+ {% translate "Manage" %}
+ {% else %}
+ {% translate "No security keys have been added." %}
+ {% translate "Add" %}
+ {% endif %}
+ {% endif %}
+ {% if "recovery_codes" in MFA_SUPPORTED_TYPES %}
+
+ {% with total_count=authenticators.recovery_codes.generate_codes|length unused_count=authenticators.recovery_codes.get_unused_codes|length %}
+ {% translate "Recovery Codes" %}
+
+ {% if authenticators.recovery_codes %}
+ {% blocktranslate count unused_count=unused_count %}There is {{ unused_count }} out of {{ total_count }} recovery codes available.{% plural %}There are {{ unused_count }} out of {{ total_count }} recovery codes available.{% endblocktranslate %}
+ {% else %}
+ {% translate "No recovery codes set up." %}
+ {% endif %}
+
+ {% if is_mfa_enabled %}
+
+ {% endif %}
+ {% endwith %}
+ {% endif %}
+ {% /container %}
+{% endblock content %}
diff --git a/allauth_ui/templates/mfa/recovery_codes/generate.html b/allauth_ui/templates/mfa/recovery_codes/generate.html
new file mode 100644
index 0000000..e577c8e
--- /dev/null
+++ b/allauth_ui/templates/mfa/recovery_codes/generate.html
@@ -0,0 +1,21 @@
+{% extends "mfa/recovery_codes/index.html" %}
+{% load i18n %}
+{% load allauth %}
+{% load allauth_ui %}
+{% block content %}
+ {% translate "Recovery Codes" as heading %}
+ {% blocktranslate asvar subheading %}You are about to generate a new set of recovery codes for your account.{% endblocktranslate %}
+ {% #container heading=heading subheading=subheading %}
+
+ {% if unused_code_count %}
+ {% blocktranslate %}This action will invalidate your existing codes.{% endblocktranslate %}
+ {% endif %}
+ {% blocktranslate %}Are you sure?{% endblocktranslate %}
+
+ {% url 'mfa_generate_recovery_codes' as action_url %}
+ {% trans "Generate" as button_text %}
+ {% #form url=action_url form=form button_text=button_text %}
+ {% csrf_token %}
+ {% /form %}
+ {% /container %}
+{% endblock content %}
diff --git a/allauth_ui/templates/mfa/recovery_codes/index.html b/allauth_ui/templates/mfa/recovery_codes/index.html
new file mode 100644
index 0000000..75dbac6
--- /dev/null
+++ b/allauth_ui/templates/mfa/recovery_codes/index.html
@@ -0,0 +1,27 @@
+{% extends "mfa/recovery_codes/index.html" %}
+{% load i18n %}
+{% load allauth %}
+{% load allauth_ui %}
+{% block content %}
+ {% translate "Recovery Codes" as heading %}
+ {% blocktranslate asvar subheading count unused_count=unused_codes|length %}There is {{ unused_count }} out of {{ total_count }} recovery codes available.{% plural %}There are {{ unused_count }} out of {{ total_count }} recovery codes available.{% endblocktranslate %}
+ {% #container heading=heading subheading=subheading %}
+
+
+
+
+
+ {% /container %}
+{% endblock content %}
diff --git a/allauth_ui/templates/mfa/totp/activate_form.html b/allauth_ui/templates/mfa/totp/activate_form.html
new file mode 100644
index 0000000..1bc78a8
--- /dev/null
+++ b/allauth_ui/templates/mfa/totp/activate_form.html
@@ -0,0 +1,31 @@
+{# djlint:off H006 #}
+{% extends "mfa/totp/activate_form.html" %}
+{% load allauth %}
+{% load allauth_ui %}
+{% load i18n %}
+{% block content %}
+ {% translate "Activate Authenticator App" as heading %}
+ {% blocktranslate asvar subheading %}To protect your account with two-factor authentication, scan the QR code below with your authenticator app. Then, input the verification code generated by the app below.{% endblocktranslate %}
+ {% #container heading=heading subheading=subheading %}
+ {% translate "Activate" as button_text %}
+ {% url 'mfa_activate_totp' as action_url %}
+ {% #form form=form method="post" url=action_url button_text=button_text render_fields="false" %}
+
+ {% #form_field field=form.code %}
+ {% /form_field %}
+
+
+
+ {% translate "You can store this secret and use it to reinstall your authenticator app at a later time." %}
+
+
+
+ {% csrf_token %}
+ {% /form %}
+ {% /container %}
+ {% endblock content %}
diff --git a/allauth_ui/templates/mfa/totp/deactivate_form.html b/allauth_ui/templates/mfa/totp/deactivate_form.html
new file mode 100644
index 0000000..f112596
--- /dev/null
+++ b/allauth_ui/templates/mfa/totp/deactivate_form.html
@@ -0,0 +1,14 @@
+{% extends "mfa/totp/deactivate_form.html" %}
+{% load i18n %}
+{% load allauth_ui %}
+{% block content %}
+ {% trans "Deactivate Authenticator App" as heading %}
+ {% blocktranslate asvar subheading %}You are about to deactivate authenticator app based authentication. Are you sure?{% endblocktranslate %}
+ {% #container heading=heading subheading=subheading %}
+ {% url 'mfa_deactivate_totp' as action_url %}
+ {% #form form=form url=action_url button_text=button_text use_default_button="false"%}
+ {% csrf_token %}
+
+ {% /form %}
+ {% /container %}
+{% endblock content %}
diff --git a/allauth_ui/templates/usersessions/usersession_list.html b/allauth_ui/templates/usersessions/usersession_list.html
index bb22100..001d5c9 100644
--- a/allauth_ui/templates/usersessions/usersession_list.html
+++ b/allauth_ui/templates/usersessions/usersession_list.html
@@ -9,7 +9,7 @@
{{ heading }}
{% if session_count > 1 %}
{% url 'usersessions_list' as action_url %}
- {% translate "Sign Out Other Sessions" as button_text %}
+ {% translate "Sign Out Other Sessions" as button_text %}
{% else %}
{% url 'account_logout' as action_url %}
{% translate "Sign Out" as button_text %}
diff --git a/allauth_ui/templatetags/allauth_ui.py b/allauth_ui/templatetags/allauth_ui.py
index 61f00e2..049b11b 100644
--- a/allauth_ui/templatetags/allauth_ui.py
+++ b/allauth_ui/templatetags/allauth_ui.py
@@ -14,6 +14,7 @@ def allauth_ui_theme():
{
"container": "components/container.html",
"form": "components/form.html",
+ "form_field": "components/form_field.html",
},
register,
)
diff --git a/poetry.lock b/poetry.lock
index 54a610a..8225032 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -110,6 +110,17 @@ files = [
[package.dependencies]
frozenlist = ">=1.1.0"
+[[package]]
+name = "appdirs"
+version = "1.4.4"
+description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
+optional = false
+python-versions = "*"
+files = [
+ {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"},
+ {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"},
+]
+
[[package]]
name = "appnope"
version = "0.1.4"
@@ -300,6 +311,70 @@ files = [
{file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"},
]
+[[package]]
+name = "cffi"
+version = "1.16.0"
+description = "Foreign Function Interface for Python calling C code."
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"},
+ {file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"},
+ {file = "cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673"},
+ {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896"},
+ {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684"},
+ {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7"},
+ {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614"},
+ {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743"},
+ {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d"},
+ {file = "cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a"},
+ {file = "cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1"},
+ {file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"},
+ {file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"},
+ {file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"},
+ {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"},
+ {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"},
+ {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"},
+ {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"},
+ {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"},
+ {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"},
+ {file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"},
+ {file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"},
+ {file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"},
+ {file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"},
+ {file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"},
+ {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"},
+ {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"},
+ {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"},
+ {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"},
+ {file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"},
+ {file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"},
+ {file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"},
+ {file = "cffi-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc"},
+ {file = "cffi-1.16.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0"},
+ {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b"},
+ {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c"},
+ {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b"},
+ {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324"},
+ {file = "cffi-1.16.0-cp38-cp38-win32.whl", hash = "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a"},
+ {file = "cffi-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36"},
+ {file = "cffi-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed"},
+ {file = "cffi-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2"},
+ {file = "cffi-1.16.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872"},
+ {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8"},
+ {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f"},
+ {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4"},
+ {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098"},
+ {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000"},
+ {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe"},
+ {file = "cffi-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4"},
+ {file = "cffi-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8"},
+ {file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"},
+]
+
+[package.dependencies]
+pycparser = "*"
+
[[package]]
name = "charset-normalizer"
version = "3.3.2"
@@ -488,6 +563,55 @@ files = [
[package.extras]
toml = ["tomli"]
+[[package]]
+name = "cryptography"
+version = "43.0.0"
+description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "cryptography-43.0.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:64c3f16e2a4fc51c0d06af28441881f98c5d91009b8caaff40cf3548089e9c74"},
+ {file = "cryptography-43.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3dcdedae5c7710b9f97ac6bba7e1052b95c7083c9d0e9df96e02a1932e777895"},
+ {file = "cryptography-43.0.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d9a1eca329405219b605fac09ecfc09ac09e595d6def650a437523fcd08dd22"},
+ {file = "cryptography-43.0.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ea9e57f8ea880eeea38ab5abf9fbe39f923544d7884228ec67d666abd60f5a47"},
+ {file = "cryptography-43.0.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:9a8d6802e0825767476f62aafed40532bd435e8a5f7d23bd8b4f5fd04cc80ecf"},
+ {file = "cryptography-43.0.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:cc70b4b581f28d0a254d006f26949245e3657d40d8857066c2ae22a61222ef55"},
+ {file = "cryptography-43.0.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:4a997df8c1c2aae1e1e5ac49c2e4f610ad037fc5a3aadc7b64e39dea42249431"},
+ {file = "cryptography-43.0.0-cp37-abi3-win32.whl", hash = "sha256:6e2b11c55d260d03a8cf29ac9b5e0608d35f08077d8c087be96287f43af3ccdc"},
+ {file = "cryptography-43.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:31e44a986ceccec3d0498e16f3d27b2ee5fdf69ce2ab89b52eaad1d2f33d8778"},
+ {file = "cryptography-43.0.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:7b3f5fe74a5ca32d4d0f302ffe6680fcc5c28f8ef0dc0ae8f40c0f3a1b4fca66"},
+ {file = "cryptography-43.0.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac1955ce000cb29ab40def14fd1bbfa7af2017cca696ee696925615cafd0dce5"},
+ {file = "cryptography-43.0.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:299d3da8e00b7e2b54bb02ef58d73cd5f55fb31f33ebbf33bd00d9aa6807df7e"},
+ {file = "cryptography-43.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ee0c405832ade84d4de74b9029bedb7b31200600fa524d218fc29bfa371e97f5"},
+ {file = "cryptography-43.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:cb013933d4c127349b3948aa8aaf2f12c0353ad0eccd715ca789c8a0f671646f"},
+ {file = "cryptography-43.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fdcb265de28585de5b859ae13e3846a8e805268a823a12a4da2597f1f5afc9f0"},
+ {file = "cryptography-43.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2905ccf93a8a2a416f3ec01b1a7911c3fe4073ef35640e7ee5296754e30b762b"},
+ {file = "cryptography-43.0.0-cp39-abi3-win32.whl", hash = "sha256:47ca71115e545954e6c1d207dd13461ab81f4eccfcb1345eac874828b5e3eaaf"},
+ {file = "cryptography-43.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:0663585d02f76929792470451a5ba64424acc3cd5227b03921dab0e2f27b1709"},
+ {file = "cryptography-43.0.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2c6d112bf61c5ef44042c253e4859b3cbbb50df2f78fa8fae6747a7814484a70"},
+ {file = "cryptography-43.0.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:844b6d608374e7d08f4f6e6f9f7b951f9256db41421917dfb2d003dde4cd6b66"},
+ {file = "cryptography-43.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:51956cf8730665e2bdf8ddb8da0056f699c1a5715648c1b0144670c1ba00b48f"},
+ {file = "cryptography-43.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:aae4d918f6b180a8ab8bf6511a419473d107df4dbb4225c7b48c5c9602c38c7f"},
+ {file = "cryptography-43.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:232ce02943a579095a339ac4b390fbbe97f5b5d5d107f8a08260ea2768be8cc2"},
+ {file = "cryptography-43.0.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:5bcb8a5620008a8034d39bce21dc3e23735dfdb6a33a06974739bfa04f853947"},
+ {file = "cryptography-43.0.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:08a24a7070b2b6804c1940ff0f910ff728932a9d0e80e7814234269f9d46d069"},
+ {file = "cryptography-43.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:e9c5266c432a1e23738d178e51c2c7a5e2ddf790f248be939448c0ba2021f9d1"},
+ {file = "cryptography-43.0.0.tar.gz", hash = "sha256:b88075ada2d51aa9f18283532c9f60e72170041bba88d7f37e49cbb10275299e"},
+]
+
+[package.dependencies]
+cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""}
+
+[package.extras]
+docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"]
+docstest = ["pyenchant (>=1.6.11)", "readme-renderer", "sphinxcontrib-spelling (>=4.0.1)"]
+nox = ["nox"]
+pep8test = ["check-sdist", "click", "mypy", "ruff"]
+sdist = ["build"]
+ssh = ["bcrypt (>=3.1.5)"]
+test = ["certifi", "cryptography-vectors (==43.0.0)", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"]
+test-randomorder = ["pytest-randomly"]
+
[[package]]
name = "cssbeautifier"
version = "1.15.1"
@@ -552,19 +676,21 @@ bcrypt = ["bcrypt"]
[[package]]
name = "django-allauth"
-version = "0.63.6"
+version = "64.0.0"
description = "Integrated set of Django applications addressing authentication, registration, account management as well as 3rd party (social) account authentication."
optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.8"
files = [
- {file = "django_allauth-0.63.6.tar.gz", hash = "sha256:f15f49defb09e0604dad5214e53a69a1f723cb03176bb115c8930bcd19b91749"},
+ {file = "django_allauth-64.0.0.tar.gz", hash = "sha256:01952c7540160ef475c12dc881f67a69c7c5e533f4bef6c811bba0417a717588"},
]
[package.dependencies]
-Django = ">=3.2"
+Django = ">=4.2"
+fido2 = {version = ">=1.1.2", optional = true, markers = "extra == \"mfa\""}
+qrcode = {version = ">=7.0.0", optional = true, markers = "extra == \"mfa\""}
[package.extras]
-mfa = ["qrcode (>=7.0.0)"]
+mfa = ["fido2 (>=1.1.2)", "qrcode (>=7.0.0)"]
openid = ["python3-openid (>=3.0.8)"]
saml = ["python3-saml (>=1.15.0,<2.0.0)"]
socialaccount = ["pyjwt[crypto] (>=1.7)", "requests (>=2.0.0)", "requests-oauthlib (>=0.3.0)"]
@@ -694,16 +820,6 @@ regex = ">=2023.0.0,<2024.0.0"
tomli = {version = ">=2.0.1,<3.0.0", markers = "python_version < \"3.11\""}
tqdm = ">=4.62.2,<5.0.0"
-[[package]]
-name = "docopt"
-version = "0.6.2"
-description = "Pythonic argument parser, that will make you smile"
-optional = false
-python-versions = "*"
-files = [
- {file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"},
-]
-
[[package]]
name = "editorconfig"
version = "0.12.4"
@@ -742,6 +858,23 @@ files = [
[package.extras]
tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich"]
+[[package]]
+name = "fido2"
+version = "1.1.3"
+description = "FIDO2/WebAuthn library for implementing clients and servers."
+optional = false
+python-versions = ">=3.8,<4.0"
+files = [
+ {file = "fido2-1.1.3-py3-none-any.whl", hash = "sha256:6be34c0b9fe85e4911fd2d103cce7ae8ce2f064384a7a2a3bd970b3ef7702931"},
+ {file = "fido2-1.1.3.tar.gz", hash = "sha256:26100f226d12ced621ca6198528ce17edf67b78df4287aee1285fee3cd5aa9fc"},
+]
+
+[package.dependencies]
+cryptography = ">=2.6,<35 || >35,<45"
+
+[package.extras]
+pcsc = ["pyscard (>=1.9,<3)"]
+
[[package]]
name = "frozenlist"
version = "1.4.1"
@@ -1326,18 +1459,16 @@ files = [
[[package]]
name = "prompt-toolkit"
-version = "2.0.10"
+version = "3.0.47"
description = "Library for building powerful interactive command lines in Python"
optional = false
-python-versions = ">=2.6,<3.0.dev0 || >=3.3.dev0"
+python-versions = ">=3.7.0"
files = [
- {file = "prompt_toolkit-2.0.10-py2-none-any.whl", hash = "sha256:e7f8af9e3d70f514373bf41aa51bc33af12a6db3f71461ea47fea985defb2c31"},
- {file = "prompt_toolkit-2.0.10-py3-none-any.whl", hash = "sha256:46642344ce457641f28fc9d1c9ca939b63dadf8df128b86f1b9860e59c73a5e4"},
- {file = "prompt_toolkit-2.0.10.tar.gz", hash = "sha256:f15af68f66e664eaa559d4ac8a928111eebd5feda0c11738b5998045224829db"},
+ {file = "prompt_toolkit-3.0.47-py3-none-any.whl", hash = "sha256:0d7bfa67001d5e39d02c224b663abc33687405033a8c422d0d675a5a13361d10"},
+ {file = "prompt_toolkit-3.0.47.tar.gz", hash = "sha256:1e1b29cb58080b1e69f207c893a1a7bf16d127a5c30c9d17a25a5d77792e5360"},
]
[package.dependencies]
-six = ">=1.9.0"
wcwidth = "*"
[[package]]
@@ -1357,23 +1488,23 @@ ptpython = {version = "*", markers = "python_version >= \"3.5\""}
[[package]]
name = "ptpython"
-version = "2.0.4"
+version = "3.0.29"
description = "Python REPL build on top of prompt_toolkit"
optional = false
-python-versions = "*"
+python-versions = ">=3.7"
files = [
- {file = "ptpython-2.0.4-py2-none-any.whl", hash = "sha256:51a74abe931f692360a32d650c2ba1ca329c08f3ed9b1de8abcd1164e0b0a6a7"},
- {file = "ptpython-2.0.4-py3-none-any.whl", hash = "sha256:938ee050e37d61c138dbbeb21383dfef8b9ed4ffb453a5f34041f42025bf5042"},
- {file = "ptpython-2.0.4.tar.gz", hash = "sha256:ebe9d68ea7532ec8ab306d4bdc7ec393701cd9bbd6eff0aa3067c821f99264d4"},
+ {file = "ptpython-3.0.29-py2.py3-none-any.whl", hash = "sha256:65d75c4871859e4305a020c9b9e204366dceb4d08e0e2bd7b7511bd5e917a402"},
+ {file = "ptpython-3.0.29.tar.gz", hash = "sha256:b9d625183aef93a673fc32cbe1c1fcaf51412e7a4f19590521cdaccadf25186e"},
]
[package.dependencies]
-docopt = "*"
-jedi = ">=0.9.0"
-prompt-toolkit = ">=2.0.6,<2.1.0"
+appdirs = "*"
+jedi = ">=0.16.0"
+prompt-toolkit = ">=3.0.43,<3.1.0"
pygments = "*"
[package.extras]
+all = ["black"]
ptipython = ["ipython"]
[[package]]
@@ -1415,6 +1546,17 @@ files = [
[package.extras]
tests = ["pytest"]
+[[package]]
+name = "pycparser"
+version = "2.22"
+description = "C parser in Python"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"},
+ {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"},
+]
+
[[package]]
name = "pygments"
version = "2.18.0"
@@ -1491,6 +1633,17 @@ files = [
[package.dependencies]
pylint = ">=1.7"
+[[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 = "pytest"
version = "8.2.2"
@@ -1609,6 +1762,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 = "regex"
version = "2023.12.25"
@@ -2079,4 +2255,4 @@ multidict = ">=4.0"
[metadata]
lock-version = "2.0"
python-versions = ">=3.8,<4.0.0"
-content-hash = "c16808ace0efb28e87518cf97bb3bcf9b85d1cd66615f3fac2345e4e385ff741"
+content-hash = "36a6df024511e4351776eb6e6e9fa51296890df6f178beba00deeeaf15db5ec0"
diff --git a/pyproject.toml b/pyproject.toml
index 9d7f9f7..2bbed57 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -32,7 +32,7 @@ slippers = "^0.6.2"
Django = "^4.0.2"
Werkzeug = "^2.0.2"
black = {extras = ["d"], version = "^24.4.2"}
-django-allauth = ">0.63.0"
+django-allauth = {extras = ["mfa"], version = "^64.0.0"}
django-browser-reload = "^1.3.0"
django-click = "^2.3.0"
django-debug-toolbar = "^3.2.4"
@@ -44,7 +44,7 @@ isort = "^5.13.2"
mypy = "^1.3.0"
mypy-extensions = "1.0.0"
ptipython = "^1.0.1"
-ptpython = "2.0.4"
+ptpython = "^3.0.29"
pudb = "2019.1"
pylint = "^3.2.2"
pylint-django = "^2.5.0"
diff --git a/sample_deployment/sample_deployment/example/templates/example/base.html b/sample_deployment/sample_deployment/example/templates/example/base.html
index 8b3d049..9adf410 100644
--- a/sample_deployment/sample_deployment/example/templates/example/base.html
+++ b/sample_deployment/sample_deployment/example/templates/example/base.html
@@ -1,3 +1,4 @@
+{# djlint:off H006 #}
{% load static tailwind_tags %}
diff --git a/tests/settings.py b/tests/settings.py
index d67270f..e085c91 100644
--- a/tests/settings.py
+++ b/tests/settings.py
@@ -30,6 +30,7 @@
"allauth_ui",
"allauth",
"allauth.account",
+ "allauth.mfa",
"allauth.socialaccount",
"allauth.socialaccount.providers.digitalocean",
"allauth.socialaccount.providers.facebook",
@@ -116,3 +117,5 @@
BASE_DIR = Path(__file__).parent.parent
USERSESSIONS_TRACK_ACTIVITY = True
+
+MFA_SUPPORTED_TYPES = ["totp", "webauthn", "recovery_codes"]