rate limit

This commit is contained in:
Simon Martens
2025-05-29 02:54:46 +02:00
parent defc13ad20
commit e0bb939764
3 changed files with 78 additions and 2 deletions

View File

@@ -8,6 +8,7 @@ import (
"github.com/Theodor-Springmann-Stiftung/musenalm/app"
"github.com/Theodor-Springmann-Stiftung/musenalm/dbmodels"
"github.com/Theodor-Springmann-Stiftung/musenalm/helpers/security"
"github.com/Theodor-Springmann-Stiftung/musenalm/middleware"
"github.com/Theodor-Springmann-Stiftung/musenalm/pagemodels"
"github.com/Theodor-Springmann-Stiftung/musenalm/templating"
"github.com/pocketbase/pocketbase/core"
@@ -48,7 +49,8 @@ type LoginPage struct {
func (p *LoginPage) Setup(router *router.Router[*core.RequestEvent], app core.App, engine *templating.Engine) error {
router.GET(URL_LOGIN, p.GET(engine, app))
router.POST(URL_LOGIN, p.POST(engine, app))
r := router.POST(URL_LOGIN, p.POST(engine, app))
r.BindFunc(middleware.RateLimiter(30, time.Minute*2, time.Hour*6))
return nil
}

View File

@@ -0,0 +1,74 @@
package middleware
import (
"net/http"
"sync"
"time"
"github.com/pocketbase/pocketbase/core"
)
type clientStats struct {
count int
windowStart time.Time
}
func RateLimiter(limit int, windowDuration time.Duration, cleanupInterval time.Duration) func(*core.RequestEvent) error {
clientRequests := make(map[string]*clientStats)
var mu sync.Mutex
startPeriodicCleanup(clientRequests, &mu, windowDuration, cleanupInterval)
return func(e *core.RequestEvent) error {
if e == nil {
return nil
}
ipStr := e.RealIP()
mu.Lock()
defer mu.Unlock()
stats, exists := clientRequests[ipStr]
now := time.Now()
if !exists || now.After(stats.windowStart.Add(windowDuration)) {
clientRequests[ipStr] = &clientStats{
count: 1,
windowStart: now,
}
return e.Next()
}
if stats.count >= limit {
return e.Error(http.StatusTooManyRequests, "Too Many Requests", nil)
}
stats.count++
return e.Next()
}
}
func performCleanupCycle(clientRequests map[string]*clientStats, mu *sync.Mutex, windowDuration time.Duration) {
mu.Lock()
defer mu.Unlock()
now := time.Now()
maxEntryAge := 2 * windowDuration
for ip, stats := range clientRequests {
if now.After(stats.windowStart.Add(maxEntryAge)) {
delete(clientRequests, ip)
}
}
}
func startPeriodicCleanup(clientRequests map[string]*clientStats, mu *sync.Mutex, windowDuration time.Duration, cleanupInterval time.Duration) {
go func() {
ticker := time.NewTicker(cleanupInterval)
defer ticker.Stop() // Stop the ticker when this goroutine exits (though it runs indefinitely here)
for range ticker.C {
performCleanupCycle(clientRequests, mu, windowDuration)
}
}()
}

File diff suppressed because one or more lines are too long