126 lines
2.8 KiB
Go
126 lines
2.8 KiB
Go
package admin
|
|
|
|
import (
|
|
"sync"
|
|
"time"
|
|
|
|
"git.asio.asia/nixevol/NixMsg/internal/auth"
|
|
)
|
|
|
|
// MemoryLoginLocks 是内存登录锁定(重启清零)。
|
|
// P3 正式实现合入前,A1 用本实现满足管理员 IP 锁定;参数对齐 DEVELOPMENT 第 5 节。
|
|
type MemoryLoginLocks struct {
|
|
mu sync.Mutex
|
|
entries map[string]*lockState
|
|
now func() time.Time
|
|
}
|
|
|
|
type lockState struct {
|
|
fails []time.Time
|
|
lockedUntil time.Time
|
|
}
|
|
|
|
// NewMemoryLoginLocks 创建内存锁定计数器。
|
|
func NewMemoryLoginLocks() *MemoryLoginLocks {
|
|
return &MemoryLoginLocks{
|
|
entries: make(map[string]*lockState),
|
|
now: time.Now,
|
|
}
|
|
}
|
|
|
|
func (l *MemoryLoginLocks) key(k auth.LockKey) string {
|
|
return string(k.Kind) + "|" + k.EndpointID + "|" + k.PeerID + "|" + k.IP
|
|
}
|
|
|
|
func (l *MemoryLoginLocks) params(kind auth.LockKind) (window time.Duration, threshold int, lockFor time.Duration) {
|
|
switch kind {
|
|
case auth.LockLoginEndpoint, auth.LockTalkTarget:
|
|
return time.Hour, 50, time.Hour
|
|
default:
|
|
// LockLoginEndpointIP / LockTalkPair / LockAdminIP / LockRegisterIP
|
|
return 5 * time.Minute, 10, 5 * time.Minute
|
|
}
|
|
}
|
|
|
|
// Check 实现 auth.LoginLocks。
|
|
func (l *MemoryLoginLocks) Check(key auth.LockKey) (bool, time.Duration) {
|
|
l.mu.Lock()
|
|
defer l.mu.Unlock()
|
|
now := l.now()
|
|
st := l.entries[l.key(key)]
|
|
if st == nil {
|
|
return false, 0
|
|
}
|
|
if now.Before(st.lockedUntil) {
|
|
return true, st.lockedUntil.Sub(now)
|
|
}
|
|
return false, 0
|
|
}
|
|
|
|
// Fail 实现 auth.LoginLocks。
|
|
func (l *MemoryLoginLocks) Fail(key auth.LockKey) (bool, time.Duration) {
|
|
l.mu.Lock()
|
|
defer l.mu.Unlock()
|
|
now := l.now()
|
|
k := l.key(key)
|
|
st := l.entries[k]
|
|
if st == nil {
|
|
st = &lockState{}
|
|
l.entries[k] = st
|
|
}
|
|
if now.Before(st.lockedUntil) {
|
|
return true, st.lockedUntil.Sub(now)
|
|
}
|
|
window, threshold, lockFor := l.params(key.Kind)
|
|
cutoff := now.Add(-window)
|
|
kept := st.fails[:0]
|
|
for _, t := range st.fails {
|
|
if t.After(cutoff) {
|
|
kept = append(kept, t)
|
|
}
|
|
}
|
|
kept = append(kept, now)
|
|
st.fails = kept
|
|
if len(st.fails) >= threshold {
|
|
st.lockedUntil = now.Add(lockFor)
|
|
st.fails = nil
|
|
return true, lockFor
|
|
}
|
|
return false, 0
|
|
}
|
|
|
|
// ClearEndpoint 实现 auth.LoginLocks。
|
|
func (l *MemoryLoginLocks) ClearEndpoint(endpointID string) {
|
|
l.mu.Lock()
|
|
defer l.mu.Unlock()
|
|
for k := range l.entries {
|
|
// kind|endpoint|peer|ip
|
|
parts := splitLockKey(k)
|
|
if len(parts) >= 2 && parts[1] == endpointID {
|
|
delete(l.entries, k)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Clear 实现 auth.LoginLocks。
|
|
func (l *MemoryLoginLocks) Clear(key auth.LockKey) {
|
|
l.mu.Lock()
|
|
defer l.mu.Unlock()
|
|
delete(l.entries, l.key(key))
|
|
}
|
|
|
|
func splitLockKey(k string) []string {
|
|
out := make([]string, 0, 4)
|
|
start := 0
|
|
for i := 0; i < len(k); i++ {
|
|
if k[i] == '|' {
|
|
out = append(out, k[start:i])
|
|
start = i + 1
|
|
}
|
|
}
|
|
out = append(out, k[start:])
|
|
return out
|
|
}
|
|
|
|
var _ auth.LoginLocks = (*MemoryLoginLocks)(nil)
|