Services: Admin-Compliance, Backend-Compliance, AI-Compliance-SDK, Consent-SDK, Developer-Portal, PCA-Platform, DSMS Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
120 lines
2.7 KiB
Go
120 lines
2.7 KiB
Go
// Package middleware provides HTTP middleware for the API Gateway
|
|
package middleware
|
|
|
|
import (
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/google/uuid"
|
|
"go.uber.org/zap"
|
|
"golang.org/x/time/rate"
|
|
)
|
|
|
|
// Logger middleware logs requests
|
|
func Logger(logger *zap.Logger) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
start := time.Now()
|
|
path := c.Request.URL.Path
|
|
raw := c.Request.URL.RawQuery
|
|
|
|
// Process request
|
|
c.Next()
|
|
|
|
// Log after request
|
|
latency := time.Since(start)
|
|
clientIP := c.ClientIP()
|
|
method := c.Request.Method
|
|
statusCode := c.Writer.Status()
|
|
|
|
if raw != "" {
|
|
path = path + "?" + raw
|
|
}
|
|
|
|
logger.Info("request",
|
|
zap.String("method", method),
|
|
zap.String("path", path),
|
|
zap.Int("status", statusCode),
|
|
zap.String("ip", clientIP),
|
|
zap.Duration("latency", latency),
|
|
zap.String("request_id", c.GetString("request_id")),
|
|
)
|
|
}
|
|
}
|
|
|
|
// CORS middleware handles Cross-Origin Resource Sharing
|
|
func CORS() gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
origin := c.GetHeader("Origin")
|
|
if origin == "" {
|
|
origin = "*"
|
|
}
|
|
|
|
c.Header("Access-Control-Allow-Origin", origin)
|
|
c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS")
|
|
c.Header("Access-Control-Allow-Headers", "Origin, Content-Type, Accept, Authorization, X-Tenant-ID, X-Request-ID")
|
|
c.Header("Access-Control-Allow-Credentials", "true")
|
|
c.Header("Access-Control-Max-Age", "86400")
|
|
|
|
if c.Request.Method == "OPTIONS" {
|
|
c.AbortWithStatus(http.StatusNoContent)
|
|
return
|
|
}
|
|
|
|
c.Next()
|
|
}
|
|
}
|
|
|
|
// RequestID middleware adds a unique request ID to each request
|
|
func RequestID() gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
requestID := c.GetHeader("X-Request-ID")
|
|
if requestID == "" {
|
|
requestID = uuid.New().String()
|
|
}
|
|
|
|
c.Set("request_id", requestID)
|
|
c.Header("X-Request-ID", requestID)
|
|
|
|
c.Next()
|
|
}
|
|
}
|
|
|
|
// RateLimitConfig holds rate limiting configuration
|
|
type RateLimitConfig struct {
|
|
RequestsPerSecond int
|
|
Burst int
|
|
}
|
|
|
|
// RateLimiter middleware limits request rate per client
|
|
func RateLimiter(config RateLimitConfig) gin.HandlerFunc {
|
|
// In production, use a distributed rate limiter with Redis
|
|
// This is a simple in-memory rate limiter per IP
|
|
limiters := make(map[string]*rate.Limiter)
|
|
|
|
return func(c *gin.Context) {
|
|
clientIP := c.ClientIP()
|
|
|
|
limiter, exists := limiters[clientIP]
|
|
if !exists {
|
|
limiter = rate.NewLimiter(rate.Limit(config.RequestsPerSecond), config.Burst)
|
|
limiters[clientIP] = limiter
|
|
}
|
|
|
|
if !limiter.Allow() {
|
|
c.AbortWithStatusJSON(http.StatusTooManyRequests, gin.H{
|
|
"error": "Rate limit exceeded",
|
|
"retry_after": "1s",
|
|
})
|
|
return
|
|
}
|
|
|
|
c.Next()
|
|
}
|
|
}
|
|
|
|
// Recovery middleware recovers from panics
|
|
func Recovery() gin.HandlerFunc {
|
|
return gin.Recovery()
|
|
}
|