package web import ( "crypto/rand" "encoding/hex" "net/http" "strconv" "vibetree/internal/models" ) // parseRole maps a form's role value to a models.Role, defaulting to // RoleUser for anything unrecognized. func parseRole(s string) models.Role { switch s { case "admin": return models.RoleAdmin case "contact": return models.RoleContact default: return models.RoleUser } } // randomPassword generates an unguessable password for contact accounts, // which can never sign in (see models.User.CanLogin) but still need some // non-empty password_hash in storage. func randomPassword() (string, error) { b := make([]byte, 24) if _, err := rand.Read(b); err != nil { return "", err } return hex.EncodeToString(b), nil } type usersListData struct { User *models.User Users []models.User } func (a *App) handleUsersList(w http.ResponseWriter, r *http.Request) { list, err := a.Users.List(r.Context()) if err != nil { writeErr(w, http.StatusInternalServerError, err.Error()) return } a.Render(w, http.StatusOK, "users_list", usersListData{User: currentUser(r), Users: list}) } // handleUserOptions returns enabled users as JSON, for pickers like the // asset checkout modal (a currently-assigned but since-disabled user still // displays correctly elsewhere; they just won't appear as a new choice). func (a *App) handleUserOptions(w http.ResponseWriter, r *http.Request) { list, err := a.Users.List(r.Context()) if err != nil { jsonErr(w, http.StatusInternalServerError, err) return } type option struct { ID int64 `json:"id"` Username string `json:"username"` } out := make([]option, 0, len(list)) for _, u := range list { if u.Enabled { out = append(out, option{ID: u.ID, Username: u.Username}) } } jsonOK(w, out) } type userFormData struct { User *models.User EditUser *models.User Error string } func (a *App) handleUserNewForm(w http.ResponseWriter, r *http.Request) { a.Render(w, http.StatusOK, "users_form", userFormData{User: currentUser(r)}) } func (a *App) handleUserCreate(w http.ResponseWriter, r *http.Request) { if err := r.ParseForm(); err != nil { writeErr(w, http.StatusBadRequest, err.Error()) return } role := parseRole(r.FormValue("role")) u := &models.User{Username: r.FormValue("username"), Role: role, Enabled: r.FormValue("enabled") == "on"} password := r.FormValue("password") if password == "" { if role != models.RoleContact { a.Render(w, http.StatusBadRequest, "users_form", userFormData{User: currentUser(r), EditUser: u, Error: "Password is required."}) return } generated, err := randomPassword() if err != nil { writeErr(w, http.StatusInternalServerError, err.Error()) return } password = generated } if err := a.Users.Create(r.Context(), u, password); err != nil { a.Render(w, http.StatusBadRequest, "users_form", userFormData{User: currentUser(r), EditUser: u, Error: err.Error()}) return } http.Redirect(w, r, "/settings/users", http.StatusSeeOther) } func (a *App) handleUserEditForm(w http.ResponseWriter, r *http.Request) { id, err := pathID(r, "id") if err != nil { writeErr(w, http.StatusBadRequest, err.Error()) return } u, err := a.Users.Get(r.Context(), id) if err != nil { writeErr(w, http.StatusNotFound, err.Error()) return } a.Render(w, http.StatusOK, "users_form", userFormData{User: currentUser(r), EditUser: u}) } func (a *App) handleUserUpdate(w http.ResponseWriter, r *http.Request) { id, err := pathID(r, "id") if err != nil { writeErr(w, http.StatusBadRequest, err.Error()) return } if err := r.ParseForm(); err != nil { writeErr(w, http.StatusBadRequest, err.Error()) return } u, err := a.Users.Get(r.Context(), id) if err != nil { writeErr(w, http.StatusNotFound, err.Error()) return } u.Username = r.FormValue("username") u.Role = parseRole(r.FormValue("role")) u.Enabled = r.FormValue("enabled") == "on" if err := a.Users.Update(r.Context(), u); err != nil { a.Render(w, http.StatusBadRequest, "users_form", userFormData{User: currentUser(r), EditUser: u, Error: err.Error()}) return } http.Redirect(w, r, "/settings/users", http.StatusSeeOther) } func (a *App) handleUserPassword(w http.ResponseWriter, r *http.Request) { id, err := pathID(r, "id") if err != nil { writeErr(w, http.StatusBadRequest, err.Error()) return } if err := r.ParseForm(); err != nil { writeErr(w, http.StatusBadRequest, err.Error()) return } password := r.FormValue("password") if password != "" { if err := a.Users.SetPassword(r.Context(), id, password); err != nil { a.Log.Warn("set password failed", "error", err) } } http.Redirect(w, r, "/settings/users/"+strconv.FormatInt(id, 10)+"/edit", http.StatusSeeOther) } func (a *App) handleUserDelete(w http.ResponseWriter, r *http.Request) { id, err := pathID(r, "id") if err != nil { writeErr(w, http.StatusBadRequest, err.Error()) return } if err := a.Users.Delete(r.Context(), id); err != nil { a.Log.Warn("delete user failed", "error", err) } http.Redirect(w, r, "/settings/users", http.StatusSeeOther) }