diff --git a/cmd/model/users.go b/cmd/model/users.go index e407db7..1bdca5c 100644 --- a/cmd/model/users.go +++ b/cmd/model/users.go @@ -1,6 +1,8 @@ package model import ( + "context" + "database/sql" "fmt" "log" @@ -198,3 +200,71 @@ func (db *DB) UpdateUserAttributes(id int64, user, first, last, oldPass, newPass return fmt.Errorf("error: %v unsuccessful retries for DB operation, aborting", TxMaxRetries) } + +func (db *DB) AddFirstUser(u *User, pass string) (int64, error) { + var numUsers int64 + txOptions := &sql.TxOptions{Isolation: sql.LevelSerializable} + selectQuery := "SELECT COUNT(*) FROM users" + insertQuery := ` + INSERT INTO users (username, password, first_name, last_name, role) + VALUES (?, ?, ?, ?, ?) + ` + + for i := 0; i < TxMaxRetries; i++ { + id, err := func() (int64, error) { + tx, err := db.BeginTx(context.Background(), txOptions) + if err != nil { + return 0, fmt.Errorf("error starting transaction: %v", err) + } + + if err := tx.QueryRow(selectQuery).Scan(&numUsers); err != nil { + if rollbackErr := tx.Rollback(); rollbackErr != nil { + log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr) + } + return 0, fmt.Errorf("error getting ID of %v: %v", u.UserName, err) + } + if numUsers != 0 { + if err = tx.Commit(); err != nil { + return 0, fmt.Errorf("error committing transaction: %v", err) + } + return 2, nil + } + + hashedPass, err := bcrypt.GenerateFromPassword([]byte(pass), bcrypt.DefaultCost) + if err != nil { + if rollbackErr := tx.Rollback(); rollbackErr != nil { + log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr) + } + return 0, fmt.Errorf("error creating password hash: %v", err) + } + + result, err := tx.Exec(insertQuery, u.UserName, string(hashedPass), u.FirstName, u.LastName, u.Role) + if err != nil { + if rollbackErr := tx.Rollback(); rollbackErr != nil { + log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr) + } + return 0, fmt.Errorf("error inserting new user %v into DB: %v", u.UserName, err) + } + + id, err := result.LastInsertId() + if err != nil { + if rollbackErr := tx.Rollback(); rollbackErr != nil { + log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr) + } + return 0, fmt.Errorf("error inserting user into DB: %v", err) + } + + if err = tx.Commit(); err != nil { + return 0, fmt.Errorf("error committing transaction: %v", err) + } + return id, nil + }() + if err == nil { + return id, nil + } + + log.Println(err) + wait(i) + } + return 0, fmt.Errorf("error: %v unsuccessful retries for DB operation, aborting", TxMaxRetries) +} diff --git a/cmd/view/users.go b/cmd/view/users.go index bf5b8bf..3281e12 100644 --- a/cmd/view/users.go +++ b/cmd/view/users.go @@ -236,12 +236,18 @@ func AddFirstUser(c *control.CliArgs, db *model.DB, s *control.CookieStore) http return } - htmlData.ID, err = db.AddUser(htmlData.User, pass) + htmlData.ID, err = db.AddFirstUser(htmlData.User, pass) if err != nil { log.Println(err) http.Error(w, err.Error(), http.StatusInternalServerError) return } + if htmlData.ID > 1 { + errString := "error: there is already a first user" + log.Println(errString) + http.Error(w, errString, http.StatusInternalServerError) + return + } if err := saveSession(w, r, s, htmlData.User); err != nil { log.Println(err) diff --git a/web/templates/first-user.html b/web/templates/first-user.html index be50608..8e43d44 100644 --- a/web/templates/first-user.html +++ b/web/templates/first-user.html @@ -1,5 +1,6 @@ {{define "page-content"}} -

Neuer Benutzer

+

Erster Benutzer (Administrator)

+