From 71e86daf4c6c9d9ef9c08a10a9a40bb3a17a6d5f Mon Sep 17 00:00:00 2001 From: Charles Oliveira Date: Thu, 4 Apr 2024 21:27:42 -0300 Subject: [PATCH] api: add custom throttling class This class is based of UserRateThrottle from DRF with a little change to only throttle social account users. It does use standard DRF throttling settings, so to make use of that just do: ``` REST_FRAMEWORK = { 'DEFAULT_THROTTLE_CLASSES': ['squad.api.SocialUserRateThrottle'], 'DEFAULT_THROTTLE_RATES': { 'socialuser': '5/h', } } ``` Note the 'socialuser' part, it is necessary for the thottling work properly. Signed-off-by: Charles Oliveira --- squad/api/__init__.py | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/squad/api/__init__.py b/squad/api/__init__.py index e69de29bb..bba06fc30 100644 --- a/squad/api/__init__.py +++ b/squad/api/__init__.py @@ -0,0 +1,40 @@ +from django.core.cache import cache +from rest_framework.throttling import UserRateThrottle + + +class SocialUserRateThrottle(UserRateThrottle): + """ + Limits the rate of API calls that may be made by a given social user. + + The user id will be used as a unique cache key if the user is + authenticated. For anonymous requests, the IP address of the request will + be used. + """ + + scope = "socialuser" + + def is_social_user(self, user): + """ + Social account user might use multiple applications (github, gitlab, google, ...) + to log in. But all of the login applications would likely point to the same + django user, which is what we want to rate limit here. + """ + + key = f"socialuser#{user.id}" + from allauth.socialaccount.models import SocialAccount + return cache.get_or_set(key, SocialAccount.objects.filter(user_id=user.id).exists()) + + def get_cache_key(self, request, view): + if request.user and request.user.is_authenticated: + if not self.is_social_user(request.user): + # do not throttle non-social users + return None + + ident = request.user.pk + else: + ident = self.get_ident(request) + + return self.cache_format % { + "scope": self.scope, + "ident": ident + }