diff --git a/cmd/main.go b/cmd/main.go index 06bc7ae..6904782 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -51,9 +51,11 @@ func main() { mux.HandleFunc("GET /create-tag", view.CreateTag(args)) mux.HandleFunc("GET /create-user", view.CreateUser(args)) - mux.HandleFunc("GET /edit-user", view.EditUser(args, db, store)) + mux.HandleFunc("GET /edit-self", view.EditSelf(args, db, store)) + mux.HandleFunc("GET /edit-user/{id}", view.EditUser(args, db)) mux.HandleFunc("GET /hub", view.ShowHub(args, db, store)) mux.HandleFunc("GET /logout", view.Logout(args, store)) + mux.HandleFunc("GET /pics/{pic}", view.ServeImage(args, store)) mux.HandleFunc("GET /publish-article/{id}", view.PublishArticle(args, db, store)) mux.HandleFunc("GET /publish-issue", view.PublishLatestIssue(args, db, store)) mux.HandleFunc("GET /reject-article/{id}", view.RejectArticle(args, db, store)) @@ -63,7 +65,7 @@ func main() { mux.HandleFunc("GET /rss", func(w http.ResponseWriter, r *http.Request) { http.ServeFile(w, r, args.RSSFile) }) - mux.HandleFunc("GET /pics/{pic}", view.ServeImage(args, store)) + mux.HandleFunc("GET /show-all-users", view.ShowAllUsers(args, db)) mux.HandleFunc("GET /this-issue", view.ShowCurrentArticles(args, db)) mux.HandleFunc("GET /unpublished-articles", view.ShowUnpublishedArticles(args, db)) mux.HandleFunc("GET /write-article", view.WriteArticle(args, db)) @@ -74,7 +76,8 @@ func main() { mux.HandleFunc("POST /login", view.Login(args, db, store)) mux.HandleFunc("POST /resubmit-article/{id}", view.ResubmitArticle(args, db, store)) mux.HandleFunc("POST /submit-article", view.SubmitArticle(args, db, store)) - mux.HandleFunc("POST /update-user", view.UpdateUser(args, db, store)) + mux.HandleFunc("POST /update-self", view.UpdateSelf(args, db, store)) + mux.HandleFunc("POST /update-user/{id}", view.UpdateUser(args, db, store)) mux.HandleFunc("POST /upload-image", view.UploadImage(args)) log.Fatalln(http.ListenAndServe(args.Port, mux)) diff --git a/cmd/model/users.go b/cmd/model/users.go index 1bdca5c..b2ae03f 100644 --- a/cmd/model/users.go +++ b/cmd/model/users.go @@ -145,7 +145,7 @@ func (db *DB) GetUser(id int64) (*User, error) { return user, nil } -func (db *DB) UpdateUserAttributes(id int64, user, first, last, oldPass, newPass, newPass2 string) error { +func (db *DB) UpdateOwnAttributes(id int64, user, first, last, oldPass, newPass, newPass2 string) error { passwordEmpty := true if len(newPass) > 0 || len(newPass2) > 0 { if newPass != newPass2 { @@ -268,3 +268,105 @@ func (db *DB) AddFirstUser(u *User, pass string) (int64, error) { } return 0, fmt.Errorf("error: %v unsuccessful retries for DB operation, aborting", TxMaxRetries) } + +func (db *DB) GetAllUsers() ([]*User, error) { + query := "SELECT id, username, first_name, last_name, role FROM users" + + rows, err := db.Query(query) + if err != nil { + return nil, fmt.Errorf("error getting all users from DB: %v", err) + } + + users := make([]*User, 0) + for rows.Next() { + user := new(User) + if err = rows.Scan(&user.ID, &user.UserName, &user.FirstName, + &user.LastName, &user.Role); err != nil { + return nil, fmt.Errorf("error getting user info: %v", err) + } + users = append(users, user) + } + + return users, nil +} + +func (tx *Tx) SetPassword(id int64, newPass string) error { + hashedPass, err := bcrypt.GenerateFromPassword([]byte(newPass), bcrypt.DefaultCost) + if err != nil { + if rollbackErr := tx.Rollback(); rollbackErr != nil { + log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr) + } + return fmt.Errorf("error creating password hash: %v", err) + } + + setQuery := ` + UPDATE users + SET password = ? + WHERE id = ? + ` + if _, err = tx.Exec(setQuery, string(hashedPass), id); err != nil { + if rollbackErr := tx.Rollback(); rollbackErr != nil { + log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr) + } + return fmt.Errorf("error updating password in DB: %v", err) + } + + return nil +} + +func (db *DB) UpdateUserAttributes(id int64, user, first, last, newPass, newPass2 string, role int) error { + passwordEmpty := true + if len(newPass) > 0 || len(newPass2) > 0 { + if newPass != newPass2 { + return fmt.Errorf("error: passwords do not match") + } + passwordEmpty = false + } + + tx := new(Tx) + var err error + + for i := 0; i < TxMaxRetries; i++ { + err := func() error { + tx.Tx, err = db.Begin() + if err != nil { + return fmt.Errorf("error starting transaction: %v", err) + } + + if !passwordEmpty { + if err = tx.SetPassword(id, newPass); err != nil { + if rollbackErr := tx.Rollback(); rollbackErr != nil { + log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr) + } + return fmt.Errorf("error changing password: %v", err) + } + } + + if err = tx.UpdateAttributes( + &Attribute{Table: "users", ID: id, AttName: "username", Value: user}, + &Attribute{Table: "users", ID: id, AttName: "first_name", Value: first}, + &Attribute{Table: "users", ID: id, AttName: "last_name", Value: last}, + &Attribute{Table: "users", ID: id, AttName: "role", Value: role}, + ); err != nil { + if rollbackErr := tx.Rollback(); rollbackErr != nil { + log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr) + } + return fmt.Errorf("error updating attributes in DB: %v", err) + } + + if err = tx.Commit(); err != nil { + return fmt.Errorf("error committing transaction: %v", err) + } + + return nil + }() + if err == nil { + return nil + } + + log.Println(err) + wait(i) + } + + return fmt.Errorf("error: %v unsuccessful retries for DB operation, aborting", TxMaxRetries) +} diff --git a/cmd/view/users.go b/cmd/view/users.go index 3281e12..c67e96b 100644 --- a/cmd/view/users.go +++ b/cmd/view/users.go @@ -100,7 +100,7 @@ func AddUser(c *control.CliArgs, db *model.DB, s *control.CookieStore) http.Hand } } -func EditUser(c *control.CliArgs, db *model.DB, s *control.CookieStore) http.HandlerFunc { +func EditSelf(c *control.CliArgs, db *model.DB, s *control.CookieStore) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { session, err := s.Get(r, "cookie") if err != nil { @@ -116,12 +116,12 @@ func EditUser(c *control.CliArgs, db *model.DB, s *control.CookieStore) http.Han return } - tmpl, err := template.ParseFiles(c.WebDir + "/templates/edit-user.html") + tmpl, err := template.ParseFiles(c.WebDir + "/templates/edit-self.html") template.Must(tmpl, err).ExecuteTemplate(w, "page-content", user) } } -func UpdateUser(c *control.CliArgs, db *model.DB, s *control.CookieStore) http.HandlerFunc { +func UpdateSelf(c *control.CliArgs, db *model.DB, s *control.CookieStore) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { session, err := s.Get(r, "cookie") if err != nil { @@ -172,7 +172,7 @@ func UpdateUser(c *control.CliArgs, db *model.DB, s *control.CookieStore) http.H } } - if err = db.UpdateUserAttributes( + if err = db.UpdateOwnAttributes( userData.ID, userData.UserName, userData.FirstName, @@ -265,3 +265,115 @@ func AddFirstUser(c *control.CliArgs, db *model.DB, s *control.CookieStore) http template.Must(tmpl, err).ExecuteTemplate(w, "page-content", 0) } } + +func ShowAllUsers(c *control.CliArgs, db *model.DB) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + users, err := db.GetAllUsers() + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + tmpl, err := template.ParseFiles(c.WebDir + "/templates/show-all-users.html") + template.Must(tmpl, err).ExecuteTemplate(w, "page-content", users) + } +} + +func EditUser(c *control.CliArgs, db *model.DB) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + id, err := strconv.ParseInt(r.PathValue("id"), 10, 64) + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + user, err := db.GetUser(id) + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + tmpl, err := template.ParseFiles(c.WebDir + "/templates/edit-user.html") + template.Must(tmpl, err).ExecuteTemplate(w, "page-content", user) + } +} + +func UpdateUser(c *control.CliArgs, db *model.DB, s *control.CookieStore) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + id, err := strconv.ParseInt(r.PathValue("id"), 10, 64) + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + role, err := strconv.Atoi(r.PostFormValue("role")) + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + userData := UserData{ + User: &model.User{ + ID: id, + UserName: r.PostFormValue("username"), + FirstName: r.PostFormValue("first-name"), + LastName: r.PostFormValue("last-name"), + Role: role, + }, + } + newPass := r.PostFormValue("password") + newPass2 := r.PostFormValue("password2") + + if len(userData.UserName) == 0 || len(userData.FirstName) == 0 || + len(userData.LastName) == 0 { + userData.Msg = "Alle Felder mit * müssen ausgefüllt sein." + tmpl, err := template.ParseFiles(c.WebDir + "/templates/edit-user.html") + tmpl = template.Must(tmpl, err) + tmpl.ExecuteTemplate(w, "page-content", userData.Msg) + return + } + + userString, stringLen, ok := checkUserStrings(userData.User) + if !ok { + userData.Msg = fmt.Sprint(userString, " ist zu lang. Maximal ", + stringLen, " Zeichen erlaubt.") + tmpl, err := template.ParseFiles(c.WebDir + "/templates/edit-user.html") + tmpl = template.Must(tmpl, err) + tmpl.ExecuteTemplate(w, "page-content", userData) + return + } + + if id, ok := db.GetID(userData.UserName); ok { + if id != userData.ID { + userData.Msg = "Benutzername bereits vergeben." + userData.UserName = "" + tmpl, err := template.ParseFiles(c.WebDir + "/templates/edit-user.html") + tmpl = template.Must(tmpl, err) + tmpl.ExecuteTemplate(w, "page-content", userData) + return + } + } + + if err = db.UpdateUserAttributes( + userData.ID, + userData.UserName, + userData.FirstName, + userData.LastName, + newPass, + newPass2, + userData.Role); err != nil { + userData.Msg = "Aktualisierung der Benutzerdaten fehlgeschlagen." + tmpl, err := template.ParseFiles(c.WebDir + "/templates/edit-user.html") + template.Must(tmpl, err).ExecuteTemplate(w, "page-content", userData) + } + + tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html") + tmpl = template.Must(tmpl, err) + tmpl.ExecuteTemplate(w, "page-content", 0) + } +} diff --git a/web/templates/edit-self.html b/web/templates/edit-self.html new file mode 100644 index 0000000..f9beb0f --- /dev/null +++ b/web/templates/edit-self.html @@ -0,0 +1,43 @@ +{{define "page-content"}} +