Repository: Shopify/themekit
Pull request #933 (2021-05-04 20:31:21)

Add redirect to ThemeKit Access when password prefix matches pattern
**Description**
Themekit needs to make requests either to ThemeKit Access or to Shopify depending on the type of password token that user has. This should have no effect for existing Themekit users.

The solution checks whether user token starts with an identifiable prefix `shptka_` and redirect all requests to the Theme Kit Access server.

**Additional context**
The issue is related to the new public ThemeKit Access app development.

### Warn Checklist
- [x] This changes the interface and requires a Major/Minor version change.
- [x] I have :tophat:'d these changes by using the commands I changed by hand.
- [ ] I have added a dependancy to the project.
- [ ] I have considered any potential impact on [node-themekit](https://github.com/Shopify/node-themekit)



Patch:
diff --git a/src/httpify/client.go b/src/httpify/client.go
index a4cef57..d024d4f 100644
--- a/src/httpify/client.go
+++ b/src/httpify/client.go
@@ -15,6 +15,8 @@ import (
 	"github.com/Shopify/themekit/src/release"
 )
 
+const themeKitPasswordPrefix = "shptka_"
+
 var (
 	// ErrConnectionIssue is an error that is thrown when a very specific error is
 	// returned from our http request that usually implies bad connections.
@@ -27,6 +29,7 @@ var (
 	httpClient = &http.Client{
 		Timeout: 30 * time.Second,
 	}
+	themeKitAccessURL = "https://theme-kit-access.shopifyapps.com/cli"
 )
 
 type proxyHandler func(*http.Request) (*url.URL, error)
@@ -103,7 +106,15 @@ func (client *HTTPClient) Delete(path string, headers map[string]string) (*http.
 
 // do will issue an authenticated json request to shopify.
 func (client *HTTPClient) do(method, path string, body interface{}, headers map[string]string) (*http.Response, error) {
-	req, err := http.NewRequest(method, client.baseURL.String()+path, nil)
+	appBaseURL := client.baseURL.String()
+
+	// redirect to Theme Kit Access
+	if strings.HasPrefix(client.password, themeKitPasswordPrefix) {
+		appBaseURL = themeKitAccessURL
+	}
+
+	req, err := http.NewRequest(method, appBaseURL+path, nil)
+
 	if err != nil {
 		return nil, err
 	}
@@ -112,6 +123,9 @@ func (client *HTTPClient) do(method, path string, body interface{}, headers map[
 	req.Header.Add("Content-Type", "application/json")
 	req.Header.Add("Accept", "application/json")
 	req.Header.Add("User-Agent", fmt.Sprintf("go/themekit (%s; %s; %s)", runtime.GOOS, runtime.GOARCH, release.ThemeKitVersion.String()))
+	if strings.HasPrefix(client.password, themeKitPasswordPrefix) {
+		req.Header.Add("X-Shopify-Shop", client.domain)
+	}
 	for label, value := range headers {
 		req.Header.Add(label, value)
 	}


Test patch:
diff --git a/src/httpify/client_test.go b/src/httpify/client_test.go
index d9a9cff..a2cd891 100644
--- a/src/httpify/client_test.go
+++ b/src/httpify/client_test.go
@@ -141,6 +141,50 @@ func TestClient_do(t *testing.T) {
 	_, err = client.do("POST", "/assets.json", body, nil)
 	assert.Contains(t, err.Error(), "request failed after 1 retries", server.URL)
 	server.Close()
+
+	// Client should query Theme Kit Access server instead of Shopify when password starts with a prefix "shptka_"
+	shopifyServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
+
+	themeKitAccessServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		assert.Equal(t, r.Header.Get("X-Shopify-Shop"), client.domain)
+	}))
+
+	client, err = NewClient(Params{
+		Domain:   shopifyServer.URL,
+		Password: "shptka_00000000000000000000000000000000",
+	})
+	themeKitAccessURL = themeKitAccessServer.URL
+
+	assert.NotNil(t, client)
+	assert.Nil(t, err)
+
+	resp, err = client.Post("/assets.json", body, map[string]string{"X-Custom-Header": "Checksum"})
+	assert.Nil(t, err)
+	assert.NotNil(t, resp)
+
+	server.Close()
+
+	// Client should query Shopify instead of Theme Kit Access server when password has no specified prefix
+	shopifyServer = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		assert.Empty(t, r.Header.Get("X-Shopify-Shop"))
+	}))
+
+	themeKitAccessServer = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
+
+	client, err = NewClient(Params{
+		Domain:   shopifyServer.URL,
+		Password: "secret_password",
+	})
+	themeKitAccessURL = themeKitAccessServer.URL
+
+	assert.NotNil(t, client)
+	assert.Nil(t, err)
+
+	resp, err = client.Post("/assets.json", body, map[string]string{"X-Custom-Header": "Checksum"})
+	assert.Nil(t, err)
+	assert.NotNil(t, resp)
+
+	server.Close()
 }
 
 func TestGenerateHTTPAdapter(t *testing.T) {