ebbeed90e9
补上 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>
32 lines
915 B
Go
32 lines
915 B
Go
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("阈值应在 3–10 合理区间,得 %d", loginFailThreshold)
|
||
}
|
||
if loginFailWindow < 5*60*1e9 {
|
||
t.Fatal("锁定窗口不该太短")
|
||
}
|
||
}
|