all repos — flounder @ d8efa1bc645cd8051a8ae6ee2744017ca11611d4

A small site builder for the Gemini protocol

test version -- needs qa
alex wennerberg alex@alexwennerberg.com
Sat, 05 Dec 2020 20:12:37 -0800
commit

d8efa1bc645cd8051a8ae6ee2744017ca11611d4

parent

e286a19d517d41e50443c3885ce8695878da32c0

4 files changed, 120 insertions(+), 18 deletions(-)

jump to
M admin.goadmin.go

@@ -23,19 +23,24 @@ if len(args) < 3 {

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

@@ -71,6 +76,34 @@ username = filepath.Clean(username)

os.Mkdir(path.Join(c.FilesDirectory, username), os.ModePerm) ioutil.WriteFile(path.Join(c.FilesDirectory, username, "index.gmi"), []byte(baseIndex), 0644) os.Mkdir(path.Join(c.FilesDirectory, username), os.ModePerm) + 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 }
M http.gohttp.go

@@ -260,6 +260,66 @@ }{c.Host, c.SiteTitle, authUser, files, authd, isAdmin}

_ = 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 @@ }

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 @@ r.ParseForm()

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 @@ if len(password) < 6 {

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 @@ port := c.HttpPort

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)
M main.gomain.go

@@ -67,6 +67,16 @@ }

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 @@ _, err := io.ReadFull(rand.Reader, k)

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) }
M templates/nav.htmltemplates/nav.html

@@ -2,6 +2,7 @@ <nav>

<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 }}