- Rename Go module switchboard-core → armature (155+ files) - Rename Docker image → gobha/armature - Rename K8s resources, secrets, deployments - Rename Prometheus metrics switchboard_* → armature_* - Rename env vars SWITCHBOARD_ADMIN_* → ARMATURE_ADMIN_* - Rename DB names switchboard_core* → armature* - Update all frontend branding, notification templates, docs - Update CI scripts, e2e tests, Keycloak realm, nginx conf - Rename scripts/switchboard-ca.sh → scripts/armature-ca.sh - Rename k8s/switchboard.yaml → k8s/armature.yaml - Rename chart alerting/dashboard files - Fix: DockerHub push uses env: binding for secret injection - Helm chart updated (name, labels, template functions, dashboard, alerting) - Replace favicon/icon assets with Armature brand No functional changes. Pure mechanical rename + CI fix. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
53 lines
1.3 KiB
Go
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()
|
|
}
|
|
}
|