From 60dd5df8bcec580961f8e4f222ed09bcfc3cb8b0 Mon Sep 17 00:00:00 2001 From: rockisch Date: Sun, 29 Oct 2023 21:21:11 -0300 Subject: [PATCH 1/2] Update signv2server to return more data --- server/signv2server/dbutils.go | 104 ++++++++++++++-------------- server/signv2server/endpoints.go | 112 ++++++++++++++++++++++--------- 2 files changed, 135 insertions(+), 81 deletions(-) diff --git a/server/signv2server/dbutils.go b/server/signv2server/dbutils.go index 3c8494c95..fb9711da9 100644 --- a/server/signv2server/dbutils.go +++ b/server/signv2server/dbutils.go @@ -10,26 +10,29 @@ import ( "golang.org/x/crypto/bcrypt" ) -func (s *Server) createNewUser(ctx context.Context, username string, password string) (int, error) { +func (s *Server) createNewUser(ctx context.Context, username string, password string) (uint32, uint32, error) { // Create salted hash of user password passwordHash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) if err != nil { - return 0, err + return 0, 0, err } - var id int + var ( + id uint32 + rights uint32 + ) err = s.db.QueryRowContext( ctx, ` INSERT INTO users (username, password, return_expires) VALUES ($1, $2, $3) - RETURNING id + RETURNING id, rights `, username, string(passwordHash), time.Now().Add(time.Hour*24*30), - ).Scan(&id) - return id, err + ).Scan(&id, &rights) + return id, rights, err } -func (s *Server) createLoginToken(ctx context.Context, uid int) (string, error) { +func (s *Server) createLoginToken(ctx context.Context, uid uint32) (string, error) { loginToken := token.Generate(16) _, err := s.db.ExecContext(ctx, "INSERT INTO sign_sessions (user_id, token) VALUES ($1, $2)", uid, loginToken) if err != nil { @@ -38,8 +41,8 @@ func (s *Server) createLoginToken(ctx context.Context, uid int) (string, error) return loginToken, nil } -func (s *Server) userIDFromToken(ctx context.Context, token string) (int, error) { - var userID int +func (s *Server) userIDFromToken(ctx context.Context, token string) (uint32, error) { + var userID uint32 err := s.db.QueryRowContext(ctx, "SELECT user_id FROM sign_sessions WHERE token = $1", token).Scan(&userID) if err == sql.ErrNoRows { return 0, fmt.Errorf("invalid login token") @@ -49,65 +52,47 @@ func (s *Server) userIDFromToken(ctx context.Context, token string) (int, error) return userID, nil } -func (s *Server) createCharacter(ctx context.Context, userID int) (int, error) { - var charID int - err := s.db.QueryRowContext(ctx, - "SELECT id FROM characters WHERE is_new_character = true AND user_id = $1", +func (s *Server) createCharacter(ctx context.Context, userID uint32) (Character, error) { + var character Character + err := s.db.GetContext(ctx, &character, + "SELECT id, name, is_female, weapon_type, hrp, gr, last_login FROM characters WHERE is_new_character = true AND user_id = $1 LIMIT 1", userID, - ).Scan(&charID) + ) if err == sql.ErrNoRows { - err = s.db.QueryRowContext(ctx, ` + var count int + s.db.QueryRowContext(ctx, "SELECT COUNT(*) FROM characters WHERE user_id = $1", userID).Scan(&count) + if count >= 16 { + return character, fmt.Errorf("cannot have more than 16 characters") + } + err = s.db.GetContext(ctx, &character, ` INSERT INTO characters ( user_id, is_female, is_new_character, name, unk_desc_string, hrp, gr, weapon_type, last_login ) VALUES ($1, false, true, '', '', 0, 0, 0, $2) - RETURNING id`, + RETURNING id, name, is_female, weapon_type, hrp, gr, last_login`, userID, uint32(time.Now().Unix()), - ).Scan(&charID) + ) } - return charID, err + return character, err } -func (s *Server) deleteCharacter(ctx context.Context, userID int, charID int) error { - tx, err := s.db.BeginTx(ctx, nil) +func (s *Server) deleteCharacter(ctx context.Context, userID uint32, charID uint32) error { + var isNew bool + err := s.db.QueryRow("SELECT is_new_character FROM characters WHERE id = $1", charID).Scan(&isNew) if err != nil { return err } - defer tx.Rollback() - - _, err = tx.ExecContext( - ctx, ` - DELETE FROM login_boost_state - WHERE char_id = $1`, - charID, - ) - if err != nil { - return err + if isNew { + _, err = s.db.Exec("DELETE FROM characters WHERE id = $1", charID) + } else { + _, err = s.db.Exec("UPDATE characters SET deleted = true WHERE id = $1", charID) } - _, err = tx.ExecContext( - ctx, ` - DELETE FROM guild_characters - WHERE character_id = $1`, - charID, - ) - if err != nil { - return err - } - _, err = tx.ExecContext( - ctx, ` - DELETE FROM characters - WHERE user_id = $1 AND id = $2`, - userID, charID, - ) - if err != nil { - return err - } - return tx.Commit() + return err } -func (s *Server) getCharactersForUser(ctx context.Context, uid int) ([]Character, error) { - characters := make([]Character, 0) +func (s *Server) getCharactersForUser(ctx context.Context, uid uint32) ([]Character, error) { + var characters []Character err := s.db.SelectContext( ctx, &characters, ` SELECT id, name, is_female, weapon_type, hrp, gr, last_login @@ -120,3 +105,20 @@ func (s *Server) getCharactersForUser(ctx context.Context, uid int) ([]Character } return characters, nil } + +func (s *Server) getReturnExpiry(uid uint32) time.Time { + var returnExpiry, lastLogin time.Time + s.db.Get(&lastLogin, "SELECT COALESCE(last_login, now()) FROM users WHERE id=$1", uid) + if time.Now().Add((time.Hour * 24) * -90).After(lastLogin) { + returnExpiry = time.Now().Add(time.Hour * 24 * 30) + s.db.Exec("UPDATE users SET return_expires=$1 WHERE id=$2", returnExpiry, uid) + } else { + err := s.db.Get(&returnExpiry, "SELECT return_expires FROM users WHERE id=$1", uid) + if err != nil { + returnExpiry = time.Now() + s.db.Exec("UPDATE users SET return_expires=$1 WHERE id=$2", returnExpiry, uid) + } + } + s.db.Exec("UPDATE users SET last_login=$1 WHERE id=$2", time.Now(), uid) + return returnExpiry +} diff --git a/server/signv2server/endpoints.go b/server/signv2server/endpoints.go index eeb7442de..b6b888b54 100644 --- a/server/signv2server/endpoints.go +++ b/server/signv2server/endpoints.go @@ -4,7 +4,9 @@ import ( "database/sql" "encoding/json" "errors" + "erupe-ce/server/channelserver" "net/http" + "strings" "time" "github.com/lib/pq" @@ -18,21 +20,78 @@ type LauncherMessage struct { Link string `json:"link"` } +type LauncherResponse struct { + Important []LauncherMessage `json:"important"` + Normal []LauncherMessage `json:"normal"` +} + +type User struct { + Token string `json:"token"` + Rights uint32 `json:"rights"` +} + type Character struct { - ID int `json:"id"` + Id uint32 `json:"id"` Name string `json:"name"` IsFemale bool `json:"isFemale" db:"is_female"` - Weapon int `json:"weapon" db:"weapon_type"` - HR int `json:"hr" db:"hrp"` - GR int `json:"gr"` + Weapon uint32 `json:"weapon" db:"weapon_type"` + Hr uint32 `json:"hr" db:"hrp"` + Gr uint32 `json:"gr"` LastLogin int64 `json:"lastLogin" db:"last_login"` } -func (s *Server) Launcher(w http.ResponseWriter, r *http.Request) { - var respData struct { - Important []LauncherMessage `json:"important"` - Normal []LauncherMessage `json:"normal"` +type MezFes struct { + Id uint32 `json:"id"` + Start uint32 `json:"start"` + End uint32 `json:"end"` + SoloTickets uint32 `json:"soloTickets"` + GroupTickets uint32 `json:"groupTickets"` + Stalls []uint32 `json:"stalls"` +} + +type AuthData struct { + CurrentTs uint32 `json:"currentTs"` + ExpiryTs uint32 `json:"expiryTs"` + EntranceCount uint32 `json:"entranceCount"` + Notifications []string `json:"notifications"` + User User `json:"user"` + Characters []Character `json:"characters"` + MezFes *MezFes `json:"mezFes"` +} + +func (s *Server) newAuthData(userID uint32, userRights uint32, userToken string, characters []Character) AuthData { + resp := AuthData{ + CurrentTs: uint32(channelserver.TimeAdjusted().Unix()), + ExpiryTs: uint32(s.getReturnExpiry(userID).Unix()), + EntranceCount: 1, + User: User{ + Rights: userRights, + Token: userToken, + }, + Characters: characters, } + if s.erupeConfig.DevModeOptions.MezFesEvent { + stalls := []uint32{10, 3, 6, 9, 4, 8, 5, 7} + if s.erupeConfig.DevModeOptions.MezFesAlt { + stalls[4] = 2 + } + resp.MezFes = &MezFes{ + Id: uint32(channelserver.TimeWeekStart().Unix()), + Start: uint32(channelserver.TimeWeekStart().Unix()), + End: uint32(channelserver.TimeWeekNext().Unix()), + SoloTickets: s.erupeConfig.GameplayOptions.MezfesSoloTickets, + GroupTickets: s.erupeConfig.GameplayOptions.MezfesGroupTickets, + Stalls: stalls, + } + } + if !s.erupeConfig.HideLoginNotice { + resp.Notifications = append(resp.Notifications, strings.Join(s.erupeConfig.LoginNotices[:], "")) + } + return resp +} + +func (s *Server) Launcher(w http.ResponseWriter, r *http.Request) { + var respData LauncherResponse respData.Important = []LauncherMessage{ { Message: "Server Update 9 Released!", @@ -75,10 +134,11 @@ func (s *Server) Login(w http.ResponseWriter, r *http.Request) { return } var ( - userID int - password string + userID uint32 + userRights uint32 + password string ) - err := s.db.QueryRow("SELECT id, password FROM users WHERE username = $1", reqData.Username).Scan(&userID, &password) + err := s.db.QueryRow("SELECT id, password, rights FROM users WHERE username = $1", reqData.Username).Scan(&userID, &password, &userRights) if err == sql.ErrNoRows { w.WriteHeader(400) w.Write([]byte("Username does not exist")) @@ -94,22 +154,19 @@ func (s *Server) Login(w http.ResponseWriter, r *http.Request) { return } - var respData struct { - Token string `json:"token"` - Characters []Character `json:"characters"` - } - respData.Token, err = s.createLoginToken(ctx, userID) + userToken, err := s.createLoginToken(ctx, userID) if err != nil { s.logger.Warn("Error registering login token", zap.Error(err)) w.WriteHeader(500) return } - respData.Characters, err = s.getCharactersForUser(ctx, userID) + characters, err := s.getCharactersForUser(ctx, userID) if err != nil { s.logger.Warn("Error getting characters from DB", zap.Error(err)) w.WriteHeader(500) return } + respData := s.newAuthData(userID, userRights, userToken, characters) w.WriteHeader(200) w.Header().Add("Content-Type", "application/json") json.NewEncoder(w).Encode(respData) @@ -128,7 +185,7 @@ func (s *Server) Register(w http.ResponseWriter, r *http.Request) { return } s.logger.Info("Creating account", zap.String("username", reqData.Username)) - userID, err := s.createNewUser(ctx, reqData.Username, reqData.Password) + userID, userRights, err := s.createNewUser(ctx, reqData.Username, reqData.Password) if err != nil { var pqErr *pq.Error if errors.As(err, &pqErr) && pqErr.Constraint == "users_username_key" { @@ -141,15 +198,13 @@ func (s *Server) Register(w http.ResponseWriter, r *http.Request) { return } - var respData struct { - Token string `json:"token"` - } - respData.Token, err = s.createLoginToken(ctx, userID) + userToken, err := s.createLoginToken(ctx, userID) if err != nil { s.logger.Error("Error registering login token", zap.Error(err)) w.WriteHeader(500) return } + respData := s.newAuthData(userID, userRights, userToken, []Character{}) json.NewEncoder(w).Encode(respData) } @@ -165,28 +220,25 @@ func (s *Server) CreateCharacter(w http.ResponseWriter, r *http.Request) { return } - var respData struct { - CharID int `json:"id"` - } userID, err := s.userIDFromToken(ctx, reqData.Token) if err != nil { w.WriteHeader(401) return } - respData.CharID, err = s.createCharacter(ctx, userID) + character, err := s.createCharacter(ctx, userID) if err != nil { s.logger.Error("Failed to create character", zap.Error(err), zap.String("token", reqData.Token)) w.WriteHeader(500) return } - json.NewEncoder(w).Encode(respData) + json.NewEncoder(w).Encode(character) } func (s *Server) DeleteCharacter(w http.ResponseWriter, r *http.Request) { ctx := r.Context() var reqData struct { Token string `json:"token"` - CharID int `json:"id"` + CharId uint32 `json:"charId"` } if err := json.NewDecoder(r.Body).Decode(&reqData); err != nil { s.logger.Error("JSON decode error", zap.Error(err)) @@ -199,8 +251,8 @@ func (s *Server) DeleteCharacter(w http.ResponseWriter, r *http.Request) { w.WriteHeader(401) return } - if err := s.deleteCharacter(ctx, userID, reqData.CharID); err != nil { - s.logger.Error("Failed to delete character", zap.Error(err), zap.String("token", reqData.Token), zap.Int("charID", reqData.CharID)) + if err := s.deleteCharacter(ctx, userID, reqData.CharId); err != nil { + s.logger.Error("Failed to delete character", zap.Error(err), zap.String("token", reqData.Token), zap.Uint32("charID", reqData.CharId)) w.WriteHeader(500) return } From 9d9cc0bf5dff927dfef4b9eaa47df8bc32a6f711 Mon Sep 17 00:00:00 2001 From: rockisch Date: Sun, 29 Oct 2023 21:24:51 -0300 Subject: [PATCH 2/2] Use Go's naming recommendations --- server/signv2server/endpoints.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/server/signv2server/endpoints.go b/server/signv2server/endpoints.go index b6b888b54..c087a70df 100644 --- a/server/signv2server/endpoints.go +++ b/server/signv2server/endpoints.go @@ -31,17 +31,17 @@ type User struct { } type Character struct { - Id uint32 `json:"id"` + ID uint32 `json:"id"` Name string `json:"name"` IsFemale bool `json:"isFemale" db:"is_female"` Weapon uint32 `json:"weapon" db:"weapon_type"` - Hr uint32 `json:"hr" db:"hrp"` - Gr uint32 `json:"gr"` + HR uint32 `json:"hr" db:"hrp"` + GR uint32 `json:"gr"` LastLogin int64 `json:"lastLogin" db:"last_login"` } type MezFes struct { - Id uint32 `json:"id"` + ID uint32 `json:"id"` Start uint32 `json:"start"` End uint32 `json:"end"` SoloTickets uint32 `json:"soloTickets"` @@ -50,8 +50,8 @@ type MezFes struct { } type AuthData struct { - CurrentTs uint32 `json:"currentTs"` - ExpiryTs uint32 `json:"expiryTs"` + CurrentTS uint32 `json:"currentTs"` + ExpiryTS uint32 `json:"expiryTs"` EntranceCount uint32 `json:"entranceCount"` Notifications []string `json:"notifications"` User User `json:"user"` @@ -61,8 +61,8 @@ type AuthData struct { func (s *Server) newAuthData(userID uint32, userRights uint32, userToken string, characters []Character) AuthData { resp := AuthData{ - CurrentTs: uint32(channelserver.TimeAdjusted().Unix()), - ExpiryTs: uint32(s.getReturnExpiry(userID).Unix()), + CurrentTS: uint32(channelserver.TimeAdjusted().Unix()), + ExpiryTS: uint32(s.getReturnExpiry(userID).Unix()), EntranceCount: 1, User: User{ Rights: userRights, @@ -76,7 +76,7 @@ func (s *Server) newAuthData(userID uint32, userRights uint32, userToken string, stalls[4] = 2 } resp.MezFes = &MezFes{ - Id: uint32(channelserver.TimeWeekStart().Unix()), + ID: uint32(channelserver.TimeWeekStart().Unix()), Start: uint32(channelserver.TimeWeekStart().Unix()), End: uint32(channelserver.TimeWeekNext().Unix()), SoloTickets: s.erupeConfig.GameplayOptions.MezfesSoloTickets, @@ -238,7 +238,7 @@ func (s *Server) DeleteCharacter(w http.ResponseWriter, r *http.Request) { ctx := r.Context() var reqData struct { Token string `json:"token"` - CharId uint32 `json:"charId"` + CharID uint32 `json:"charId"` } if err := json.NewDecoder(r.Body).Decode(&reqData); err != nil { s.logger.Error("JSON decode error", zap.Error(err)) @@ -251,8 +251,8 @@ func (s *Server) DeleteCharacter(w http.ResponseWriter, r *http.Request) { w.WriteHeader(401) return } - if err := s.deleteCharacter(ctx, userID, reqData.CharId); err != nil { - s.logger.Error("Failed to delete character", zap.Error(err), zap.String("token", reqData.Token), zap.Uint32("charID", reqData.CharId)) + if err := s.deleteCharacter(ctx, userID, reqData.CharID); err != nil { + s.logger.Error("Failed to delete character", zap.Error(err), zap.String("token", reqData.Token), zap.Uint32("charID", reqData.CharID)) w.WriteHeader(500) return }