From 81efb631e2a771520f8321d46aa388fb2fe12ddc Mon Sep 17 00:00:00 2001 From: Lindsay Date: Tue, 25 Aug 2020 12:50:21 +0200 Subject: [PATCH] Add the possibility for the user to change the double authenticaiton method he uses --- .../templates/two_factor/_wizard_actions.html | 15 +++ .../two_factor/core/backup_tokens.html | 28 +++++ example/templates/two_factor/core/login.html | 52 +++++++++ .../two_factor/core/otp_required.html | 20 ++++ .../two_factor/core/phone_register.html | 24 +++++ example/templates/two_factor/core/setup.html | 55 ++++++++++ .../two_factor/core/setup_complete.html | 24 +++++ .../core/setup_reset_generator.html | 19 ++++ .../two_factor/core/setup_reset_phone.html | 23 ++++ .../templates/two_factor/profile/disable.html | 14 +++ .../templates/two_factor/profile/profile.html | 84 +++++++++++++++ .../core/setup_reset_generator.html | 19 ++++ .../two_factor/core/setup_reset_phone.html | 24 +++++ .../templates/two_factor/profile/profile.html | 23 ++++ two_factor/urls.py | 14 ++- two_factor/views/__init__.py | 3 +- two_factor/views/core.py | 102 +++++++++++++++++- two_factor/views/utils.py | 8 ++ 18 files changed, 546 insertions(+), 5 deletions(-) create mode 100644 example/templates/two_factor/_wizard_actions.html create mode 100644 example/templates/two_factor/core/backup_tokens.html create mode 100644 example/templates/two_factor/core/login.html create mode 100644 example/templates/two_factor/core/otp_required.html create mode 100644 example/templates/two_factor/core/phone_register.html create mode 100644 example/templates/two_factor/core/setup.html create mode 100644 example/templates/two_factor/core/setup_complete.html create mode 100644 example/templates/two_factor/core/setup_reset_generator.html create mode 100644 example/templates/two_factor/core/setup_reset_phone.html create mode 100644 example/templates/two_factor/profile/disable.html create mode 100644 example/templates/two_factor/profile/profile.html create mode 100644 two_factor/templates/two_factor/core/setup_reset_generator.html create mode 100644 two_factor/templates/two_factor/core/setup_reset_phone.html diff --git a/example/templates/two_factor/_wizard_actions.html b/example/templates/two_factor/_wizard_actions.html new file mode 100644 index 000000000..4abfa20c8 --- /dev/null +++ b/example/templates/two_factor/_wizard_actions.html @@ -0,0 +1,15 @@ +{% load i18n %} + +{% if cancel_url %} + {% trans "Cancel" %} +{% endif %} +{% if wizard.steps.prev %} + +{% else %} + +{% endif %} + diff --git a/example/templates/two_factor/core/backup_tokens.html b/example/templates/two_factor/core/backup_tokens.html new file mode 100644 index 000000000..3211df796 --- /dev/null +++ b/example/templates/two_factor/core/backup_tokens.html @@ -0,0 +1,28 @@ +{% extends "two_factor/_base_focus.html" %} +{% load i18n %} + +{% block content %} +

{% block title %}{% trans "Backup Tokens" %}{% endblock %}

+

{% blocktrans trimmed %}Backup tokens can be used when your primary and backup + phone numbers aren't available. The backup tokens below can be used + for login verification. If you've used up all your backup tokens, you + can generate a new set of backup tokens. Only the backup tokens shown + below will be valid.{% endblocktrans %}

+ + {% if device.token_set.count %} + +

{% blocktrans %}Print these tokens and keep them somewhere safe.{% endblocktrans %}

+ {% else %} +

{% trans "You don't have any backup codes yet." %}

+ {% endif %} + +
{% csrf_token %}{{ form }} + {% trans "Back to Account Security" %} + +
+{% endblock %} diff --git a/example/templates/two_factor/core/login.html b/example/templates/two_factor/core/login.html new file mode 100644 index 000000000..607dc2ce0 --- /dev/null +++ b/example/templates/two_factor/core/login.html @@ -0,0 +1,52 @@ +{% extends "two_factor/_base_focus.html" %} +{% load i18n two_factor %} + +{% block content %} +

