diff --git a/config.json b/config.json index e3e2b7ea4..9fcb4ef68 100644 --- a/config.json +++ b/config.json @@ -133,7 +133,11 @@ }, "SignV2": { "Enabled": false, - "Port": 8080 + "Port": 8080, + "PatchServer": "", + "Banners": [], + "Messages": [], + "Links": [] }, "Channel": { "Enabled": true diff --git a/config/config.go b/config/config.go index ee0e6b377..e992d6594 100644 --- a/config/config.go +++ b/config/config.go @@ -195,8 +195,30 @@ type Sign struct { // SignV2 holds the new sign server config type SignV2 struct { - Enabled bool - Port int + Enabled bool + Port int + PatchServer string + Banners []SignV2Banner + Messages []SignV2Message + Links []SignV2Link +} + +type SignV2Banner struct { + Src string `json:"src"` // Displayed image URL + Link string `json:"link"` // Link accessed on click +} + +type SignV2Message struct { + Message string `json:"message"` // Displayed message + Date int64 `json:"date"` // Displayed date + Kind int `json:"kind"` // 0 for 'Default', 1 for 'New' + Link string `json:"link"` // Link accessed on click +} + +type SignV2Link struct { + Name string `json:"name"` // Displayed name + Icon string `json:"icon"` // Displayed icon. It will be cast as a monochrome color as long as it is transparent. + Link string `json:"link"` // Link accessed on click } type Channel struct { diff --git a/server/channelserver/sys_channel_server.go b/server/channelserver/sys_channel_server.go index d12d713f5..70f52e461 100644 --- a/server/channelserver/sys_channel_server.go +++ b/server/channelserver/sys_channel_server.go @@ -9,7 +9,7 @@ import ( "erupe-ce/common/byteframe" ps "erupe-ce/common/pascalstring" - "erupe-ce/config" + _config "erupe-ce/config" "erupe-ce/network/binpacket" "erupe-ce/network/mhfpacket" "erupe-ce/server/discordbot" diff --git a/server/signv2server/dbutils.go b/server/signv2server/dbutils.go index fb9711da9..dde729ac9 100644 --- a/server/signv2server/dbutils.go +++ b/server/signv2server/dbutils.go @@ -122,3 +122,13 @@ func (s *Server) getReturnExpiry(uid uint32) time.Time { s.db.Exec("UPDATE users SET last_login=$1 WHERE id=$2", time.Now(), uid) return returnExpiry } + +func (s *Server) exportSave(ctx context.Context, uid uint32, cid uint32) (map[string]interface{}, error) { + row := s.db.QueryRowxContext(ctx, "SELECT * FROM characters WHERE id=$1 AND user_id=$2", cid, uid) + result := make(map[string]interface{}) + err := row.MapScan(result) + if err != nil { + return nil, err + } + return result, nil +} diff --git a/server/signv2server/endpoints.go b/server/signv2server/endpoints.go index c087a70df..cde28591c 100644 --- a/server/signv2server/endpoints.go +++ b/server/signv2server/endpoints.go @@ -4,25 +4,25 @@ import ( "database/sql" "encoding/json" "errors" + _config "erupe-ce/config" "erupe-ce/server/channelserver" "net/http" "strings" - "time" "github.com/lib/pq" "go.uber.org/zap" "golang.org/x/crypto/bcrypt" ) -type LauncherMessage struct { - Message string `json:"message"` - Date int64 `json:"date"` - Link string `json:"link"` -} +const ( + NotificationDefault = iota + NotificationNew +) type LauncherResponse struct { - Important []LauncherMessage `json:"important"` - Normal []LauncherMessage `json:"normal"` + Banners []_config.SignV2Banner `json:"banners"` + Messages []_config.SignV2Message `json:"messages"` + Links []_config.SignV2Link `json:"links"` } type User struct { @@ -37,7 +37,7 @@ type Character struct { Weapon uint32 `json:"weapon" db:"weapon_type"` HR uint32 `json:"hr" db:"hrp"` GR uint32 `json:"gr"` - LastLogin int64 `json:"lastLogin" db:"last_login"` + LastLogin int32 `json:"lastLogin" db:"last_login"` } type MezFes struct { @@ -53,10 +53,15 @@ type AuthData struct { CurrentTS uint32 `json:"currentTs"` ExpiryTS uint32 `json:"expiryTs"` EntranceCount uint32 `json:"entranceCount"` - Notifications []string `json:"notifications"` + Notices []string `json:"notices"` User User `json:"user"` Characters []Character `json:"characters"` MezFes *MezFes `json:"mezFes"` + PatchServer string `json:"patchServer"` +} + +type ExportData struct { + Character map[string]interface{} `json:"character"` } func (s *Server) newAuthData(userID uint32, userRights uint32, userToken string, characters []Character) AuthData { @@ -68,7 +73,14 @@ func (s *Server) newAuthData(userID uint32, userRights uint32, userToken string, Rights: userRights, Token: userToken, }, - Characters: characters, + Characters: characters, + PatchServer: s.erupeConfig.SignV2.PatchServer, + Notices: []string{}, + } + if s.erupeConfig.DevModeOptions.MaxLauncherHR { + for i := range resp.Characters { + resp.Characters[i].HR = 7 + } } if s.erupeConfig.DevModeOptions.MezFesEvent { stalls := []uint32{10, 3, 6, 9, 4, 8, 5, 7} @@ -85,38 +97,16 @@ func (s *Server) newAuthData(userID uint32, userRights uint32, userToken string, } } if !s.erupeConfig.HideLoginNotice { - resp.Notifications = append(resp.Notifications, strings.Join(s.erupeConfig.LoginNotices[:], "")) + resp.Notices = append(resp.Notices, 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!", - Date: time.Date(2022, 8, 2, 0, 0, 0, 0, time.UTC).Unix(), - Link: "https://discord.com/channels/368424389416583169/929509970624532511/1003985850255818762", - }, - { - Message: "Eng 2.0 & Ravi Patch Released!", - Date: time.Date(2022, 5, 3, 0, 0, 0, 0, time.UTC).Unix(), - Link: "https://discord.com/channels/368424389416583169/929509970624532511/969305400795078656", - }, - { - Message: "Launcher Patch V1.0 Released!", - Date: time.Date(2022, 4, 24, 0, 0, 0, 0, time.UTC).Unix(), - Link: "https://discord.com/channels/368424389416583169/929509970624532511/969286397301248050", - }, - } - respData.Normal = []LauncherMessage{ - { - Message: "Join the community Discord for updates!", - Date: time.Date(2022, 4, 24, 0, 0, 0, 0, time.UTC).Unix(), - Link: "https://discord.gg/CFnzbhQ", - }, - } - w.WriteHeader(200) + respData.Banners = s.erupeConfig.SignV2.Banners + respData.Messages = s.erupeConfig.SignV2.Messages + respData.Links = s.erupeConfig.SignV2.Links w.Header().Add("Content-Type", "application/json") json.NewEncoder(w).Encode(respData) } @@ -130,7 +120,6 @@ func (s *Server) Login(w http.ResponseWriter, r *http.Request) { if err := json.NewDecoder(r.Body).Decode(&reqData); err != nil { s.logger.Error("JSON decode error", zap.Error(err)) w.WriteHeader(400) - w.Write([]byte("Invalid data received")) return } var ( @@ -141,7 +130,7 @@ func (s *Server) Login(w http.ResponseWriter, r *http.Request) { 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")) + w.Write([]byte("username-error")) return } else if err != nil { s.logger.Warn("SQL query error", zap.Error(err)) @@ -150,7 +139,7 @@ func (s *Server) Login(w http.ResponseWriter, r *http.Request) { } if bcrypt.CompareHashAndPassword([]byte(password), []byte(reqData.Password)) != nil { w.WriteHeader(400) - w.Write([]byte("Your password is incorrect")) + w.Write([]byte("password-error")) return } @@ -166,8 +155,10 @@ func (s *Server) Login(w http.ResponseWriter, r *http.Request) { w.WriteHeader(500) return } + if characters == nil { + characters = []Character{} + } respData := s.newAuthData(userID, userRights, userToken, characters) - w.WriteHeader(200) w.Header().Add("Content-Type", "application/json") json.NewEncoder(w).Encode(respData) } @@ -181,7 +172,10 @@ func (s *Server) Register(w http.ResponseWriter, r *http.Request) { if err := json.NewDecoder(r.Body).Decode(&reqData); err != nil { s.logger.Error("JSON decode error", zap.Error(err)) w.WriteHeader(400) - w.Write([]byte("Invalid data received")) + return + } + if reqData.Username == "" || reqData.Password == "" { + w.WriteHeader(400) return } s.logger.Info("Creating account", zap.String("username", reqData.Username)) @@ -190,7 +184,7 @@ func (s *Server) Register(w http.ResponseWriter, r *http.Request) { var pqErr *pq.Error if errors.As(err, &pqErr) && pqErr.Constraint == "users_username_key" { w.WriteHeader(400) - w.Write([]byte("User already exists")) + w.Write([]byte("username-exists-error")) return } s.logger.Error("Error checking user", zap.Error(err), zap.String("username", reqData.Username)) @@ -205,6 +199,7 @@ func (s *Server) Register(w http.ResponseWriter, r *http.Request) { return } respData := s.newAuthData(userID, userRights, userToken, []Character{}) + w.Header().Add("Content-Type", "application/json") json.NewEncoder(w).Encode(respData) } @@ -216,7 +211,6 @@ func (s *Server) CreateCharacter(w http.ResponseWriter, r *http.Request) { if err := json.NewDecoder(r.Body).Decode(&reqData); err != nil { s.logger.Error("JSON decode error", zap.Error(err)) w.WriteHeader(400) - w.Write([]byte("Invalid data received")) return } @@ -231,6 +225,10 @@ func (s *Server) CreateCharacter(w http.ResponseWriter, r *http.Request) { w.WriteHeader(500) return } + if s.erupeConfig.DevModeOptions.MaxLauncherHR { + character.HR = 7 + } + w.Header().Add("Content-Type", "application/json") json.NewEncoder(w).Encode(character) } @@ -243,7 +241,6 @@ func (s *Server) DeleteCharacter(w http.ResponseWriter, r *http.Request) { if err := json.NewDecoder(r.Body).Decode(&reqData); err != nil { s.logger.Error("JSON decode error", zap.Error(err)) w.WriteHeader(400) - w.Write([]byte("Invalid data received")) return } userID, err := s.userIDFromToken(ctx, reqData.Token) @@ -256,5 +253,35 @@ func (s *Server) DeleteCharacter(w http.ResponseWriter, r *http.Request) { w.WriteHeader(500) return } + w.Header().Add("Content-Type", "application/json") json.NewEncoder(w).Encode(struct{}{}) } + +func (s *Server) ExportSave(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + var reqData struct { + Token string `json:"token"` + CharID uint32 `json:"charId"` + } + if err := json.NewDecoder(r.Body).Decode(&reqData); err != nil { + s.logger.Error("JSON decode error", zap.Error(err)) + w.WriteHeader(400) + return + } + userID, err := s.userIDFromToken(ctx, reqData.Token) + if err != nil { + w.WriteHeader(401) + return + } + character, err := s.exportSave(ctx, userID, reqData.CharID) + if err != nil { + s.logger.Error("Failed to export save", zap.Error(err), zap.String("token", reqData.Token), zap.Uint32("charID", reqData.CharID)) + w.WriteHeader(500) + return + } + save := ExportData{ + Character: character, + } + w.Header().Add("Content-Type", "application/json") + json.NewEncoder(w).Encode(save) +} diff --git a/server/signv2server/signv2_server.go b/server/signv2server/signv2_server.go index c6db00b09..fedbabba2 100644 --- a/server/signv2server/signv2_server.go +++ b/server/signv2server/signv2_server.go @@ -51,6 +51,7 @@ func (s *Server) Start() error { r.HandleFunc("/register", s.Register) r.HandleFunc("/character/create", s.CreateCharacter) r.HandleFunc("/character/delete", s.DeleteCharacter) + r.HandleFunc("/character/export", s.ExportSave) handler := handlers.CORS(handlers.AllowedHeaders([]string{"Content-Type"}))(r) s.httpServer.Handler = handlers.LoggingHandler(os.Stdout, handler) s.httpServer.Addr = fmt.Sprintf(":%d", s.erupeConfig.SignV2.Port)