Forward real IP in internal-api client calls

This commit is contained in:
Andrey Beletsky 2019-09-18 18:02:03 +07:00
parent fd916d9eae
commit a8c339e5b4
2 changed files with 83 additions and 49 deletions

View file

@ -15,9 +15,18 @@ import (
// Client stores data about internal-apis call it is about to make. // Client stores data about internal-apis call it is about to make.
type Client struct { type Client struct {
ServerAddress string
AuthToken string AuthToken string
Logger *log.Logger Logger *log.Logger
serverAddress string
extraHeaders map[string]string
}
// ClientOpts allow to provide extra parameters to NewClient:
// - ServerAddress
// - RemoteIP — to forward the IP of a frontend client making the request
type ClientOpts struct {
ServerAddress string
RemoteIP string
} }
// APIResponse reflects internal-apis JSON response format. // APIResponse reflects internal-apis JSON response format.
@ -31,23 +40,35 @@ type APIResponse struct {
type ResponseData map[string]interface{} type ResponseData map[string]interface{}
const ( const (
defaultAPIHost = "https://api.lbry.com" defaultServerAddress = "https://api.lbry.com"
timeout = 5 * time.Second timeout = 5 * time.Second
userObjectPath = "user" userObjectPath = "user"
headerForwardedFor = "X-Forwarded-For"
) )
// NewClient returns a client instance for internal-apis. It requires authToken to be provided // NewClient returns a client instance for internal-apis. It requires authToken to be provided
// for authentication. // for authentication.
func NewClient(authToken string) Client { func NewClient(authToken string, opts *ClientOpts) Client {
return Client{ c := Client{
ServerAddress: defaultAPIHost, serverAddress: defaultServerAddress,
extraHeaders: make(map[string]string),
AuthToken: authToken, AuthToken: authToken,
Logger: log.StandardLogger(), Logger: log.StandardLogger(),
} }
if opts != nil {
if opts.ServerAddress != "" {
c.serverAddress = opts.ServerAddress
}
if opts.RemoteIP != "" {
c.extraHeaders[headerForwardedFor] = opts.RemoteIP
}
}
return c
} }
func (c Client) getEndpointURL(object, method string) string { func (c Client) getEndpointURL(object, method string) string {
return fmt.Sprintf("%s/%s/%s", c.ServerAddress, object, method) return fmt.Sprintf("%s/%s/%s", c.serverAddress, object, method)
} }
func (c Client) prepareParams(params map[string]interface{}) (string, error) { func (c Client) prepareParams(params map[string]interface{}) (string, error) {
@ -69,9 +90,14 @@ func (c Client) doCall(url string, payload string) ([]byte, error) {
if err != nil { if err != nil {
return body, err return body, err
} }
req.Header.Add("Accept", "application/json") req.Header.Add("Accept", "application/json")
req.Header.Add("Content-Type", "application/x-www-form-urlencoded") req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
for k, v := range c.extraHeaders {
req.Header.Set(k, v)
}
client := &http.Client{Timeout: timeout} client := &http.Client{Timeout: timeout}
r, err := client.Do(req) r, err := client.Do(req)
if err != nil { if err != nil {

View file

@ -1,8 +1,8 @@
package lbryinc package lbryinc
import ( import (
"log"
"net/http" "net/http"
"net/http/httptest"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -10,19 +10,16 @@ import (
) )
func TestUserMeWrongToken(t *testing.T) { func TestUserMeWrongToken(t *testing.T) {
c := NewClient("abc") c := NewClient("abc", nil)
r, err := c.UserMe() r, err := c.UserMe()
require.NotNil(t, err) require.NotNil(t, err)
assert.Equal(t, "could not authenticate user", err.Error()) assert.Equal(t, "could not authenticate user", err.Error())
assert.Nil(t, r) assert.Nil(t, r)
} }
const dummyServerURL = "http://127.0.0.1:59999" func launchDummyServer(lastReq **http.Request) *httptest.Server {
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
func launchDummyServer() { *lastReq = &*r
s := &http.Server{
Addr: "127.0.0.1:59999",
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8") w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
response := []byte(`{ response := []byte(`{
@ -50,16 +47,27 @@ func launchDummyServer() {
} }
}`) }`)
w.Write(response) w.Write(response)
}), }))
}
log.Fatal(s.ListenAndServe())
} }
func TestUserMe(t *testing.T) { func TestUserMe(t *testing.T) {
go launchDummyServer() var req *http.Request
c := NewClient("realToken") ts := launchDummyServer(&req)
c.ServerAddress = dummyServerURL defer ts.Close()
c := NewClient("realToken", &ClientOpts{ServerAddress: ts.URL})
r, err := c.UserMe() r, err := c.UserMe()
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, r["primary_email"], "andrey@lbry.com") assert.Equal(t, r["primary_email"], "andrey@lbry.com")
} }
func TestRemoteIP(t *testing.T) {
var req *http.Request
ts := launchDummyServer(&req)
defer ts.Close()
c := NewClient("realToken", &ClientOpts{ServerAddress: ts.URL, RemoteIP: "8.8.8.8"})
_, err := c.UserMe()
assert.Nil(t, err)
assert.Equal(t, []string{"8.8.8.8"}, req.Header["X-Forwarded-For"])
}