diff --git a/app/auth/auth.go b/app/auth/auth.go index d0555fae..a96e2697 100644 --- a/app/auth/auth.go +++ b/app/auth/auth.go @@ -23,8 +23,8 @@ type ctxKey int const userContextKey ctxKey = iota type CurrentUser struct { - IP string - IAPIClient *iapi.Client + ipAddr string + iac *iapi.Client user *models.User err error @@ -68,8 +68,8 @@ func GetCurrentUserData(ctx context.Context) (*CurrentUser, error) { return res, nil } -func NewCurrentUser(u *models.User, e error) *CurrentUser { - return &CurrentUser{user: u, err: e} +func NewCurrentUser(u *models.User, ipAddr string, iac *iapi.Client, e error) *CurrentUser { + return &CurrentUser{user: u, ipAddr: ipAddr, iac: iac, err: e} } func (cu CurrentUser) User() *models.User { @@ -80,6 +80,14 @@ func (cu CurrentUser) Err() error { return cu.err } +func (cu CurrentUser) IP() string { + return cu.ipAddr +} + +func (cu CurrentUser) IAPIClient() *iapi.Client { + return cu.iac +} + // NewIAPIProvider authenticates a user by hitting internal-api with the auth token // and matching the response to a local user. If auth is successful, the user will have a // lbrynet server assigned and a wallet that's created and ready to use. diff --git a/app/auth/middleware.go b/app/auth/middleware.go index 07f298f5..574bd714 100644 --- a/app/auth/middleware.go +++ b/app/auth/middleware.go @@ -44,9 +44,7 @@ func Middleware(auther Authenticator) mux.MiddlewareFunc { } } - cu := NewCurrentUser(user, err) - cu.IP = ipAddr - cu.IAPIClient = iac + cu := NewCurrentUser(user, ipAddr, iac, err) next.ServeHTTP(w, r.Clone(context.WithValue(r.Context(), userContextKey, cu))) }) } @@ -79,9 +77,7 @@ func LegacyMiddleware(provider Provider) mux.MiddlewareFunc { hub.Scope().SetUser(sentry.User{ID: strconv.Itoa(user.ID), IPAddress: ipAddr}) } } - cu := NewCurrentUser(user, err) - cu.IP = ipAddr - cu.IAPIClient = iac + cu := NewCurrentUser(user, ipAddr, iac, err) next.ServeHTTP(w, r.Clone(context.WithValue(r.Context(), userContextKey, cu))) return } diff --git a/app/auth/middleware_test.go b/app/auth/middleware_test.go index 925409c5..b43b2a17 100644 --- a/app/auth/middleware_test.go +++ b/app/auth/middleware_test.go @@ -192,7 +192,7 @@ func TestFromRequestFail(t *testing.T) { func dummyHandler(w http.ResponseWriter, r *http.Request) { cu, err := GetCurrentUserData(r.Context()) - w.Header().Add("x-remote-ip", cu.IP) + w.Header().Add("x-remote-ip", cu.IP()) if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(fmt.Sprintf("unexpected error: %s", err))) diff --git a/app/query/paid_test.go b/app/query/paid_test.go index db2496a7..a49b6bb7 100644 --- a/app/query/paid_test.go +++ b/app/query/paid_test.go @@ -32,7 +32,7 @@ const ( noAccessPaidURL = "lbry://@PlayNice#4/Alexswar#c" noAccessMembersOnlyURL = "lbry://@gifprofile#7/members-only-no-access#8" - livestreamURL = "lbry://@gifprofile#7/members-only-livestream#f" + livestreamURL = "lbry://@gifprofile:7/members-only-live-2:4" falseIP = "8.8.8.8" ) @@ -88,9 +88,8 @@ func (s *paidContentSuite) SetupSuite() { ) s.Require().NoError(err) - cu := auth.NewCurrentUser(u, nil) - cu.IP = falseIP - cu.IAPIClient = iac + cu := auth.NewCurrentUser( + u, falseIP, iac, nil) s.cu = cu } @@ -111,7 +110,7 @@ func (s *paidContentSuite) TestUnauthorized() { for _, tc := range cases { s.Run(tc.url, func() { request := jsonrpc.NewRequest(MethodGet, map[string]interface{}{"uri": tc.url}) - ctx := auth.AttachCurrentUser(bgctx(), auth.NewCurrentUser(nil, errors.Err("anonymous"))) + ctx := auth.AttachCurrentUser(bgctx(), auth.NewCurrentUser(nil, falseIP, nil, errors.Err("anonymous"))) _, err := NewCaller(s.sdkAddress, 0).Call(ctx, request) s.EqualError(err, tc.errString) }) diff --git a/app/query/processors.go b/app/query/processors.go index a0f6042d..6d89f33c 100644 --- a/app/query/processors.go +++ b/app/query/processors.go @@ -91,7 +91,7 @@ func preflightHookGet(caller *Caller, ctx context.Context) (*jsonrpc.RPCResponse if err != nil { return nil, err } - ip := cu.IP + ip := cu.IP() hlsHash := signStreamURL(hlsUrl, fmt.Sprintf("ip=%s&pass=%s", ip, pcfg["paidpass"])) startQuery := fmt.Sprintf("hash-hls=%s&ip=%s&pass=%s", hlsHash, ip, pcfg["paidpass"]) @@ -113,7 +113,7 @@ func preflightHookGet(caller *Caller, ctx context.Context) (*jsonrpc.RPCResponse if err != nil { return nil, err } - ip := cu.IP + ip := cu.IP() query := fmt.Sprintf("ip=%s&pass=%s", ip, pcfg["paidpass"]) responseResult[ParamStreamingUrl] = fmt.Sprintf( "%s?ip=%s&hash=%s", @@ -217,11 +217,14 @@ func preflightHookGet(caller *Caller, ctx context.Context) (*jsonrpc.RPCResponse func checkStreamAccess(ctx context.Context, claim *ljsonrpc.Claim) (bool, error) { var ( - accessType string + accessType, environ string ) params := GetQuery(ctx).ParamsAsMap() _, isLivestream := params["base_streaming_url"] + if p, ok := params[iapi.ParamEnviron]; ok { + environ, _ = p.(string) + } TagLoop: for _, t := range claim.Value.Tags { @@ -252,10 +255,14 @@ TagLoop: if err != nil { return false, errors.Err("no user data in context: %w", err) } - if cu.IAPIClient == nil { + + iac := cu.IAPIClient() + if iac == nil { return false, errors.Err("authentication required") } - iac := cu.IAPIClient + if environ == iapi.EnvironTest { + iac = iac.Clone(iapi.WithEnvironment(iapi.EnvironTest)) + } switch accessType { case accessTypePurchase, accessTypeRental: diff --git a/internal/e2etest/e2etest.go b/internal/e2etest/e2etest.go index a156f80d..f0e5a09d 100644 --- a/internal/e2etest/e2etest.go +++ b/internal/e2etest/e2etest.go @@ -90,9 +90,7 @@ func (s *UserTestHelper) Setup(t *testing.T) error { return err } - cu := auth.NewCurrentUser(u, nil) - cu.IP = "8.8.8.8" - cu.IAPIClient = iac + cu := auth.NewCurrentUser(u, "8.8.8.8", iac, nil) s.TestUser = &TestUser{ User: u, diff --git a/pkg/iapi/iapi.go b/pkg/iapi/iapi.go index cfa49c24..3b8b111a 100644 --- a/pkg/iapi/iapi.go +++ b/pkg/iapi/iapi.go @@ -12,6 +12,10 @@ import ( ) const ( + EnvironLive = "live" + EnvironTest = "test" + ParamEnviron = "environment" + defaultServerAddress = "https://api.odysee.com" timeout = 5 * time.Second headerForwardedFor = "X-Forwarded-For" @@ -35,24 +39,17 @@ type httpClient interface { // - RemoteIP — to forward the IP of a frontend client making the request type clientOptions struct { server string - legacyToken string - oauthToken string - remoteIP string extraHeaders map[string]string extraParams map[string]string httpClient httpClient } func WithLegacyToken(token string) func(options *clientOptions) { - return func(options *clientOptions) { - options.legacyToken = token - } + return WithExtraParam(paramLegacyToken, token) } func WithOAuthToken(token string) func(options *clientOptions) { - return func(options *clientOptions) { - options.oauthToken = token - } + return WithExtraHeader(headerOauthToken, "Bearer "+token) } func WithServer(server string) func(options *clientOptions) { @@ -62,16 +59,19 @@ func WithServer(server string) func(options *clientOptions) { } func WithRemoteIP(remoteIP string) func(options *clientOptions) { - return func(options *clientOptions) { - options.remoteIP = remoteIP - } + return WithExtraHeader(headerForwardedFor, remoteIP) } + func WithExtraHeader(key, value string) func(options *clientOptions) { return func(options *clientOptions) { options.extraHeaders[key] = value } } +func WithEnvironment(name string) func(options *clientOptions) { + return WithExtraParam(ParamEnviron, name) +} + func WithExtraParam(key, value string) func(options *clientOptions) { return func(options *clientOptions) { options.extraParams[key] = value @@ -90,7 +90,7 @@ func NewClient(optionFuncs ...func(*clientOptions)) (*Client, error) { options := &clientOptions{ server: "https://api.odysee.com", extraHeaders: map[string]string{}, - extraParams: map[string]string{}, + extraParams: map[string]string{ParamEnviron: EnvironLive}, httpClient: &http.Client{Timeout: timeout}, } @@ -98,14 +98,7 @@ func NewClient(optionFuncs ...func(*clientOptions)) (*Client, error) { optionFunc(options) } - if options.remoteIP != "" { - options.extraHeaders[headerForwardedFor] = options.remoteIP - } - if options.legacyToken != "" { - options.extraParams[paramLegacyToken] = options.legacyToken - } else if options.oauthToken != "" { - options.extraHeaders[headerOauthToken] = "Bearer " + options.oauthToken - } else { + if options.extraHeaders[headerOauthToken] == "" && options.extraParams[paramLegacyToken] == "" { return nil, errors.New("either legacy or oauth token required") } @@ -113,7 +106,24 @@ func NewClient(optionFuncs ...func(*clientOptions)) (*Client, error) { return c, nil } -func (c Client) prepareParams(params map[string]string) url.Values { +func (c Client) Clone(optionFuncs ...func(*clientOptions)) *Client { + o := c.options + o.extraHeaders = map[string]string{} + o.extraParams = map[string]string{} + for k, v := range c.options.extraHeaders { + o.extraHeaders[k] = v + } + for k, v := range c.options.extraParams { + o.extraParams[k] = v + } + + for _, optionFunc := range optionFuncs { + optionFunc(&o) + } + return &Client{options: o} +} + +func (c *Client) prepareParams(params map[string]string) url.Values { data := url.Values{} for k, v := range c.options.extraParams { data.Add(k, v) @@ -124,7 +134,7 @@ func (c Client) prepareParams(params map[string]string) url.Values { return data } -func (c Client) Call(path string, params map[string]string, target interface{}) error { +func (c *Client) Call(path string, params map[string]string, target interface{}) error { r, err := http.NewRequest( http.MethodPost, fmt.Sprintf("%s/%s", c.options.server, path), diff --git a/pkg/iapi/iapi_test.go b/pkg/iapi/iapi_test.go index 91519cf4..a689382b 100644 --- a/pkg/iapi/iapi_test.go +++ b/pkg/iapi/iapi_test.go @@ -11,6 +11,26 @@ import ( "github.com/stretchr/testify/require" ) +func TestNewClient(t *testing.T) { + dummyToken := randomdata.Alphanumeric(120) + remoteIP := "8.8.8.8" + + _, err := NewClient(WithRemoteIP(remoteIP)) + assert.Error(t, err) + + c, err := NewClient(WithOAuthToken(dummyToken), WithRemoteIP(remoteIP)) + require.NoError(t, err) + assert.Equal(t, "Bearer "+dummyToken, c.options.extraHeaders[headerOauthToken]) + assert.Equal(t, remoteIP, c.options.extraHeaders[headerForwardedFor]) + assert.Equal(t, EnvironLive, c.options.extraParams[ParamEnviron]) + + cc := c.Clone(WithEnvironment(EnvironTest)) + assert.Equal(t, EnvironTest, cc.options.extraParams[ParamEnviron]) + assert.Equal(t, remoteIP, cc.options.extraHeaders[headerForwardedFor]) + assert.Equal(t, "Bearer "+dummyToken, cc.options.extraHeaders[headerOauthToken]) + assert.Equal(t, EnvironLive, c.options.extraParams[ParamEnviron]) +} + func TestCallCustomerList(t *testing.T) { oat, err := test.GetTestToken() require.NoError(t, err)