security: create /api/v1/validate endpoint for pre-validation

- Created ValidateHandler with Validate method
- Endpoint accepts POST /api/v1/validate with type and data
- Supports RegisterRequest and LoginRequest validation types
- Uses existing validator from CommonHandler
- Returns ValidateResponse with valid flag and errors array
- Public endpoint (no auth required)
- Route registered in setupValidateRoutes
- Code compiles successfully
- Follows existing handler patterns
- Action 5.2.1.1 complete
This commit is contained in:
senke 2026-01-15 20:04:16 +01:00
parent c23bad2099
commit 97ad8a61e3
3 changed files with 127 additions and 3 deletions

View file

@ -1664,11 +1664,20 @@ Critical path dependencies:
### Sub-Epic 5.2: Pre-Validation 🟢
#### Task 5.2.1: Add Backend Validation Endpoint
- [ ] **Action 5.2.1.1**: Create `/api/v1/validate` endpoint
- [x] **Action 5.2.1.1**: Create `/api/v1/validate` endpoint
- **Scope**: `veza-backend-api/internal/handlers/validate.go` (create)
- **Dependencies**: None
- **Dependencies**: None
- **Risk**: LOW
- **Validation**: Endpoint validates request bodies
- **Validation**: ✅ Endpoint validates request bodies:
- Created ValidateHandler with Validate method
- Endpoint accepts POST /api/v1/validate with type and data
- Supports RegisterRequest and LoginRequest validation types
- Uses existing validator from CommonHandler
- Returns ValidateResponse with valid flag and errors array
- Public endpoint (no auth required)
- Route registered in setupValidateRoutes
- Code compiles successfully
- Follows existing handler patterns
- **Rollback**: Delete endpoint
- [ ] **Action 5.2.1.2**: Frontend: Call validate before submit

View file

@ -264,6 +264,9 @@ func (r *APIRouter) Setup(router *gin.Engine) error {
return err
}
// Action 5.2.1.1: Validation endpoint for pre-validation
r.setupValidateRoutes(v1)
// Réactivation des routes User et Track pour Phase 1
r.setupUserRoutes(v1)
r.setupTrackRoutes(v1)
@ -512,6 +515,14 @@ func (r *APIRouter) setupAuthRoutes(router *gin.RouterGroup) error {
return nil
}
// setupValidateRoutes configures the validation endpoint
// Action 5.2.1.1: Create /api/v1/validate endpoint
func (r *APIRouter) setupValidateRoutes(router *gin.RouterGroup) {
validateHandler := handlers.NewValidateHandler(r.logger)
// Public endpoint - no auth required for validation
router.POST("/validate", validateHandler.Validate)
}
// setupInternalRoutes configure les routes internal (legacy and modern)
// These routes must be on the root router, not under /api/v1
func (r *APIRouter) setupInternalRoutes(router *gin.Engine) {

View file

@ -0,0 +1,104 @@
package handlers
import (
"encoding/json"
"net/http"
"veza-backend-api/internal/dto"
"veza-backend-api/internal/errors"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
// ValidateRequest represents the request body for the validate endpoint
type ValidateRequest struct {
Type string `json:"type" binding:"required"` // e.g., "RegisterRequest", "LoginRequest"
Data json.RawMessage `json:"data" binding:"required"` // The data to validate
}
// ValidateResponse represents the response from the validate endpoint
type ValidateResponse struct {
Valid bool `json:"valid"`
Errors []dto.ValidationError `json:"errors,omitempty"`
Message string `json:"message,omitempty"`
}
// ValidateHandler handles validation requests
type ValidateHandler struct {
commonHandler *CommonHandler
logger *zap.Logger
}
// NewValidateHandler creates a new ValidateHandler
func NewValidateHandler(logger *zap.Logger) *ValidateHandler {
return &ValidateHandler{
commonHandler: NewCommonHandler(logger),
logger: logger,
}
}
// Validate validates request bodies against known DTOs
// POST /api/v1/validate
// @Summary Validate request body
// @Description Validates request data against known DTO types without executing the actual operation
// @Tags Validation
// @Accept json
// @Produce json
// @Param request body ValidateRequest true "Validation request with type and data"
// @Success 200 {object} ValidateResponse "Validation result"
// @Failure 400 {object} APIResponse "Invalid request format"
// @Router /validate [post]
func (h *ValidateHandler) Validate(c *gin.Context) {
var req ValidateRequest
if err := c.ShouldBindJSON(&req); err != nil {
RespondWithError(c, http.StatusBadRequest, "Invalid request format", errors.ErrorDetail{
Message: err.Error(),
})
return
}
// Map type string to DTO struct and validate
var validationErrors []dto.ValidationError
switch req.Type {
case "RegisterRequest":
var dtoReq dto.RegisterRequest
if err := json.Unmarshal(req.Data, &dtoReq); err != nil {
RespondWithError(c, http.StatusBadRequest, "Invalid data format for RegisterRequest", errors.ErrorDetail{
Message: err.Error(),
})
return
}
validationErrors = h.commonHandler.validator.Validate(&dtoReq)
case "LoginRequest":
var dtoReq dto.LoginRequest
if err := json.Unmarshal(req.Data, &dtoReq); err != nil {
RespondWithError(c, http.StatusBadRequest, "Invalid data format for LoginRequest", errors.ErrorDetail{
Message: err.Error(),
})
return
}
validationErrors = h.commonHandler.validator.Validate(&dtoReq)
default:
RespondWithError(c, http.StatusBadRequest, "Unknown validation type: "+req.Type)
return
}
// Return validation result
if len(validationErrors) > 0 {
RespondSuccess(c, http.StatusOK, ValidateResponse{
Valid: false,
Errors: validationErrors,
Message: "Validation failed",
})
return
}
RespondSuccess(c, http.StatusOK, ValidateResponse{
Valid: true,
Message: "Validation passed",
})
}