{% block title %}{% trans "Login" %}{% endblock %}

+ + {% if wizard.steps.current == 'auth' %} +

{% blocktrans %}Enter your credentials.{% endblocktrans %}

+ {% elif wizard.steps.current == 'token' %} + {% if device.method == 'call' %} +

{% blocktrans trimmed %}We are calling your phone right now, please enter the + digits you hear.{% endblocktrans %}

+ {% elif device.method == 'sms' %} +

{% blocktrans trimmed %}We sent you a text message, please enter the tokens we + sent.{% endblocktrans %}

+ {% else %} +

{% blocktrans trimmed %}Please enter the tokens generated by your token + generator.{% endblocktrans %}

+ {% endif %} + {% elif wizard.steps.current == 'backup' %} +

{% 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 %}

+ {% endif %} + +
{% csrf_token %} + {% include "two_factor/_wizard_forms.html" %} + + {# hidden submit button to enable [enter] key #} +
+ + {% if other_devices %} +

{% trans "Or, alternatively, use one of your backup phones:" %}

+

+ {% for other in other_devices %} + + {% endfor %}

+ {% endif %} + {% if backup_tokens %} +

{% trans "As a last resort, you can use a backup token:" %}

+

+ +

+ {% endif %} + + {% include "two_factor/_wizard_actions.html" %} +
+{% endblock %} diff --git a/example/templates/two_factor/core/otp_required.html b/example/templates/two_factor/core/otp_required.html new file mode 100644 index 000000000..7221a4a92 --- /dev/null +++ b/example/templates/two_factor/core/otp_required.html @@ -0,0 +1,20 @@ +{% extends "two_factor/_base_focus.html" %} +{% load i18n %} + +{% block content %} +

{% block title %}{% trans "Permission Denied" %}{% endblock %}

+ +

{% blocktrans trimmed %}The page you requested, enforces users to verify using + two-factor authentication for security reasons. You need to enable these + security features in order to access this page.{% endblocktrans %}

+ +

{% blocktrans trimmed %}Two-factor authentication is not enabled for your + account. Enable two-factor authentication for enhanced account + security.{% endblocktrans %}

+

+ {% trans "Go back" %} + + {% trans "Enable Two-Factor Authentication" %} +

+{% endblock %} diff --git a/example/templates/two_factor/core/phone_register.html b/example/templates/two_factor/core/phone_register.html new file mode 100644 index 000000000..f471de559 --- /dev/null +++ b/example/templates/two_factor/core/phone_register.html @@ -0,0 +1,24 @@ +{% extends "two_factor/_base_focus.html" %} +{% load i18n %} + +{% block content %} +

{% block title %}{% trans "Add Backup Phone" %}{% endblock %}

+ + {% if wizard.steps.current == 'setup' %} +

{% blocktrans trimmed %}You'll be adding a backup phone number to your + account. This number will be used if your primary method of + registration is not available.{% endblocktrans %}

+ {% elif wizard.steps.current == 'validation' %} +

{% blocktrans trimmed %}We've sent a token to your phone number. Please + enter the token you've received.{% endblocktrans %}

+ {% endif %} + +
{% csrf_token %} + {% include "two_factor/_wizard_forms.html" %} + + {# hidden submit button to enable [enter] key #} +
+ + {% include "two_factor/_wizard_actions.html" %} +
+{% endblock %} diff --git a/example/templates/two_factor/core/setup.html b/example/templates/two_factor/core/setup.html new file mode 100644 index 000000000..364398a07 --- /dev/null +++ b/example/templates/two_factor/core/setup.html @@ -0,0 +1,55 @@ +{% extends "two_factor/_base_focus.html" %} +{% load i18n %} +{% block content %} +

{% block title %}{% trans "Enable Two-Factor Authentication" %}{% endblock %}

+ {% if wizard.steps.current == 'welcome' %} +

{% blocktrans trimmed %}You are about to take your account security to the + next level. Follow the steps in this wizard to enable two-factor + authentication.{% endblocktrans %}

+ {% elif wizard.steps.current == 'method' %} +

{% blocktrans trimmed %}Please select which authentication method you would + like to use.{% endblocktrans %}

+ {% elif wizard.steps.current == 'generator' %} +

{% blocktrans trimmed %}To start using a token generator, please use your + smartphone to scan the QR code below. For example, use Google + Authenticator. Then, enter the token generated by the app. + {% endblocktrans %}

+

QR Code

+ {% elif wizard.steps.current == 'sms' %} +

{% blocktrans trimmed %}Please enter the phone number you wish to receive the + text messages on. This number will be validated in the next step. + {% endblocktrans %}

+ {% elif wizard.steps.current == 'call' %} +

{% blocktrans trimmed %}Please enter the phone number you wish to be called on. + This number will be validated in the next step. {% endblocktrans %}

+ {% elif wizard.steps.current == 'validation' %} + {% if challenge_succeeded %} + {% if device.method == 'call' %} +

{% blocktrans trimmed %}We are calling your phone right now, please enter the + digits you hear.{% endblocktrans %}

+ {% elif device.method == 'sms' %} +

{% blocktrans trimmed %}We sent you a text message, please enter the tokens we + sent.{% endblocktrans %}

+ {% endif %} + {% else %} + + {% endif %} + {% elif wizard.steps.current == 'yubikey' %} +

{% blocktrans trimmed %}To identify and verify your YubiKey, please insert a + token in the field below. Your YubiKey will be linked to your + account.{% endblocktrans %}

+ {% endif %} + +
{% csrf_token %} + {% include "two_factor/_wizard_forms.html" %} + + {# hidden submit button to enable [enter] key #} +
+ + {% include "two_factor/_wizard_actions.html" %} +
+{% endblock %} diff --git a/example/templates/two_factor/core/setup_complete.html b/example/templates/two_factor/core/setup_complete.html new file mode 100644 index 000000000..c16e9835e --- /dev/null +++ b/example/templates/two_factor/core/setup_complete.html @@ -0,0 +1,24 @@ +{% extends "two_factor/_base_focus.html" %} +{% load i18n %} + +{% block content %} +

{% block title %}{% trans "Enable Two-Factor Authentication" %}{% endblock %}

+ +

{% blocktrans trimmed %}Congratulations, you've successfully enabled two-factor + authentication.{% endblocktrans %}

+ + {% if not phone_methods %} +

{% trans "Back to Profile" %}

+ {% else %} +

{% blocktrans trimmed %}However, it might happen that you don't have access to + your primary token device. To enable account recovery, add a phone + number.{% endblocktrans %}

+ + {% trans "Back to Profile" %} +

{% trans "Add Phone Number" %}

+ {% endif %} + +{% endblock %} diff --git a/example/templates/two_factor/core/setup_reset_generator.html b/example/templates/two_factor/core/setup_reset_generator.html new file mode 100644 index 000000000..e9f2efede --- /dev/null +++ b/example/templates/two_factor/core/setup_reset_generator.html @@ -0,0 +1,19 @@ +{% extends "two_factor/_base_focus.html" %} +{% load i18n %} + +{% block content %} +

{% block title %}{% trans "Change Two-Factor Authentication method" %}{% endblock %}

+

{% blocktrans trimmed %}To start using a token generator, please use your + smartphone to scan the QR code below. For example, use Google + Authenticator. Then, enter the token generated by the app. + {% endblocktrans %}

+

QR Code

+
{% csrf_token %} + {% include "two_factor/_wizard_forms.html" %} + + {# hidden submit button to enable [enter] key #} +
+ + {% include "two_factor/_wizard_actions.html" %} +
+{% endblock %} diff --git a/example/templates/two_factor/core/setup_reset_phone.html b/example/templates/two_factor/core/setup_reset_phone.html new file mode 100644 index 000000000..ea3342ea6 --- /dev/null +++ b/example/templates/two_factor/core/setup_reset_phone.html @@ -0,0 +1,23 @@ +{% extends "two_factor/_base_focus.html" %} +{% load i18n %} + +{% block content %} +

{% block title %}{% trans "Change Two-Factor Authentication method" %}{% endblock %}

+ + {% if wizard.steps.current == 'setup' %} +

{% blocktrans trimmed %}You are about to change your double authentication method form + Google authenticator to phone method.{% endblocktrans %}

+ {% elif wizard.steps.current == 'validation' %} +

{% blocktrans trimmed %}We've sent a token to your phone number. Please + enter the token you've received.{% endblocktrans %}

+ {% endif %} + +
{% csrf_token %} + {% include "two_factor/_wizard_forms.html" %} + + {# hidden submit button to enable [enter] key #} +
+ + {% include "two_factor/_wizard_actions.html" %} +
+{% endblock %} \ No newline at end of file diff --git a/example/templates/two_factor/profile/disable.html b/example/templates/two_factor/profile/disable.html new file mode 100644 index 000000000..249db4ab0 --- /dev/null +++ b/example/templates/two_factor/profile/disable.html @@ -0,0 +1,14 @@ +{% extends "two_factor/_base_focus.html" %} +{% load i18n %} + +{% block content %} +

{% block title %}{% trans "Disable Two-factor Authentication" %}{% endblock %}

+

{% blocktrans trimmed %}You are about to disable two-factor authentication. This + weakens your account security, are you sure?{% endblocktrans %}

+
+ {% csrf_token %} + {{ form }}
+ +
+{% endblock %} diff --git a/example/templates/two_factor/profile/profile.html b/example/templates/two_factor/profile/profile.html new file mode 100644 index 000000000..3b91fa0d9 --- /dev/null +++ b/example/templates/two_factor/profile/profile.html @@ -0,0 +1,84 @@ +{% extends "two_factor/_base.html" %} +{% load i18n two_factor %} + +{% block content %} +

{% block title %}{% trans "Account Security" %}{% endblock %}

+ {% if default_device %} + {% if default_device_type == 'TOTPDevice' %} +

{% trans "Tokens will be generated by your token generator." %}

+ {% elif default_device_type == 'PhoneDevice' %} +

{% blocktrans with primary=default_device|device_action %}Primary method: {{ primary }}{% endblocktrans %}

+ {% elif default_device_type == 'RemoteYubikeyDevice' %} +

{% blocktrans %}Tokens will be generated by your YubiKey.{% endblocktrans %}

+ {% endif %} + + {% if available_phone_methods %} +

{% trans "Backup Phone Numbers" %}

+

{% blocktrans trimmed %}If your primary method is not available, we are able to + send backup tokens to the phone numbers listed below.{% endblocktrans %}

+ +

{% trans "Add Phone Number" %}

+ {% endif %} + +

{% trans "Backup Tokens" %}

+

+ {% blocktrans trimmed %}If you don't have any device with you, you can access + your account using backup tokens.{% endblocktrans %} + {% blocktrans trimmed count counter=backup_tokens %} + You have only one backup token remaining. + {% plural %} + You have {{ counter }} backup tokens remaining. + {% endblocktrans %} +

+

{% trans "Show Codes" %}

+ +

{% trans "Disable Two-Factor Authentication" %}

+

{% blocktrans trimmed %}However we strongly discourage you to do so, you can + also disable two-factor authentication for your account.{% endblocktrans %}

+

+ {% trans "Disable Two-Factor Authentication" %}

+ + +

{% trans "Change Two-Factor Authentication method" %}

+ + {% if default_device_type == 'TOTPDevice' %} +

{% blocktrans trimmed %} + You choose to get the 6-digits authentication code using Google Authenticator. + If you want to receive the code by SMS, please click below and follow the instructions.{% endblocktrans %} +

+

+ {% trans "Change Two-Factor Authentication method" %}

+ {% elif default_device_type == 'PhoneDevice' %} +

+ {% blocktrans trimmed %} + You choose to get the 6-digits authentication code using SMS. + If you want to use Google Authenticator instead, please click below and follow the instructions.{% endblocktrans %} +

+

+ {% trans "Change Two-Factor Authentication method" %}

+ {% endif %} + + + {% else %} +

{% blocktrans trimmed %}Two-factor authentication is not enabled for your + account. Enable two-factor authentication for enhanced account + security.{% endblocktrans %}

+

+ {% trans "Enable Two-Factor Authentication" %} +

+ {% endif %} +{% endblock %} diff --git a/two_factor/templates/two_factor/core/setup_reset_generator.html b/two_factor/templates/two_factor/core/setup_reset_generator.html new file mode 100644 index 000000000..e9f2efede --- /dev/null +++ b/two_factor/templates/two_factor/core/setup_reset_generator.html @@ -0,0 +1,19 @@ +{% extends "two_factor/_base_focus.html" %} +{% load i18n %} + +{% block content %} +

{% block title %}{% trans "Change Two-Factor Authentication method" %}{% endblock %}

+

{% blocktrans trimmed %}To start using a token generator, please use your + smartphone to scan the QR code below. For example, use Google + Authenticator. Then, enter the token generated by the app. + {% endblocktrans %}

+

QR Code

+
{% csrf_token %} + {% include "two_factor/_wizard_forms.html" %} + + {# hidden submit button to enable [enter] key #} +
+ + {% include "two_factor/_wizard_actions.html" %} +
+{% endblock %} diff --git a/two_factor/templates/two_factor/core/setup_reset_phone.html b/two_factor/templates/two_factor/core/setup_reset_phone.html new file mode 100644 index 000000000..841b159bc --- /dev/null +++ b/two_factor/templates/two_factor/core/setup_reset_phone.html @@ -0,0 +1,24 @@ +{% extends "two_factor/_base_focus.html" %} +{% load i18n %} + +{% block content %} +

{% block title %}{% trans "Change Two-Factor Authentication method" %}{% endblock %}

+ + {% if wizard.steps.current == 'setup' %} +

{% blocktrans trimmed %}You are about to change your double authentication method form + Google authenticator to phone method.{% endblocktrans %}

+ {% elif wizard.steps.current == 'validation' %} +

{% blocktrans trimmed %}We've sent a token to your phone number. Please + enter the token you've received.{% endblocktrans %}

+ {% endif %} + +
{% csrf_token %} + {% include "two_factor/_wizard_forms.html" %} + + {# hidden submit button to enable [enter] key #} +
+ + {% include "two_factor/_wizard_actions.html" %} +
+{% endblock %} + diff --git a/two_factor/templates/two_factor/profile/profile.html b/two_factor/templates/two_factor/profile/profile.html index 265e046e2..1e8a1fa24 100644 --- a/two_factor/templates/two_factor/profile/profile.html +++ b/two_factor/templates/two_factor/profile/profile.html @@ -52,6 +52,29 @@

{% trans "Disable Two-Factor Authentication" %}

also disable two-factor authentication for your account.{% endblocktrans %}

{% trans "Disable Two-Factor Authentication" %}

+ + +

{% trans "Change Two-Factor Authentication method" %}

+ + + {% if default_device_type == 'TOTPDevice' %} +

{% blocktrans trimmed %} + You choose to get the 6-digits authentication code using Google Authenticator. + If you want to receive the code by SMS, please click below and follow the instructions.{% endblocktrans %} +

+

+ {% trans "Change Two-Factor Authentication method" %}

+ {% elif default_device_type == 'PhoneDevice' %} +

+ {% blocktrans trimmed %} + You choose to get the 6-digits authentication code using SMS. + If you want to use Google Authenticator instead, please click below and follow the instructions.{% endblocktrans %} +

+

+ {% trans "Change Two-Factor Authentication method" %}

+ {% endif %} + + {% else %}

{% blocktrans trimmed %}Two-factor authentication is not enabled for your account. Enable two-factor authentication for enhanced account diff --git a/two_factor/urls.py b/two_factor/urls.py index 8482978dd..72937a113 100644 --- a/two_factor/urls.py +++ b/two_factor/urls.py @@ -2,7 +2,8 @@ from two_factor.views import ( BackupTokensView, DisableView, LoginView, PhoneDeleteView, PhoneSetupView, - ProfileView, QRGeneratorView, SetupCompleteView, SetupView, + ProfileView, QRGeneratorView, SetupCompleteView, SetupView, ResetSetupGeneratorView, + ResetSetupPhoneView, ) core = [ @@ -26,6 +27,16 @@ SetupCompleteView.as_view(), name='setup_complete', ), + path( + 'account/two_factor/setup/reset/generator/', + view=ResetSetupGeneratorView.as_view(), + name='setup_reset_generator', + ), + path( + 'account/two_factor/setup/reset/phone/', + view=ResetSetupPhoneView.as_view(), + name='setup_reset_phone', + ), path( 'account/two_factor/backup/tokens/', BackupTokensView.as_view(), @@ -55,5 +66,4 @@ name='disable', ), ] - urlpatterns = (core + profile, 'two_factor') diff --git a/two_factor/views/__init__.py b/two_factor/views/__init__.py index c2abab1e4..7c0cf38f0 100644 --- a/two_factor/views/__init__.py +++ b/two_factor/views/__init__.py @@ -1,6 +1,7 @@ from .core import ( BackupTokensView, LoginView, PhoneDeleteView, PhoneSetupView, - QRGeneratorView, SetupCompleteView, SetupView, + QRGeneratorView, ResetSetupGeneratorView, ResetSetupPhoneView, SetupCompleteView, SetupView ) from .mixins import OTPRequiredMixin from .profile import DisableView, ProfileView + diff --git a/two_factor/views/core.py b/two_factor/views/core.py index c7ce0defc..c4813a3dc 100644 --- a/two_factor/views/core.py +++ b/two_factor/views/core.py @@ -33,10 +33,10 @@ from django_otp.decorators import otp_required from django_otp.plugins.otp_static.models import StaticDevice, StaticToken + from two_factor import signals from two_factor.models import get_available_methods, random_hex_str from two_factor.utils import totp_digits - from ..forms import ( AuthenticationTokenForm, BackupTokenForm, DeviceValidationForm, MethodForm, PhoneNumberForm, PhoneNumberMethodForm, TOTPDeviceForm, YubiKeyDeviceForm, @@ -434,6 +434,7 @@ def get_form_list(self): """ Check if there is only one method, then skip the MethodForm from form_list """ + form_list = super().get_form_list() available_methods = get_available_methods() if len(available_methods) == 1: @@ -465,7 +466,6 @@ def done(self, form_list, **kwargs): del self.request.session[self.session_key_name] except KeyError: pass - # TOTPDeviceForm if self.get_method() == 'generator': form = [form for form in form_list if isinstance(form, TOTPDeviceForm)][0] @@ -530,9 +530,12 @@ def get_device(self, **kwargs): def get_key(self, step): self.storage.extra_data.setdefault('keys', {}) + if step in self.storage.extra_data['keys']: return self.storage.extra_data['keys'].get(step) + key = random_hex_str(20) + self.storage.extra_data['keys'][step] = key return key @@ -549,6 +552,7 @@ def get_context_data(self, form, **kwargs): elif self.steps.current == 'validation': context['device'] = self.get_device() context['cancel_url'] = resolve_url(settings.LOGIN_REDIRECT_URL) + return context def process_step(self, form): @@ -664,6 +668,7 @@ def get_key(self): if self.key_name not in self.storage.extra_data: key = random_hex_str(20) self.storage.extra_data[self.key_name] = key + return self.storage.extra_data[self.key_name] def get_context_data(self, form, **kwargs): @@ -700,6 +705,99 @@ def get_context_data(self): } +@class_view_decorator(never_cache) +@class_view_decorator(login_required) +class ResetSetupGeneratorView(IdempotentSessionWizardView): + """ + View for changing the two-factor authentication method from phone number to token generator. + """ + template_name = 'two_factor/core/setup_reset_generator.html' + success_url = settings.LOGIN_REDIRECT_URL + qrcode_url = 'two_factor:qr' + session_key_name = 'django_two_factor-qr_secret_key' + + form_list = ( + ('generator', TOTPDeviceForm), + ) + + def done(self, form_list, **kwargs): + """ + Finish the wizard. Save all forms and redirect. + """ + # Delete previous device + self.delete_previous_device() + + # Remove secret key used for QR code generation + try: + del self.request.session[self.session_key_name] + except KeyError: + pass + + form = [form for form in form_list if isinstance(form, TOTPDeviceForm)][0] + device = form.save() + django_otp.login(self.request, device) + return redirect(self.success_url) + + def get_form_kwargs(self, step=None): + kwargs = {} + kwargs.update({ + 'key': self.get_key(step), + 'user': self.request.user, + }) + return kwargs + + def get_key(self, step): + self.storage.extra_data.setdefault('keys', {}) + if step in self.storage.extra_data['keys']: + return self.storage.extra_data['keys'].get(step) + key = random_hex_str(20) + self.storage.extra_data['keys'][step] = key + return key + + def get_context_data(self, form, **kwargs): + context = super().get_context_data(form, **kwargs) + key = self.get_key('generator') + rawkey = unhexlify(key.encode('ascii')) + b32key = b32encode(rawkey).decode('utf-8') + self.request.session[self.session_key_name] = b32key + context.update({ + 'QR_URL': reverse(self.qrcode_url) + }) + context['cancel_url'] = resolve_url(settings.LOGIN_REDIRECT_URL) + + return context + + +@class_view_decorator(never_cache) +@class_view_decorator(login_required) +class ResetSetupPhoneView(PhoneSetupView): + """ + View for changing the two-factor authentication method from token generator to phone number. + """ + template_name = 'two_factor/core/setup_reset_phone.html' + + def get_device(self, **kwargs): + """ + Uses the data from the setup step and generated key to recreate device. + """ + kwargs = kwargs or {} + kwargs['name'] = 'default' + kwargs['user'] = self.request.user + kwargs.update(self.storage.validated_step_data.get('setup', {})) + return PhoneDevice(key=self.get_key(), **kwargs) + + def done(self, form_list, **kwargs): + """ + Finish the wizard. Save all forms and redirect. + """ + self.delete_previous_device() + # Create new device for user + device = self.get_device(user=self.request.user) + device.save() + django_otp.login(self.request, device) + return redirect(self.success_url) + + @class_view_decorator(never_cache) @class_view_decorator(login_required) class QRGeneratorView(View): diff --git a/two_factor/views/utils.py b/two_factor/views/utils.py index ad3b92bfa..ad8462e29 100644 --- a/two_factor/views/utils.py +++ b/two_factor/views/utils.py @@ -8,6 +8,7 @@ from django.contrib.auth import load_backend from django.core.exceptions import SuspiciousOperation from django.core.signing import BadSignature, SignatureExpired +from django_otp import devices_for_user, user_has_device from django.utils import baseconv from django.utils.decorators import method_decorator from django.utils.encoding import force_bytes @@ -81,6 +82,13 @@ class IdempotentSessionWizardView(SessionWizardView): storage_name = 'two_factor.views.utils.ExtraSessionStorage' idempotent_dict = {} + def delete_previous_device(self): + # Delete the previous device associated to the user if you want to change device + if user_has_device(self.request.user): + devices = devices_for_user(self.request.user) + for current_device in devices: + current_device.delete() + def is_step_visible(self, step): """ Returns whether the given `step` should be included in the wizard; it