更改用户的电子邮件

我对管理员更改用户邮箱地址的流程感到有些困惑。

有些问题我无法理解,而且存在一个 bug(所以我才在 bug 频道发帖,而不是 Support)。

当管理员从该用户的偏好设置页面更改其邮箱地址时:

  • 不会向用户发送确认邮箱变更的邮件。而是会发送一封密码重置邮件,以便用户在新邮箱地址上设置账户密码。
  • 仍会向用户的旧邮箱发送邮件,通知他们邮箱已被更改。

#1 我不理解为什么要发送密码重置邮件(“以便他们为新邮箱地址设置账户密码”)。他们不需要更改密码吗?而且用户体验令人困惑——用户并不预期会收到密码重置邮件,且邮件中没有任何说明文字,只是显示“有人在 [论坛名称] 请求重置您的密码”。

#2 该密码重置邮件是发送到地址,而不是邮箱地址。

尽管在 update_user_email 方法的第 46 行更新了用户邮箱,但 @user 对象并未重新加载,仍然包含旧的邮箱地址。

#3 如果执行操作的是管理员,而被操作的用户不是工作人员,则不会按照上述规范发送确认邮件。然而,在更改邮箱地址后,管理员仍会收到以下成功提示:“我们已向该地址发送邮件。请按照确认说明操作”。

#4 为什么用户不需要确认他们的新邮箱地址?该拉取请求引用了 此主题,但似乎其中缺少了许多帖子。不过该主题确实提到:“对于普通用户,唯一需要验证的邮箱地址是邮箱地址”。编辑:哦,等等,参见 #6 / #7。

#5 管理员更改用户邮箱的流程通常用于旧邮箱地址无法访问的情况(我假设是这样?)。既然如此,为什么还会向旧地址发送通知?

#6 当该用户尝试登录时,会弹出以下提示:

您暂时无法登录。我们之前已向您的 旧邮箱地址 发送了激活邮件。请按照该邮件中的说明激活您的账户。

  • 实际上并未收到此类邮件
  • 提示中提到了旧邮箱地址

点击“重新发送”按钮后显示:

我们已向您的 新邮箱地址 重新发送了激活邮件。邮件可能需要几分钟才能到达;请务必检查您的垃圾邮件文件夹。

#7 该激活邮件确实到达了新邮箱地址,标题为“确认您的新账户”(而不是“确认您的新邮箱地址”)。

这不应该简化为:

向新邮箱地址发送一封邮件,内容为“您的邮箱地址已由 [管理员姓名] 更改。请点击以下链接进行确认 [链接]”。

编辑:#8 管理员可以从用户的公开个人资料页面(/u/username)更改邮箱地址,但不能从该用户的管理员页面(/admin/users/id/username)进行更改。这令人感到困惑。

10 个赞

我们能否复现这个问题 @tshenry?这里是否出现了回归?

2 个赞

