diff --git a/hypha/apply/users/templates/two_factor/core/setup.html b/hypha/apply/users/templates/two_factor/core/setup.html index 4eb1cf4f74ff4a9334636c117458d7607e69ebf1..76824d0926f91b3b975c5497b11882ffc6a956ab 100644 --- a/hypha/apply/users/templates/two_factor/core/setup.html +++ b/hypha/apply/users/templates/two_factor/core/setup.html @@ -2,11 +2,11 @@ {% load i18n %} {% block content %} - <h1>{% block title %}{% trans "Two-Factor Authentication(2FA)" %}{% endblock %}</h1> + <h1>{% block title %}{% trans "Two-Factor Authentication (2FA)" %}{% endblock %}</h1> {% if wizard.steps.current == 'welcome' %} <p>{% blocktrans trimmed %}You are about to take your account security to the next level.{% endblocktrans %}</p> - <p>{% blocktrans trimmed %}To start using 2FA, you need to install an Authenticator app(eg. Google Authenticator app) on your smartphone.{% endblocktrans %}</p> + <p>{% blocktrans trimmed %}To start using 2FA, you need to install an Authenticator app on your smartphone or computer. With Safari on Apple devices you can also use a built in system.{% endblocktrans %}</p> <p>{% blocktrans trimmed %}Install the app you choose then continue to Enable Two-Factor Authentication. {% endblocktrans %}</p> {% elif wizard.steps.current == 'method' %} <p>{% blocktrans trimmed %}Please select which authentication method you would @@ -16,7 +16,8 @@ smartphone to scan the QR code below. For example, use Google Authenticator. Then, enter the token generated by the app. {% endblocktrans %}</p> - <p><img src="{{ QR_URL }}" alt="QR Code" /></p> + <p><img src="{{ QR_URL }}" alt="QR Code" class="bg-white" width="200" height="200" /></p> + <p><a href="{{ otpauth_url }}">Alternative otpauth set up link</a></p> {% elif wizard.steps.current == 'sms' %} <p>{% blocktrans trimmed %}Please enter the phone number you wish to receive the text messages on. This number will be validated in the next step. @@ -49,8 +50,6 @@ <form action="" method="post">{% csrf_token %} {% include "two_factor/_wizard_forms.html" %} - <!-- Add empty p tag to add some space in between wizard form fields and wizard actions buttons. --> - <p></p> {# hidden submit button to enable [enter] key #} <input type="submit" value="" class="d-none" /> diff --git a/hypha/apply/users/templates/two_factor/profile/profile.html b/hypha/apply/users/templates/two_factor/profile/profile.html index b419947de0d3e6cb0da529a2cebd4bf590e97709..fc7177d1cb56b1d8a451688ec3a9af34ba6155f9 100644 --- a/hypha/apply/users/templates/two_factor/profile/profile.html +++ b/hypha/apply/users/templates/two_factor/profile/profile.html @@ -1,5 +1,5 @@ {% extends "two_factor/_base.html" %} -{% load i18n two_factor %} +{% load i18n %} {% block content %} <p><a href="{% url 'users:account' %}" class="btn btn-primary"> @@ -12,7 +12,7 @@ {% if default_device_type == 'TOTPDevice' %} <p>{% trans "Tokens will be generated by your token generator." %}</p> {% elif default_device_type == 'PhoneDevice' %} - <p>{% blocktrans with primary=default_device|device_action %}Primary method: {{ primary }}{% endblocktrans %}</p> + <p>{% blocktrans with primary=default_device.generate_challenge_button_title %}Primary method: {{ primary }}{% endblocktrans %}</p> {% elif default_device_type == 'RemoteYubikeyDevice' %} <p>{% blocktrans %}Tokens will be generated by your YubiKey.{% endblocktrans %}</p> {% endif %} @@ -24,11 +24,11 @@ <ul> {% for phone in backup_phones %} <li> - {{ phone|device_action }} + {{ phone.generate_challenge_button_title }} <form method="post" action="{% url 'two_factor:phone_delete' phone.id %}" - onsubmit="return confirm('Are you sure?')"> + onsubmit="return confirm({% trans 'Are you sure?' %})"> {% csrf_token %} - <button class="btn btn-xs btn-warning" + <button class="btn btn-sm btn-warning" type="submit">{% trans "Unregister" %}</button> </form> </li> diff --git a/hypha/apply/users/templates/users/login.html b/hypha/apply/users/templates/users/login.html index 2468c63e8075207946dd5f04946eda84b93fcb60..be26db3d22b8ae331bbea809e86d11552238b9a7 100644 --- a/hypha/apply/users/templates/users/login.html +++ b/hypha/apply/users/templates/users/login.html @@ -1,5 +1,5 @@ {% extends 'base.html' %} -{% load i18n two_factor wagtailcore_tags %} +{% load i18n wagtailcore_tags %} {% block header_modifier %}header--light-bg{% endblock %} {% block page_title %}Login{% endblock %} @@ -51,7 +51,7 @@ {% for other in other_devices %} <button name="challenge_device" value="{{ other.persistent_id }}" class="btn btn-default btn-block" type="submit"> - {{ other|device_action }} + {{ other.generate_challenge_button_title }} </button> {% endfor %}</p> {% endif %} diff --git a/hypha/apply/users/urls.py b/hypha/apply/users/urls.py index 2fd5ff936cf1fa11cb01ccab2aad009889fbae91..ec4e26546911823f463da082bc1143c8ec336a1e 100644 --- a/hypha/apply/users/urls.py +++ b/hypha/apply/users/urls.py @@ -13,6 +13,7 @@ from .views import ( TWOFABackupTokensPasswordView, TWOFADisableView, TWOFARequiredMessageView, + TWOFASetupView, become, create_password, oauth, @@ -87,6 +88,7 @@ urlpatterns = [ ), # Two factor redirect path('two_factor/required/', TWOFARequiredMessageView.as_view(), name='two_factor_required'), + path('two_factor/setup/', TWOFASetupView.as_view(), name='setup'), path('two_factor/backup_tokens/password/', TWOFABackupTokensPasswordView.as_view(), name='backup_tokens_password'), path('two_factor/disable/', TWOFADisableView.as_view(), name='disable'), path('two_factor/admin/disable/<str:user_id>/', TWOFAAdminDisableView.as_view(), name='admin_disable'), diff --git a/hypha/apply/users/views.py b/hypha/apply/users/views.py index e3a3dc9aa2be4cfcb9415eca4d114c796328c42f..eb896769a4ef58c83645bd1dfef2c82264e6ff52 100644 --- a/hypha/apply/users/views.py +++ b/hypha/apply/users/views.py @@ -7,6 +7,7 @@ from django.contrib.auth import get_user_model, login, update_session_auth_hash from django.contrib.auth.decorators import login_required, permission_required from django.contrib.auth.forms import AdminPasswordChangeForm from django.contrib.auth.tokens import PasswordResetTokenGenerator +from django.contrib.sites.shortcuts import get_current_site from django.core.exceptions import PermissionDenied from django.core.signing import BadSignature, Signer, TimestampSigner, dumps, loads from django.shortcuts import Http404, get_object_or_404, redirect, render @@ -16,16 +17,18 @@ from django.utils.decorators import method_decorator from django.utils.encoding import force_str from django.utils.http import urlsafe_base64_decode from django.utils.translation import gettext_lazy as _ +from django.views.decorators.cache import never_cache from django.views.generic import UpdateView from django.views.generic.base import TemplateView from django.views.generic.edit import FormView from django_otp import devices_for_user from hijack.views import AcquireUserView from two_factor.forms import AuthenticationTokenForm, BackupTokenForm -from two_factor.utils import default_device +from two_factor.utils import default_device, get_otpauth_url, totp_digits from two_factor.views import BackupTokensView as TwoFactorBackupTokensView from two_factor.views import DisableView as TwoFactorDisableView from two_factor.views import LoginView as TwoFactorLoginView +from two_factor.views import SetupView as TwoFactorSetupView from wagtail.admin.views.account import password_management_enabled from wagtail.core.models import Site from wagtail.users.views.users import change_user_perm @@ -284,6 +287,34 @@ def create_password(request): }) +@method_decorator(never_cache, name='dispatch') +@method_decorator(login_required, name='dispatch') +class TWOFASetupView(TwoFactorSetupView): + def get_issuer(self): + return get_current_site(self.request).name + + def get_context_data(self, form, **kwargs): + context = super().get_context_data(form, **kwargs) + if self.steps.current == 'generator': + try: + username = self.request.user.get_username() + except AttributeError: + username = self.request.user.username + + otpauth_url = get_otpauth_url(accountname=username, + issuer=self.get_issuer(), + secret=context['secret_key'], + digits=totp_digits()) + import logging + logger = logging.getLogger('hypha') + logger.debug(otpauth_url) + context.update({ + 'otpauth_url': otpauth_url, + }) + + return context + + @method_decorator(login_required, name='dispatch') class TWOFABackupTokensPasswordView(TwoFactorBackupTokensView): """ diff --git a/requirements.txt b/requirements.txt index ae395c233b0995025c6537b685735319a1bd60e0..c66d1540300e2172c40cbf54f95da9a7aad5f82a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -27,7 +27,7 @@ django-select2==7.9.0 django-storages==1.12.3 django-tables2==2.4.1 django-tinymce==3.4.0 -django-two-factor-auth==1.13.1 +django-two-factor-auth==1.14.0 django-webpack-loader==1.0.0 django==3.2.13 djangorestframework-api-key==2.1.0