Repository: miniflux/v2 (language: go)

Issue:
User names forbidden by RFC 7617 are allowed
[RFC 7617](https://tools.ietf.org/html/rfc7617) notes:

> The user-id and password MUST NOT contain any control characters (see "CTL" in [Appendix B.1 of RFC5234](https://tools.ietf.org/html/rfc5234#appendix-B.1)).
>
> Furthermore, a user-id containing a colon character is invalid, as the first colon in a user-pass string separates user-id and password from one another; text after the first colon is part of the password. User-ids containing colons cannot be encoded in user-pass strings.

While Basic authentication is only one of several authentication schemes supported by Miniflux, and both controls and colons are uncommonly used in usernames, it may help to avoid confusion by simply forbidding them.


Discussion / hints:

Fix patch:
diff --git a/internal/validator/user.go b/internal/validator/user.go
index a7e05edb5cc..53220148596 100644
--- a/internal/validator/user.go
+++ b/internal/validator/user.go
@@ -6,6 +6,7 @@ package validator // import "miniflux.app/v2/internal/validator"
 import (
 	"slices"
 	"strings"
+	"unicode"
 
 	"miniflux.app/v2/internal/locale"
 	"miniflux.app/v2/internal/model"
@@ -22,6 +23,10 @@ func ValidateUserCreationWithPassword(store *storage.Storage, request *model.Use
 		return locale.NewLocalizedError("error.user_already_exists")
 	}
 
+	if err := validateUsername(request.Username); err != nil {
+		return err
+	}
+
 	if err := validatePassword(request.Password); err != nil {
 		return err
 	}
@@ -146,6 +151,23 @@ func validatePassword(password string) *locale.LocalizedError {
 	return nil
 }
 
+// validateUsername return an error if the `username` argument contains
+// a character that isn't alphanumerical nor `_` and `-`.
+func validateUsername(username string) *locale.LocalizedError {
+	if strings.ContainsFunc(username, func(r rune) bool {
+		if unicode.IsLetter(r) || unicode.IsNumber(r) {
+			return false
+		}
+		if r == '_' || r == '-' || r == '@' || r == '.' {
+			return false
+		}
+		return true
+	}) {
+		return locale.NewLocalizedError("error.invalid_username")
+	}
+	return nil
+}
+
 func validateTheme(theme string) *locale.LocalizedError {
 	themes := model.Themes()
 	if _, found := themes[theme]; !found {


Test patch:
diff --git a/internal/validator/validator_test.go b/internal/validator/validator_test.go
index 7121a111f99..e9a42b2acac 100644
--- a/internal/validator/validator_test.go
+++ b/internal/validator/validator_test.go
@@ -3,7 +3,11 @@
 
 package validator // import "miniflux.app/v2/internal/validator"
 
-import "testing"
+import (
+	"testing"
+
+	"miniflux.app/v2/internal/locale"
+)
 
 func TestIsValidURL(t *testing.T) {
 	scenarios := map[string]bool{
@@ -77,3 +81,25 @@ func TestIsValidDomain(t *testing.T) {
 		}
 	}
 }
+
+func TestValidateUsername(t *testing.T) {
+	scenarios := map[string]*locale.LocalizedError{
+		"jvoisin":          nil,
+		"j.voisin":         nil,
+		"j@vois.in":        nil,
+		"invalid username": locale.NewLocalizedError("error.invalid_username"),
+	}
+
+	for username, expected := range scenarios {
+		result := validateUsername(username)
+		if expected == nil {
+			if result != nil {
+				t.Errorf(`got an unexpected error for %q instead of nil: %v`, username, result)
+			}
+		} else {
+			if result == nil {
+				t.Errorf(`expected an error, got nil.`)
+			}
+		}
+	}
+}