我首先会梳理一下我观察到的当前流程(大部分,如果不是全部,这与 @RGJ 概述的内容一致):

  1. 管理员进入非管理员用户的偏好设置并更改其电子邮件地址:

    提交后,管理员会看到以下消息:

    我们已向该地址发送了一封电子邮件。请按照确认说明操作。

  2. 上述消息似乎不准确,因为两封电子邮件被发送到了电子邮件地址:

    [演示] 您的电子邮件地址已更改

    这是一条自动消息,通知您 Demo 的电子邮件地址已更改。如果这是误操作,请联系
    站点管理员。

    您的电子邮件地址已更改为:

    new@email.com

    [演示] 密码重置

    有人请求在 Demo 上重置您的密码。

    如果不是您操作的,您可以安全地忽略此邮件。

    点击以下链接选择新密码:
    https://example.discourse.site/u/password-reset/74d53d7d2cf20dsbc360614844c653s2

  3. 我从这一点测试了三个不同的场景。每个项目符号代表一个独立的场景:

    • 用户可以访问旧电子邮件并跟随重置密码链接。在更新密码后,他们会被登录。从那里,他们可以使用用户名或电子邮件地址登录。此时新电子邮件似乎尚未生效。

    • 用户尝试使用用户名或电子邮件地址登录,并收到以下提示:

      选项 1:点击“重新发送”将显示以下消息(注意这次提到了新电子邮件地址):

      确实有一封电子邮件发送到了电子邮件地址:

      [演示] 确认您的新账户

      欢迎加入 Demo!

      点击以下链接确认并激活您的新账户:
      https://example.discourse.site/u/activate-account/74d53d7d2cf20dsbc360614844c653s2

      如果上述链接无法点击,请尝试将其复制并粘贴到浏览器的地址栏中。

      跟随链接会进入激活新账户的某些页面。最终用户成功登录。从这一点开始,用户可以使用用户名或电子邮件地址登录。新电子邮件似乎仍未生效。

      选项 2:点击“更改电子邮件地址”按钮会显示以下内容。当文本框中输入新电子邮件地址时,“更新电子邮件地址”按钮会被禁用,暗示新电子邮件已经激活(但这似乎并不属实)。

    • 用户发起密码重置。电子邮件会发送到电子邮件地址,用户可以通过链接登录。与其他场景一样,用户可以使用用户名或电子邮件地址登录。新电子邮件似乎仍未生效。

因此,这里确实可以复现这些问题。虽然清晰地描述出来有些棘手,但希望结合原始帖子和这个概述,能够让人理解。显然确实有一些问题需要修复。

@martin 我知道你过去曾对核心部分的这一模块进行过调整。方便时能否在此发表一下意见?

9 个赞

为什么会出现这种退化?:thinking:

编辑:我也可以确认它确实退化了。在编辑普通用户的邮箱时,确认邮件等会发送到旧邮箱。这在过去可不是这样的。

4 个赞

那种令人不适的熟悉感,当你阅读被引用的拉取请求描述时,意识到自己就是罪魁祸首……

感谢 @tshenry@RGJ 提供的详细说明,我本周会优先处理这个问题。

4 个赞

好的,我现在已经理清楚了,并且查阅了旧话题中被删除的评论以追溯历史。我从 @sam 那里挖出了这段内容,我现在想起来了:

管理员重置邮箱的情况非常特殊,实际上是管理员同时重置邮箱和密码。因为如果用户拥有账户访问权限,他们完全可以通过自助服务完成所有操作。

所以我们的意思是,既然邮箱是由管理员更改的,就应该发送一封密码重置邮件。因为如果用户还能访问旧邮箱,他们本可以自行登录并完成操作?但这封密码重置邮件同时也起到了确认的作用。如果不完成密码重置流程(目前这是不可能的,因为邮件发送到了旧邮箱),新邮箱就不会被“确认”,而这正是导致出现以下模态框的原因:

密码重置邮件被发送到旧地址的问题很容易修复,修复后我们至少能进入一个可以跟进重置流程的状态:

此外,由于当前密码重置邮件是发送到旧邮箱的,一旦确认,它反而会确认错误的地址,并将用户的邮箱设置回旧地址。

我将修改更改邮箱的管理员提示信息,明确告知他们:用户必须点击新邮箱中的链接并更改密码,更改才会完全生效(同时也能修复邮箱地址错误的问题)。

4 个赞

等等,我不太理解这一点。

用户能够访问旧邮箱,与用户需要为其 Discourse 账户重置密码,这两者之间存在区别。前者完全不能推导出后者,这是两种截然不同的情况。

管理员执行大量邮箱变更操作,往往是因为用户不知道如何自行操作,或者是因为管理员需要临时解除 email_editable = false 的限制。

我认为将密码重置作为邮箱确认手段非常令人困惑。就我个人而言,我甚至不会回复那封密码重置邮件,因为我并没有主动请求它,也不会意识到这是一个必要的确认步骤(而且我认为这并非必要,一封常规的确认邮件就足够了?)

