Files
orbit/internal/consoleauth/login_policy.go

74 lines
3.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)
}