| |
| |
| |
| package middleware |
|
|
| import ( |
| "net/http" |
| "time" |
|
|
| "github.com/gin-gonic/gin" |
| "github.com/router-for-me/CLIProxyAPI/v6/internal/domain/ports" |
| ) |
|
|
| |
| |
| |
| type RateLimitMiddleware struct { |
| service ports.RateLimitService |
| } |
|
|
| |
| func NewRateLimitMiddleware(service ports.RateLimitService) *RateLimitMiddleware { |
| return &RateLimitMiddleware{ |
| service: service, |
| } |
| } |
|
|
| |
| |
| func (m *RateLimitMiddleware) Middleware() gin.HandlerFunc { |
| return func(c *gin.Context) { |
| if m.service == nil { |
| c.Next() |
| return |
| } |
|
|
| clientIP := c.ClientIP() |
| ctx := c.Request.Context() |
|
|
| |
| blocked, blockedUntil, err := m.service.IsBlocked(ctx, clientIP) |
| if err != nil { |
| c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{ |
| "error": "rate limit check failed", |
| }) |
| return |
| } |
| if blocked { |
| remaining := time.Until(blockedUntil) |
| if remaining < 0 { |
| remaining = 0 |
| } |
| c.AbortWithStatusJSON(http.StatusForbidden, gin.H{ |
| "error": "IP banned due to too many failed attempts", |
| "retry_after": remaining.String(), |
| }) |
| return |
| } |
|
|
| |
| allowed, err := m.service.Allow(ctx, clientIP) |
| if err != nil { |
| c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{ |
| "error": "rate limit check failed", |
| }) |
| return |
| } |
| if !allowed { |
| c.AbortWithStatusJSON(http.StatusTooManyRequests, gin.H{ |
| "error": "rate limit exceeded", |
| }) |
| return |
| } |
|
|
| c.Next() |
| } |
| } |
|
|
| |
| |
| type AuthMiddleware struct { |
| rateLimitService ports.RateLimitService |
| getSecretHash func() string |
| getEnvSecret func() string |
| allowRemote func() bool |
| } |
|
|
| |
| func NewAuthMiddleware( |
| service ports.RateLimitService, |
| getSecretHash func() string, |
| getEnvSecret func() string, |
| allowRemote func() bool, |
| ) *AuthMiddleware { |
| return &AuthMiddleware{ |
| rateLimitService: service, |
| getSecretHash: getSecretHash, |
| getEnvSecret: getEnvSecret, |
| allowRemote: allowRemote, |
| } |
| } |
|
|
| |
| func (m *AuthMiddleware) OnAuthFailure(c *gin.Context) { |
| if m.rateLimitService == nil { |
| return |
| } |
|
|
| clientIP := c.ClientIP() |
| ctx := c.Request.Context() |
|
|
| m.rateLimitService.RecordAttempt(ctx, clientIP, false) |
| } |
|
|
| |
| func (m *AuthMiddleware) OnAuthSuccess(c *gin.Context) { |
| if m.rateLimitService == nil { |
| return |
| } |
|
|
| clientIP := c.ClientIP() |
| ctx := c.Request.Context() |
|
|
| m.rateLimitService.RecordAttempt(ctx, clientIP, true) |
| } |
|
|
| |
| func (m *AuthMiddleware) GetRetryAfter(c *gin.Context) time.Duration { |
| if m.rateLimitService == nil { |
| return 0 |
| } |
|
|
| clientIP := c.ClientIP() |
| ctx := c.Request.Context() |
|
|
| blocked, blockedUntil, err := m.rateLimitService.IsBlocked(ctx, clientIP) |
| if err != nil { |
| return 0 |
| } |
| if blocked { |
| remaining := time.Until(blockedUntil) |
| if remaining > 0 { |
| return remaining |
| } |
| } |
|
|
| return 0 |
| } |
|
|