77 lines
1.6 KiB
Go
Executable File
77 lines
1.6 KiB
Go
Executable File
package auth
|
|
|
|
import (
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
const maxTrackedClients = 10000
|
|
|
|
type attemptWindow struct {
|
|
failures int
|
|
startedAt time.Time
|
|
}
|
|
|
|
type LoginLimiter struct {
|
|
mutex sync.Mutex
|
|
maxFailures int
|
|
window time.Duration
|
|
attempts map[string]*attemptWindow
|
|
}
|
|
|
|
func NewLoginLimiter(maxFailures int, window time.Duration) *LoginLimiter {
|
|
return &LoginLimiter{
|
|
maxFailures: maxFailures,
|
|
window: window,
|
|
attempts: make(map[string]*attemptWindow),
|
|
}
|
|
}
|
|
|
|
func (limiter *LoginLimiter) Allowed(client string) bool {
|
|
limiter.mutex.Lock()
|
|
defer limiter.mutex.Unlock()
|
|
attempt := limiter.current(client)
|
|
return attempt == nil || attempt.failures < limiter.maxFailures
|
|
}
|
|
|
|
func (limiter *LoginLimiter) RecordFailure(client string) {
|
|
limiter.mutex.Lock()
|
|
defer limiter.mutex.Unlock()
|
|
|
|
attempt := limiter.current(client)
|
|
if attempt == nil {
|
|
if len(limiter.attempts) >= maxTrackedClients {
|
|
limiter.removeExpired()
|
|
}
|
|
attempt = &attemptWindow{startedAt: time.Now()}
|
|
limiter.attempts[client] = attempt
|
|
}
|
|
attempt.failures++
|
|
}
|
|
|
|
func (limiter *LoginLimiter) Reset(client string) {
|
|
limiter.mutex.Lock()
|
|
defer limiter.mutex.Unlock()
|
|
delete(limiter.attempts, client)
|
|
}
|
|
|
|
func (limiter *LoginLimiter) current(client string) *attemptWindow {
|
|
attempt, found := limiter.attempts[client]
|
|
if !found {
|
|
return nil
|
|
}
|
|
if time.Since(attempt.startedAt) > limiter.window {
|
|
delete(limiter.attempts, client)
|
|
return nil
|
|
}
|
|
return attempt
|
|
}
|
|
|
|
func (limiter *LoginLimiter) removeExpired() {
|
|
for client, attempt := range limiter.attempts {
|
|
if time.Since(attempt.startedAt) > limiter.window {
|
|
delete(limiter.attempts, client)
|
|
}
|
|
}
|
|
}
|