From 52c5c9ee6ca78d4ee96ca908dd802b9884d462d8 Mon Sep 17 00:00:00 2001 From: Frederico Santos Date: Thu, 6 Jun 2024 03:56:29 +0100 Subject: [PATCH 01/23] Reject saves with 2 days difference (#29) * Reject saves with 2 days difference * improved timestamp checks * chore: Refactor timestamp checks for saving system data * Refactor timestamp checks for saving system data --- db/savedata.go | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/db/savedata.go b/db/savedata.go index bb593b8..833550f 100644 --- a/db/savedata.go +++ b/db/savedata.go @@ -21,6 +21,8 @@ import ( "bytes" "encoding/gob" "errors" + "fmt" + "time" "github.com/pagefaultgames/rogueserver/defs" ) @@ -71,8 +73,29 @@ func ReadSystemSaveData(uuid []byte) (defs.SystemSaveData, error) { func StoreSystemSaveData(uuid []byte, data defs.SystemSaveData) error { systemData, err := ReadSystemSaveData(uuid) - if err == nil && systemData.Timestamp > data.Timestamp { - return errors.New("attempted to save an older system save") + + currentTime := time.Now() + futureTime := currentTime.Add(time.Hour * 24).UnixMilli() + pastTime := currentTime.Add(-time.Hour * 24).UnixMilli() + if err == nil { // system save exists + // Check if the new data timestamp is in the past against the system save but only if the system save is not past 24 hours from now + if systemData.Timestamp > data.Timestamp && systemData.Timestamp < int(futureTime) { + // Error if the new data timestamp is older than the current system save timestamp + return fmt.Errorf("attempted to save an older system save from %d when the current system save is from %d", + data.Timestamp, + systemData.Timestamp) + } + } + + // Check if the data.Timestamp is too far in the future + if data.Timestamp > int(futureTime) { + return fmt.Errorf("attempted to save a system save in the future from %d", data.Timestamp) + + } + + // Check if the data.Timestamp is too far in the past + if data.Timestamp < int(pastTime) { + return fmt.Errorf("attempted to save a system save in the past from %d", data.Timestamp) } var buf bytes.Buffer From 9efd1b751ad113532356955a799e06e81e37f2b0 Mon Sep 17 00:00:00 2001 From: Pancakes Date: Wed, 5 Jun 2024 23:01:12 -0400 Subject: [PATCH 02/23] Update StoreSystemSaveData --- db/savedata.go | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/db/savedata.go b/db/savedata.go index 833550f..78bf8fb 100644 --- a/db/savedata.go +++ b/db/savedata.go @@ -72,25 +72,22 @@ func ReadSystemSaveData(uuid []byte) (defs.SystemSaveData, error) { } func StoreSystemSaveData(uuid []byte, data defs.SystemSaveData) error { - systemData, err := ReadSystemSaveData(uuid) - currentTime := time.Now() futureTime := currentTime.Add(time.Hour * 24).UnixMilli() pastTime := currentTime.Add(-time.Hour * 24).UnixMilli() + + systemData, err := ReadSystemSaveData(uuid) if err == nil { // system save exists // Check if the new data timestamp is in the past against the system save but only if the system save is not past 24 hours from now if systemData.Timestamp > data.Timestamp && systemData.Timestamp < int(futureTime) { // Error if the new data timestamp is older than the current system save timestamp - return fmt.Errorf("attempted to save an older system save from %d when the current system save is from %d", - data.Timestamp, - systemData.Timestamp) + return fmt.Errorf("attempted to save an older system save from %d when the current system save is from %d", data.Timestamp, systemData.Timestamp) } } // Check if the data.Timestamp is too far in the future if data.Timestamp > int(futureTime) { return fmt.Errorf("attempted to save a system save in the future from %d", data.Timestamp) - } // Check if the data.Timestamp is too far in the past From 048db6760930d3446dd39f24eeddaf7fe9f38081 Mon Sep 17 00:00:00 2001 From: Pancakes Date: Thu, 6 Jun 2024 03:45:59 -0400 Subject: [PATCH 03/23] Remove timestamp guard --- db/savedata.go | 35 ++--------------------------------- 1 file changed, 2 insertions(+), 33 deletions(-) diff --git a/db/savedata.go b/db/savedata.go index 78bf8fb..bb792c5 100644 --- a/db/savedata.go +++ b/db/savedata.go @@ -20,9 +20,6 @@ package db import ( "bytes" "encoding/gob" - "errors" - "fmt" - "time" "github.com/pagefaultgames/rogueserver/defs" ) @@ -72,31 +69,8 @@ func ReadSystemSaveData(uuid []byte) (defs.SystemSaveData, error) { } func StoreSystemSaveData(uuid []byte, data defs.SystemSaveData) error { - currentTime := time.Now() - futureTime := currentTime.Add(time.Hour * 24).UnixMilli() - pastTime := currentTime.Add(-time.Hour * 24).UnixMilli() - - systemData, err := ReadSystemSaveData(uuid) - if err == nil { // system save exists - // Check if the new data timestamp is in the past against the system save but only if the system save is not past 24 hours from now - if systemData.Timestamp > data.Timestamp && systemData.Timestamp < int(futureTime) { - // Error if the new data timestamp is older than the current system save timestamp - return fmt.Errorf("attempted to save an older system save from %d when the current system save is from %d", data.Timestamp, systemData.Timestamp) - } - } - - // Check if the data.Timestamp is too far in the future - if data.Timestamp > int(futureTime) { - return fmt.Errorf("attempted to save a system save in the future from %d", data.Timestamp) - } - - // Check if the data.Timestamp is too far in the past - if data.Timestamp < int(pastTime) { - return fmt.Errorf("attempted to save a system save in the past from %d", data.Timestamp) - } - var buf bytes.Buffer - err = gob.NewEncoder(&buf).Encode(data) + err := gob.NewEncoder(&buf).Encode(data) if err != nil { return err } @@ -146,13 +120,8 @@ func GetLatestSessionSaveDataSlot(uuid []byte) (int, error) { } func StoreSessionSaveData(uuid []byte, data defs.SessionSaveData, slot int) error { - session, err := ReadSessionSaveData(uuid, slot) - if err == nil && session.Seed == data.Seed && session.WaveIndex > data.WaveIndex { - return errors.New("attempted to save an older session") - } - var buf bytes.Buffer - err = gob.NewEncoder(&buf).Encode(data) + err := gob.NewEncoder(&buf).Encode(data) if err != nil { return err } From ddf1e8d9e5ecc9e7e6823144b97b4992d66d30ea Mon Sep 17 00:00:00 2001 From: Pancakes Date: Fri, 7 Jun 2024 18:05:41 -0400 Subject: [PATCH 04/23] Clean up handleUpdateAll --- api/endpoints.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/api/endpoints.go b/api/endpoints.go index 92d4570..f43238c 100644 --- a/api/endpoints.go +++ b/api/endpoints.go @@ -586,6 +586,7 @@ func handleUpdateAll(w http.ResponseWriter, r *http.Request) { httpError(w, r, fmt.Errorf("failed to decode request body: %s", err), http.StatusBadRequest) return } + if data.ClientSessionId == "" { data.ClientSessionId = legacyClientSessionId } @@ -602,9 +603,6 @@ func handleUpdateAll(w http.ResponseWriter, r *http.Request) { return } - trainerId := data.System.TrainerId - secretId := data.System.SecretId - storedTrainerId, storedSecretId, err := db.FetchTrainerIds(uuid) if err != nil { httpError(w, r, err, http.StatusInternalServerError) @@ -612,12 +610,13 @@ func handleUpdateAll(w http.ResponseWriter, r *http.Request) { } if storedTrainerId > 0 || storedSecretId > 0 { - if trainerId != storedTrainerId || secretId != storedSecretId { + if data.System.TrainerId != storedTrainerId || data.System.SecretId != storedSecretId { httpError(w, r, fmt.Errorf("session out of date: stored trainer or secret ID does not match"), http.StatusBadRequest) return } } else { - if err = db.UpdateTrainerIds(trainerId, secretId, uuid); err != nil { + err = db.UpdateTrainerIds(data.System.TrainerId, data.System.SecretId, uuid) + if err != nil { httpError(w, r, err, http.StatusInternalServerError) return } @@ -628,11 +627,13 @@ func handleUpdateAll(w http.ResponseWriter, r *http.Request) { httpError(w, r, err, http.StatusInternalServerError) return } + err = savedata.Update(uuid, 0, data.System) if err != nil { httpError(w, r, err, http.StatusInternalServerError) return } + w.WriteHeader(http.StatusOK) } From 023b8aaed469a090a684b3c3b8d3ad4bc2b2281c Mon Sep 17 00:00:00 2001 From: Pancakes Date: Fri, 7 Jun 2024 18:24:55 -0400 Subject: [PATCH 05/23] Remove /savedata/get --- api/common.go | 1 - api/endpoints.go | 151 ++++++++++++---------------------------- api/savedata/get.go | 82 ---------------------- api/savedata/session.go | 24 +++++++ api/savedata/system.go | 48 +++++++++++++ 5 files changed, 115 insertions(+), 191 deletions(-) delete mode 100644 api/savedata/get.go create mode 100644 api/savedata/session.go create mode 100644 api/savedata/system.go diff --git a/api/common.go b/api/common.go index e1666f5..a8496ed 100644 --- a/api/common.go +++ b/api/common.go @@ -52,7 +52,6 @@ func Init(mux *http.ServeMux) error { mux.HandleFunc("GET /game/classicsessioncount", handleGameClassicSessionCount) // savedata - mux.HandleFunc("GET /savedata/get", legacyHandleGetSaveData) mux.HandleFunc("POST /savedata/update", legacyHandleSaveData) mux.HandleFunc("GET /savedata/delete", legacyHandleSaveData) // TODO use deleteSystemSave mux.HandleFunc("POST /savedata/clear", legacyHandleSaveData) // TODO use clearSessionData diff --git a/api/endpoints.go b/api/endpoints.go index f43238c..4e4fa90 100644 --- a/api/endpoints.go +++ b/api/endpoints.go @@ -173,8 +173,7 @@ func handleGetSessionData(w http.ResponseWriter, r *http.Request) { return } - var save any - save, err = savedata.Get(uuid, 1, slot) + save, err := savedata.Session(uuid, slot) if errors.Is(err, sql.ErrNoRows) { http.Error(w, err.Error(), http.StatusNotFound) return @@ -190,54 +189,6 @@ func handleGetSessionData(w http.ResponseWriter, r *http.Request) { const legacyClientSessionId = "LEGACY_CLIENT" -func legacyHandleGetSaveData(w http.ResponseWriter, r *http.Request) { - uuid, err := uuidFromRequest(r) - if err != nil { - httpError(w, r, err, http.StatusBadRequest) - return - } - - datatype := -1 - if r.URL.Query().Has("datatype") { - datatype, err = strconv.Atoi(r.URL.Query().Get("datatype")) - if err != nil { - httpError(w, r, err, http.StatusBadRequest) - return - } - } - - var slot int - if r.URL.Query().Has("slot") { - slot, err = strconv.Atoi(r.URL.Query().Get("slot")) - if err != nil { - httpError(w, r, err, http.StatusBadRequest) - return - } - } - - var save any - if datatype == 0 { - err = db.UpdateActiveSession(uuid, legacyClientSessionId) // we dont have a client id - if err != nil { - httpError(w, r, fmt.Errorf("failed to update active session: %s", err), http.StatusBadRequest) - return - } - } - - save, err = savedata.Get(uuid, datatype, slot) - if errors.Is(err, sql.ErrNoRows) { - http.Error(w, err.Error(), http.StatusNotFound) - return - } - - if err != nil { - httpError(w, r, err, http.StatusInternalServerError) - return - } - - writeJSON(w, r, save) -} - // FIXME UNFINISHED!!! /*func clearSessionData(w http.ResponseWriter, r *http.Request) { uuid, err := uuidFromRequest(r) @@ -438,8 +389,8 @@ func legacyHandleSaveData(w http.ResponseWriter, r *http.Request) { } var save any - // /savedata/get and /savedata/delete specify datatype, but don't expect data in body - if r.URL.Path != "/savedata/get" && r.URL.Path != "/savedata/delete" { + // /savedata/delete specify datatype, but don't expect data in body + if r.URL.Path != "/savedata/delete" { if datatype == 0 { var system defs.SystemSaveData err = json.NewDecoder(r.Body).Decode(&system) @@ -463,74 +414,58 @@ func legacyHandleSaveData(w http.ResponseWriter, r *http.Request) { } var active bool - if r.URL.Path == "/savedata/get" { - if datatype == 0 { - err = db.UpdateActiveSession(uuid, clientSessionId) + active, err = db.IsActiveSession(uuid, clientSessionId) + if err != nil { + httpError(w, r, fmt.Errorf("failed to check active session: %s", err), http.StatusBadRequest) + return + } + + // TODO: make this not suck + if !active && r.URL.Path != "/savedata/clear" { + httpError(w, r, fmt.Errorf("session out of date: not active"), http.StatusBadRequest) + return + } + + var trainerId, secretId int + + if r.URL.Path != "/savedata/update" || datatype == 1 { + if r.URL.Query().Has("trainerId") && r.URL.Query().Has("secretId") { + trainerId, err = strconv.Atoi(r.URL.Query().Get("trainerId")) + if err != nil { + httpError(w, r, err, http.StatusBadRequest) + return + } + + secretId, err = strconv.Atoi(r.URL.Query().Get("secretId")) if err != nil { - httpError(w, r, fmt.Errorf("failed to update active session: %s", err), http.StatusBadRequest) + httpError(w, r, err, http.StatusBadRequest) return } } } else { - active, err = db.IsActiveSession(uuid, clientSessionId) - if err != nil { - httpError(w, r, fmt.Errorf("failed to check active session: %s", err), http.StatusBadRequest) - return - } + trainerId = save.(defs.SystemSaveData).TrainerId + secretId = save.(defs.SystemSaveData).SecretId + } - // TODO: make this not suck - if !active && r.URL.Path != "/savedata/clear" { - httpError(w, r, fmt.Errorf("session out of date: not active"), http.StatusBadRequest) - return - } + storedTrainerId, storedSecretId, err := db.FetchTrainerIds(uuid) + if err != nil { + httpError(w, r, err, http.StatusInternalServerError) + return + } - var trainerId, secretId int - - if r.URL.Path != "/savedata/update" || datatype == 1 { - if r.URL.Query().Has("trainerId") && r.URL.Query().Has("secretId") { - trainerId, err = strconv.Atoi(r.URL.Query().Get("trainerId")) - if err != nil { - httpError(w, r, err, http.StatusBadRequest) - return - } - - secretId, err = strconv.Atoi(r.URL.Query().Get("secretId")) - if err != nil { - httpError(w, r, err, http.StatusBadRequest) - return - } - } - } else { - trainerId = save.(defs.SystemSaveData).TrainerId - secretId = save.(defs.SystemSaveData).SecretId + if storedTrainerId > 0 || storedSecretId > 0 { + if trainerId != storedTrainerId || secretId != storedSecretId { + httpError(w, r, fmt.Errorf("session out of date: stored trainer or secret ID does not match"), http.StatusBadRequest) + return } - - storedTrainerId, storedSecretId, err := db.FetchTrainerIds(uuid) - if err != nil { + } else { + if err := db.UpdateTrainerIds(trainerId, secretId, uuid); err != nil { httpError(w, r, err, http.StatusInternalServerError) return } - - if storedTrainerId > 0 || storedSecretId > 0 { - if trainerId != storedTrainerId || secretId != storedSecretId { - httpError(w, r, fmt.Errorf("session out of date: stored trainer or secret ID does not match"), http.StatusBadRequest) - return - } - } else { - if err := db.UpdateTrainerIds(trainerId, secretId, uuid); err != nil { - httpError(w, r, err, http.StatusInternalServerError) - return - } - } } switch r.URL.Path { - case "/savedata/get": - save, err = savedata.Get(uuid, datatype, slot) - if errors.Is(err, sql.ErrNoRows) { - http.Error(w, err.Error(), http.StatusNotFound) - return - } case "/savedata/update": err = savedata.Update(uuid, slot, save) case "/savedata/delete": @@ -716,8 +651,7 @@ func handleGetSystemData(w http.ResponseWriter, r *http.Request) { return } - var save any //TODO this is always system save data - save, err = savedata.Get(uuid, 0, 0) + save, err := savedata.System(uuid) if err != nil { if errors.Is(err, sql.ErrNoRows) { http.Error(w, err.Error(), http.StatusNotFound) @@ -727,6 +661,7 @@ func handleGetSystemData(w http.ResponseWriter, r *http.Request) { return } + //TODO apply vouchers writeJSON(w, r, save) diff --git a/api/savedata/get.go b/api/savedata/get.go deleted file mode 100644 index d87abdf..0000000 --- a/api/savedata/get.go +++ /dev/null @@ -1,82 +0,0 @@ -/* - Copyright (C) 2024 Pagefault Games - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package savedata - -import ( - "fmt" - "strconv" - - "github.com/pagefaultgames/rogueserver/db" - "github.com/pagefaultgames/rogueserver/defs" -) - -// /savedata/get - get save data -func Get(uuid []byte, datatype, slot int) (any, error) { - switch datatype { - case 0: // System - system, err := db.ReadSystemSaveData(uuid) - if err != nil { - return nil, err - } - - // TODO this should be a transaction - compensations, err := db.FetchAndClaimAccountCompensations(uuid) - if err != nil { - return nil, fmt.Errorf("failed to fetch compensations: %s", err) - } - - var needsUpdate bool - for compensationType, amount := range compensations { - system.VoucherCounts[strconv.Itoa(compensationType)] += amount - if amount > 0 { - needsUpdate = true - } - } - - if needsUpdate { - err = db.StoreSystemSaveData(uuid, system) - if err != nil { - return nil, fmt.Errorf("failed to update system save data: %s", err) - } - err = db.DeleteClaimedAccountCompensations(uuid) - if err != nil { - return nil, fmt.Errorf("failed to delete claimed compensations: %s", err) - } - - err = db.UpdateAccountStats(uuid, system.GameStats, system.VoucherCounts) - if err != nil { - return nil, fmt.Errorf("failed to update account stats: %s", err) - } - } - - return system, nil - case 1: // Session - if slot < 0 || slot >= defs.SessionSlotCount { - return nil, fmt.Errorf("slot id %d out of range", slot) - } - - session, err := db.ReadSessionSaveData(uuid, slot) - if err != nil { - return nil, err - } - - return session, nil - default: - return nil, fmt.Errorf("invalid data type") - } -} diff --git a/api/savedata/session.go b/api/savedata/session.go new file mode 100644 index 0000000..d6d54a8 --- /dev/null +++ b/api/savedata/session.go @@ -0,0 +1,24 @@ +package savedata + +import ( + "fmt" + + "github.com/pagefaultgames/rogueserver/db" + "github.com/pagefaultgames/rogueserver/defs" +) + +func Session(uuid []byte, slot int) (defs.SessionSaveData, error) { + var session defs.SessionSaveData + + if slot < 0 || slot >= defs.SessionSlotCount { + return session, fmt.Errorf("slot id %d out of range", slot) + } + + var err error + session, err = db.ReadSessionSaveData(uuid, slot) + if err != nil { + return session, err + } + + return session, nil +} diff --git a/api/savedata/system.go b/api/savedata/system.go new file mode 100644 index 0000000..104df7d --- /dev/null +++ b/api/savedata/system.go @@ -0,0 +1,48 @@ +package savedata + +import ( + "fmt" + "strconv" + + "github.com/pagefaultgames/rogueserver/db" + "github.com/pagefaultgames/rogueserver/defs" +) + +func System(uuid []byte) (defs.SystemSaveData, error) { + system, err := db.ReadSystemSaveData(uuid) + if err != nil { + return system, err + } + + // TODO this should be a transaction + compensations, err := db.FetchAndClaimAccountCompensations(uuid) + if err != nil { + return system, fmt.Errorf("failed to fetch compensations: %s", err) + } + + var needsUpdate bool + for compensationType, amount := range compensations { + system.VoucherCounts[strconv.Itoa(compensationType)] += amount + if amount > 0 { + needsUpdate = true + } + } + + if needsUpdate { + err = db.StoreSystemSaveData(uuid, system) + if err != nil { + return system, fmt.Errorf("failed to update system save data: %s", err) + } + err = db.DeleteClaimedAccountCompensations(uuid) + if err != nil { + return system, fmt.Errorf("failed to delete claimed compensations: %s", err) + } + + err = db.UpdateAccountStats(uuid, system.GameStats, system.VoucherCounts) + if err != nil { + return system, fmt.Errorf("failed to update account stats: %s", err) + } + } + + return system, nil +} \ No newline at end of file From 674ed2aa51f26aa492d632ff586687c654efe082 Mon Sep 17 00:00:00 2001 From: Pancakes Date: Fri, 7 Jun 2024 18:27:59 -0400 Subject: [PATCH 06/23] Run go fmt on everything Signed-off-by: Pancakes --- api/savedata/delete.go | 2 +- api/savedata/system.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/api/savedata/delete.go b/api/savedata/delete.go index 7285328..71ac023 100644 --- a/api/savedata/delete.go +++ b/api/savedata/delete.go @@ -48,6 +48,6 @@ func Delete(uuid []byte, datatype, slot int) error { if err != nil { return err } - + return nil } diff --git a/api/savedata/system.go b/api/savedata/system.go index 104df7d..99aeb68 100644 --- a/api/savedata/system.go +++ b/api/savedata/system.go @@ -45,4 +45,4 @@ func System(uuid []byte) (defs.SystemSaveData, error) { } return system, nil -} \ No newline at end of file +} From 9d8c6b88bf58d0482926622a9223fbfaf5b4f9bf Mon Sep 17 00:00:00 2001 From: Pancakes Date: Fri, 7 Jun 2024 18:30:22 -0400 Subject: [PATCH 07/23] Consistent styling on TODO comments --- api/common.go | 4 ++-- api/endpoints.go | 6 +++--- api/savedata/system.go | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/api/common.go b/api/common.go index a8496ed..080b5dd 100644 --- a/api/common.go +++ b/api/common.go @@ -53,8 +53,8 @@ func Init(mux *http.ServeMux) error { // savedata mux.HandleFunc("POST /savedata/update", legacyHandleSaveData) - mux.HandleFunc("GET /savedata/delete", legacyHandleSaveData) // TODO use deleteSystemSave - mux.HandleFunc("POST /savedata/clear", legacyHandleSaveData) // TODO use clearSessionData + mux.HandleFunc("GET /savedata/delete", legacyHandleSaveData) // TODO: use deleteSystemSave + mux.HandleFunc("POST /savedata/clear", legacyHandleSaveData) // TODO: use clearSessionData mux.HandleFunc("GET /savedata/newclear", legacyHandleNewClear) // new session diff --git a/api/endpoints.go b/api/endpoints.go index 4e4fa90..50bb040 100644 --- a/api/endpoints.go +++ b/api/endpoints.go @@ -217,7 +217,7 @@ const legacyClientSessionId = "LEGACY_CLIENT" save = session var active bool - active, err = db.IsActiveSession(uuid, legacyClientSessionId) //TODO unfinished, read token from query + active, err = db.IsActiveSession(uuid, legacyClientSessionId) // TODO: unfinished, read token from query if err != nil { httpError(w, r, fmt.Errorf("failed to check active session: %s", err), http.StatusBadRequest) return @@ -304,7 +304,7 @@ func deleteSystemSave(w http.ResponseWriter, r *http.Request) { } var active bool - active, err = db.IsActiveSession(uuid, legacyClientSessionId) //TODO unfinished, read token from query + active, err = db.IsActiveSession(uuid, legacyClientSessionId) // TODO: unfinished, read token from query if err != nil { httpError(w, r, fmt.Errorf("failed to check active session: %s", err), http.StatusInternalServerError) return @@ -662,7 +662,7 @@ func handleGetSystemData(w http.ResponseWriter, r *http.Request) { return } - //TODO apply vouchers + // TODO: apply vouchers writeJSON(w, r, save) } diff --git a/api/savedata/system.go b/api/savedata/system.go index 99aeb68..75735e6 100644 --- a/api/savedata/system.go +++ b/api/savedata/system.go @@ -14,7 +14,7 @@ func System(uuid []byte) (defs.SystemSaveData, error) { return system, err } - // TODO this should be a transaction + // TODO: this should be a transaction compensations, err := db.FetchAndClaimAccountCompensations(uuid) if err != nil { return system, fmt.Errorf("failed to fetch compensations: %s", err) From 1448b6f14d0be729c4ff64ba102e314d1b54d463 Mon Sep 17 00:00:00 2001 From: Frederico Santos Date: Sat, 8 Jun 2024 00:50:01 +0100 Subject: [PATCH 08/23] feat: Add ChallengeData to SessionSaveData struct (#34) This commit adds the `ChallengeData` struct to the `SessionSaveData` struct. The `ChallengeData` struct includes fields for `id`, `value`, and `severity`. This change allows for storing challenge data in the session save data. --- defs/savedata.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/defs/savedata.go b/defs/savedata.go index 1d63603..094ffc5 100644 --- a/defs/savedata.go +++ b/defs/savedata.go @@ -107,6 +107,13 @@ type SessionSaveData struct { Trainer TrainerData `json:"trainer"` GameVersion string `json:"gameVersion"` Timestamp int `json:"timestamp"` + Challenges []ChallengeData `json:"challenges"` +} + +type ChallengeData struct { + Id int `json:"id"` + Value int `json:"value"` + Severity int `json:"severity"` } type GameMode int From 593c2f82ea6935bf78fc96629939dad1fbc1a60b Mon Sep 17 00:00:00 2001 From: Pancakes Date: Fri, 7 Jun 2024 21:10:57 -0400 Subject: [PATCH 09/23] Remove recordNewDaily --- api/daily/common.go | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/api/daily/common.go b/api/daily/common.go index c340917..9c1df94 100644 --- a/api/daily/common.go +++ b/api/daily/common.go @@ -61,7 +61,7 @@ func Init() error { secret = newSecret } - seed, err := recordNewDaily() + seed, err := db.TryAddDailyRun(Seed()) if err != nil { log.Print(err) } @@ -71,7 +71,7 @@ func Init() error { _, err = scheduler.AddFunc("@daily", func() { time.Sleep(time.Second) - seed, err = recordNewDaily() + seed, err = db.TryAddDailyRun(Seed()) if err != nil { log.Printf("error while recording new daily: %s", err) } else { @@ -99,7 +99,3 @@ func deriveSeed(seedTime time.Time) []byte { return hashedSeed[:] } - -func recordNewDaily() (string, error) { - return db.TryAddDailyRun(Seed()) -} From 16340858bba9118752e49615f55a10aa055de4ad Mon Sep 17 00:00:00 2001 From: Pancakes Date: Fri, 7 Jun 2024 22:23:02 -0400 Subject: [PATCH 10/23] Add new save endpoints --- api/common.go | 17 ++-- api/endpoints.go | 178 +++++++++++++++++----------------------- api/savedata/delete.go | 2 - api/savedata/session.go | 28 ++++++- api/savedata/system.go | 33 +++++++- 5 files changed, 146 insertions(+), 112 deletions(-) diff --git a/api/common.go b/api/common.go index 080b5dd..d67f0bf 100644 --- a/api/common.go +++ b/api/common.go @@ -52,16 +52,23 @@ func Init(mux *http.ServeMux) error { mux.HandleFunc("GET /game/classicsessioncount", handleGameClassicSessionCount) // savedata - mux.HandleFunc("POST /savedata/update", legacyHandleSaveData) - mux.HandleFunc("GET /savedata/delete", legacyHandleSaveData) // TODO: use deleteSystemSave + mux.HandleFunc("POST /savedata/update", legacyHandleSaveData) // DEPRECATED: use PUT method + mux.HandleFunc("GET /savedata/delete", legacyHandleSaveData) // DEPRECATED: use DELETE method mux.HandleFunc("POST /savedata/clear", legacyHandleSaveData) // TODO: use clearSessionData mux.HandleFunc("GET /savedata/newclear", legacyHandleNewClear) // new session - mux.HandleFunc("POST /savedata/updateall", handleUpdateAll) + mux.HandleFunc("POST /savedata/updateall", handleUpdateAll) // DEPRECATED: use PUT method + mux.HandleFunc("PUT /savedata/updateall", handleUpdateAll) + + mux.HandleFunc("GET /savedata/system", handleSystem) + mux.HandleFunc("PUT /savedata/system", handleSystem) + mux.HandleFunc("DELETE /savedata/system", handleSystem) mux.HandleFunc("POST /savedata/system/verify", handleSystemVerify) - mux.HandleFunc("GET /savedata/system", handleGetSystemData) - mux.HandleFunc("GET /savedata/session", handleGetSessionData) + + mux.HandleFunc("GET /savedata/session", handleSession) + mux.HandleFunc("PUT /savedata/session", handleSession) + mux.HandleFunc("DELETE /savedata/session", handleSession) // daily mux.HandleFunc("GET /daily/seed", handleDailySeed) diff --git a/api/endpoints.go b/api/endpoints.go index 50bb040..a4aa8fa 100644 --- a/api/endpoints.go +++ b/api/endpoints.go @@ -146,7 +146,7 @@ func handleGameClassicSessionCount(w http.ResponseWriter, r *http.Request) { w.Write([]byte(strconv.Itoa(classicSessionCount))) } -func handleGetSessionData(w http.ResponseWriter, r *http.Request) { +func handleSession(w http.ResponseWriter, r *http.Request) { uuid, err := uuidFromRequest(r) if err != nil { httpError(w, r, err, http.StatusBadRequest) @@ -173,18 +173,44 @@ func handleGetSessionData(w http.ResponseWriter, r *http.Request) { return } - save, err := savedata.Session(uuid, slot) - if errors.Is(err, sql.ErrNoRows) { - http.Error(w, err.Error(), http.StatusNotFound) - return - } + switch r.Method { + case "GET": + save, err := savedata.GetSession(uuid, slot) + if errors.Is(err, sql.ErrNoRows) { + http.Error(w, err.Error(), http.StatusNotFound) + return + } + + if err != nil { + httpError(w, r, err, http.StatusInternalServerError) + return + } + + writeJSON(w, r, save) + case "PUT": + var session defs.SessionSaveData + err = json.NewDecoder(r.Body).Decode(&session) + if err != nil { + httpError(w, r, fmt.Errorf("failed to decode request body: %s", err), http.StatusBadRequest) + return + } - if err != nil { - httpError(w, r, err, http.StatusInternalServerError) - return - } + err = savedata.PutSession(uuid, slot, session) + if err != nil { + httpError(w, r, fmt.Errorf("failed to put session data: %s", err), http.StatusInternalServerError) + return + } - writeJSON(w, r, save) + w.WriteHeader(http.StatusOK) + case "DELETE": + err := savedata.DeleteSession(uuid, slot) + if err != nil { + httpError(w, r, err, http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusOK) + } } const legacyClientSessionId = "LEGACY_CLIENT" @@ -276,87 +302,7 @@ const legacyClientSessionId = "LEGACY_CLIENT" jsonResponse(w, r, response) } - -// FIXME UNFINISHED!!! -func deleteSystemSave(w http.ResponseWriter, r *http.Request) { - uuid, err := uuidFromRequest(r) - if err != nil { - httpError(w, r, err, http.StatusBadRequest) - return - } - - datatype := 0 - if r.URL.Query().Has("datatype") { - datatype, err = strconv.Atoi(r.URL.Query().Get("datatype")) - if err != nil { - httpError(w, r, err, http.StatusBadRequest) - return - } - } - - var slot int - if r.URL.Query().Has("slot") { - slot, err = strconv.Atoi(r.URL.Query().Get("slot")) - if err != nil { - httpError(w, r, err, http.StatusBadRequest) - return - } - } - - var active bool - active, err = db.IsActiveSession(uuid, legacyClientSessionId) // TODO: unfinished, read token from query - if err != nil { - httpError(w, r, fmt.Errorf("failed to check active session: %s", err), http.StatusInternalServerError) - return - } - - if !active { - httpError(w, r, fmt.Errorf("session out of date: not active"), http.StatusBadRequest) - return - } - - var trainerId, secretId int - - if r.URL.Query().Has("trainerId") && r.URL.Query().Has("secretId") { - trainerId, err = strconv.Atoi(r.URL.Query().Get("trainerId")) - if err != nil { - httpError(w, r, err, http.StatusBadRequest) - return - } - - secretId, err = strconv.Atoi(r.URL.Query().Get("secretId")) - if err != nil { - httpError(w, r, err, http.StatusBadRequest) - return - } - } - - storedTrainerId, storedSecretId, err := db.FetchTrainerIds(uuid) - if err != nil { - httpError(w, r, err, http.StatusInternalServerError) - return - } - - if storedTrainerId > 0 || storedSecretId > 0 { - if trainerId != storedTrainerId || secretId != storedSecretId { - httpError(w, r, fmt.Errorf("session out of date: stored trainer or secret ID does not match"), http.StatusBadRequest) - return - } - } else { - if err := db.UpdateTrainerIds(trainerId, secretId, uuid); err != nil { - httpError(w, r, err, http.StatusInternalServerError) - return - } - } - - err = savedata.Delete(uuid, datatype, slot) - if err != nil { - httpError(w, r, err, http.StatusInternalServerError) - return - } - - w.WriteHeader(http.StatusOK) -}*/ +*/ func legacyHandleSaveData(w http.ResponseWriter, r *http.Request) { uuid, err := uuidFromRequest(r) @@ -633,7 +579,7 @@ func handleSystemVerify(w http.ResponseWriter, r *http.Request) { writeJSON(w, r, response) } -func handleGetSystemData(w http.ResponseWriter, r *http.Request) { +func handleSystem(w http.ResponseWriter, r *http.Request) { uuid, err := uuidFromRequest(r) if err != nil { httpError(w, r, err, http.StatusBadRequest) @@ -651,20 +597,46 @@ func handleGetSystemData(w http.ResponseWriter, r *http.Request) { return } - save, err := savedata.System(uuid) - if err != nil { - if errors.Is(err, sql.ErrNoRows) { - http.Error(w, err.Error(), http.StatusNotFound) - } else { - httpError(w, r, err, http.StatusInternalServerError) + switch r.Method { + case "GET": + save, err := savedata.GetSystem(uuid) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + http.Error(w, err.Error(), http.StatusNotFound) + } else { + httpError(w, r, err, http.StatusInternalServerError) + } + + return + } + + // TODO: apply vouchers + + writeJSON(w, r, save) + case "PUT": + var system defs.SystemSaveData + err = json.NewDecoder(r.Body).Decode(&system) + if err != nil { + httpError(w, r, fmt.Errorf("failed to decode request body: %s", err), http.StatusBadRequest) + return } - return - } + err = savedata.PutSystem(uuid, system) + if err != nil { + httpError(w, r, fmt.Errorf("failed to put system data: %s", err), http.StatusInternalServerError) + return + } - // TODO: apply vouchers + w.WriteHeader(http.StatusOK) + case "DELETE": + err := savedata.DeleteSystem(uuid) + if err != nil { + httpError(w, r, err, http.StatusInternalServerError) + return + } - writeJSON(w, r, save) + w.WriteHeader(http.StatusOK) + } } func legacyHandleNewClear(w http.ResponseWriter, r *http.Request) { diff --git a/api/savedata/delete.go b/api/savedata/delete.go index 71ac023..5d98756 100644 --- a/api/savedata/delete.go +++ b/api/savedata/delete.go @@ -33,8 +33,6 @@ func Delete(uuid []byte, datatype, slot int) error { } switch datatype { - case 0: // System - err = db.DeleteSystemSaveData(uuid) case 1: // Session if slot < 0 || slot >= defs.SessionSlotCount { err = fmt.Errorf("slot id %d out of range", slot) diff --git a/api/savedata/session.go b/api/savedata/session.go index d6d54a8..ae91c5b 100644 --- a/api/savedata/session.go +++ b/api/savedata/session.go @@ -7,7 +7,7 @@ import ( "github.com/pagefaultgames/rogueserver/defs" ) -func Session(uuid []byte, slot int) (defs.SessionSaveData, error) { +func GetSession(uuid []byte, slot int) (defs.SessionSaveData, error) { var session defs.SessionSaveData if slot < 0 || slot >= defs.SessionSlotCount { @@ -22,3 +22,29 @@ func Session(uuid []byte, slot int) (defs.SessionSaveData, error) { return session, nil } + +func PutSession(uuid []byte, slot int, data defs.SessionSaveData) error { + if slot < 0 || slot >= defs.SessionSlotCount { + return fmt.Errorf("slot id %d out of range", slot) + } + + err := db.StoreSessionSaveData(uuid, data, slot) + if err != nil { + return err + } + + return nil +} + +func DeleteSession(uuid []byte, slot int) error { + if slot < 0 || slot >= defs.SessionSlotCount { + return fmt.Errorf("slot id %d out of range", slot) + } + + err := db.DeleteSessionSaveData(uuid, slot) + if err != nil { + return err + } + + return nil +} diff --git a/api/savedata/system.go b/api/savedata/system.go index 75735e6..1f9c5d2 100644 --- a/api/savedata/system.go +++ b/api/savedata/system.go @@ -8,7 +8,7 @@ import ( "github.com/pagefaultgames/rogueserver/defs" ) -func System(uuid []byte) (defs.SystemSaveData, error) { +func GetSystem(uuid []byte) (defs.SystemSaveData, error) { system, err := db.ReadSystemSaveData(uuid) if err != nil { return system, err @@ -46,3 +46,34 @@ func System(uuid []byte) (defs.SystemSaveData, error) { return system, nil } + +func PutSystem(uuid []byte, data defs.SystemSaveData) error { + if data.TrainerId == 0 && data.SecretId == 0 { + return fmt.Errorf("invalid system data") + } + + if data.GameVersion != "1.0.4" { + return fmt.Errorf("client version out of date") + } + + err := db.UpdateAccountStats(uuid, data.GameStats, data.VoucherCounts) + if err != nil { + return fmt.Errorf("failed to update account stats: %s", err) + } + + err = db.DeleteClaimedAccountCompensations(uuid) + if err != nil { + return fmt.Errorf("failed to delete claimed compensations: %s", err) + } + + return db.StoreSystemSaveData(uuid, data) +} + +func DeleteSystem(uuid []byte) error { + err := db.DeleteSystemSaveData(uuid) + if err != nil { + return err + } + + return nil +} From f6743743fa393e242272b6f767f9430e9b93fa2b Mon Sep 17 00:00:00 2001 From: Pancakes Date: Fri, 7 Jun 2024 22:38:24 -0400 Subject: [PATCH 11/23] Clean up session handlers --- api/common.go | 4 ++-- api/endpoints.go | 25 +++++++++++++------------ api/savedata/session.go | 19 +------------------ 3 files changed, 16 insertions(+), 32 deletions(-) diff --git a/api/common.go b/api/common.go index d67f0bf..7c7094d 100644 --- a/api/common.go +++ b/api/common.go @@ -53,8 +53,8 @@ func Init(mux *http.ServeMux) error { // savedata mux.HandleFunc("POST /savedata/update", legacyHandleSaveData) // DEPRECATED: use PUT method - mux.HandleFunc("GET /savedata/delete", legacyHandleSaveData) // DEPRECATED: use DELETE method - mux.HandleFunc("POST /savedata/clear", legacyHandleSaveData) // TODO: use clearSessionData + mux.HandleFunc("GET /savedata/delete", legacyHandleSaveData) // DEPRECATED: use DELETE method + mux.HandleFunc("POST /savedata/clear", legacyHandleSaveData) // TODO: use clearSessionData mux.HandleFunc("GET /savedata/newclear", legacyHandleNewClear) // new session diff --git a/api/endpoints.go b/api/endpoints.go index a4aa8fa..3894067 100644 --- a/api/endpoints.go +++ b/api/endpoints.go @@ -153,13 +153,14 @@ func handleSession(w http.ResponseWriter, r *http.Request) { return } - var slot int - if r.URL.Query().Has("slot") { - slot, err = strconv.Atoi(r.URL.Query().Get("slot")) - if err != nil { - httpError(w, r, err, http.StatusBadRequest) - return - } + slot, err := strconv.Atoi(r.URL.Query().Get("slot")) + if err != nil { + httpError(w, r, err, http.StatusBadRequest) + return + } + + if slot < 0 || slot >= defs.SessionSlotCount { + httpError(w, r, fmt.Errorf("slot id %d out of range", slot), http.StatusBadRequest) } if !r.URL.Query().Has("clientSessionId") { @@ -180,12 +181,12 @@ func handleSession(w http.ResponseWriter, r *http.Request) { http.Error(w, err.Error(), http.StatusNotFound) return } - + if err != nil { httpError(w, r, err, http.StatusInternalServerError) return } - + writeJSON(w, r, save) case "PUT": var session defs.SessionSaveData @@ -606,12 +607,12 @@ func handleSystem(w http.ResponseWriter, r *http.Request) { } else { httpError(w, r, err, http.StatusInternalServerError) } - + return } - + // TODO: apply vouchers - + writeJSON(w, r, save) case "PUT": var system defs.SystemSaveData diff --git a/api/savedata/session.go b/api/savedata/session.go index ae91c5b..97cc5d5 100644 --- a/api/savedata/session.go +++ b/api/savedata/session.go @@ -1,21 +1,12 @@ package savedata import ( - "fmt" - "github.com/pagefaultgames/rogueserver/db" "github.com/pagefaultgames/rogueserver/defs" ) func GetSession(uuid []byte, slot int) (defs.SessionSaveData, error) { - var session defs.SessionSaveData - - if slot < 0 || slot >= defs.SessionSlotCount { - return session, fmt.Errorf("slot id %d out of range", slot) - } - - var err error - session, err = db.ReadSessionSaveData(uuid, slot) + session, err := db.ReadSessionSaveData(uuid, slot) if err != nil { return session, err } @@ -24,10 +15,6 @@ func GetSession(uuid []byte, slot int) (defs.SessionSaveData, error) { } func PutSession(uuid []byte, slot int, data defs.SessionSaveData) error { - if slot < 0 || slot >= defs.SessionSlotCount { - return fmt.Errorf("slot id %d out of range", slot) - } - err := db.StoreSessionSaveData(uuid, data, slot) if err != nil { return err @@ -37,10 +24,6 @@ func PutSession(uuid []byte, slot int, data defs.SessionSaveData) error { } func DeleteSession(uuid []byte, slot int) error { - if slot < 0 || slot >= defs.SessionSlotCount { - return fmt.Errorf("slot id %d out of range", slot) - } - err := db.DeleteSessionSaveData(uuid, slot) if err != nil { return err From 16b73c71303ffbf442945c7c9bd017339cb199fe Mon Sep 17 00:00:00 2001 From: Pancakes Date: Fri, 7 Jun 2024 22:58:50 -0400 Subject: [PATCH 12/23] Put SystemVerify request struct above response --- api/endpoints.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/api/endpoints.go b/api/endpoints.go index 3894067..54edbc0 100644 --- a/api/endpoints.go +++ b/api/endpoints.go @@ -519,15 +519,15 @@ func handleUpdateAll(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) } +type SystemVerifyRequest struct { + ClientSessionId string `json:"clientSessionId"` +} + type SystemVerifyResponse struct { Valid bool `json:"valid"` SystemData *defs.SystemSaveData `json:"systemData"` } -type SystemVerifyRequest struct { - ClientSessionId string `json:"clientSessionId"` -} - func handleSystemVerify(w http.ResponseWriter, r *http.Request) { uuid, err := uuidFromRequest(r) if err != nil { From 46f6127aacf91fe641fe561b6eae37e0bd20248c Mon Sep 17 00:00:00 2001 From: Pancakes Date: Fri, 7 Jun 2024 23:10:27 -0400 Subject: [PATCH 13/23] Use NoContent status code for DELETE and PUT success --- api/endpoints.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/api/endpoints.go b/api/endpoints.go index 54edbc0..0d3eea9 100644 --- a/api/endpoints.go +++ b/api/endpoints.go @@ -202,7 +202,7 @@ func handleSession(w http.ResponseWriter, r *http.Request) { return } - w.WriteHeader(http.StatusOK) + w.WriteHeader(http.StatusNoContent) case "DELETE": err := savedata.DeleteSession(uuid, slot) if err != nil { @@ -210,7 +210,7 @@ func handleSession(w http.ResponseWriter, r *http.Request) { return } - w.WriteHeader(http.StatusOK) + w.WriteHeader(http.StatusNoContent) } } @@ -628,7 +628,7 @@ func handleSystem(w http.ResponseWriter, r *http.Request) { return } - w.WriteHeader(http.StatusOK) + w.WriteHeader(http.StatusNoContent) case "DELETE": err := savedata.DeleteSystem(uuid) if err != nil { @@ -636,7 +636,7 @@ func handleSystem(w http.ResponseWriter, r *http.Request) { return } - w.WriteHeader(http.StatusOK) + w.WriteHeader(http.StatusNoContent) } } From e4a9311fe4c02214e512919d444a893eacf840c6 Mon Sep 17 00:00:00 2001 From: Pancakes Date: Fri, 7 Jun 2024 23:11:09 -0400 Subject: [PATCH 14/23] Add missing return --- api/endpoints.go | 1 + 1 file changed, 1 insertion(+) diff --git a/api/endpoints.go b/api/endpoints.go index 0d3eea9..e56878f 100644 --- a/api/endpoints.go +++ b/api/endpoints.go @@ -161,6 +161,7 @@ func handleSession(w http.ResponseWriter, r *http.Request) { if slot < 0 || slot >= defs.SessionSlotCount { httpError(w, r, fmt.Errorf("slot id %d out of range", slot), http.StatusBadRequest) + return } if !r.URL.Query().Has("clientSessionId") { From 656a935a6cba73d0676f8d4506d738981360a781 Mon Sep 17 00:00:00 2001 From: Pancakes Date: Fri, 7 Jun 2024 23:16:27 -0400 Subject: [PATCH 15/23] Use Unauthorized status code where applicable --- api/endpoints.go | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/api/endpoints.go b/api/endpoints.go index e56878f..70bacf8 100644 --- a/api/endpoints.go +++ b/api/endpoints.go @@ -43,7 +43,7 @@ import ( func handleAccountInfo(w http.ResponseWriter, r *http.Request) { uuid, err := uuidFromRequest(r) if err != nil { - httpError(w, r, err, http.StatusBadRequest) + httpError(w, r, err, http.StatusUnauthorized) return } @@ -103,7 +103,7 @@ func handleAccountChangePW(w http.ResponseWriter, r *http.Request) { uuid, err := uuidFromRequest(r) if err != nil { - httpError(w, r, err, http.StatusBadRequest) + httpError(w, r, err, http.StatusUnauthorized) return } @@ -125,7 +125,8 @@ func handleAccountLogout(w http.ResponseWriter, r *http.Request) { err = account.Logout(token) if err != nil { - httpError(w, r, err, http.StatusInternalServerError) + // also possible for InternalServerError but that's unlikely unless the server blew up + httpError(w, r, err, http.StatusUnauthorized) return } @@ -149,7 +150,7 @@ func handleGameClassicSessionCount(w http.ResponseWriter, r *http.Request) { func handleSession(w http.ResponseWriter, r *http.Request) { uuid, err := uuidFromRequest(r) if err != nil { - httpError(w, r, err, http.StatusBadRequest) + httpError(w, r, err, http.StatusUnauthorized) return } @@ -221,7 +222,7 @@ const legacyClientSessionId = "LEGACY_CLIENT" /*func clearSessionData(w http.ResponseWriter, r *http.Request) { uuid, err := uuidFromRequest(r) if err != nil { - httpError(w, r, err, http.StatusBadRequest) + httpError(w, r, err, http.StatusUnauthorized) return } @@ -309,7 +310,7 @@ const legacyClientSessionId = "LEGACY_CLIENT" func legacyHandleSaveData(w http.ResponseWriter, r *http.Request) { uuid, err := uuidFromRequest(r) if err != nil { - httpError(w, r, err, http.StatusBadRequest) + httpError(w, r, err, http.StatusUnauthorized) return } @@ -459,7 +460,7 @@ type CombinedSaveData struct { func handleUpdateAll(w http.ResponseWriter, r *http.Request) { uuid, err := uuidFromRequest(r) if err != nil { - httpError(w, r, err, http.StatusBadRequest) + httpError(w, r, err, http.StatusUnauthorized) return } @@ -532,7 +533,7 @@ type SystemVerifyResponse struct { func handleSystemVerify(w http.ResponseWriter, r *http.Request) { uuid, err := uuidFromRequest(r) if err != nil { - httpError(w, r, err, http.StatusBadRequest) + httpError(w, r, err, http.StatusUnauthorized) return } @@ -584,7 +585,7 @@ func handleSystemVerify(w http.ResponseWriter, r *http.Request) { func handleSystem(w http.ResponseWriter, r *http.Request) { uuid, err := uuidFromRequest(r) if err != nil { - httpError(w, r, err, http.StatusBadRequest) + httpError(w, r, err, http.StatusUnauthorized) return } @@ -644,7 +645,7 @@ func handleSystem(w http.ResponseWriter, r *http.Request) { func legacyHandleNewClear(w http.ResponseWriter, r *http.Request) { uuid, err := uuidFromRequest(r) if err != nil { - httpError(w, r, err, http.StatusBadRequest) + httpError(w, r, err, http.StatusUnauthorized) return } From f9cce330f6fa3292f85223b717fc7b464911acfc Mon Sep 17 00:00:00 2001 From: Pancakes Date: Sun, 9 Jun 2024 20:03:27 -0400 Subject: [PATCH 16/23] Rework savedata API --- api/common.go | 18 ++-- api/endpoints.go | 226 +++++++++++++++++------------------------------ 2 files changed, 87 insertions(+), 157 deletions(-) diff --git a/api/common.go b/api/common.go index 7c7094d..246cb27 100644 --- a/api/common.go +++ b/api/common.go @@ -52,23 +52,19 @@ func Init(mux *http.ServeMux) error { mux.HandleFunc("GET /game/classicsessioncount", handleGameClassicSessionCount) // savedata - mux.HandleFunc("POST /savedata/update", legacyHandleSaveData) // DEPRECATED: use PUT method - mux.HandleFunc("GET /savedata/delete", legacyHandleSaveData) // DEPRECATED: use DELETE method - mux.HandleFunc("POST /savedata/clear", legacyHandleSaveData) // TODO: use clearSessionData + mux.HandleFunc("POST /savedata/update", legacyHandleSaveData) + mux.HandleFunc("GET /savedata/delete", legacyHandleSaveData) + mux.HandleFunc("POST /savedata/clear", legacyHandleSaveData) mux.HandleFunc("GET /savedata/newclear", legacyHandleNewClear) + mux.HandleFunc("/savedata/session/{action}", handleSession) + mux.HandleFunc("/savedata/system/{action}", handleSystem) + // new session - mux.HandleFunc("POST /savedata/updateall", handleUpdateAll) // DEPRECATED: use PUT method - mux.HandleFunc("PUT /savedata/updateall", handleUpdateAll) + mux.HandleFunc("POST /savedata/updateall", handleUpdateAll) mux.HandleFunc("GET /savedata/system", handleSystem) - mux.HandleFunc("PUT /savedata/system", handleSystem) - mux.HandleFunc("DELETE /savedata/system", handleSystem) - mux.HandleFunc("POST /savedata/system/verify", handleSystemVerify) - mux.HandleFunc("GET /savedata/session", handleSession) - mux.HandleFunc("PUT /savedata/session", handleSession) - mux.HandleFunc("DELETE /savedata/session", handleSession) // daily mux.HandleFunc("GET /daily/seed", handleDailySeed) diff --git a/api/endpoints.go b/api/endpoints.go index 70bacf8..2d025ce 100644 --- a/api/endpoints.go +++ b/api/endpoints.go @@ -176,8 +176,10 @@ func handleSession(w http.ResponseWriter, r *http.Request) { return } - switch r.Method { - case "GET": + switch r.PathValue("action") { + default: + fallthrough + case "get": save, err := savedata.GetSession(uuid, slot) if errors.Is(err, sql.ErrNoRows) { http.Error(w, err.Error(), http.StatusNotFound) @@ -190,7 +192,7 @@ func handleSession(w http.ResponseWriter, r *http.Request) { } writeJSON(w, r, save) - case "PUT": + case "update": var session defs.SessionSaveData err = json.NewDecoder(r.Body).Decode(&session) if err != nil { @@ -204,108 +206,48 @@ func handleSession(w http.ResponseWriter, r *http.Request) { return } - w.WriteHeader(http.StatusNoContent) - case "DELETE": - err := savedata.DeleteSession(uuid, slot) + w.WriteHeader(http.StatusOK) + case "clear": + var session defs.SessionSaveData + err = json.NewDecoder(r.Body).Decode(&session) if err != nil { - httpError(w, r, err, http.StatusInternalServerError) + httpError(w, r, fmt.Errorf("failed to decode request body: %s", err), http.StatusBadRequest) return } - w.WriteHeader(http.StatusNoContent) - } -} - -const legacyClientSessionId = "LEGACY_CLIENT" - -// FIXME UNFINISHED!!! -/*func clearSessionData(w http.ResponseWriter, r *http.Request) { - uuid, err := uuidFromRequest(r) - if err != nil { - httpError(w, r, err, http.StatusUnauthorized) - return - } - - var slot int - if r.URL.Query().Has("slot") { - slot, err = strconv.Atoi(r.URL.Query().Get("slot")) + seed, err := db.GetDailyRunSeed() if err != nil { - httpError(w, r, err, http.StatusBadRequest) + httpError(w, r, err, http.StatusInternalServerError) return } - } - - var save any - var session defs.SessionSaveData - err = json.NewDecoder(r.Body).Decode(&session) - if err != nil { - httpError(w, r, fmt.Errorf("failed to decode request body: %s", err), http.StatusBadRequest) - return - } - save = session - - var active bool - active, err = db.IsActiveSession(uuid, legacyClientSessionId) // TODO: unfinished, read token from query - if err != nil { - httpError(w, r, fmt.Errorf("failed to check active session: %s", err), http.StatusBadRequest) - return - } - - var trainerId, secretId int - if r.URL.Query().Has("trainerId") && r.URL.Query().Has("secretId") { - trainerId, err = strconv.Atoi(r.URL.Query().Get("trainerId")) + resp, err := savedata.Clear(uuid, slot, seed, session) if err != nil { - httpError(w, r, err, http.StatusBadRequest) + httpError(w, r, err, http.StatusInternalServerError) return } - secretId, err = strconv.Atoi(r.URL.Query().Get("secretId")) + writeJSON(w, r, resp) + case "newclear": + resp, err := savedata.NewClear(uuid, slot) if err != nil { - httpError(w, r, err, http.StatusBadRequest) + httpError(w, r, fmt.Errorf("failed to read new clear: %s", err), http.StatusInternalServerError) return } - } - - storedTrainerId, storedSecretId, err := db.FetchTrainerIds(uuid) - if err != nil { - httpError(w, r, err, http.StatusInternalServerError) - return - } - if storedTrainerId > 0 || storedSecretId > 0 { - if trainerId != storedTrainerId || secretId != storedSecretId { - httpError(w, r, fmt.Errorf("session out of date: stored trainer or secret ID does not match"), http.StatusBadRequest) - return - } - } else { - err = db.UpdateTrainerIds(trainerId, secretId, uuid) + writeJSON(w, r, resp) + case "delete": + err := savedata.DeleteSession(uuid, slot) if err != nil { - httpError(w, r, fmt.Errorf("unable to update trainer ID: %s", err), http.StatusInternalServerError) + httpError(w, r, err, http.StatusInternalServerError) return } - } - - if !active { - save = savedata.ClearResponse{Error: "session out of date: not active"} - } - - var seed string - seed, err = db.GetDailyRunSeed() - if err != nil { - httpError(w, r, err, http.StatusInternalServerError) - return - } - response, err := savedata.Clear(uuid, slot, seed, save.(defs.SessionSaveData)) - if err != nil { - httpError(w, r, err, http.StatusInternalServerError) - return + w.WriteHeader(http.StatusOK) } - - jsonResponse(w, r, response) } -*/ + +const legacyClientSessionId = "LEGACY_CLIENT" func legacyHandleSaveData(w http.ResponseWriter, r *http.Request) { uuid, err := uuidFromRequest(r) @@ -526,60 +468,8 @@ type SystemVerifyRequest struct { } type SystemVerifyResponse struct { - Valid bool `json:"valid"` - SystemData *defs.SystemSaveData `json:"systemData"` -} - -func handleSystemVerify(w http.ResponseWriter, r *http.Request) { - uuid, err := uuidFromRequest(r) - if err != nil { - httpError(w, r, err, http.StatusUnauthorized) - return - } - - var input SystemVerifyRequest - err = json.NewDecoder(r.Body).Decode(&input) - if err != nil { - httpError(w, r, fmt.Errorf("failed to decode request body: %s", err), http.StatusBadRequest) - return - } - - var active bool - active, err = db.IsActiveSession(uuid, input.ClientSessionId) - if err != nil { - httpError(w, r, fmt.Errorf("failed to check active session: %s", err), http.StatusBadRequest) - return - } - - response := SystemVerifyResponse{ - Valid: active, - } - - // not valid, send server state - if !active { - err = db.UpdateActiveSession(uuid, input.ClientSessionId) - if err != nil { - httpError(w, r, fmt.Errorf("failed to update active session: %s", err), http.StatusBadRequest) - return - } - - var storedSaveData defs.SystemSaveData - storedSaveData, err = db.ReadSystemSaveData(uuid) - if err != nil { - httpError(w, r, fmt.Errorf("failed to read session save data: %s", err), http.StatusInternalServerError) - return - } - - response.SystemData = &storedSaveData - } - - err = db.UpdateAccountLastActivity(uuid) - if err != nil { - httpError(w, r, fmt.Errorf("failed to update account last activity: %s", err), http.StatusInternalServerError) - return - } - - writeJSON(w, r, response) + Valid bool `json:"valid"` + SystemData defs.SystemSaveData `json:"systemData"` } func handleSystem(w http.ResponseWriter, r *http.Request) { @@ -594,14 +484,24 @@ func handleSystem(w http.ResponseWriter, r *http.Request) { return } - err = db.UpdateActiveSession(uuid, r.URL.Query().Get("clientSessionId")) + active, err := db.IsActiveSession(uuid, r.URL.Query().Get("clientSessionId")) if err != nil { - httpError(w, r, fmt.Errorf("failed to update active session: %s", err), http.StatusBadRequest) + httpError(w, r, fmt.Errorf("failed to check active session: %s", err), http.StatusBadRequest) return } - switch r.Method { - case "GET": + switch r.PathValue("action") { + default: + fallthrough + case "get": + if !active { + err = db.UpdateActiveSession(uuid, r.URL.Query().Get("clientSessionId")) + if err != nil { + httpError(w, r, fmt.Errorf("failed to update active session: %s", err), http.StatusBadRequest) + return + } + } + save, err := savedata.GetSystem(uuid) if err != nil { if errors.Is(err, sql.ErrNoRows) { @@ -613,10 +513,13 @@ func handleSystem(w http.ResponseWriter, r *http.Request) { return } - // TODO: apply vouchers - writeJSON(w, r, save) - case "PUT": + case "update": + if !active { + httpError(w, r, fmt.Errorf("session out of date: not active"), http.StatusBadRequest) + return + } + var system defs.SystemSaveData err = json.NewDecoder(r.Body).Decode(&system) if err != nil { @@ -631,14 +534,45 @@ func handleSystem(w http.ResponseWriter, r *http.Request) { } w.WriteHeader(http.StatusNoContent) - case "DELETE": + case "verify": + var input SystemVerifyRequest + err = json.NewDecoder(r.Body).Decode(&input) + if err != nil { + httpError(w, r, fmt.Errorf("failed to decode request body: %s", err), http.StatusBadRequest) + return + } + + response := SystemVerifyResponse{ + Valid: active, + } + + // not valid, send server state + if !active { + err = db.UpdateActiveSession(uuid, input.ClientSessionId) + if err != nil { + httpError(w, r, fmt.Errorf("failed to update active session: %s", err), http.StatusBadRequest) + return + } + + var storedSaveData defs.SystemSaveData + storedSaveData, err = db.ReadSystemSaveData(uuid) + if err != nil { + httpError(w, r, fmt.Errorf("failed to read session save data: %s", err), http.StatusInternalServerError) + return + } + + response.SystemData = storedSaveData + } + + writeJSON(w, r, response) + case "delete": err := savedata.DeleteSystem(uuid) if err != nil { httpError(w, r, err, http.StatusInternalServerError) return } - w.WriteHeader(http.StatusNoContent) + w.WriteHeader(http.StatusOK) } } From 23c2458d8f4acaf75464c148c201da733235da44 Mon Sep 17 00:00:00 2001 From: Pancakes Date: Mon, 10 Jun 2024 14:56:15 -0400 Subject: [PATCH 17/23] Support legacy /savedata/system/verify --- api/endpoints.go | 39 +++++++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/api/endpoints.go b/api/endpoints.go index 2d025ce..e71962a 100644 --- a/api/endpoints.go +++ b/api/endpoints.go @@ -479,15 +479,18 @@ func handleSystem(w http.ResponseWriter, r *http.Request) { return } - if !r.URL.Query().Has("clientSessionId") { - httpError(w, r, fmt.Errorf("missing clientSessionId"), http.StatusBadRequest) - return - } - - active, err := db.IsActiveSession(uuid, r.URL.Query().Get("clientSessionId")) - if err != nil { - httpError(w, r, fmt.Errorf("failed to check active session: %s", err), http.StatusBadRequest) - return + var active bool + if r.URL.Path != "/savedata/system/verify" { + if !r.URL.Query().Has("clientSessionId") { + httpError(w, r, fmt.Errorf("missing clientSessionId"), http.StatusBadRequest) + return + } + + active, err = db.IsActiveSession(uuid, r.URL.Query().Get("clientSessionId")) + if err != nil { + httpError(w, r, fmt.Errorf("failed to check active session: %s", err), http.StatusBadRequest) + return + } } switch r.PathValue("action") { @@ -536,12 +539,20 @@ func handleSystem(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNoContent) case "verify": var input SystemVerifyRequest - err = json.NewDecoder(r.Body).Decode(&input) - if err != nil { - httpError(w, r, fmt.Errorf("failed to decode request body: %s", err), http.StatusBadRequest) - return + if !r.URL.Query().Has("clientSessionId") { + err = json.NewDecoder(r.Body).Decode(&input) + if err != nil { + httpError(w, r, fmt.Errorf("failed to decode request body: %s", err), http.StatusBadRequest) + return + } + } else { + active, err = db.IsActiveSession(uuid, r.URL.Query().Get("clientSessionId")) + if err != nil { + httpError(w, r, fmt.Errorf("failed to check active session: %s", err), http.StatusBadRequest) + return + } } - + response := SystemVerifyResponse{ Valid: active, } From b51f5cbf692240c8eea54470b73c71f99cef106a Mon Sep 17 00:00:00 2001 From: Pancakes Date: Mon, 10 Jun 2024 18:04:48 -0400 Subject: [PATCH 18/23] Fix out of date session in /savedata/system/verify --- api/endpoints.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/api/endpoints.go b/api/endpoints.go index e71962a..25037c9 100644 --- a/api/endpoints.go +++ b/api/endpoints.go @@ -545,6 +545,12 @@ func handleSystem(w http.ResponseWriter, r *http.Request) { httpError(w, r, fmt.Errorf("failed to decode request body: %s", err), http.StatusBadRequest) return } + + active, err = db.IsActiveSession(uuid, input.ClientSessionId) + if err != nil { + httpError(w, r, fmt.Errorf("failed to check active session: %s", err), http.StatusBadRequest) + return + } } else { active, err = db.IsActiveSession(uuid, r.URL.Query().Get("clientSessionId")) if err != nil { From 6b1b80b61ec6f9bfcd88d29955b7fcd63344ee42 Mon Sep 17 00:00:00 2001 From: Pancakes Date: Sat, 15 Jun 2024 22:21:14 -0400 Subject: [PATCH 19/23] Remove savedata backwards compatibility code --- api/common.go | 8 -- api/endpoints.go | 229 ++++------------------------------------ api/savedata/session.go | 2 +- api/savedata/system.go | 2 +- 4 files changed, 20 insertions(+), 221 deletions(-) diff --git a/api/common.go b/api/common.go index 246cb27..96119f3 100644 --- a/api/common.go +++ b/api/common.go @@ -52,20 +52,12 @@ func Init(mux *http.ServeMux) error { mux.HandleFunc("GET /game/classicsessioncount", handleGameClassicSessionCount) // savedata - mux.HandleFunc("POST /savedata/update", legacyHandleSaveData) - mux.HandleFunc("GET /savedata/delete", legacyHandleSaveData) - mux.HandleFunc("POST /savedata/clear", legacyHandleSaveData) - mux.HandleFunc("GET /savedata/newclear", legacyHandleNewClear) - mux.HandleFunc("/savedata/session/{action}", handleSession) mux.HandleFunc("/savedata/system/{action}", handleSystem) // new session mux.HandleFunc("POST /savedata/updateall", handleUpdateAll) - mux.HandleFunc("GET /savedata/system", handleSystem) - mux.HandleFunc("GET /savedata/session", handleSession) - // daily mux.HandleFunc("GET /daily/seed", handleDailySeed) mux.HandleFunc("GET /daily/rankings", handleDailyRankings) diff --git a/api/endpoints.go b/api/endpoints.go index 25037c9..827e8e2 100644 --- a/api/endpoints.go +++ b/api/endpoints.go @@ -177,8 +177,6 @@ func handleSession(w http.ResponseWriter, r *http.Request) { } switch r.PathValue("action") { - default: - fallthrough case "get": save, err := savedata.GetSession(uuid, slot) if errors.Is(err, sql.ErrNoRows) { @@ -200,7 +198,7 @@ func handleSession(w http.ResponseWriter, r *http.Request) { return } - err = savedata.PutSession(uuid, slot, session) + err = savedata.UpdateSession(uuid, slot, session) if err != nil { httpError(w, r, fmt.Errorf("failed to put session data: %s", err), http.StatusInternalServerError) return @@ -244,151 +242,10 @@ func handleSession(w http.ResponseWriter, r *http.Request) { } w.WriteHeader(http.StatusOK) - } -} - -const legacyClientSessionId = "LEGACY_CLIENT" - -func legacyHandleSaveData(w http.ResponseWriter, r *http.Request) { - uuid, err := uuidFromRequest(r) - if err != nil { - httpError(w, r, err, http.StatusUnauthorized) - return - } - - datatype := -1 - if r.URL.Query().Has("datatype") { - datatype, err = strconv.Atoi(r.URL.Query().Get("datatype")) - if err != nil { - httpError(w, r, err, http.StatusBadRequest) - return - } - } - - var slot int - if r.URL.Query().Has("slot") { - slot, err = strconv.Atoi(r.URL.Query().Get("slot")) - if err != nil { - httpError(w, r, err, http.StatusBadRequest) - return - } - } - - clientSessionId := r.URL.Query().Get("clientSessionId") - if clientSessionId == "" { - clientSessionId = legacyClientSessionId - } - - var save any - // /savedata/delete specify datatype, but don't expect data in body - if r.URL.Path != "/savedata/delete" { - if datatype == 0 { - var system defs.SystemSaveData - err = json.NewDecoder(r.Body).Decode(&system) - if err != nil { - httpError(w, r, fmt.Errorf("failed to decode request body: %s", err), http.StatusBadRequest) - return - } - - save = system - // /savedata/clear doesn't specify datatype, it is assumed to be 1 (session) - } else if datatype == 1 || r.URL.Path == "/savedata/clear" { - var session defs.SessionSaveData - err = json.NewDecoder(r.Body).Decode(&session) - if err != nil { - httpError(w, r, fmt.Errorf("failed to decode request body: %s", err), http.StatusBadRequest) - return - } - - save = session - } - } - - var active bool - active, err = db.IsActiveSession(uuid, clientSessionId) - if err != nil { - httpError(w, r, fmt.Errorf("failed to check active session: %s", err), http.StatusBadRequest) - return - } - - // TODO: make this not suck - if !active && r.URL.Path != "/savedata/clear" { - httpError(w, r, fmt.Errorf("session out of date: not active"), http.StatusBadRequest) - return - } - - var trainerId, secretId int - - if r.URL.Path != "/savedata/update" || datatype == 1 { - if r.URL.Query().Has("trainerId") && r.URL.Query().Has("secretId") { - trainerId, err = strconv.Atoi(r.URL.Query().Get("trainerId")) - if err != nil { - httpError(w, r, err, http.StatusBadRequest) - return - } - - secretId, err = strconv.Atoi(r.URL.Query().Get("secretId")) - if err != nil { - httpError(w, r, err, http.StatusBadRequest) - return - } - } - } else { - trainerId = save.(defs.SystemSaveData).TrainerId - secretId = save.(defs.SystemSaveData).SecretId - } - - storedTrainerId, storedSecretId, err := db.FetchTrainerIds(uuid) - if err != nil { - httpError(w, r, err, http.StatusInternalServerError) - return - } - - if storedTrainerId > 0 || storedSecretId > 0 { - if trainerId != storedTrainerId || secretId != storedSecretId { - httpError(w, r, fmt.Errorf("session out of date: stored trainer or secret ID does not match"), http.StatusBadRequest) - return - } - } else { - if err := db.UpdateTrainerIds(trainerId, secretId, uuid); err != nil { - httpError(w, r, err, http.StatusInternalServerError) - return - } - } - - switch r.URL.Path { - case "/savedata/update": - err = savedata.Update(uuid, slot, save) - case "/savedata/delete": - err = savedata.Delete(uuid, datatype, slot) - case "/savedata/clear": - if !active { - // TODO: make this not suck - save = savedata.ClearResponse{Error: "session out of date: not active"} - break - } - - var seed string - seed, err = db.GetDailyRunSeed() - if err != nil { - httpError(w, r, err, http.StatusInternalServerError) - return - } - - // doesn't return a save, but it works - save, err = savedata.Clear(uuid, slot, seed, save.(defs.SessionSaveData)) - } - if err != nil { - httpError(w, r, err, http.StatusInternalServerError) - return - } - - if save == nil || r.URL.Path == "/savedata/update" { - w.WriteHeader(http.StatusOK) + default: + httpError(w, r, fmt.Errorf("unknown action"), http.StatusBadRequest) return } - - writeJSON(w, r, save) } type CombinedSaveData struct { @@ -414,7 +271,8 @@ func handleUpdateAll(w http.ResponseWriter, r *http.Request) { } if data.ClientSessionId == "" { - data.ClientSessionId = legacyClientSessionId + httpError(w, r, fmt.Errorf("missing clientSessionId"), http.StatusBadRequest) + return } var active bool @@ -463,10 +321,6 @@ func handleUpdateAll(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) } -type SystemVerifyRequest struct { - ClientSessionId string `json:"clientSessionId"` -} - type SystemVerifyResponse struct { Valid bool `json:"valid"` SystemData defs.SystemSaveData `json:"systemData"` @@ -480,22 +334,18 @@ func handleSystem(w http.ResponseWriter, r *http.Request) { } var active bool - if r.URL.Path != "/savedata/system/verify" { - if !r.URL.Query().Has("clientSessionId") { - httpError(w, r, fmt.Errorf("missing clientSessionId"), http.StatusBadRequest) - return - } - - active, err = db.IsActiveSession(uuid, r.URL.Query().Get("clientSessionId")) - if err != nil { - httpError(w, r, fmt.Errorf("failed to check active session: %s", err), http.StatusBadRequest) - return - } + if !r.URL.Query().Has("clientSessionId") { + httpError(w, r, fmt.Errorf("missing clientSessionId"), http.StatusBadRequest) + return + } + + active, err = db.IsActiveSession(uuid, r.URL.Query().Get("clientSessionId")) + if err != nil { + httpError(w, r, fmt.Errorf("failed to check active session: %s", err), http.StatusBadRequest) + return } switch r.PathValue("action") { - default: - fallthrough case "get": if !active { err = db.UpdateActiveSession(uuid, r.URL.Query().Get("clientSessionId")) @@ -530,7 +380,7 @@ func handleSystem(w http.ResponseWriter, r *http.Request) { return } - err = savedata.PutSystem(uuid, system) + err = savedata.UpdateSystem(uuid, system) if err != nil { httpError(w, r, fmt.Errorf("failed to put system data: %s", err), http.StatusInternalServerError) return @@ -538,34 +388,13 @@ func handleSystem(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNoContent) case "verify": - var input SystemVerifyRequest - if !r.URL.Query().Has("clientSessionId") { - err = json.NewDecoder(r.Body).Decode(&input) - if err != nil { - httpError(w, r, fmt.Errorf("failed to decode request body: %s", err), http.StatusBadRequest) - return - } - - active, err = db.IsActiveSession(uuid, input.ClientSessionId) - if err != nil { - httpError(w, r, fmt.Errorf("failed to check active session: %s", err), http.StatusBadRequest) - return - } - } else { - active, err = db.IsActiveSession(uuid, r.URL.Query().Get("clientSessionId")) - if err != nil { - httpError(w, r, fmt.Errorf("failed to check active session: %s", err), http.StatusBadRequest) - return - } - } - response := SystemVerifyResponse{ Valid: active, } // not valid, send server state if !active { - err = db.UpdateActiveSession(uuid, input.ClientSessionId) + err = db.UpdateActiveSession(uuid, r.URL.Query().Get("clientSessionId")) if err != nil { httpError(w, r, fmt.Errorf("failed to update active session: %s", err), http.StatusBadRequest) return @@ -590,32 +419,10 @@ func handleSystem(w http.ResponseWriter, r *http.Request) { } w.WriteHeader(http.StatusOK) - } -} - -func legacyHandleNewClear(w http.ResponseWriter, r *http.Request) { - uuid, err := uuidFromRequest(r) - if err != nil { - httpError(w, r, err, http.StatusUnauthorized) - return - } - - var slot int - if r.URL.Query().Has("slot") { - slot, err = strconv.Atoi(r.URL.Query().Get("slot")) - if err != nil { - httpError(w, r, err, http.StatusBadRequest) - return - } - } - - newClear, err := savedata.NewClear(uuid, slot) - if err != nil { - httpError(w, r, fmt.Errorf("failed to read new clear: %s", err), http.StatusInternalServerError) + default: + httpError(w, r, fmt.Errorf("unknown action"), http.StatusBadRequest) return } - - writeJSON(w, r, newClear) } // daily diff --git a/api/savedata/session.go b/api/savedata/session.go index 97cc5d5..78ff85d 100644 --- a/api/savedata/session.go +++ b/api/savedata/session.go @@ -14,7 +14,7 @@ func GetSession(uuid []byte, slot int) (defs.SessionSaveData, error) { return session, nil } -func PutSession(uuid []byte, slot int, data defs.SessionSaveData) error { +func UpdateSession(uuid []byte, slot int, data defs.SessionSaveData) error { err := db.StoreSessionSaveData(uuid, data, slot) if err != nil { return err diff --git a/api/savedata/system.go b/api/savedata/system.go index 1f9c5d2..3b0a46b 100644 --- a/api/savedata/system.go +++ b/api/savedata/system.go @@ -47,7 +47,7 @@ func GetSystem(uuid []byte) (defs.SystemSaveData, error) { return system, nil } -func PutSystem(uuid []byte, data defs.SystemSaveData) error { +func UpdateSystem(uuid []byte, data defs.SystemSaveData) error { if data.TrainerId == 0 && data.SecretId == 0 { return fmt.Errorf("invalid system data") } From 567cb9c7bea771fab6482cb4299c4daf57682928 Mon Sep 17 00:00:00 2001 From: Pancakes Date: Sun, 16 Jun 2024 18:29:13 -0400 Subject: [PATCH 20/23] Optimize /account/info --- api/account/info.go | 24 +++--------------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/api/account/info.go b/api/account/info.go index 6b3b94e..d76b58f 100644 --- a/api/account/info.go +++ b/api/account/info.go @@ -19,7 +19,6 @@ package account import ( "github.com/pagefaultgames/rogueserver/db" - "github.com/pagefaultgames/rogueserver/defs" ) type InfoResponse struct { @@ -29,24 +28,7 @@ type InfoResponse struct { // /account/info - get account info func Info(username string, uuid []byte) (InfoResponse, error) { - response := InfoResponse{Username: username, LastSessionSlot: -1} - - highest := -1 - for i := 0; i < defs.SessionSlotCount; i++ { - data, err := db.ReadSessionSaveData(uuid, i) - if err != nil { - continue - } - - if data.Timestamp > highest { - highest = data.Timestamp - response.LastSessionSlot = i - } - } - - if response.LastSessionSlot < 0 || response.LastSessionSlot >= defs.SessionSlotCount { - response.LastSessionSlot = -1 - } - - return response, nil + slot, _ := db.GetLatestSessionSaveDataSlot(uuid) + + return InfoResponse{Username: username, LastSessionSlot: slot}, nil } From 72d9e593b9cd81bfe67da2c1401523355a4f511f Mon Sep 17 00:00:00 2001 From: Frederico Santos Date: Thu, 20 Jun 2024 18:45:09 +0100 Subject: [PATCH 21/23] chore: Add additional fields to EggData struct --- defs/savedata.go | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/defs/savedata.go b/defs/savedata.go index 094ffc5..1ef6b80 100644 --- a/defs/savedata.go +++ b/defs/savedata.go @@ -79,10 +79,17 @@ type VoucherUnlocks map[string]int type VoucherCounts map[string]int type EggData struct { - Id int `json:"id"` - GachaType GachaType `json:"gachaType"` - HatchWaves int `json:"hatchWaves"` - Timestamp int `json:"timestamp"` + Id int `json:"id"` + GachaType GachaType `json:"gachaType"` + HatchWaves int `json:"hatchWaves"` + Timestamp int `json:"timestamp"` + Tier int `json:"tier"` + SourceType int `json:"sourceType"` + VariantTier int `json:"variantTier"` + IsShiny bool `json:"isShiny"` + Species int `json:"species"` + EggMoveIndex int `json:"eggMoveIndex"` + OverrideHiddenAbility bool `json:"overrideHiddenAbility"` } type GachaType int From dd00d902115845792096505bcb5c74028c0d1ffc Mon Sep 17 00:00:00 2001 From: Pancakes Date: Sat, 22 Jun 2024 16:10:35 -0400 Subject: [PATCH 22/23] Remove voucher compensation code --- api/savedata/system.go | 36 ------------------------------------ api/savedata/update.go | 5 ----- db/account.go | 37 ------------------------------------- db/db.go | 5 +++++ 4 files changed, 5 insertions(+), 78 deletions(-) diff --git a/api/savedata/system.go b/api/savedata/system.go index 3b0a46b..408ea64 100644 --- a/api/savedata/system.go +++ b/api/savedata/system.go @@ -2,7 +2,6 @@ package savedata import ( "fmt" - "strconv" "github.com/pagefaultgames/rogueserver/db" "github.com/pagefaultgames/rogueserver/defs" @@ -14,36 +13,6 @@ func GetSystem(uuid []byte) (defs.SystemSaveData, error) { return system, err } - // TODO: this should be a transaction - compensations, err := db.FetchAndClaimAccountCompensations(uuid) - if err != nil { - return system, fmt.Errorf("failed to fetch compensations: %s", err) - } - - var needsUpdate bool - for compensationType, amount := range compensations { - system.VoucherCounts[strconv.Itoa(compensationType)] += amount - if amount > 0 { - needsUpdate = true - } - } - - if needsUpdate { - err = db.StoreSystemSaveData(uuid, system) - if err != nil { - return system, fmt.Errorf("failed to update system save data: %s", err) - } - err = db.DeleteClaimedAccountCompensations(uuid) - if err != nil { - return system, fmt.Errorf("failed to delete claimed compensations: %s", err) - } - - err = db.UpdateAccountStats(uuid, system.GameStats, system.VoucherCounts) - if err != nil { - return system, fmt.Errorf("failed to update account stats: %s", err) - } - } - return system, nil } @@ -61,11 +30,6 @@ func UpdateSystem(uuid []byte, data defs.SystemSaveData) error { return fmt.Errorf("failed to update account stats: %s", err) } - err = db.DeleteClaimedAccountCompensations(uuid) - if err != nil { - return fmt.Errorf("failed to delete claimed compensations: %s", err) - } - return db.StoreSystemSaveData(uuid, data) } diff --git a/api/savedata/update.go b/api/savedata/update.go index 6876b57..58eee72 100644 --- a/api/savedata/update.go +++ b/api/savedata/update.go @@ -47,11 +47,6 @@ func Update(uuid []byte, slot int, save any) error { return fmt.Errorf("failed to update account stats: %s", err) } - err = db.DeleteClaimedAccountCompensations(uuid) - if err != nil { - return fmt.Errorf("failed to delete claimed compensations: %s", err) - } - return db.StoreSystemSaveData(uuid, save) case defs.SessionSaveData: // Session diff --git a/db/account.go b/db/account.go index 9ac1ec2..6879c50 100644 --- a/db/account.go +++ b/db/account.go @@ -154,43 +154,6 @@ func SetAccountBanned(uuid []byte, banned bool) error { return nil } -func FetchAndClaimAccountCompensations(uuid []byte) (map[int]int, error) { - var compensations = make(map[int]int) - - results, err := handle.Query("SELECT voucherType, count FROM accountCompensations WHERE uuid = ?", uuid) - if err != nil { - return nil, err - } - - defer results.Close() - - for results.Next() { - var voucherType int - var count int - err := results.Scan(&voucherType, &count) - if err != nil { - return compensations, err - } - compensations[voucherType] = count - } - - _, err = handle.Exec("UPDATE accountCompensations SET claimed = 1 WHERE uuid = ?", uuid) - if err != nil { - return compensations, err - } - - return compensations, nil -} - -func DeleteClaimedAccountCompensations(uuid []byte) error { - _, err := handle.Exec("DELETE FROM accountCompensations WHERE uuid = ? AND claimed = 1", uuid) - if err != nil { - return err - } - - return nil -} - func FetchAccountKeySaltFromUsername(username string) ([]byte, []byte, error) { var key, salt []byte err := handle.QueryRow("SELECT hash, salt FROM accounts WHERE username = ?", username).Scan(&key, &salt) diff --git a/db/db.go b/db/db.go index 0e21c9c..a91a94a 100644 --- a/db/db.go +++ b/db/db.go @@ -92,6 +92,11 @@ func setupDb(tx *sql.Tx) error { `ALTER TABLE sessions DROP COLUMN IF EXISTS active`, `CREATE TABLE IF NOT EXISTS activeClientSessions (uuid BINARY(16) NOT NULL PRIMARY KEY, clientSessionId VARCHAR(32) NOT NULL, FOREIGN KEY (uuid) REFERENCES accounts (uuid) ON DELETE CASCADE ON UPDATE CASCADE)`, + + // ---------------------------------- + // MIGRATION 002 + + `DROP TABLE accountCompensations`, } for _, q := range queries { From 4cac6b6ce8fbdcaf6ef640b8c0d80a8afe2c9a37 Mon Sep 17 00:00:00 2001 From: Frederico Santos Date: Sat, 22 Jun 2024 22:28:14 +0100 Subject: [PATCH 23/23] Update SaveData to handle session and system out of date overwrites (#43) * chore: Update savedata API to handle session out of date errors * chore: Handle session out of date errors in savedata API * chore: Handle session out of date errors in savedata API * chore: Update savedata API to handle session out of date errors --- api/endpoints.go | 56 ++++++++++++++++++++++++++++++++++++++++++++++++ db/savedata.go | 10 +++++++++ 2 files changed, 66 insertions(+) diff --git a/api/endpoints.go b/api/endpoints.go index 827e8e2..2eccdd9 100644 --- a/api/endpoints.go +++ b/api/endpoints.go @@ -198,6 +198,17 @@ func handleSession(w http.ResponseWriter, r *http.Request) { return } + existingSave, err := savedata.GetSession(uuid, slot) + if err != nil && !errors.Is(err, sql.ErrNoRows) { + httpError(w, r, fmt.Errorf("failed to retrieve session save data: %s", err), http.StatusInternalServerError) + return + } else { + if existingSave.Seed == session.Seed && existingSave.WaveIndex > session.WaveIndex { + httpError(w, r, fmt.Errorf("session out of date: existing wave index is greater"), http.StatusBadRequest) + return + } + } + err = savedata.UpdateSession(uuid, slot, session) if err != nil { httpError(w, r, fmt.Errorf("failed to put session data: %s", err), http.StatusInternalServerError) @@ -306,6 +317,34 @@ func handleUpdateAll(w http.ResponseWriter, r *http.Request) { } } + existingPlaytime, err := db.RetrievePlaytime(uuid) + if err != nil && !errors.Is(err, sql.ErrNoRows) { + httpError(w, r, fmt.Errorf("failed to retrieve playtime: %s", err), http.StatusInternalServerError) + return + } else { + playtime, ok := data.System.GameStats.(map[string]interface{})["playTime"].(float64) + if !ok { + httpError(w, r, fmt.Errorf("no playtime found"), http.StatusBadRequest) + return + } + + if float64(existingPlaytime) > playtime { + httpError(w, r, fmt.Errorf("session out of date: existing playtime is greater"), http.StatusBadRequest) + return + } + } + + existingSave, err := savedata.GetSession(uuid, data.SessionSlotId) + if err != nil && !errors.Is(err, sql.ErrNoRows) { + httpError(w, r, fmt.Errorf("failed to retrieve session save data: %s", err), http.StatusInternalServerError) + return + } else { + if existingSave.Seed == data.Session.Seed && existingSave.WaveIndex > data.Session.WaveIndex { + httpError(w, r, fmt.Errorf("session out of date: existing wave index is greater"), http.StatusBadRequest) + return + } + } + err = savedata.Update(uuid, data.SessionSlotId, data.Session) if err != nil { httpError(w, r, err, http.StatusInternalServerError) @@ -380,6 +419,23 @@ func handleSystem(w http.ResponseWriter, r *http.Request) { return } + existingPlaytime, err := db.RetrievePlaytime(uuid) + if err != nil && !errors.Is(err, sql.ErrNoRows) { + httpError(w, r, fmt.Errorf("failed to retrieve playtime: %s", err), http.StatusInternalServerError) + return + } else { + playtime, ok := system.GameStats.(map[string]interface{})["playTime"].(float64) + if !ok { + httpError(w, r, fmt.Errorf("no playtime found"), http.StatusBadRequest) + return + } + + if float64(existingPlaytime) > playtime { + httpError(w, r, fmt.Errorf("session out of date: existing playtime is greater"), http.StatusBadRequest) + return + } + } + err = savedata.UpdateSystem(uuid, system) if err != nil { httpError(w, r, fmt.Errorf("failed to put system data: %s", err), http.StatusInternalServerError) diff --git a/db/savedata.go b/db/savedata.go index bb792c5..10256a3 100644 --- a/db/savedata.go +++ b/db/savedata.go @@ -142,3 +142,13 @@ func DeleteSessionSaveData(uuid []byte, slot int) error { return nil } + +func RetrievePlaytime(uuid []byte) (int, error) { + var playtime int + err := handle.QueryRow("SELECT playTime FROM accountStats WHERE uuid = ?", uuid).Scan(&playtime) + if err != nil { + return 0, err + } + + return playtime, nil +}