[U-03][medium] 对话密码锁定不一致:按对计数键三处各不相同、锁定期内不带密码也报 rate_limited、后台改对话密码与删除端不清锁定 #41

Closed
opened 2026-09-30 13:57:01 +08:00 by nixevol · 1 comment
Owner

编号:U-03 严重级:medium 工作线:身份、群、在线与认证基础(internal/app/identity、group、presence、internal/auth) 来源:审查 I-13、M-17、I-14、A-11
依赖:无 被依赖:U-04 (#42)(同改 auth/locks.go)

结论与统一方案

合并审查 I-13、I-14、M-17、A-11:

  1. 按对计数统一为 {LockTalkPair, 发送方, 对方},不带 IP;unlock、单聊发送、拉人进群三处与成功后清零都用同一个键。(审查 I-13、M-17 第 2 点)
  2. 发送时不带密码直接返回 talk_password_required,只有带密码时才检查锁定。DEVELOPMENT 第 5 节原文是"期间 unlock、带密码的发送和拉人进群都返回 rate_limited"。(审查 M-17 第 1 点)
  3. 后台改对话密码改为调用 identity.SelfSetTalkPassword(或成功后清除 LockTalkTarget),not_found 映射 404。(审查 A-11、I-14)
  4. 删除端后清除该编号的全部锁定(auth 增加按编号清除所有类型的方法),同编号重开不继承锁定。(审查 I-14)

改动文件

internal/app/identity/talk.go、lifecycle.go(删除路径加清锁一处);internal/app/message/submit.go(dmAuthNeeded、talkClear 等对话密码函数);internal/app/group/app.go(checkAddMember);internal/admin/endpoints.go(handleEndpointTalkPassword);internal/auth/locks.go。

与其他问题的交互 / 冲突说明

这些文件都有其他 issue 在改,本条只动对话密码相关函数:submit.go 的限速归 C-05、校验归 C-07;lifecycle.go 的作废归 C-04、断开 goroutine 归 B-04;endpoints.go 其余函数由管理后端线修改;locks.go 与 U-04 同文件,身份线内顺序合入。

验收与测试

  • 同一发送方从两个 IP 交替猜错,第 10 次锁定;unlock、send、加人三条路径合计 10 次锁定;成功后计数清零。
  • 锁定期内不带密码返回 talk_password_required。
  • 被暂停的端经后台改对话密码后,立即 unlock 成功。
  • 删除一个被锁定的端再重开,立即能用密码登录。

问题明细(各区审查原文,证据含文件与行号)

以下是本次复审各区审查报告的原文段落。A、M、I、P、S 开头的是原始发现编号(A 管理后台与网页、M 消息核心、I 身份认证群在线、P 传输平台部署、S SDK)。解决方案以本 issue 上方的"结论与统一方案"为准;原文里的方案与之不一致时,按上方执行。

[I-13] 对话密码「按对锁定」的计数键和文档不一致,三处用法也各不相同

  • 严重级:low
  • 分类:协议一致性 / 安全
  • 现象与影响
    • unlock 和单聊用的是(发送方, 对方, IP),换个 IP 就重新获得 10 次机会。
    • 群加人用的 IP 恒为空串(serve 没有设 DefaultRemoteIP),是第三套独立计数。
    • 消息线成功后清零用的是不带 IP 的键,清不到真正的计数。
    • 按对方计的总数锁仍然有效,所以风险有限。
  • 证据:identity/talk.go:35、52、56、119、136、140;message/submit.go:560、573、581;group/app.go:633;cmd/nixmsg/serve.go:164-170。
  • 文档依据:DEVELOPMENT 第 5 节(292)「按『发送方 + 对方』计数」;PRD F15(327)。
  • 为何不是故意设计:I2 第 9 条只说增加 remoteIP 参数用于锁定计数,没有说对话密码的按对计数要带 IP;三处互不一致也显然不是设计。
  • 解决方案:三处统一用 {LockTalkPair, 发送方, 对方},不带 IP。
  • 改动文件:identity/talk.go、message/submit.go(消息线)、group/app.go。
  • 与其他模块的交互/冲突风险:需要和消息线的改动一起合入。
  • 需补测试:同一发送方从两个 IP 交替猜错,第 10 次锁定;unlock、send、加人三条路径合计 10 次锁定;成功后计数清零。
  • 置信度:代码阅读确定。

[M-17] 对话密码锁定的细节:锁定期内不带密码也返回 rate_limited,成功后计数没清零

  • 严重级:low
  • 分类:协议一致性
  • 现象与影响:
    • 锁定检查排在「没带密码」检查之前。锁定期内不带密码发送也返回 rate_limited,而 SDK 对发送收到 rate_limited 会自动退避重交,最长静默重试 1 小时,而不是立即得到 talk_password_required。
    • talkClear 构造的锁定键没带 IP,而计数键带了,所以密码输对后「发送方+对方」的失败计数不会清零。
  • 证据:submit.go:117-123、submit.go:577-582;internal/auth/locks.go:55-57。
  • 文档依据:DEVELOPMENT 第 5 节「期间 unlock、带密码的发送和拉人进群都返回 rate_limited」;DEVELOPMENT 第 9 节 SDK 的重交规则。
  • 为何不是故意设计:DEVIATIONS 里没有相关说明。
  • 解决方案:没带密码时直接返回 talk_password_required,只有带密码时才检查锁定;talkClear 带上 IP,与 identity 一致。
  • 改动文件:internal/app/message/submit.go。
  • 交互/冲突风险:无。
  • 需补测试:锁定期内不带密码返回 talk_password_required;9 次输错后输对一次,再错一次不锁定。
  • 置信度:代码阅读确定。

[I-14] 后台改对话密码不清零按对方的计数;删除端后同编号重开会继承锁定

  • 严重级:low
  • 分类:与PRD不符
  • 现象与影响
    • 端自己改对话密码会清零计数,后台改不会。后台的解除锁定只清登录锁定,所以管理员没办法解除对话密码暂停。
    • 删除端时不清内存里的锁定,同编号重新开通后可能直接处于锁定状态。
  • 证据:admin/endpoints.go:547-589、admin/endpoints_db.go:398-413;对照 identity/self.go:114-116;identity/lifecycle.go:72-140;auth/locks.go:115-133。
  • 文档依据:PRD F15(327)、D24、F01(137)。
  • 为何不是故意设计:没有偏差记录。
  • 解决方案:后台改对话密码后清除 LockTalkTarget;删除端后清除该编号的全部锁定。
  • 改动文件:admin/endpoints.go(后台线)、identity/lifecycle.go、auth/locks.go。
  • 与其他模块的交互/冲突风险:无。
  • 需补测试:被暂停的端经后台改密后,立即 unlock 成功;删除一个被锁定的端再重开,立即能用密码登录。
  • 置信度:代码阅读确定。

[A-11] 后台设置对话密码不清零"按被猜端"的锁定计数

  • 严重级:medium
  • 分类:逻辑 / 与PRD不符
  • 现象与影响:遇到轮流猜对话密码时,管理员的主要补救手段是在后台换对话密码。但暂停验证的锁定仍会持续到满 1 小时,知道新密码的正常发送方依然收到 rate_limited。
  • 证据:
    • endpoints.go:547-589 只更新了 talk_hash 和版本号。
    • 端自己改对话密码时会清零:internal/app/identity/self.go:114-115 调用 a.locks.Clear(auth.LockKey{Kind: auth.LockTalkTarget, ...})。
    • identity.Service.SelfSetTalkPassword(service.go:48)已经对外提供。
  • 文档依据:PRD F15(约 328 行)"它改对话密码时计数清零(D24)";DEVELOPMENT §5(约 293 行)。
  • 为何不是故意设计:DEVIATIONS 没有相关条目。
  • 解决方案:注入了 Identity 时,改为调用 h.identity.SelfSetTalkPassword(ctx, id, pw),其中 not_found 映射为 404;否则在成功后执行 h.locks.Clear(LockTalkTarget, id)。
  • 改动文件:internal/admin/endpoints.go。
  • 交互/冲突风险:与 I 线共用一套逻辑,减少两边不一致。
  • 需补测试:先连续失败 50 次触发锁定,再调用 PUT talk-password,之后 Check 为 false。
  • 置信度:代码阅读确定

复审基线:main 4059a15(2026-09-30)。编号说明、各工作线的合并顺序、共享文件归属见总览 #7。

**编号**:U-03 **严重级**:medium **工作线**:身份、群、在线与认证基础(internal/app/identity、group、presence、internal/auth) **来源**:审查 I-13、M-17、I-14、A-11 **依赖**:无 **被依赖**:U-04 (#42)(同改 auth/locks.go) ### 结论与统一方案 合并审查 I-13、I-14、M-17、A-11: 1. 按对计数统一为 `{LockTalkPair, 发送方, 对方}`,不带 IP;unlock、单聊发送、拉人进群三处与成功后清零都用同一个键。(审查 I-13、M-17 第 2 点) 2. 发送时不带密码直接返回 `talk_password_required`,只有带密码时才检查锁定。DEVELOPMENT 第 5 节原文是"期间 unlock、**带密码的**发送和拉人进群都返回 rate_limited"。(审查 M-17 第 1 点) 3. 后台改对话密码改为调用 `identity.SelfSetTalkPassword`(或成功后清除 `LockTalkTarget`),not_found 映射 404。(审查 A-11、I-14) 4. 删除端后清除该编号的全部锁定(auth 增加按编号清除所有类型的方法),同编号重开不继承锁定。(审查 I-14) ### 改动文件 `internal/app/identity/talk.go`、`lifecycle.go`(删除路径加清锁一处);`internal/app/message/submit.go`(`dmAuthNeeded`、`talkClear` 等对话密码函数);`internal/app/group/app.go`(`checkAddMember`);`internal/admin/endpoints.go`(`handleEndpointTalkPassword`);`internal/auth/locks.go`。 ### 与其他问题的交互 / 冲突说明 这些文件都有其他 issue 在改,本条只动对话密码相关函数:submit.go 的限速归 C-05、校验归 C-07;lifecycle.go 的作废归 C-04、断开 goroutine 归 B-04;endpoints.go 其余函数由管理后端线修改;locks.go 与 U-04 同文件,身份线内顺序合入。 ### 验收与测试 - 同一发送方从两个 IP 交替猜错,第 10 次锁定;unlock、send、加人三条路径合计 10 次锁定;成功后计数清零。 - 锁定期内不带密码返回 `talk_password_required`。 - 被暂停的端经后台改对话密码后,立即 unlock 成功。 - 删除一个被锁定的端再重开,立即能用密码登录。 --- ### 问题明细(各区审查原文,证据含文件与行号) > 以下是本次复审各区审查报告的原文段落。A、M、I、P、S 开头的是原始发现编号(A 管理后台与网页、M 消息核心、I 身份认证群在线、P 传输平台部署、S SDK)。**解决方案以本 issue 上方的"结论与统一方案"为准**;原文里的方案与之不一致时,按上方执行。 #### [I-13] 对话密码「按对锁定」的计数键和文档不一致,三处用法也各不相同 - **严重级**:low - **分类**:协议一致性 / 安全 - **现象与影响** - unlock 和单聊用的是(发送方, 对方, IP),换个 IP 就重新获得 10 次机会。 - 群加人用的 IP 恒为空串(serve 没有设 `DefaultRemoteIP`),是第三套独立计数。 - 消息线成功后清零用的是不带 IP 的键,清不到真正的计数。 - 按对方计的总数锁仍然有效,所以风险有限。 - **证据**:`identity/talk.go:35`、`52`、`56`、`119`、`136`、`140`;`message/submit.go:560`、`573`、`581`;`group/app.go:633`;`cmd/nixmsg/serve.go:164-170`。 - **文档依据**:DEVELOPMENT 第 5 节(292)「按『发送方 + 对方』计数」;PRD F15(327)。 - **为何不是故意设计**:I2 第 9 条只说增加 remoteIP 参数用于锁定计数,没有说对话密码的按对计数要带 IP;三处互不一致也显然不是设计。 - **解决方案**:三处统一用 `{LockTalkPair, 发送方, 对方}`,不带 IP。 - **改动文件**:`identity/talk.go`、`message/submit.go`(消息线)、`group/app.go`。 - **与其他模块的交互/冲突风险**:需要和消息线的改动一起合入。 - **需补测试**:同一发送方从两个 IP 交替猜错,第 10 次锁定;unlock、send、加人三条路径合计 10 次锁定;成功后计数清零。 - **置信度**:代码阅读确定。 #### [M-17] 对话密码锁定的细节:锁定期内不带密码也返回 rate_limited,成功后计数没清零 - 严重级:low - 分类:协议一致性 - 现象与影响: - 锁定检查排在「没带密码」检查之前。锁定期内不带密码发送也返回 `rate_limited`,而 SDK 对发送收到 `rate_limited` 会自动退避重交,最长静默重试 1 小时,而不是立即得到 `talk_password_required`。 - `talkClear` 构造的锁定键没带 IP,而计数键带了,所以密码输对后「发送方+对方」的失败计数不会清零。 - 证据:`submit.go:117-123`、`submit.go:577-582`;`internal/auth/locks.go:55-57`。 - 文档依据:DEVELOPMENT 第 5 节「期间 unlock、**带密码的**发送和拉人进群都返回 rate_limited」;DEVELOPMENT 第 9 节 SDK 的重交规则。 - 为何不是故意设计:DEVIATIONS 里没有相关说明。 - 解决方案:没带密码时直接返回 `talk_password_required`,只有带密码时才检查锁定;`talkClear` 带上 IP,与 identity 一致。 - 改动文件:`internal/app/message/submit.go`。 - 交互/冲突风险:无。 - 需补测试:锁定期内不带密码返回 `talk_password_required`;9 次输错后输对一次,再错一次不锁定。 - 置信度:代码阅读确定。 #### [I-14] 后台改对话密码不清零按对方的计数;删除端后同编号重开会继承锁定 - **严重级**:low - **分类**:与PRD不符 - **现象与影响** - 端自己改对话密码会清零计数,后台改不会。后台的解除锁定只清登录锁定,所以管理员没办法解除对话密码暂停。 - 删除端时不清内存里的锁定,同编号重新开通后可能直接处于锁定状态。 - **证据**:`admin/endpoints.go:547-589`、`admin/endpoints_db.go:398-413`;对照 `identity/self.go:114-116`;`identity/lifecycle.go:72-140`;`auth/locks.go:115-133`。 - **文档依据**:PRD F15(327)、D24、F01(137)。 - **为何不是故意设计**:没有偏差记录。 - **解决方案**:后台改对话密码后清除 `LockTalkTarget`;删除端后清除该编号的全部锁定。 - **改动文件**:`admin/endpoints.go`(后台线)、`identity/lifecycle.go`、`auth/locks.go`。 - **与其他模块的交互/冲突风险**:无。 - **需补测试**:被暂停的端经后台改密后,立即 unlock 成功;删除一个被锁定的端再重开,立即能用密码登录。 - **置信度**:代码阅读确定。 #### [A-11] 后台设置对话密码不清零"按被猜端"的锁定计数 - 严重级:medium - 分类:逻辑 / 与PRD不符 - 现象与影响:遇到轮流猜对话密码时,管理员的主要补救手段是在后台换对话密码。但暂停验证的锁定仍会持续到满 1 小时,知道新密码的正常发送方依然收到 `rate_limited`。 - 证据: - `endpoints.go:547-589` 只更新了 `talk_hash` 和版本号。 - 端自己改对话密码时会清零:`internal/app/identity/self.go:114-115` 调用 `a.locks.Clear(auth.LockKey{Kind: auth.LockTalkTarget, ...})`。 - `identity.Service.SelfSetTalkPassword`(`service.go:48`)已经对外提供。 - 文档依据:PRD F15(约 328 行)"它改对话密码时计数清零(D24)";DEVELOPMENT §5(约 293 行)。 - 为何不是故意设计:DEVIATIONS 没有相关条目。 - 解决方案:注入了 Identity 时,改为调用 `h.identity.SelfSetTalkPassword(ctx, id, pw)`,其中 not_found 映射为 404;否则在成功后执行 `h.locks.Clear(LockTalkTarget, id)`。 - 改动文件:`internal/admin/endpoints.go`。 - 交互/冲突风险:与 I 线共用一套逻辑,减少两边不一致。 - 需补测试:先连续失败 50 次触发锁定,再调用 PUT talk-password,之后 `Check` 为 false。 - 置信度:代码阅读确定 --- <sub>复审基线:main `4059a15`(2026-09-30)。编号说明、各工作线的合并顺序、共享文件归属见总览 #7。</sub>
nixevol added the P2-mediumlane/identityreview-2026-09-30 labels 2026-09-30 13:57:01 +08:00
Author
Owner

已合入 origin/main 0c9b459。落地提交 e294b5d fix: 统一对话密码锁键为发送方加对方不含 IP (#41)。

已合入 origin/main `0c9b459`。落地提交 `e294b5d` fix: 统一对话密码锁键为发送方加对方不含 IP (#41)。
Sign in to join this conversation.