74 lines
3.7 KiB
Go
74 lines
3.7 KiB
Go
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)
|
||
}
|