Skip to content

Conversation

@NarayanBavisetti
Copy link
Collaborator

@NarayanBavisetti NarayanBavisetti commented Nov 15, 2025

Description

this pull request introduces a feature that allows users to update their email address.

Type of Change

  • Feature (non-breaking change which adds functionality)

Summary by CodeRabbit

  • New Features

    • Two-step email change flow: request a verification code for a new address and confirm it to update your account email.
    • Confirmation email sent after successful update; user is signed out to refresh session.
    • New modal UI guides email entry and code verification.
  • Improvements

    • Stronger validation with clear error messages for invalid/duplicate emails.
    • Rate limiting on verification requests to reduce abuse.
  • Localization

    • Added translations for the change-email modal in many languages and a new confirmation email template.

✏️ Tip: You can customize this high-level summary in your review settings.


Note

Adds a two-step email change flow with rate-limited code generation, backend endpoints and emails, a new UI modal, and translations.

  • Backend:
    • Endpoints: Add POST /api/users/me/email/generate-code/ and PATCH /api/users/me/email/ in apps/api/plane/app/urls/user.py handled by UserEndpoint.
    • UserEndpoint: Implement _validate_new_email, generate_email_verification_code (cached token, sends magic code, rate-limited), and update_email (verifies code, updates User.email, resets verification, logs out, clears cache, sends confirmations).
    • Rate limiting: Introduce EmailVerificationThrottle (3/hour) in apps/api/plane/authentication/rate_limit.py and apply via get_throttles.
    • Background tasks: Add send_email_update_magic_code and send_email_update_confirmation in apps/api/plane/bgtasks/user_email_update_task.py.
    • Emails: Add confirmation template apps/api/templates/emails/user/email_updated.html.
  • Frontend:
    • Modal: Add ChangeEmailModal (apps/web/core/components/core/modals/change-email-modal.tsx) for email entry and code verification; signs out on success.
    • Profile integration: Hook modal into ProfileForm (SMTP-gated) and expose "Change email" action.
    • Service API: Extend UserService with checkEmail, generateEmailCode, and verifyEmailCode.
  • Localization:
    • Add change-email modal strings across locales (EN, DE, ES, FR, ID, IT, JA, KO, PL, PT-BR, RO, RU, SK, TR-TR, UA, VI-VN, ZH-CN, ZH-TW).

Written by Cursor Bugbot for commit 55cfb78. This will update automatically on new commits. Configure here.

Copilot AI review requested due to automatic review settings November 15, 2025 18:34
@makeplane
Copy link

makeplane bot commented Nov 15, 2025

Linked to Plane Work Item(s)

This comment was auto-generated by Plane

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 15, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds a two-step email-update flow: new API routes and view logic to generate and verify cache-backed magic codes, Celery tasks and an HTML confirmation template, a React ChangeEmailModal with frontend service calls, rate-limiting throttle, and i18n strings across many locales.

Changes

