feat: added feature to rate limit secondary email change (#37356)

This commit is contained in:
Muhammad Adeel Tajamul
2025-09-15 13:45:46 +05:00
committed by GitHub
parent 8126142836
commit 51a254a45c
3 changed files with 43 additions and 6 deletions

View File

@@ -1110,6 +1110,37 @@ class TestAccountsAPI(FilteredQueryCountMixin, CacheIsolationTestCase, UserAPITe
field_errors['email']['developer_message']
assert 'Valid e-mail address required.' == field_errors['email']['user_message']
@override_settings(SECONDARY_EMAIL_RATE_LIMIT='1/m')
def test_patch_secondary_email_ratelimit(self):
"""
Tests if rate limit is applied on secondary_email patch
"""
client = self.login_client("client", "user")
self.send_patch(client, {"secondary_email": "new_email_01@example.com"},
expected_status=status.HTTP_200_OK)
self.send_patch(client, {"secondary_email": "new_email_02@example.com"},
expected_status=status.HTTP_429_TOO_MANY_REQUESTS)
@override_settings(SECONDARY_EMAIL_RATE_LIMIT='')
def test_ratelimit_is_disabled_on_secondary_email_patch_if_settings_is_empty(self):
"""
Tests rate limit doesn't applied on secondary_email patch if SECONDARY_EMAIL_RATE_LIMIT is empty string or None
"""
client = self.login_client("client", "user")
self.send_patch(client, {"secondary_email": "email_new_01@example.com"},
expected_status=status.HTTP_200_OK)
self.send_patch(client, {"secondary_email": "email_new_02@example.com"},
expected_status=status.HTTP_200_OK)
@override_settings(SECONDARY_EMAIL_RATE_LIMIT='1/d')
def test_ratelimit_is_only_on_secondary_email_change(self):
"""
Tests if rate limit is only applied for secondary_email attribute i.e. when user changes recovery email
"""
client = self.login_client("client", "user")
for i in range(5):
self.send_patch(client, {"name": f"new_name_{i}"}, expected_status=status.HTTP_200_OK)
@mock.patch('common.djangoapps.student.views.management.do_email_change_request')
def test_patch_duplicate_email(self, do_email_change_request):
"""

View File

@@ -396,12 +396,17 @@ class AccountViewSet(ViewSet):
"""
if request.content_type != MergePatchParser.media_type:
raise UnsupportedMediaType(request.content_type)
if request.data.get("email") and settings.EMAIL_CHANGE_RATE_LIMIT:
if is_ratelimited(
request=request, group="email_change_rate_limit", key="user",
rate=settings.EMAIL_CHANGE_RATE_LIMIT, increment=True,
):
return Response({"error": "Too many requests"}, status=status.HTTP_429_TOO_MANY_REQUESTS)
for key, limit in [
('email', settings.EMAIL_CHANGE_RATE_LIMIT),
('secondary_email', settings.SECONDARY_EMAIL_RATE_LIMIT)
]:
if request.data.get(key) and limit:
if is_ratelimited(
request=request, group=f"{key}_change_rate_limit", key="user",
rate=limit, increment=True,
):
return Response({"error": "Too many requests"}, status=status.HTTP_429_TOO_MANY_REQUESTS)
try:
with transaction.atomic():

View File

@@ -830,6 +830,7 @@ SKIP_RATE_LIMIT_ON_ACCOUNT_AFTER_DAYS = 0
ONE_CLICK_UNSUBSCRIBE_RATE_LIMIT = '100/m'
EMAIL_CHANGE_RATE_LIMIT = ''
SECONDARY_EMAIL_RATE_LIMIT = ''
LMS_ROOT_URL = None
LMS_INTERNAL_ROOT_URL = Derived(lambda settings: settings.LMS_ROOT_URL)