验证失败:在处理 Gmail 点号变体或添加辅助邮箱时,主邮箱已被占用

Discourse 版本: 2026.5.0-latest.1

背景

当外部用户使用 Gmail“点号”变体(例如 user.name@gmail.com)向收件邮件处理器发送邮件,但其注册的论坛账户是未带点号的主版本(username@gmail.com)时,收件邮件处理器会因未处理的异常而崩溃:ActiveRecord::RecordInvalid (Validation failed: Primary email has already been taken)

此外,尝试通过将带点号的变体作为次要邮箱添加到用户档案来解决此问题——无论是通过 UI 还是使用 UserEmail.create! 在 Rails 控制台模型层操作——都会因完全相同的验证循环错误而失败。唯一的变通方法是对数据库进行原始 SQL 注入,绕过 ActiveRecord。

复现步骤

  1. 在 Discourse 上创建一个主邮箱为 username@gmail.com 的用户账户。

  2. 让该用户从 user.name@gmail.com 向某个分类/回复地址发送一封收件邮件。

  3. 观察收件邮件日志中的拒绝记录,原因是 ActiveRecord::RecordInvalid

  4. 尝试通过 Rails 控制台将 user.name@gmail.com 作为次要邮箱添加到 username@gmail.com 账户:

    UserEmail.create!(user_id: target_id, email: 'user.name@gmail.com', primary: false)
    
    
  5. 观察模型验证崩溃。

预期行为

Discourse 应能干净地处理 Gmail 规范化。它应该:

  1. 在收件邮件处理阶段,无缝识别带点号的 Gmail 变体属于主账户。

  2. 或者至少允许管理员将带点号的变体作为次要邮箱附加到主账户,而不会触发“主邮箱已被占用”的应用程序阻塞,因为它属于同一用户,且已明确设置为 primary: false

实际行为

应用层陷入逻辑循环:

  • 它将 user.name@gmail.com 视为“新”字符串,因此尝试对其采取行动(创建暂存用户或附加次要邮箱)。

  • 在验证阶段,UserEmail 模型运行其 Gmail 规范化逻辑,移除点号,发现 username@gmail.com 已经是该 user_id 的主邮箱索引,并错误地假设发生了重复记录冲突,从而阻止其自身执行。

已使用的解除阻塞变通方法

唯一的解决方法是 SSH 登录到容器并执行原始 SQL,完全绕过 ActiveRecord 验证:

sql = "INSERT INTO user_emails (user_id, email, \"primary\", created_at, updated_at) VALUES (X, 'user.name@gmail.com', false, NOW(), NOW())"
ActiveRecord::Base.connection.execute(sql)

一旦通过原始 SQL 强制插入,收件邮件跟踪即可完美工作。验证代码应进行更新以处理此边缘情况。

谢谢!

2 个赞