Skip to content

Commit

Permalink
Add spam filter to bootstap lab form
Browse files Browse the repository at this point in the history
  • Loading branch information
neoformit committed Dec 17, 2024
1 parent a19d6ea commit 9aea5c8
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 23 deletions.
28 changes: 16 additions & 12 deletions app/labs/forms.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
"""User facing forms for making support requests (help/tools/data)."""

import logging
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, HTML, Submit
from django import forms
from django.conf import settings
from django.core.mail import EmailMultiAlternatives
from django.core.validators import FileExtensionValidator
from utils.mail import retry_send_mail
from utils.webforms import SpamFilterFormMixin

from . import bootstrap, validators

Expand Down Expand Up @@ -95,7 +98,7 @@ def dispatch(self, subject=None):
)


class LabBootstrapForm(forms.Form):
class LabBootstrapForm(SpamFilterFormMixin, forms.Form):
"""Form to bootstrap a new lab."""

lab_name = forms.CharField(
Expand Down Expand Up @@ -144,7 +147,18 @@ class LabBootstrapForm(forms.Form):
'accept': ','.join(f"image/{ext}" for ext in IMAGE_EXTENSIONS),
}),
)
# sections_json = forms.CharField()

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.layout = Layout(
'lab_name',
'subdomain',
'github_username',
'logo',
HTML(self.antispam_html),
Submit('submit', 'Build'),
)

def clean_subdomain(self):
"""Validate the subdomain."""
Expand All @@ -164,16 +178,6 @@ def clean_github_username(self):
username = self.cleaned_data.get('github_username')
return username.strip() if username else None

# def clean_section_json(self):
# """Validate the section JSON."""
# try:
# data = json.loads(self.cleaned_data['sections_json'])
# # Validate with Pydantic models...?
# except Exception as exc:
# raise forms.ValidationError(f"Invalid JSON: {exc}")

# return data

def bootstrap_lab(self):
data = self.cleaned_data
data.update({
Expand Down
9 changes: 1 addition & 8 deletions app/labs/templates/labs/bootstrap.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@


{% block head %}
<link rel="stylesheet" href="{% static 'labs/css/custom.css' %}">
{% endblock %}


Expand All @@ -14,14 +13,8 @@
<main class="container">
<h1>Create a new Galaxy Lab</h1>

<form method="post" enctype="multipart/form-data">
{% crispy form %}

{% csrf_token %}

{{ form|crispy }}

<button type="submit" class="btn btn-primary">Build</button>
</form>
</main>

{% endblock %}
Expand Down
4 changes: 1 addition & 3 deletions app/labs/templates/labs/header.html
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,7 @@

{% if request.path != '/' %}
<div class="container text-center" style="margin: -50px auto 50px auto;">
<h3>
<a class="ga-btn" href="/">Home</a>
</h3>
<a class="ga-btn" href="/">Home</a>
</div>
{% endif %}

Expand Down
83 changes: 83 additions & 0 deletions app/utils/webforms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
"""Utilities for Django forms."""

import logging
from django import forms
from django.utils.safestring import mark_safe

logger = logging.getLogger('django')


class SubmitDelaySpamFilterMixin(forms.Form):
"""Enforce a submit delay to prevent spam/bot submissions."""

SUBMIT_DELAY_MINIMUM_SECONDS = 3.0

submit_delay_seconds = forms.FloatField(
min_value=SUBMIT_DELAY_MINIMUM_SECONDS, required=False)

submit_delay_field_html = """
<script>
const initTime = new Date();
const setSubmitDelay = () => {
const submitDelaySeconds = (new Date() - initTime) / 1000;
const submitDelayInput = $(
`<input
type="hidden"
name="submit_delay_seconds"
value="${submitDelaySeconds}"
>`);
$('form').append(submitDelayInput);
}
$('form').submit(setSubmitDelay);
</script>
"""


class HoneypotSpamFilterMixin(forms.Form):
"""Include a honeypot field to catch spam/bot submissions."""

DELAY_DISABLE_MS = 1000
HONEYPOT_FIELD_NAME = 'institution_hp'
institution_hp = forms.CharField(
required=False,
widget=forms.HiddenInput()
)
honeypot_field_html = f"""
<input
type="text"
name="{HONEYPOT_FIELD_NAME}"
id="id_{HONEYPOT_FIELD_NAME}"
required
/>
<script type="text/javascript">
setTimeout( () => $("#id_{HONEYPOT_FIELD_NAME}")
.prop("disabled", 1), {DELAY_DISABLE_MS});
$("#id_{HONEYPOT_FIELD_NAME}").css("position", "absolute");
$("#id_{HONEYPOT_FIELD_NAME}").css("bottom", "0");
$("#id_{HONEYPOT_FIELD_NAME}").css("right", "0");
$("#id_{HONEYPOT_FIELD_NAME}").css("opacity", "0");
</script>"""

def clean_institution_hp(self):
"""Check honeypot field."""
value = self.cleaned_data.get(self.HONEYPOT_FIELD_NAME)
if value:
logger.warning('Honeypot field was filled in.')
raise forms.ValidationError('This value is incorrect.')
return value


class SpamFilterFormMixin(
SubmitDelaySpamFilterMixin,
HoneypotSpamFilterMixin,
):
"""A base form with multiple levels of spam filtering/prevention."""

INTERNAL_FIELDS = (
'submit_delay_seconds',
'institution_hp',
'captcha',
)
antispam_html = mark_safe(
HoneypotSpamFilterMixin.honeypot_field_html
+ SubmitDelaySpamFilterMixin.submit_delay_field_html)

0 comments on commit 9aea5c8

Please sign in to comment.