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() }