0.3.33
This commit is contained in:
@@ -87,6 +87,13 @@ func (s *Server) handleLogin(w http.ResponseWriter, r *http.Request) {
|
||||
writeError(w, http.StatusBadRequest, "device name is too long")
|
||||
return
|
||||
}
|
||||
log := s.loggerFor(r.Context()).With(
|
||||
"emby_client", s.cfg.ClientName,
|
||||
"device", req.DeviceName,
|
||||
"device_id", req.DeviceID,
|
||||
"client_version", clientVersion(r),
|
||||
)
|
||||
log.Info("Emby client registration starting")
|
||||
|
||||
auth, err := s.emby.Authenticate(
|
||||
r.Context(), req.Username, req.Password,
|
||||
@@ -98,7 +105,7 @@ func (s *Server) handleLogin(w http.ResponseWriter, r *http.Request) {
|
||||
if err != nil {
|
||||
// Never echo Emby's body here: a failed sign-in is the one place a wrong
|
||||
// password could be reflected back.
|
||||
s.loggerFor(r.Context()).Warn("sign-in rejected",
|
||||
log.Warn("sign-in rejected",
|
||||
"username", req.Username, "device", req.DeviceName, "device_id", req.DeviceID,
|
||||
"reason", "emby refused the credentials",
|
||||
)
|
||||
@@ -179,6 +186,11 @@ func (s *Server) handleLogin(w http.ResponseWriter, r *http.Request) {
|
||||
if len(created.ReplacedHash) > 0 {
|
||||
_ = s.cache.Delete(r.Context(), cache.SessionKey(hexHash(created.ReplacedHash)))
|
||||
}
|
||||
log.Info("Emby client registration succeeded",
|
||||
"user", sess.Username,
|
||||
"user_id", sess.EmbyUserID,
|
||||
"replaced", len(created.ReplacedHash) > 0,
|
||||
)
|
||||
s.retireSupersededDevices(r.Context(), created.Superseded)
|
||||
// Recorded after the session exists, so a build history can only describe a
|
||||
// television that got as far as signing in.
|
||||
|
||||
@@ -284,6 +284,12 @@ func (s *Server) handleInstallLogin(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// Gateway, not a television: this sign-in is the admin console or the web installer
|
||||
// checking a password, so Emby records it under the gateway's own client name.
|
||||
log := s.loggerFor(r.Context()).With(
|
||||
"emby_client", s.cfg.GatewayClientName,
|
||||
"device", s.installerDeviceName(),
|
||||
"device_id", installerDeviceID,
|
||||
)
|
||||
log.Info("Emby installer authentication starting")
|
||||
auth, err := s.emby.Authenticate(
|
||||
r.Context(), username, password,
|
||||
emby.Credentials{
|
||||
@@ -291,10 +297,11 @@ func (s *Server) handleInstallLogin(w http.ResponseWriter, r *http.Request) {
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
s.loggerFor(r.Context()).Warn("installer Emby authentication failed", "username", username)
|
||||
log.Warn("installer Emby authentication failed", "username", username)
|
||||
s.renderAccessLogin(w, r, "Sign-in failed.", http.StatusUnauthorized, next)
|
||||
return
|
||||
}
|
||||
log.Info("Emby installer authentication succeeded", "username", username, "user_id", auth.User.ID)
|
||||
// Ask before the token is retired below: the fallback lookup needs it. Whether the
|
||||
// answer is wanted depends on where the sign-in was headed, but it is asked either way
|
||||
// so that the cleanup underneath runs on one path rather than two.
|
||||
|
||||
@@ -559,7 +559,9 @@ func (s *Server) playbackSubtitles(
|
||||
) ([]playableSubtitle, string, string, string, string) {
|
||||
started := time.Now()
|
||||
log := s.loggerFor(ctx).With("item", itemID, "force_transcode", forceTranscode,
|
||||
"subtitle_index", subtitleIndex != nil, "resume", millisecondDuration(startTicks/ticksPerMillisecond))
|
||||
"subtitle_index", subtitleIndex != nil, "resume", millisecondDuration(startTicks/ticksPerMillisecond),
|
||||
"emby_client", s.cfg.ClientName, "device", cred.DeviceName, "device_id", cred.DeviceID,
|
||||
"client_version", cred.ClientVersion)
|
||||
log.Log(ctx, serverlogging.LevelTrace, "Emby playback negotiation started")
|
||||
info, err := s.emby.PlaybackInfo(
|
||||
ctx, cred, itemID, startTicks, subtitleIndex, currentPlaySessionID, forceTranscode,
|
||||
|
||||
@@ -47,6 +47,9 @@ type Config struct {
|
||||
// the header travels to whatever Emby does with its logs, and the product name has
|
||||
// no business being the thing that identifies a client to a third party.
|
||||
ClientName string
|
||||
// IgnoredClientName records a legacy MEMBY_CLIENT_NAME override that no longer takes
|
||||
// effect. It is logged at start-up so a stale deployment can be corrected deliberately.
|
||||
IgnoredClientName string
|
||||
|
||||
// GatewayClientName is reported instead for a request the gateway makes on its own
|
||||
// behalf — the library sync, the health probe, device cleanup, and an operator
|
||||
@@ -229,7 +232,8 @@ func Load() (Config, error) {
|
||||
EmbyMediaURL: strings.TrimRight(os.Getenv("MEMBY_EMBY_MEDIA_URL"), "/"),
|
||||
DatabaseURL: os.Getenv("MEMBY_DATABASE_URL"),
|
||||
RedisURL: env("MEMBY_REDIS_URL", "redis://localhost:6379/0"),
|
||||
ClientName: env("MEMBY_CLIENT_NAME", "MbyATV"),
|
||||
ClientName: emby.DefaultClientName,
|
||||
IgnoredClientName: ignoredClientName(),
|
||||
GatewayClientName: env("MEMBY_GATEWAY_CLIENT_NAME", emby.DefaultGatewayClientName),
|
||||
HomeTTL: duration("MEMBY_HOME_TTL", 60*time.Second),
|
||||
ItemTTL: duration("MEMBY_ITEM_TTL", 10*time.Minute),
|
||||
@@ -369,6 +373,14 @@ func secret(key string) (string, error) {
|
||||
return trimmed, nil
|
||||
}
|
||||
|
||||
func ignoredClientName() string {
|
||||
raw := strings.TrimSpace(os.Getenv("MEMBY_CLIENT_NAME"))
|
||||
if raw == "" || raw == emby.DefaultClientName {
|
||||
return ""
|
||||
}
|
||||
return raw
|
||||
}
|
||||
|
||||
func boolean(key string, fallback bool) bool {
|
||||
raw := strings.TrimSpace(os.Getenv(key))
|
||||
if raw == "" {
|
||||
|
||||
@@ -5,6 +5,8 @@ import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ponzischeme89/memby/server/internal/emby"
|
||||
)
|
||||
|
||||
func TestReleasePublishTokenCanComeFromSecretFile(t *testing.T) {
|
||||
@@ -109,3 +111,20 @@ func TestAnalyticsRetentionCannotDropBelowThirtyDays(t *testing.T) {
|
||||
t.Fatalf("analytics retention = %v, want 30 days", cfg.AnalyticsRetention)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLegacyClientNameOverrideIsIgnored(t *testing.T) {
|
||||
t.Setenv("MEMBY_EMBY_URL", "http://emby")
|
||||
t.Setenv("MEMBY_DATABASE_URL", "postgres://memby")
|
||||
t.Setenv("MEMBY_CLIENT_NAME", "Memby")
|
||||
|
||||
cfg, err := Load()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if cfg.ClientName != emby.DefaultClientName {
|
||||
t.Fatalf("client name = %q, want %q", cfg.ClientName, emby.DefaultClientName)
|
||||
}
|
||||
if cfg.IgnoredClientName != "Memby" {
|
||||
t.Fatalf("ignored client name = %q, want Memby", cfg.IgnoredClientName)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,6 +157,10 @@ func (e *APIError) Error() string {
|
||||
return fmt.Sprintf("emby: status %d: %s", e.StatusCode, e.Body)
|
||||
}
|
||||
|
||||
// DefaultClientName is the television identity Memby reports to Emby. It is a wire identity,
|
||||
// not the product name, and every path that creates or refreshes an Emby session must agree.
|
||||
const DefaultClientName = "MbyATV"
|
||||
|
||||
// DefaultGatewayClientName is what Emby records for a request the gateway makes for
|
||||
// itself. It is deliberately not the product name: this travels to whatever Emby does
|
||||
// with its own logs, and it must never read as one of the household's televisions.
|
||||
|
||||
Reference in New Issue
Block a user