diff --git a/request.go b/request.go index d0616fe..4d49c23 100644 --- a/request.go +++ b/request.go @@ -1,5 +1,11 @@ package retailcrm +import ( + "crypto/hmac" + "crypto/sha256" + "encoding/hex" +) + // CustomerRequest type. type CustomerRequest struct { By string `url:"by,omitempty"` @@ -215,3 +221,35 @@ type CustomDictionariesRequest struct { Limit int `url:"limit,omitempty"` Page int `url:"page,omitempty"` } + +// ConnectRequest contains information about the system connection that is requested to be created. +type ConnectRequest struct { + // Token is used to verify the request. Do not use directly; use Verify instead. + Token string `json:"token"` + // APIKey that was generated for the module. + APIKey string `json:"apiKey"` + // URL of the system. Do not use directly; use SystemURL instead. + URL string `json:"systemUrl"` +} + +// SystemURL returns system URL from the connection request without trailing slash. +func (r ConnectRequest) SystemURL() string { + if r.URL == "" { + return "" + } + + if r.URL[len(r.URL)-1:] == "/" { + return r.URL[:len(r.URL)-1] + } + + return r.URL +} + +// Verify returns true if connection request is legitimate. Application secret should be provided to this method. +func (r ConnectRequest) Verify(secret string) bool { + mac := hmac.New(sha256.New, []byte(secret)) + if _, err := mac.Write([]byte(r.APIKey)); err != nil { + panic(err) + } + return hmac.Equal([]byte(r.Token), []byte(hex.EncodeToString(mac.Sum(nil)))) +} diff --git a/request_test.go b/request_test.go new file mode 100644 index 0000000..669e342 --- /dev/null +++ b/request_test.go @@ -0,0 +1,43 @@ +package retailcrm + +import ( + "crypto/hmac" + "crypto/sha256" + "encoding/hex" + "testing" + + "github.com/stretchr/testify/suite" +) + +type ConnectRequestTest struct { + suite.Suite +} + +func TestConnectRequest(t *testing.T) { + suite.Run(t, new(ConnectRequestTest)) +} + +func (t *ConnectRequestTest) Test_SystemURL() { + t.Assert().Equal("", ConnectRequest{}.SystemURL()) + t.Assert().Equal("https://test.retailcrm.pro", ConnectRequest{URL: "https://test.retailcrm.pro"}.SystemURL()) + t.Assert().Equal("https://test.retailcrm.pro", ConnectRequest{URL: "https://test.retailcrm.pro/"}.SystemURL()) +} + +func (t *ConnectRequestTest) Test_Verify() { + t.Assert().True(ConnectRequest{ + APIKey: "key", + Token: createConnectToken("key", "secret"), + }.Verify("secret")) + t.Assert().False(ConnectRequest{ + APIKey: "key", + Token: createConnectToken("key", "secret2"), + }.Verify("secret")) +} + +func createConnectToken(apiKey, secret string) string { + mac := hmac.New(sha256.New, []byte(secret)) + if _, err := mac.Write([]byte(apiKey)); err != nil { + panic(err) + } + return hex.EncodeToString(mac.Sum(nil)) +} diff --git a/response.go b/response.go index 5b753c9..a38d276 100644 --- a/response.go +++ b/response.go @@ -495,3 +495,30 @@ type UnitsResponse struct { Success bool `json:"success"` Units *[]Unit `json:"units,omitempty"` } + +// ErrorResponse should be returned to the one-step connection request in case of failure. +type ErrorResponse struct { + SuccessfulResponse + ErrorMessage string `json:"errorMsg"` +} + +// ConnectResponse should be returned to the one-step connection request in case of successful connection. +type ConnectResponse struct { + SuccessfulResponse + AccountURL string `json:"accountUrl"` +} + +// ConnectionConfigResponse contains connection configuration for one-step connection. +type ConnectionConfigResponse struct { + SuccessfulResponse + Scopes []string `json:"scopes"` + RegisterURL string `json:"registerUrl"` +} + +// NewConnectResponse returns ConnectResponse with the provided account URL. +func NewConnectResponse(accountURL string) ConnectResponse { + return ConnectResponse{ + SuccessfulResponse: SuccessfulResponse{Success: true}, + AccountURL: accountURL, + } +} diff --git a/response_test.go b/response_test.go new file mode 100644 index 0000000..42bd7a2 --- /dev/null +++ b/response_test.go @@ -0,0 +1,16 @@ +package retailcrm + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNewConnectResponse(t *testing.T) { + assert.Equal(t, ConnectResponse{ + SuccessfulResponse: SuccessfulResponse{ + Success: true, + }, + AccountURL: "https://example.com", + }, NewConnectResponse("https://example.com")) +}