4 个赞

可能相关:

今天,当我的一个论坛用户尝试重置密码时(使用的是截至今天早上最新版本的 Discourse),他收到了邮件,但点击邮件链接后出现错误:

他在多个浏览器中都遇到此错误,且未使用广告拦截器。

当我进入该用户的账户偏好设置页面并点击“发送密码重置邮件”时,那里也出现了错误提示:

在按钮旁边显示“(错误)”之前,会短暂闪现“(正在发送邮件)”。看起来邮件实际上并未发送。我可以确认,今天其他论坛邮件均正常发送。

该功能此前运行正常……似乎在过去一周内的某个时间出现了故障。

以管理员身份登录 Web 浏览器,检查 Discourse 错误日志,那里应该有一份包含更多详细信息的错误报告。

以下是错误条目:

398 作业异常:指定的复制源大小超过了复制源允许的最大大小:5368709120

aws-sdk-core-3.99.1/lib/seahorse/client/plugins/raise_response_errors.rb:15:in `call'

aws-sdk-s3-1.66.0/lib/aws-sdk-s3/plugins/sse_cpk.rb:22:in `call'

aws-sdk-s3-1.66.0/lib/aws-sdk-s3/plugins/dualstack.rb:26:in `call'

aws-sdk-s3-1.66.0/lib/aws-sdk-s3/plugins/accelerate.rb:35:in `call'

aws-sdk-core-3.99.1/lib/aws-sdk-core/plugins/jsonvalue_converter.rb:20:in `call'

aws-sdk-core-3.99.1/lib/aws-sdk-core/plugins/idempotency_token.rb:17:in `call'

aws-sdk-core-3.99.1/lib/aws-sdk-core/plugins/param_converter.rb:24:in `call'

aws-sdk-core-3.99.1/lib/aws-sdk-core/plugins/response_paging.rb:10:in `call'

aws-sdk-core-3.99.1/lib/seahorse/client/plugins/response_target.rb:23:in `call'

aws-sdk-core-3.99.1/lib/seahorse/client/request.rb:70:in `send_request'

aws-sdk-s3-1.66.0/lib/aws-sdk-s3/client.rb:1108:in `copy_object'

/var/www/discourse/lib/backup_restore/s3_backup_store.rb:61:in `block in vacate_legacy_prefix'

/var/www/discourse/lib/backup_restore/s3_backup_store.rb:60:in `each'

/var/www/discourse/lib/backup_restore/s3_backup_store.rb:60:in `vacate_legacy_prefix'

/var/www/discourse/app/jobs/onceoff/vacate_legacy_prefix_backups.rb:7:in `execute_onceoff'

/var/www/discourse/app/jobs/onceoff/onceoff.rb:25:in `execute'

/var/www/discourse/app/jobs/base.rb:232:in `block (2 levels) in perform'

rails_multisite-2.5.0/lib/rails_multisite/connection_management.rb:76:in `with_connection'

/var/www/discourse/app/jobs/base.rb:221:in `block in perform'

/var/www/discourse/app/jobs/base.rb:217:in `each'

/var/www/discourse/app/jobs/base.rb:217:in `perform'

sidekiq-6.1.2/lib/sidekiq/processor.rb:196:in `execute_job'

sidekiq-6.1.2/lib/sidekiq/processor.rb:164:in `block (2 levels) in process'

sidekiq-6.1.2/lib/sidekiq/middleware/chain.rb:138:in `block in invoke'

/var/www/discourse/lib/sidekiq/pausable.rb:138:in `call'

sidekiq-6.1.2/lib/sidekiq/middleware/chain.rb:140:in `block in invoke'

sidekiq-6.1.2/lib/sidekiq/middleware/chain.rb:143:in `invoke'

sidekiq-6.1.2/lib/sidekiq/processor.rb:163:in `block in process'

sidekiq-6.1.2/lib/sidekiq/processor.rb:136:in `block (6 levels) in dispatch'

sidekiq-6.1.2/lib/sidekiq/job_retry.rb:111:in `local'

