djangoformsrecaptchaenterprise

reCaptcha Enterprise in django forms without django-recaptcha


How can I implement new reCaptcha Enterprise within django on the form without using old django-recaptcha?


Solution

  • Ive deleted django-recaptcha = "==2.0.6" that used v2 or v3 reCaptcha not Enterprise (. But I`ve used logic of integration reCaptcha from there). For It:

    
    RECAPTCHA_CREDENTIALS_JSON = ast.literal_eval(
        os.getenv("RECAPTCHA_CREDENTIALS_JSON", "{}",)
        .replace("\r", "\\r")
        .replace("\n", "\\n")
    )
    

    RECAPTCHA_CREDENTIALS_JSON should be provided as string like:

    RECAPTCHA_CREDENTIALS_JSON="{ "type": "service_account", "project_id":..., "private_key": "..." ...., }"
    

    In Forms.py:

    from google.cloud import recaptchaenterprise_v1
    
    RECAPTCHA_ACTION = "login"
    
    if settings.RECAPTCHA_CREDENTIALS_JSON and settings.RECAPTCHA_PUBLIC_KEY:
        client = recaptchaenterprise_v1.RecaptchaEnterpriseServiceClient.from_service_account_info(
            settings.RECAPTCHA_CREDENTIALS_JSON
        )
    else:
        client = None
    

    Method that check result for captcha-field:

    def create_assessment(token):
        """Create an assessment to analyze the risk of a UI action.
        Args:
        projectID: GCloud Project ID
        recaptchaSiteKey: Site key obtained by registering a domain/app to use recaptcha services.
        token: The token obtained from the client on passing the recaptchaSiteKey.
        recaptchaAction: Action name corresponding to the token.
        Return:
        bool: True if successful.
        """
        project_id = "....."
        recaptcha_site_key = settings.RECAPTCHA_PUBLIC_KEY
        recaptcha_action = RECAPTCHA_ACTION
    
        # Set the properties of the event to be tracked.
        event = recaptchaenterprise_v1.Event(expected_action=recaptcha_action)
        event.site_key = recaptcha_site_key
        event.token = token
    
        assessment = recaptchaenterprise_v1.Assessment()
        assessment.event = event
    
        project_name = f"projects/{project_id}"
    
        # Build the assessment request.
        request = recaptchaenterprise_v1.CreateAssessmentRequest()
        request.assessment = assessment
        request.parent = project_name
    
        response = client.create_assessment(request)
    
        # Check if the token is valid.
        if not response.token_properties.valid:
            logger.info(
                "The CreateAssessment call failed because the token was "
                + "invalid for for the following reasons: "
                + str(response.token_properties.invalid_reason)
            )
            return None
        return True
    

    The method above receive token that we should get from "widget". Widget code:

    class ReCaptchaBase(forms.widgets.Widget):
        """
        Base widget to be used for Google ReCAPTCHA.
        public_key -- String value: can optionally be passed to not make use of the
            project wide Google Site Key.
        """
        recaptcha_response_name = "g-recaptcha-response"
        template_name = "oauth2_provider/widget_v2_checkbox.html"
    
        def value_from_datadict(self, data, files, name):
            return data.get(self.recaptcha_response_name, None)
    
        def build_attrs(self, base_attrs, extra_attrs=None):
            attrs = super(ReCaptchaBase, self).build_attrs(base_attrs, extra_attrs)
            attrs["data-callback"] = base_attrs.get("data-callback", "onloadCallback")
            attrs["sitekey"] = base_attrs.get("sitekey", settings.RECAPTCHA_PUBLIC_KEY)
            attrs["theme"] = base_attrs.get("theme", "light")
            attrs["action"] = base_attrs.get("action", RECAPTCHA_ACTION)
            return attrs
    

    Add the field to the our form, Ive been rewriting the PasswordResetForm. Ive added it like a CharFiled within our widget.

    class ReCaptchaField(forms.CharField):
    
        widget = ReCaptchaBase()
    
        def validate(self, value):
            if not create_assessment(value):
                raise forms.ValidationError(
                    self.error_messages["required"], code="required"
                )
            return value
    

    Form:

    class CustomPasswordResetForm(PasswordResetForm):
        """Reset password form override"""
    
        def __init__(self, *args, **kwargs):
            super(CustomPasswordResetForm, self).__init__(*args, **kwargs)
            if not settings.RECAPTCHA_CREDENTIALS_JSON or not settings.RECAPTCHA_PUBLIC_KEY:
                self.fields.pop("captcha")
    
        captcha = ReCaptchaField()
    

    Add captcha block to the form in main template password_reset_form.html:

    {% block captcha %}
    <div class="justify-center">
        <div class="w-full flex mt-3 justify-center">
            {{ form.captcha }}
        </div>
        <div class="w-full flex mt-3 justify-center">
            <p>{{ form.errors.captcha }}</p>
        </div>
    </div>
    {% endblock captcha %}
    

    template for widget widget_v2_checkbox.html:

    <script src="https://www.google.com/recaptcha/enterprise.js?onload=onloadCallback&render=explicit" async defer></script>
    <script type="text/javascript">
        // Submit function to be called, after reCAPTCHA was successful.
        var onloadCallback = function () {
            grecaptcha.enterprise.render('id_captcha', {
                'sitekey': '{{ widget.attrs.sitekey }}',
                'action': '{{ widget.attrs.action }}',
                'theme': '{{ widget.attrs.theme }}',
            });
        };
    </script>
    <div class="captcha" id="id_captcha" 
    {% for name, value in widget.attrs.items %} 
        {% if value is not False %} {{name}} 
        {% if value is not True %}="{{ value|stringformat:'s' }}"
        {% endif %}{% endif %}
    {% endfor %}>
    </div>