diff --git a/server/errors.go b/server/errors.go index 657d1ddc..c6076f3f 100644 --- a/server/errors.go +++ b/server/errors.go @@ -122,6 +122,7 @@ var ( errHTTPBadRequestTemplateInvalid = &errHTTP{40043, http.StatusBadRequest, "invalid request: could not parse template", "https://ntfy.sh/docs/publish/#message-templating", nil} errHTTPBadRequestTemplateDisallowedFunctionCalls = &errHTTP{40044, http.StatusBadRequest, "invalid request: template contains disallowed function calls, e.g. template, call, or define", "https://ntfy.sh/docs/publish/#message-templating", nil} errHTTPBadRequestTemplateExecuteFailed = &errHTTP{40045, http.StatusBadRequest, "invalid request: template execution failed", "https://ntfy.sh/docs/publish/#message-templating", nil} + errHTTPBadRequestInvalidUsername = &errHTTP{40046, http.StatusBadRequest, "invalid request: invalid username", "", nil} errHTTPNotFound = &errHTTP{40401, http.StatusNotFound, "page not found", "", nil} errHTTPUnauthorized = &errHTTP{40101, http.StatusUnauthorized, "unauthorized", "https://ntfy.sh/docs/publish/#authentication", nil} errHTTPForbidden = &errHTTP{40301, http.StatusForbidden, "forbidden", "https://ntfy.sh/docs/publish/#authentication", nil} diff --git a/server/server_account.go b/server/server_account.go index cb841d07..3f2368da 100644 --- a/server/server_account.go +++ b/server/server_account.go @@ -2,6 +2,7 @@ package server import ( "encoding/json" + "errors" "heckel.io/ntfy/v2/log" "heckel.io/ntfy/v2/user" "heckel.io/ntfy/v2/util" @@ -37,6 +38,9 @@ func (s *Server) handleAccountCreate(w http.ResponseWriter, r *http.Request, v * } logvr(v, r).Tag(tagAccount).Field("user_name", newAccount.Username).Info("Creating user %s", newAccount.Username) if err := s.userManager.AddUser(newAccount.Username, newAccount.Password, user.RoleUser); err != nil { + if errors.Is(err, user.ErrInvalidArgument) { + return errHTTPBadRequestInvalidUsername + } return err } v.AccountCreated() diff --git a/user/types.go b/user/types.go index 68ee02f3..6f6b1f69 100644 --- a/user/types.go +++ b/user/types.go @@ -241,7 +241,7 @@ const ( ) var ( - allowedUsernameRegex = regexp.MustCompile(`^[-_.@a-zA-Z0-9]+$`) // Does not include Everyone (*) + allowedUsernameRegex = regexp.MustCompile(`^[-_.+@a-zA-Z0-9]+$`) // Does not include Everyone (*) allowedTopicRegex = regexp.MustCompile(`^[-_A-Za-z0-9]{1,64}$`) // No '*' allowedTopicPatternRegex = regexp.MustCompile(`^[-_*A-Za-z0-9]{1,64}$`) // Adds '*' for wildcards! allowedTierRegex = regexp.MustCompile(`^[-_A-Za-z0-9]{1,64}$`) diff --git a/user/types_test.go b/user/types_test.go index 811d33f2..690fddf3 100644 --- a/user/types_test.go +++ b/user/types_test.go @@ -61,3 +61,15 @@ func TestTierContext(t *testing.T) { require.Equal(t, "price_456", context["stripe_yearly_price_id"]) } + +func TestUsernameRegex(t *testing.T) { + username := "phil" + usernameEmail := "phil@ntfy.sh" + usernameEmailAlias := "phil+alias@ntfy.sh" + usernameInvalid := "phil\rocks" + + require.True(t, AllowedUsername(username)) + require.True(t, AllowedUsername(usernameEmail)) + require.True(t, AllowedUsername(usernameEmailAlias)) + require.False(t, AllowedUsername(usernameInvalid)) +}