Skip to content

Commit

Permalink
Cloudflare Turnstile implementation (#2526)
Browse files Browse the repository at this point in the history
* initial commit for turnstile module approach

* start integrating turnstile into concordia forms and views

* Update the url for Originally from reference.

* First attempt at adding form widget to form and view and template.

* settings is now passing TURN values, moved view to save transcription - still not the right place....

* clean up - still stuck

* Basic implementation of turnstile to transcription form

* Added turnstile to forms that previously used captcha. Removed captcha from codebase. Updated tests to correctly handle Turnstile. Fixed issue with caching and tests that only appeared when running tests multiple times in the same environment more often than once per hour. Updated docs to include Turnstile info

* Updated error message for when a user fails Turnstile validation

---------

Co-authored-by: Jen Kuenning <jkue@loc.gov>
  • Loading branch information
joshuastegmaier and jkueloc authored Sep 18, 2024
1 parent 981946f commit dea0c05
Show file tree
Hide file tree
Showing 23 changed files with 695 additions and 579 deletions.
1 change: 0 additions & 1 deletion Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ requests = "*"
Django = ">=4.2.14, <5.0"
bagit = "*"
django-registration = "*"
django-simple-captcha = "*"
django-tinymce = "*"
elasticsearch = "<7.14.0"
django-elasticsearch-dsl = "==7.3"
Expand Down
586 changes: 292 additions & 294 deletions Pipfile.lock

Large diffs are not rendered by default.

29 changes: 29 additions & 0 deletions concordia/context_processors.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,32 @@ def site_navigation(request):
def maintenance_mode_frontend_available(request):
value = cache.get("maintenance_mode_frontend_available", False)
return {"maintenance_mode_frontend_available": value}


def turnstile_default_settings(request):
"""
Expose turnstile default settings to the default template context
- Cloudflare Turnstile
"""

return {
"TURNSTILE_JS_API_URL": getattr(
settings,
"TURN_JS_API_URL",
"https://challenges.cloudflare.com/turnstile/v0/api.js",
),
"TURNSTILE_VERIFY_URL": getattr(
settings,
"TURNSTILE_VERIFY_URL",
"https://challenges.cloudflare.com/turnstile/v0/siteverify",
),
"TURNSTILE_SITEKEY": getattr(
settings, "TURNSTILE_SITEKEY", "1x00000000000000000000BB"
),
"TURNSTILE_SECRET": getattr(
settings, "TURNSTILE_SECRET", "1x0000000000000000000000000000000AA"
),
"TURNSTILE_TIMEOUT": getattr(settings, "TURNSTILE_TIMEOUT", 5),
"TURNSTILE_DEFAULT_CONFIG": getattr(settings, "TURNSTILE_DEFAULT_CONFIG", {}),
"TURNSTILE_PROXIES": getattr(settings, "TURNSTILE_PROXIES", {}),
}
6 changes: 6 additions & 0 deletions concordia/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
from django_registration.forms import RegistrationForm
from django_registration.signals import user_activated

from .turnstile.fields import TurnstileField

User = get_user_model()


Expand Down Expand Up @@ -119,6 +121,10 @@ def __init__(self, *, request, **kwargs):
super().__init__(**kwargs)


class TurnstileForm(forms.Form):
turnstile = TurnstileField()


class ContactUsForm(forms.Form):
referrer = forms.CharField(
label="Referring Page", widget=forms.HiddenInput(), required=False
Expand Down
5 changes: 5 additions & 0 deletions concordia/settings_ecs.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@

DATABASES["default"].update({"PASSWORD": postgres_secret["password"]})

cf_turnstile_secret_json = get_secret("crowd/%s/Turnstile" % ENV_NAME)
cf_turnstile_secret = json.loads(cf_turnstile_secret_json)
TURNSTILE_SITEKEY = cf_turnstile_secret["TurnstileSiteKey"]
TURNSTILE_SECRET = cf_turnstile_secret["TurnstileSecret"]

smtp_secret_json = get_secret("concordia/SMTP")
smtp_secret = json.loads(smtp_secret_json)
EMAIL_HOST = smtp_secret["Hostname"]
Expand Down
16 changes: 16 additions & 0 deletions concordia/settings_local_test.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import os

from .settings_template import * # NOQA ignore=F405
from .settings_template import DATABASES

Expand All @@ -21,3 +23,17 @@
"CONFIG": {"hosts": [("localhost", 63791)]},
}
}

