veza/veza-backend-api/internal/handlers/report_handler.go
senke c0e2fe2e12 fix(v0.12.6.1): remediate remaining 15 MEDIUM + LOW pentest findings
MEDIUM-002: Remove manual X-Forwarded-For parsing in metrics_protection.go,
  use c.ClientIP() only (respects SetTrustedProxies)
MEDIUM-003: Pin ClamAV Docker image to 1.4 across all compose files
MEDIUM-004: Add clampLimit(100) to 15+ handlers that parsed limit directly
MEDIUM-006: Remove unsafe-eval from CSP script-src on Swagger routes
MEDIUM-007: Pin all GitHub Actions to SHA in 11 workflow files
MEDIUM-008: Replace rabbitmq:3-management-alpine with rabbitmq:3-alpine in prod
MEDIUM-009: Add trial-already-used check in subscription service
MEDIUM-010: Add 60s periodic token re-validation to WebSocket connections
MEDIUM-011: Mask email in auth handler logs with maskEmail() helper
MEDIUM-012: Add k-anonymity threshold (k=5) to playback analytics stats
LOW-001: Align frontend password policy to 12 chars (matching backend)
LOW-003: Replace deprecated dotenv with dotenvy crate in Rust stream server
LOW-004: Enable xpack.security in Elasticsearch dev/local compose files
LOW-005: Accept context.Context in CleanupExpiredSessions instead of Background()
LOW-002: Noted — Hyperswitch version update deferred (requires payment integration tests)

29/30 findings remediated. 1 noted (LOW-002).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 06:13:38 +01:00

101 lines
2.8 KiB
Go

package handlers
import (
"net/http"
"strconv"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
apperrors "veza-backend-api/internal/errors"
"veza-backend-api/internal/services"
)
// ReportHandler handles moderation report endpoints (v0.803 ADM1)
type ReportHandler struct {
reportService *services.ReportService
}
// NewReportHandler creates a new ReportHandler
func NewReportHandler(reportService *services.ReportService) *ReportHandler {
return &ReportHandler{reportService: reportService}
}
// ListReports returns paginated reports (admin only)
func (h *ReportHandler) ListReports(c *gin.Context) {
status := c.DefaultQuery("status", "all")
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "20"))
limit = clampLimit(limit) // SECURITY(MEDIUM-004)
offset, _ := strconv.Atoi(c.DefaultQuery("offset", "0"))
reports, total, err := h.reportService.List(c.Request.Context(), services.ListReportsParams{
Status: status,
Limit: limit,
Offset: offset,
})
if err != nil {
RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to list reports", err))
return
}
c.JSON(http.StatusOK, gin.H{
"reports": reports,
"pagination": gin.H{
"total": total,
"limit": limit,
"offset": offset,
},
})
}
// ResolveReport resolves a report (admin only)
func (h *ReportHandler) ResolveReport(c *gin.Context) {
reportIDStr := c.Param("id")
reportID, err := uuid.Parse(reportIDStr)
if err != nil {
RespondWithAppError(c, apperrors.NewValidationError("Invalid report ID"))
return
}
userID, ok := GetUserIDUUID(c)
if !ok || userID == uuid.Nil {
RespondWithAppError(c, apperrors.NewUnauthorizedError("Authentication required"))
return
}
var req services.ResolveReportRequest
if err := c.ShouldBindJSON(&req); err != nil {
RespondWithAppError(c, apperrors.NewValidationError("action is required"))
return
}
if err := h.reportService.Resolve(c.Request.Context(), reportID, userID, req.Action); err != nil {
RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to resolve report", err))
return
}
c.JSON(http.StatusOK, gin.H{"message": "Report resolved"})
}
// CreateReport creates a report (authenticated user)
func (h *ReportHandler) CreateReport(c *gin.Context) {
userID, ok := GetUserIDUUID(c)
if !ok || userID == uuid.Nil {
RespondWithAppError(c, apperrors.NewUnauthorizedError("Authentication required"))
return
}
var req services.CreateReportRequest
if err := c.ShouldBindJSON(&req); err != nil {
RespondWithAppError(c, apperrors.NewValidationError("content_type and reason are required"))
return
}
report, err := h.reportService.Create(c.Request.Context(), userID, req)
if err != nil {
RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to create report", err))
return
}
c.JSON(http.StatusCreated, report)
}