diff --git a/allauth_2fa/app_settings.py b/allauth_2fa/app_settings.py index d3975bf..014de06 100644 --- a/allauth_2fa/app_settings.py +++ b/allauth_2fa/app_settings.py @@ -45,6 +45,14 @@ def REQUIRE_OTP_ON_DEVICE_REMOVAL(self) -> bool: True, ) + @property + def BACKUP_TOKENS_NUMBER(self) -> int: + return getattr( + settings, + "ALLAUTH_2FA_BACKUP_TOKENS_NUMBER", + 3, + ) + _app_settings = _AppSettings() diff --git a/allauth_2fa/views.py b/allauth_2fa/views.py index a0eb586..8f245c3 100644 --- a/allauth_2fa/views.py +++ b/allauth_2fa/views.py @@ -180,7 +180,7 @@ def get_context_data(self, **kwargs): def post(self, request, *args, **kwargs): static_device, _ = request.user.staticdevice_set.get_or_create(name="backup") static_device.token_set.all().delete() - for _ in range(3): + for _ in range(app_settings.BACKUP_TOKENS_NUMBER): static_device.token_set.create(token=StaticToken.random_token()) self.reveal_tokens = True return self.get(request, *args, **kwargs) diff --git a/docs/configuration.rst b/docs/configuration.rst index ff267b4..11d0497 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -41,3 +41,10 @@ Possible keys (and default values): * ``authenticate``: :class:`allauth_2fa.forms.TOTPAuthenticateForm` * ``setup``: :class:`allauth_2fa.forms.TOTPDeviceForm` * ``remove``: :class:`allauth_2fa.forms.TOTPDeviceRemoveForm` + +``ALLAUTH_2FA_BACKUP_TOKENS_NUMBER`` +---------------------------------- + +Sets the number of generated backup tokens. + +Defaults to ``3``. diff --git a/tests/test_allauth_2fa.py b/tests/test_allauth_2fa.py index 6a8136e..edfdd18 100644 --- a/tests/test_allauth_2fa.py +++ b/tests/test_allauth_2fa.py @@ -30,6 +30,7 @@ ] TWO_FACTOR_AUTH_URL = reverse_lazy("two-factor-authenticate") +TWO_FACTOR_BACKUP_TOKENS_URL = reverse_lazy("two-factor-backup-tokens") TWO_FACTOR_SETUP_URL = reverse_lazy("two-factor-setup") LOGIN_URL = reverse_lazy("account_login") JOHN_CREDENTIALS = {"login": "john", "password": "doe"} @@ -321,6 +322,23 @@ def test_not_configured_redirect(client, john): assertRedirects(resp, TWO_FACTOR_SETUP_URL, fetch_redirect_response=False) +def test_backup_tokens_number(client, john_with_totp): + """Tests that the configured number of tokens is sent.""" + user, totp_device, static_device = john_with_totp + login(client, expected_redirect_url=TWO_FACTOR_AUTH_URL) + do_totp_authentication( + client, + totp_device=totp_device, + expected_redirect_url=settings.LOGIN_REDIRECT_URL, + ) + resp = client.post(TWO_FACTOR_BACKUP_TOKENS_URL) + assert len(resp.context_data["backup_tokens"]) == 3 + + with override_settings(ALLAUTH_2FA_BACKUP_TOKENS_NUMBER=10): + resp = client.post(TWO_FACTOR_BACKUP_TOKENS_URL) + assert len(resp.context_data["backup_tokens"]) == 10 + + class Require2FA(BaseRequire2FAMiddleware): def require_2fa(self, request): return True