modular-forms icon indicating copy to clipboard operation
modular-forms copied to clipboard

Spam protection using HoneyPot field

Open martinpesout opened this issue 10 months ago • 4 comments

I'm trying to create a form with spam protection using the HoneyPot field. So my code is something like this:

const contactFormDefaultValues = { email: "", message: "", somethingSweet: "" }

export const contactFormSchema = z.object({
  message: z
    .string()
    .min(1, "Please enter some message.")
    .min(10, "Can you be more specific?"),
  email: z
    .string()
    .min(1, "Please enter your e-mail.")
    .email("Please enter a valid e-mail."),
  somethingSweet: z // bot honey trap
    .string()
    .max(0, "Please leave this field empty."),
})

const [contactForm, { Form, Field }] = useForm<ContactForm, ResponseData>({
  loader: { value: contactFormDefaultValues },
  action: useFormAction(),
  validate: zodForm$(contactFormSchema),
})

return (
  <Form id="contact">
    <Field name="message">
      {(field, props) => (
        <input
          {...props}
          value={field.value}
          aria-invalid={!!field.error}
          placeholder="Your message"
          required
        />
      )}
    </Field>

    <Field name="email">
      {(field, props) => (
        <input
          {...props}
          value={field.value}
          aria-invalid={!!field.error}
          type="email"
          placeholder="[email protected]"
          required
        />
      )}
    </Field>

    <Field name="somethingSweet">
      {(field, props) => (
        <input
          {...props}
          value={field.value}
          aria-invalid={!!field.error}
          type="text"
        />
      )}
    </Field>
  </Form>
)

When I use only keyboard interaction, everything works (even validation of somethingSweet field is ok). However, this is useless to me in protecting against spam. Spam is still coming. I'll try to simulate filling the value of the field with id somethingSweet using JavaScript. For example, in DevTools I use $('#somethingSweet').value = 'test' or manually change the DOM again using DevTools. When I do this, validation isn't triggered because whole form frameworks isn't able to recognise that some value has been entered. How I can do HoneyPot spam protection using Modular Forms? Is that possible? Or am I doing something wrong?

I also tried to check values present in useFormAction() method, but I'm getting the same situation. When I fill in the value using $('#somethingSweet').value = 'test' in my browser, this field looks still as unchanged.

martinpesout avatar Jan 14 '25 09:01 martinpesout

I'm a bit worried that the problem is caused by the nature of the Field component https://github.com/fabian-hiller/modular-forms/blob/main/packages/qwik/src/components/Field.tsx because it seems to me, that an external update of the text input value (e.g. by $('#somethingSweet').value = 'test') doesn't trigger necessary update in the Field to store new field value.

martinpesout avatar Jan 14 '25 10:01 martinpesout

How about checking if the honeypot contains an input after submitting? You could access the element directly and check if it contains an input.

fabian-hiller avatar Jan 14 '25 16:01 fabian-hiller

@fabian-hiller Interesting idea. If I want to do that how I can directly access the element in useFormAction()? I think that I have to create something like const honeyPotRef = useSignal<HTMLInputElement>() referencing the HTML input field. But how I can pass this const into defined action? My action method looks like this:

export const useFormAction = formAction$<ContactForm, ResponseData>(
  async (values, event) => {

  .... some code ...

  },
  zodForm$(contactFormSchema)
)

To clarify the context... I'm switching to Qwik from React, and I'm still struggling a bit with how to pass extra information like this.

martinpesout avatar Jan 14 '25 20:01 martinpesout

Unfortunately, Modular Forms does not yet allow you to easily execute code before the form is submitted. It is technically possible to implement a honeypot, but it requires some extra work. Instead of using formAction$, you use a normal routeAction$ from Qwik that you submit manually in a custom onSubmit handler after checking the honeypot.

fabian-hiller avatar Jan 15 '25 00:01 fabian-hiller