Repository: WordPress/openverse-api

Issue:
Adjust throttling of thumbnail endpoints
## Problem
<!-- Describe a problem solved by this feature; or delete the section entirely. -->

Currently thumbnail requests are throttled at a rate of one thousand per minute:

https://github.com/WordPress/openverse-api/blob/e9cfd3cc705c573e3f6ab2b632737e94bb82aee0/api/catalog/api/views/image_views.py#L96

When used by unauthenticated users making requests rapidly, as is common in scraping workflows, this can potentially send a lot of traffic to our API partners which may lead to rate limiting or denylisting of our API from their endpoints.

## Description
<!-- Describe the feature and how it solves the problem. -->

We should reduce the unauthenticated thumbnail throttling to something like `150/min`. This seems a reasonable amount for anyone building a user interface that displays thumbnails on-demand, which is more in-line with our intended use cases for the API. 

The throttling for _authenticated_ accounts should be higher, perhaps `500/min`?

## Additional context
<!-- Add any other context about the feature here; or delete the section entirely. -->

We have also discussed implementing CSRF tokens on the front-end to solve this problem, which would be used in conjunction with this approach but with the unauthenticated throttling even more aggressive.

## Implementation
<!-- Replace the [ ] with [x] to check the box. -->
- [ ] 🙋 I would be interested in implementing this feature.



Discussion:

Resolving patch:
diff --git a/api/catalog/api/utils/throttle.py b/api/catalog/api/utils/throttle.py
--- a/api/catalog/api/utils/throttle.py
+++ b/api/catalog/api/utils/throttle.py
@@ -53,6 +53,10 @@ class SustainedRateThrottle(AbstractAnonRateThrottle):
     scope = "anon_sustained"
 
 
+class AnonThumbnailRateThrottle(AbstractAnonRateThrottle):
+    scope = "anon_thumbnail"
+
+
 class TenPerDay(AbstractAnonRateThrottle):
     rate = "10/day"
 
@@ -90,6 +94,11 @@ def get_cache_key(self, request, view):
         return self.cache_format % {"scope": self.scope, "ident": ident}
 
 
+class OAuth2IdThumbnailRateThrottle(AbstractOAuth2IdRateThrottle):
+    applies_to_rate_limit_model = "standard"
+    scope = "oauth2_client_credentials_thumbnail"
+
+
 class OAuth2IdSustainedRateThrottle(AbstractOAuth2IdRateThrottle):
     applies_to_rate_limit_model = "standard"
     scope = "oauth2_client_credentials_sustained"
diff --git a/api/catalog/api/views/image_views.py b/api/catalog/api/views/image_views.py
--- a/api/catalog/api/views/image_views.py
+++ b/api/catalog/api/views/image_views.py
@@ -32,7 +32,10 @@
 )
 from catalog.api.serializers.media_serializers import MediaThumbnailRequestSerializer
 from catalog.api.utils.exceptions import get_api_exception
-from catalog.api.utils.throttle import OneThousandPerMinute
+from catalog.api.utils.throttle import (
+    AnonThumbnailRateThrottle,
+    OAuth2IdThumbnailRateThrottle,
+)
 from catalog.api.utils.watermark import watermark
 from catalog.api.views.media_views import MediaViewSet
 
@@ -93,7 +96,7 @@ def oembed(self, request, *_, **__):
         url_path="thumb",
         url_name="thumb",
         serializer_class=MediaThumbnailRequestSerializer,
-        throttle_classes=[OneThousandPerMinute],
+        throttle_classes=[AnonThumbnailRateThrottle, OAuth2IdThumbnailRateThrottle],
     )
     def thumbnail(self, request, *_, **__):
         image = self.get_object()
diff --git a/api/catalog/settings.py b/api/catalog/settings.py
--- a/api/catalog/settings.py
+++ b/api/catalog/settings.py
@@ -129,6 +129,8 @@
 
 THROTTLE_ANON_BURST = config("THROTTLE_ANON_BURST", default="5/hour")
 THROTTLE_ANON_SUSTAINED = config("THROTTLE_ANON_SUSTAINED", default="100/day")
+THROTTLE_ANON_THUMBS = config("THROTTLE_ANON_THUMBS", default="150/minute")
+THROTTLE_OAUTH2_THUMBS = config("THROTTLE_OAUTH2_THUMBS", default="500/minute")
 
 REST_FRAMEWORK = {
     "DEFAULT_AUTHENTICATION_CLASSES": (
@@ -143,6 +145,8 @@
     "DEFAULT_THROTTLE_CLASSES": (
         "catalog.api.utils.throttle.BurstRateThrottle",
         "catalog.api.utils.throttle.SustainedRateThrottle",
+        "catalog.api.utils.throttle.AnonThumbnailRateThrottle",
+        "catalog.api.utils.throttle.OAuth2IdThumbnailRateThrottle",
         "catalog.api.utils.throttle.OAuth2IdSustainedRateThrottle",
         "catalog.api.utils.throttle.OAuth2IdBurstRateThrottle",
         "catalog.api.utils.throttle.EnhancedOAuth2IdSustainedRateThrottle",
@@ -152,6 +156,8 @@
     "DEFAULT_THROTTLE_RATES": {
         "anon_burst": THROTTLE_ANON_BURST,
         "anon_sustained": THROTTLE_ANON_SUSTAINED,
+        "anon_thumbnail": THROTTLE_ANON_THUMBS,
+        "oauth2_client_credentials_thumbnail": THROTTLE_OAUTH2_THUMBS,
         "oauth2_client_credentials_sustained": "10000/day",
         "oauth2_client_credentials_burst": "100/min",
         "enhanced_oauth2_client_credentials_sustained": "20000/day",


Test patch: