Honeypot form element

Vote:
 

Since Captcha isn't compliant with WCAG and my customer doesn't like the idea of google recaptcha I've decided to do a honeypot element instead.

I've created the element and am no trying to validate it before it sends out. It's all sent by mail.

Now when the forms get's submitted I've managed to hit the run method but this is where I get stuck. I don't now how to handle it from here. 

Sorry if the code is messy, been trying all kinds of different solutions since I'm fairly new to all this, mostly there for example.

But basically what I want is if the honeypot contains any value i don't want to send the mail but it should look like everything went well for the user. 

    public class EpiFormsHoneyPotActor : SendEmailAfterSubmissionActor
    {
        private readonly Injected<IFormDataRepository> _formDataRepository;
        public override object Run(object input)
        {            
            var result = new SubmissionActorResult { CancelSubmit = false, ErrorMessage = string.Empty };

            var submittedData = _formDataRepository.Service.TransformSubmissionDataWithFriendlyName(
                SubmissionData.Data, SubmissionFriendlyNameInfos, true).ToList();            

            var honeyPot = submittedData.FirstOrDefault(x => x.Key == "HoneypotElementBlock");
            if (honeyPot.Value != null && honeyPot.Value.ToString().Length > 0)
            {
            }

            result.CancelSubmit = true;
            result.ErrorMessage = "shit hit the fan";

            return string.Empty;
        }
    }

Any help is appreciated.

Thanks

#221736
Edited, Apr 24, 2020 7:42
Vote:
 

Hi Erik,

The problem with this approach, as you probably noted, is that returning a SubmissionActorResult with a CancelSubmit value of true gives you an error message. To fix this, on your success path you could call base.Run(input) and on your failure path you could return null:

var honeypot = submittedData.FirstOrDefault(x => x.Key == "HoneypotElementBlock");

if (honeypot.Value != null && honeypot.Value.ToString().Length > 0)
{
    return base.Run(input);
}

return null;

I think this'll get the Actor you have above working as expected. However, it's worth noting that at this point your submission has already been saved to the database (the SaveDataToStorageActor runs first). That may or may not be problematic depending on whether you care about that spam data being saved anywhere. Any other actors you have may also have been executed at this point, which could be an issue if you intend to use webhooks in the future for example.

In an ideal world you'd register an Actor to execute before the SaveDataToStorageActor (which can be done by implementing the ISyncOrderedSubmissionActor), but as already mentioned whilst setting the CancelSubmit to true will stop the rest of the actors but present the user with an error.

I think the best option (probably) is to move your logic to a custom ActorsExecutingService, something like this:

public class HoneypotActorsExecutingService : ActorsExecutingService
{
    private readonly IFormDataRepository _formDataRepository;
    private readonly IFormRepository _formRepository;

    public HoneypotActorsExecutingService(IFormDataRepository formDataRepository, IFormRepository formRepository)
    {
        _formDataRepository = formDataRepository;
        _formRepository = formRepository;
    }

    public override IEnumerable<object> ExecuteActors(Submission submission, FormContainerBlock formContainer,
        FormIdentity formIden, HttpRequestBase request, HttpResponseBase response, bool isFormFinalizedSubmission)
    {
        if (HasValidHoneypot(submission, formIden))
        {
            return base.ExecuteActors(submission, formContainer, formIden, request, response,
                isFormFinalizedSubmission);
        }

        return new object[0];
    }

    /// <summary>
    /// Indicates whether the form has a completed honeypot element.
    /// </summary>
    /// <param name="submission">The submission.</param>
    /// <param name="formIden">The form identity.</param>
    /// <returns><c>true</c> if the form has a valid honeypot element, otherwise <c>false</c></returns>
    private bool HasValidHoneypot(Submission submission, FormIdentity formIden)
    {
        var dataFriendlyNameInfos = _formRepository.GetDataFriendlyNameInfos(formIden);

        var submittedData = _formDataRepository.TransformSubmissionDataWithFriendlyName(
            submission.Data, dataFriendlyNameInfos, true).ToList();

        // The form doesn't contain a honeypot element, return true
        if (!submittedData.Any(x => x.Key.Equals(nameof(HoneypotElementBlock), StringComparison.Ordinal)))
        {
            return true;
        }

        var honeypot =
            submittedData.FirstOrDefault(x => x.Key.Equals(nameof(HoneypotElementBlock), StringComparison.Ordinal));

        return !string.IsNullOrEmpty(honeypot.Value?.ToString());
    }
}

This prevents any actors being executed if the honeypot trap has been triggered.

You'll need to register this with a configurable module:

[InitializableModule]
public class DependencyResolverInitialization : IConfigurableModule
{
    public void ConfigureContainer(ServiceConfigurationContext context)
    {
        context.ConfigurationComplete += (o, e) =>
        {
            context.Services.AddSingleton<ActorsExecutingService, HoneypotActorsExecutingService>();
        };
    }

    public void Initialize(InitializationEngine context) {}

    public void Uninitialize(InitializationEngine context) {}
}

Hope that works out for you!

#221885
Edited, Apr 27, 2020 16:11
Vote:
 

Hi Jake. 

Thank you, it works perfectly!

Well put, I had also figured out I had to add a own custom actor that way but your solution is much better with handling the other actors besides the mailactor. 

#221910
Apr 28, 2020 6:27
Vote:
 

Hi Jake

Great solution. Just what I was looking for :)

/Thomas

#223498
May 28, 2020 20:09
This topic was created over six months ago and has been resolved. If you have a similar question, please create a new topic and refer to this one.
* You are NOT allowed to include any hyperlinks in the post because your account hasn't associated to your company. User profile should be updated.