fix: stabilize console scrolling and protect administrator login
This commit is contained in:
73
internal/consoleauth/login_policy.go
Normal file
73
internal/consoleauth/login_policy.go
Normal 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)
|
||||
}
|
||||
Reference in New Issue
Block a user