diff --git a/README.md b/README.md index 72f67964d45..7d64fe37642 100644 --- a/README.md +++ b/README.md @@ -1180,12 +1180,20 @@ The JWT is expected to contain the `mediamtx_permissions` scope, with a list of } ``` -Clients are expected to pass the JWT in query parameters, for instance: +Clients are expected to pass the JWT in the Authorization header (in case of HLS and WebRTC) or in query parameters (in case of any other protocol), for instance (RTSP): ``` ffmpeg -re -stream_loop -1 -i file.ts -c copy -f rtsp rtsp://localhost:8554/mystream?jwt=MY_JWT ``` +For instance (HLS): + +``` +GET /mypath/index.m3u8 HTTP/1.1 +Host: example.com +Authorization: Bearer MY_JWT +``` + Here's a tutorial on how to setup the [Keycloak identity server](https://www.keycloak.org/) in order to provide such JWTs: 1. Start Keycloak: diff --git a/internal/servers/hls/http_server.go b/internal/servers/hls/http_server.go index 50d019c0cec..752b5c2a9dc 100644 --- a/internal/servers/hls/http_server.go +++ b/internal/servers/hls/http_server.go @@ -5,6 +5,7 @@ import ( "errors" "net" "net/http" + "net/url" gopath "path" "strings" "time" @@ -36,6 +37,17 @@ func mergePathAndQuery(path string, rawQuery string) string { return res } +func addJWTFromAuthorization(rawQuery string, auth string) string { + jwt := strings.TrimPrefix(auth, "Bearer ") + if rawQuery != "" { + if v, err := url.ParseQuery(rawQuery); err == nil && v.Get("jwt") == "" { + v.Set("jwt", jwt) + return v.Encode() + } + } + return url.Values{"jwt": []string{jwt}}.Encode() +} + type httpServer struct { address string encryption bool @@ -145,10 +157,15 @@ func (s *httpServer) onRequest(ctx *gin.Context) { user, pass, hasCredentials := ctx.Request.BasicAuth() + q := ctx.Request.URL.RawQuery + if h := ctx.Request.Header.Get("Authorization"); strings.HasPrefix(h, "Bearer ") { + q = addJWTFromAuthorization(q, h) + } + pathConf, err := s.pathManager.FindPathConf(defs.PathFindPathConfReq{ AccessRequest: defs.PathAccessRequest{ Name: dir, - Query: ctx.Request.URL.RawQuery, + Query: q, Publish: false, IP: net.ParseIP(ctx.ClientIP()), User: user, diff --git a/internal/servers/hls/server_test.go b/internal/servers/hls/server_test.go index 5db01b8195d..76ef901d7a2 100644 --- a/internal/servers/hls/server_test.go +++ b/internal/servers/hls/server_test.go @@ -11,7 +11,6 @@ import ( "github.com/bluenviron/gohlslib" "github.com/bluenviron/gohlslib/pkg/codecs" "github.com/bluenviron/gortsplib/v4/pkg/description" - "github.com/bluenviron/mediamtx/internal/auth" "github.com/bluenviron/mediamtx/internal/conf" "github.com/bluenviron/mediamtx/internal/defs" "github.com/bluenviron/mediamtx/internal/externalcmd" @@ -49,21 +48,16 @@ func (pa *dummyPath) RemoveReader(_ defs.PathRemoveReaderReq) { } type dummyPathManager struct { - stream *stream.Stream + findPathConf func(req defs.PathFindPathConfReq) (*conf.Path, error) + addReader func(req defs.PathAddReaderReq) (defs.Path, *stream.Stream, error) } func (pm *dummyPathManager) FindPathConf(req defs.PathFindPathConfReq) (*conf.Path, error) { - if req.AccessRequest.User != "myuser" || req.AccessRequest.Pass != "mypass" { - return nil, auth.Error{} - } - return &conf.Path{}, nil + return pm.findPathConf(req) } func (pm *dummyPathManager) AddReader(req defs.PathAddReaderReq) (defs.Path, *stream.Stream, error) { - if req.AccessRequest.Name == "nonexisting" { - return nil, nil, fmt.Errorf("not found") - } - return &dummyPath{}, pm.stream, nil + return pm.addReader(req) } func TestServerNotFound(t *testing.T) { @@ -72,6 +66,19 @@ func TestServerNotFound(t *testing.T) { "always remux on", } { t.Run(ca, func(t *testing.T) { + pm := &dummyPathManager{ + findPathConf: func(req defs.PathFindPathConfReq) (*conf.Path, error) { + require.Equal(t, "nonexisting", req.AccessRequest.Name) + require.Equal(t, "myuser", req.AccessRequest.User) + require.Equal(t, "mypass", req.AccessRequest.Pass) + return &conf.Path{}, nil + }, + addReader: func(req defs.PathAddReaderReq) (defs.Path, *stream.Stream, error) { + require.Equal(t, "nonexisting", req.AccessRequest.Name) + return nil, nil, fmt.Errorf("not found") + }, + } + s := &Server{ Address: "127.0.0.1:8888", Encryption: false, @@ -88,7 +95,7 @@ func TestServerNotFound(t *testing.T) { Directory: "", ReadTimeout: conf.StringDuration(10 * time.Second), WriteQueueSize: 512, - PathManager: &dummyPathManager{}, + PathManager: pm, Parent: test.NilLogger, } err := s.Initialize() @@ -126,7 +133,7 @@ func TestServerRead(t *testing.T) { t.Run("always remux off", func(t *testing.T) { desc := &description.Session{Medias: []*description.Media{test.MediaH264}} - stream, err := stream.New( + str, err := stream.New( 1460, desc, true, @@ -134,7 +141,18 @@ func TestServerRead(t *testing.T) { ) require.NoError(t, err) - pathManager := &dummyPathManager{stream: stream} + pm := &dummyPathManager{ + findPathConf: func(req defs.PathFindPathConfReq) (*conf.Path, error) { + require.Equal(t, "mystream", req.AccessRequest.Name) + require.Equal(t, "myuser", req.AccessRequest.User) + require.Equal(t, "mypass", req.AccessRequest.Pass) + return &conf.Path{}, nil + }, + addReader: func(req defs.PathAddReaderReq) (defs.Path, *stream.Stream, error) { + require.Equal(t, "mystream", req.AccessRequest.Name) + return &dummyPath{}, str, nil + }, + } s := &Server{ Address: "127.0.0.1:8888", @@ -152,7 +170,7 @@ func TestServerRead(t *testing.T) { Directory: "", ReadTimeout: conf.StringDuration(10 * time.Second), WriteQueueSize: 512, - PathManager: pathManager, + PathManager: pm, Parent: test.NilLogger, } err = s.Initialize() @@ -192,7 +210,7 @@ func TestServerRead(t *testing.T) { go func() { time.Sleep(100 * time.Millisecond) for i := 0; i < 4; i++ { - stream.WriteUnit(test.MediaH264, test.FormatH264, &unit.H264{ + str.WriteUnit(test.MediaH264, test.FormatH264, &unit.H264{ Base: unit.Base{ NTP: time.Time{}, PTS: time.Duration(i) * time.Second, @@ -210,7 +228,7 @@ func TestServerRead(t *testing.T) { t.Run("always remux on", func(t *testing.T) { desc := &description.Session{Medias: []*description.Media{test.MediaH264}} - stream, err := stream.New( + str, err := stream.New( 1460, desc, true, @@ -218,7 +236,18 @@ func TestServerRead(t *testing.T) { ) require.NoError(t, err) - pathManager := &dummyPathManager{stream: stream} + pm := &dummyPathManager{ + findPathConf: func(req defs.PathFindPathConfReq) (*conf.Path, error) { + require.Equal(t, "mystream", req.AccessRequest.Name) + require.Equal(t, "myuser", req.AccessRequest.User) + require.Equal(t, "mypass", req.AccessRequest.Pass) + return &conf.Path{}, nil + }, + addReader: func(req defs.PathAddReaderReq) (defs.Path, *stream.Stream, error) { + require.Equal(t, "mystream", req.AccessRequest.Name) + return &dummyPath{}, str, nil + }, + } s := &Server{ Address: "127.0.0.1:8888", @@ -236,7 +265,7 @@ func TestServerRead(t *testing.T) { Directory: "", ReadTimeout: conf.StringDuration(10 * time.Second), WriteQueueSize: 512, - PathManager: pathManager, + PathManager: pm, Parent: test.NilLogger, } err = s.Initialize() @@ -248,7 +277,7 @@ func TestServerRead(t *testing.T) { time.Sleep(100 * time.Millisecond) for i := 0; i < 4; i++ { - stream.WriteUnit(test.MediaH264, test.FormatH264, &unit.H264{ + str.WriteUnit(test.MediaH264, test.FormatH264, &unit.H264{ Base: unit.Base{ NTP: time.Time{}, PTS: time.Duration(i) * time.Second, @@ -293,6 +322,102 @@ func TestServerRead(t *testing.T) { }) } +func TestServerReadAuthorizationHeader(t *testing.T) { + desc := &description.Session{Medias: []*description.Media{test.MediaH264}} + + str, err := stream.New( + 1460, + desc, + true, + test.NilLogger, + ) + require.NoError(t, err) + + pm := &dummyPathManager{ + findPathConf: func(req defs.PathFindPathConfReq) (*conf.Path, error) { + require.Equal(t, "jwt=testing", req.AccessRequest.Query) + return &conf.Path{}, nil + }, + addReader: func(_ defs.PathAddReaderReq) (defs.Path, *stream.Stream, error) { + return &dummyPath{}, str, nil + }, + } + + s := &Server{ + Address: "127.0.0.1:8888", + Encryption: false, + ServerKey: "", + ServerCert: "", + AlwaysRemux: true, + Variant: conf.HLSVariant(gohlslib.MuxerVariantMPEGTS), + SegmentCount: 7, + SegmentDuration: conf.StringDuration(1 * time.Second), + PartDuration: conf.StringDuration(200 * time.Millisecond), + SegmentMaxSize: 50 * 1024 * 1024, + AllowOrigin: "", + TrustedProxies: conf.IPNetworks{}, + Directory: "", + ReadTimeout: conf.StringDuration(10 * time.Second), + WriteQueueSize: 512, + PathManager: pm, + Parent: test.NilLogger, + } + err = s.Initialize() + require.NoError(t, err) + defer s.Close() + + s.PathReady(&dummyPath{}) + + time.Sleep(100 * time.Millisecond) + + for i := 0; i < 4; i++ { + str.WriteUnit(test.MediaH264, test.FormatH264, &unit.H264{ + Base: unit.Base{ + NTP: time.Time{}, + PTS: time.Duration(i) * time.Second, + }, + AU: [][]byte{ + {5, 1}, // IDR + }, + }) + } + + c := &gohlslib.Client{ + URI: "http://127.0.0.1:8888/mystream/index.m3u8", + OnRequest: func(r *http.Request) { + r.Header.Set("Authorization", "Bearer testing") + }, + } + + recv := make(chan struct{}) + + c.OnTracks = func(tracks []*gohlslib.Track) error { + require.Equal(t, []*gohlslib.Track{{ + Codec: &codecs.H264{}, + }}, tracks) + + c.OnDataH26x(tracks[0], func(pts, dts time.Duration, au [][]byte) { + require.Equal(t, time.Duration(0), pts) + require.Equal(t, time.Duration(0), dts) + require.Equal(t, [][]byte{ + test.FormatH264.SPS, + test.FormatH264.PPS, + {5, 1}, + }, au) + close(recv) + }) + + return nil + } + + err = c.Start() + require.NoError(t, err) + defer func() { <-c.Wait() }() + defer c.Close() + + <-recv +} + func TestDirectory(t *testing.T) { dir, err := os.MkdirTemp("", "mediamtx-playback") require.NoError(t, err) @@ -300,7 +425,7 @@ func TestDirectory(t *testing.T) { desc := &description.Session{Medias: []*description.Media{test.MediaH264}} - stream, err := stream.New( + str, err := stream.New( 1460, desc, true, @@ -308,7 +433,11 @@ func TestDirectory(t *testing.T) { ) require.NoError(t, err) - pathManager := &dummyPathManager{stream: stream} + pm := &dummyPathManager{ + addReader: func(_ defs.PathAddReaderReq) (defs.Path, *stream.Stream, error) { + return &dummyPath{}, str, nil + }, + } s := &Server{ Address: "127.0.0.1:8888", @@ -326,7 +455,7 @@ func TestDirectory(t *testing.T) { Directory: filepath.Join(dir, "mydir"), ReadTimeout: conf.StringDuration(10 * time.Second), WriteQueueSize: 512, - PathManager: pathManager, + PathManager: pm, Parent: test.NilLogger, } err = s.Initialize() diff --git a/internal/servers/webrtc/http_server.go b/internal/servers/webrtc/http_server.go index fe9b14620a5..665f6abe661 100644 --- a/internal/servers/webrtc/http_server.go +++ b/internal/servers/webrtc/http_server.go @@ -7,6 +7,7 @@ import ( "io" "net" "net/http" + "net/url" "regexp" "strings" "time" @@ -59,6 +60,17 @@ func sessionLocation(publish bool, path string, secret uuid.UUID) string { return ret } +func addJWTFromAuthorization(rawQuery string, auth string) string { + jwt := strings.TrimPrefix(auth, "Bearer ") + if rawQuery != "" { + if v, err := url.ParseQuery(rawQuery); err == nil && v.Get("jwt") == "" { + v.Set("jwt", jwt) + return v.Encode() + } + } + return url.Values{"jwt": []string{jwt}}.Encode() +} + type httpServer struct { address string encryption bool @@ -110,10 +122,15 @@ func (s *httpServer) close() { func (s *httpServer) checkAuthOutsideSession(ctx *gin.Context, pathName string, publish bool) bool { user, pass, hasCredentials := ctx.Request.BasicAuth() + q := ctx.Request.URL.RawQuery + if h := ctx.Request.Header.Get("Authorization"); strings.HasPrefix(h, "Bearer ") { + q = addJWTFromAuthorization(q, h) + } + _, err := s.pathManager.FindPathConf(defs.PathFindPathConfReq{ AccessRequest: defs.PathAccessRequest{ Name: pathName, - Query: ctx.Request.URL.RawQuery, + Query: q, Publish: publish, IP: net.ParseIP(ctx.ClientIP()), User: user, @@ -178,10 +195,15 @@ func (s *httpServer) onWHIPPost(ctx *gin.Context, pathName string, publish bool) user, pass, _ := ctx.Request.BasicAuth() + q := ctx.Request.URL.RawQuery + if h := ctx.Request.Header.Get("Authorization"); strings.HasPrefix(h, "Bearer ") { + q = addJWTFromAuthorization(q, h) + } + res := s.parent.newSession(webRTCNewSessionReq{ pathName: pathName, remoteAddr: httpp.RemoteAddr(ctx), - query: ctx.Request.URL.RawQuery, + query: q, user: user, pass: pass, offer: offer, diff --git a/internal/servers/webrtc/server_test.go b/internal/servers/webrtc/server_test.go index 621e13ec297..a8252d67136 100644 --- a/internal/servers/webrtc/server_test.go +++ b/internal/servers/webrtc/server_test.go @@ -12,7 +12,6 @@ import ( "github.com/bluenviron/gortsplib/v4/pkg/description" "github.com/bluenviron/gortsplib/v4/pkg/format" "github.com/bluenviron/mediamtx/internal/asyncwriter" - "github.com/bluenviron/mediamtx/internal/auth" "github.com/bluenviron/mediamtx/internal/conf" "github.com/bluenviron/mediamtx/internal/defs" "github.com/bluenviron/mediamtx/internal/externalcmd" @@ -72,34 +71,32 @@ func (p *dummyPath) RemoveReader(_ defs.PathRemoveReaderReq) { } type dummyPathManager struct { - path *dummyPath + findPathConf func(req defs.PathFindPathConfReq) (*conf.Path, error) + addPublisher func(req defs.PathAddPublisherReq) (defs.Path, error) + addReader func(req defs.PathAddReaderReq) (defs.Path, *stream.Stream, error) } func (pm *dummyPathManager) FindPathConf(req defs.PathFindPathConfReq) (*conf.Path, error) { - if req.AccessRequest.User != "myuser" || req.AccessRequest.Pass != "mypass" { - return nil, auth.Error{} - } - return &conf.Path{}, nil + return pm.findPathConf(req) } -func (pm *dummyPathManager) AddPublisher(_ defs.PathAddPublisherReq) (defs.Path, error) { - return pm.path, nil +func (pm *dummyPathManager) AddPublisher(req defs.PathAddPublisherReq) (defs.Path, error) { + return pm.addPublisher(req) } func (pm *dummyPathManager) AddReader(req defs.PathAddReaderReq) (defs.Path, *stream.Stream, error) { - if req.AccessRequest.Name == "nonexisting" { - return nil, nil, defs.PathNoOnePublishingError{} - } - return pm.path, pm.path.stream, nil + return pm.addReader(req) } func initializeTestServer(t *testing.T) *Server { - path := &dummyPath{ - streamCreated: make(chan struct{}), + pm := &dummyPathManager{ + findPathConf: func(req defs.PathFindPathConfReq) (*conf.Path, error) { + require.Equal(t, "myuser", req.AccessRequest.User) + require.Equal(t, "mypass", req.AccessRequest.Pass) + return &conf.Path{}, nil + }, } - pathManager := &dummyPathManager{path: path} - s := &Server{ Address: "127.0.0.1:8886", Encryption: false, @@ -118,7 +115,7 @@ func initializeTestServer(t *testing.T) *Server { HandshakeTimeout: conf.StringDuration(10 * time.Second), TrackGatherTimeout: conf.StringDuration(2 * time.Second), ExternalCmdPool: nil, - PathManager: pathManager, + PathManager: pm, Parent: test.NilLogger, } err := s.Initialize() @@ -174,7 +171,13 @@ func TestServerOptionsPreflight(t *testing.T) { } func TestServerOptionsICEServer(t *testing.T) { - pathManager := &dummyPathManager{} + pathManager := &dummyPathManager{ + findPathConf: func(req defs.PathFindPathConfReq) (*conf.Path, error) { + require.Equal(t, "myuser", req.AccessRequest.User) + require.Equal(t, "mypass", req.AccessRequest.Pass) + return &conf.Path{}, nil + }, + } s := &Server{ Address: "127.0.0.1:8886", @@ -234,7 +237,20 @@ func TestServerPublish(t *testing.T) { streamCreated: make(chan struct{}), } - pathManager := &dummyPathManager{path: path} + pathManager := &dummyPathManager{ + findPathConf: func(req defs.PathFindPathConfReq) (*conf.Path, error) { + require.Equal(t, "teststream", req.AccessRequest.Name) + require.Equal(t, "myuser", req.AccessRequest.User) + require.Equal(t, "mypass", req.AccessRequest.Pass) + return &conf.Path{}, nil + }, + addPublisher: func(req defs.PathAddPublisherReq) (defs.Path, error) { + require.Equal(t, "teststream", req.AccessRequest.Name) + require.Equal(t, "myuser", req.AccessRequest.User) + require.Equal(t, "mypass", req.AccessRequest.Pass) + return path, nil + }, + } s := &Server{ Address: "127.0.0.1:8886", @@ -486,7 +502,7 @@ func TestServerRead(t *testing.T) { t.Run(ca.name, func(t *testing.T) { desc := &description.Session{Medias: ca.medias} - stream, err := stream.New( + str, err := stream.New( 1460, desc, reflect.TypeOf(ca.unit) != reflect.TypeOf(&unit.Generic{}), @@ -494,9 +510,22 @@ func TestServerRead(t *testing.T) { ) require.NoError(t, err) - path := &dummyPath{stream: stream} + path := &dummyPath{stream: str} - pathManager := &dummyPathManager{path: path} + pathManager := &dummyPathManager{ + findPathConf: func(req defs.PathFindPathConfReq) (*conf.Path, error) { + require.Equal(t, "teststream", req.AccessRequest.Name) + require.Equal(t, "myuser", req.AccessRequest.User) + require.Equal(t, "mypass", req.AccessRequest.Pass) + return &conf.Path{}, nil + }, + addReader: func(req defs.PathAddReaderReq) (defs.Path, *stream.Stream, error) { + require.Equal(t, "teststream", req.AccessRequest.Name) + require.Equal(t, "myuser", req.AccessRequest.User) + require.Equal(t, "mypass", req.AccessRequest.Pass) + return path, str, nil + }, + } s := &Server{ Address: "127.0.0.1:8886", @@ -556,9 +585,9 @@ func TestServerRead(t *testing.T) { if g, ok := r.Interface().(*unit.Generic); ok { clone := *g.RTPPackets[0] - stream.WriteRTPPacket(desc.Medias[0], desc.Medias[0].Formats[0], &clone, time.Time{}, 0) + str.WriteRTPPacket(desc.Medias[0], desc.Medias[0].Formats[0], &clone, time.Time{}, 0) } else { - stream.WriteUnit(desc.Medias[0], desc.Medias[0].Formats[0], r.Interface().(unit.Unit)) + str.WriteUnit(desc.Medias[0], desc.Medias[0].Formats[0], r.Interface().(unit.Unit)) } } }() @@ -574,8 +603,118 @@ func TestServerRead(t *testing.T) { } } -func TestServerPostNotFound(t *testing.T) { - s := initializeTestServer(t) +func TestServerReadAuthorizationHeader(t *testing.T) { + desc := &description.Session{Medias: []*description.Media{test.MediaH264}} + + str, err := stream.New( + 1460, + desc, + true, + test.NilLogger, + ) + require.NoError(t, err) + + path := &dummyPath{stream: str} + + pm := &dummyPathManager{ + findPathConf: func(req defs.PathFindPathConfReq) (*conf.Path, error) { + require.Equal(t, "jwt=testing", req.AccessRequest.Query) + return &conf.Path{}, nil + }, + addReader: func(req defs.PathAddReaderReq) (defs.Path, *stream.Stream, error) { + require.Equal(t, "jwt=testing", req.AccessRequest.Query) + return path, str, nil + }, + } + + s := &Server{ + Address: "127.0.0.1:8886", + Encryption: false, + ServerKey: "", + ServerCert: "", + AllowOrigin: "", + TrustedProxies: conf.IPNetworks{}, + ReadTimeout: conf.StringDuration(10 * time.Second), + WriteQueueSize: 512, + LocalUDPAddress: "127.0.0.1:8887", + LocalTCPAddress: "127.0.0.1:8887", + IPsFromInterfaces: true, + IPsFromInterfacesList: []string{}, + AdditionalHosts: []string{}, + ICEServers: []conf.WebRTCICEServer{}, + HandshakeTimeout: conf.StringDuration(10 * time.Second), + TrackGatherTimeout: conf.StringDuration(2 * time.Second), + ExternalCmdPool: nil, + PathManager: pm, + Parent: test.NilLogger, + } + err = s.Initialize() + require.NoError(t, err) + defer s.Close() + + tr := &http.Transport{} + defer tr.CloseIdleConnections() + hc := &http.Client{Transport: tr} + + pc, err := pwebrtc.NewPeerConnection(pwebrtc.Configuration{}) + require.NoError(t, err) + defer pc.Close() //nolint:errcheck + + _, err = pc.AddTransceiverFromKind(pwebrtc.RTPCodecTypeVideo) + require.NoError(t, err) + + offer, err := pc.CreateOffer(nil) + require.NoError(t, err) + + req, err := http.NewRequest(http.MethodPost, + "http://localhost:8886/teststream/whep", bytes.NewReader([]byte(offer.SDP))) + require.NoError(t, err) + + req.Header.Set("Content-Type", "application/sdp") + req.Header.Set("Authorization", "Bearer testing") + + res, err := hc.Do(req) + require.NoError(t, err) + defer res.Body.Close() + + require.Equal(t, http.StatusCreated, res.StatusCode) +} + +func TestServerReadNotFound(t *testing.T) { + pm := &dummyPathManager{ + findPathConf: func(req defs.PathFindPathConfReq) (*conf.Path, error) { + require.Equal(t, "myuser", req.AccessRequest.User) + require.Equal(t, "mypass", req.AccessRequest.Pass) + return &conf.Path{}, nil + }, + addReader: func(_ defs.PathAddReaderReq) (defs.Path, *stream.Stream, error) { + return nil, nil, defs.PathNoOnePublishingError{} + }, + } + + s := &Server{ + Address: "127.0.0.1:8886", + Encryption: false, + ServerKey: "", + ServerCert: "", + AllowOrigin: "", + TrustedProxies: conf.IPNetworks{}, + ReadTimeout: conf.StringDuration(10 * time.Second), + WriteQueueSize: 512, + LocalUDPAddress: "127.0.0.1:8887", + LocalTCPAddress: "127.0.0.1:8887", + IPsFromInterfaces: true, + IPsFromInterfacesList: []string{}, + AdditionalHosts: []string{}, + ICEServers: []conf.WebRTCICEServer{}, + HandshakeTimeout: conf.StringDuration(10 * time.Second), + TrackGatherTimeout: conf.StringDuration(2 * time.Second), + ExternalCmdPool: nil, + PathManager: pm, + Parent: test.NilLogger, + } + err := s.Initialize() + require.NoError(t, err) defer s.Close() tr := &http.Transport{} diff --git a/mediamtx.yml b/mediamtx.yml index e3929d9ec67..dbc451eb9f7 100644 --- a/mediamtx.yml +++ b/mediamtx.yml @@ -117,7 +117,7 @@ authHTTPExclude: # } # ] # } -# Users are then expected to pass the JWT as a query parameter, i.e. ?jwt=... +# Users are expected to pass the JWT in the Authorization header or as a query parameter. # This is the JWKS URL that will be used to pull (once) the public key that allows # to validate JWTs. authJWTJWKS: