-
-
Notifications
You must be signed in to change notification settings - Fork 313
feat: add rate limiting to email verification on profile update #4839
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add rate limiting to email verification on profile update #4839
Conversation
|
👋 Hi @Tia-ani! This pull request needs a peer review before it can be merged. Please request a review from a team member who is not:
Once a valid peer review is submitted, this check will pass automatically. Thank you! |
WalkthroughAdds a per-user, cache-backed rate limiter to the profile_edit email verification flow: attempts an atomic cache.add with a 60-second key; if the key exists the request is rejected with a warning and redirect, otherwise the verification email is sent and the key is set. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant profile_edit as profile_edit()
participant Cache
User->>profile_edit: POST request to trigger email verification
profile_edit->>Cache: cache.add(email_verification_rate_<user_id>, True, 60)
alt cache.add returned False (rate limit active)
Cache-->>profile_edit: False
profile_edit-->>User: Show warning + redirect to profile
else cache.add returned True (allowed)
Cache-->>profile_edit: True
profile_edit->>profile_edit: Send verification email
profile_edit-->>User: Continue normal flow
end
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
website/views/user.py (1)
191-209: Rate limit applied before email send blocks retry on legitimate failures.The rate limit is set at line 201 before attempting to send the email (lines 204-209). If
send_email_confirmation()fails due to network issues, SMTP errors, or other transient problems, the user is still rate-limited for 60 seconds and cannot immediately retry. This creates a poor user experience when failures are legitimate.Apply this diff to set the rate limit only after successful email send:
# Rate limit: atomic check-and-set to prevent race conditions rate_key = f"email_verification_rate_{request.user.id}" - # add() only sets if key doesn't exist (atomic operation) - if not cache.add(rate_key, True, timeout=60): + if cache.get(rate_key): messages.error( request, "You are doing that too often. Please wait a minute before trying again.", ) return redirect("profile", slug=request.user.username) # Send verification email try: send_email_confirmation(request, request.user, email=new_email) + # Set rate limit only after successful send (atomic operation) + cache.add(rate_key, True, timeout=60) except Exception as e: logger.exception(f"Failed to send email confirmation to {new_email}: {e}") messages.error(request, "Failed to send verification email. Please try again later.") return redirect("profile", slug=request.user.username)Note: This combined with the atomic
cache.add()from the previous comment resolves both the race condition and the retry-blocking issue.
🧹 Nitpick comments (1)
website/views/user.py (1)
191-201: Consider using a dedicated rate limiting library.While the current cache-based implementation works, consider using a well-tested library like
django-ratelimitfor more robust rate limiting with better handling of edge cases, distributed systems support, and configurable backends.Example with django-ratelimit:
First, install the package:
pip install django-ratelimitThen apply this refactor:
+from django_ratelimit.decorators import ratelimit +from django_ratelimit.exceptions import Ratelimited @login_required +@ratelimit(key='user', rate='1/60s', method='POST', block=False) def profile_edit(request): from allauth.account.models import EmailAddress from allauth.account.utils import send_email_confirmation Tag.objects.get_or_create(name="GSOC") user_profile, created = UserProfile.objects.get_or_create(user=request.user) # Get the user's current email BEFORE changes original_email = request.user.email if request.method == "POST": + # Check if rate limited + if getattr(request, 'limited', False): + messages.error( + request, + "You are doing that too often. Please wait a minute before trying again.", + ) + return redirect("profile", slug=request.user.username) + form = UserProfileForm(request.POST, request.FILES, instance=user_profile) if form.is_valid(): ... if email_changed: # Remove any pending unverified emails EmailAddress.objects.filter(user=request.user, verified=False).delete() # Create new unverified email entry EmailAddress.objects.update_or_create( user=request.user, email=new_email, defaults={"verified": False, "primary": False}, ) - # Rate limit: - rate_key = f"email_verification_rate_{request.user.id}" - if cache.get(rate_key): - messages.error( - request, - "You are doing that too often. Please wait a minute before trying again.", - ) - return redirect("profile", slug=request.user.username) - - # Set limit for 60 seconds - cache.set(rate_key, True, timeout=60) - # Send verification email try: send_email_confirmation(request, request.user, email=new_email)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting
📒 Files selected for processing (1)
website/views/user.py(2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Run Tests
- GitHub Check: docker-test
🔇 Additional comments (2)
website/views/user.py (2)
14-14: LGTM! Cache import added correctly.The Django cache import is properly placed and follows standard conventions.
191-201: The rate limiting cache backend is properly configured and does not use DummyCache.The codebase uses
LocMemCache(functional in-memory cache) across all deployment scenarios and falls back toPyLibMCCache(Memcached) when configured. There is no scenario whereDummyCache(the silent-failure backend) is used. Rate limiting will function correctly.Likely an incorrect or invalid review comment.
|
LGTM. Just a nitpick, maybe use messages.warning for a bit of UX improvement overall and change the message to "Too many requests. Please wait a minute before trying again" instead if it makes sense. |
|
Thank you for reviewing @sidd190! |
|
LGTM. Implements rate limiting to the previously merged PR. |
|
Hi @DonnieBLT ! I accidentally clicked “Request re-review” |
|
/tip @Tia-ani $1 |
|
💰 Tip Request from @DonnieBLT to @Tia-ani Amount: $1 To complete this tip, please visit @Tia-ani's GitHub Sponsors page and select a one-time payment: Note: GitHub Sponsors does not support automated payments via API. Please complete the transaction manually by selecting "One-time" on the sponsor page and entering your desired amount. This comment was generated by OWASP BLT-Action |
Fixes : #4840
Summary
This PR introduces a rate-limiting mechanism to the email verification step in
the profile edit flow. It is a follow-up to PR #4804, adding an
extra safety improvement recommended by the maintainers.
What this PR adds
Why this improvement was needed
Email systems can be abused by repeatedly triggering verification emails.
Adding rate-limiting aligns the behavior with common best practices and ensures
a smoother user experience.
How it works
email_verification_rate_<user_id>Additional notes
This PR builds upon the previous merged improvement and follows the project’s
existing structure and coding patterns.
Summary by CodeRabbit