mirror of
https://github.com/navidrome/navidrome.git
synced 2026-08-31 07:30:32 +00:00
fix(transcoding): adapt transcode claims to struct-based auth.Claims
Updated transcode token handling to use the struct-based auth.Claims introduced on master, replacing the previous map[string]any approach. Extended auth.Claims with transcoding-specific fields (MediaID, DirectPlay, UpdatedAt, Channels, SampleRate, BitDepth) and added float64 fallback in ClaimsFromToken for numeric claims that lose their Go type during JWT string serialization. Also added the missing lyrics parameter to all subsonic.New() calls in test files.
This commit is contained in:
parent
004ebea7d0
commit
5af03e1feb
@ -21,6 +21,14 @@ type Claims struct {
|
||||
ID string // "id" - artwork/mediafile ID
|
||||
Format string // "f" - audio format
|
||||
BitRate int // "b" - audio bitrate
|
||||
|
||||
// Transcoding claims
|
||||
MediaID string // "mid" - media file ID
|
||||
DirectPlay bool // "dp" - direct play flag
|
||||
UpdatedAt int64 // "ua" - source file updated-at (Unix seconds)
|
||||
Channels int // "ch" - target channels
|
||||
SampleRate int // "sr" - target sample rate (Hz)
|
||||
BitDepth int // "bd" - target bit depth
|
||||
}
|
||||
|
||||
// ToMap converts Claims to a map[string]any for use with TokenAuth.Encode().
|
||||
@ -54,6 +62,24 @@ func (c Claims) ToMap() map[string]any {
|
||||
if c.BitRate != 0 {
|
||||
m["b"] = c.BitRate
|
||||
}
|
||||
if c.MediaID != "" {
|
||||
m["mid"] = c.MediaID
|
||||
}
|
||||
if c.DirectPlay {
|
||||
m["dp"] = c.DirectPlay
|
||||
}
|
||||
if c.UpdatedAt != 0 {
|
||||
m["ua"] = c.UpdatedAt
|
||||
}
|
||||
if c.Channels != 0 {
|
||||
m["ch"] = c.Channels
|
||||
}
|
||||
if c.SampleRate != 0 {
|
||||
m["sr"] = c.SampleRate
|
||||
}
|
||||
if c.BitDepth != 0 {
|
||||
m["bd"] = c.BitDepth
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
@ -86,9 +112,43 @@ func ClaimsFromToken(token jwt.Token) Claims {
|
||||
if err := token.Get("f", &f); err == nil {
|
||||
c.Format = f
|
||||
}
|
||||
var b int
|
||||
if err := token.Get("b", &b); err == nil {
|
||||
c.BitRate = b
|
||||
if err := token.Get("b", &c.BitRate); err != nil {
|
||||
var bf float64
|
||||
if err := token.Get("b", &bf); err == nil {
|
||||
c.BitRate = int(bf)
|
||||
}
|
||||
}
|
||||
var mid string
|
||||
if err := token.Get("mid", &mid); err == nil {
|
||||
c.MediaID = mid
|
||||
}
|
||||
var dp bool
|
||||
if err := token.Get("dp", &dp); err == nil {
|
||||
c.DirectPlay = dp
|
||||
}
|
||||
if err := token.Get("ua", &c.UpdatedAt); err != nil {
|
||||
var uaf float64
|
||||
if err := token.Get("ua", &uaf); err == nil {
|
||||
c.UpdatedAt = int64(uaf)
|
||||
}
|
||||
}
|
||||
if err := token.Get("ch", &c.Channels); err != nil {
|
||||
var chf float64
|
||||
if err := token.Get("ch", &chf); err == nil {
|
||||
c.Channels = int(chf)
|
||||
}
|
||||
}
|
||||
if err := token.Get("sr", &c.SampleRate); err != nil {
|
||||
var srf float64
|
||||
if err := token.Get("sr", &srf); err == nil {
|
||||
c.SampleRate = int(srf)
|
||||
}
|
||||
}
|
||||
if err := token.Get("bd", &c.BitDepth); err != nil {
|
||||
var bdf float64
|
||||
if err := token.Get("bd", &bdf); err == nil {
|
||||
c.BitDepth = int(bdf)
|
||||
}
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
@ -15,16 +15,6 @@ import (
|
||||
const (
|
||||
tokenTTL = 12 * time.Hour
|
||||
defaultBitrate = 256 // kbps
|
||||
|
||||
// JWT claim keys for transcode params tokens
|
||||
claimMediaID = "mid" // Media file ID
|
||||
claimDirectPlay = "dp" // Direct play flag (bool)
|
||||
claimUpdatedAt = "ua" // Source file updated-at (Unix seconds)
|
||||
claimFormat = "fmt" // Target transcoding format
|
||||
claimBitrate = "br" // Target bitrate (kbps)
|
||||
claimChannels = "ch" // Target channels
|
||||
claimSampleRate = "sr" // Target sample rate (Hz)
|
||||
claimBitDepth = "bd" // Target bit depth
|
||||
)
|
||||
|
||||
func NewDecider(ds model.DataStore) Decider {
|
||||
@ -308,22 +298,22 @@ func (s *deciderService) applyCodecLimitations(ctx context.Context, sourceBitrat
|
||||
|
||||
func (s *deciderService) CreateTranscodeParams(decision *Decision) (string, error) {
|
||||
exp := time.Now().Add(tokenTTL)
|
||||
claims := map[string]any{
|
||||
claimMediaID: decision.MediaID,
|
||||
claimDirectPlay: decision.CanDirectPlay,
|
||||
claimUpdatedAt: decision.SourceUpdatedAt.Truncate(time.Second).Unix(),
|
||||
claims := auth.Claims{
|
||||
MediaID: decision.MediaID,
|
||||
DirectPlay: decision.CanDirectPlay,
|
||||
UpdatedAt: decision.SourceUpdatedAt.Truncate(time.Second).Unix(),
|
||||
}
|
||||
if decision.CanTranscode && decision.TargetFormat != "" {
|
||||
claims[claimFormat] = decision.TargetFormat
|
||||
claims[claimBitrate] = decision.TargetBitrate
|
||||
claims.Format = decision.TargetFormat
|
||||
claims.BitRate = decision.TargetBitrate
|
||||
if decision.TargetChannels > 0 {
|
||||
claims[claimChannels] = decision.TargetChannels
|
||||
claims.Channels = decision.TargetChannels
|
||||
}
|
||||
if decision.TargetSampleRate > 0 {
|
||||
claims[claimSampleRate] = decision.TargetSampleRate
|
||||
claims.SampleRate = decision.TargetSampleRate
|
||||
}
|
||||
if decision.TargetBitDepth > 0 {
|
||||
claims[claimBitDepth] = decision.TargetBitDepth
|
||||
claims.BitDepth = decision.TargetBitDepth
|
||||
}
|
||||
}
|
||||
return auth.CreateExpiringPublicToken(exp, claims)
|
||||
@ -335,43 +325,25 @@ func (s *deciderService) ParseTranscodeParams(token string) (*Params, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
params := &Params{}
|
||||
|
||||
// Required claims
|
||||
mid, ok := claims[claimMediaID].(string)
|
||||
if !ok || mid == "" {
|
||||
if claims.MediaID == "" {
|
||||
return nil, fmt.Errorf("%w: invalid transcode token: missing media ID", ErrTokenInvalid)
|
||||
}
|
||||
params.MediaID = mid
|
||||
|
||||
dp, ok := claims[claimDirectPlay].(bool)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%w: invalid transcode token: missing direct play flag", ErrTokenInvalid)
|
||||
}
|
||||
params.DirectPlay = dp
|
||||
|
||||
// Optional claims (legitimately absent for direct-play tokens)
|
||||
if f, ok := claims[claimFormat].(string); ok {
|
||||
params.TargetFormat = f
|
||||
}
|
||||
if br, ok := claims[claimBitrate].(float64); ok {
|
||||
params.TargetBitrate = int(br)
|
||||
}
|
||||
if ch, ok := claims[claimChannels].(float64); ok {
|
||||
params.TargetChannels = int(ch)
|
||||
}
|
||||
if sr, ok := claims[claimSampleRate].(float64); ok {
|
||||
params.TargetSampleRate = int(sr)
|
||||
}
|
||||
if bd, ok := claims[claimBitDepth].(float64); ok {
|
||||
params.TargetBitDepth = int(bd)
|
||||
}
|
||||
|
||||
ua, ok := claims[claimUpdatedAt].(float64)
|
||||
if !ok {
|
||||
if claims.UpdatedAt == 0 {
|
||||
return nil, fmt.Errorf("%w: invalid transcode token: missing source timestamp", ErrTokenInvalid)
|
||||
}
|
||||
params.SourceUpdatedAt = time.Unix(int64(ua), 0)
|
||||
|
||||
params := &Params{
|
||||
MediaID: claims.MediaID,
|
||||
DirectPlay: claims.DirectPlay,
|
||||
TargetFormat: claims.Format,
|
||||
TargetBitrate: claims.BitRate,
|
||||
TargetChannels: claims.Channels,
|
||||
TargetSampleRate: claims.SampleRate,
|
||||
TargetBitDepth: claims.BitDepth,
|
||||
SourceUpdatedAt: time.Unix(claims.UpdatedAt, 0),
|
||||
}
|
||||
|
||||
return params, nil
|
||||
}
|
||||
|
||||
@ -27,7 +27,7 @@ var _ = Describe("Album Lists", func() {
|
||||
ds = &tests.MockDataStore{}
|
||||
auth.Init(ds)
|
||||
mockRepo = ds.Album(ctx).(*tests.MockAlbumRepo)
|
||||
router = New(ds, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
|
||||
router = New(ds, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
|
||||
w = httptest.NewRecorder()
|
||||
})
|
||||
|
||||
|
||||
@ -27,7 +27,7 @@ var _ = Describe("MediaAnnotationController", func() {
|
||||
ds = &tests.MockDataStore{}
|
||||
playTracker = &fakePlayTracker{}
|
||||
eventBroker = &fakeEventBroker{}
|
||||
router = New(ds, nil, nil, nil, nil, nil, nil, eventBroker, nil, playTracker, nil, nil, nil, nil)
|
||||
router = New(ds, nil, nil, nil, nil, nil, nil, eventBroker, nil, playTracker, nil, nil, nil, nil, nil)
|
||||
})
|
||||
|
||||
Describe("Scrobble", func() {
|
||||
|
||||
@ -19,7 +19,7 @@ var _ = Describe("GetOpenSubsonicExtensions", func() {
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
router = subsonic.New(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
|
||||
router = subsonic.New(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
|
||||
w = httptest.NewRecorder()
|
||||
r = httptest.NewRequest("GET", "/getOpenSubsonicExtensions?f=json", nil)
|
||||
})
|
||||
|
||||
@ -24,7 +24,7 @@ var _ = Describe("buildPlaylist", func() {
|
||||
|
||||
BeforeEach(func() {
|
||||
ds = &tests.MockDataStore{}
|
||||
router = New(ds, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
|
||||
router = New(ds, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
|
||||
ctx = context.Background()
|
||||
})
|
||||
|
||||
@ -224,7 +224,7 @@ var _ = Describe("UpdatePlaylist", func() {
|
||||
BeforeEach(func() {
|
||||
ds = &tests.MockDataStore{}
|
||||
playlists = &fakePlaylists{}
|
||||
router = New(ds, nil, nil, nil, nil, nil, nil, nil, playlists, nil, nil, nil, nil, nil)
|
||||
router = New(ds, nil, nil, nil, nil, nil, nil, nil, playlists, nil, nil, nil, nil, nil, nil)
|
||||
})
|
||||
|
||||
It("clears the comment when parameter is empty", func() {
|
||||
|
||||
@ -21,7 +21,7 @@ var _ = Describe("Search", func() {
|
||||
ds = &tests.MockDataStore{}
|
||||
auth.Init(ds)
|
||||
|
||||
router = New(ds, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
|
||||
router = New(ds, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
|
||||
|
||||
// Get references to the mock repositories so we can inspect their Options
|
||||
mockAlbumRepo = ds.Album(nil).(*tests.MockAlbumRepo)
|
||||
|
||||
@ -28,7 +28,7 @@ var _ = Describe("Transcode endpoints", func() {
|
||||
mockMFRepo = &tests.MockMediaFileRepo{}
|
||||
ds = &tests.MockDataStore{MockedMediaFile: mockMFRepo}
|
||||
mockTD = &mockTranscodeDecision{}
|
||||
router = New(ds, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, mockTD)
|
||||
router = New(ds, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, mockTD)
|
||||
w = httptest.NewRecorder()
|
||||
})
|
||||
|
||||
@ -260,7 +260,7 @@ var _ = Describe("Transcode endpoints", func() {
|
||||
|
||||
It("builds correct StreamRequest for direct play", func() {
|
||||
fakeStreamer := &fakeMediaStreamer{}
|
||||
router = New(ds, nil, fakeStreamer, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, mockTD)
|
||||
router = New(ds, nil, fakeStreamer, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, mockTD)
|
||||
mockTD.validateParams = &transcode.Params{MediaID: "song-1", DirectPlay: true}
|
||||
mockTD.validateMF = &model.MediaFile{ID: "song-1"}
|
||||
|
||||
@ -278,7 +278,7 @@ var _ = Describe("Transcode endpoints", func() {
|
||||
|
||||
It("builds correct StreamRequest for transcoding", func() {
|
||||
fakeStreamer := &fakeMediaStreamer{}
|
||||
router = New(ds, nil, fakeStreamer, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, mockTD)
|
||||
router = New(ds, nil, fakeStreamer, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, mockTD)
|
||||
mockTD.validateParams = &transcode.Params{
|
||||
MediaID: "song-2",
|
||||
DirectPlay: false,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user