Optimizely Forms - Spam in stepped forms

A.D
A.D
Vote:
 

Hi,

Curious about spam prevention for Forms with Step elements. By default, the form data gets saved to the database at each step, which poses an issue with spam prevention given the reCAPTCHA is typically only on the last step, just before the submit button.

So effectively a spam bot does not need to complete a form to spam the database if the form has steps.

I was hoping there's an easy config change to prevent this.

Or do we need to do something like implement a CustomActorsExecutingService to override default behaviour?

Thanks,

#296622
Feb 16, 2023 7:28
Vote:
 

With no additional code could the Retention Policy help you ?

Set the Partial Submission Data for a short length of time i.e. 1 Day and Keep Finalised Submitted Data for longer ? 

#296632
Edited, Feb 16, 2023 12:14
A.D
Vote:
 

Thanks Minesh,

Yeah, we're using retention policy to mitigate the issue, but ideally we need a way to stop any spam getting through in the first place.

So far I've tried removing the 'SaveDataToStorageActor' when the form isn't finalized, but that causes an error.

I've highlighted this issue to Optimizely Support, we see it as pretty high priority.

#296673
Feb 17, 2023 0:23
Vote:
 

I've looked at overriding the implementation of 'SaveDataToStorageActor' there is a property called PerformOnEveryStepSubmission that is set to true and is overridable. However it doesn't matter how I set up the DI for it, the SaveDataToStorageActor during a step is the built in version, but the submit event ends up with the custom version.  So that really didn't help.

public class CustomSaveDataToStorageActor : SaveDataToStorageActor
{
    public override bool PerformOnEveryStepSubmission => false;
}

So I then tried overriding the ActorsExcutingService and removing the SaveDataToStorageActor so it is not actioned.

public class CustomActorsExecutingService : ActorsExecutingService
{
    public override IEnumerable<IPostSubmissionActor> GetFormSubmissionActors(
        Submission submission,
        FormContainerBlock formContainer,
        FormIdentity formIden,
        HttpRequest request,
        HttpResponse response,
        bool isFormFinalizedSubmission)
    {
        var actors = base.GetFormSubmissionActors(submission, formContainer, formIden, request, response, isFormFinalizedSubmission).ToList();
        var saveActor = actors.OfType<SaveDataToStorageActor>().FirstOrDefault();
        if (saveActor is not null && !isFormFinalizedSubmission)
        {
            actors.Remove(saveActor);
        }

        return actors;
    }
}

Unfortunately that results in the submit event err'ing, I'm wondering if at this point its because it's trying to update a record that doesn't exist.

So I think your only option is to implement your own custom FormContainerBlock to override the behaviour of the steps or use Minesh's advice.

#296690
Feb 17, 2023 11:41
A.D
Vote:
 

Thanks Mark,

Yeah, I tried exactly the same thing with removing that SaveDataToStorageActor, came to the same conclusion.

Optimizely support suggested reCAPTCHA V3 can help, it may need to be added for each step. Something to investigate further. They also have the workaround copied below - adding a couple of extra Actors that basically deletes the partial submission immediately after it's stored.

I've tested and it works ok, but we'd rather the data not get into the database at all. I think we may request a new feature where we can switch off partial submissions globally via web config perhaps, with the option to activate per form:

