package main import ( "fmt" "html/template" "log" "net/http" "os" "strconv" "time" "github.com/gorilla/mux" "golang.org/x/crypto/argon2" ) type User struct { ID int Name string PasswordHash []byte } ////////////////////////////////////////////////////////////////////////////// // GENERAL PURPOSE func UserRegister(username string, passwordHash []byte) (int, error) { id, err := globalState.InsertUser(User{Name: username, PasswordHash: passwordHash}) if err != nil { return 0, err } else { return id, nil } } // UserCheckPasswordHash returns a boolean that is true if the users password // is correct and false if the users password is false and thus doesn't match // the one stored in the database func UserCheckPasswordHash(username string, passwordHash []byte) bool { return globalState.CheckUserHash(username, passwordHash) } // UserUpdatePasswordHash does exactly that func UserUpdatePasswordHash(orig_username string, passwordHash []byte) error { return globalState.UpdateUserPasswordHash(orig_username, passwordHash) } func UserUpdateUsername(id int, new_username string) error { return globalState.UpdateUserUsername(id, new_username) } func UserLinkBot(username string, botid int) error { return globalState.LinkUserBot(username, botid) } func UserGetBotsUsingUsername(username string) ([]Bot, error) { return globalState.GetUserBotsUsername(username) } func UserGetBotsUsingUserID(userid int) ([]Bot, error) { return globalState.GetUserBotsId(userid) } func UserGetUserFromID(userid int) (User, error) { return globalState.GetUserFromId(userid) } func UserGetUserFromUsername(username string) (User, error) { return globalState.GetUserFromUsername(username) } func UserGetAll() ([]User, error) { return globalState.GetAllUsers() } ////////////////////////////////////////////////////////////////////////////// // DATABASE func (s *State) InsertUser(user User) (int, error) { res, err := s.db.Exec("INSERT INTO users VALUES(NULL,?,?,?);", time.Now(), user.Name, user.PasswordHash) if err != nil { return 0, err } var id int64 if id, err = res.LastInsertId(); err != nil { return 0, err } return int(id), nil } // returns true if the password matches func (s *State) CheckUserHash(username string, passwordHash []byte) bool { var created time.Time err := s.db.QueryRow("SELECT created_at FROM users WHERE name=? AND passwordHash=?", username, passwordHash).Scan(&created) switch { case err != nil: return false default: return true } } func (s *State) UpdateUserPasswordHash(username string, passwordHash []byte) error { _, err := s.db.Exec("UPDATE users SET passwordHash=? WHERE name=?", passwordHash, username) if err != nil { return err } else { return nil } } func (s *State) UpdateUserUsername(id int, new_username string) error { _, err := s.db.Exec("UPDATE users SET name=? WHERE id=?", new_username, id) if err != nil { return err } else { return nil } } // Links the given bot to the given user in the user_bot_rel table func (s *State) LinkUserBot(username string, botid int) error { _, err := s.db.Exec("INSERT INTO user_bot_rel VALUES ((SELECT id FROM users WHERE name=?), ?)", username, botid) if err != nil { return err } else { return nil } } // Links the given bot to the given user in the user_bot_rel table func (s *State) GetUserFromId(id int) (User, error) { var user_id int var username string err := s.db.QueryRow("SELECT id, name FROM users WHERE id=?", id).Scan(&user_id, &username) if err != nil { return User{}, err } else { return User{user_id, username, nil}, nil } } func (s *State) GetUserFromUsername(username string) (User, error) { var id int var name string err := s.db.QueryRow("SELECT id, name FROM users WHERE name=?", username).Scan(&id, &name) if err != nil { return User{}, err } else { return User{id, name, nil}, nil } } // Returns the bots belonging to the given user // TODO(emile): Also fetch the bits and the archs for displaying in the single battle page. In order to do so, join in both those tables func (s *State) GetUserBotsUsername(username string) ([]Bot, error) { rows, err := s.db.Query("SELECT id, name, source FROM bots b LEFT JOIN user_bot_rel ub ON ub.bot_id = b.id WHERE ub.user_id=(SELECT id FROM users WHERE name=?)", username) defer rows.Close() if err != nil { return nil, err } var bots []Bot for rows.Next() { var bot Bot if err := rows.Scan(&bot.ID, &bot.Name, &bot.Source); err != nil { return bots, err } bots = append(bots, bot) } if err = rows.Err(); err != nil { return bots, err } return bots, nil } // Returns the bots belonging to the given user func (s *State) GetUserBotsId(id int) ([]Bot, error) { rows, err := s.db.Query("SELECT id, name, source FROM bots b LEFT JOIN user_bot_rel ub ON ub.bot_id = b.id WHERE ub.user_id=?", id) defer rows.Close() if err != nil { return nil, err } var bots []Bot for rows.Next() { var bot Bot if err := rows.Scan(&bot.ID, &bot.Name, &bot.Source); err != nil { return bots, err } bots = append(bots, bot) } if err = rows.Err(); err != nil { return bots, err } return bots, nil } // Returns the bots belonging to the given user func (s *State) GetAllUsers() ([]User, error) { rows, err := s.db.Query("SELECT id, name FROM users") defer rows.Close() if err != nil { return nil, err } var users []User for rows.Next() { var user User if err := rows.Scan(&user.ID, &user.Name); err != nil { return users, err } users = append(users, user) } if err = rows.Err(); err != nil { return users, err } return users, nil } ////////////////////////////////////////////////////////////////////////////// // HTTP func loginHandler(w http.ResponseWriter, r *http.Request) { switch r.Method { case "GET": // define data data := map[string]interface{}{} data["version"] = os.Getenv("VERSION") data["pagelink1"] = Link{"login", "/login"} data["pagelink1options"] = []Link{ {Name: "register", Target: "/register"}, } data["pagelinkauth"] = []Link{ {Name: "register/", Target: "/register"}, } // session foo session, _ := globalState.sessions.Get(r, "session") username := session.Values["username"] // get the user if username != nil { log.Printf("[d] Getting the user %s\n", username.(string)) user, err := UserGetUserFromUsername(username.(string)) if user.Name == "" { } else if err != nil { log.Println(err) msg := "Error: could not get the user for given username" http.Redirect(w, r, fmt.Sprintf("/login?res=%s", msg), http.StatusSeeOther) return } else { data["user"] = user } } // display errors passed via query parameters queryres := r.URL.Query().Get("res") if queryres != "" { data["res"] = queryres } // get the template t, err := template.ParseGlob(fmt.Sprintf("%s/*.html", templatesPath)) if err != nil { log.Printf("Error reading the template Path: %s/*.html", templatesPath) w.WriteHeader(http.StatusInternalServerError) w.Write([]byte("500 - Error reading template file")) http.Error(w, err.Error(), http.StatusInternalServerError) return } // exec! t.ExecuteTemplate(w, "login", data) case "POST": // parse the post parameters r.ParseForm() username := r.Form.Get("username") password := r.Form.Get("password") // if we've got a password, hash it and compare it with the stored one if password != "" { passwordHash := argon2.IDKey([]byte(password), []byte(os.Getenv("SALT")), 1, 64*1024, 4, 32) // check if it's valid valid := UserCheckPasswordHash(username, passwordHash) if valid { // if it's valid, we set a session for the user session, _ := globalState.sessions.Get(r, "session") session.Values["username"] = username err := session.Save(r, w) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } http.Redirect(w, r, "/", http.StatusSeeOther) return } else { // invalid password http.Redirect(w, r, "/login?err=Invalid+Password", http.StatusSeeOther) return } } else { // empty password http.Redirect(w, r, "/login?err=Empty+Password", http.StatusSeeOther) return } default: http.Redirect(w, r, "/", http.StatusMethodNotAllowed) } } func registerHandler(w http.ResponseWriter, r *http.Request) { switch r.Method { case "GET": // define data data := map[string]interface{}{} data["version"] = os.Getenv("VERSION") // get the session session, _ := globalState.sessions.Get(r, "session") username := session.Values["username"] data["pagelink1"] = Link{"register", "/register"} data["pagelink1options"] = []Link{ {Name: "login", Target: "/login"}, } data["pagelinkauth"] = []Link{ {Name: "login/", Target: "/login"}, } if username != nil { data["logged_in"] = true } // get the template t, err := template.ParseGlob(fmt.Sprintf("%s/*.html", templatesPath)) if err != nil { log.Printf("Error reading the template Path: %s/*.html", templatesPath) w.WriteHeader(http.StatusInternalServerError) w.Write([]byte("500 - Error reading template file")) http.Error(w, err.Error(), http.StatusInternalServerError) return } // exec! t.ExecuteTemplate(w, "register", data) case "POST": // parse the post parameters r.ParseForm() username := r.Form.Get("username") password1 := r.Form.Get("password1") password2 := r.Form.Get("password2") if len(username) >= 64 { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte("500 - Oi', Backend here! Please enter less than 64 chars!")) return } if len(password1) >= 256 { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte("500 - Oi', Backend here! Don't overdo with the length please!")) return } if password1 != password2 { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte("500 - Oi', Backend here! The passwords you entered don't match!")) return } // if we've got a password, hash it and store it and create a User if password1 != "" { passwordHash := argon2.IDKey([]byte(password1), []byte(os.Getenv("SALT")), 1, 64*1024, 4, 32) _, err := UserRegister(username, passwordHash) if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte("500 - We had problems inserting you into the DB")) return } http.Redirect(w, r, "/login", http.StatusSeeOther) return } default: http.Redirect(w, r, "/", http.StatusMethodNotAllowed) } } func logoutHandler(w http.ResponseWriter, r *http.Request) { switch r.Method { case "POST": session, _ := globalState.sessions.Get(r, "session") session.Options.MaxAge = -1 err := session.Save(r, w) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } http.Redirect(w, r, "/", http.StatusSeeOther) default: http.Redirect(w, r, "/", http.StatusMethodNotAllowed) } } func userHandler(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) id, err := strconv.Atoi(vars["id"]) if err != nil { http.Redirect(w, r, "/user", http.StatusSeeOther) } switch r.Method { case "GET": // define data data := map[string]interface{}{} data["version"] = os.Getenv("VERSION") data["pagelink1"] = Link{"user", "/user"} data["pagelink1options"] = []Link{ {Name: "bot", Target: "/bot"}, {Name: "battle", Target: "/battle"}, } // session foo session, _ := globalState.sessions.Get(r, "session") username := session.Values["username"].(string) // the the user making the request user, err := UserGetUserFromUsername(username) if err != nil { http.Redirect(w, r, "/login", http.StatusSeeOther) return } else { data["user"] = user } // get the target user using the provided id targetUser, err := UserGetUserFromID(id) if err != nil { data["err"] = "Could not find that user" } else { data["targetUser"] = targetUser } // define the breadcrumbs data["pagelink2"] = Link{targetUser.Name, fmt.Sprintf("/%s", targetUser.Name)} allUserNames, err := UserGetAll() var opts []Link for _, user := range allUserNames { opts = append(opts, Link{Name: user.Name, Target: fmt.Sprintf("/%d", user.ID)}) } data["pagelink2options"] = opts // get the bots for the given user bots, err := UserGetBotsUsingUserID(id) if err != nil { http.Redirect(w, r, "/user", http.StatusSeeOther) } else { data["bots"] = bots } // get the template t, err := template.ParseGlob(fmt.Sprintf("%s/*.html", templatesPath)) if err != nil { log.Printf("Error reading the template Path: %s/*.html", templatesPath) w.WriteHeader(http.StatusInternalServerError) w.Write([]byte("500 - Error reading template file")) http.Error(w, err.Error(), http.StatusInternalServerError) return } // exec! t.ExecuteTemplate(w, "user", data) default: http.Redirect(w, r, "/", http.StatusMethodNotAllowed) } } func usersHandler(w http.ResponseWriter, r *http.Request) { switch r.Method { case "GET": // define data data := map[string]interface{}{} data["version"] = os.Getenv("VERSION") data["pagelink1"] = Link{Name: "user", Target: "/user"} data["pagelink1options"] = []Link{ {Name: "bot", Target: "/bot"}, {Name: "battle", Target: "/battle"}, } // sessions session, _ := globalState.sessions.Get(r, "session") username := session.Values["username"].(string) // get the user user, err := UserGetUserFromUsername(username) if err != nil { http.Redirect(w, r, "/login", http.StatusSeeOther) return } else { data["user"] = user } // get all users users, err := UserGetAll() data["users"] = users // get the template t, err := template.ParseGlob(fmt.Sprintf("%s/*.html", templatesPath)) if err != nil { log.Printf("Error reading the template Path: %s/*.html", templatesPath) w.WriteHeader(http.StatusInternalServerError) w.Write([]byte("500 - Error reading template file")) http.Error(w, err.Error(), http.StatusInternalServerError) return } // exec! t.ExecuteTemplate(w, "users", data) default: http.Redirect(w, r, "/", http.StatusMethodNotAllowed) } } func profileHandler(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) id, err := strconv.Atoi(vars["id"]) if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte("500 - Error reading the profile id")) http.Error(w, err.Error(), http.StatusInternalServerError) return } switch r.Method { case "GET": // define data data := map[string]interface{}{} data["version"] = os.Getenv("VERSION") data["pagelink1"] = Link{"user", "/user"} data["pagelink1options"] = []Link{ {Name: "bot", Target: "/bot"}, {Name: "battle", Target: "/battle"}, } session, _ := globalState.sessions.Get(r, "session") username := session.Values["username"].(string) target_user, err := UserGetUserFromID(id) if err != nil { // w.WriteHeader(http.StatusUnauthorized) // w.Write([]byte("500 - Error reading template file")) // http.Error(w, err.Error(), http.StatusInternalServerError) data["err"] = "Error getting with the given id" } if username != target_user.Name { // w.WriteHeader(http.StatusInternalServerError) // w.Write([]byte("500 - Error reading template file")) // http.Error(w, err.Error(), http.StatusInternalServerError) data["err"] = "You aren't allowed to edit any user except yourself" } editing_user, err := UserGetUserFromUsername(username) if err != nil { data["err"] = "Coulnd't get a user for that id" } data["user"] = editing_user data["target_user"] = target_user data["pagelink2"] = Link{target_user.Name, fmt.Sprintf("/%d", id)} allUserNames, err := UserGetAll() var opts []Link for _, user := range allUserNames { opts = append(opts, Link{Name: user.Name, Target: fmt.Sprintf("/%d", user.ID)}) } data["pagelink2options"] = opts data["pagelink3"] = Link{"profile", "/profile"} if username != "" { data["username"] = username } // get the template t, err := template.ParseGlob(fmt.Sprintf("%s/*.html", templatesPath)) if err != nil { log.Printf("Error reading the template Path: %s/*.html", templatesPath) w.WriteHeader(http.StatusInternalServerError) w.Write([]byte("500 - Error reading template file")) http.Error(w, err.Error(), http.StatusInternalServerError) return } // exec! t.ExecuteTemplate(w, "profile", data) case "POST": session, _ := globalState.sessions.Get(r, "session") orig_username := session.Values["username"].(string) // parse the post parameters r.ParseForm() new_username := r.Form.Get("username") password1 := r.Form.Get("password1") password2 := r.Form.Get("password2") if len(new_username) >= 64 { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte("500 - Oi', Backend here! Please enter less than 64 chars!")) return } if len(password1) >= 256 { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte("500 - Oi', Backend here! Don't overdo with the length please!")) return } if password1 != password2 { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte("500 - Oi', Backend here! The passwords you entered don't match!")) return } // first update the password, as they might have also changed their // username if password1 != "" { passwordHash := argon2.IDKey([]byte(password1), []byte(os.Getenv("SALT")), 1, 64*1024, 4, 32) err := UserUpdatePasswordHash(orig_username, passwordHash) if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte("500 - We had problems inserting your new pw into the DB")) return } } if new_username != "" { err := UserUpdateUsername(id, new_username) if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte("500 - We had problems inserting your new uname into the DB")) return } // after changing the username, we also have to update the username // stored in the session session.Values["username"] = new_username err = session.Save(r, w) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } } http.Redirect(w, r, fmt.Sprintf("/user/%d/profile", id), http.StatusSeeOther) return default: http.Redirect(w, r, "/", http.StatusMethodNotAllowed) } }