0.2.68
This commit is contained in:
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user