diff --git a/bedrock/mozorg/forms.py b/bedrock/mozorg/forms.py index ce8c0f63ff4..4ab4e19c862 100644 --- a/bedrock/mozorg/forms.py +++ b/bedrock/mozorg/forms.py @@ -45,11 +45,12 @@ def render(self, name, value, attrs=None, renderer=None): # semi-randomized in case we have more than one per page. # this is maybe/probably overthought honeypot_id = "office-fax-" + str(randrange(1001)) + "-" + str(datetime.now().strftime("%Y%m%d%H%M%S%f")) + tabindex_attr = 'tabindex="-1"' return mark_safe( '
' '' - '' - "
" % (honeypot_id, honeypot_txt, honeypot_id) + '' + "" % (honeypot_id, honeypot_txt, honeypot_id, tabindex_attr) ) diff --git a/bedrock/newsletter/forms.py b/bedrock/newsletter/forms.py index 9c1d486d959..44c2903008a 100644 --- a/bedrock/newsletter/forms.py +++ b/bedrock/newsletter/forms.py @@ -10,7 +10,7 @@ from product_details import product_details -from bedrock.mozorg.forms import EmailInput, PrivacyWidget, strip_parenthetical +from bedrock.mozorg.forms import EmailInput, HoneyPotWidget, PrivacyWidget, strip_parenthetical from bedrock.newsletter import utils from lib.l10n_utils.fluent import ftl, ftl_lazy @@ -186,6 +186,8 @@ class NewsletterFooterForm(forms.Form): privacy = forms.BooleanField(widget=PrivacyWidget(attrs={"data-testid": "newsletter-privacy-checkbox"})) source_url = forms.CharField(required=False) newsletters = forms.MultipleChoiceField(widget=forms.CheckboxSelectMultiple()) + # office_fax is a honeypot field + office_fax = forms.CharField(widget=HoneyPotWidget(attrs={"tabindex": "-1"}), required=False, empty_value="") # has to take a newsletters argument so it can figure # out which languages to list in the form. @@ -251,6 +253,11 @@ def clean_source_url(self): return su + def clean_office_fax(self): + honeypot = self.cleaned_data.pop("office_fax", None) + if honeypot: + raise forms.ValidationError("Your submission could not be processed") + class EmailForm(forms.Form): """ diff --git a/bedrock/newsletter/templates/newsletter/includes/form.html b/bedrock/newsletter/templates/newsletter/includes/form.html index 15dbbbb931f..9ca3a70452a 100644 --- a/bedrock/newsletter/templates/newsletter/includes/form.html +++ b/bedrock/newsletter/templates/newsletter/includes/form.html @@ -15,6 +15,9 @@ {% endif %} + {# Honeypot field #} + {{ form.office_fax|safe }} + {% if include_title and is_multi_newsletter_form %}

{{ title|d(ftl('multi-newsletter-form-title'), true) }}

diff --git a/bedrock/newsletter/tests/test_forms.py b/bedrock/newsletter/tests/test_forms.py index 3fe7cc2309f..f2ecd90d016 100644 --- a/bedrock/newsletter/tests/test_forms.py +++ b/bedrock/newsletter/tests/test_forms.py @@ -233,3 +233,28 @@ def test_multiple_newsletters(self): form = NewsletterFooterForm(spacey_newsletters, "en-US", data=data.copy()) self.assertTrue(form.is_valid()) self.assertEqual(form.cleaned_data["newsletters"], newsletters) + + def test_honeypot_empty_valid(self): + """Honeypot field should be valid when empty""" + data = { + "email": "foo@example.com", + "lang": "fr", + "privacy": True, + "newsletters": [self.newsletter_name], + "office_fax": "", # honeypot field + } + form = NewsletterFooterForm(self.newsletter_name, locale="en-US", data=data.copy()) + self.assertTrue(form.is_valid(), form.errors) + + def test_honeypot_filled_invalid(self): + """Honeypot field should be invalid when filled""" + data = { + "email": "foo@example.com", + "lang": "fr", + "privacy": True, + "newsletters": [self.newsletter_name], + "office_fax": "some value", # honeypot field + } + form = NewsletterFooterForm(self.newsletter_name, locale="en-US", data=data.copy()) + self.assertFalse(form.is_valid()) + self.assertIn("office_fax", form.errors) diff --git a/bedrock/newsletter/views.py b/bedrock/newsletter/views.py index ecee8ff0253..8fb8a87cfc8 100644 --- a/bedrock/newsletter/views.py +++ b/bedrock/newsletter/views.py @@ -262,6 +262,9 @@ def newsletter_subscribe(request): errors.append(ftl("newsletter-form-please-enter-a-valid")) if "privacy" in form.errors: errors.append(ftl("newsletter-form-you-must-agree-to")) + if "office_fax" in form.errors: + # Honeypot field was filled + errors.append(ftl("newsletter-form-we-are-sorry-but-there")) for fieldname in ("newsletters", "lang", "country"): if fieldname in form.errors: errors.extend(form.errors[fieldname]) diff --git a/media/css/protocol/components/newsletter-form.scss b/media/css/protocol/components/newsletter-form.scss index 17d1729ec0b..43d6ee520bd 100644 --- a/media/css/protocol/components/newsletter-form.scss +++ b/media/css/protocol/components/newsletter-form.scss @@ -42,4 +42,12 @@ $image-path: '/media/protocol/img'; } } -/* stylelint-enable declaration-no-important */ +// Honeypot field styling - hide from users but keep accessible to bots +.super-priority-field { + @include visually-hidden; + + // Hide from screen readers + label { + display: none !important; + } +}