From 9dab3bc12c82c709724426d54268bcf36bc5990a Mon Sep 17 00:00:00 2001 From: Christoph Haas Date: Sun, 22 Mar 2026 23:52:37 +0100 Subject: [PATCH] Add domain name validation - Validate domain name format: letters, numbers, dots, hyphens only - Must have at least one dot (TLD required) - Cannot start/end with dot or hyphen - Label length 1-63 characters - Return ValidationError with descriptive message --- backend/internal/api/handlers/domains.go | 52 +++++++++++++++++++++++- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/backend/internal/api/handlers/domains.go b/backend/internal/api/handlers/domains.go index 4156ccf..40ea8d7 100644 --- a/backend/internal/api/handlers/domains.go +++ b/backend/internal/api/handlers/domains.go @@ -2,7 +2,9 @@ package handlers import ( "net/http" + "regexp" "strconv" + "strings" "github.com/gin-gonic/gin" "github.com/imc-vibe/backend/internal/db" @@ -98,13 +100,19 @@ func (h *DomainHandler) Create(c *gin.Context) { return } - existing, err := h.db.GetDomainByName(req.Name) + name := strings.ToLower(strings.TrimSpace(req.Name)) + if err := validateDomainName(name); err != nil { + Error(c, http.StatusBadRequest, err.Error()) + return + } + + existing, err := h.db.GetDomainByName(name) if err == nil && existing != nil { Error(c, http.StatusConflict, "domain already exists") return } - domain, err := h.db.CreateDomain(req.Name) + domain, err := h.db.CreateDomain(name) if err != nil { Error(c, http.StatusInternalServerError, "failed to create domain") return @@ -113,6 +121,46 @@ func (h *DomainHandler) Create(c *gin.Context) { Created(c, domain) } +func validateDomainName(name string) error { + if len(name) < 3 || len(name) > 253 { + return &ValidationError{Message: "domain name must be between 3 and 253 characters"} + } + + if strings.Contains(name, "..") { + return &ValidationError{Message: "domain name cannot contain consecutive dots"} + } + + if strings.HasPrefix(name, ".") || strings.HasSuffix(name, ".") { + return &ValidationError{Message: "domain name cannot start or end with a dot"} + } + + if strings.HasPrefix(name, "-") || strings.HasSuffix(name, "-") { + return &ValidationError{Message: "domain name cannot start or end with a hyphen"} + } + + domainRegex := regexp.MustCompile(`^[a-z0-9]([a-z0-9.-]*[a-z0-9])?$`) + if !domainRegex.MatchString(name) { + return &ValidationError{Message: "domain name can only contain letters, numbers, dots, and hyphens"} + } + + labels := strings.Split(name, ".") + for _, label := range labels { + if len(label) < 1 || len(label) > 63 { + return &ValidationError{Message: "each part of the domain name must be between 1 and 63 characters"} + } + } + + return nil +} + +type ValidationError struct { + Message string +} + +func (e *ValidationError) Error() string { + return e.Message +} + func (h *DomainHandler) Delete(c *gin.Context) { authCtx := GetAuthContext(c) if authCtx == nil || !authCtx.IsAdmin() {