Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implementing g-recaptcha-response validation #417

Open
heliusAurelius opened this issue Nov 18, 2021 · 9 comments
Open

Implementing g-recaptcha-response validation #417

heliusAurelius opened this issue Nov 18, 2021 · 9 comments

Comments

@heliusAurelius
Copy link

I managed to implement a simple Google reCAPTCHA validation in the form by adding in the reCAPTCHA, and using javascript to enable the submit button once it is solved. Doing so, captures the reCAPTCHA response in the sheet and email notification. But it seems that there are some bots which account for this and 're-enable' disabled submit buttons as well as ignoring my honeypot in the form.

Is there a way to add the required attribute to this reCAPTCHA response to prevent users/bots from simply re-enabling the submit button by editing the page code? When I add 'required' to the reCAPTCHA div, it doesn't validate the same way as the rest of the fields, and adding a script to return onsubmit="false" for the form seems to fire after the action="[google script]".

My form code is as follows:

<form class="gform" action="[google script URL]" method="POST">
	<div class="form-group" hidden>
		<label>
			Validation
		</label>
		<input name="honeypot" class="form-control"/>
	</div>
	<div class="form-group">
		<label>
			Name
		</label>
		<input name="Name" class="form-control" required />
	</div>
	<div class="form-group">
		<label>
			Email
		</label>
		<input name="Email" class="form-control" type="email" required />
	</div>
	<div class="form-group">
		<label>
			Message
		</label><textarea name="Message" class="form-control" rows="4" cols="50" required ></textarea>
	</div>
	<div align="center" class="g-recaptcha" data-sitekey="[captcha public key]" data-callback="callback"></div>
	<br />
	<button id="submit-button" class="btn-islamic-green btn-rd bloc-button btn btn-d btn-lg btn-block" type="submit" disabled="disabled">
			Submit
	</button>
	<div style="display:none" class="thankyou_message">
	<p class="text-center">Thanks for reaching out! I look forward to connecting with you soon!</p>
	</div>
</form>

I use page-level JS to re-enable the submit button once the reCAPTCHA is solved

<script type="text/javascript">
	function callback() {
	  const submitButton = document.getElementById("submit-button");
	  submitButton.disabled = false;
	}
</script>
@bledatunay
Copy link

Doesn't really matter if you disable or even completely hide the submit button (from human eyes), bots just care about the gform link inside the HTML. Using JavaScript for validation is meaningless, as bots bypass these steps anyways - I've seen bots spamming with honeypot field filled (Fun fact: It even adds a new honeypot column to the contact form sheet).

As far as I can tell, validation absolutely needs to happen inside the Google Apps Script (.gs), and CORS is simply not letting it. Anything besides making that happen looks like a dead end to me.

@heliusAurelius
Copy link
Author

Has anyone been successful implementing this validation in Google scripts yet? I found this tutorial, but haven’t been successful with my project yet http://www.googleappsscript.org/recent-additions/recaptchawithgoogleappsscript

@bledatunay
Copy link

In the link you have provided, it would work because everything happens (including the user interaction) in the Google Script thus inside the parent domain (which is x.google.com), but again due to CORS restrictions, this won't work when your website on your own domain (which can't be x.google.com) requests it.

@mckennapsean
Copy link
Collaborator

the tutorial that @heliusAurelius posted does seem to allow custom domains (does that still not work with CORS though? it is possible that could still be a blocker)

I'd be curious if it could work, but I have not tried to see what happens in this case. it would be neat to have recaptcha built in, and it would make a nice enhancement for an advanced section of our tutorial here!

@bledatunay
Copy link

bledatunay commented Jan 16, 2022

@mckennapsean Okay, so after you have said that it seems to allow custom domains, I have examined it again. It honestly looked like you were right, so I decided to give it another go. This time, voilà, it worked. My previous attempt may have been troubled by a completely different error, of which I may have falsely presumed as CORS related - I honestly don't know at this point.

Anyways, here's how I did it.

In form-submission-handler.js, I passed the g-recaptcha-response as a seperate form-specific value.

data.gRecaptchaResponse = document.getElementById("g-recaptcha-response").value;

In .gs, I assigned a variable to it in the very first line of doPost.

var r = String(e.parameters.gRecaptchaResponse);

Verified it with the function below just like in the tutorial @heliusAurelius posted.

function verifyCaptcha(r){
  var pl = {
    'secret' : secret,
    'response': r
  }
  var url = 'https://www.google.com/recaptcha/api/siteverify';
  var resp = UrlFetchApp.fetch(url, {
    payload : pl,
    method : 'POST'
  }).getContentText();
  return JSON.parse(resp).success;
}

Then bounded our main function to the verification.

if (verifyCaptcha(r)){
  try { 
  ...
  } else return;
}

Also, we can add the following into form-submission-handler.js to catch errors and give visitors some feedback.

xhr.onerror = function() {
  //error stuff
}

This works in a variety of conditions, like an expired captcha or a JavaScript blocker on the visitors browser.

Any submission attempts without the solved recaptcha will also return this, but it is preferable that we enable the submit button after user solves the recaptcha anyways.

We can disable the button in the function loaded() of form-submission-handler.js, enable it with data-callback when captcha is solved, and disable it again with data-expired-callback when it expires.

Only bots will be able to attempt submitting a response without solving the recaptcha, but we finally don't need to care about them.

I will now push it to my master and try it out for a few days - will try to give an update with my results.

@bledatunay
Copy link

Update: Not a single spam for 5 days straight (it was around 2-5 spams/day). I have tried submitting forms to myself from different devices (Android, iOS, Windows 10, Windows 7) with different browsers (Firefox, Chrome, Opera) - no issues so far.

@heliusAurelius
Copy link
Author

@bledatunay I was able to follow along with your guide up until the "bounded our main function to the verification" section. Where in the gscript do I add this code?

@bledatunay
Copy link

You will be putting your whole doPost into an if statement which verifies the captcha. It will look like this:

function doPost(e) {
  var r = String(e.parameters.gresp);
  if (verifyCaptcha(r)){
  try {
    Logger.log(e);
    record_data(e);
    
    var mailData = e.parameters;

    var orderParameter = e.parameters.formDataNameOrder;
    var dataOrder;
    if (orderParameter) {
      dataOrder = JSON.parse(orderParameter);
    }
    var sendEmailTo = (typeof TO_ADDRESS !== "undefined") ? TO_ADDRESS : mailData.formGoogleSendEmail;
    
    if (sendEmailTo) {
      MailApp.sendEmail({
        to: String(sendEmailTo),
        subject: "Contact form submitted",
        htmlBody: formatMailBody(mailData, dataOrder)
      });
    }

    return ContentService
          .createTextOutput(
            JSON.stringify({"result":"success",
                            "data": JSON.stringify(e.parameters) }))
          .setMimeType(ContentService.MimeType.JSON);
  } catch(error) {
    Logger.log(error);
    return ContentService
          .createTextOutput(JSON.stringify({"result":"error", "error": error}))
          .setMimeType(ContentService.MimeType.JSON);
  }} else return;
}

@heliusAurelius
Copy link
Author

@bledatunay worked like a charm! Thank you!

I'll be testing over the next few days to ensure this recaptcha is working to prevent spam submissions as well

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants