69 lines
1.2 KiB
Go
69 lines
1.2 KiB
Go
package auth
|
|
|
|
import (
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
type LoginRateLimiter struct {
|
|
mu sync.Mutex
|
|
attempts map[string]loginAttempt
|
|
limit int
|
|
window time.Duration
|
|
}
|
|
|
|
type loginAttempt struct {
|
|
Count int
|
|
ExpiresAt time.Time
|
|
}
|
|
|
|
func NewLoginRateLimiter(limit int, window time.Duration) *LoginRateLimiter {
|
|
return &LoginRateLimiter{
|
|
attempts: map[string]loginAttempt{},
|
|
limit: limit,
|
|
window: window,
|
|
}
|
|
}
|
|
|
|
func (l *LoginRateLimiter) IsBlocked(key string) bool {
|
|
l.mu.Lock()
|
|
defer l.mu.Unlock()
|
|
|
|
attempt, ok := l.attempts[key]
|
|
if !ok || time.Now().After(attempt.ExpiresAt) {
|
|
delete(l.attempts, key)
|
|
return false
|
|
}
|
|
|
|
return attempt.Count >= l.limit
|
|
}
|
|
|
|
func (l *LoginRateLimiter) RecordFailure(key string) {
|
|
l.mu.Lock()
|
|
defer l.mu.Unlock()
|
|
|
|
now := time.Now()
|
|
attempt, ok := l.attempts[key]
|
|
if !ok || now.After(attempt.ExpiresAt) {
|
|
l.attempts[key] = loginAttempt{
|
|
Count: 1,
|
|
ExpiresAt: now.Add(l.window),
|
|
}
|
|
return
|
|
}
|
|
|
|
attempt.Count++
|
|
l.attempts[key] = attempt
|
|
}
|
|
|
|
func (l *LoginRateLimiter) Clear(key string) {
|
|
l.mu.Lock()
|
|
defer l.mu.Unlock()
|
|
delete(l.attempts, key)
|
|
}
|
|
|
|
func LoginRateLimitKey(ip string, email string) string {
|
|
return strings.TrimSpace(ip) + ":" + strings.ToLower(strings.TrimSpace(email))
|
|
}
|