feat: 实现管理鉴权、API 令牌与操作日志
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
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)
|
||||
Reference in New Issue
Block a user