Initial commit
This commit is contained in:
60
server/internal/httpapi/limiter.go
Normal file
60
server/internal/httpapi/limiter.go
Normal file
@@ -0,0 +1,60 @@
|
||||
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()
|
||||
}
|
||||
Reference in New Issue
Block a user