sidekiq-6.1.2/lib/sidekiq/processor.rb:135:in `block (5 levels) in dispatch'

sidekiq-6.1.2/lib/sidekiq.rb:38:in `block in <module:Sidekiq>'

sidekiq-6.1.2/lib/sidekiq/processor.rb:131:in `block (4 levels) in dispatch'

sidekiq-6.1.2/lib/sidekiq/processor.rb:257:in `stats'

sidekiq-6.1.2/lib/sidekiq/processor.rb:126:in `block (3 levels) in dispatch'

sidekiq-6.1.2/lib/sidekiq/job_logger.rb:13:in `call'

sidekiq-6.1.2/lib/sidekiq/processor.rb:125:in `block (2 levels) in dispatch'

sidekiq-6.1.2/lib/sidekiq/job_retry.rb:78:in `global'

sidekiq-6.1.2/lib/sidekiq/processor.rb:124:in `block in dispatch'

sidekiq-6.1.2/lib/sidekiq/logger.rb:10:in `with'

sidekiq-6.1.2/lib/sidekiq/job_logger.rb:33:in `prepare'

sidekiq-6.1.2/lib/sidekiq/processor.rb:123:in `dispatch'

sidekiq-6.1.2/lib/sidekiq/processor.rb:162:in `process'

sidekiq-6.1.2/lib/sidekiq/processor.rb:78:in `process_one'

sidekiq-6.1.2/lib/sidekiq/processor.rb:68:in `run'

sidekiq-6.1.2/lib/sidekiq/util.rb:15:in `watchdog'

sidekiq-6.1.2/lib/sidekiq/util.rb:24:in `block in safe_thread'
1 个赞

不,这完全无关。
请尝试清除日志并强制触发此错误,然后再次检查日志。

随后,错误发生后日志仍保持空白。

我理解你的出发点。对我来说,昨天“将密码重置作为确认”这一机制确实让我感到困惑。我认为这可以成为管理员在更改用户邮箱时的一个次级选项,即勾选一个“同时重置用户密码”的复选框。我打算先按当前状态合并我为修复此问题提交的 PR,因为目前的流程完全行不通。

我希望 @sam 能就新的流程发表意见,因为 Sam 最初曾阐述过密码重置流程背后的原因:

  1. 管理员更改用户的邮箱。他们可以选择同时重置其密码。
  2. 用户会收到一封发往新邮箱的邮件,请求确认邮箱变更。
    • 如果用户选择“是”,则更改邮箱。我们会向旧邮箱发送一封通知,告知邮箱已变更。
    • 如果用户选择“否”,则不执行任何操作。
  3. 如果管理员在第 1 步中指定了需要重置密码,那么一旦用户确认了邮箱变更,他们就会在新邮箱地址收到一封密码重置邮件。

我认为这样会清晰得多,而且密码重置将与确认邮箱变更完全无关。

2 个赞

是的,我不明白这两件事为什么会有关联?

1 个赞

我看到一个很棒的已合并的新提交,大家会很高兴的 :smiley:

3 个赞

谢谢,这只会合并一个针对“完全混乱”状态的修复。另一个 PR 即将跟进!

这是根据之前与 Sam 在上一主题中的讨论而这样处理的。我将按照新流程推进,消除困惑,并切断这些无关事物之间的关联。

4 个赞

我刚合并了以下 PR:

  • 修改了管理员为用户更改邮箱的流程,现在会向用户发送确认邮件
  • 我们现已记录邮箱更改请求是由谁发起的
  • 如果请求者是管理员而非用户本人,我们会在向用户发送的邮件中注明这一点
  • 我们还使确认更改邮箱的路径对匿名用户开放,这样即使用户无法访问其账户,也可以点击该链接。如果当前有已登录用户,我们会确保确认信息与当前用户匹配。

希望这能让整个流程更加清晰!

4 个赞