Cohort / File(s) Summary
URL Routing
apps/api/plane/app/urls/user.py
Added POST /users/me/email/generate-code/ and PATCH /users/me/email/ routes for the email-update flow.
View Logic (Backend)
apps/api/plane/app/views/user/base.py
Added _validate_new_email, generate_email_verification_code, update_email, and get_throttles to validate new emails, create/verify cache-backed magic codes, apply throttling, update user.email & is_email_verified, logout/invalidate session, and enqueue confirmation emails.
Background Tasks
apps/api/plane/bgtasks/user_email_update_task.py
Added send_email_update_magic_code(email, token) and send_email_update_confirmation(email) Celery tasks that render templates and send verification/confirmation emails with error logging.
Email Template
apps/api/templates/emails/user/email_updated.html
Added HTML email template notifying users their Plane email was updated, using {{email}} placeholder and branded layout.
Frontend — Modal UI
apps/web/core/components/core/modals/change-email-modal.tsx
New exported ChangeEmailModal implementing a two-step flow: request code (Step 1) and submit verification code (Step 2), with CSRF handling, validation, toasts, sign-out on success, and error mapping.
Frontend — Profile Integration
apps/web/core/components/profile/form.tsx
Imported and rendered ChangeEmailModal; added state/handlers and a Change Email button shown when SMTP is configured.
Frontend — Service API
apps/web/core/services/user.service.ts
Added checkEmail(token, email), generateEmailCode({email}), and verifyEmailCode({email, code}); imported IEmailCheckResponse; now exports userService default.
Rate Limiting
apps/api/plane/authentication/rate_limit.py
Added EmailVerificationThrottle (subclass of UserRateThrottle) with rate = "3/hour" and scope = "email_verification"; includes throttle failure handling.
i18n
packages/i18n/src/locales/*/translations.ts
Added account_settings.profile.change_email_modal translations across many locales (en, cs, de, es, fr, id, it, ja, ko, pl, pt-BR, ro, ru, sk, tr-TR, ua, vi-VN, zh-CN, zh-TW) covering modal text, form fields, errors, actions, toasts, and states.

Sequence Diagram(s)

sequenceDiagram
    participant Browser
    participant Frontend as "ChangeEmailModal"
    participant API as "Backend API"
    participant Cache as "Redis/Cache"
    participant Queue as "Email Queue"
    participant Mail as "Mail Server"

    rect rgb(240,248,255)
    Note over Browser,Frontend: Step 1 — request verification code
    Browser->>Frontend: enter new_email
    Frontend->>API: POST /users/me/email/generate-code {email}
    API->>API: validate email & throttle
    API->>Cache: set magic_email_update_<user_id>_<email> = token
    API->>Queue: enqueue send_email_update_magic_code(email, token)
    API-->>Frontend: 200 OK
    end

    rect rgb(245,255,240)
    Note over Browser,Frontend: Step 2 — submit code
    Browser->>Frontend: submit {email, code}
    Frontend->>API: PATCH /users/me/email {email, code}
    API->>Cache: get magic_email_update_<user_id>_<email>
    Cache-->>API: token present / absent
    API->>API: final uniqueness check, update user.email, set is_email_verified=false, logout/invalidate session
    API->>Queue: enqueue send_email_update_confirmation(email)
    API-->>Frontend: 200 OK (updated user)
    end

    Queue->>Mail: deliver emails
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Areas to focus on:
    • Cache key format, expiration and isolation from other magic-code flows
    • Race conditions on uniqueness checks and concurrent updates
    • Throttle scope/limits and correct application to the generate endpoint
    • Session/logout/invalidation semantics and frontend sign-out interaction
    • Email template rendering, SMTP connection usage, and error handling pathways
    • Frontend CSRF handling and server error-to-form-error mappings

Poem

🐇
I hopped through code with whiskers bright,
Hid a token in Redis by night.
A tiny mail took flight and ran,
"Your new address now belongs to you" — it sang.
I twitched my nose and nibbled the plan.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 41.67% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Description check ❓ Inconclusive The PR description addresses the template but is incomplete. It identifies the feature and change type, but lacks Test Scenarios, Screenshots, and References sections. Add Test Scenarios detailing how the email change flow was tested, include Screenshots/Media showing the modal UI, and add References to related issues if applicable.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title '[WEB-5430] feat: allow users to change email' clearly summarizes the main feature being added - email change functionality for users.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat-user-email-change

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@NarayanBavisetti NarayanBavisetti changed the title [WEB-5430] feat: change user email [WEB-5430] feat: allow users to change email Nov 15, 2025
Copilot finished reviewing on behalf of NarayanBavisetti November 15, 2025 18:37
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
apps/api/plane/app/views/user/base.py (2)

91-118: Consider extracting duplicate email validation logic.

The email validation logic (required check, format validation, same-email check, duplicate check) is duplicated between generate_email_verification_code and update_email. This violates the DRY principle.

Consider extracting a helper method:

def _validate_new_email(self, user, new_email):
    """Validate new email address for update."""
    if not new_email:
        return {"error": "Email is required"}
    
    try:
        validate_email(new_email)
    except Exception:
        return {"error": "Invalid email format"}
    
    if new_email == user.email:
        return {"error": "New email must be different from current email"}
    
    if User.objects.filter(email=new_email).exclude(id=user.id).exists():
        return {"error": "An account with this email already exists"}
    
    return None  # No error

Then use it in both methods:

error = self._validate_new_email(user, new_email)
if error:
    return Response(error, status=status.HTTP_400_BAD_REQUEST)

Also applies to: 152-185


123-123: Consider extracting Redis key construction to avoid coupling.

The Redis key construction is split between two methods with implicit knowledge that MagicCodeProvider prepends "magic_". This creates tight coupling and fragility.

Consider extracting key construction:

def _get_email_update_redis_key(self, email, include_magic_prefix=False):
    """Get Redis key for email update verification."""
    key = f"email_update_{email}"
    if include_magic_prefix:
        return f"magic_{key}"
    return key

Then use:

  • Line 123: key=self._get_email_update_redis_key(new_email)
  • Line 190: redis_key = self._get_email_update_redis_key(new_email, include_magic_prefix=True)

Also applies to: 190-190

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c04ae51 and 97c8901.

📒 Files selected for processing (4)
  • apps/api/plane/app/urls/user.py (1 hunks)
  • apps/api/plane/app/views/user/base.py (3 hunks)
  • apps/api/plane/bgtasks/user_email_update_task.py (1 hunks)
  • apps/api/templates/emails/user/email_updated.html (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
apps/api/plane/bgtasks/user_email_update_task.py (2)
apps/api/plane/db/models/user.py (1)
  • User (38-164)
apps/api/plane/utils/exception_logger.py (1)
  • log_exception (9-20)
apps/api/plane/app/views/user/base.py (4)
apps/api/plane/authentication/adapter/error.py (2)
  • AuthenticationException (72-87)
  • get_error_dict (82-87)
apps/api/plane/bgtasks/user_email_update_task.py (2)
  • send_email_update_magic_code (19-60)
  • send_email_update_confirmation (64-111)
apps/api/plane/db/models/user.py (1)
  • User (38-164)
apps/api/plane/app/serializers/user.py (1)
  • UserMeSerializer (59-82)
apps/api/plane/app/urls/user.py (1)
apps/api/plane/app/views/user/base.py (1)
  • UserEndpoint (56-339)
⏰ 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). (3)
  • GitHub Check: Agent
  • GitHub Check: CodeQL analysis (python)
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (8)
apps/api/plane/app/urls/user.py (1)

33-42: LGTM!

The new URL patterns for email update functionality are well-structured and follow the existing REST conventions.

apps/api/templates/emails/user/email_updated.html (3)

1-267: LGTM!

The email template follows HTML email best practices with proper DOCTYPE, MSO compatibility, and responsive design.


537-551: LGTM!

The {{email}} placeholder is correctly used in both the main content and footer sections. Django's template rendering will properly substitute this value.

Also applies to: 1028-1033


375-383: Verify logo visibility on white background.

The logo URL references "new-logo-white.png" which appears to be a white-colored image displayed on the white email background (confirmed at line 297: style="background-color: #ffffff"). No alternative logo variants or design rationale were found in the codebase. Consider using a darker logo variant or adding a subtle background container to ensure readability.

apps/api/plane/bgtasks/user_email_update_task.py (2)

18-60: LGTM with observation.

The task correctly reuses the existing magic sign-in template for email verification codes. Error handling logs exceptions but returns None regardless of success or failure. This is acceptable for background tasks, though consider whether the calling code needs to know about failures.


63-111: LGTM!

The confirmation email task correctly uses the new template and follows the same pattern as the verification code task. Logging includes the recipient email for traceability.

apps/api/plane/app/views/user/base.py (2)

3-4: LGTM!

All new imports are correctly organized and necessary for the email update functionality.

Also applies to: 10-13, 45-51, 53-53


220-241: LGTM!

The email update logic is well-implemented with several good security practices:

  • Final duplicate-check prevents TOCTOU race conditions (line 221)
  • Correctly resets is_email_verified to False (line 230)
  • Logs out the user to force re-authentication with the new email (line 234)

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR introduces functionality to allow users to update their email address through a verification flow using magic codes sent via email.

  • Implements a two-step email update process: code generation and verification
  • Adds email templates for verification codes and confirmation messages
  • Automatically logs out users after successful email update for security

Reviewed Changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 11 comments.

File Description
apps/api/templates/emails/user/email_updated.html New HTML email template to confirm successful email address update
apps/api/plane/bgtasks/user_email_update_task.py Background tasks for sending verification codes and confirmation emails
apps/api/plane/app/views/user/base.py View methods to generate verification codes and process email updates with validation
apps/api/plane/app/urls/user.py New API endpoints for email verification code generation and email update
Comments suppressed due to low confidence (1)

apps/api/plane/bgtasks/user_email_update_task.py:13

  • Import of 'User' is not used.
from plane.db.models import User

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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

♻️ Duplicate comments (3)
apps/api/plane/bgtasks/user_email_update_task.py (1)

62-110: Same exception handling concern applies here.

Like the previous task, consider letting exceptions propagate for Celery's retry mechanism.

apps/api/plane/app/views/user/base.py (2)

217-229: Critical bug: Old email address is never notified of the email change.

On line 218, user.email is updated to new_email, and then saved on line 221. However, line 229 attempts to send a notification to user.email, which at that point contains the new email address, not the old one. This means the old email address is never notified of this security-sensitive change.

Apply this diff to fix:

+    # Save the old email before updating
+    old_email = user.email
+
     # Update the email - this updates the existing user record without creating a new user
     user.email = new_email
     # Reset email verification status when email is changed
     user.is_email_verified = False
     user.save()

     # Logout the user
     logout(request)

     # Send confirmation email to the new email address
     send_email_update_confirmation.delay(new_email)
     # send the email to the old email address
-    send_email_update_confirmation.delay(user.email)
+    send_email_update_confirmation.delay(old_email)

102-108: Catch specific ValidationError instead of broad Exception.

The validate_email function raises ValidationError, not a generic Exception. Catching Exception could hide programming errors.

Apply this diff:

+from django.core.exceptions import ValidationError
+
 # Validate email format
 try:
     validate_email(new_email)
-except Exception:
+except ValidationError:
     return Response(
         {"error": "Invalid email format"},
         status=status.HTTP_400_BAD_REQUEST,
     )
🧹 Nitpick comments (2)
apps/api/plane/bgtasks/user_email_update_task.py (1)

57-59: Consider letting exceptions propagate for Celery retry behavior.

The broad except Exception block catches all errors and always returns None, which prevents Celery's automatic retry mechanism from working. For transient failures (like SMTP connection issues), you may want to let the exception propagate so Celery can retry the task.

Consider this pattern:

     except Exception as e:
         log_exception(e)
-        return
+        raise  # Allow Celery to retry on failure

Alternatively, catch specific exceptions that should be retried vs. those that shouldn't:

from smtplib import SMTPException

try:
    # ... email sending code ...
except SMTPException as e:
    log_exception(e)
    raise  # Retry on SMTP issues
except Exception as e:
    log_exception(e)
    return  # Don't retry on other errors
apps/api/plane/app/views/user/base.py (1)

203-208: Catch specific exceptions for better error handling.

The broad except Exception block on line 203 catches all errors including JSON decode errors, Redis connection issues, and unexpected programming errors. This makes debugging difficult and provides users with a generic error message.

Consider catching specific exceptions:

import json
from redis.exceptions import RedisError

try:
    data = json.loads(ri.get(redis_key))
    stored_token = data.get("token")
    # ... validation ...
except json.JSONDecodeError as e:
    logger.error(f"Failed to parse Redis data: {str(e)}", exc_info=True)
    return Response(
        {"error": "Invalid verification data. Please request a new code."},
        status=status.HTTP_400_BAD_REQUEST,
    )
except RedisError as e:
    logger.error(f"Redis connection error: {str(e)}", exc_info=True)
    return Response(
        {"error": "Service temporarily unavailable. Please try again."},
        status=status.HTTP_503_SERVICE_UNAVAILABLE,
    )
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 97c8901 and 4bf6622.

📒 Files selected for processing (2)
  • apps/api/plane/app/views/user/base.py (3 hunks)
  • apps/api/plane/bgtasks/user_email_update_task.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
apps/api/plane/bgtasks/user_email_update_task.py (1)
apps/api/plane/utils/exception_logger.py (1)
  • log_exception (9-20)
apps/api/plane/app/views/user/base.py (4)
apps/api/plane/authentication/adapter/error.py (2)
  • AuthenticationException (72-87)
  • get_error_dict (82-87)
apps/api/plane/bgtasks/user_email_update_task.py (2)
  • send_email_update_magic_code (18-59)
  • send_email_update_confirmation (63-110)
apps/api/plane/db/models/user.py (1)
  • User (38-164)
apps/api/plane/app/serializers/user.py (1)
  • UserMeSerializer (59-82)
⏰ 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). (1)
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (3)
apps/api/plane/bgtasks/user_email_update_task.py (1)

1-14: LGTM!

Imports are appropriate and there are no unused dependencies.

apps/api/plane/app/views/user/base.py (2)

3-4: LGTM!

New imports are appropriate for the email update functionality.

Also applies to: 10-13, 45-50, 53-53


126-156: LGTM!

The email verification code generation flow is well-structured with proper validation and error handling.

@CLAassistant
Copy link

CLAassistant commented Nov 18, 2025

CLA assistant check
All committers have signed the CLA.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
apps/web/core/components/profile/form.tsx (1)

20-20: SMTP‑gated change‑email entrypoint looks good; consider minor UX/i18n polish

The wiring for the change‑email flow (instance config read, isSMTPConfigured guard, modal state, and button handler) is consistent and safe — the modal only becomes reachable when SMTP is configured, and the email field itself stays read‑only.

You might optionally:

  • Localize the "Change Email" label through the existing translation layer to match nearby labels.
  • Consider reusing the shared Button component here for consistency with other actions in this form (if design allows).

Also applies to: 25-25, 54-55, 84-87, 165-166, 365-373

apps/web/core/services/user.service.ts (1)

14-15: Email-check and verification service methods are consistent; minor DRY opportunity

The new checkEmail, generateEmailCode, and verifyEmailCode methods are consistent with the rest of this service (URL shapes, CSRF header usage for /auth/*, and error propagation via error?.response?.data). The typed IEmailCheckResponse result for checkEmail is a good addition.

If you want to tighten things up later, you could:

  • Extract the common then(response => response?.data).catch(...) pattern into a small helper to reduce repetition.
  • Type the payloads/return types for generateEmailCode and verifyEmailCode more specifically once the backend response contracts are stable.

Also applies to: 262-293

apps/web/core/components/core/modals/change-email-modal.tsx (1)

1-240: Change-email flow is solid; a couple of small UX/robustness tweaks to consider

The two-step flow, error handling, and cleanup on close are all wired correctly and align with the new service methods. Nice job keeping the form logic straightforward and resetting state via handleClose.

A few small improvements you could make without changing the core behavior:

  • When verifying the code (step 2), trim the submitted value before sending it so accidental whitespace doesn’t cause spurious “invalid code” errors:
  • await userService.verifyEmailCode({ email: formData.email, code: formData.code });
  • await userService.verifyEmailCode({ email: formData.email, code: formData.code.trim() });
    
    
  • The primary button label uses "Sending" for both steps. If you want clearer feedback, you could vary the loading text by step (e.g., "Sending code" vs "Verifying"), though this is purely UX polish.

  • In the step‑2 catch, you’re already showing a fallback message. If the backend also returns structured error_code/error_message like in step 1, you might eventually want to route those through the same authErrorHandler for consistency, but it’s fine to keep it simpler for now.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4bf6622 and ce6c47a.

📒 Files selected for processing (3)
  • apps/web/core/components/core/modals/change-email-modal.tsx (1 hunks)
  • apps/web/core/components/profile/form.tsx (5 hunks)
  • apps/web/core/services/user.service.ts (2 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-07-14T11:22:43.964Z
Learnt from: gakshita
Repo: makeplane/plane PR: 7393
File: apps/admin/app/(all)/(dashboard)/email/email-config-form.tsx:104-104
Timestamp: 2025-07-14T11:22:43.964Z
Learning: In the Plane project's SMTP configuration implementation, the email configuration form (email-config-form.tsx) hardcodes ENABLE_SMTP to "1" in form submission because the form is only rendered when SMTP is enabled. The enable/disable functionality is managed at the page level (page.tsx) with a toggle, and the form only handles configuration details when SMTP is already enabled.

Applied to files:

  • apps/web/core/components/profile/form.tsx
🧬 Code graph analysis (2)
apps/web/core/components/core/modals/change-email-modal.tsx (1)
packages/propel/src/toast/toast.tsx (1)
  • setToast (199-219)
apps/web/core/components/profile/form.tsx (1)
apps/web/core/components/core/modals/change-email-modal.tsx (1)
  • ChangeEmailModal (28-240)
⏰ 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: Build and lint web apps
  • GitHub Check: Analyze (javascript)

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the final PR Bugbot will review for you during this billing cycle

Your free Bugbot reviews will reset on December 20

Details

Your team is on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle for each member of your team.

To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (3)
apps/api/plane/app/views/user/base.py (3)

229-240: Handle potential race condition with IntegrityError.

There's a race condition between checking email availability (line 230) and saving the user (line 240). Although the database has a unique constraint on email, the code should handle the potential IntegrityError exception that could occur if another user claims the email between the check and save.

Apply this diff to handle the race condition:

+        from django.db import IntegrityError
+        
         # Final check: ensure email is still available (might have been taken between code generation and update)
         if User.objects.filter(email=new_email).exclude(id=user.id).exists():
             return Response(
                 {"error": "An account with this email already exists"},
                 status=status.HTTP_400_BAD_REQUEST,
             )
         old_email = user.email
-        # Update the email - this updates the existing user record without creating a new user
-        user.email = new_email
-        # Reset email verification status when email is changed
-        user.is_email_verified = False
-        user.save()
+        
+        try:
+            # Update the email - this updates the existing user record without creating a new user
+            user.email = new_email
+            # Reset email verification status when email is changed
+            user.is_email_verified = False
+            user.save()
+        except IntegrityError:
+            return Response(
+                {"error": "An account with this email already exists"},
+                status=status.HTTP_400_BAD_REQUEST,
+            )

108-115: Catch specific exception type.

The broad except Exception clause can hide programming errors or unexpected issues. The validate_email function raises ValidationError from django.core.exceptions.

Apply this diff to catch the specific exception:

-        try:
-            validate_email(new_email)
-        except Exception:
-            return Response(
-                {"error": "Invalid email format"},
-                status=status.HTTP_400_BAD_REQUEST,
-            )
+        from django.core.exceptions import ValidationError
+        
+        try:
+            validate_email(new_email)
+        except ValidationError:
+            return Response(
+                {"error": "Invalid email format"},
+                status=status.HTTP_400_BAD_REQUEST,
+            )

170-175: Catch specific exception types.

The broad except Exception clause can hide programming errors or unexpected issues. Consider catching more specific exceptions related to cache operations and email sending, or at minimum log and re-raise unexpected exceptions.

Apply this diff to improve exception handling:

-        except Exception as e:
-            logger.error("Failed to generate verification code: %s", str(e), exc_info=True)
-            return Response(
-                {"error": "Failed to generate verification code. Please try again."},
-                status=status.HTTP_400_BAD_REQUEST,
-            )
+        except (ValueError, TypeError) as e:
+            logger.error("Failed to generate verification code: %s", str(e), exc_info=True)
+            return Response(
+                {"error": "Failed to generate verification code. Please try again."},
+                status=status.HTTP_400_BAD_REQUEST,
+            )
+        except Exception as e:
+            logger.error("Unexpected error in generate_email_verification_code: %s", str(e), exc_info=True)
+            raise
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ce6c47a and abee7f6.

📒 Files selected for processing (2)
  • apps/api/plane/app/views/user/base.py (4 hunks)
  • apps/api/plane/authentication/rate_limit.py (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
apps/api/plane/authentication/rate_limit.py (1)
apps/api/plane/authentication/adapter/error.py (2)
  • AuthenticationException (72-87)
  • get_error_dict (82-87)
apps/api/plane/app/views/user/base.py (5)
apps/api/plane/bgtasks/user_email_update_task.py (2)
  • send_email_update_magic_code (18-59)
  • send_email_update_confirmation (63-110)
apps/api/plane/authentication/rate_limit.py (1)
  • EmailVerificationThrottle (27-43)
apps/api/plane/api/views/base.py (1)
  • get_throttles (60-73)
apps/api/plane/db/models/user.py (1)
  • User (38-164)
apps/api/plane/app/serializers/user.py (1)
  • UserMeSerializer (59-82)
⏰ 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). (3)
  • GitHub Check: Cursor Bugbot
  • GitHub Check: Build and lint web apps
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (6)
apps/api/plane/authentication/rate_limit.py (2)

2-2: LGTM!

The addition of UserRateThrottle import is necessary for the new EmailVerificationThrottle class.


27-43: LGTM!

The EmailVerificationThrottle implementation follows the existing throttle pattern correctly. The 3 requests per hour limit is reasonable for preventing abuse of the email verification flow while allowing legitimate use cases.

apps/api/plane/app/views/user/base.py (4)

3-6: LGTM!

All new imports are necessary for the email verification functionality and are properly utilized in the new methods.

Also applies to: 12-16, 48-52


63-69: LGTM!

The selective application of EmailVerificationThrottle to the generate_email_verification_code action is correct and ensures rate limiting is applied only where needed.


177-196: LGTM!

The validation logic appropriately reuses the _validate_new_email helper and includes proper checks for the verification code.


250-252: LGTM!

Returning the updated user data via UserMeSerializer is appropriate for the client to reflect the changes immediately.

// store hooks
const { signOut } = useUser();
// services
const authService = new AuthService();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move this outside of the component.

Comment on lines 59 to 74
message: "Failed to sign out. Please try again.",
})
);
};

const onSubmit = async (formData: TUniqueCodeValuesForm) => {
if (currentStep === "UNIQUE_CODE") {
// Step 2: Verify the code and update email
try {
await userService.verifyEmailCode({ email: formData.email, code: formData.code });

setToast({
type: TOAST_TYPE.SUCCESS,
title: "Success!",
message: "Email updated successfully. Please sign in again.",
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add these text to translations and then use it here.

- Added translation support for the change email modal, including titles, descriptions, and error messages.
- Integrated the useTranslation hook for dynamic text rendering.
- Updated form validation messages to utilize localized strings.
- Enhanced user feedback with localized success and error toast messages.
- Updated button labels and placeholders to reflect localization changes.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (8)
packages/i18n/src/locales/zh-CN/translations.ts (1)

1531-1531: Use formal "您的" for consistency.

Line 1531 uses "你的" (informal "your"), while the rest of the file consistently uses "您的" (formal "your") throughout account settings and user-facing text (e.g., lines 211, 409, 210).

Apply this diff:

-            helper_text: "验证码已发送至你的新邮箱。",
+            helper_text: "验证码已发送至您的新邮箱。",
packages/i18n/src/locales/tr-TR/translations.ts (1)

1532-1570: Change‑email modal Turkish copy is solid; consider minor consistency tweaks

The structure and keys look correct and consistent with the rest of the locale. Two small optional polish items you might consider:

  • Align wording with the “code” flow: description currently mentions a “verification link” while the UI below talks about a “verification code” (Doğrulama kodu yeni e-postanıza gönderildi.). You could make them consistent, e.g. change “bağlantısı” → “kodu”.
  • Reuse the existing localized unique-code placeholder from auth.common.unique_code.placeholder ("alır-atar-uçar") instead of the English "gets-sets-flys" so the example slug style stays localized and consistent within this file.

Example diff:

-        description: "Doğrulama bağlantısı almak için yeni bir e-posta adresi girin.",
+        description: "Doğrulama kodunu almak için yeni bir e-posta adresi girin.",
...
-          code: {
-            label: "Benzersiz kod",
-            placeholder: "gets-sets-flys",
+          code: {
+            label: "Benzersiz kod",
+            placeholder: "alır-atar-uçar",
packages/i18n/src/locales/ro/translations.ts (1)

1540-1554: Standardize "email" terminology for consistency.

The new translations use "e-mail" (hyphenated), but existing Romanian translations in this file use "Email" without a hyphen (see lines 24, 27-28, 200). For consistency across the Romanian locale, consider using the same form throughout.

Apply this diff to align with existing conventions:

-        title: "Schimbă e-mailul",
-        description: "Introduceți o nouă adresă de e-mail pentru a primi un link de verificare.",
+        title: "Schimbă email-ul",
+        description: "Introduceți o nouă adresă de email pentru a primi un link de verificare.",
         toasts: {
           success_title: "Succes!",
-          success_message: "E-mail actualizat cu succes. Conectați-vă din nou.",
+          success_message: "Email actualizat cu succes. Conectați-vă din nou.",
         },
         form: {
           email: {
-            label: "E-mail nou",
-            placeholder: "Introduceți e-mailul",
+            label: "Email nou",
+            placeholder: "Introduceți email-ul",
             errors: {
-              required: "E-mailul este obligatoriu",
-              invalid: "E-mailul este invalid",
-              exists: "E-mailul există deja. Folosiți altul.",
-              validation_failed: "Validarea e-mailului a eșuat. Încercați din nou.",
+              required: "Email-ul este obligatoriu",
+              invalid: "Email-ul este invalid",
+              exists: "Email-ul există deja. Folosiți altul.",
+              validation_failed: "Validarea email-ului a eșuat. Încercați din nou.",
             },
           },

Also update line 1560:

-            helper_text: "Codul de verificare a fost trimis la noul e-mail.",
+            helper_text: "Codul de verificare a fost trimis la noul email.",
packages/i18n/src/locales/cs/translations.ts (1)

1525-1565: LGTM! Czech translations for change email modal are well-structured.

The new translation subtree is properly nested, syntactically correct, and consistent with existing Czech translations throughout the file. The structure aligns with the PR's two-step email change flow (enter email → enter code), and all necessary keys are present including error states, form labels, and action buttons.

Minor observation: Some email validation error messages (lines 1539-1540: required, invalid) duplicate existing translations at lines 27-28 under auth.common.email.errors. While this keeps translations feature-scoped and may be intentional, you could optionally refactor to reference the shared translations if you want to reduce maintenance overhead. Not a blocker given the clear namespacing benefit.

packages/i18n/src/locales/de/translations.ts (1)

1543-1583: Consider aligning with formal address used in security flows.

The new change email modal uses informal German address ("Gib", "deine", "versuche"), while the similar forgot_password flow (lines 136-157) uses formal address ("Geben Sie", "Ihre"). For security-sensitive operations like email changes, maintaining a consistent tone with other authentication flows improves user trust and experience.

Apply this diff to align with formal address:

     profile: {
       change_email_modal: {
         title: "E-Mail ändern",
-        description: "Gib eine neue E-Mail-Adresse ein, um einen Verifizierungslink zu erhalten.",
+        description: "Geben Sie eine neue E-Mail-Adresse ein, um einen Verifizierungslink zu erhalten.",
         toasts: {
           success_title: "Erfolg!",
-          success_message: "E-Mail erfolgreich aktualisiert. Bitte melde dich erneut an.",
+          success_message: "E-Mail erfolgreich aktualisiert. Bitte melden Sie sich erneut an.",
         },
         form: {
           email: {
             label: "Neue E-Mail",
-            placeholder: "Gib deine E-Mail ein",
+            placeholder: "Geben Sie Ihre E-Mail ein",
             errors: {
               required: "E-Mail ist erforderlich",
               invalid: "E-Mail ist ungültig",
-              exists: "E-Mail existiert bereits. Bitte nutze eine andere.",
-              validation_failed: "E-Mail-Verifizierung fehlgeschlagen. Bitte versuche es erneut.",
+              exists: "E-Mail existiert bereits. Bitte nutzen Sie eine andere.",
+              validation_failed: "E-Mail-Verifizierung fehlgeschlagen. Bitte versuchen Sie es erneut.",
             },
           },
           code: {
             label: "Einmaliger Code",
             placeholder: "gets-sets-flys",
-            helper_text: "Verifizierungscode wurde an deine neue E-Mail gesendet.",
+            helper_text: "Verifizierungscode wurde an Ihre neue E-Mail gesendet.",
             errors: {
               required: "Einmaliger Code ist erforderlich",
-              invalid: "Ungültiger Verifizierungscode. Bitte versuche es erneut.",
+              invalid: "Ungültiger Verifizierungscode. Bitte versuchen Sie es erneut.",
             },
           },
         },
packages/i18n/src/locales/it/translations.ts (1)

1537-1577: Italian change_email_modal block is correct; consider normalizing apostrophe style

The block’s structure and keys line up with the English source and other locales, and the Italian copy reads well. It will integrate cleanly with the modal.

Tiny style nit: the new email error messages use a curly apostrophe (L’email), while most existing strings in this file use a straight apostrophe (l'elemento, etc.). If you want to keep punctuation style consistent across the locale, you could normalize these:

-              required: "L’email è obbligatoria",
-              invalid: "L’email non è valida",
+              required: "L'email è obbligatoria",
+              invalid: "L'email non è valida",

Purely cosmetic; no functional impact.

packages/i18n/src/locales/ua/translations.ts (1)

1532-1572: Align email terminology and description with existing UA locale and code-based flow

The structure and keys look good, but a few text tweaks would make this consistent with the rest of the UA translations and less confusing for the two-step code flow:

  • Use “адреса електронної пошти”/“електронна пошта” instead of Latin “email” to match existing UA strings (e.g., auth.common.email).
  • Description currently says “посилання для підтвердження”, but the UI here is code-based; better to mention a confirmation code.
  • Reuse the same terminology in toasts, labels, placeholders, and error messages for consistency.

Suggested diff (values only):

   account_settings: {
     profile: {
       change_email_modal: {
-        title: "Змінити email",
-        description: "Введіть нову адресу електронної пошти, щоб отримати посилання для підтвердження.",
+        title: "Змінити адресу електронної пошти",
+        description: "Введіть нову адресу електронної пошти, щоб отримати код підтвердження.",
         toasts: {
           success_title: "Успіх!",
-          success_message: "Email успішно оновлено. Увійдіть знову.",
+          success_message: "Адресу електронної пошти успішно оновлено. Увійдіть знову.",
         },
         form: {
           email: {
-            label: "Новий email",
-            placeholder: "Введіть свій email",
+            label: "Нова адреса електронної пошти",
+            placeholder: "Введіть нову адресу електронної пошти",
             errors: {
-              required: "Email є обов’язковим",
-              invalid: "Email недійсний",
-              exists: "Email уже існує. Використайте інший.",
-              validation_failed: "Не вдалося підтвердити email. Спробуйте ще раз.",
+              required: "Адреса електронної пошти є обов’язковою",
+              invalid: "Неправильна адреса електронної пошти",
+              exists: "Така адреса електронної пошти вже існує. Вкажіть іншу.",
+              validation_failed: "Не вдалося підтвердити адресу електронної пошти. Спробуйте ще раз.",
             },
           },
           code: {
             label: "Унікальний код",
             placeholder: "gets-sets-flys",
-            helper_text: "Код підтвердження надіслано на ваш новий email.",
+            helper_text: "Код підтвердження надіслано на вашу нову адресу електронної пошти.",
             errors: {
               required: "Унікальний код є обов’язковим",
               invalid: "Недійсний код підтвердження. Спробуйте ще раз.",
             },
           },
         },

This keeps behavior unchanged while tightening copy and aligning with the rest of the locale.

packages/i18n/src/locales/pt-BR/translations.ts (1)

1558-1563: Consider adding SMTP configuration error message for defensive coding.

While the PR description indicates the "Change Email" action is only visible when SMTP is configured, consider adding an error message for SMTP not being enabled, similar to the forgot_password flow (lines 140-142).

This provides defensive messaging in case the UI logic changes or for edge cases where the modal might be accessed when SMTP is not properly configured.

Apply this diff to add the SMTP error message:

             errors: {
               required: "O e-mail é obrigatório",
               invalid: "O e-mail é inválido",
               exists: "O e-mail já existe. Use outro.",
               validation_failed: "Falha na validação do e-mail. Tente novamente.",
+              smtp_not_enabled: "SMTP não está habilitado. Entre em contato com o administrador.",
             },
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between abee7f6 and 15550a1.

📒 Files selected for processing (21)
  • apps/web/core/components/core/modals/change-email-modal.tsx (1 hunks)
  • apps/web/core/components/profile/form.tsx (5 hunks)
  • packages/i18n/src/locales/cs/translations.ts (2 hunks)
  • packages/i18n/src/locales/de/translations.ts (2 hunks)
  • packages/i18n/src/locales/en/translations.ts (1 hunks)
  • packages/i18n/src/locales/es/translations.ts (2 hunks)
  • packages/i18n/src/locales/fr/translations.ts (1 hunks)
  • packages/i18n/src/locales/id/translations.ts (2 hunks)
  • packages/i18n/src/locales/it/translations.ts (2 hunks)
  • packages/i18n/src/locales/ja/translations.ts (2 hunks)
  • packages/i18n/src/locales/ko/translations.ts (2 hunks)
  • packages/i18n/src/locales/pl/translations.ts (2 hunks)
  • packages/i18n/src/locales/pt-BR/translations.ts (2 hunks)
  • packages/i18n/src/locales/ro/translations.ts (2 hunks)
  • packages/i18n/src/locales/ru/translations.ts (2 hunks)
  • packages/i18n/src/locales/sk/translations.ts (2 hunks)
  • packages/i18n/src/locales/tr-TR/translations.ts (2 hunks)
  • packages/i18n/src/locales/ua/translations.ts (2 hunks)
  • packages/i18n/src/locales/vi-VN/translations.ts (2 hunks)
  • packages/i18n/src/locales/zh-CN/translations.ts (2 hunks)
  • packages/i18n/src/locales/zh-TW/translations.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/web/core/components/core/modals/change-email-modal.tsx
  • apps/web/core/components/profile/form.tsx
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-03-11T19:42:41.769Z
Learnt from: janreges
Repo: makeplane/plane PR: 6743
File: packages/i18n/src/store/index.ts:160-161
Timestamp: 2025-03-11T19:42:41.769Z
Learning: In the Plane project, the file 'packages/i18n/src/store/index.ts' already includes support for Polish language translations with the case "pl".

Applied to files:

  • packages/i18n/src/locales/sk/translations.ts
  • packages/i18n/src/locales/cs/translations.ts
  • packages/i18n/src/locales/pl/translations.ts
⏰ 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). (3)
  • GitHub Check: Cursor Bugbot
  • GitHub Check: Build and lint web apps
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (8)
packages/i18n/src/locales/fr/translations.ts (1)

1545-1585: LGTM! Well-structured translations for the change email modal.

The French translations are comprehensive, covering all necessary strings including form labels, validation errors, toast messages, and action buttons. The structure is consistent with other modal translations in the file, and the error messages are clear and actionable.

packages/i18n/src/locales/pl/translations.ts (1)

1528-1568: LGTM! Complete and consistent Polish translations for change-email modal.

The translation block is well-structured and covers all necessary UI elements for the two-step email-change flow:

  • Clear title, description, and success messaging
  • Comprehensive form field labels, placeholders, and helper text
  • Complete error coverage (required, invalid, exists, validation_failed)
  • All actions and loading states

The terminology is consistent with existing translations in the file (e.g., "E-mail" at lines 1542-1543 matches lines 27-28, "Unikalny kod" at line 1549 matches line 76).

Based on learnings, Polish language support is already established in the project, and this addition integrates cleanly with the existing translation structure.

packages/i18n/src/locales/zh-CN/translations.ts (1)

1508-1548: LGTM! Well-structured translations for the email change modal.

The translation block is properly structured with all necessary keys for the two-step email change flow. Translations are accurate, natural Chinese, and follow established patterns in the file (error messages match lines 27-28, placeholder matches line 77).

packages/i18n/src/locales/zh-TW/translations.ts (1)

1509-1549: LGTM! Well-structured and consistent translations.

The Traditional Chinese translations for the email change modal are complete and properly integrated:

  • Terminology is consistent with existing strings (e.g., "電子郵件", "驗證碼", "必填")
  • Two-step flow clearly represented (email entry → code verification)
  • All necessary UI elements covered: form fields, validation errors, success/loading states, and action buttons
  • JSON structure is valid and properly nested
packages/i18n/src/locales/tr-TR/translations.ts (1)

2626-2626: Blank line change is harmless

This trailing blank-line change has no functional impact; fine to keep as-is or drop per formatting preferences.

packages/i18n/src/locales/en/translations.ts (1)

1360-1397: New English change_email_modal translations are structurally and semantically sound

The account_settings.profile.change_email_modal block mirrors the expected schema (title/description/toasts/form/email+code/actions/states) and the copy matches the intended flow and error cases; no issues from an i18n or key-structure standpoint.

packages/i18n/src/locales/pt-BR/translations.ts (1)

1545-1585: Well-structured translation block for email change flow.

The translation structure is comprehensive and well-organized:

  • Clear two-step flow (email → code)
  • Comprehensive form field error messages
  • Proper action labels
  • State indicators for async operations

The translations appear natural and consistent with the existing Portuguese (pt-BR) patterns throughout the file.

packages/i18n/src/locales/ja/translations.ts (1)

1524-1564: All keys and structure verified; only translation accuracy requires native speaker review.

The change_email_modal translations are correctly implemented:

Key completeness: All keys match the English locale (lines 1360-1397). The structure includes email/code fields, error handling, actions, and states as required.

Placeholder consistency: The "gets-sets-flys" placeholder at line 1546 matches the pattern used throughout the file and is consistent with the English locale (line 1381).

Structure: Properly nested under account_settings.profile with correct formatting and indentation.

Remaining verification: Confirm the Japanese translations are natural and contextually appropriate for the email change workflow. This requires review by a native Japanese speaker familiar with the application's user experience.

Comment on lines +1547 to +1585
account_settings: {
profile: {
change_email_modal: {
title: "Cambiar correo electrónico",
description: "Introduce una nueva dirección de correo electrónico para recibir un enlace de verificación.",
toasts: {
success_title: "¡Éxito!",
success_message: "Correo electrónico actualizado correctamente. Inicia sesión de nuevo.",
},
form: {
email: {
label: "Nuevo correo electrónico",
placeholder: "Introduce tu correo electrónico",
errors: {
required: "El correo electrónico es obligatorio",
invalid: "El correo electrónico no es válido",
exists: "El correo electrónico ya existe. Usa uno diferente.",
validation_failed: "La validación del correo electrónico falló. Inténtalo de nuevo.",
},
},
code: {
label: "Código único",
placeholder: "gets-sets-flys",
helper_text: "Código de verificación enviado a tu nuevo correo electrónico.",
errors: {
required: "El código único es obligatorio",
invalid: "Código de verificación inválido. Inténtalo de nuevo.",
},
},
},
actions: {
continue: "Continuar",
confirm: "Confirmar",
cancel: "Cancelar",
},
states: {
sending: "Enviando…",
},
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Spanish change_email_modal block looks correct; align unique-code placeholder with existing locale usage

The structure and keys here match the English source and other locales, so this will wire up cleanly in the UI.

Minor localization nit: in this file, the existing unique code placeholder under auth.common.unique_code.placeholder is localized ("obtiene-establece-vuela"), while change_email_modal.form.code.placeholder keeps the English "gets-sets-flys". For consistency within the es locale, consider reusing the localized variant:

-            placeholder: "gets-sets-flys",
+            placeholder: "obtiene-establece-vuela",
🤖 Prompt for AI Agents
In packages/i18n/src/locales/es/translations.ts around lines 1547 to 1585, the
change_email_modal.form.code.placeholder uses the English string
"gets-sets-flys" while the rest of the es locale uses a localized unique code
placeholder; update change_email_modal.form.code.placeholder to the same
localized variant used elsewhere in this file (e.g., the value under
auth.common.unique_code.placeholder) so the Spanish locale is consistent across
the app.

profile: {
change_email_modal: {
title: "Ubah email",
description: "Masukkan alamat email baru untuk menerima tautan verifikasi.",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Minor translation accuracy issue in description.

The description mentions "tautan verifikasi" (verification link), but the actual feature sends a verification code, not a link. This is confirmed by the helper text at line 1556: "Kode verifikasi dikirim ke email baru Anda."

Apply this diff to correct the translation:

-        description: "Masukkan alamat email baru untuk menerima tautan verifikasi.",
+        description: "Masukkan alamat email baru untuk menerima kode verifikasi.",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
description: "Masukkan alamat email baru untuk menerima tautan verifikasi.",
description: "Masukkan alamat email baru untuk menerima kode verifikasi.",
🤖 Prompt for AI Agents
In packages/i18n/src/locales/id/translations.ts around line 1537, the
description text incorrectly says "tautan verifikasi" (verification link) while
the feature sends a verification code; update the string to reference "kode
verifikasi" (verification code) instead so it matches the helper text at line
1556 and the actual behavior.

Comment on lines +1517 to +1557
account_settings: {
profile: {
change_email_modal: {
title: "이메일 변경",
description: "확인 링크를 받으려면 새 이메일 주소를 입력하세요.",
toasts: {
success_title: "성공!",
success_message: "이메일이 업데이트되었습니다. 다시 로그인하세요.",
},
form: {
email: {
label: "새 이메일",
placeholder: "이메일을 입력하세요",
errors: {
required: "이메일은 필수입니다",
invalid: "유효하지 않은 이메일입니다",
exists: "이미 존재하는 이메일입니다. 다른 주소를 사용하세요.",
validation_failed: "이메일 확인에 실패했습니다. 다시 시도하세요.",
},
},
code: {
label: "고유 코드",
placeholder: "gets-sets-flys",
helper_text: "인증 코드가 새 이메일로 전송되었습니다.",
errors: {
required: "고유 코드는 필수입니다",
invalid: "잘못된 인증 코드입니다. 다시 시도하세요.",
},
},
},
actions: {
continue: "계속",
confirm: "확인",
cancel: "취소",
},
states: {
sending: "전송 중…",
},
},
},
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Check terminology consistency in the modal description.

The description at line 1521 mentions "확인 링크를 받으려면" (to receive a confirmation link), but the actual flow sends a verification code to the user's email, as indicated by the helper text at line 1540: "인증 코드가 새 이메일로 전송되었습니다" (Verification code has been sent to the new email). This terminology mismatch could confuse users about what to expect.

Consider updating line 1521 to match the actual implementation:

-        description: "확인 링크를 받으려면 새 이메일 주소를 입력하세요.",
+        description: "인증 코드를 받으려면 새 이메일 주소를 입력하세요.",

This would change "confirmation link" to "verification code", making it consistent with the rest of the flow and matching how other locales likely describe this feature.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
account_settings: {
profile: {
change_email_modal: {
title: "이메일 변경",
description: "확인 링크를 받으려면 새 이메일 주소를 입력하세요.",
toasts: {
success_title: "성공!",
success_message: "이메일이 업데이트되었습니다. 다시 로그인하세요.",
},
form: {
email: {
label: "새 이메일",
placeholder: "이메일을 입력하세요",
errors: {
required: "이메일은 필수입니다",
invalid: "유효하지 않은 이메일입니다",
exists: "이미 존재하는 이메일입니다. 다른 주소를 사용하세요.",
validation_failed: "이메일 확인에 실패했습니다. 다시 시도하세요.",
},
},
code: {
label: "고유 코드",
placeholder: "gets-sets-flys",
helper_text: "인증 코드가 새 이메일로 전송되었습니다.",
errors: {
required: "고유 코드는 필수입니다",
invalid: "잘못된 인증 코드입니다. 다시 시도하세요.",
},
},
},
actions: {
continue: "계속",
confirm: "확인",
cancel: "취소",
},
states: {
sending: "전송 중…",
},
},
},
},
account_settings: {
profile: {
change_email_modal: {
title: "이메일 변경",
description: "인증 코드를 받으려면 새 이메일 주소를 입력하세요.",
toasts: {
success_title: "성공!",
success_message: "이메일이 업데이트되었습니다. 다시 로그인하세요.",
},
form: {
email: {
label: "새 이메일",
placeholder: "이메일을 입력하세요",
errors: {
required: "이메일은 필수입니다",
invalid: "유효하지 않은 이메일입니다",
exists: "이미 존재하는 이메일입니다. 다른 주소를 사용하세요.",
validation_failed: "이메일 확인에 실패했습니다. 다시 시도하세요.",
},
},
code: {
label: "고유 코드",
placeholder: "gets-sets-flys",
helper_text: "인증 코드가 새 이메일로 전송되었습니다.",
errors: {
required: "고유 코드는 필수입니다",
invalid: "잘못된 인증 코드입니다. 다시 시도하세요.",
},
},
},
actions: {
continue: "계속",
confirm: "확인",
cancel: "취소",
},
states: {
sending: "전송 중…",
},
},
},
},
🤖 Prompt for AI Agents
In packages/i18n/src/locales/ko/translations.ts around lines 1517–1557, the
modal description at line 1521 refers to a "confirmation link" ("확인 링크를 받으려면")
while the flow actually sends a verification code (helper text at line 1540 says
"인증 코드가 새 이메일로 전송되었습니다"); update the description to reference a verification
code (e.g., replace "확인 링크를 받으려면" with "인증 코드를 받으려면") so the wording matches the
rest of the modal and other locales, and verify similar phrasing is consistent
across related keys.

Comment on lines +1550 to +1553
toasts: {
success_title: "Sucesso!",
success_message: "E-mail atualizado com sucesso. Faça login novamente.",
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add error toast messages for consistency and better error handling.

The toast section only includes success messages but lacks error messages. This is inconsistent with established patterns throughout this translation file (e.g., auth.forgot_password.toast at lines 143-153, auth.common.password.toast.change_password at lines 62-73).

When the email change request fails due to server-side issues (network errors, rate limiting, service unavailable, etc.), users should receive clear feedback via toast notifications.

Apply this diff to add error toast messages:

         toasts: {
           success_title: "Sucesso!",
           success_message: "E-mail atualizado com sucesso. Faça login novamente.",
+          error_title: "Erro!",
+          error_message: "Não foi possível atualizar o e-mail. Por favor, tente novamente.",
         },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
toasts: {
success_title: "Sucesso!",
success_message: "E-mail atualizado com sucesso. Faça login novamente.",
},
toasts: {
success_title: "Sucesso!",
success_message: "E-mail atualizado com sucesso. Faça login novamente.",
error_title: "Erro!",
error_message: "Não foi possível atualizar o e-mail. Por favor, tente novamente.",
},
🤖 Prompt for AI Agents
In packages/i18n/src/locales/pt-BR/translations.ts around lines 1550-1553, the
toasts block for email update only contains success messages; add corresponding
error toast keys to match the file's pattern (e.g., add error_title and
error_message alongside success_title/success_message) using Portuguese text
consistent with other entries (for example "Erro!" and a message like "Falha ao
atualizar o e-mail. Tente novamente mais tarde."). Ensure the new keys are
placed within the same toasts object and follow the same key naming and
punctuation conventions as nearby toast blocks.

Comment on lines +1530 to +1569
account_settings: {
profile: {
change_email_modal: {
title: "Изменить email",
description: "Введите новый адрес электронной почты, чтобы получить ссылку для подтверждения.",
toasts: {
success_title: "Успех!",
success_message: "Email успешно обновлён. Пожалуйста, войдите снова.",
},
form: {
email: {
label: "Новый email",
placeholder: "Введите свой email",
errors: {
required: "Email обязателен",
invalid: "Email недействителен",
exists: "Email уже существует. Используйте другой.",
validation_failed: "Не удалось подтвердить email. Попробуйте ещё раз.",
},
},
code: {
label: "Уникальный код",
placeholder: "gets-sets-flys",
helper_text: "Код подтверждения отправлен на ваш новый email.",
errors: {
required: "Уникальный код обязателен",
invalid: "Неверный код подтверждения. Попробуйте ещё раз.",
},
},
},
actions: {
continue: "Продолжить",
confirm: "Подтвердить",
cancel: "Отмена",
},
states: {
sending: "Отправка…",
},
},
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Align description with code-based verification flow

The modal description says “ссылку для подтверждения”, but the flow (and this copy) is centered around entering a magic/unique code. This can slightly confuse users expecting a link instead of a code.

I’d align the wording with the code-based flow:

-        description: "Введите новый адрес электронной почты, чтобы получить ссылку для подтверждения.",
+        description: "Введите новый адрес электронной почты, чтобы получить код подтверждения.",

Everything else in this block (keys, structure, and Russian phrasing) looks consistent with the rest of the locale.

🤖 Prompt for AI Agents
In packages/i18n/src/locales/ru/translations.ts around lines 1530 to 1569,
update the modal description which currently says it will send a confirmation
"link" to instead refer to a confirmation "code" to match the code-based
verification flow; change the description string to something like "Введите код
подтверждения, отправленный на ваш новый адрес электронной почты." so it clearly
instructs users to enter the code they receive.

Comment on lines +1528 to +1566
account_settings: {
profile: {
change_email_modal: {
title: "Zmeniť e-mail",
description: "Zadajte novú e-mailovú adresu, aby ste dostali overovací odkaz.",
toasts: {
success_title: "Úspech!",
success_message: "E-mail bol úspešne aktualizovaný. Prihláste sa znova.",
},
form: {
email: {
label: "Nový e-mail",
placeholder: "Zadajte svoj e-mail",
errors: {
required: "E-mail je povinný",
invalid: "E-mail je neplatný",
exists: "E-mail už existuje. Použite iný.",
validation_failed: "Overenie e-mailu zlyhalo. Skúste znova.",
},
},
code: {
label: "Jedinečný kód",
placeholder: "gets-sets-flys",
helper_text: "Overovací kód bol odoslaný na váš nový e-mail.",
errors: {
required: "Jedinečný kód je povinný",
invalid: "Neplatný overovací kód. Skúste znova.",
},
},
},
actions: {
continue: "Pokračovať",
confirm: "Potvrdiť",
cancel: "Zrušiť",
},
states: {
sending: "Odosielanie…",
},
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Align wording with code-based verification flow

The new block looks structurally correct, but the description talks about an “overovací odkaz” (verification link) while the UI flow and helper text are code-based, which can confuse users. You might also make the email placeholder explicitly “new email” for clarity.

Suggested tweak:

       change_email_modal: {
         title: "Zmeniť e-mail",
-        description: "Zadajte novú e-mailovú adresu, aby ste dostali overovací odkaz.",
+        description: "Zadajte novú e-mailovú adresu, aby ste dostali overovací kód.",
         toasts: {
           success_title: "Úspech!",
           success_message: "E-mail bol úspešne aktualizovaný. Prihláste sa znova.",
         },
         form: {
           email: {
             label: "Nový e-mail",
-            placeholder: "Zadajte svoj e-mail",
+            placeholder: "Zadajte svoj nový e-mail",

Optionally, you may also standardize the ellipsis in states.sending to "...“ if you prefer consistency with other strings in this locale.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
account_settings: {
profile: {
change_email_modal: {
title: "Zmeniť e-mail",
description: "Zadajte novú e-mailovú adresu, aby ste dostali overovací odkaz.",
toasts: {
success_title: "Úspech!",
success_message: "E-mail bol úspešne aktualizovaný. Prihláste sa znova.",
},
form: {
email: {
label: "Nový e-mail",
placeholder: "Zadajte svoj e-mail",
errors: {
required: "E-mail je povinný",
invalid: "E-mail je neplatný",
exists: "E-mail už existuje. Použite iný.",
validation_failed: "Overenie e-mailu zlyhalo. Skúste znova.",
},
},
code: {
label: "Jedinečný kód",
placeholder: "gets-sets-flys",
helper_text: "Overovací kód bol odoslaný na váš nový e-mail.",
errors: {
required: "Jedinečný kód je povinný",
invalid: "Neplatný overovací kód. Skúste znova.",
},
},
},
actions: {
continue: "Pokračovať",
confirm: "Potvrdiť",
cancel: "Zrušiť",
},
states: {
sending: "Odosielanie…",
},
},
account_settings: {
profile: {
change_email_modal: {
title: "Zmeniť e-mail",
description: "Zadajte novú e-mailovú adresu, aby ste dostali overovací kód.",
toasts: {
success_title: "Úspech!",
success_message: "E-mail bol úspešne aktualizovaný. Prihláste sa znova.",
},
form: {
email: {
label: "Nový e-mail",
placeholder: "Zadajte svoj nový e-mail",
errors: {
required: "E-mail je povinný",
invalid: "E-mail je neplatný",
exists: "E-mail už existuje. Použite iný.",
validation_failed: "Overenie e-mailu zlyhalo. Skúste znova.",
},
},
code: {
label: "Jedinečný kód",
placeholder: "gets-sets-flys",
helper_text: "Overovací kód bol odoslaný na váš nový e-mail.",
errors: {
required: "Jedinečný kód je povinný",
invalid: "Neplatný overovací kód. Skúste znova.",
},
},
},
actions: {
continue: "Pokračovať",
confirm: "Potvrdiť",
cancel: "Zrušiť",
},
states: {
sending: "Odosielanie…",
},
},
🤖 Prompt for AI Agents
In packages/i18n/src/locales/sk/translations.ts around lines 1528-1566, the
change_email_modal description mentions an "overovací odkaz" (verification link)
while the UI and helper_text use a code-based flow; update the description to
refer to a verification code (e.g., "Zadajte novú e-mailovú adresu, aby ste
obdržali overovací kód."), change the email placeholder to explicitly say "Nový
e-mail" instead of the generic "Zadajte svoj e-mail", and optionally normalize
states.sending to use three ASCII dots ("...") for consistency with other locale
strings.

Comment on lines +1534 to +1574
account_settings: {
profile: {
change_email_modal: {
title: "Đổi email",
description: "Nhập địa chỉ email mới để nhận liên kết xác minh.",
toasts: {
success_title: "Thành công!",
success_message: "Email đã được cập nhật. Vui lòng đăng nhập lại.",
},
form: {
email: {
label: "Email mới",
placeholder: "Nhập email của bạn",
errors: {
required: "Email là bắt buộc",
invalid: "Email không hợp lệ",
exists: "Email đã tồn tại. Vui lòng dùng email khác.",
validation_failed: "Xác thực email thất bại. Thử lại.",
},
},
code: {
label: "Mã duy nhất",
placeholder: "gets-sets-flys",
helper_text: "Mã xác minh đã được gửi tới email mới của bạn.",
errors: {
required: "Mã duy nhất là bắt buộc",
invalid: "Mã xác minh không hợp lệ. Thử lại.",
},
},
},
actions: {
continue: "Tiếp tục",
confirm: "Xác nhận",
cancel: "Hủy",
},
states: {
sending: "Đang gửi…",
},
},
},
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

Well-structured translation branch with minor terminology concern.

The new account_settings.profile.change_email_modal translation branch is well-organized and follows existing patterns. Good reuse of common terms like "Mã duy nhất" (line 76), "Tiếp tục" (line 769), and "Xác nhận" (line 826).

However, there's a potential terminology inconsistency at line 1538: the description mentions "liên kết xác minh" (verification link), but line 1557's helper_text and the PR's backend endpoints indicate this is a code-based verification flow. Consider updating line 1538 to use "mã xác minh" (verification code) instead of "liên kết xác minh" for consistency.

Please verify:

  1. All translation keys match the frontend usage in change-email-modal.tsx and form.tsx
  2. This terminology inconsistency is mirrored across all locale files mentioned in the AI summary (cs, de, en, es, fr, etc.) — if the English version also says "link", it should be corrected to "code" across all locales
  3. The Vietnamese translation accurately reflects the intended two-step code verification flow

🏁 Script executed:

#!/bin/bash
# Verify that other locale files have the same structure and check for "link" vs "code" terminology

echo "=== Checking for account_settings.profile.change_email_modal in other locale files ==="
fd -e ts -e json "translations" packages/i18n/src/locales/ --exec grep -l "change_email_modal" {}

echo -e "\n=== Checking English locale for 'link' vs 'code' terminology ==="
rg -A2 "change_email_modal" packages/i18n/src/locales/en/translations.ts | grep -i "description"

Length of output: 1104



🏁 Script executed:

#!/bin/bash
# Check English locale for the complete change_email_modal structure including code field

echo "=== English locale change_email_modal structure ==="
rg -A30 "change_email_modal:" packages/i18n/src/locales/en/translations.ts | head -35

echo -e "\n=== Check if frontend implementation exists ==="
fd -e ts -e tsx "change.*email" packages/src/ --type f 2>/dev/null | head -10

echo -e "\n=== Search for email code verification flow ==="
rg -l "email.*code\|verification.*code" packages/src/ --type ts --type tsx 2>/dev/null | head -5

Length of output: 1269


Fix terminology inconsistency across all locale files: "verification link" should be "verification code".

Verification confirms the issue is systematic. The English locale description says "Enter a new email address to receive a verification link," but the code field and helper_text reference a code. The actual flow uses code-based verification (as confirmed by the backend endpoints in the PR description), so "link" is incorrect terminology.

The Vietnamese translation accurately mirrors the English source, so the issue exists across all 19 locale files that were updated. Update the description field in all locales—en, vi-VN, cs, de, es, fr, ja, ko, pl, pt-BR, ro, ru, sk, tr-TR, ua, zh-CN, zh-TW, id, and it—from "verification link" to "verification code" for consistency with the actual code-based flow.

🤖 Prompt for AI Agents
In packages/i18n/src/locales/vi-VN/translations.ts around lines 1534-1574, the
description text says "Nhập địa chỉ email mới để nhận liên kết xác minh"
(verification link) but the flow uses a verification code; update the
description to use "mã xác minh" (verification code) instead of "liên kết xác
minh". Apply the same replacement in the corresponding description fields across
all updated locale files (en, vi-VN, cs, de, es, fr, ja, ko, pl, pt-BR, ro, ru,
sk, tr-TR, ua, zh-CN, zh-TW, id, it) so the UI text consistently refers to a
verification code matching the backend flow. Ensure translation grammar is
correct for each language and run a quick grep across the locale directory to
catch any remaining "verification link" instances.

JayashTripathy
JayashTripathy previously approved these changes Nov 24, 2025
@sriramveeraghanta sriramveeraghanta dismissed stale reviews from JayashTripathy, pablohashescobar, and themself via 55cfb78 November 24, 2025 15:48
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
packages/i18n/src/locales/ko/translations.ts (1)

1517-1557: Terminology mismatch: description references "confirmation link" but flow uses "verification code".

This issue was previously identified but remains unresolved. Line 1521 states "확인 링크를 받으려면" (to receive a confirmation link), while line 1540 correctly indicates "인증 코드가 새 이메일로 전송되었습니다" (Verification code has been sent to the new email). This inconsistency may confuse users about what to expect.

Apply this diff to align the description with the actual implementation:

-        description: "확인 링크를 받으려면 새 이메일 주소를 입력하세요.",
+        description: "인증 코드를 받으려면 새 이메일 주소를 입력하세요.",
🧹 Nitpick comments (1)
packages/i18n/src/locales/cs/translations.ts (1)

1525-1565: Align description wording with code-based flow (minor translation nit)

The flow uses a verification code rather than a link, but the description currently mentions “ověřovací odkaz”. For clarity and parity with the UI/other locales, consider updating it (and optionally tightening the email placeholder):

       change_email_modal: {
         title: "Změnit e-mail",
-        description: "Zadejte novou e-mailovou adresu a obdržíte ověřovací odkaz.",
+        description: "Zadejte novou e-mailovou adresu a obdržíte ověřovací kód.",
         toasts: {
@@
         form: {
           email: {
             label: "Nový e-mail",
-            placeholder: "Zadejte svůj e-mail",
+            placeholder: "Zadejte novou e-mailovou adresu",

Structurally the keys and other messages look good and consistent with the existing cs locale; this is just a wording polish.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a0c18ec and 55cfb78.

📒 Files selected for processing (17)
  • packages/i18n/src/locales/cs/translations.ts (1 hunks)
  • packages/i18n/src/locales/de/translations.ts (1 hunks)
  • packages/i18n/src/locales/es/translations.ts (1 hunks)
  • packages/i18n/src/locales/id/translations.ts (1 hunks)
  • packages/i18n/src/locales/it/translations.ts (1 hunks)
  • packages/i18n/src/locales/ja/translations.ts (1 hunks)
  • packages/i18n/src/locales/ko/translations.ts (1 hunks)
  • packages/i18n/src/locales/pl/translations.ts (1 hunks)
  • packages/i18n/src/locales/pt-BR/translations.ts (1 hunks)
  • packages/i18n/src/locales/ro/translations.ts (1 hunks)
  • packages/i18n/src/locales/ru/translations.ts (1 hunks)
  • packages/i18n/src/locales/sk/translations.ts (1 hunks)
  • packages/i18n/src/locales/tr-TR/translations.ts (1 hunks)
  • packages/i18n/src/locales/ua/translations.ts (1 hunks)
  • packages/i18n/src/locales/vi-VN/translations.ts (1 hunks)
  • packages/i18n/src/locales/zh-CN/translations.ts (1 hunks)
  • packages/i18n/src/locales/zh-TW/translations.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (15)
  • packages/i18n/src/locales/ro/translations.ts
  • packages/i18n/src/locales/it/translations.ts
  • packages/i18n/src/locales/sk/translations.ts
  • packages/i18n/src/locales/pt-BR/translations.ts
  • packages/i18n/src/locales/id/translations.ts
  • packages/i18n/src/locales/es/translations.ts
  • packages/i18n/src/locales/zh-TW/translations.ts
  • packages/i18n/src/locales/vi-VN/translations.ts
  • packages/i18n/src/locales/ru/translations.ts
  • packages/i18n/src/locales/tr-TR/translations.ts
  • packages/i18n/src/locales/ua/translations.ts
  • packages/i18n/src/locales/ja/translations.ts
  • packages/i18n/src/locales/zh-CN/translations.ts
  • packages/i18n/src/locales/de/translations.ts
  • packages/i18n/src/locales/pl/translations.ts
⏰ 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). (4)
  • GitHub Check: Cursor Bugbot
  • GitHub Check: Analyze (javascript)
  • GitHub Check: Analyze (javascript)
  • GitHub Check: Build and lint web apps

user.email = new_email
# Reset email verification status when email is changed
user.is_email_verified = False
user.save()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Race condition in email update causes database error

A race condition exists between checking email availability and saving the user. After the final User.objects.filter(email=new_email).exclude(id=user.id).exists() check at line 228, another user could claim the same email before user.save() executes at line 238. This creates a window where two concurrent requests could pass the validation check but one would fail with a database integrity error when attempting to save a duplicate email.

Fix in Cursor Fix in Web

return Response(
{"error": "Failed to verify code. Please try again."},
status=status.HTTP_400_BAD_REQUEST,
)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Exception swallows error details during code verification

The exception handler catches all exceptions during code verification but doesn't log the actual error or provide specific feedback. This makes debugging difficult when legitimate errors occur, such as JSON parsing failures or cache connection issues. The caught exception e is never logged or used.

Fix in Cursor Fix in Web

)

# Check if email is the same as current email
if new_email == user.email:
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Email case sensitivity causes validation inconsistency

The comparison new_email == user.email compares a lowercased email against the user's stored email which may have mixed case. If a user's current email is "User@Example.com" and they enter "user@example.com", the validation incorrectly allows this as a "different" email, but the database save at line 235 will fail due to the unique constraint since emails are typically case-insensitive in practice. The comparison needs to normalize both sides: new_email == user.email.lower().

Fix in Cursor Fix in Web


# Send magic code to the new email
send_email_update_magic_code.delay(new_email, token)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Cache entry persists after email sending failure

If send_email_update_magic_code.delay(new_email, token) fails or the exception handler is triggered after cache.set() executes, the cache entry remains for 10 minutes even though no verification email was sent. Users cannot retry immediately because the cache key exists, but they never received the code. The cache should be cleaned up in the exception handler or the cache should only be set after successful email dispatch.

Fix in Cursor Fix in Web

@sriramveeraghanta sriramveeraghanta merged commit ce62999 into preview Nov 24, 2025
9 of 11 checks passed
@sriramveeraghanta sriramveeraghanta deleted the feat-user-email-change branch November 24, 2025 15:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants