85 lines
1.6 KiB
Go
85 lines
1.6 KiB
Go
package httpapi
|
|
|
|
import (
|
|
"net"
|
|
"net/http"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
resetCSRFTokenName = "scert_reset_csrf"
|
|
resetRateWindow = 15 * time.Minute
|
|
resetRateLimit = 5
|
|
resetMaxBodyBytes = 16 << 10
|
|
)
|
|
|
|
type resetRateLimiter struct {
|
|
mu sync.Mutex
|
|
failure map[string][]time.Time
|
|
}
|
|
|
|
func newResetRateLimiter() *resetRateLimiter {
|
|
return &resetRateLimiter{failure: make(map[string][]time.Time)}
|
|
}
|
|
|
|
func (l *resetRateLimiter) allowed(ip string, now time.Time) bool {
|
|
if l == nil {
|
|
return true
|
|
}
|
|
l.mu.Lock()
|
|
defer l.mu.Unlock()
|
|
l.pruneLocked(ip, now)
|
|
return len(l.failure[ip]) < resetRateLimit
|
|
}
|
|
|
|
func (l *resetRateLimiter) failed(ip string, now time.Time) {
|
|
if l == nil {
|
|
return
|
|
}
|
|
l.mu.Lock()
|
|
defer l.mu.Unlock()
|
|
l.pruneLocked(ip, now)
|
|
l.failure[ip] = append(l.failure[ip], now)
|
|
}
|
|
|
|
func (l *resetRateLimiter) clear(ip string) {
|
|
if l == nil {
|
|
return
|
|
}
|
|
l.mu.Lock()
|
|
delete(l.failure, ip)
|
|
l.mu.Unlock()
|
|
}
|
|
|
|
func (l *resetRateLimiter) pruneLocked(ip string, now time.Time) {
|
|
cutoff := now.Add(-resetRateWindow)
|
|
items := l.failure[ip]
|
|
keep := items[:0]
|
|
for _, item := range items {
|
|
if item.After(cutoff) {
|
|
keep = append(keep, item)
|
|
}
|
|
}
|
|
if len(keep) == 0 {
|
|
delete(l.failure, ip)
|
|
return
|
|
}
|
|
l.failure[ip] = keep
|
|
}
|
|
|
|
func resetClientIP(request *http.Request) string {
|
|
if request == nil {
|
|
return "unknown"
|
|
}
|
|
host, _, err := net.SplitHostPort(strings.TrimSpace(request.RemoteAddr))
|
|
if err == nil && host != "" {
|
|
return host
|
|
}
|
|
if value := strings.TrimSpace(request.RemoteAddr); value != "" {
|
|
return value
|
|
}
|
|
return "unknown"
|
|
}
|