Files
Blizzard ebbeed90e9 feat(auth): 登录账户级失败锁定 + 密码强度 8-72 位(B4)
补上 A5 只做了 IP 限流、没做账户锁定的缺口(审计 B4)。

- 账户级失败锁定:连续登录失败达 5 次即临时锁定 15min(Redis 计数+TTL),锁定期直接拒绝
  不再校验密码,成功登录即清零。与 A5 的 IP 限流互补:IP 限流挡'一个 IP 猛打',账户锁定
  挡'分布式慢速猜一个号'。权衡:per-account 可被人为锁死受害者(lockout DoS),故窗口设短
  自动解锁+叠加 IP 限流;Redis 降级则不锁定(尽力而为,IP 限流仍在)。
- 密码强度:注册从'至少 6 位'提到 8-72 位。上限 72 字节——bcrypt 超 72 字节静默截断,
  不拦会让超长密码实际只用前 72 字节。桌面端注册提示同步改 8-72 位。

存量用户不受影响(只校验注册/改密时的新密码)。build+vet+test 绿;desktop tsc 绿。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 15:56:29 +08:00

32 lines
915 B
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package store
import "testing"
// streamKey 必须按 channel 分命名空间,token 与 exec 流互不串扰。
func TestStreamKey_ChannelIsolation(t *testing.T) {
tok := streamKey(ChannelToken, "task_1")
ex := streamKey(ChannelExec, "task_1")
if tok == ex {
t.Fatalf("同任务的 token/exec 流 key 不应相同: %q", tok)
}
if tok != "sundynix:stream:task_1" {
t.Errorf("token key=%q", tok)
}
if ex != "sundynix:exec:task_1" {
t.Errorf("exec key=%q", ex)
}
}
// 登录失败计数 key 独立命名空间,且阈值/窗口是合理防撞库值。
func TestLoginFailKey(t *testing.T) {
if k := loginFailKey("a@b.com"); k != "sundynix:loginfail:a@b.com" {
t.Fatalf("key=%q", k)
}
if loginFailThreshold < 3 || loginFailThreshold > 10 {
t.Fatalf("阈值应在 310 合理区间,得 %d", loginFailThreshold)
}
if loginFailWindow < 5*60*1e9 {
t.Fatal("锁定窗口不该太短")
}
}