UserRateLimiter had been created in initMiddlewares() + stored on
config.UserRateLimiter but never mounted — dead wiring. Per-user rate
limiting was silently not running anywhere.
Applying it as a separate `v1.Use(...)` would fire *before* the JWT
auth middleware sets `user_id`, so the limiter would always skip. The
alternative (add it after every `RequireAuth()` in ~15 route files)
bloats every routes_*.go and invites forgetting.
Solution: centralise it on AuthMiddleware. After a successful
`authenticate()` in `RequireAuth`, invoke the limiter's handler. When
the limiter is nil (tests, early boot), it's a no-op.
Changes:
- internal/middleware/auth.go
* new field AuthMiddleware.userRateLimiter *UserRateLimiter
* new method AuthMiddleware.SetUserRateLimiter(url)
* RequireAuth() flow: authenticate → presence → user rate limit
→ c.Next(). Abort surfaces as early-return without c.Next().
- internal/config/middlewares_init.go
* call c.AuthMiddleware.SetUserRateLimiter(c.UserRateLimiter)
right after AuthMiddleware construction.
Behavior:
- Authenticated requests: per-user limit enforced via Redis, with
X-RateLimit-Limit / Remaining / Reset headers, 429 + retry-after
on overflow. Defaults: 1000 req/min, burst 100 (env-tunable via
USER_RATE_LIMIT_PER_MINUTE / USER_RATE_LIMIT_BURST).
- Unauthenticated requests: RequireAuth already rejected them → the
limiter never runs, no behavior change there.
Tests: `go test ./internal/middleware/ -short` green (33s).
`go build ./...` + `go vet ./internal/middleware/` clean.
Refs: AUDIT_REPORT.md §4.3 "UserRateLimiter configuré non wiré"
+ §9 priority #11.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
96 lines
3.6 KiB
Go
96 lines
3.6 KiB
Go
package config
|
|
|
|
import (
|
|
"time"
|
|
|
|
"veza-backend-api/internal/middleware"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// InitMiddlewaresForTest initializes middlewares for integration/E2E tests.
|
|
// Exported for use by internal/integration and tests packages.
|
|
func (c *Config) InitMiddlewaresForTest() error {
|
|
return c.initMiddlewares()
|
|
}
|
|
|
|
// initMiddlewares initialise tous les middlewares
|
|
func (c *Config) initMiddlewares() error {
|
|
// Rate limiter global (TASK-SEC-003: 100 req/h non-auth, 1000 req/h auth in prod)
|
|
ipLimit := getDefaultRateLimitIPPerHour(c.Env)
|
|
userLimit := getDefaultRateLimitUserPerHour(c.Env)
|
|
windowSeconds := 3600 // 1 hour
|
|
|
|
rateLimiterConfig := &middleware.RateLimiterConfig{
|
|
IPLimit: ipLimit,
|
|
UserLimit: userLimit,
|
|
WindowSeconds: windowSeconds,
|
|
RedisClient: c.RedisClient,
|
|
KeyPrefix: "veza:rate_limit",
|
|
}
|
|
c.RateLimiter = middleware.NewRateLimiter(rateLimiterConfig)
|
|
|
|
// Simple rate limiter (T0015) - sans dépendance Redis
|
|
window := time.Duration(c.RateLimitWindow) * time.Second
|
|
c.SimpleRateLimiter = middleware.NewSimpleRateLimiter(c.RateLimitLimit, window)
|
|
|
|
// Rate limiter par endpoint
|
|
endpointLimiterConfig := &middleware.EndpointLimiterConfig{
|
|
RedisClient: c.RedisClient,
|
|
KeyPrefix: "veza:endpoint_limit",
|
|
}
|
|
endpointLimits := middleware.DefaultEndpointLimits()
|
|
// Override defaults with config (PR-3)
|
|
endpointLimits.LoginAttempts = c.AuthRateLimitLoginAttempts
|
|
endpointLimits.LoginWindow = time.Duration(c.AuthRateLimitLoginWindow) * time.Minute
|
|
// A04: Limites register assouplies en dev (20/heure au lieu de 3/heure)
|
|
endpointLimits.RegisterAttempts = getDefaultRegisterAttempts(c.Env)
|
|
endpointLimits.RegisterWindow = time.Hour
|
|
|
|
c.EndpointLimiter = middleware.NewEndpointLimiter(endpointLimiterConfig, endpointLimits)
|
|
|
|
// BE-SVC-002: Initialize per-user rate limiter
|
|
userRateLimiterConfig := &middleware.UserRateLimiterConfig{
|
|
RequestsPerMinute: getEnvAsInt("USER_RATE_LIMIT_PER_MINUTE", 1000), // Default: 1000 requests per minute per user
|
|
Burst: getEnvAsInt("USER_RATE_LIMIT_BURST", 100), // Default: 100 burst
|
|
Window: time.Minute,
|
|
RedisClient: c.RedisClient,
|
|
KeyPrefix: "user_rate_limit",
|
|
Logger: c.Logger,
|
|
}
|
|
c.UserRateLimiter = middleware.NewUserRateLimiter(userRateLimiterConfig)
|
|
|
|
// Middleware d'authentification (supports JWT and X-API-Key for developer keys)
|
|
c.AuthMiddleware = middleware.NewAuthMiddleware(
|
|
c.SessionService,
|
|
c.AuditService,
|
|
c.PermissionService,
|
|
c.JWTService,
|
|
c.UserService,
|
|
c.APIKeyService,
|
|
c.TokenBlacklist, // VEZA-SEC-006: nil if Redis unavailable (implements TokenBlacklistChecker)
|
|
c.Logger,
|
|
)
|
|
if c.PresenceService != nil {
|
|
c.AuthMiddleware.SetPresenceService(c.PresenceService)
|
|
}
|
|
|
|
// BE-SVC-002: Wire per-user rate limiter into the auth middleware so it
|
|
// fires automatically after every successful RequireAuth on any route.
|
|
// Previously UserRateLimiter was created but never mounted (dead wiring).
|
|
if c.UserRateLimiter != nil {
|
|
c.AuthMiddleware.SetUserRateLimiter(c.UserRateLimiter)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// SetupMiddleware configure les middlewares globaux
|
|
// DÉPRÉCIÉ : Cette méthode est conservée pour compatibilité mais ne fait plus rien
|
|
// Les middlewares globaux sont maintenant configurés dans internal/api/router.go via APIRouter.Setup()
|
|
// NOTE: CORS could use c.CORSOrigins from config in api/router.go
|
|
func (c *Config) SetupMiddleware(router *gin.Engine) {
|
|
// No-op : Les middlewares sont configurés dans api/router.go
|
|
// Cette méthode existe uniquement pour compatibilité avec cmd/main.go (legacy)
|
|
// qui sera désactivé dans le Chantier 1 - Étape 2
|
|
}
|