fix: 统一对话密码锁键为发送方加对方不含 IP

This commit is contained in:
Nixevol
2026-09-30 16:23:25 +08:00
parent b7c8b6ffd6
commit e294b5db71
17 changed files with 449 additions and 32 deletions
+3 -1
View File
@@ -64,7 +64,7 @@ const (
LockLoginEndpointIP LockKind = "login_endpoint_ip"
// LockLoginEndpoint:编号总数,1 小时内 50 次错 → 暂停该编号密码登录 1 小时。
LockLoginEndpoint LockKind = "login_endpoint"
// LockTalkPair:发送方 + 对方对话密码。
// LockTalkPair:发送方 + 对方对话密码(不含 IP)。
LockTalkPair LockKind = "talk_pair"
// LockTalkTarget:对方对话密码总数。
LockTalkTarget LockKind = "talk_target"
@@ -90,6 +90,8 @@ type LoginLocks interface {
Fail(key LockKey) (locked bool, retryAfter time.Duration)
// ClearEndpoint 清除某端编号相关的登录锁定(两种都清),对应管理 unlock。
ClearEndpoint(endpointID string)
// ClearAllForEndpoint 清除该编号作为 EndpointID 或 PeerID 出现的全部锁定(删除端后防编号复用继承)。
ClearAllForEndpoint(endpointID string)
// Clear 清除精确键。
Clear(key LockKey)
}
+51
View File
@@ -138,3 +138,54 @@ func TestLoginLocksClearEndpoint(t *testing.T) {
t.Fatal("e1 total lock should clear")
}
}
func TestLoginLocksClearEndpointKeepsTalk(t *testing.T) {
locks := NewLoginLocks()
talk := LockKey{Kind: LockTalkPair, EndpointID: "e1", PeerID: "e2"}
target := LockKey{Kind: LockTalkTarget, EndpointID: "e1"}
login := LockKey{Kind: LockLoginEndpoint, EndpointID: "e1"}
for i := 0; i < 10; i++ {
locks.Fail(talk)
locks.Fail(login)
}
for i := 0; i < 50; i++ {
locks.Fail(target)
}
locks.ClearEndpoint("e1")
if locked, _ := locks.Check(login); locked {
t.Fatal("login lock should clear")
}
if locked, _ := locks.Check(talk); !locked {
t.Fatal("talk pair lock must survive ClearEndpoint")
}
if locked, _ := locks.Check(target); !locked {
t.Fatal("talk target lock must survive ClearEndpoint")
}
}
func TestLoginLocksClearAllForEndpoint(t *testing.T) {
locks := NewLoginLocks()
asSender := LockKey{Kind: LockTalkPair, EndpointID: "gone", PeerID: "peer"}
asPeer := LockKey{Kind: LockTalkPair, EndpointID: "other", PeerID: "gone"}
target := LockKey{Kind: LockTalkTarget, EndpointID: "gone"}
login := LockKey{Kind: LockLoginEndpointIP, EndpointID: "gone", IP: "1.1.1.1"}
keep := LockKey{Kind: LockTalkPair, EndpointID: "keep", PeerID: "peer"}
for i := 0; i < 10; i++ {
locks.Fail(asSender)
locks.Fail(asPeer)
locks.Fail(login)
locks.Fail(keep)
}
for i := 0; i < 50; i++ {
locks.Fail(target)
}
locks.ClearAllForEndpoint("gone")
for _, k := range []LockKey{asSender, asPeer, target, login} {
if locked, _ := locks.Check(k); locked {
t.Fatalf("expected %s cleared", lockMapKey(k))
}
}
if locked, _ := locks.Check(keep); !locked {
t.Fatal("unrelated pair should remain")
}
}
+18
View File
@@ -245,6 +245,24 @@ func (l *MemoryLocks) ClearEndpoint(endpointID string) {
}
}
// ClearAllForEndpoint 清除该编号作为 EndpointID 或 PeerID 出现的全部锁定。
func (l *MemoryLocks) ClearAllForEndpoint(endpointID string) {
l.mu.Lock()
defer l.mu.Unlock()
if endpointID == "" {
return
}
for k := range l.entries {
parts := splitLockKey(k)
if len(parts) != 4 {
continue
}
if parts[1] == endpointID || parts[2] == endpointID {
delete(l.entries, k)
}
}
}
// Clear 清除精确键。
func (l *MemoryLocks) Clear(key LockKey) {
l.mu.Lock()
+6
View File
@@ -94,6 +94,12 @@ func (l *StubLoginLocks) ClearEndpoint(endpointID string) {
l.Cleared = append(l.Cleared, endpointID)
}
func (l *StubLoginLocks) ClearAllForEndpoint(endpointID string) {
l.mu.Lock()
defer l.mu.Unlock()
l.Cleared = append(l.Cleared, endpointID)
}
func (l *StubLoginLocks) Clear(LockKey) {}
// 编译期检查:假实现满足接口。