This repository has been archived on 2026-04-03. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
core/server/middleware/ratelimit.go
Jeffrey Smith 680ec3b897
All checks were successful
CI/CD / detect-changes (push) Successful in 3s
CI/CD / test-frontend (push) Successful in 5s
CI/CD / test-go-pg (push) Successful in 2m34s
CI/CD / test-sqlite (push) Successful in 2m46s
CI/CD / build-and-deploy (push) Successful in 1m55s
Feat rebrand armature (#43)
Co-authored-by: Jeffrey Smith <jasafpro@gmail.com>
Co-committed-by: Jeffrey Smith <jasafpro@gmail.com>
2026-03-31 23:25:37 +00:00

53 lines
1.3 KiB
Go

package middleware
import (
"log"
"net/http"
"github.com/gin-gonic/gin"
"armature/store"
)
// RateLimiter implements per-IP rate limiting backed by a shared store.
//
// Fail-open: if the store is unavailable (PG down), requests are allowed.
// Auth endpoints have their own protections (bcrypt, lockout); blocking
// legitimate logins due to a rate-limit DB failure is worse than a brief burst.
type RateLimiter struct {
store store.RateLimitStore
rate float64
burst int
}
// NewRateLimiter creates a rate limiter backed by the given store.
// rate is requests per second, burst is max requests per window.
func NewRateLimiter(s store.RateLimitStore, rate float64, burst int) *RateLimiter {
return &RateLimiter{store: s, rate: rate, burst: burst}
}
// Limit returns a Gin middleware that enforces the rate limit.
func (rl *RateLimiter) Limit() gin.HandlerFunc {
return func(c *gin.Context) {
key := "auth:" + c.ClientIP()
allowed, err := rl.store.Allow(c.Request.Context(), key, rl.rate, rl.burst)
if err != nil {
// Fail open — don't block auth on rate limit DB failure.
log.Printf("[ratelimit] store error (allowing request): %v", err)
c.Next()
return
}
if !allowed {
c.Header("Retry-After", "1")
c.AbortWithStatusJSON(http.StatusTooManyRequests, gin.H{
"error": "rate limit exceeded",
})
return
}
c.Next()
}
}