diff --git a/user/net.go b/user/net.go index 831c722..d1799a3 100644 --- a/user/net.go +++ b/user/net.go @@ -4,6 +4,7 @@ import ( "crypto/hmac" "crypto/sha256" "encoding/hex" + "errors" "fmt" "log" "net/http" @@ -41,6 +42,9 @@ func LogoutFromRequest(w http.ResponseWriter, rq *http.Request) { // Register registers the given user. If it fails, a non-nil error is returned. func Register(username, password, group, source string, force bool) error { + if !IsValidUsername(username) { + return fmt.Errorf("illegal username ‘%s’", username) + } username = util.CanonicalName(username) switch { @@ -75,26 +79,26 @@ func Register(username, password, group, source string, force bool) error { // LoginDataHTTP logs such user in and returns string representation of an error if there is any. // // The HTTP parameters are used for setting header status (bad request, if it is bad) and saving a cookie. -func LoginDataHTTP(w http.ResponseWriter, rq *http.Request, username, password string) string { +func LoginDataHTTP(w http.ResponseWriter, username, password string) error { w.Header().Set("Content-Type", "text/html;charset=utf-8") if !HasUsername(username) { w.WriteHeader(http.StatusBadRequest) log.Println("Unknown username", username, "was entered") - return "unknown username" + return errors.New("unknown username") } if !CredentialsOK(username, password) { w.WriteHeader(http.StatusBadRequest) log.Println("A wrong password was entered for username", username) - return "wrong password" + return errors.New("wrong password") } token, err := AddSession(username) if err != nil { log.Println(err) w.WriteHeader(http.StatusBadRequest) - return err.Error() + return err } http.SetCookie(w, cookie("token", token, time.Now().Add(365*24*time.Hour))) - return "" + return nil } // AddSession saves a session for `username` and returns a token to use. diff --git a/user/user.go b/user/user.go index 731e679..c87388b 100644 --- a/user/user.go +++ b/user/user.go @@ -1,8 +1,8 @@ package user import ( + "fmt" "net/http" - "regexp" "strings" "sync" "time" @@ -11,8 +11,6 @@ import ( "golang.org/x/crypto/bcrypt" ) -var usernamePattern = regexp.MustCompile(`[^?!:#@><*|"'&%{}/]+`) - // User contains information about a given user required for identification. type User struct { // Name is a username. It must follow hypha naming rules. @@ -138,8 +136,14 @@ func (user *User) ShowLockMaybe(w http.ResponseWriter, rq *http.Request) bool { // IsValidUsername checks if the given username is valid. func IsValidUsername(username string) bool { - return username != "anon" && username != "wikimind" && - usernamePattern.MatchString(strings.TrimSpace(username)) && + fmt.Println("Is", username, "ok") + for _, r := range username { + if strings.ContainsRune("?!:#@><*|\"'&%{}/", r) { + return false + } + } + return username != "anon" && + username != "wikimind" && usernameIsWhiteListed(username) } diff --git a/web/auth.go b/web/auth.go index f4ea069..4fd550b 100644 --- a/web/auth.go +++ b/web/auth.go @@ -27,7 +27,7 @@ func initAuth(r *mux.Router) { return } if cfg.AllowRegistration { - r.HandleFunc("/register", handlerRegister) + r.HandleFunc("/register", handlerRegister).Methods(http.MethodPost, http.MethodGet) } if cfg.TelegramEnabled { r.HandleFunc("/telegram-login", handlerTelegramLogin) @@ -60,34 +60,38 @@ func handlerRegister(w http.ResponseWriter, rq *http.Request) { views.Register(rq), ), ) - } else if rq.Method == http.MethodPost { - var ( - username = rq.PostFormValue("username") - password = rq.PostFormValue("password") - err = user.Register(username, password, "editor", "local", false) - ) - if err != nil { - log.Printf("Failed to register ‘%s’: %s", username, err.Error()) - w.Header().Set("Content-Type", mime.TypeByExtension(".html")) - w.WriteHeader(http.StatusBadRequest) - _, _ = io.WriteString( - w, - views.Base( - viewutil.MetaFrom(w, rq), - lc.Get("auth.register_title"), - fmt.Sprintf( - `

%s

%s

`, - err.Error(), - lc.Get("auth.try_again"), - ), - ), - ) - } else { - log.Printf("Successfully registered ‘%s’", username) - user.LoginDataHTTP(w, rq, username, password) - http.Redirect(w, rq, "/"+rq.URL.RawQuery, http.StatusSeeOther) - } + return } + + var ( + username = rq.PostFormValue("username") + password = rq.PostFormValue("password") + err = user.Register(username, password, "editor", "local", false) + ) + if err != nil { + log.Printf("Failed to register ‘%s’: %s", username, err.Error()) + w.Header().Set("Content-Type", mime.TypeByExtension(".html")) + w.WriteHeader(http.StatusBadRequest) + _, _ = io.WriteString( + w, + views.Base( + viewutil.MetaFrom(w, rq), + lc.Get("auth.register_title"), + fmt.Sprintf( + `

%s

%s

`, + err.Error(), + lc.Get("auth.try_again"), + ), + ), + ) + return + } + + log.Printf("Successfully registered ‘%s’", username) + if err := user.LoginDataHTTP(w, username, password); err != nil { + return + } + http.Redirect(w, rq, "/"+rq.URL.RawQuery, http.StatusSeeOther) } // handlerLogout shows the logout form (GET) or logs the user out (POST). @@ -134,12 +138,12 @@ func handlerLogin(w http.ResponseWriter, rq *http.Request) { var ( username = util.CanonicalName(rq.PostFormValue("username")) password = rq.PostFormValue("password") - err = user.LoginDataHTTP(w, rq, username, password) + err = user.LoginDataHTTP(w, username, password) ) - if err != "" { + if err != nil { w.Header().Set("Content-Type", "text/html;charset=utf-8") w.WriteHeader(http.StatusInternalServerError) - _, _ = io.WriteString(w, views.Base(viewutil.MetaFrom(w, rq), err, views.LoginError(err, lc))) + _, _ = io.WriteString(w, views.Base(viewutil.MetaFrom(w, rq), err.Error(), views.LoginError(err.Error(), lc))) return } http.Redirect(w, rq, "/", http.StatusSeeOther) @@ -191,8 +195,8 @@ func handlerTelegramLogin(w http.ResponseWriter, rq *http.Request) { return } - errmsg := user.LoginDataHTTP(w, rq, username, "") - if errmsg != "" { + errmsg := user.LoginDataHTTP(w, username, "") + if errmsg != nil { log.Printf("Failed to login ‘%s’ using Telegram: %s", username, err.Error()) w.WriteHeader(http.StatusBadRequest) _, _ = io.WriteString(