Security fixes: - Fix sensitive data exposure: UserResponse no longer contains email/phone - Add UserSelfResponse for authenticated user's own data - Protect Gorse endpoints with admin role requirement - Add rate limiting (10 req/min) to auth endpoints - Fix CORS configuration to use allowed origins list - Add pagination parameter validation (max 100 per page) Changes: - dto/dto.go: Add UserSelfResponse type - dto/user_converter.go: Add ConvertUserToSelfResponse - handler/user_handler.go: Use secure response types - middleware/cors.go: Implement origin whitelist - middleware/ratelimit.go: Enhance rate limiter - router/router.go: Add auth rate limit, protect Gorse - service/admin_*.go: Use ConvertUserToResponse
118 lines
2.0 KiB
Go
118 lines
2.0 KiB
Go
package middleware
|
|
|
|
import (
|
|
"net/http"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
type RateLimiter struct {
|
|
requests map[string][]time.Time
|
|
mu sync.Mutex
|
|
limit int
|
|
window time.Duration
|
|
}
|
|
|
|
func NewRateLimiter(limit int, window time.Duration) *RateLimiter {
|
|
rl := &RateLimiter{
|
|
requests: make(map[string][]time.Time),
|
|
limit: limit,
|
|
window: window,
|
|
}
|
|
|
|
go func() {
|
|
for {
|
|
time.Sleep(window)
|
|
rl.cleanup()
|
|
}
|
|
}()
|
|
|
|
return rl
|
|
}
|
|
|
|
func (rl *RateLimiter) cleanup() {
|
|
rl.mu.Lock()
|
|
defer rl.mu.Unlock()
|
|
|
|
now := time.Now()
|
|
for key, times := range rl.requests {
|
|
var valid []time.Time
|
|
for _, t := range times {
|
|
if now.Sub(t) < rl.window {
|
|
valid = append(valid, t)
|
|
}
|
|
}
|
|
if len(valid) == 0 {
|
|
delete(rl.requests, key)
|
|
} else {
|
|
rl.requests[key] = valid
|
|
}
|
|
}
|
|
}
|
|
|
|
func (rl *RateLimiter) isAllowed(key string) bool {
|
|
rl.mu.Lock()
|
|
defer rl.mu.Unlock()
|
|
|
|
now := time.Now()
|
|
times := rl.requests[key]
|
|
|
|
var valid []time.Time
|
|
for _, t := range times {
|
|
if now.Sub(t) < rl.window {
|
|
valid = append(valid, t)
|
|
}
|
|
}
|
|
|
|
if len(valid) >= rl.limit {
|
|
rl.requests[key] = valid
|
|
return false
|
|
}
|
|
|
|
rl.requests[key] = append(valid, now)
|
|
return true
|
|
}
|
|
|
|
func RateLimit(requestsPerMinute int) gin.HandlerFunc {
|
|
limiter := NewRateLimiter(requestsPerMinute, time.Minute)
|
|
|
|
return func(c *gin.Context) {
|
|
ip := c.ClientIP()
|
|
|
|
if !limiter.isAllowed(ip) {
|
|
c.JSON(http.StatusTooManyRequests, gin.H{
|
|
"code": 429,
|
|
"message": "too many requests, please try again later",
|
|
})
|
|
c.Abort()
|
|
return
|
|
}
|
|
|
|
c.Next()
|
|
}
|
|
}
|
|
|
|
func RateLimitWithKey(requestsPerMinute int, keyFunc func(c *gin.Context) string) gin.HandlerFunc {
|
|
limiter := NewRateLimiter(requestsPerMinute, time.Minute)
|
|
|
|
return func(c *gin.Context) {
|
|
key := keyFunc(c)
|
|
if key == "" {
|
|
key = c.ClientIP()
|
|
}
|
|
|
|
if !limiter.isAllowed(key) {
|
|
c.JSON(http.StatusTooManyRequests, gin.H{
|
|
"code": 429,
|
|
"message": "too many requests, please try again later",
|
|
})
|
|
c.Abort()
|
|
return
|
|
}
|
|
|
|
c.Next()
|
|
}
|
|
}
|