Repository: amp-labs/connectors
Pull request #252 (2024-04-24 12:35:29)

fix: Print token in script
* Prints token out using `fmt.Printf` which also prints the unexported `raw` field, helping us get a hold of the extra metadata that is sent along with the token.


Patch:
diff --git a/providers/catalog.go b/providers/catalog.go
index f7824d14..dc274ca5 100644
--- a/providers/catalog.go
+++ b/providers/catalog.go
@@ -14,6 +14,7 @@ const (
 	Pipedrive                           Provider = "pipedrive"
 	Copper                              Provider = "copper"
 	ZohoCRM                             Provider = "zohoCRM"
+	Mural                               Provider = "mural"
 	Klaviyo                             Provider = "klaviyo"
 	Sellsy                              Provider = "sellsy"
 	Attio                               Provider = "attio"
@@ -297,6 +298,33 @@ var catalog = CatalogType{ // nolint:gochecknoglobals
 		},
 	},
 
+	// Mural configuration
+	Mural: {
+		AuthType: Oauth2,
+		BaseURL:  "https://api.mural.co/api",
+		OauthOpts: OauthOpts{
+			AuthURL:                   "https://api.mural.co/oauth/authorize",
+			TokenURL:                  "https://api.mural.co/oauth/token",
+			ExplicitScopesRequired:    true,
+			ExplicitWorkspaceRequired: false,
+			TokenMetadataFields: TokenMetadataFields{
+				ScopesField: "scope",
+			},
+		},
+		Support: Support{
+			BulkWrite: BulkWriteSupport{
+				Insert: false,
+				Update: false,
+				Upsert: false,
+				Delete: false,
+			},
+			Proxy:     false,
+			Read:      false,
+			Subscribe: false,
+			Write:     false,
+		},
+	},
+
 	// Klaviyo configuration
 	Klaviyo: {
 		AuthType: Oauth2,
@@ -580,7 +608,7 @@ var catalog = CatalogType{ // nolint:gochecknoglobals
 	// DocuSign configuration
 	// TODO: we don't have a good way to get the server string yet. Need to make API call to /oauth/userinfo.
 	// Leaving this connector commented out until that is unblocked.
-	//DocuSign: {
+	// DocuSign: {
 	//	AuthType: Oauth2,
 	//	BaseURL:  "https://{{.server}}.docusign.net",
 	//	OauthOpts: OauthOpts{
diff --git a/salesforce/errors.go b/salesforce/errors.go
index 036966ed..1e3b471a 100644
--- a/salesforce/errors.go
+++ b/salesforce/errors.go
@@ -5,8 +5,10 @@ import (
 	"errors"
 	"fmt"
 	"net/http"
+	"net/url"
 
 	"github.com/amp-labs/connectors/common"
+	"golang.org/x/oauth2"
 )
 
 var (
@@ -72,6 +74,16 @@ func (c *Connector) interpretJSONError(res *http.Response, body []byte) error {
 }
 
 func (c *Connector) HandleError(err error) error {
+	var urlErr *url.Error
+	if errors.As(err, &urlErr) {
+		var oauthErr *oauth2.RetrieveError
+		if urlErr != nil && errors.As(urlErr.Err, &oauthErr) {
+			if oauthErr.ErrorCode == "invalid_grant" {
+				return errors.Join(common.ErrInvalidGrant, err)
+			}
+		}
+	}
+
 	switch {
 	case errors.Is(err, common.ErrAccessToken):
 		// Retryable, so just log and retry
diff --git a/scripts/oauth/token.go b/scripts/oauth/token.go
index 7120f071..06f9f519 100644
--- a/scripts/oauth/token.go
+++ b/scripts/oauth/token.go
@@ -170,12 +170,15 @@ func (a *OAuthApp) processCallback(writer http.ResponseWriter, request *http.Req
 		return
 	}
 
+	// Print the token which will also print raw metadata
+	fmt.Printf("%+v", tok)
+
 	writer.Header().Set("Content-Type", "application/json")
 	writer.Header().Set("Content-Length", strconv.FormatInt(int64(len(jsonBody)), 10))
 	writer.WriteHeader(http.StatusOK)
 
 	// All done
-	if _, err = writer.Write(jsonBody); err != nil { // nosemgrep
+	if _, err = writer.Write([]byte("Received a token, printed in the console")); err != nil { // nosemgrep
 		slog.Error("Error writing token", "error", err)
 
 		os.Exit(1)


Test patch:
diff --git a/providers/catalog_test.go b/providers/catalog_test.go
index afb16542..abd695dd 100644
--- a/providers/catalog_test.go
+++ b/providers/catalog_test.go
@@ -305,6 +305,37 @@ var testCases = []struct { // nolint
 		expectedErr: nil,
 	},
 
+	{
+		provider:    Mural,
+		description: "Valid Mural provider config with no substitutions",
+		expected: &ProviderInfo{
+			Support: Support{
+				Read:  false,
+				Write: false,
+				BulkWrite: BulkWriteSupport{
+					Insert: false,
+					Update: false,
+					Upsert: false,
+					Delete: false,
+				},
+				Subscribe: false,
+				Proxy:     false,
+			},
+			AuthType: Oauth2,
+			OauthOpts: OauthOpts{
+				AuthURL:                   "https://api.mural.co/oauth/authorize",
+				TokenURL:                  "https://api.mural.co/oauth/token",
+				ExplicitScopesRequired:    true,
+				ExplicitWorkspaceRequired: false,
+				TokenMetadataFields: TokenMetadataFields{
+					ScopesField: "scope",
+				},
+			},
+			BaseURL: "https://api.mural.co/api",
+		},
+		expectedErr: nil,
+	},
+
 	{
 		provider:    Klaviyo,
 		description: "Valid Klaviyo provider config with no substitutions",