// Package httpx : middlewares transverses (sécurité, CORS, rate-limit). package httpx import ( "net/http" "sync" "github.com/gin-gonic/gin" "golang.org/x/time/rate" ) // SecurityHeaders ajoute les en-têtes de sécurité recommandés (OWASP). func SecurityHeaders() gin.HandlerFunc { return func(c *gin.Context) { h := c.Writer.Header() h.Set("X-Content-Type-Options", "nosniff") h.Set("X-Frame-Options", "DENY") h.Set("Referrer-Policy", "no-referrer") h.Set("Content-Security-Policy", "default-src 'none'; frame-ancestors 'none'") h.Set("Strict-Transport-Security", "max-age=63072000; includeSubDomains") c.Next() } } // CORS restreint les origines à une allowlist explicite. func CORS(allowed []string) gin.HandlerFunc { set := make(map[string]struct{}, len(allowed)) for _, o := range allowed { set[o] = struct{}{} } return func(c *gin.Context) { origin := c.GetHeader("Origin") if _, ok := set[origin]; ok { h := c.Writer.Header() h.Set("Access-Control-Allow-Origin", origin) h.Set("Vary", "Origin") h.Set("Access-Control-Allow-Methods", "GET,POST,PATCH,DELETE,OPTIONS") h.Set("Access-Control-Allow-Headers", "Authorization,Content-Type") } if c.Request.Method == http.MethodOptions { c.AbortWithStatus(http.StatusNoContent) return } c.Next() } } // ipLimiter : rate-limiter par IP (token bucket). type ipLimiter struct { mu sync.Mutex buck map[string]*rate.Limiter r rate.Limit burst int } func newIPLimiter(r rate.Limit, burst int) *ipLimiter { return &ipLimiter{buck: make(map[string]*rate.Limiter), r: r, burst: burst} } func (l *ipLimiter) get(ip string) *rate.Limiter { l.mu.Lock() defer l.mu.Unlock() lim, ok := l.buck[ip] if !ok { lim = rate.NewLimiter(l.r, l.burst) l.buck[ip] = lim } return lim } // RateLimit limite chaque IP à r req/s avec un burst donné. func RateLimit(perSecond float64, burst int) gin.HandlerFunc { limiter := newIPLimiter(rate.Limit(perSecond), burst) return func(c *gin.Context) { if !limiter.get(c.ClientIP()).Allow() { c.AbortWithStatusJSON(http.StatusTooManyRequests, gin.H{"error": "trop de requêtes"}) return } c.Next() } }