[Serializable] 
    public class RemovePartialSubmissionActor : PostSubmissionActorBase, ISyncOrderedSubmissionActor, ICloneable 

    { 

        public int Order => 5000; 

        public override bool IsSyncedWithSubmissionProcess { get { return true; } } 

        public override bool PerformOnEveryStepSubmission { get { return true; } } 

        public override bool AvailableInEditView { get { return true; } } 

        public override string EditViewFriendlyTitle { get { return "Don't store partial submisison"; } } 



        [Display(Name = "Enabled", Order = 1100)] 

        public virtual bool IsEnabled { get; set; } = true; 



        public override object Run(object input) 

        { 

            var item = SubmissionData; 

            var formId = FormIdentity.Guid; 

            var storeName = "FormData_" + formId.ToString().ToLower(); 

            var cookieName = "Forms_" + formId.ToString().ToLower() + "_"; 



            // Delete incompleted submission from DB 

            if (item.Data.ContainsKey("SYSTEMCOLUMN_FinalizedSubmission") 

                && (item.Data["SYSTEMCOLUMN_FinalizedSubmission"] is bool) 

                && !(bool)item.Data["SYSTEMCOLUMN_FinalizedSubmission"]) 

            { 

                var store = DynamicDataStoreFactory.Instance.GetStore(storeName); 

                if (store != null) 

                { 

                    store.Delete(new Guid(item.Id)); 

                } 

            } 

            else 

            { 

                var c = HttpRequestContext.Cookies.Get(cookieName); 

                if (c!=null) 

                { 

                    c.Value = ""; 

                    HttpResponseContext.Cookies.Set(c); 

                } 

                HttpResponseContext.Cookies.Remove(cookieName); 

            } 

            return 1; 

        } 



        public object Clone() 

        { 

            return new RemovePartialSubmissionActor(); 

        } 

    } 



    [Serializable] 

    public class SavePartialSubmissionActor : PostSubmissionActorBase, ISyncOrderedSubmissionActor, ICloneable 

    { 

        public int Order => 999; 

        public override bool IsSyncedWithSubmissionProcess { get { return true; } } 

        public override bool PerformOnEveryStepSubmission { get { return true; } } 

        public override bool AvailableInEditView { get { return true; } } 

        public override string EditViewFriendlyTitle { get { return "Don't store partial submisison"; } } 



        [Display(Name = "Enabled", Order = 1100)] 

        public virtual bool IsEnabled { get; set; } = true; 



        public override object Run(object input) 

        { 

            var item = SubmissionData; 

            var formId = FormIdentity.Guid; 

            var response = this.HttpResponseContext.Cookies; 

            var request = this.HttpRequestContext.Cookies; 

            var cookieName = "Forms_" + formId.ToString().ToLower() + "_"; 



            // Temporary store values in cookie 

            if (request.Get(cookieName) == null || string.IsNullOrEmpty(request.Get(cookieName).Value)) 

            { 

                response.Remove(cookieName); request.Remove(cookieName); 

                response.Add(new System.Web.HttpCookie(cookieName, JsonConvert.SerializeObject(item.Data))); 

            } 

            else 

            { 

                var currentCookie = request.Get(cookieName); 

                Dictionary<string, object> cookieDataDict; 

                try 

                { 

                    cookieDataDict = JsonConvert.DeserializeObject<Dictionary<string, object>>(currentCookie.Value); 

                    foreach (var key in cookieDataDict.Keys) 

                    { 

                        if (!item.Data.ContainsKey(key)) 

                        { 

                            item.Data.Add(key, cookieDataDict[key]); 

                        } 

                    } 

                } 

                catch (Exception ex) 

                { 

                    // ignore 

                } 

                currentCookie.Value = JsonConvert.SerializeObject(item.Data); 

                response.Set(currentCookie); 

            } 

            return null; 

        } 



        public object Clone() 

        { 

            return new SavePartialSubmissionActor(); 

        } 

    } 
#296861
Feb 20, 2023 7:20
Vote:
 

I've given you vote on the feedback site and thought I'd drop the link to what looks like your feature request here incase anyone else wants to add a vote :)

https://feedback.optimizely.com/ideas/CMS-I-377

#297463
Mar 01, 2023 8:40
Vote:
 

Have you thought of the honey pot approach?

https://www.thryv.com/blog/honeypot-technique-html-form-spam-protection/

Prevent the form submitission when the input field has value in it. 

#297477
Mar 01, 2023 21:46
Vote:
 

Aniket,

It would be simple enough to prevent saving of the data if a honeypot field is completed and in a single step form this is okay.  However in a multi step form, preventing the data being saved causes an error with subsequent steps.  You could argue that in this case the error shouldn't matter because it's a bot doing the form completion and not a user and so if your honeypot is in the first step, who cares if any other step fails.  You just have to hope that the browser doesn't try to be too smart and pre-populate the honeypot in that situation.

#297632
Mar 03, 2023 9:22
Vote:
 

Aniket / Mark - Just thinking about this a bit more would it not then just make sense to have the Captcha on the first step and then going forward we know its a human and should not need to have any more Captcha fields ?  

#297653
Mar 03, 2023 14:41
Vote:
 

Hi Mark,

Good point. The autocomplete="off" should prevent the browsers from filling out the values.

Hi Minesh,

The discussion started with reCaptcha only happens on form submission which is the last step and doesn't help spam from getting into the other steps of the form. 

I agree reCaptcha is the industry standard and has advantages included tracking and reports on the Google dashboard.

Honestly, I personally hate reCaptcha because I have to proove to a robot/application that "I am an human" :) ...and it's annoying to select traffic lights or picture of cats when you want to get through the form quickly. I would rather have the application slow down the bots without slowing down the humans...let alone the user experience of random images popping up on my website.

Well just my free unasked opinion... 

#297655
Mar 03, 2023 14:58
Vote:
 

I have witnessed some bots hit forms with JavaScript disabled which allowed the bots to get around recaptcha, but they still fell foul of the honey pot.  This was however on a standard MVC form on another Optimizely build.  I do feel that this does echo Aniket's opinion on reCaptcha being a pain for humans and increasing friction of forms rather than being a foolproof method of preventing spam.

#297656
Mar 03, 2023 15:39
Vote:
 

Have you seen this update? and does that address your concern now?

https://world.optimizely.com/documentation/Release-Notes/ReleaseNote/?releaseNoteId=AFORM-3187

#297912
Mar 08, 2023 15:03
Minesh Shah (Netcel) - Mar 08, 2023 16:08
I believe he wanted to avoid the spam getting to the database in the first place, this option technically just hides from view or export. It should suffice but can see OP's concerns as well.
Mark Stott - Mar 08, 2023 16:16
It is at least a step closer to where it needs to be and could potentially be considered functionally "Good Enough". If the retention policy is at an absolute minimum for partial forms and they are hidden. That whole next step may require significantly more work. I did note that the item on the feedback site was not updated so a more complete solution may still be pending.
* 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.