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:
@@ -18,25 +18,21 @@
|
||||
<a href="/admin/users" {{if eq .activeFolder "users"}}class="active"{{end}}>用户管理</a>
|
||||
<a href="/admin/mails" {{if eq .activeFolder "mails"}}class="active"{{end}}>所有邮件</a>
|
||||
<a href="/admin/outbound" {{if eq .activeFolder "outbound"}}class="active"{{end}}>外发队列</a>
|
||||
<a href="/admin/protocol-logs" {{if eq .activeFolder "protocol-logs"}}class="active"{{end}}>协议日志</a>
|
||||
<a href="/admin/bans" {{if eq .activeFolder "bans"}}class="active"{{end}}>IP黑名单</a>
|
||||
<a href="/admin/protocol-logs" {{if eq .activeFolder "protocol-logs"}}class="active"{{end}}>协议日志</a>
|
||||
<a href="/admin/bans" {{if eq .activeFolder "bans"}}class="active"{{end}}>IP黑名单</a>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="card">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:16px;">
|
||||
<h2>IP 黑名单</h2>
|
||||
<form method="POST" action="/admin/bans/cleanup" style="display:inline;">
|
||||
<button type="submit" class="btn btn-primary">清理过期记录</button>
|
||||
</form>
|
||||
<span style="color:#7f8c8d;font-size:13px;">阶段性封禁:第 4 次触发起封禁,30分钟 → 3小时 → 3个月 → 半年(上限)</span>
|
||||
</div>
|
||||
{{if not .bans}}
|
||||
<p style="color:#7f8c8d;text-align:center;padding:40px 0;">暂无被封禁的 IP</p>
|
||||
{{else}}
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>IP 地址</th>
|
||||
<th>状态</th>
|
||||
<th>封禁次数</th>
|
||||
<th>失败次数</th>
|
||||
<th>原因</th>
|
||||
<th>到期时间</th>
|
||||
@@ -44,16 +40,24 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range .bans}}
|
||||
{{range .rows}}
|
||||
<tr>
|
||||
<td>{{.ID}}</td>
|
||||
<td>{{.IPAddress}}</td>
|
||||
<td>
|
||||
{{if .Active}}<span class="badge" style="background:#e74c3c;color:#fff;">封禁中</span>
|
||||
{{else}}<span class="badge" style="background:#95a5a6;color:#fff;">已过期</span>{{end}}
|
||||
</td>
|
||||
<td>
|
||||
{{if .BanCount}}
|
||||
{{if gt .BanCount 3}}第{{sub .BanCount 3}}次封禁{{else}}仅计数{{end}}
|
||||
{{else}}—{{end}}
|
||||
</td>
|
||||
<td>{{.FailCount}}</td>
|
||||
<td>{{.Reason}}</td>
|
||||
<td>{{.ExpiresAt.Format "2006-01-02 15:04:05"}}</td>
|
||||
<td>{{if .Reason}}{{.Reason}}{{else}}—{{end}}</td>
|
||||
<td>{{.ExpiresAt.Format "2006-01-02 15:04:05"}}{{if not .Active}}(已过期){{end}}</td>
|
||||
<td>
|
||||
<form method="POST" action="/admin/bans/{{.ID}}/unban" style="display:inline;"
|
||||
onsubmit="return confirm('确定要解封 IP {{.IPAddress}} 吗?');">
|
||||
onsubmit="return confirm('确定要解封 IP {{.IPAddress}} 吗?解封后该 IP 的封禁档位将清零。');">
|
||||
<button type="submit" class="btn btn-primary btn-sm">解封</button>
|
||||
</form>
|
||||
</td>
|
||||
@@ -61,6 +65,8 @@
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
{{if not .rows}}
|
||||
<p style="color:#7f8c8d;text-align:center;padding:40px 0;">暂无封禁记录</p>
|
||||
{{end}}
|
||||
</div>
|
||||
{{if .totalPages}}
|
||||
@@ -68,7 +74,7 @@
|
||||
{{if gt .page 1}}
|
||||
<a href="/admin/bans?page={{sub .page 1}}">上一页</a>
|
||||
{{end}}
|
||||
<span>第 {{.page}} / {{.totalPages}} 页</span>
|
||||
<span>第 {{.page}} / {{.totalPages}} 页(共 {{.total}} 条)</span>
|
||||
{{if lt .page .totalPages}}
|
||||
<a href="/admin/bans?page={{add .page 1}}">下一页</a>
|
||||
{{end}}
|
||||
|
||||
@@ -51,6 +51,10 @@
|
||||
<span class="detail-label">IP 地址</span>
|
||||
<span class="detail-value">{{.entry.IPAddress}}</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">封禁档位</span>
|
||||
<span class="detail-value">{{if gt .entry.BanCount 3}}第 {{sub .entry.BanCount 3}} 次封禁{{else}}—{{end}}</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">原因</span>
|
||||
<span class="detail-value">{{.entry.Reason}}</span>
|
||||
|
||||
Reference in New Issue
Block a user