flounder

A simple gemini site builder
Log | Files | Refs | README | LICENSE

commit d8efa1bc645cd8051a8ae6ee2744017ca11611d4
parent e286a19d517d41e50443c3885ce8695878da32c0
Author: alex wennerberg <alex@alexwennerberg.com>
Date:   Sat,  5 Dec 2020 20:12:37 -0800

test version -- needs qa

Diffstat:
Madmin.go | 43++++++++++++++++++++++++++++++++++++++-----
Mhttp.go | 82+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
Mmain.go | 12+++++++++++-
Mtemplates/nav.html | 1+
4 files changed, 120 insertions(+), 18 deletions(-)

diff --git a/admin.go b/admin.go @@ -23,19 +23,24 @@ func runAdminCommand() { fmt.Println("Expected subcommand with parameter activate-user|delete-user|make-admin") os.Exit(1) } + var err error switch args[1] { case "activate-user": username := args[2] - err := activateUser(username) - log.Fatal(err) + err = activateUser(username) case "delete-user": username := args[2] // TODO add confirmation - err := deleteUser(username) - log.Fatal(err) + err = deleteUser(username) case "make-admin": username := args[2] - err := makeAdmin(username) + err = makeAdmin(username) + case "rename-user": + username := args[2] + newUsername := args[3] + err = renameUser(username, newUsername) + } + if err != nil { log.Fatal(err) } // reset password @@ -74,6 +79,34 @@ Have fun!` return nil } +func renameUser(oldUsername string, newUsername string) error { + err := isOkUsername(newUsername) + if err != nil { + return err + } + fmt.Println("Old user", oldUsername) + fmt.Println("new user", newUsername) + res, err := DB.Exec("UPDATE user set username = ? WHERE username = ?", newUsername, oldUsername) + if err != nil { + return err + } + rowsAffected, err := res.RowsAffected() + if rowsAffected != 1 { + return fmt.Errorf("No User updated %s %s", oldUsername, newUsername) + } else if err != nil { + return err + } + userFolder := path.Join(c.FilesDirectory, oldUsername) + newUserFolder := path.Join(c.FilesDirectory, newUsername) + err = os.Rename(userFolder, newUserFolder) + if err != nil { + // This would be bad. User in broken, insecure state. + // TODO some sort of better handling? + return err + } + return nil +} + func deleteUser(username string) error { _, err := DB.Exec("DELETE FROM user WHERE username = $1", username) if err != nil { diff --git a/http.go b/http.go @@ -260,6 +260,66 @@ func mySiteHandler(w http.ResponseWriter, r *http.Request) { _ = t.ExecuteTemplate(w, "my_site.html", data) } +func myAccountHandler(w http.ResponseWriter, r *http.Request) { + authd, authUser, isAdmin := getAuthUser(r) + if !authd { + renderDefaultError(w, http.StatusForbidden) + return + } + me, _ := getUserByName(authUser) + type pageData struct { + PageTitle string + LoggedIn bool + AuthUser string + IsAdmin bool + Email string + Errors []string + } + data := pageData{"My Account", true, authUser, isAdmin, me.Email, nil} + + if r.Method == "GET" { + err := t.ExecuteTemplate(w, "me.html", data) + if err != nil { + log.Println(err) + renderDefaultError(w, http.StatusInternalServerError) + return + } + } else if r.Method == "POST" { + r.ParseForm() + newUsername := r.Form.Get("username") + fmt.Println(newUsername) + errors := []string{} + newEmail := r.Form.Get("email") + newUsername = strings.ToLower(newUsername) + var err error + if newEmail != me.Email { + _, err = DB.Exec("update user set email = ? where username = ?", newEmail, me.Email) + if err != nil { + // TODO better error not sql + errors = append(errors, err.Error()) + } + } + if newUsername != authUser { + // Rename User + err = renameUser(authUser, newUsername) + fmt.Println(newEmail, me.Email, newUsername, authUser) + if err != nil { + errors = append(errors, err.Error()) + } else { + session, _ := SessionStore.Get(r, "cookie-session") + session.Values["auth_user"] = newUsername + session.Save(r, w) + } + } + // reset auth + authd, authUser, isAdmin = getAuthUser(r) + data.Errors = errors + data.AuthUser = authUser + data.Email = newEmail + _ = t.ExecuteTemplate(w, "me.html", data) + } +} + func archiveHandler(w http.ResponseWriter, r *http.Request) { authd, authUser, _ := getAuthUser(r) if !authd { @@ -339,19 +399,19 @@ func logoutHandler(w http.ResponseWriter, r *http.Request) { const ok = "-0123456789abcdefghijklmnopqrstuvwxyz" -func isOkUsername(s string) bool { +func isOkUsername(s string) error { if len(s) < 1 { - return false + return fmt.Errorf("Username is too short") } - if len(s) > 31 { - return false + if len(s) > 32 { + return fmt.Errorf("Username is too long. 32 char max.") } for _, char := range s { if !strings.Contains(ok, strings.ToLower(string(char))) { - return false + return fmt.Errorf("Username contains invalid characters. Valid characters include lowercase letters, numbers, and hyphens.") } } - return true + return nil } func registerHandler(w http.ResponseWriter, r *http.Request) { if r.Method == "GET" { @@ -371,9 +431,6 @@ func registerHandler(w http.ResponseWriter, r *http.Request) { email := r.Form.Get("email") password := r.Form.Get("password") errors := []string{} - if !strings.Contains(email, "@") { - errors = append(errors, "Invalid Email") - } if r.Form.Get("password") != r.Form.Get("password2") { errors = append(errors, "Passwords don't match") } @@ -381,15 +438,15 @@ func registerHandler(w http.ResponseWriter, r *http.Request) { errors = append(errors, "Password is too short") } username := strings.ToLower(r.Form.Get("username")) - if !isOkUsername(username) { - errors = append(errors, "Username is invalid: can only contain letters, numbers and hypens. Maximum 32 characters.") + err := isOkUsername(username) + if err != nil { + errors = append(errors, err.Error()) } hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), 8) // TODO handle error reference := r.Form.Get("reference") if len(errors) == 0 { _, err = DB.Exec("insert into user (username, email, password_hash, reference) values ($1, $2, $3, $4)", username, email, string(hashedPassword), reference) if err != nil { - log.Println(err) errors = append(errors, "Username or email is already used") } } @@ -543,6 +600,7 @@ func runHTTPServer() { serveMux.HandleFunc(hostname+"/", rootHandler) serveMux.HandleFunc(hostname+"/my_site", mySiteHandler) + serveMux.HandleFunc(hostname+"/me", myAccountHandler) serveMux.HandleFunc(hostname+"/my_site/flounder-archive.zip", archiveHandler) serveMux.HandleFunc(hostname+"/admin", adminHandler) serveMux.HandleFunc(hostname+"/edit/", editFileHandler) diff --git a/main.go b/main.go @@ -67,6 +67,16 @@ func getActiveUserNames() ([]string, error) { return dest, nil } +func getUserByName(username string) (*User, error) { + var user User + row := DB.QueryRow(`SELECT username, email, active, admin, created_at, reference from user WHERE username = ?`, username) + err := row.Scan(&user.Username, &user.Email, &user.Active, &user.Admin, &user.CreatedAt, &user.Reference) + if err != nil { + return nil, err + } + return &user, nil +} + func getUsers() ([]User, error) { rows, err := DB.Query(`SELECT username, email, active, admin, created_at, reference from user ORDER BY created_at DESC`) if err != nil { @@ -197,7 +207,7 @@ func generateCookieKeyIfDNE() []byte { if err != nil { log.Fatal(err) } - _, err = DB.Exec("insert into cookie_key values ($1)", k) + _, err = DB.Exec("insert into cookie_key values (?)", k) if err != nil { log.Fatal(err) } diff --git a/templates/nav.html b/templates/nav.html @@ -2,6 +2,7 @@ <a href="/">home</a> {{ if .LoggedIn }} <a href="/my_site">my_site</a> + <a href="/me">me</a> {{ if .IsAdmin }} <a href="/admin">admin</a> {{ end }}