61 lines
1.2 KiB
Go
61 lines
1.2 KiB
Go
package httpapi
|
|
|
|
import (
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
type loginAttempt struct {
|
|
failures int
|
|
windowStart time.Time
|
|
blockedTill time.Time
|
|
}
|
|
|
|
type loginLimiter struct {
|
|
mu sync.Mutex
|
|
attempts map[string]loginAttempt
|
|
now func() time.Time
|
|
}
|
|
|
|
func newLoginLimiter(now func() time.Time) *loginLimiter {
|
|
return &loginLimiter{attempts: make(map[string]loginAttempt), now: now}
|
|
}
|
|
|
|
func (l *loginLimiter) allow(key string) (bool, time.Duration) {
|
|
l.mu.Lock()
|
|
defer l.mu.Unlock()
|
|
now := l.now()
|
|
attempt, ok := l.attempts[key]
|
|
if !ok {
|
|
return true, 0
|
|
}
|
|
if now.Before(attempt.blockedTill) {
|
|
return false, attempt.blockedTill.Sub(now)
|
|
}
|
|
if now.Sub(attempt.windowStart) > 10*time.Minute {
|
|
delete(l.attempts, key)
|
|
}
|
|
return true, 0
|
|
}
|
|
|
|
func (l *loginLimiter) failure(key string) {
|
|
l.mu.Lock()
|
|
defer l.mu.Unlock()
|
|
now := l.now()
|
|
attempt := l.attempts[key]
|
|
if attempt.windowStart.IsZero() || now.Sub(attempt.windowStart) > 10*time.Minute {
|
|
attempt = loginAttempt{windowStart: now}
|
|
}
|
|
attempt.failures++
|
|
if attempt.failures >= 5 {
|
|
attempt.blockedTill = now.Add(10 * time.Minute)
|
|
}
|
|
l.attempts[key] = attempt
|
|
}
|
|
|
|
func (l *loginLimiter) success(key string) {
|
|
l.mu.Lock()
|
|
delete(l.attempts, key)
|
|
l.mu.Unlock()
|
|
}
|