mirror of
https://github.com/Mezeporta/Erupe.git
synced 2025-12-16 00:44:42 +01:00
Merge pull request #95 from rockisch/signv2server-update
Update signv2server to return more data
This commit is contained in:
@@ -10,26 +10,29 @@ import (
|
|||||||
"golang.org/x/crypto/bcrypt"
|
"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
|
// Create salted hash of user password
|
||||||
passwordHash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
passwordHash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var id int
|
var (
|
||||||
|
id uint32
|
||||||
|
rights uint32
|
||||||
|
)
|
||||||
err = s.db.QueryRowContext(
|
err = s.db.QueryRowContext(
|
||||||
ctx, `
|
ctx, `
|
||||||
INSERT INTO users (username, password, return_expires)
|
INSERT INTO users (username, password, return_expires)
|
||||||
VALUES ($1, $2, $3)
|
VALUES ($1, $2, $3)
|
||||||
RETURNING id
|
RETURNING id, rights
|
||||||
`,
|
`,
|
||||||
username, string(passwordHash), time.Now().Add(time.Hour*24*30),
|
username, string(passwordHash), time.Now().Add(time.Hour*24*30),
|
||||||
).Scan(&id)
|
).Scan(&id, &rights)
|
||||||
return id, err
|
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)
|
loginToken := token.Generate(16)
|
||||||
_, err := s.db.ExecContext(ctx, "INSERT INTO sign_sessions (user_id, token) VALUES ($1, $2)", uid, loginToken)
|
_, err := s.db.ExecContext(ctx, "INSERT INTO sign_sessions (user_id, token) VALUES ($1, $2)", uid, loginToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -38,8 +41,8 @@ func (s *Server) createLoginToken(ctx context.Context, uid int) (string, error)
|
|||||||
return loginToken, nil
|
return loginToken, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) userIDFromToken(ctx context.Context, token string) (int, error) {
|
func (s *Server) userIDFromToken(ctx context.Context, token string) (uint32, error) {
|
||||||
var userID int
|
var userID uint32
|
||||||
err := s.db.QueryRowContext(ctx, "SELECT user_id FROM sign_sessions WHERE token = $1", token).Scan(&userID)
|
err := s.db.QueryRowContext(ctx, "SELECT user_id FROM sign_sessions WHERE token = $1", token).Scan(&userID)
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
return 0, fmt.Errorf("invalid login token")
|
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
|
return userID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) createCharacter(ctx context.Context, userID int) (int, error) {
|
func (s *Server) createCharacter(ctx context.Context, userID uint32) (Character, error) {
|
||||||
var charID int
|
var character Character
|
||||||
err := s.db.QueryRowContext(ctx,
|
err := s.db.GetContext(ctx, &character,
|
||||||
"SELECT id FROM characters WHERE is_new_character = true AND user_id = $1",
|
"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,
|
userID,
|
||||||
).Scan(&charID)
|
)
|
||||||
if err == sql.ErrNoRows {
|
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 (
|
INSERT INTO characters (
|
||||||
user_id, is_female, is_new_character, name, unk_desc_string,
|
user_id, is_female, is_new_character, name, unk_desc_string,
|
||||||
hrp, gr, weapon_type, last_login
|
hrp, gr, weapon_type, last_login
|
||||||
)
|
)
|
||||||
VALUES ($1, false, true, '', '', 0, 0, 0, $2)
|
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()),
|
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 {
|
func (s *Server) deleteCharacter(ctx context.Context, userID uint32, charID uint32) error {
|
||||||
tx, err := s.db.BeginTx(ctx, nil)
|
var isNew bool
|
||||||
|
err := s.db.QueryRow("SELECT is_new_character FROM characters WHERE id = $1", charID).Scan(&isNew)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer tx.Rollback()
|
if isNew {
|
||||||
|
_, err = s.db.Exec("DELETE FROM characters WHERE id = $1", charID)
|
||||||
_, err = tx.ExecContext(
|
} else {
|
||||||
ctx, `
|
_, err = s.db.Exec("UPDATE characters SET deleted = true WHERE id = $1", charID)
|
||||||
DELETE FROM login_boost_state
|
|
||||||
WHERE char_id = $1`,
|
|
||||||
charID,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
_, err = tx.ExecContext(
|
return err
|
||||||
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()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) getCharactersForUser(ctx context.Context, uid int) ([]Character, error) {
|
func (s *Server) getCharactersForUser(ctx context.Context, uid uint32) ([]Character, error) {
|
||||||
characters := make([]Character, 0)
|
var characters []Character
|
||||||
err := s.db.SelectContext(
|
err := s.db.SelectContext(
|
||||||
ctx, &characters, `
|
ctx, &characters, `
|
||||||
SELECT id, name, is_female, weapon_type, hrp, gr, last_login
|
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
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ import (
|
|||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"erupe-ce/server/channelserver"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/lib/pq"
|
"github.com/lib/pq"
|
||||||
@@ -18,21 +20,78 @@ type LauncherMessage struct {
|
|||||||
Link string `json:"link"`
|
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 {
|
type Character struct {
|
||||||
ID int `json:"id"`
|
ID uint32 `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
IsFemale bool `json:"isFemale" db:"is_female"`
|
IsFemale bool `json:"isFemale" db:"is_female"`
|
||||||
Weapon int `json:"weapon" db:"weapon_type"`
|
Weapon uint32 `json:"weapon" db:"weapon_type"`
|
||||||
HR int `json:"hr" db:"hrp"`
|
HR uint32 `json:"hr" db:"hrp"`
|
||||||
GR int `json:"gr"`
|
GR uint32 `json:"gr"`
|
||||||
LastLogin int64 `json:"lastLogin" db:"last_login"`
|
LastLogin int64 `json:"lastLogin" db:"last_login"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) Launcher(w http.ResponseWriter, r *http.Request) {
|
type MezFes struct {
|
||||||
var respData struct {
|
ID uint32 `json:"id"`
|
||||||
Important []LauncherMessage `json:"important"`
|
Start uint32 `json:"start"`
|
||||||
Normal []LauncherMessage `json:"normal"`
|
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[:], "<PAGE>"))
|
||||||
|
}
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) Launcher(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var respData LauncherResponse
|
||||||
respData.Important = []LauncherMessage{
|
respData.Important = []LauncherMessage{
|
||||||
{
|
{
|
||||||
Message: "Server Update 9 Released!",
|
Message: "Server Update 9 Released!",
|
||||||
@@ -75,10 +134,11 @@ func (s *Server) Login(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
var (
|
var (
|
||||||
userID int
|
userID uint32
|
||||||
password string
|
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 {
|
if err == sql.ErrNoRows {
|
||||||
w.WriteHeader(400)
|
w.WriteHeader(400)
|
||||||
w.Write([]byte("Username does not exist"))
|
w.Write([]byte("Username does not exist"))
|
||||||
@@ -94,22 +154,19 @@ func (s *Server) Login(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var respData struct {
|
userToken, err := s.createLoginToken(ctx, userID)
|
||||||
Token string `json:"token"`
|
|
||||||
Characters []Character `json:"characters"`
|
|
||||||
}
|
|
||||||
respData.Token, err = s.createLoginToken(ctx, userID)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Warn("Error registering login token", zap.Error(err))
|
s.logger.Warn("Error registering login token", zap.Error(err))
|
||||||
w.WriteHeader(500)
|
w.WriteHeader(500)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
respData.Characters, err = s.getCharactersForUser(ctx, userID)
|
characters, err := s.getCharactersForUser(ctx, userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Warn("Error getting characters from DB", zap.Error(err))
|
s.logger.Warn("Error getting characters from DB", zap.Error(err))
|
||||||
w.WriteHeader(500)
|
w.WriteHeader(500)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
respData := s.newAuthData(userID, userRights, userToken, characters)
|
||||||
w.WriteHeader(200)
|
w.WriteHeader(200)
|
||||||
w.Header().Add("Content-Type", "application/json")
|
w.Header().Add("Content-Type", "application/json")
|
||||||
json.NewEncoder(w).Encode(respData)
|
json.NewEncoder(w).Encode(respData)
|
||||||
@@ -128,7 +185,7 @@ func (s *Server) Register(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
s.logger.Info("Creating account", zap.String("username", reqData.Username))
|
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 {
|
if err != nil {
|
||||||
var pqErr *pq.Error
|
var pqErr *pq.Error
|
||||||
if errors.As(err, &pqErr) && pqErr.Constraint == "users_username_key" {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var respData struct {
|
userToken, err := s.createLoginToken(ctx, userID)
|
||||||
Token string `json:"token"`
|
|
||||||
}
|
|
||||||
respData.Token, err = s.createLoginToken(ctx, userID)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("Error registering login token", zap.Error(err))
|
s.logger.Error("Error registering login token", zap.Error(err))
|
||||||
w.WriteHeader(500)
|
w.WriteHeader(500)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
respData := s.newAuthData(userID, userRights, userToken, []Character{})
|
||||||
json.NewEncoder(w).Encode(respData)
|
json.NewEncoder(w).Encode(respData)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,28 +220,25 @@ func (s *Server) CreateCharacter(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var respData struct {
|
|
||||||
CharID int `json:"id"`
|
|
||||||
}
|
|
||||||
userID, err := s.userIDFromToken(ctx, reqData.Token)
|
userID, err := s.userIDFromToken(ctx, reqData.Token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.WriteHeader(401)
|
w.WriteHeader(401)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
respData.CharID, err = s.createCharacter(ctx, userID)
|
character, err := s.createCharacter(ctx, userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("Failed to create character", zap.Error(err), zap.String("token", reqData.Token))
|
s.logger.Error("Failed to create character", zap.Error(err), zap.String("token", reqData.Token))
|
||||||
w.WriteHeader(500)
|
w.WriteHeader(500)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
json.NewEncoder(w).Encode(respData)
|
json.NewEncoder(w).Encode(character)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) DeleteCharacter(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) DeleteCharacter(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
var reqData struct {
|
var reqData struct {
|
||||||
Token string `json:"token"`
|
Token string `json:"token"`
|
||||||
CharID int `json:"id"`
|
CharID uint32 `json:"charId"`
|
||||||
}
|
}
|
||||||
if err := json.NewDecoder(r.Body).Decode(&reqData); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(&reqData); err != nil {
|
||||||
s.logger.Error("JSON decode error", zap.Error(err))
|
s.logger.Error("JSON decode error", zap.Error(err))
|
||||||
@@ -200,7 +252,7 @@ func (s *Server) DeleteCharacter(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := s.deleteCharacter(ctx, userID, reqData.CharID); err != nil {
|
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))
|
s.logger.Error("Failed to delete character", zap.Error(err), zap.String("token", reqData.Token), zap.Uint32("charID", reqData.CharID))
|
||||||
w.WriteHeader(500)
|
w.WriteHeader(500)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user