veza/veza-backend-api/internal/websocket/chat/rate_limiter.go
senke c7fb240dc3 feat(chat): Sprint 3 -- message handlers, real-time features, permissions
- Implement full MessageHandler dispatch with all 18 incoming message types
- Add handler_messages.go: SendMessage, EditMessage, DeleteMessage with ownership checks
- Add handler_rooms.go: JoinConversation, LeaveConversation
- Add handler_history.go: FetchHistory (cursor-based), SearchMessages (ILIKE), SyncMessages
- Add handler_realtime.go: Typing, MarkAsRead, Delivered, AddReaction, RemoveReaction
- Add handler_calls.go: WebRTC signaling relay (CallOffer/Answer/ICE/Hangup/Reject)
- Add PermissionService: CanRead/CanSend/CanJoin/CanModerate based on room_members
- Add RateLimiter: per-user per-action sliding window (in-memory)
- Wire all dependencies in router.go setupChatWebSocket
2026-02-22 20:43:44 +01:00

62 lines
1.1 KiB
Go

package chat
import (
"sync"
"time"
"github.com/google/uuid"
)
type RateLimiter struct {
limits map[string]rateConfig
entries map[string]*rateBucket
mu sync.Mutex
}
type rateConfig struct {
maxRequests int
window time.Duration
}
type rateBucket struct {
count int
windowAt time.Time
}
func NewRateLimiter() *RateLimiter {
return &RateLimiter{
limits: map[string]rateConfig{
"send_message": {maxRequests: 10, window: time.Second},
"typing": {maxRequests: 5, window: time.Second},
"search": {maxRequests: 2, window: time.Second},
"fetch_history": {maxRequests: 5, window: time.Second},
},
entries: make(map[string]*rateBucket),
}
}
func (rl *RateLimiter) Allow(userID uuid.UUID, action string) bool {
cfg, ok := rl.limits[action]
if !ok {
return true
}
key := userID.String() + ":" + action
now := time.Now()
rl.mu.Lock()
defer rl.mu.Unlock()
bucket, exists := rl.entries[key]
if !exists || now.Sub(bucket.windowAt) > cfg.window {
rl.entries[key] = &rateBucket{count: 1, windowAt: now}
return true
}
if bucket.count >= cfg.maxRequests {
return false
}
bucket.count++
return true
}