From 16b6d9df60f3c11d6f4e3f174b2483b632d22973 Mon Sep 17 00:00:00 2001 From: Christoph Haas Date: Mon, 23 Mar 2026 00:16:15 +0100 Subject: [PATCH] Fix email local part validation per RFC 5321 - Remove dots from character class (handled separately) - Check username doesn't start/end with dot - Check for no consecutive dots (..) - Valid chars: a-zA-Z0-9!#$%&'*+-/=?^_`{|}~- --- backend/internal/api/handlers/users.go | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/backend/internal/api/handlers/users.go b/backend/internal/api/handlers/users.go index ea2d86a..ddb8b79 100644 --- a/backend/internal/api/handlers/users.go +++ b/backend/internal/api/handlers/users.go @@ -263,7 +263,7 @@ func (h *UserHandler) ListAll(c *gin.Context) { Success(c, users) } -var emailLocalPartRegex = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+\\-/=?^_`{|}~-]+$") +var emailLocalPartRegex = regexp.MustCompile("^[a-zA-Z0-9!#$%&'*+\\-/=?^_`{|}~-]+$") func validateEmailLocalPart(email string) error { parts := strings.Split(email, "@") @@ -276,8 +276,19 @@ func validateEmailLocalPart(email string) error { return &ValidationError{Message: "username must be between 1 and 64 characters"} } + // RFC 5321: local-part cannot start or end with a dot + if strings.HasPrefix(localPart, ".") || strings.HasSuffix(localPart, ".") { + return &ValidationError{Message: "username cannot start or end with a dot"} + } + + // RFC 5321: local-part cannot contain consecutive dots + if strings.Contains(localPart, "..") { + return &ValidationError{Message: "username cannot contain consecutive dots"} + } + + // Check valid characters (RFC 5321: letters, digits, and special chars !#$%&'*+/=?^_`{|}~-) if !emailLocalPartRegex.MatchString(localPart) { - return &ValidationError{Message: "username can only contain letters, numbers, and common special characters"} + return &ValidationError{Message: "username contains invalid characters"} } return nil