veza/veza-backend-api/internal/handlers/chat_handler.go
senke 24b29d229d fix(v0.12.6.1): remediate 2 CRITICAL + 10 HIGH + 1 MEDIUM pentest findings
Security fixes implemented:

CRITICAL:
- CRIT-001: IDOR on chat rooms — added IsRoomMember check before
  returning room data or message history (returns 404, not 403)
- CRIT-002: play_count/like_count exposed publicly — changed JSON
  tags to "-" so they are never serialized in API responses

HIGH:
- HIGH-001: TOCTOU race on marketplace downloads — transaction +
  SELECT FOR UPDATE on GetDownloadURL
- HIGH-002: HS256 in production docker-compose — replaced JWT_SECRET
  with JWT_PRIVATE_KEY_PATH / JWT_PUBLIC_KEY_PATH (RS256)
- HIGH-003: context.Background() bypass in user repository — full
  context propagation from handlers → services → repository (29 files)
- HIGH-004: Race condition on promo codes — SELECT FOR UPDATE
- HIGH-005: Race condition on exclusive licenses — SELECT FOR UPDATE
- HIGH-006: Rate limiter IP spoofing — SetTrustedProxies(nil) default
- HIGH-007: RGPD hard delete incomplete — added cleanup for sessions,
  settings, follows, notifications, audit_logs anonymization
- HIGH-008: RTMP callback auth weak — fail-closed when unconfigured,
  header-only (no query param), constant-time compare
- HIGH-009: Co-listening host hijack — UpdateHostState now takes *Conn
  and verifies IsHost before processing
- HIGH-010: Moderator self-strike — added issuedBy != userID check

MEDIUM:
- MEDIUM-001: Recovery codes used math/rand — replaced with crypto/rand
- MEDIUM-005: Stream token forgeable — resolved by HIGH-002 (RS256)

Updated REMEDIATION_MATRIX: 14 findings marked  CORRIGÉ.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 05:40:53 +01:00

127 lines
4 KiB
Go

package handlers
import (
"context"
"fmt"
"net/http"
apperrors "veza-backend-api/internal/errors"
"veza-backend-api/internal/models"
"veza-backend-api/internal/services"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"go.uber.org/zap"
)
// ChatServiceInterfaceForChatHandler defines methods needed for chat handler
type ChatServiceInterfaceForChatHandler interface {
GenerateToken(userID uuid.UUID, username string) (*services.ChatTokenResponse, error)
GetStats(ctx context.Context) (*services.ChatStats, error)
}
// UserServiceInterfaceForChatHandler defines methods needed for chat handler
type UserServiceInterfaceForChatHandler interface {
GetByID(ctx context.Context, userID uuid.UUID) (*models.User, error)
}
type ChatHandler struct {
chatService ChatServiceInterfaceForChatHandler
userService UserServiceInterfaceForChatHandler
logger *zap.Logger
}
func NewChatHandler(chatService *services.ChatService, userService *services.UserService, logger *zap.Logger) *ChatHandler {
return &ChatHandler{
chatService: &chatServiceWrapper{chatService: chatService},
userService: &userServiceWrapper{userService: userService},
logger: logger,
}
}
// chatServiceWrapper wraps *services.ChatService to implement ChatServiceInterfaceForChatHandler
type chatServiceWrapper struct {
chatService *services.ChatService
}
func (w *chatServiceWrapper) GenerateToken(userID uuid.UUID, username string) (*services.ChatTokenResponse, error) {
return w.chatService.GenerateToken(userID, username)
}
func (w *chatServiceWrapper) GetStats(ctx context.Context) (*services.ChatStats, error) {
return w.chatService.GetStats(ctx)
}
// userServiceWrapper wraps *services.UserService to implement UserServiceInterfaceForChatHandler
type userServiceWrapper struct {
userService *services.UserService
}
func (w *userServiceWrapper) GetByID(ctx context.Context, userID uuid.UUID) (*models.User, error) {
return w.userService.GetByID(ctx, userID)
}
// NewChatHandlerWithInterface creates a new chat handler with interfaces (for testing)
func NewChatHandlerWithInterface(chatService ChatServiceInterfaceForChatHandler, userService UserServiceInterfaceForChatHandler, logger *zap.Logger) *ChatHandler {
return &ChatHandler{
chatService: chatService,
userService: userService,
logger: logger,
}
}
// GetToken generates a JWT token for the chat service
// @Summary Get Chat Token
// @Description Generate a short-lived token for chat authentication
// @Tags Chat
// @Accept json
// @Produce json
// @Security BearerAuth
// @Success 200 {object} APIResponse{data=object{token=string}}
// @Failure 401 {object} APIResponse "Unauthorized"
// @Failure 500 {object} APIResponse "Internal Error"
// @Router /chat/token [get]
func (h *ChatHandler) GetToken(c *gin.Context) {
userIDVal, exists := c.Get("user_id")
if !exists {
RespondWithAppError(c, apperrors.NewUnauthorizedError("unauthorized"))
return
}
userID, ok := userIDVal.(uuid.UUID)
if !ok || userID == uuid.Nil {
RespondWithAppError(c, apperrors.NewUnauthorizedError("unauthorized"))
return
}
// Get username from DB
user, err := h.userService.GetByID(c.Request.Context(), userID)
username := "user"
if err == nil && user != nil {
username = user.Username
} else {
// Fallback
username = fmt.Sprintf("user_%s", userID)
}
token, err := h.chatService.GenerateToken(userID, username)
if err != nil {
h.logger.Error("Failed to generate chat token", zap.Error(err))
RespondWithAppError(c, apperrors.NewInternalErrorWrap("failed to generate token", err))
return
}
RespondSuccess(c, http.StatusOK, token)
}
// GetStats returns chat statistics
// BE-API-006: Implement chat stats endpoint
func (h *ChatHandler) GetStats(c *gin.Context) {
stats, err := h.chatService.GetStats(c.Request.Context())
if err != nil {
h.logger.Error("Failed to get chat stats", zap.Error(err))
RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "Failed to get chat stats", err))
return
}
RespondSuccess(c, http.StatusOK, stats)
}