Scert/backend/internal/httpapi/reset_security.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"
}