Skip to content

Commit ce9496a

Browse files
GautierPetrDlouhy
authored andcommitted
Demonstrate #473 - Valid backup authentication codes don't log the user in.
1 parent 9159d1c commit ce9496a

File tree

1 file changed

+90
-1
lines changed

1 file changed

+90
-1
lines changed

tests/test_views_login.py

+90-1
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,8 @@ def test_with_backup_phone(self, mock_signal, fake):
364364

365365
# Valid token should be accepted.
366366
response = self._post({'token-otp_token': totp_str(device.bin_key),
367-
'login_view-current_step': 'token'})
367+
'login_view-current_step': 'token',
368+
'device_id': device.persistent_id})
368369
self.assertRedirects(response, resolve_url(settings.LOGIN_REDIRECT_URL))
369370
self.assertEqual(device.persistent_id,
370371
self.client.session.get(DEVICE_ID_SESSION_KEY))
@@ -406,6 +407,94 @@ def test_with_backup_token(self, mock_signal):
406407
# Check that the signal was fired.
407408
mock_signal.assert_called_with(sender=mock.ANY, request=mock.ANY, user=user, device=device)
408409

410+
def test_totp_token_does_not_impact_backup_token(self):
411+
"""
412+
Ensures that successfully authenticating with a TOTP token does not
413+
inadvertently increase the throttling count of the backup token device.
414+
415+
Addresses issue #473, where correct TOTP token usage was unintentionally
416+
affecting the throttling count of backup tokens, potentially leading to
417+
their invalidation.
418+
"""
419+
user = self.create_user()
420+
backup_device = user.staticdevice_set.create(name='backup')
421+
backup_device.token_set.create(token='abcdef123')
422+
totp_device = user.totpdevice_set.create(name='default', key=random_hex())
423+
424+
response = self._post({'auth-username': '[email protected]',
425+
'auth-password': 'secret',
426+
'login_view-current_step': 'auth'})
427+
self.assertContains(response, 'Token:')
428+
429+
backup_device.refresh_from_db()
430+
self.assertEqual(backup_device.throttling_failure_count, 0)
431+
response = self._post({'token-otp_token': totp_str(totp_device.bin_key),
432+
'login_view-current_step': 'token'})
433+
self.assertRedirects(response, resolve_url(settings.LOGIN_REDIRECT_URL))
434+
self.assertEqual(self.client.session['_auth_user_id'], str(user.pk))
435+
436+
backup_device.refresh_from_db()
437+
self.assertEqual(backup_device.throttling_failure_count, 0)
438+
439+
def test_wrong_token_does_not_affect_other_device_throttling(self):
440+
"""
441+
Tests that entering an incorrect backup token increases the throttling count
442+
of the backup device, but does not affect the TOTP device's throttling count (and other way around).
443+
444+
This addresses issue #473 where TOTP token submissions were incorrectly
445+
impacting the backup device's throttling count.
446+
"""
447+
# Setup: Create a user, backup device, and TOTP device.
448+
user = self.create_user()
449+
backup_device = user.staticdevice_set.create(user=user, name='backup')
450+
backup_token = 'abcdef123'
451+
backup_device.token_set.create(token=backup_token)
452+
totp_device = user.totpdevice_set.create(
453+
user=user, name='default', confirmed=True,
454+
key=random_hex()
455+
)
456+
457+
# Simulate login process: username and password step.
458+
response = self._post({
459+
'auth-username': user.get_username(),
460+
'auth-password': 'secret',
461+
'login_view-current_step': 'auth',
462+
})
463+
self.assertContains(response, 'Token:')
464+
465+
# Attempt login with incorrect backup token and check response.
466+
response = self._post({
467+
'backup-otp_token': 'WRONG',
468+
'login_view-current_step': 'backup',
469+
})
470+
expected_error = 'Invalid token. Please make sure you have entered it correctly.'
471+
self.assertEqual(response.context_data['wizard']['form'].errors,
472+
{'__all__': [expected_error]})
473+
474+
# Verify that incorrect backup token submission throttles backup device.
475+
backup_device.refresh_from_db()
476+
self.assertEqual(backup_device.throttling_failure_count, 1)
477+
478+
# Ensure TOTP device is not affected by the incorrect backup token submission.
479+
totp_device.refresh_from_db()
480+
self.assertEqual(totp_device.throttling_failure_count, 0)
481+
482+
# Attempt login with incorrect TOTP token and check response.
483+
response = self._post({
484+
'token-otp_token': "123456",
485+
'login_view-current_step': 'token'
486+
})
487+
self.assertEqual(response.context_data['wizard']['form'].errors,
488+
{'__all__': [expected_error]})
489+
490+
# Backup device's throttling count should remain unchanged after TOTP token submission.
491+
backup_device.refresh_from_db()
492+
self.assertEqual(backup_device.throttling_failure_count, 1)
493+
494+
# Incorrect TOTP token submission should throttle TOTP device.
495+
totp_device.refresh_from_db()
496+
self.assertEqual(totp_device.throttling_failure_count, 1)
497+
409498
@mock.patch('two_factor.views.utils.logger')
410499
def test_reset_wizard_state(self, mock_logger):
411500
self.create_user()

0 commit comments

Comments
 (0)