- Kompletna Go implementacija licencnog servera (19 Go fajlova) - Klijentski API: activate, deactivate, validate - Admin API: CRUD licence, stats, audit log - Admin dashboard: htmx + Go templates - RSA-2048 potpisivanje licencnih podataka - Rate limiting i API key autentifikacija - MySQL migracije i seed podaci (ESIR, ARV, LIGHT_TICKET) - Unit testovi: keygen, crypto, model, middleware (24 testa) - Dokumentacija: SPEC.md, ARCHITECTURE.md, SETUP.md, API.md, TESTING.md, README.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
65 lines
1.3 KiB
Go
65 lines
1.3 KiB
Go
package middleware
|
|
|
|
import (
|
|
"net/http"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
type rateLimiter struct {
|
|
mu sync.Mutex
|
|
visitors map[string][]time.Time
|
|
limit int
|
|
window time.Duration
|
|
}
|
|
|
|
func newRateLimiter(limit int) *rateLimiter {
|
|
return &rateLimiter{
|
|
visitors: make(map[string][]time.Time),
|
|
limit: limit,
|
|
window: time.Minute,
|
|
}
|
|
}
|
|
|
|
func (rl *rateLimiter) allow(ip string) bool {
|
|
rl.mu.Lock()
|
|
defer rl.mu.Unlock()
|
|
|
|
now := time.Now()
|
|
cutoff := now.Add(-rl.window)
|
|
|
|
var valid []time.Time
|
|
for _, t := range rl.visitors[ip] {
|
|
if t.After(cutoff) {
|
|
valid = append(valid, t)
|
|
}
|
|
}
|
|
|
|
if len(valid) >= rl.limit {
|
|
rl.visitors[ip] = valid
|
|
return false
|
|
}
|
|
|
|
rl.visitors[ip] = append(valid, now)
|
|
return true
|
|
}
|
|
|
|
func RateLimit(limit int) func(http.Handler) http.Handler {
|
|
rl := newRateLimiter(limit)
|
|
return func(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
ip := r.RemoteAddr
|
|
if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
|
|
ip = xff
|
|
}
|
|
if !rl.allow(ip) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusTooManyRequests)
|
|
w.Write([]byte(`{"error":{"code":"RATE_LIMITED","message":"Previse zahteva, pokusajte ponovo za minut"}}`))
|
|
return
|
|
}
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
}
|