| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899 |
- package controller
- import (
- "strings"
- "sync"
- "time"
- )
- const (
- loginLimitMaxFailures = 5
- loginLimitWindow = 5 * time.Minute
- loginLimitCooldown = 15 * time.Minute
- )
- var defaultLoginLimiter = newLoginLimiter(loginLimitMaxFailures, loginLimitWindow, loginLimitCooldown)
- type loginLimiter struct {
- mu sync.Mutex
- now func() time.Time
- maxFailures int
- window time.Duration
- cooldown time.Duration
- attempts map[string]*loginLimitRecord
- }
- type loginLimitRecord struct {
- failures []time.Time
- blockedUntil time.Time
- }
- func newLoginLimiter(maxFailures int, window, cooldown time.Duration) *loginLimiter {
- return &loginLimiter{
- now: time.Now,
- maxFailures: maxFailures,
- window: window,
- cooldown: cooldown,
- attempts: make(map[string]*loginLimitRecord),
- }
- }
- func (l *loginLimiter) allow(ip, username string) (time.Time, bool) {
- l.mu.Lock()
- defer l.mu.Unlock()
- key := loginLimitKey(ip, username)
- record := l.attempts[key]
- if record == nil {
- return time.Time{}, true
- }
- now := l.now()
- if now.Before(record.blockedUntil) {
- return record.blockedUntil, false
- }
- record.blockedUntil = time.Time{}
- record.failures = pruneLoginFailures(record.failures, now.Add(-l.window))
- if len(record.failures) == 0 {
- delete(l.attempts, key)
- }
- return time.Time{}, true
- }
- func (l *loginLimiter) registerFailure(ip, username string) (time.Time, bool) {
- l.mu.Lock()
- defer l.mu.Unlock()
- key := loginLimitKey(ip, username)
- record := l.attempts[key]
- if record == nil {
- record = &loginLimitRecord{}
- l.attempts[key] = record
- }
- now := l.now()
- record.failures = pruneLoginFailures(record.failures, now.Add(-l.window))
- record.failures = append(record.failures, now)
- if len(record.failures) >= l.maxFailures {
- record.failures = nil
- record.blockedUntil = now.Add(l.cooldown)
- return record.blockedUntil, true
- }
- return time.Time{}, false
- }
- func (l *loginLimiter) registerSuccess(ip, username string) {
- l.mu.Lock()
- defer l.mu.Unlock()
- delete(l.attempts, loginLimitKey(ip, username))
- }
- func loginLimitKey(ip, username string) string {
- return strings.TrimSpace(ip) + "\x00" + strings.ToLower(strings.TrimSpace(username))
- }
- func pruneLoginFailures(failures []time.Time, cutoff time.Time) []time.Time {
- keepFrom := 0
- for keepFrom < len(failures) && failures[keepFrom].Before(cutoff) {
- keepFrom++
- }
- return failures[keepFrom:]
- }
|