fix: stabilize console scrolling and protect administrator login

This commit is contained in:
2026-09-30 00:09:01 +08:00
parent 9a15406f76
commit f56f233704
32 changed files with 1308 additions and 86 deletions

View File

@@ -0,0 +1,73 @@
package consoleauth
import (
"context"
"errors"
"git.yuntuo.hk/k2safe/orbit/platform/auth"
"net/netip"
"time"
)
// LoginPolicy 是所有节点统一配置的登录保护策略;计数及锁定保存在数据库。
type LoginPolicy struct {
FailureLimit int // FailureLimit 失败窗口内允许的连续失败次数,达到后锁定。
FailureWindow time.Duration // FailureWindow 连续失败的统计窗口。
LockDuration time.Duration // LockDuration 临时锁定时长,锁定期间请求不续期。
AccountLimit int // AccountLimit 每个限流窗口最多接受的账号登录尝试数。
IPLimit int // IPLimit 每个限流窗口最多接受的来源 IP 登录尝试数。
RateWindow time.Duration // RateWindow 固定限流窗口;成功登录不清空流量计数。
AuditDays int // AuditDays 登录审计保留天数,与业务数据策略独立。
TrustedProxies []netip.Prefix // TrustedProxies 明确信任的反向代理网段;空时忽略所有转发头。
}
func DefaultLoginPolicy() LoginPolicy {
return LoginPolicy{FailureLimit: 5, FailureWindow: 15 * time.Minute, LockDuration: 15 * time.Minute, AccountLimit: 10, IPLimit: 30, RateWindow: time.Minute, AuditDays: 90}
}
func (p LoginPolicy) Validate() error {
if p.FailureLimit < 1 || p.FailureLimit > 100 || p.AccountLimit < 1 || p.AccountLimit > 1000 || p.IPLimit < 1 || p.IPLimit > 10000 {
return errors.New("console login limits out of range")
}
for _, d := range []time.Duration{p.FailureWindow, p.LockDuration, p.RateWindow} {
if d < time.Second || d > 24*time.Hour {
return errors.New("console login durations must be 1s–24h")
}
}
if p.AuditDays < 1 || p.AuditDays > 3650 {
return errors.New("console login audit retention must be 1–3650 days")
}
for _, n := range p.TrustedProxies {
if !n.IsValid() {
return errors.New("invalid console trusted proxy CIDR")
}
}
return nil
}
// LoginBlocked 对外不区分账号是否存在,只给出拒绝类型和最短等待时间。
type LoginBlocked struct {
Locked bool // Locked 表示临时锁定,否则为请求限流。
RetryAfter time.Duration // RetryAfter 按数据库时钟计算的剩余等待时间。
}
func (*LoginBlocked) Error() string { return "console login temporarily blocked" }
// LoginAuditEntry 是脱敏的登录审计;从不记录密码、Cookie、Token 或它们的摘要。
type LoginAuditEntry struct {
ID uint64 `json:"id,string"` // ID 审计游标,按插入序号倒序分页。
Username string `json:"username"` // Username 提交的有界登录名,不证明账号存在。
RemoteIP string `json:"remote_ip"` // RemoteIP 经可信代理规则解析的来源地址。
UserAgent string `json:"user_agent"` // UserAgent 截断为最多 512 字节的浏览器信息。
Outcome string `json:"outcome"` // Outcome PENDING、SUCCESS、INVALID、LOCKED、ACCOUNT_LIMIT、IP_LIMIT 或 ERROR。
CreatedAt time.Time `json:"created_at"` // CreatedAt 尝试时间,UTC。
FinishedAt *time.Time `json:"finished_at,omitempty"` // FinishedAt 完成时间;空表示尚未确认结果。
RetryAt *time.Time `json:"retry_at,omitempty"` // RetryAt 拒绝时建议最早重试时间,UTC。
}
// LoginAuditPage 固定每页 10 条,Next 是下一页排他上界;不承诺跨页快照。
type LoginAuditPage struct {
Items []LoginAuditEntry `json:"items"` // Items 当前页脱敏记录。
Next string `json:"next,omitempty"` // Next 下一页游标,无更多时为空。
}
type LoginAuditor interface {
ListLoginAudit(context.Context, auth.Token, string) (LoginAuditPage, error)
}