django-two-factor-auth icon indicating copy to clipboard operation
django-two-factor-auth copied to clipboard

No message due to failed attempts

Open pbakiewicz opened this issue 4 years ago • 2 comments

Expected Behavior

When settings OTP_HOTP_THROTTLE_FACTOR is not 0 user after failed attempts should get error message:

Verification temporarily disabled because of {n} failed attempts, please try again soon.

Current Behavior

User gets misleading error:

Invalid token. Please make sure you have entered it correctly.

Possible Solution

Problem is that in AuthenticationTokenForm we don't have a field for user selected device which OTPAuthenticationFormMixin requires:

        #. Define three additional form fields::

            otp_device = forms.CharField(required=False, widget=forms.Select)

and therefore later in method _verify_token it never triggers device.verify_is_allowed() since device at this point will always be None and it tries to match the token with all devices, and if it fails return generic Invalid token error whereas it should return Verification disabled due to failed attempts.

To Fix this issue, we could simply override in AuthenticationTokenForm method _chosen_device:

    def _chosen_device(self, user):
        device = super()._chosen_device(user)
        if not device:
            device_qs = TOTPDevice.objects.devices_for_user(user, confirmed=True)
            return device_qs.last()
        return device

In that case, when device is not specified it takes the last confirmed one (if exists) and later properly triggers for it verify_is_allowed. Therefore when user already had few failed attempts doesn't get a misleading Invalid token when she is sure the token is correct.

pbakiewicz avatar Jul 17 '20 10:07 pbakiewicz

There is also a second way, we already pass initial_device to AuthenticationTokenForm we could simply save it:

self.initial_device = initial_device

and overload _chosen_device this way:

    def _chosen_device(self, user):
        device = super()._chosen_device(user)
        if not device:
            device = self.initial_device
        return device

If someone says that this wouldn't work properly if someone has multiple devices, we can even go a step further and add:

    def _chosen_device(self, user):
        device = super()._chosen_device(user)
        if not device and count(TOTPDevice.objects.devices_for_user(user, confirmed=True)) == 1:
            device = self.initial_device
        return device

To make sure that if user has more than one device and didn't select any match_token method will be invoked.

I think we should implement something along this lines since I guess most implementation are single device only, and for those user shouldn't get invalid token error when she should get Too many failed attempts. Wait a moment.

pbakiewicz avatar Jul 17 '20 14:07 pbakiewicz

Related #299

moggers87 avatar Jul 17 '20 14:07 moggers87

What is the status of this? How can I get correct failure message when throttle limit os exceeded?

hauva007 avatar Jul 25 '23 11:07 hauva007

@claudep I think this issue is solved by https://github.com/jazzband/django-two-factor-auth/pull/626 too and can be closed

mlec1 avatar Aug 12 '23 19:08 mlec1