Search code examples
djangorecaptcha

Hide reCatpcha error message if user has 3 or less failed login attempts in Django


I'm a beginner with Django and have a login page and when the user has over 3 failed login attempts the login page will then display reCaptcha which works fine.

However, my login page still displays the error message for invalid captcha on every login attempt regardless of logging in successfully or not (this error message is from recaptcha decorator - 'Invalid reCAPTCHA. Please try again.', although lets me login as I have coded) - I would like to hide this specific error message from my login page unless the user has over 3 failed login attempts as well like how I display the actual captcha on the page.

I am using django axes in checking failed login attempts, and have disabled user lock which was not needed.

I'd greatly apprecaite any help with this.

Urls

from django.urls import path, include

from django.contrib.auth import views as auth_views

from . import views
from .views import RequestPasswordResetEmail, CompletePasswordReset
#from .forms import EmailValidationOnForgotPassword

urlpatterns = [
    path('login/', views.loginpage, name="login"),
    path('logout/', views.logoutuser, name="logout"),
]

recaptcha decorator.py

from functools import wraps

from django.conf import settings
from django.contrib import messages

import requests

def check_recaptcha(view_func):
    @wraps(view_func)
    def _wrapped_view(request, *args, **kwargs):
        request.recaptcha_is_valid = None
        if request.method == 'POST':
            recaptcha_response = request.POST.get('g-recaptcha-response')
            data = {
                'secret': settings.RECAPTCHA_PRIVATE_KEY,
                'response': recaptcha_response
            }
            r = requests.post('https://www.google.com/recaptcha/api/siteverify', data=data)
            result = r.json()
            if result['success']:
                request.recaptcha_is_valid = True
            else:
                request.recaptcha_is_valid = False
                messages.error(request, 'Invalid reCAPTCHA. Please try again.')
        return view_func(request, *args, **kwargs)
    return _wrapped_view

View

from django.conf import settings
from .decorators import check_recaptcha
from django.shortcuts import render, redirect
from django.contrib import messages, auth
from django.contrib.auth import authenticate, login, logout
from django.views import View
from .decorators import check_recaptcha
from axes.decorators import axes_dispatch
from axes.attempts import (
    clean_expired_user_attempts,
    get_user_attempts,
    reset_user_attempts,
)


@axes_dispatch
@check_recaptcha
def loginpage(request, credentials: dict = None):

    attempts_list = get_user_attempts(request, credentials)

    attempt_count = max(
        (
                attempts.aggregate(Sum("failures_since_start"))[
                    "failures_since_start__sum"
                ]
                or 0
        )
        for attempts in attempts_list
    )
    print(attempt_count)

    attempt_count = attempt_count +1

    context = {'attempt_count': attempt_count}

    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')

        user = authenticate(request, username=username, password=password)

        if user is not None:

            if user.is_active and attempt_count <= 3:
                request.recaptcha_is_valid = True
                login(request, user)
                return redirect('home')

            if user.is_active and attempt_count > 3 and request.recaptcha_is_valid:
                login(request, user)
                return redirect('home')

            if not user.is_active:
                messages.error(request, 'Account for this User has not been Activated.')
        else:
            messages.error(request, 'Username or Password is Incorrect.')

    print(context)
    return render(request, 'login.html', context)

Template login

{% extends 'base.html' %}

{% block title %}User Login{% endblock %}

{% block content %}
    <div class="d-flex justify-content-center">
        <h3 id="form-title">Login</h3>
    </div>
    <div class="d-flex justify-content-center form_container">
        <form method="POST" action="">
            {% csrf_token %}

<div align="center">
            <div class="input-group mb-2">
                <div class="input-group-append">
                    <span class="input-group-text"><i class="fas fa-user"></i></span>
                </div>
                <input type="text" name="username" placeholder="Email" class="form-control">
            </div>



            <div class="input-group mb-3">
                <div class="input-group-append">
                    <span class="input-group-text"><i class="fas fa-key"></i></span>
                </div>
                <input type="password" name="password" placeholder="Password" class="form-control">
            </div>
</div>


 {% include 'recaptcha.html' %}


            <div class="d-flex justify-content-center mt-3 login_container">
                <input class="btn login_btn" type="submit" value="Login">
            </div>



        </form>
    </div>

    {% for message in messages %}
    
    <p>{{ message }}</p> 

    {% endfor %}



    <div class="input-group mb-1">
    </div>
    <div class="mt-1">
        <div class="d-flex justify-content-center links">
            Don't have an account? <a href="{% url 'register' %}" class="ml-2">Sign Up</a>
        </div>
        <div class="d-flex justify-content-center links">
            Forgot Password? <a href="{% url 'request-password' %}" class="ml-2">Reset Password</a>
        </div>
    </div>

{% endblock content %}


Solution

  • I managed to solve this by updating the decorator to only display the error message after 4 login attempt failures as follows:

    recaptcha decorator.py

    from django.conf import settings
    from django.contrib import messages
    from django.db.models import Sum
    from functools import wraps
    
    
    from axes.attempts import (
        get_user_attempts,
    )
    
    import requests
    
    
    def check_recaptcha(view_func, credentials: dict = None):
        @wraps(view_func)
        def _wrapped_view(request, *args, **kwargs):
    
            attempts_list = get_user_attempts(request, credentials)
    
            attempt_count = max(
                (
                        attempts.aggregate(Sum("failures_since_start"))[
                            "failures_since_start__sum"
                        ]
                        or 0
                )
                for attempts in attempts_list
            )
            print(attempt_count)
    
            attempt_count = attempt_count + 1
    
    
    
            request.recaptcha_is_valid = None
            if request.method == 'POST':
                recaptcha_response = request.POST.get('g-recaptcha-response')
                data = {
                    'secret': settings.RECAPTCHA_PRIVATE_KEY,
                    'response': recaptcha_response
                }
                r = requests.post('https://www.google.com/recaptcha/api/siteverify', data=data)
                result = r.json()
                if result['success']:
                    request.recaptcha_is_valid = True
                else:
                    request.recaptcha_is_valid = False
    
                    if attempt_count > 3+1:
                        messages.error(request, 'Invalid reCAPTCHA. Please try again.')
    
    
            return view_func(request, *args, **kwargs)
        return _wrapped_view
    

    All works as I required!