# Turnstile settings
TURNSTILE_JS_API_URL = os.environ.get(
"TURNSTILE_JS_API_URL", "https://challenges.cloudflare.com/turnstile/v0/api.js"
)
TURNSTILE_VERIFY_URL = os.environ.get(
"TURNSTILE_VERIFY_URL", "https://challenges.cloudflare.com/turnstile/v0/siteverify"
)
TURNSTILE_SITEKEY = os.environ.get(
"TURNSTILE_SITEKEY", "1x00000000000000000000BB"
) # Always pass, invisible
TURNSTILE_SECRET = os.environ.get(
"TURNSTILE_SECRET", "1x0000000000000000000000000000000AA"
) # Always pass
16 changes: 9 additions & 7 deletions concordia/settings_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,6 @@
"concordia.apps.ConcordiaAppConfig",
"exporter",
"importer",
"captcha",
"prometheus_metrics.apps.PrometheusMetricsConfig",
"robots",
"django_celery_beat",
Expand Down Expand Up @@ -161,6 +160,7 @@
"concordia.context_processors.system_configuration",
"concordia.context_processors.site_navigation",
"concordia.context_processors.maintenance_mode_frontend_available",
"concordia.context_processors.turnstile_default_settings",
],
"libraries": {
"staticfiles": "django.templatetags.static",
Expand Down Expand Up @@ -317,12 +317,14 @@
"concordia.authentication_backends.EmailOrUsernameModelBackend"
]

CAPTCHA_CHALLENGE_FUNCT = "captcha.helpers.random_char_challenge"
#: Anonymous sessions require captcha validation every day by default:
ANONYMOUS_CAPTCHA_VALIDATION_INTERVAL = 86400

CAPTCHA_IMAGE_SIZE = [150, 100]
CAPTCHA_FONT_SIZE = 40
# Turnstile settings
TURNSTILE_JS_API_URL = os.environ.get("TURNSTILE_JS_API_URL", "")
TURNSTILE_VERIFY_URL = os.environ.get("TURNSTILE_VERIFY_URL", "")
TURNSTILE_SITEKEY = os.environ.get("TURNSTILE_SITEKEY", "")
TURNSTILE_SECRET = os.environ.get("TURNSTILE_SECRET", "")
TURNSTILE_TIMEOUT = os.environ.get("TURNSTILE_TIMEOUT", 5)
TURNSTILE_DEFAULT_CONFIG = os.environ.get("TURNSTILE_DEFAULT_CONFIG", {})
TURNSTILE_PROXIES = os.environ.get("TURNSTILE_PROXIES", {})

STORAGES = {
"default": {
Expand Down
16 changes: 16 additions & 0 deletions concordia/settings_test.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import os

from .settings_template import * # NOQA ignore=F405
from .settings_template import DATABASES

Expand All @@ -21,3 +23,17 @@
"CONFIG": {"hosts": [("localhost", 6379)]},
}
}

# Turnstile settings
TURNSTILE_JS_API_URL = os.environ.get(
"TURNSTILE_JS_API_URL", "https://challenges.cloudflare.com/turnstile/v0/api.js"
)
TURNSTILE_VERIFY_URL = os.environ.get(
"TURNSTILE_VERIFY_URL", "https://challenges.cloudflare.com/turnstile/v0/siteverify"
)
TURNSTILE_SITEKEY = os.environ.get(
"TURNSTILE_SITEKEY", "1x00000000000000000000BB"
) # Always pass, invisible
TURNSTILE_SECRET = os.environ.get(
"TURNSTILE_SECRET", "1x0000000000000000000000000000000AA"
) # Always pass
70 changes: 15 additions & 55 deletions concordia/static/js/src/contribute.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,40 +62,6 @@ $(document).on('keydown', function (event) {
});

