Files
dyn/internal/middleware/ratelimit.go
Tanishq Dubey 2470f121e2 Initial commit: DDNS service with NIC V2 protocol support
Features:
- Token-based subdomain claiming
- NIC V2 (DynDNS2) protocol implementation
- Technitium DNS integration
- Rate limiting (10 req/min IP, 1 req/min token)
- Web UI for space claiming
- Docker/Docker Compose support
- Compatible with UniFi, pfSense, EdgeRouter

Module: git.dws.rip/DWS/dyn
2026-02-01 16:37:09 -05:00

113 lines
2.0 KiB
Go

package middleware
import (
"net/http"
"strings"
"sync"
"time"
"github.com/gin-gonic/gin"
)
type RateLimiter struct {
ipLimits map[string]*RateLimitEntry
tokenLimits map[string]*RateLimitEntry
mu sync.RWMutex
limitPerIP int
limitPerToken int
}
type RateLimitEntry struct {
Count int
ResetTime time.Time
}
func NewRateLimiter(perIP, perToken int) *RateLimiter {
return &RateLimiter{
ipLimits: make(map[string]*RateLimitEntry),
tokenLimits: make(map[string]*RateLimitEntry),
limitPerIP: perIP,
limitPerToken: perToken,
}
}
func (rl *RateLimiter) RateLimitByIP() gin.HandlerFunc {
return func(c *gin.Context) {
ip := c.ClientIP()
rl.mu.Lock()
entry, exists := rl.ipLimits[ip]
now := time.Now()
if !exists || now.After(entry.ResetTime) {
rl.ipLimits[ip] = &RateLimitEntry{
Count: 1,
ResetTime: now.Add(time.Minute),
}
rl.mu.Unlock()
c.Next()
return
}
if entry.Count >= rl.limitPerIP {
rl.mu.Unlock()
c.String(http.StatusTooManyRequests, "rate limit exceeded")
c.Abort()
return
}
entry.Count++
rl.mu.Unlock()
c.Next()
}
}
func (rl *RateLimiter) RateLimitByToken() gin.HandlerFunc {
return func(c *gin.Context) {
token := extractToken(c)
if token == "" {
c.Next()
return
}
rl.mu.Lock()
entry, exists := rl.tokenLimits[token]
now := time.Now()
if !exists || now.After(entry.ResetTime) {
rl.tokenLimits[token] = &RateLimitEntry{
Count: 1,
ResetTime: now.Add(time.Minute),
}
rl.mu.Unlock()
c.Next()
return
}
if entry.Count >= rl.limitPerToken {
rl.mu.Unlock()
c.String(http.StatusTooManyRequests, "rate limit exceeded")
c.Abort()
return
}
entry.Count++
rl.mu.Unlock()
c.Next()
}
}
func extractToken(c *gin.Context) string {
auth := c.GetHeader("Authorization")
if auth == "" {
return ""
}
parts := strings.SplitN(auth, " ", 2)
if len(parts) != 2 || parts[0] != "Basic" {
return ""
}
return parts[1]
}