From 248f5d66e69fa99b64de27aa454819158d19872a Mon Sep 17 00:00:00 2001 From: sriram veeraghanta Date: Fri, 29 May 2026 00:13:41 +0530 Subject: [PATCH] refactor(api): source API_KEY_RATE_LIMIT from settings, drop service token throttle (#9161) - Define API_KEY_RATE_LIMIT in plane/settings/common.py and read it via django.conf.settings in ApiKeyRateThrottle instead of os.environ. - Remove ServiceTokenRateThrottle and the service-token branch in BaseAPIView.get_throttles; all API key requests now go through ApiKeyRateThrottle. --- apps/api/plane/api/rate_limit.py | 47 ++----------------------------- apps/api/plane/api/views/base.py | 17 ++--------- apps/api/plane/settings/common.py | 3 ++ 3 files changed, 8 insertions(+), 59 deletions(-) diff --git a/apps/api/plane/api/rate_limit.py b/apps/api/plane/api/rate_limit.py index 33df895cbf..7567ec156f 100644 --- a/apps/api/plane/api/rate_limit.py +++ b/apps/api/plane/api/rate_limit.py @@ -2,8 +2,8 @@ # SPDX-License-Identifier: AGPL-3.0-only # See the LICENSE file for details. -# python imports -import os +# Django imports +from django.conf import settings # Third party imports from rest_framework.throttling import SimpleRateThrottle @@ -11,48 +11,7 @@ from rest_framework.throttling import SimpleRateThrottle class ApiKeyRateThrottle(SimpleRateThrottle): scope = "api_key" - rate = os.environ.get("API_KEY_RATE_LIMIT", "60/minute") - - def get_cache_key(self, request, view): - # Retrieve the API key from the request header - api_key = request.headers.get("X-Api-Key") - if not api_key: - return None # Allow the request if there's no API key - - # Use the API key as part of the cache key - return f"{self.scope}:{api_key}" - - def allow_request(self, request, view): - allowed = super().allow_request(request, view) - - if allowed: - now = self.timer() - # Calculate the remaining limit and reset time - history = self.cache.get(self.key, []) - - # Remove old histories - while history and history[-1] <= now - self.duration: - history.pop() - - # Calculate the requests - num_requests = len(history) - - # Check available requests - available = self.num_requests - num_requests - - # Unix timestamp for when the rate limit will reset - reset_time = int(now + self.duration) - - # Add headers - request.META["X-RateLimit-Remaining"] = max(0, available) - request.META["X-RateLimit-Reset"] = reset_time - - return allowed - - -class ServiceTokenRateThrottle(SimpleRateThrottle): - scope = "service_token" - rate = "300/minute" + rate = settings.API_KEY_RATE_LIMIT def get_cache_key(self, request, view): # Retrieve the API key from the request header diff --git a/apps/api/plane/api/views/base.py b/apps/api/plane/api/views/base.py index fc65e7abdc..2fb20c8d17 100644 --- a/apps/api/plane/api/views/base.py +++ b/apps/api/plane/api/views/base.py @@ -22,9 +22,8 @@ from rest_framework.exceptions import APIException from rest_framework.generics import GenericAPIView # Module imports -from plane.db.models.api import APIToken from plane.api.middleware.api_authentication import APIKeyAuthentication -from plane.api.rate_limit import ApiKeyRateThrottle, ServiceTokenRateThrottle +from plane.api.rate_limit import ApiKeyRateThrottle from plane.utils.exception_logger import log_exception from plane.utils.paginator import BasePaginator from plane.utils.core.mixins import ReadReplicaControlMixin @@ -60,19 +59,7 @@ class BaseAPIView(TimezoneMixin, GenericAPIView, ReadReplicaControlMixin, BasePa return queryset def get_throttles(self): - throttle_classes = [] - api_key = self.request.headers.get("X-Api-Key") - - if api_key: - service_token = APIToken.objects.filter(token=api_key, is_service=True).first() - - if service_token: - throttle_classes.append(ServiceTokenRateThrottle()) - return throttle_classes - - throttle_classes.append(ApiKeyRateThrottle()) - - return throttle_classes + return [ApiKeyRateThrottle()] def handle_exception(self, exc): """ diff --git a/apps/api/plane/settings/common.py b/apps/api/plane/settings/common.py index 8dba3066e7..65b5d7b9f8 100644 --- a/apps/api/plane/settings/common.py +++ b/apps/api/plane/settings/common.py @@ -132,6 +132,9 @@ REST_FRAMEWORK = { "SCHEMA_COERCE_PATH_PK": False, } +# API key throttle rate (DRF SimpleRateThrottle format, e.g. "60/minute") +API_KEY_RATE_LIMIT = os.environ.get("API_KEY_RATE_LIMIT", "60/minute") + # Django Auth Backend AUTHENTICATION_BACKENDS = ("django.contrib.auth.backends.ModelBackend",) # default