From 6acbb6448a470a14a56b7b09e212a397c3d9d003 Mon Sep 17 00:00:00 2001 From: Flashfyre Date: Sun, 21 Apr 2024 16:23:25 -0400 Subject: [PATCH] Protect against cross-session overwrites --- api/endpoints.go | 82 ++++++++++++++++++++++++++++++++++++------- api/savedata/clear.go | 5 +-- db/account.go | 19 ++++++++++ 3 files changed, 91 insertions(+), 15 deletions(-) diff --git a/api/endpoints.go b/api/endpoints.go index 58ab6bd..82850da 100644 --- a/api/endpoints.go +++ b/api/endpoints.go @@ -12,12 +12,13 @@ import ( "github.com/pagefaultgames/pokerogue-server/api/account" "github.com/pagefaultgames/pokerogue-server/api/daily" "github.com/pagefaultgames/pokerogue-server/api/savedata" + "github.com/pagefaultgames/pokerogue-server/db" "github.com/pagefaultgames/pokerogue-server/defs" ) type Server struct { Debug bool - Exit *sync.RWMutex + Exit *sync.RWMutex } /* @@ -174,7 +175,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { } save = system - // /savedata/clear doesn't specify datatype, it is assumed to be 1 (session) + // /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) @@ -187,22 +188,77 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { } } + var token []byte + token, err = base64.StdEncoding.DecodeString(r.Header.Get("Authorization")) + if err != nil { + httpError(w, r, fmt.Errorf("failed to decode token: %s", err), http.StatusBadRequest) + return + } + switch r.URL.Path { case "/savedata/get": - save, err = savedata.Get(uuid, datatype, slot) - case "/savedata/update": - err = savedata.Update(uuid, slot, save) - case "/savedata/delete": - err = savedata.Delete(uuid, datatype, slot) - case "/savedata/clear": - s, ok := save.(defs.SessionSaveData) - if !ok { - httpError(w, r, fmt.Errorf("save data is not type SessionSaveData"), http.StatusBadRequest) + err = db.UpdateActiveSession(uuid, token) + if err != nil { + httpError(w, r, fmt.Errorf("failed to update active session: %s", err), http.StatusInternalServerError) return } - // doesn't return a save, but it works - save, err = savedata.Clear(uuid, slot, daily.Seed(), s) + save, err = savedata.Get(uuid, datatype, slot) + case "/savedata/update": + var token []byte + token, err = base64.StdEncoding.DecodeString(r.Header.Get("Authorization")) + if err != nil { + httpError(w, r, fmt.Errorf("failed to decode token: %s", err), http.StatusBadRequest) + return + } + + var active bool + active, err = db.IsActiveSession(token) + 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"), http.StatusBadRequest) + return + } + + err = savedata.Update(uuid, slot, save) + case "/savedata/delete": + var active bool + active, err = db.IsActiveSession(token) + 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"), http.StatusBadRequest) + return + } + + err = savedata.Delete(uuid, datatype, slot) + case "/savedata/clear": + var active bool + active, err = db.IsActiveSession(token) + if err != nil { + httpError(w, r, fmt.Errorf("failed to check active session: %s", err), http.StatusInternalServerError) + return + } + + if active { + s, ok := save.(defs.SessionSaveData) + if !ok { + httpError(w, r, fmt.Errorf("save data is not type SessionSaveData"), http.StatusBadRequest) + return + } + + // doesn't return a save, but it works + save, err = savedata.Clear(uuid, slot, daily.Seed(), s) + } else { + var response savedata.ClearResponse + response.Error = "session out of date" + save = response + } } if err != nil { httpError(w, r, err, http.StatusInternalServerError) diff --git a/api/savedata/clear.go b/api/savedata/clear.go index 9e6426d..7b4a3b6 100644 --- a/api/savedata/clear.go +++ b/api/savedata/clear.go @@ -12,7 +12,8 @@ import ( ) type ClearResponse struct { - Success bool `json:"success"` + Success bool `json:"success"` + Error string `json:"error"` } // /savedata/clear - mark session save data as cleared and delete @@ -34,7 +35,7 @@ func Clear(uuid []byte, slot int, seed string, save defs.SessionSaveData) (Clear if !sessionCompleted { waveCompleted-- } - + err = db.AddOrUpdateAccountDailyRun(uuid, save.Score, waveCompleted) if err != nil { log.Printf("failed to add or update daily run record: %s", err) diff --git a/db/account.go b/db/account.go index d667717..21b7a32 100644 --- a/db/account.go +++ b/db/account.go @@ -175,6 +175,25 @@ func FetchAccountKeySaltFromUsername(username string) ([]byte, []byte, error) { return key, salt, nil } +func IsActiveSession(token []byte) (bool, error) { + var active int + err := handle.QueryRow("SELECT `active` FROM sessions WHERE token = ?", token).Scan(&active) + if err != nil { + return false, err + } + + return active == 1, nil +} + +func UpdateActiveSession(uuid []byte, token []byte) error { + _, err := handle.Exec("UPDATE sessions SET `active` = CASE WHEN token = ? THEN 1 ELSE 0 END WHERE uuid = ?", token, uuid) + if err != nil { + return err + } + + return nil +} + func FetchUUIDFromToken(token []byte) ([]byte, error) { var uuid []byte err := handle.QueryRow("SELECT uuid FROM sessions WHERE token = ? AND expire > UTC_TIMESTAMP()", token).Scan(&uuid)