function setupPage() {
var $captchaModal = $('#captcha-modal');
var $triggeringCaptchaForm = false;
var $captchaForm = $captchaModal
.find('form')
.on('submit', function (event) {
event.preventDefault();

var formData = $captchaForm.serializeArray();

$.ajax({
url: $captchaForm.attr('action'),
method: 'POST',
dataType: 'json',
data: $.param(formData),
})
.done(function () {
$captchaModal.modal('hide');
if ($triggeringCaptchaForm) {
$triggeringCaptchaForm.submit();
}
$triggeringCaptchaForm = false;
})
.fail(function (jqXHR) {
if (jqXHR.status == 401) {
$captchaModal
.find('[name=key]')
.val(jqXHR.responseJSON.key);
$captchaModal
.find('#captcha-image')
.attr('src', jqXHR.responseJSON.image);
}
});
});

$('form.ajax-submission').each(function (index, formElement) {
/*
Generic AJAX submission logic which takes a form and POSTs its data to the
Expand Down Expand Up @@ -149,27 +115,16 @@ function setupPage() {
}
})
.fail(function (jqXHR, textStatus, errorThrown) {
if (jqXHR.status == 401) {
$captchaModal
.find('[name=key]')
.val(jqXHR.responseJSON.key);
$captchaModal
.find('#captcha-image')
.attr('src', jqXHR.responseJSON.image);
$triggeringCaptchaForm = $form;
$captchaModal.modal();
} else {
$form.trigger('form-submit-failure', {
textStatus: textStatus,
errorThrown: errorThrown,
requestData: formData,
$form: $form,
jqXHR: jqXHR,
});
unlockControls($form);
if (eventData.lockElement) {
unlockControls($(eventData.lockElement));
}
$form.trigger('form-submit-failure', {
textStatus: textStatus,
errorThrown: errorThrown,
requestData: formData,
$form: $form,
jqXHR: jqXHR,
});
unlockControls($form);
if (eventData.lockElement) {
unlockControls($(eventData.lockElement));
}
});

Expand Down Expand Up @@ -518,6 +473,11 @@ function setupPage() {
url: url,
method: 'POST',
dataType: 'json',
data: {
'cf-turnstile-response': $transcriptionEditor
.find('input[name="cf-turnstile-response"]')
.val(),
},
})
.done(function (responseData) {
displayMessage(
Expand Down
1 change: 1 addition & 0 deletions concordia/templates/forms/widgets/turnstile_widget.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<div class="cf-turnstile" {% include "django/forms/widgets/attrs.html" %}></div>
2 changes: 2 additions & 0 deletions concordia/templates/registration/login.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
{% block head_content %}
<meta name="robots" content="noindex">
{{ block.super }}
<script module src="{{ TURNSTILE_JS_API_URL }}"></script>
{% endblock head_content %}

{% block title %}Login{% endblock title %}
Expand All @@ -23,6 +24,7 @@ <h2 id="dialog-title" class="text-center">Welcome back!</h2>
{% endif %}

{% bootstrap_form form %}
<div class="w-100 text-center mt-0 mb-3">{{ turnstile_form.turnstile }}</div>
<p>
By using this system, you agree to comply with
<a href="https://www.loc.gov/legal/" target="_blank">the Library's
Expand Down
4 changes: 1 addition & 3 deletions concordia/templates/transcriptions/asset_detail.html
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
</script>
<script module src="{% static 'openseadragon/build/openseadragon/openseadragon.min.js' %}"></script>
<script module src="{% static 'openseadragon-filtering/openseadragon-filtering.js' %}"></script>
<script module src="{{ TURNSTILE_JS_API_URL }}"></script>

<script type="module" src="{% static 'js/contribute.js' %}"></script>
<script type="module" src="{% static 'js/viewer-split.js' %}"></script>
Expand Down Expand Up @@ -115,9 +116,6 @@
<div id="review-accepted-modal" class="modal" tabindex="-1" role="dialog">
{% include "transcriptions/asset_detail/review_accepted_modal.html" %}
</div>
<div id="captcha-modal" class="modal" tabindex="-1" role="alertdialog" aria-labeledby="captcha-modal-title" aria-describedby="captcha-modal-description">
{% include "transcriptions/asset_detail/captcha_modal.html" %}
</div>
<div id="ocr-transcription-modal" class="modal" tabindex="-1" role="dialog">
{% include "transcriptions/asset_detail/ocr_transcription_modal.html" %}
</div>
Expand Down
38 changes: 0 additions & 38 deletions concordia/templates/transcriptions/asset_detail/captcha_modal.html

This file was deleted.

2 changes: 2 additions & 0 deletions concordia/templates/transcriptions/asset_detail/editor.html
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ <h2 id="message-contributors" {% if transcription_status == 'not_started' %}hidd
</a>
</div>

<div class="w-100 text-center mt-0 mb-3">{{ turnstile_form.turnstile }}</div>

<button id="save-transcription-button" disabled type="submit" class="btn btn-primary mx-1" title="Save the text you entered above">
Save
</button>
Expand Down
Loading

0 comments on commit dea0c05

Please sign in to comment.