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