feat(security): IP 阶段性封禁,前3次触发不封禁、第4次起按档位递增

- 封禁规则:达到失败阈值记为一次触发,前 3 次只计数不封禁;
  第 4 次起封禁并按档位递增:30分钟(ban_duration_min)→ 3小时
  → 3个月 → 半年(上限);封禁过期后保留记录作为升档依据,
  成功登录或管理员解封清零
- BanEntry 新增 BanCount(累计触发次数),每 IP 一条记录 upsert,
  不再重复建行;RecordAuthFailure 统一 Web/LDAP/SMTP/IMAP/POP3
  五处封禁逻辑,原因带档位(如"第1次封禁:登录失败次数过多
  (第4次触发,失败5次)")
- 黑名单页修复:列表仅显示已封禁或曾封禁记录(原因/到期时间必填),
  新增封禁次数列与封禁中/已过期状态徽章,移除清理过期按钮
- 用户封禁页显示第 N 次封禁档位;新增档位升级与列表过滤单测
This commit is contained in:
2026-08-19 19:07:25 +08:00
parent 353bfa88f2
commit f2493da03e
14 changed files with 350 additions and 107 deletions
+2 -2
View File
@@ -44,8 +44,8 @@ func (b *imapBackend) Login(connInfo *imap.ConnInfo, username, password string)
user, err := b.stores.Users.Authenticate(username, password)
if err != nil {
// 认证失败计数,达到阈值封禁(与 Web 登录共用 ban_entries
b.stores.RecordAuthFailure(clientIP, b.banCfg.MaxFailAttempts, b.banCfg.BanDurationMin)
// 认证失败计数,达到阈值按档位封禁(与 Web 登录共用 ban_entries
b.stores.RecordAuthFailure(clientIP, b.banCfg.MaxFailAttempts, b.banCfg.BanDurationMin, "邮件协议认证失败次数过多")
b.recordLogin(clientIP, username, false, "用户名或密码错误", "LOGIN 失败", 0, now)
return nil, fmt.Errorf("invalid credentials: %w", err)
}