This commit is contained in:
ponzischeme89
2026-08-16 12:13:51 +12:00
parent bd3732fba5
commit b9374baaf1
47 changed files with 2793 additions and 364 deletions
+39 -13
View File
@@ -11,6 +11,7 @@ import (
"net/url"
"strings"
"time"
"unicode/utf8"
"github.com/ponzischeme89/memby/server/internal/emby"
)
@@ -84,11 +85,24 @@ func (s *Server) newInstallerSession() (string, error) {
}
func (s *Server) newBrowserSession(purpose string, ttl time.Duration) (string, error) {
payload := make([]byte, 8+16)
return s.newBrowserSessionFor(purpose, ttl, "")
}
// newBrowserSessionFor includes the verified account name in an admin session. It remains
// inside the signed, HttpOnly cookie: the console learns who is at the keyboard from the
// status response, without adding a second identity cookie that JavaScript could alter.
// Sessions minted by older gateways had only the 24-byte prefix and remain valid.
func (s *Server) newBrowserSessionFor(purpose string, ttl time.Duration, username string) (string, error) {
username = strings.TrimSpace(username)
if len(username) > 512 || !utf8.ValidString(username) {
username = ""
}
payload := make([]byte, 8+16+len(username))
binary.BigEndian.PutUint64(payload[:8], uint64(time.Now().Add(ttl).Unix()))
if _, err := rand.Read(payload[8:]); err != nil {
if _, err := rand.Read(payload[8:24]); err != nil {
return "", err
}
copy(payload[24:], username)
signature := s.signInstallerValue(purpose, payload)
return base64.RawURLEncoding.EncodeToString(payload) + "." +
base64.RawURLEncoding.EncodeToString(signature), nil
@@ -98,31 +112,39 @@ func (s *Server) newBrowserSession(purpose string, ttl time.Duration) (string, e
// cookie that is missing, malformed, forged, signed for a different purpose or already
// expired is reported the same way: no session.
func (s *Server) browserSessionExpiry(r *http.Request, purpose string) (time.Time, bool) {
expires, _, ok := s.browserSession(r, purpose)
return expires, ok
}
// browserSession verifies the session once and returns the optional identity carried by
// newer cookies. The empty identity is legitimate for a session issued before it was
// added, so validity is reported separately.
func (s *Server) browserSession(r *http.Request, purpose string) (time.Time, string, bool) {
if len(s.installerSecret()) == 0 {
return time.Time{}, false
return time.Time{}, "", false
}
cookie, err := r.Cookie(installerCookieName)
if err != nil {
return time.Time{}, false
return time.Time{}, "", false
}
parts := strings.Split(cookie.Value, ".")
if len(parts) != 2 {
return time.Time{}, false
return time.Time{}, "", false
}
payload, err := base64.RawURLEncoding.DecodeString(parts[0])
if err != nil || len(payload) != 24 {
return time.Time{}, false
if err != nil || len(payload) < 24 || len(payload) > 536 || !utf8.Valid(payload[24:]) {
return time.Time{}, "", false
}
signature, err := base64.RawURLEncoding.DecodeString(parts[1])
if err != nil || !hmac.Equal(signature, s.signInstallerValue(purpose, payload)) {
return time.Time{}, false
return time.Time{}, "", false
}
expires := int64(binary.BigEndian.Uint64(payload[:8]))
now := time.Now().Unix()
if expires <= now || expires > now+int64(adminSessionTTL/time.Second)+60 {
return time.Time{}, false
return time.Time{}, "", false
}
return time.Unix(expires, 0), true
return time.Unix(expires, 0), strings.TrimSpace(string(payload[24:])), true
}
// validInstallerSession gates the public installer, which an administrator's own session
@@ -149,11 +171,11 @@ func (s *Server) validAdminSession(r *http.Request) bool {
// an operator actually made — see operatorPresent — or an abandoned tab's own polling
// would keep the session alive indefinitely, which is what the TTL exists to stop.
func (s *Server) renewAdminSession(w http.ResponseWriter, r *http.Request) {
expires, ok := s.browserSessionExpiry(r, adminSessionPurpose)
expires, username, ok := s.browserSession(r, adminSessionPurpose)
if !ok || time.Until(expires) > adminRenewWithin {
return
}
session, err := s.newBrowserSession(adminSessionPurpose, adminSessionTTL)
session, err := s.newBrowserSessionFor(adminSessionPurpose, adminSessionTTL, username)
if err != nil {
s.loggerFor(r.Context()).Error("installer session renewal failed", "error", err)
return
@@ -317,7 +339,11 @@ func (s *Server) handleInstallLogin(w http.ResponseWriter, r *http.Request) {
}
purpose, ttl = adminSessionPurpose, adminSessionTTL
}
session, err := s.newBrowserSession(purpose, ttl)
verifiedUsername := strings.TrimSpace(auth.User.Name)
if verifiedUsername == "" {
verifiedUsername = username
}
session, err := s.newBrowserSessionFor(purpose, ttl, verifiedUsername)
if err != nil {
s.loggerFor(r.Context()).Error("installer session generation failed", "error", err)
writeError(w, http.StatusInternalServerError, "could not start installer session")