Skip to content

Commit

Permalink
feat: add support for MFA using TOTP and recovery codes (#109)
Browse files Browse the repository at this point in the history
  • Loading branch information
danihodovic committed Aug 7, 2024
1 parent 1ea55b1 commit eb9e843
Show file tree
Hide file tree
Showing 15 changed files with 401 additions and 46 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions allauth_ui/templates/allauth/layouts/base.html
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
{# djlint:off H006 #}
{% load i18n %}
{% load static %}
{% load allauth_ui %}
Expand Down
17 changes: 5 additions & 12 deletions allauth_ui/templates/components/form.html
Original file line number Diff line number Diff line change
@@ -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" %}
<form class="py-3" method="post" action="{{ url }}">
Expand All @@ -8,17 +10,8 @@
{% if render_fields == "true" %}
{% for field in form.visible_fields %}
{% if field.name != "remember" %}
<label class="label" for="{{ field.id_for_label }}">
<span class="label-text">{{ field.label }}</span>
</label>
{% if field.errors %}
{% render_field field placeholder="" class="w-full input input-bordered text-primary input-error" %}
{% else %}
{% render_field field placeholder="" class="w-full input input-bordered text-primary" %}
{% endif %}
{% for error in field.errors %}
<span class="flex items-center max-w-xs mt-1 ml-1 text-xs font-medium tracking-wide text-error">{{ error }}</span>
{% endfor %}
{% #form_field field=field %}
{% /form_field %}
{% endif %}
{% endfor %}
{% endif %}
Expand Down
12 changes: 12 additions & 0 deletions allauth_ui/templates/components/form_field.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{% load widget_tweaks %}
<label class="label" for="{{ field.id_for_label }}">
<span class="label-text">{{ field.label }}</span>
</label>
{% if field.errors %}
{% render_field field placeholder="" class="w-full input input-bordered text-primary input-error" %}
{% else %}
{% render_field field placeholder="" class="w-full input input-bordered text-primary" %}
{% endif %}
{% for error in field.errors %}
<span class="flex items-center max-w-xs mt-1 ml-1 text-xs font-medium tracking-wide text-error">{{ error }}</span>
{% endfor %}
73 changes: 73 additions & 0 deletions allauth_ui/templates/mfa/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
{% extends "mfa/index.html" %}
{% load i18n %}
{% load allauth_ui %}
{% block content %}
{% trans "Two-Factor Authentication" as heading %}
{% #container heading=heading %}
{% if "totp" in MFA_SUPPORTED_TYPES %}
<h2 class="mt-6 mb-3 text-lg">{% translate "Authenticator App" %}</h2>
{% if authenticators.totp %}
<p class="my-2">
{% translate "Authentication using an authenticator app is active." %}
<div>
<a href="{% url "mfa_activate_totp" %}"
role="button"
class="my-3 btn btn-warning">{% translate "Deactivate" %}</a>
</div>
</p>
{% else %}
<p class="my-2">{% translate "An authenticator app is not active." %}</p>
<a href="{% url "mfa_activate_totp" %}"
role="button"
class="btn btn-neutral">{% translate "Activate" %}</a>
{% endif %}
{% endif %}
{% if "webauthn" in MFA_SUPPORTED_TYPES %}
<div class="divider divider-neutral"></div>
<h2 class="mt-6 mb-3 text-lg">{% translate "Security Keys" %}</h2>
{% if authenticators.webauthn|length %}
<p class="my-2">
{% blocktranslate count count=authenticators.webauthn|length %}You have added {{ count }} security key.{% plural %}You have added {{ count }} security keys.{% endblocktranslate %}
</p>
<a href="{% url "mfa_list_webauthn" %}"
role="button"
class="btn btn-neutral">{% translate "Manage" %}</a>
{% else %}
<p class="my-2">{% translate "No security keys have been added." %}</p>
<a href="{% url "mfa_add_webauthn" %}"
role="button"
class="btn btn-neutral">{% translate "Add" %}</a>
{% endif %}
{% endif %}
{% if "recovery_codes" in MFA_SUPPORTED_TYPES %}
<div class="divider divider-neutral"></div>
{% with total_count=authenticators.recovery_codes.generate_codes|length unused_count=authenticators.recovery_codes.get_unused_codes|length %}
<h2 class="mt-6 mb-3 text-lg">{% translate "Recovery Codes" %}</h2>
<p>
{% 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 %}
</p>
{% if is_mfa_enabled %}
<div class="flex flex-col justify-evenly my-3 md:flex-row gap-1">
{% if authenticators.recovery_codes %}
{% if unused_count > 0 %}
<a href="{% url "mfa_view_recovery_codes" %}"
role="button"
class="btn btn-neutral">{% translate "View" %}</a>
<a href="{% url "mfa_download_recovery_codes" %}"
role="button"
class="btn btn-neutral">{% translate "Download" %}</a>
{% endif %}
{% endif %}
<a href="{% url "mfa_generate_recovery_codes" %}"
role="button"
class="btn btn-neutral">{% translate "Generate" %}</a>
</div>
{% endif %}
{% endwith %}
{% endif %}
{% /container %}
{% endblock content %}
21 changes: 21 additions & 0 deletions allauth_ui/templates/mfa/recovery_codes/generate.html
Original file line number Diff line number Diff line change
@@ -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 %}
<p class="py-3">
{% if unused_code_count %}
{% blocktranslate %}This action will invalidate your existing codes.{% endblocktranslate %}
{% endif %}
{% blocktranslate %}Are you sure?{% endblocktranslate %}
</p>
{% 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 %}
27 changes: 27 additions & 0 deletions allauth_ui/templates/mfa/recovery_codes/index.html
Original file line number Diff line number Diff line change
@@ -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 %}
<div class="my-3">
<label class="label" for="codes">
<span class="label-text">{% translate "Unused codes" %}</span>
</label>
<textarea id="codes" class="w-full m-3 mx-auto textarea" rows="{{unused_codes|length}}" disabled readonly>
{% for code in unused_codes %}{{ code }}
{% endfor %}
</textarea>
</div>
<div class="flex flex-col mx-auto sm:w-8/12 xl:w-7/12">
{% if unused_codes %}
<a href="{% url "mfa_download_recovery_codes" %}"
class="md:w-full btn btn-neutral">{% trans "Download codes" %}</a>
{% endif %}
<a href="{% url "mfa_generate_recovery_codes" %}"
class="mt-4 md:w-full btn btn-neutral">{% trans "Generate new codes" %}</a>
</div>
{% /container %}
{% endblock content %}
31 changes: 31 additions & 0 deletions allauth_ui/templates/mfa/totp/activate_form.html
Original file line number Diff line number Diff line change
@@ -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" %}
<img src="{{ totp_svg_data_uri }}"
alt="{{ form.secret }}"
class="mx-auto my-5" />
{% #form_field field=form.code %}
{% /form_field %}
<div class="my-3">
<label class="label" for="authenticator_secret">
<span class="label-text">{% translate "Authenticator secret" %}</span>
</label>
<p class="text-xs mb-2">
{% translate "You can store this secret and use it to reinstall your authenticator app at a later time." %}
</p>
<input type="text" id="authenticator_secret"" value="{{ form.secret }}" disabled
class="w-full input input-bordered text-primary"/>
</div>
{% csrf_token %}
{% /form %}
{% /container %}
{% endblock content %}
14 changes: 14 additions & 0 deletions allauth_ui/templates/mfa/totp/deactivate_form.html
Original file line number Diff line number Diff line change
@@ -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 %}
<button type="submit" class="btn btn-warning">{% trans "Deactivate" %}</button>
{% /form %}
{% /container %}
{% endblock content %}
2 changes: 1 addition & 1 deletion allauth_ui/templates/usersessions/usersession_list.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<h1 class="mb-3 text-2xl">{{ heading }}</h1>
{% 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 %}
Expand Down
1 change: 1 addition & 0 deletions allauth_ui/templatetags/allauth_ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ def allauth_ui_theme():
{
"container": "components/container.html",
"form": "components/form.html",
"form_field": "components/form_field.html",
},
register,
)
Loading

0 comments on commit eb9e843

Please sign in to comment.