OpenAI-compatible AI gateway with Vue admin UI, multi-provider egress, ingress key governance, monitoring, and security controls. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
package loginlimit
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Limiter tracks failed admin login attempts per client IP.
|
||||
type Limiter struct {
|
||||
mu sync.Mutex
|
||||
attempts map[string]*record
|
||||
maxAttempts int
|
||||
lockout time.Duration
|
||||
}
|
||||
|
||||
type record struct {
|
||||
failures int
|
||||
lockedUntil time.Time
|
||||
}
|
||||
|
||||
func New(maxAttempts int, lockout time.Duration) *Limiter {
|
||||
if maxAttempts <= 0 {
|
||||
maxAttempts = 5
|
||||
}
|
||||
if lockout <= 0 {
|
||||
lockout = 15 * time.Minute
|
||||
}
|
||||
return &Limiter{
|
||||
attempts: make(map[string]*record),
|
||||
maxAttempts: maxAttempts,
|
||||
lockout: lockout,
|
||||
}
|
||||
}
|
||||
|
||||
// Check reports whether the IP is locked and how long to wait.
|
||||
func (l *Limiter) Check(ip string) (locked bool, retryAfter time.Duration) {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
r := l.attempts[ip]
|
||||
if r == nil {
|
||||
return false, 0
|
||||
}
|
||||
now := time.Now()
|
||||
if now.Before(r.lockedUntil) {
|
||||
return true, r.lockedUntil.Sub(now)
|
||||
}
|
||||
if r.failures >= l.maxAttempts {
|
||||
delete(l.attempts, ip)
|
||||
}
|
||||
return false, 0
|
||||
}
|
||||
|
||||
// RecordFailure increments the failure count and locks the IP when the limit is reached.
|
||||
func (l *Limiter) RecordFailure(ip string) {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
now := time.Now()
|
||||
r := l.attempts[ip]
|
||||
if r == nil {
|
||||
r = &record{}
|
||||
l.attempts[ip] = r
|
||||
}
|
||||
if now.Before(r.lockedUntil) {
|
||||
return
|
||||
}
|
||||
if r.failures >= l.maxAttempts && !r.lockedUntil.IsZero() && now.After(r.lockedUntil) {
|
||||
r.failures = 0
|
||||
r.lockedUntil = time.Time{}
|
||||
}
|
||||
r.failures++
|
||||
if r.failures >= l.maxAttempts {
|
||||
r.lockedUntil = now.Add(l.lockout)
|
||||
}
|
||||
}
|
||||
|
||||
// Configure updates rate limit parameters at runtime.
|
||||
func (l *Limiter) Configure(maxAttempts int, lockout time.Duration) {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
if maxAttempts > 0 {
|
||||
l.maxAttempts = maxAttempts
|
||||
}
|
||||
if lockout > 0 {
|
||||
l.lockout = lockout
|
||||
}
|
||||
}
|
||||
|
||||
// Reset clears failure state after a successful login.
|
||||
func (l *Limiter) Reset(ip string) {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
delete(l.attempts, ip)
|
||||
}
|
||||
Reference in New Issue
Block a user