From 5decbb4a3d86e839b4caf7939ed96ba325c10c58 Mon Sep 17 00:00:00 2001 From: Alex McKenzie Date: Wed, 3 Jun 2026 04:29:07 +1000 Subject: [PATCH] Add user agent field to RTMP, RTSP, WebRTC, and HLS (#5753) Surface user agent in the auth HTTP webhook payload as "userAgent" and in the API structs for RTMP, RTSP, WebRTC and HLS connections. --------- Co-authored-by: aler9 <46489434+aler9@users.noreply.github.com> --- api/openapi.yaml | 8 ++++++ docs/2-features/06-authentication.md | 3 +- internal/auth/manager.go | 38 ++++++++++++++------------ internal/auth/manager_test.go | 36 +++++++++++++----------- internal/auth/request.go | 1 + internal/core/api_test.go | 6 ++++ internal/defs/api_hls.go | 1 + internal/defs/api_rtmp.go | 1 + internal/defs/api_rtsp.go | 1 + internal/defs/api_webrtc.go | 1 + internal/defs/path_access_request.go | 10 ++++--- internal/servers/hls/session.go | 16 +++++++---- internal/servers/rtmp/conn.go | 23 ++++++++++------ internal/servers/rtmp/server_test.go | 2 ++ internal/servers/rtsp/server_test.go | 2 ++ internal/servers/rtsp/session.go | 29 ++++++++++++++++---- internal/servers/webrtc/http_server.go | 1 + internal/servers/webrtc/server_test.go | 2 ++ internal/servers/webrtc/session.go | 12 +++++--- mediamtx.yml | 13 +-------- 20 files changed, 130 insertions(+), 76 deletions(-) diff --git a/api/openapi.yaml b/api/openapi.yaml index 87942c45..513cdd7f 100644 --- a/api/openapi.yaml +++ b/api/openapi.yaml @@ -1213,6 +1213,8 @@ components: type: string user: type: string + userAgent: + type: string isCDN: type: boolean outboundBytes: @@ -1281,6 +1283,8 @@ components: type: string user: type: string + userAgent: + type: string inboundBytes: type: integer format: uint64 @@ -1376,6 +1380,8 @@ components: type: string user: type: string + userAgent: + type: string transport: type: string nullable: true @@ -1774,6 +1780,8 @@ components: type: string user: type: string + userAgent: + type: string inboundBytes: type: integer format: uint64 diff --git a/docs/2-features/06-authentication.md b/docs/2-features/06-authentication.md index 85bd1a70..05ff2bb3 100644 --- a/docs/2-features/06-authentication.md +++ b/docs/2-features/06-authentication.md @@ -93,7 +93,8 @@ Each time a user needs to be authenticated, the specified URL will be requested "path": "path", "protocol": "rtsp|rtmp|hls|webrtc|srt", "id": "id", - "query": "query" + "query": "query", + "userAgent": "userAgent" } ``` diff --git a/internal/auth/manager.go b/internal/auth/manager.go index 9c5adecb..0e8c75b2 100644 --- a/internal/auth/manager.go +++ b/internal/auth/manager.go @@ -197,25 +197,27 @@ func (m *Manager) authenticateHTTP(req *Request, token string) (string, error) { } enc, _ := json.Marshal(struct { - IP string `json:"ip"` - User string `json:"user"` - Password string `json:"password"` - Token string `json:"token"` - Action string `json:"action"` - Path string `json:"path"` - Protocol string `json:"protocol"` - ID *uuid.UUID `json:"id"` - Query string `json:"query"` + IP string `json:"ip"` + User string `json:"user"` + Password string `json:"password"` + Token string `json:"token"` + Action string `json:"action"` + Path string `json:"path"` + Protocol string `json:"protocol"` + ID *uuid.UUID `json:"id"` + Query string `json:"query"` + UserAgent string `json:"userAgent"` }{ - IP: req.IP.String(), - User: req.Credentials.User, - Password: req.Credentials.Pass, - Token: token, - Action: string(req.Action), - Path: req.Path, - Protocol: string(req.Protocol), - ID: req.ID, - Query: req.Query, + IP: req.IP.String(), + User: req.Credentials.User, + Password: req.Credentials.Pass, + Token: token, + Action: string(req.Action), + Path: req.Path, + Protocol: string(req.Protocol), + ID: req.ID, + Query: req.Query, + UserAgent: req.UserAgent, }) tr := &http.Transport{ diff --git a/internal/auth/manager_test.go b/internal/auth/manager_test.go index 23cc1164..0e38e661 100644 --- a/internal/auth/manager_test.go +++ b/internal/auth/manager_test.go @@ -274,14 +274,15 @@ func TestAuthHTTP(t *testing.T) { require.Equal(t, "/auth", r.URL.Path) var in struct { - IP string `json:"ip"` - User string `json:"user"` - Password string `json:"password"` - Path string `json:"path"` - Protocol string `json:"protocol"` - ID string `json:"id"` - Action string `json:"action"` - Query string `json:"query"` + IP string `json:"ip"` + User string `json:"user"` + Password string `json:"password"` + Path string `json:"path"` + Protocol string `json:"protocol"` + ID string `json:"id"` + Action string `json:"action"` + Query string `json:"query"` + UserAgent string `json:"userAgent"` } err := json.NewDecoder(r.Body).Decode(&in) require.NoError(t, err) @@ -293,6 +294,7 @@ func TestAuthHTTP(t *testing.T) { in.Protocol != "rtsp" || (firstReceived && in.ID == "") || in.Action != "publish" || + in.UserAgent != "testagent" || (in.Query != "user=testreader&pass=testpass¶m=value" && in.Query != "user=testpublisher&pass=testpass¶m=value" && in.Query != "param=value") { @@ -319,10 +321,11 @@ func TestAuthHTTP(t *testing.T) { if outcome == "ok" { req = &Request{ - Action: conf.AuthActionPublish, - Path: "teststream", - Query: "param=value", - Protocol: ProtocolRTSP, + Action: conf.AuthActionPublish, + Path: "teststream", + Query: "param=value", + Protocol: ProtocolRTSP, + UserAgent: "testagent", Credentials: &Credentials{ User: "testpublisher", Pass: "testpass", @@ -331,10 +334,11 @@ func TestAuthHTTP(t *testing.T) { } } else { req = &Request{ - Action: conf.AuthActionPublish, - Path: "teststream", - Query: "param=value", - Protocol: ProtocolRTSP, + Action: conf.AuthActionPublish, + Path: "teststream", + Query: "param=value", + Protocol: ProtocolRTSP, + UserAgent: "testagent", Credentials: &Credentials{ User: "invalid", Pass: "testpass", diff --git a/internal/auth/request.go b/internal/auth/request.go index 3418b477..94e18969 100644 --- a/internal/auth/request.go +++ b/internal/auth/request.go @@ -26,6 +26,7 @@ type Request struct { Query string Protocol Protocol // only for ActionPublish, ActionRead ID *uuid.UUID // only for ActionPublish, ActionRead + UserAgent string Credentials *Credentials IP net.IP CustomVerifyFunc func(expectedUser string, expectedPass string) bool diff --git a/internal/core/api_test.go b/internal/core/api_test.go index 0d99c572..8a8c2d75 100644 --- a/internal/core/api_test.go +++ b/internal/core/api_test.go @@ -679,6 +679,7 @@ func TestAPIProtocolListGet(t *testing.T) { "rtcpPacketsSent": float64(0), "rtcpPacketsInError": float64(0), "conns": out1.(map[string]any)["items"].([]any)[0].(map[string]any)["conns"], + "userAgent": "gortsplib", }, }, }, out1) @@ -740,6 +741,7 @@ func TestAPIProtocolListGet(t *testing.T) { "rtcpPacketsSent": float64(0), "rtcpPacketsInError": float64(0), "conns": out1.(map[string]any)["items"].([]any)[0].(map[string]any)["conns"], + "userAgent": "gortsplib", }, }, }, out1) @@ -762,6 +764,7 @@ func TestAPIProtocolListGet(t *testing.T) { "user": "", "remoteAddr": out1.(map[string]any)["items"].([]any)[0].(map[string]any)["remoteAddr"], "state": "publish", + "userAgent": "LNX 9,0,124,2", }, }, }, out1) @@ -784,6 +787,7 @@ func TestAPIProtocolListGet(t *testing.T) { "user": "", "remoteAddr": out1.(map[string]any)["items"].([]any)[0].(map[string]any)["remoteAddr"], "state": "publish", + "userAgent": "LNX 9,0,124,2", }, }, }, out1) @@ -802,6 +806,7 @@ func TestAPIProtocolListGet(t *testing.T) { "user": "", "isCDN": false, "outboundBytes": out1.(map[string]any)["items"].([]any)[0].(map[string]any)["outboundBytes"], + "userAgent": "Go-http-client/1.1", }, }, }, out1) @@ -855,6 +860,7 @@ func TestAPIProtocolListGet(t *testing.T) { "rtpPacketsLost": float64(0), "rtpPacketsReceived": float64(0), "rtpPacketsSent": float64(1), + "userAgent": "Go-http-client/1.1", }, }, }, out1) diff --git a/internal/defs/api_hls.go b/internal/defs/api_hls.go index 04219782..3da8b72b 100644 --- a/internal/defs/api_hls.go +++ b/internal/defs/api_hls.go @@ -30,6 +30,7 @@ type APIHLSSession struct { Path string `json:"path"` Query string `json:"query"` User string `json:"user"` + UserAgent string `json:"userAgent"` IsCDN bool `json:"isCDN"` OutboundBytes uint64 `json:"outboundBytes"` } diff --git a/internal/defs/api_rtmp.go b/internal/defs/api_rtmp.go index 07fa0a2a..ae92d78a 100644 --- a/internal/defs/api_rtmp.go +++ b/internal/defs/api_rtmp.go @@ -32,6 +32,7 @@ type APIRTMPConn struct { Path string `json:"path"` Query string `json:"query"` User string `json:"user"` + UserAgent string `json:"userAgent"` InboundBytes uint64 `json:"inboundBytes"` OutboundBytes uint64 `json:"outboundBytes"` OutboundFramesDiscarded uint64 `json:"outboundFramesDiscarded"` diff --git a/internal/defs/api_rtsp.go b/internal/defs/api_rtsp.go index 2e8521ea..b137d8da 100644 --- a/internal/defs/api_rtsp.go +++ b/internal/defs/api_rtsp.go @@ -54,6 +54,7 @@ type APIRTSPSession struct { Path string `json:"path"` Query string `json:"query"` User string `json:"user"` + UserAgent string `json:"userAgent"` Transport *string `json:"transport"` Profile *string `json:"profile"` Conns []uuid.UUID `json:"conns"` diff --git a/internal/defs/api_webrtc.go b/internal/defs/api_webrtc.go index 23f61099..3398f2d9 100644 --- a/internal/defs/api_webrtc.go +++ b/internal/defs/api_webrtc.go @@ -34,6 +34,7 @@ type APIWebRTCSession struct { Path string `json:"path"` Query string `json:"query"` User string `json:"user"` + UserAgent string `json:"userAgent"` InboundBytes uint64 `json:"inboundBytes"` InboundRTPPackets uint64 `json:"inboundRTPPackets"` InboundRTPPacketsLost uint64 `json:"inboundRTPPacketsLost"` diff --git a/internal/defs/path_access_request.go b/internal/defs/path_access_request.go index 9111fe9c..f22dd9fb 100644 --- a/internal/defs/path_access_request.go +++ b/internal/defs/path_access_request.go @@ -10,10 +10,11 @@ import ( // PathAccessRequest is a path access request. type PathAccessRequest struct { - Name string - Query string - Publish bool - SkipAuth bool + Name string + Query string + Publish bool + SkipAuth bool + UserAgent string // only if skipAuth = false Proto auth.Protocol @@ -36,6 +37,7 @@ func (r *PathAccessRequest) ToAuthRequest() *auth.Request { Query: r.Query, Protocol: r.Proto, ID: r.ID, + UserAgent: r.UserAgent, Credentials: r.Credentials, IP: r.IP, CustomVerifyFunc: r.CustomVerifyFunc, diff --git a/internal/servers/hls/session.go b/internal/servers/hls/session.go index c34cd70a..f413264c 100644 --- a/internal/servers/hls/session.go +++ b/internal/servers/hls/session.go @@ -38,6 +38,7 @@ type session struct { created time.Time query string user string + userAgent string lastRequestTime atomic.Int64 bytesSent atomic.Uint64 path defs.Path @@ -53,15 +54,17 @@ func (s *session) initialize(ctx *gin.Context) error { s.ip, _, _ = net.SplitHostPort(s.remoteAddr) s.created = time.Now() s.query = ctx.Request.URL.RawQuery + s.userAgent = ctx.Request.UserAgent() s.lastRequestTime.Store(time.Now().UnixNano()) accessReq := defs.PathAccessRequest{ - Name: s.pathName, - Query: s.query, - Publish: false, - Proto: auth.ProtocolHLS, - ID: &s.uuid, - IP: net.ParseIP(ctx.ClientIP()), + Name: s.pathName, + Query: s.query, + Publish: false, + UserAgent: s.userAgent, + Proto: auth.ProtocolHLS, + ID: &s.uuid, + IP: net.ParseIP(ctx.ClientIP()), } if s.isCDN { accessReq.SkipAuth = true @@ -167,6 +170,7 @@ func (s *session) apiItem() *defs.APIHLSSession { Path: s.pathName, Query: s.query, User: s.user, + UserAgent: s.userAgent, IsCDN: s.isCDN, OutboundBytes: outboundBytes, } diff --git a/internal/servers/rtmp/conn.go b/internal/servers/rtmp/conn.go index e0b368f5..853b6341 100644 --- a/internal/servers/rtmp/conn.go +++ b/internal/servers/rtmp/conn.go @@ -48,6 +48,7 @@ type conn struct { pathName string query string user string + userAgent string reader *stream.Reader } @@ -141,6 +142,7 @@ func (c *conn) runReader() error { c.mutex.Lock() c.rconn = conn + c.userAgent = conn.FlashVer c.mutex.Unlock() if !conn.Publish { @@ -156,10 +158,11 @@ func (c *conn) runRead() error { res, err := c.pathManager.AddReader(defs.PathAddReaderReq{ Author: c, AccessRequest: defs.PathAccessRequest{ - Name: pathName, - Query: c.rconn.URL.RawQuery, - Proto: auth.ProtocolRTMP, - ID: &c.uuid, + Name: pathName, + Query: c.rconn.URL.RawQuery, + UserAgent: c.userAgent, + Proto: auth.ProtocolRTMP, + ID: &c.uuid, Credentials: &auth.Credentials{ User: query.Get("user"), Pass: query.Get("pass"), @@ -249,11 +252,12 @@ func (c *conn) runPublish() error { UseRTPPackets: false, ReplaceNTP: true, AccessRequest: defs.PathAccessRequest{ - Name: pathName, - Query: c.rconn.URL.RawQuery, - Publish: true, - Proto: auth.ProtocolRTMP, - ID: &c.uuid, + Name: pathName, + Query: c.rconn.URL.RawQuery, + Publish: true, + UserAgent: c.userAgent, + Proto: auth.ProtocolRTMP, + ID: &c.uuid, Credentials: &auth.Credentials{ User: query.Get("user"), Pass: query.Get("pass"), @@ -344,6 +348,7 @@ func (c *conn) apiItem() *defs.APIRTMPConn { Path: c.pathName, Query: c.query, User: c.user, + UserAgent: c.userAgent, InboundBytes: bytesReceived, OutboundBytes: bytesSent, BytesReceived: bytesReceived, diff --git a/internal/servers/rtmp/server_test.go b/internal/servers/rtmp/server_test.go index eceed8df..5aea9c2e 100644 --- a/internal/servers/rtmp/server_test.go +++ b/internal/servers/rtmp/server_test.go @@ -196,6 +196,7 @@ func TestServerPublish(t *testing.T) { Path: "teststream", Query: "user=myuser&pass=mypass¶m=value", User: "myuser", + UserAgent: list.Items[0].UserAgent, InboundBytes: list.Items[0].InboundBytes, OutboundBytes: list.Items[0].OutboundBytes, OutboundFramesDiscarded: list.Items[0].OutboundFramesDiscarded, @@ -351,6 +352,7 @@ func TestServerRead(t *testing.T) { Path: "teststream", Query: "user=myuser&pass=mypass¶m=value", User: "myuser", + UserAgent: list.Items[0].UserAgent, InboundBytes: list.Items[0].InboundBytes, OutboundBytes: list.Items[0].OutboundBytes, OutboundFramesDiscarded: list.Items[0].OutboundFramesDiscarded, diff --git a/internal/servers/rtsp/server_test.go b/internal/servers/rtsp/server_test.go index 9700c11d..f16abc4b 100644 --- a/internal/servers/rtsp/server_test.go +++ b/internal/servers/rtsp/server_test.go @@ -188,6 +188,7 @@ func TestServerPublish(t *testing.T) { Path: "teststream", Query: "param=value", User: "myuser", + UserAgent: list.Items[0].UserAgent, InboundBytes: list.Items[0].InboundBytes, InboundRTPPackets: list.Items[0].InboundRTPPackets, OutboundBytes: list.Items[0].OutboundBytes, @@ -510,6 +511,7 @@ func TestServerRead(t *testing.T) { Path: "teststream", Query: "param=value", User: "myuser", + UserAgent: list.Items[0].UserAgent, InboundBytes: list.Items[0].InboundBytes, InboundRTPPackets: list.Items[0].InboundRTPPackets, OutboundBytes: list.Items[0].OutboundBytes, diff --git a/internal/servers/rtsp/session.go b/internal/servers/rtsp/session.go index 976a1387..fba8fa44 100644 --- a/internal/servers/rtsp/session.go +++ b/internal/servers/rtsp/session.go @@ -79,6 +79,7 @@ type session struct { outboundRTPPacketsDiscarded *counterdumper.Dumper mutex sync.RWMutex user string + userAgent string mpegtsDemuxer *mpegtsDemuxer } @@ -194,11 +195,17 @@ func (s *session) onAnnounce(c *conn, ctx *gortsplib.ServerHandlerOnAnnounceCtx) } } + var userAgent string + if ua, ok := ctx.Request.Header["User-Agent"]; ok && len(ua) > 0 { + userAgent = ua[0] + } + res, err := s.pathManager.FindPathConf(defs.PathFindPathConfReq{ AccessRequest: defs.PathAccessRequest{ Name: ctx.Path, Query: ctx.Query, Publish: true, + UserAgent: userAgent, Proto: auth.ProtocolRTSP, ID: &c.uuid, Credentials: rtsp.Credentials(ctx.Request), @@ -221,6 +228,7 @@ func (s *session) onAnnounce(c *conn, ctx *gortsplib.ServerHandlerOnAnnounceCtx) s.mutex.Lock() s.user = res.User + s.userAgent = userAgent s.mutex.Unlock() return &base.Response{ @@ -266,6 +274,11 @@ func (s *session) onSetup(c *conn, ctx *gortsplib.ServerHandlerOnSetupCtx, } } + var userAgent string + if ua, ok := ctx.Request.Header["User-Agent"]; ok && len(ua) > 0 { + userAgent = ua[0] + } + switch s.rsession.State() { case gortsplib.ServerSessionStateInitial: // play res, err := s.pathManager.AddReader(defs.PathAddReaderReq{ @@ -273,6 +286,7 @@ func (s *session) onSetup(c *conn, ctx *gortsplib.ServerHandlerOnSetupCtx, AccessRequest: defs.PathAccessRequest{ Name: ctx.Path, Query: ctx.Query, + UserAgent: userAgent, Proto: auth.ProtocolRTSP, ID: &c.uuid, Credentials: rtsp.Credentials(ctx.Request), @@ -304,6 +318,7 @@ func (s *session) onSetup(c *conn, ctx *gortsplib.ServerHandlerOnSetupCtx, s.mutex.Lock() s.user = res.User + s.userAgent = userAgent s.mutex.Unlock() return &base.Response{ @@ -385,10 +400,11 @@ func (s *session) onRecord(_ *gortsplib.ServerHandlerOnRecordCtx) (*base.Respons ReplaceNTP: !s.pathConf.UseAbsoluteTimestamp, ConfToCompare: s.pathConf, AccessRequest: defs.PathAccessRequest{ - Name: s.rsession.Path()[1:], - Query: s.rsession.Query(), - Publish: true, - SkipAuth: true, + Name: s.rsession.Path()[1:], + Query: s.rsession.Query(), + Publish: true, + SkipAuth: true, + UserAgent: s.userAgent, }, }) if err != nil { @@ -507,8 +523,9 @@ func (s *session) apiItem() *defs.APIRTSPSession { } return "" }(), - Query: s.rsession.Query(), - User: s.user, + Query: s.rsession.Query(), + User: s.user, + UserAgent: s.userAgent, Transport: func() *string { transport := s.rsession.Transport() if transport == nil { diff --git a/internal/servers/webrtc/http_server.go b/internal/servers/webrtc/http_server.go index 3d80574f..49bee4b5 100644 --- a/internal/servers/webrtc/http_server.go +++ b/internal/servers/webrtc/http_server.go @@ -142,6 +142,7 @@ func (s *httpServer) checkAuthOutsideSession(ctx *gin.Context, pathName string, Name: pathName, Query: ctx.Request.URL.RawQuery, Publish: publish, + UserAgent: ctx.Request.Header.Get("User-Agent"), Proto: auth.ProtocolWebRTC, Credentials: httpp.Credentials(ctx.Request), IP: net.ParseIP(ctx.ClientIP()), diff --git a/internal/servers/webrtc/server_test.go b/internal/servers/webrtc/server_test.go index 8a252e2a..a0e242a4 100644 --- a/internal/servers/webrtc/server_test.go +++ b/internal/servers/webrtc/server_test.go @@ -469,6 +469,7 @@ func TestServerPublish(t *testing.T) { Path: "teststream", Query: "param=value", User: "myuser", + UserAgent: list.Items[0].UserAgent, InboundBytes: list.Items[0].InboundBytes, InboundRTPPackets: list.Items[0].InboundRTPPackets, InboundRTPPacketsLost: list.Items[0].InboundRTPPacketsLost, @@ -763,6 +764,7 @@ func TestServerRead(t *testing.T) { Path: "teststream", Query: "param=value", User: "myuser", + UserAgent: list.Items[0].UserAgent, InboundBytes: list.Items[0].InboundBytes, InboundRTPPackets: list.Items[0].InboundRTPPackets, InboundRTPPacketsLost: list.Items[0].InboundRTPPacketsLost, diff --git a/internal/servers/webrtc/session.go b/internal/servers/webrtc/session.go index 4f755b20..ee348f7d 100644 --- a/internal/servers/webrtc/session.go +++ b/internal/servers/webrtc/session.go @@ -341,6 +341,7 @@ func (s *session) runPublish(req *initialRequestReq) (int, error) { Name: s.pathName, Query: s.httpRequest.URL.RawQuery, Publish: true, + UserAgent: s.httpRequest.Header.Get("User-Agent"), Proto: auth.ProtocolWebRTC, ID: &s.uuid, Credentials: httpp.Credentials(s.httpRequest), @@ -449,10 +450,11 @@ func (s *session) runPublish(req *initialRequestReq) (int, error) { ReplaceNTP: !res1.Conf.UseAbsoluteTimestamp, ConfToCompare: res1.Conf, AccessRequest: defs.PathAccessRequest{ - Name: s.pathName, - Query: s.httpRequest.URL.RawQuery, - Publish: true, - SkipAuth: true, + Name: s.pathName, + Query: s.httpRequest.URL.RawQuery, + Publish: true, + SkipAuth: true, + UserAgent: s.httpRequest.Header.Get("User-Agent"), }, }) if err != nil { @@ -482,6 +484,7 @@ func (s *session) runRead(req *initialRequestReq) (int, error) { AccessRequest: defs.PathAccessRequest{ Name: s.pathName, Query: s.httpRequest.URL.RawQuery, + UserAgent: s.httpRequest.Header.Get("User-Agent"), Proto: auth.ProtocolWebRTC, ID: &s.uuid, Credentials: httpp.Credentials(s.httpRequest), @@ -754,6 +757,7 @@ func (s *session) apiItem() *defs.APIWebRTCSession { Path: s.pathName, Query: s.httpRequest.URL.RawQuery, User: s.user, + UserAgent: s.httpRequest.Header.Get("User-Agent"), InboundBytes: bytesReceived, InboundRTPPackets: rtpPacketsReceived, InboundRTPPacketsLost: rtpPacketsLost, diff --git a/mediamtx.yml b/mediamtx.yml index 156988a7..951062cd 100644 --- a/mediamtx.yml +++ b/mediamtx.yml @@ -94,18 +94,7 @@ authInternalUsers: # HTTP-based authentication. # URL called to perform authentication. Every time a user wants # to authenticate, the server calls this URL with the POST method -# and a body containing: -# { -# "user": "user", -# "password": "password", -# "token": "token", -# "ip": "ip", -# "action": "publish|read|playback|api|metrics|pprof", -# "path": "path", -# "protocol": "rtsp|rtmp|hls|webrtc|srt", -# "id": "id", -# "query": "query" -# } +# and a payload described in the documentation. # If the response code is 20x, authentication is accepted, otherwise # it is discarded. authHTTPAddress: