aboutsummaryrefslogtreecommitdiff
path: root/internal/admin/server.go
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-09-18 01:50:17 -0400
committerhistoria <[not public]>2026-09-18 01:50:17 -0400
commit87d08f0151e643951769e323995f1c5ed89ee200 (patch)
treedc42a0b30ebb5a00dd7ba505cc4eaa44f4f308bc /internal/admin/server.go
parent32a3c59cebf2a16277e54d715a67adfdbe00b456 (diff)
downloadthehouseoficarus-87d08f0151e643951769e323995f1c5ed89ee200.tar.gz
fix: harden economy recovery, shutdown concurrency, world validation, and inventory-loss paths
Diffstat (limited to 'internal/admin/server.go')
-rw-r--r--internal/admin/server.go29
1 files changed, 21 insertions, 8 deletions
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