From 46464f65446f90d26e5ec7f335f1c1e6c85d407f Mon Sep 17 00:00:00 2001
From: Jochen Klar
Date: Tue, 21 Jan 2025 16:36:57 +0100
Subject: [PATCH] Add throttling to contact api endpoint
---
rdmo/core/assets/js/api/BaseApi.js | 14 ++++++++++++++
rdmo/core/settings.py | 9 ++++++---
rdmo/core/throttling.py | 8 ++++++++
.../assets/js/interview/components/main/Contact.js | 9 ++++++++-
rdmo/projects/viewsets.py | 4 +++-
5 files changed, 39 insertions(+), 5 deletions(-)
create mode 100644 rdmo/core/throttling.py
diff --git a/rdmo/core/assets/js/api/BaseApi.js b/rdmo/core/assets/js/api/BaseApi.js
index c7d6d76510..7f2c371110 100644
--- a/rdmo/core/assets/js/api/BaseApi.js
+++ b/rdmo/core/assets/js/api/BaseApi.js
@@ -19,6 +19,12 @@ function BadRequestError(errors) {
this.errors = errors
}
+function ThrottlingError(errors) {
+ this.errors = {
+ throttling: errors.detail
+ }
+}
+
class BaseApi {
static get(url) {
@@ -31,6 +37,10 @@ class BaseApi {
return response.json().then(errors => {
throw new BadRequestError(errors)
})
+ } else if (response.status === 429) {
+ return response.json().then(errors => {
+ throw new ThrottlingError(errors)
+ })
} else {
throw new ApiError(response.statusText, response.status)
}
@@ -59,6 +69,10 @@ class BaseApi {
return response.json().then(errors => {
throw new ValidationError(errors)
})
+ } else if (response.status === 429) {
+ return response.json().then(errors => {
+ throw new ThrottlingError(errors)
+ })
} else {
throw new ApiError(response.statusText, response.status)
}
diff --git a/rdmo/core/settings.py b/rdmo/core/settings.py
index 63d3b17459..87d6c0f2c9 100644
--- a/rdmo/core/settings.py
+++ b/rdmo/core/settings.py
@@ -164,8 +164,8 @@
CACHES = {
'default': {
- 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
- 'LOCATION': 'rdmo_default'
+ 'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
+ 'LOCATION': 'django_cache'
}
}
@@ -180,7 +180,10 @@
),
'DEFAULT_RENDERER_CLASSES': (
'rest_framework.renderers.JSONRenderer',
- )
+ ),
+ 'DEFAULT_THROTTLE_RATES': {
+ 'email': '1/minute'
+ }
}
SETTINGS_EXPORT = [
diff --git a/rdmo/core/throttling.py b/rdmo/core/throttling.py
new file mode 100644
index 0000000000..215ac552c4
--- /dev/null
+++ b/rdmo/core/throttling.py
@@ -0,0 +1,8 @@
+from rest_framework.throttling import UserRateThrottle
+
+
+class EmailThrottle(UserRateThrottle):
+ scope = 'email'
+
+ def allow_request(self, request, view):
+ return request.method == 'GET' or super().allow_request(request, view)
diff --git a/rdmo/projects/assets/js/interview/components/main/Contact.js b/rdmo/projects/assets/js/interview/components/main/Contact.js
index 84d88109d1..7bb1369ebb 100644
--- a/rdmo/projects/assets/js/interview/components/main/Contact.js
+++ b/rdmo/projects/assets/js/interview/components/main/Contact.js
@@ -31,7 +31,7 @@ const Contact = ({ templates, contact, sendContact, closeContact }) => {
bsSize: 'lg'
}}>
-
>
diff --git a/rdmo/projects/viewsets.py b/rdmo/projects/viewsets.py
index 0527882b2e..efd28c1b21 100644
--- a/rdmo/projects/viewsets.py
+++ b/rdmo/projects/viewsets.py
@@ -22,6 +22,7 @@
from rdmo.conditions.models import Condition
from rdmo.core.permissions import HasModelPermission
+from rdmo.core.throttling import EmailThrottle
from rdmo.core.utils import human2bytes, is_truthy, return_file_response
from rdmo.options.models import OptionSet
from rdmo.questions.models import Catalog, Page, Question, QuestionSet
@@ -329,7 +330,8 @@ def visibility(self, request, pk=None):
raise Http404
@action(detail=True, methods=['get', 'post'],
- permission_classes=(HasModelPermission | HasProjectPermission, ))
+ permission_classes=(HasModelPermission | HasProjectPermission, ),
+ throttle_classes=[EmailThrottle])
def contact(self, request, pk):
if settings.PROJECT_CONTACT:
if request.method == 'POST':