Files
apocapoc-api/internal/infrastructure/http/rate_limit_middleware.go
T
david 7575853355 Add rate limiting for password reset and improve SMTP tests
- Add RateLimitByEmail middleware (3 attempts/hour per email)
- Apply rate limiting to /api/v1/auth/forgot-password endpoint
- Expand SMTP tests with config validation, message types, and error detection
- Improve test coverage from 44.6% to 53.3%
2025-12-04 22:54:24 +01:00

79 lines
2.1 KiB
Go

package http
import (
"bytes"
"encoding/json"
"io"
"net/http"
"strconv"
"strings"
"time"
"apocapoc-api/internal/infrastructure/auth"
"github.com/go-chi/httprate"
)
func RateLimitByUser(jwtService *auth.JWTService, requestsPerMinute int, duration time.Duration) func(http.Handler) http.Handler {
limiter := httprate.NewRateLimiter(
requestsPerMinute,
duration,
httprate.WithKeyFuncs(func(r *http.Request) (string, error) {
userID, ok := GetUserIDFromContext(r.Context())
if !ok {
return r.RemoteAddr, nil
}
return "user:" + userID, nil
}),
httprate.WithLimitHandler(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusTooManyRequests)
w.Write([]byte(`{"error":"Rate limit exceeded. Please try again later."}`))
}),
)
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-RateLimit-Limit", strconv.Itoa(requestsPerMinute))
limiter.Handler(next).ServeHTTP(w, r)
})
}
}
func RateLimitByEmail(requests int, duration time.Duration) func(http.Handler) http.Handler {
limiter := httprate.NewRateLimiter(
requests,
duration,
httprate.WithKeyFuncs(func(r *http.Request) (string, error) {
body, err := io.ReadAll(r.Body)
if err != nil {
return r.RemoteAddr, nil
}
r.Body = io.NopCloser(bytes.NewBuffer(body))
var data map[string]interface{}
if err := json.Unmarshal(body, &data); err != nil {
return r.RemoteAddr, nil
}
if email, ok := data["email"].(string); ok && email != "" {
return "email:" + strings.ToLower(email), nil
}
return r.RemoteAddr, nil
}),
httprate.WithLimitHandler(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusTooManyRequests)
w.Write([]byte(`{"error":"Too many password reset attempts. Please try again later."}`))
}),
)
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
limiter.Handler(next).ServeHTTP(w, r)
})
}
}