Skip to content

chore: invite email hardening — throttle, dedup, validation gaps #173

@mostlyvirtual

Description

@mostlyvirtual

Context

Pre-existing issues surfaced during review of PR #149 (send invite email on user creation). These are out of scope for that PR but should be tracked.

Findings

High Priority

  • Missing rate limiting on customer_users_createapps/api/customers/views.py:980. No @throttle_classes decorator. A compromised portal token can create users in a loop, triggering unbounded email sending. Other similar endpoints (e.g., update_customer_billing_address at line 688) use @throttle_classes([BurstAPIThrottle]).

  • Duplicate _send_welcome_email_secure across two service classesapps/users/services.py:364 (on SecureUserRegistrationService) and apps/users/services.py:854 (on SecureCustomerUserService). Byte-for-byte identical. A bug fix to one won't propagate to the other. Extract to a standalone function or shared mixin.

Medium Priority

  • Empty company_name produces broken email textapps/users/services.py:386. For individual-type customers, company_name is blank. Email subject becomes "Account Created for " and body reads "You have been invited to join ." — confusing first impression. Fix: use customer.company_name or customer.name or "your organization".

  • Private method _send_welcome_email_secure called from external modulesapps/api/customers/views.py:1029 and apps/customers/user_management_views.py:123 both call a _-prefixed method. Either rename to send_welcome_email (public API) or add a public facade.

  • No integration test for email rendering — All tests mock _send_welcome_email_secure. No test verifies that templates render without error, that the token is valid, or that send_mail is called with correct context. Add at least one test using Django's mail.outbox.

Low Priority

  • Hardcoded "2 hours" in email templatetemplates/customers/emails/welcome_email.html:24. Should derive from settings.PASSWORD_RESET_TIMEOUT // 3600 and pass as template context variable.

  • Email not lowercased before DB lookupapps/api/customers/views.py:996 and apps/customers/user_management_views.py:96. email.strip() without .lower() means case-mismatched duplicates hit IntegrityError instead of clean duplicate message.

Files

  • services/platform/apps/api/customers/views.py
  • services/platform/apps/customers/user_management_views.py
  • services/platform/apps/users/services.py
  • services/platform/templates/customers/emails/welcome_email.html
  • services/platform/templates/customers/emails/welcome_email.txt

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions