diff --git a/desktop/lib.go b/desktop/lib.go index add38e623..6fc035f69 100644 --- a/desktop/lib.go +++ b/desktop/lib.go @@ -11,6 +11,8 @@ import ( "strings" "time" + "github.com/joho/godotenv" + "github.com/getlantern/appdir" "github.com/getlantern/errors" "github.com/getlantern/flashlight/v7" @@ -26,7 +28,6 @@ import ( "github.com/getlantern/lantern-client/internalsdk/common" "github.com/getlantern/lantern-client/internalsdk/protos" "github.com/getlantern/osversion" - "github.com/joho/godotenv" ) import "C" diff --git a/internalsdk/android.go b/internalsdk/android.go index f717d580e..239f5b020 100644 --- a/internalsdk/android.go +++ b/internalsdk/android.go @@ -543,7 +543,7 @@ func run(configDir, locale string, settings Settings, wrappedSession Session) { config.ForceCountry(forcedCountryCode) } - userConfig := newUserConfig(session) + userConfig := &userConfig{session} globalConfigChanged := make(chan interface{}) geoRefreshed := geolookup.OnRefresh() diff --git a/internalsdk/auth/auth.go b/internalsdk/auth/auth.go index 69b7c6ae9..10e36e9e8 100644 --- a/internalsdk/auth/auth.go +++ b/internalsdk/auth/auth.go @@ -2,16 +2,14 @@ package auth import ( "context" + "time" - "fmt" "net/http" "strings" - "time" "github.com/getlantern/flashlight/v7/proxied" "github.com/getlantern/golog" "github.com/getlantern/lantern-client/internalsdk/common" - "github.com/getlantern/lantern-client/internalsdk/pro" "github.com/getlantern/lantern-client/internalsdk/protos" "github.com/getlantern/lantern-client/internalsdk/webclient" @@ -52,21 +50,9 @@ type AuthClient interface { // NewClient creates a new instance of AuthClient func NewClient(baseURL string, userConfig func() common.UserConfig) AuthClient { - // The default http.RoundTripper is ChainedNonPersistent which proxies requests through chained servers - // and does not use keep alive connections. Since no root CA is specified, we do not need to check for an error. - - var httpClient *http.Client - - if baseURL == fmt.Sprintf("https://%s", common.APIBaseUrl) { - log.Debug("using proxied.Fronted") - //this is ios version - httpClient = &http.Client{ - Transport: proxied.Fronted(30 * time.Second), - } - } else { - log.Debug("using proxied.ChainedNonPersistent") - rt, _ := proxied.ChainedNonPersistent("") - httpClient = pro.NewHTTPClient(rt, 30*time.Second) + httpClient := &http.Client{ + Transport: proxied.ChainedThenFronted(), + Timeout: 30 * time.Second, } rc := webclient.NewRESTClient(&webclient.Opts{ diff --git a/internalsdk/common/const.go b/internalsdk/common/const.go index 7bed11d4b..ee0ea2a12 100644 --- a/internalsdk/common/const.go +++ b/internalsdk/common/const.go @@ -40,10 +40,15 @@ var ( // GlobalStagingURL is the URL for fetching the global config in a staging environment. GlobalStagingURL = "https://globalconfig.flashlightproxy.com/global.yaml.gz" - ProAPIHost = "api.getiantem.org" + // ProAPIBaseURL is the URL for all requests to the back-end pro server. Paths at this URL can + // be hit directly (not recommended due to censorship), through proxies, or through domain + // fronting. + ProAPIBaseURL = "df.iantem.io/api/pro-server" - DFBaseUrl = "df.iantem.io/api/v1" - APIBaseUrl = "iantem.io/api/v1" + // APIBaseURL is the URL for all requests to the back-end "API service". Paths at this URL can + // be hit directly (not recommended due to censorship), through proxies, or through domain + // fronting. + APIBaseURL = "df.iantem.io/api/v1" log = golog.LoggerFor("flashlight.common") @@ -63,9 +68,6 @@ func ForceStaging() { func initInternal() { isStaging := IsStagingEnvironment() log.Debugf("****************************** stagingMode: %v", isStaging) - if isStaging { - ProAPIHost = "api-staging.getiantem.org" - } forceAds, _ = strconv.ParseBool(os.Getenv("FORCEADS")) } diff --git a/internalsdk/issue.go b/internalsdk/issue.go index f09e844a7..338b3ece9 100644 --- a/internalsdk/issue.go +++ b/internalsdk/issue.go @@ -34,8 +34,9 @@ func SendIssueReport( if err != nil { return err } + userConfig := &userConfig{&panickingSessionImpl{session}} return issue.SendReport( - newUserConfig(&panickingSessionImpl{session}), + userConfig, issueTypeInt, description, subscriptionLevel, diff --git a/internalsdk/pro/http.go b/internalsdk/pro/http.go deleted file mode 100644 index 738f82abf..000000000 --- a/internalsdk/pro/http.go +++ /dev/null @@ -1,25 +0,0 @@ -package pro - -import ( - "net/http" - "time" - - "github.com/getlantern/flashlight/v7/proxied" -) - -// NewHTTPClient creates a new http.Client that is configured to use the given options and http.RoundTripper wrapped with -// proxied.AsRoundTripper to process requests -func NewHTTPClient(rt http.RoundTripper, timeout time.Duration) *http.Client { - if timeout == 0 { - timeout = 30 * time.Second - } - return &http.Client{ - Transport: proxied.AsRoundTripper( - func(req *http.Request) (*http.Response, error) { - log.Tracef("Pro client processing request to: %v (%v)", req.Host, req.URL.Host) - return rt.RoundTrip(req) - }, - ), - Timeout: timeout, - } -} diff --git a/internalsdk/pro/pro.go b/internalsdk/pro/pro.go index 0de1d51da..6a2d4f1e3 100644 --- a/internalsdk/pro/pro.go +++ b/internalsdk/pro/pro.go @@ -6,6 +6,9 @@ import ( "fmt" "net/http" "strings" + "time" + + "github.com/go-resty/resty/v2" "github.com/getlantern/errors" "github.com/getlantern/flashlight/v7/proxied" @@ -13,7 +16,6 @@ import ( "github.com/getlantern/lantern-client/internalsdk/common" "github.com/getlantern/lantern-client/internalsdk/protos" "github.com/getlantern/lantern-client/internalsdk/webclient" - "github.com/go-resty/resty/v2" "github.com/leekchan/accounting" "github.com/shopspring/decimal" @@ -57,17 +59,26 @@ type ProClient interface { // NewClient creates a new instance of ProClient func NewClient(baseURL string, opts *webclient.Opts) ProClient { if opts.HttpClient == nil { - // The default http.RoundTripper used by the ProClient is ParallelForIdempotent which - // attempts to send requests through both chained and direct fronted routes in parallel - // for HEAD and GET requests and ChainedThenFronted for all others. - opts.HttpClient = NewHTTPClient(proxied.ParallelForIdempotent(), opts.Timeout) + opts.HttpClient = &http.Client{ + // The default http.RoundTripper used by the ProClient is ParallelForIdempotent which + // attempts to send requests through both chained and direct fronted routes in parallel + // for HEAD and GET requests and ChainedThenFronted for all others. + Transport: proxied.ParallelForIdempotent(), + Timeout: 30 * time.Second, + } } + if opts.OnBeforeRequest == nil { opts.OnBeforeRequest = func(client *resty.Client, req *http.Request) error { - prepareProRequest(req, common.ProAPIHost, opts.UserConfig()) + prepareProRequest(req, opts.UserConfig()) return nil } } + + if opts.BaseURL == "" { + opts.BaseURL = baseURL + } + return &proClient{ userConfig: opts.UserConfig, RESTClient: webclient.NewRESTClient(opts), @@ -75,11 +86,10 @@ func NewClient(baseURL string, opts *webclient.Opts) ProClient { } // prepareProRequest normalizes requests to the pro server with device ID, user ID, etc set. -func prepareProRequest(r *http.Request, proAPIHost string, userConfig common.UserConfig) { +func prepareProRequest(r *http.Request, userConfig common.UserConfig) { if r.URL.Scheme == "" { r.URL.Scheme = "http" } - r.URL.Host = proAPIHost r.RequestURI = "" // http: Request.RequestURI can't be set in client requests. r.Header.Set("Access-Control-Allow-Headers", strings.Join([]string{ common.DeviceIdHeader, diff --git a/internalsdk/pro/pro_test.go b/internalsdk/pro/pro_test.go index e7ea13f95..9a9890b20 100644 --- a/internalsdk/pro/pro_test.go +++ b/internalsdk/pro/pro_test.go @@ -26,7 +26,7 @@ package pro // }, // } -// proClient := NewClient(fmt.Sprintf("https://%s", common.ProAPIHost), webclientOpts) +// proClient := NewClient(common.ProAPIBaseURL, webclientOpts) // puchaseData := map[string]interface{}{ // "idempotencyKey": strconv.FormatInt(time.Now().UnixNano(), 10), diff --git a/internalsdk/pro/proxy.go b/internalsdk/pro/proxy.go deleted file mode 100644 index 61fb3f70c..000000000 --- a/internalsdk/pro/proxy.go +++ /dev/null @@ -1,115 +0,0 @@ -package pro - -import ( - "bytes" - "compress/gzip" - "encoding/json" - "io" - "io/ioutil" - "net/http" - "net/http/httputil" - "strconv" - "strings" - "time" - - "github.com/getlantern/lantern-client/internalsdk/common" - "github.com/getlantern/lantern-client/internalsdk/protos" -) - -type proxyTransport struct { - // Satisfies http.RoundTripper -} - -func (pt *proxyTransport) processOptions(req *http.Request) *http.Response { - resp := &http.Response{ - StatusCode: http.StatusOK, - Header: http.Header{ - "Connection": {"keep-alive"}, - "Via": {"Lantern Client"}, - }, - Body: ioutil.NopCloser(strings.NewReader("preflight complete")), - } - if !common.ProcessCORS(resp.Header, req) { - return &http.Response{ - StatusCode: http.StatusForbidden, - } - } - return resp -} - -func (pt *proxyTransport) RoundTrip(req *http.Request) (resp *http.Response, err error) { - if req.Method == "OPTIONS" { - // No need to proxy the OPTIONS request. - return pt.processOptions(req), nil - } - origin := req.Header.Get("Origin") - // Workaround for https://github.com/getlantern/pro-server/issues/192 - req.Header.Del("Origin") - resp, err = NewHTTPClient(http.DefaultTransport, time.Duration(time.Second*30)).Do(req) - if err != nil { - log.Errorf("Could not issue HTTP request? %v", err) - return - } - - // Put the header back for subsequent CORS processing. - req.Header.Set("Origin", origin) - common.ProcessCORS(resp.Header, req) - if req.URL.Path != "/user-data" || resp.StatusCode != http.StatusOK { - return - } - // Try to update user data implicitly - _userID := req.Header.Get("X-Lantern-User-Id") - if _userID == "" { - log.Error("Request has an empty user ID") - return - } - userID, parseErr := strconv.ParseInt(_userID, 10, 64) - if parseErr != nil { - log.Errorf("User ID %s is invalid", _userID) - return - } - body, readErr := ioutil.ReadAll(resp.Body) - if readErr != nil { - log.Errorf("Error read response body: %v", readErr) - return - } - resp.Body = ioutil.NopCloser(bytes.NewReader(body)) - encoding := resp.Header.Get("Content-Encoding") - var br io.Reader = bytes.NewReader(body) - switch encoding { - case "gzip": - gzr, readErr := gzip.NewReader(bytes.NewReader(body)) - if readErr != nil { - log.Errorf("Unable to decode gzipped data: %v", readErr) - return - } - br = gzr - case "": - default: - log.Errorf("Unsupported response encoding %s", encoding) - return - } - user := protos.User{} - readErr = json.NewDecoder(br).Decode(&user) - if readErr != nil { - log.Errorf("Error decoding JSON: %v", readErr) - return - } - log.Debugf("Updating user data implicitly for user %v", userID) - return -} - -// APIHandler returns an HTTP handler that specifically looks for and properly handles pro server requests. -func APIHandler(proAPIHost string, userConfig common.UserConfig) http.Handler { - log.Debugf("Returning pro API handler hitting host: %v", proAPIHost) - return &httputil.ReverseProxy{ - Transport: &proxyTransport{}, - Director: func(r *http.Request) { - // Strip /pro from path. - if strings.HasPrefix(r.URL.Path, "/pro/") { - r.URL.Path = r.URL.Path[4:] - } - prepareProRequest(r, proAPIHost, userConfig) - }, - } -} diff --git a/internalsdk/pro/proxy_test.go b/internalsdk/pro/proxy_test.go deleted file mode 100644 index 80acdc18a..000000000 --- a/internalsdk/pro/proxy_test.go +++ /dev/null @@ -1,64 +0,0 @@ -package pro - -// func TestProxy(t *testing.T) { -// uc := common.NewUserConfigData(common.DefaultAppName, "device", 0, "token", nil, "en-US") -// m := &testutils.MockRoundTripper{Header: http.Header{}, Body: strings.NewReader("GOOD")} -// // httpClient := &http.Client{Transport: m} -// l, err := net.Listen("tcp", "localhost:0") -// if !assert.NoError(t, err) { -// return -// } - -// addr := l.Addr() -// url := fmt.Sprintf("http://%s/pro/abc", addr) -// t.Logf("Test server listening at %s", url) -// go http.Serve(l, APIHandler(addr.String(), uc)) - -// req, err := http.NewRequest("OPTIONS", url, nil) -// if !assert.NoError(t, err) { -// return -// } - -// origin := "http://localhost:48933" -// req.Header.Set("Origin", origin) -// resp, err := (&http.Client{}).Do(req) -// if assert.NoError(t, err, "OPTIONS request should succeed") { -// assert.Equal(t, 200, resp.StatusCode, "should respond 200 to OPTIONS") -// assert.Equal(t, origin, resp.Header.Get("Access-Control-Allow-Origin"), "should respond with correct header") -// _ = resp.Body.Close() -// } -// assert.Nil(t, m.Req, "should not pass the OPTIONS request to origin server") - -// req, err = http.NewRequest("GET", url, nil) -// if !assert.NoError(t, err) { -// return -// } -// req.Header.Set("Origin", origin) -// resp, err = (&http.Client{}).Do(req) -// if assert.NoError(t, err, "GET request should have no error") { -// assert.Equal(t, 200, resp.StatusCode, "should respond 200 ok") -// assert.Equal(t, origin, resp.Header.Get("Access-Control-Allow-Origin"), "should respond with correct header") -// assert.NotEmpty(t, resp.Header.Get("Access-Control-Allow-Methods"), "should respond with correct header") -// msg, _ := ioutil.ReadAll(resp.Body) -// _ = resp.Body.Close() -// assert.Equal(t, "GOOD", string(msg), "should respond expected body") -// } -// if assert.NotNil(t, m.Req, "should pass through non-OPTIONS requests to origin server") { -// t.Log(m.Req) -// assert.Equal(t, origin, resp.Header.Get("Access-Control-Allow-Origin"), "should respond with correct header") -// assert.NotEmpty(t, resp.Header.Get("Access-Control-Allow-Methods"), "should respond with correct header") -// } - -// url = fmt.Sprintf("http://%s/pro/user-data", addr) -// msg, _ := json.Marshal(&protos.User{Email: "a@a.com"}) -// m.Body = bytes.NewReader(msg) -// req, err = http.NewRequest("GET", url, nil) -// if !assert.NoError(t, err) { -// return -// } -// req.Header.Set("X-Lantern-User-Id", "1234") -// resp, err = (&http.Client{}).Do(req) -// if assert.NoError(t, err, "GET request should have no error") { -// assert.Equal(t, 200, resp.StatusCode, "should respond 200 ok") -// } -// } diff --git a/internalsdk/session_model.go b/internalsdk/session_model.go index 3198e6a26..860f737ce 100644 --- a/internalsdk/session_model.go +++ b/internalsdk/session_model.go @@ -157,35 +157,13 @@ func NewSessionModel(mdb minisql.DB, opts *SessionModelOpts) (*SessionModel, err m := &SessionModel{baseModel: base} - deviceID, _ := m.GetDeviceID() - userID, _ := m.GetUserID() - token, _ := m.GetToken() - lang, _ := m.Locale() - m.proClient = createProClient(m, opts.Platform) - authUrl := common.DFBaseUrl - if opts.Platform == "ios" { - authUrl = common.APIBaseUrl - } - m.authClient = auth.NewClient(fmt.Sprintf("https://%s", authUrl), func() common.UserConfig { - internalHeaders := map[string]string{ - common.PlatformHeader: opts.Platform, - common.AppVersionHeader: common.ApplicationVersion, - } - return common.NewUserConfig( - common.DefaultAppName, - deviceID, - userID, - token, - internalHeaders, - lang, - ) - }) + m.authClient = auth.NewClient(common.APIBaseURL, newUserConfig(m, opts.Platform)) m.baseModel.doInvokeMethod = m.doInvokeMethod if opts.Platform == "ios" { - go m.setupIosConfigure(opts.ConfigPath, int(userID), token, deviceID) + go m.setupIosConfigure(opts.ConfigPath) } go m.initSessionModel(context.Background(), opts) return m, nil @@ -193,9 +171,12 @@ func NewSessionModel(mdb minisql.DB, opts *SessionModelOpts) (*SessionModel, err // setupIosConfigure sets up the iOS configuration for the session model. // It continuously checks if the global configuration is available and retries every second if not. -func (m *SessionModel) setupIosConfigure(configPath string, userId int, token string, deviceId string) { +func (m *SessionModel) setupIosConfigure(configPath string) { + deviceId, _ := m.GetDeviceID() + userId, _ := m.GetUserID() + token, _ := m.GetToken() go func() { - cf := ios.NewConfigurer(configPath, userId, token, deviceId, "") + cf := ios.NewConfigurer(configPath, int(userId), token, deviceId, "") ticker := time.NewTicker(1 * time.Second) defer ticker.Stop() for range ticker.C { diff --git a/internalsdk/user.go b/internalsdk/user.go index a1f486220..54f4bfca5 100644 --- a/internalsdk/user.go +++ b/internalsdk/user.go @@ -53,8 +53,25 @@ func (uc *userConfig) GetInternalHeaders() map[string]string { return h } -func newUserConfig(session PanickingSession) *userConfig { - return &userConfig{session: session} +func newUserConfig(session ClientSession, platform string) func() common.UserConfig { + return func() common.UserConfig { + internalHeaders := map[string]string{ + common.PlatformHeader: platform, + common.AppVersionHeader: common.ApplicationVersion, + } + deviceID, _ := session.GetDeviceID() + userID, _ := session.GetUserID() + token, _ := session.GetToken() + lang, _ := session.Locale() + return common.NewUserConfig( + common.DefaultAppName, + deviceID, + userID, + token, + internalHeaders, + lang, + ) + } } func createUser(ctx context.Context, proClient pro.ProClient, session ClientSession) error { diff --git a/internalsdk/utils.go b/internalsdk/utils.go index 20cb586dc..d48d0f652 100644 --- a/internalsdk/utils.go +++ b/internalsdk/utils.go @@ -32,27 +32,10 @@ func createProClient(session ClientSession, platform string) pro.ProClient { dialTimeout = 20 * time.Second } webclientOpts := &webclient.Opts{ - Timeout: dialTimeout, - UserConfig: func() common.UserConfig { - internalHeaders := map[string]string{ - common.PlatformHeader: platform, - common.AppVersionHeader: common.ApplicationVersion, - } - deviceID, _ := session.GetDeviceID() - userID, _ := session.GetUserID() - token, _ := session.GetToken() - lang, _ := session.Locale() - return common.NewUserConfig( - common.DefaultAppName, - deviceID, - userID, - token, - internalHeaders, - lang, - ) - }, + Timeout: dialTimeout, + UserConfig: newUserConfig(session, platform), } - return pro.NewClient(fmt.Sprintf("https://%s", common.ProAPIHost), webclientOpts) + return pro.NewClient(fmt.Sprintf("https://%s", common.ProAPIBaseURL), webclientOpts) } func BytesToFloat64LittleEndian(b []byte) (float64, error) {