From 87d08f0151e643951769e323995f1c5ed89ee200 Mon Sep 17 00:00:00 2001 From: historia <[not public]> Date: Fri, 18 Sep 2026 01:50:17 -0400 Subject: fix: harden economy recovery, shutdown concurrency, world validation, and inventory-loss paths --- internal/admin/server.go | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) (limited to 'internal/admin/server.go') diff --git a/internal/admin/server.go b/internal/admin/server.go index 12d090d..d9a0386 100644 --- a/internal/admin/server.go +++ b/internal/admin/server.go @@ -324,19 +324,27 @@ func (s *AdminServer) checkAuth(r *http.Request) bool { if err != nil { return false } - parts := strings.SplitN(cookie.Value, ":", 2) - if len(parts) != 2 { + parts := strings.SplitN(cookie.Value, ":", 3) + if len(parts) != 3 { return false } account := parts[0] - sig := parts[1] + issued, err := strconv.ParseInt(parts[1], 10, 64) + if err != nil { + return false + } + issuedAt := time.Unix(issued, 0) + if time.Since(issuedAt) > adminSessionTTL || time.Until(issuedAt) > time.Minute { + return false + } + sig := parts[2] if !s.isAdminAccount(account) { return false } mac := hmac.New(sha256.New, s.cookieSecret) - mac.Write([]byte(account)) + mac.Write([]byte(account + ":" + parts[1])) expected := hex.EncodeToString(mac.Sum(nil)) return hmac.Equal([]byte(sig), []byte(expected)) } @@ -349,18 +357,24 @@ func (s *AdminServer) isAdminAccount(name string) bool { return acc.Admin } +// adminSessionTTL bounds how long a signed admin cookie is accepted. The +// signature covers the issue time, so a leaked cookie cannot authenticate +// indefinitely and the server can age it out without server-side state. +const adminSessionTTL = 24 * time.Hour + func (s *AdminServer) setAuthCookie(w http.ResponseWriter, account string) { + issued := strconv.FormatInt(time.Now().Unix(), 10) mac := hmac.New(sha256.New, s.cookieSecret) - mac.Write([]byte(account)) + mac.Write([]byte(account + ":" + issued)) sig := hex.EncodeToString(mac.Sum(nil)) http.SetCookie(w, &http.Cookie{ Name: "admin_session", - Value: account + ":" + sig, + Value: account + ":" + issued + ":" + sig, Path: "/", HttpOnly: true, Secure: s.useTLS, SameSite: http.SameSiteStrictMode, - MaxAge: 86400, + MaxAge: int(adminSessionTTL / time.Second), }) } @@ -409,7 +423,6 @@ func (s *AdminServer) handleLogin(w http.ResponseWriter, r *http.Request) { log.Printf("admin login: password rehash save for %s: %v", account, err) } } - s.undoStack.Clear() }) { writeJSONError(w, "game is shutting down", http.StatusServiceUnavailable) return -- cgit v1.2.3