diff --git a/README.md b/README.md index 2e2dd65..01d4788 100644 --- a/README.md +++ b/README.md @@ -1,39 +1,25 @@ -# GO +# Go -You can find the UPS Widget Token Generation for GO Lang here. +You can find the UPS Widget Token Generation for Go programing language here. ## Using the Token Generation Library need to utilize this package. -- First, download TokenGeneration.go from this repository. Add the file to your project. -- Next, install the package by using `go install`. -- Finally, import the package by using - ```GO - import { - go/TokenGeneration - } - ``` +- First, download token_generation.go from this repository. Add the file to your project under `your-project/ups/token_generation.go` +- Import the `ups` package and start using. -Once the package is added and installed, you will be able to use the `UPSSDK` class and call the `generateToken` method. +Once the package is added you will be able to use `ups.GenerateToken()` to request an oauth token from UPS. -# TokenGeneration Class -## Definition - -Provides a package for generating an OAuth token to use with UPS Widgets. -```GO -package TokenGeneration -``` ## Parameters | Definition | Description | |------------|-------------| -| clientId | Your Client Id found in the UPS Developer portal | +| clientID | Your Client Id found in the UPS Developer portal | | clientSecret | Your Client Secret found in the UPS Developer portal | -| headers | A `map` of `[string]string` | +| additionalHeaders | A `map` of `[string]string` | | postData | A `map` of `[string]string` | -| queryParams | A `map` of `[string]string` | -## Methods +## Funcs | Definition | Description | |------------|-------------| @@ -49,5 +35,5 @@ headers["HEADER_KEY"] = "HEADER_VALUE" postData := make(map[string]string) postData["PROPERTY_NAME"] = "PROPERTY_VALUE" -response, err := TokenGeneration.GenerateToken(clientId, clientSecret, headers, postData, nil) +response, err := TokenGeneration.GenerateToken(context.Background(), clientId, clientSecret, headers, postData) ``` diff --git a/TokenGeneration.go b/TokenGeneration.go deleted file mode 100644 index dfdfa86..0000000 --- a/TokenGeneration.go +++ /dev/null @@ -1,115 +0,0 @@ -package TokenGeneration - -import ( - b64 "encoding/base64" - "encoding/json" - "errors" - "fmt" - "io" - "net/http" - "net/url" - "strings" -) - -func GenerateToken(clientId string, clientSecret string, headers map[string]string, postData map[string]string, querryParams map[string]string) (string, error) { - - if clientId == "" || clientSecret == "" { - return "", errors.New(errMsg("DTG002")) - } - data := clientId + ":" + clientSecret - - posturl := "https://onlinetools.ups.com/security/v1/oauth/token" - - body := url.Values{} - body.Add("grant_type", "client_credentials") - body.Add("scope", "public") - - for keys := range postData { - body.Add(keys, postData[keys]) - fmt.Println(body) - } - encodedData := body.Encode() - - r, err := http.NewRequest(http.MethodPost, posturl, strings.NewReader(encodedData)) - r.Header.Set("Content-Type", "application/x-www-form-urlencoded") - r.Header.Set("Authorization", "Basic "+b64.StdEncoding.EncodeToString([]byte(data))) - - for keys := range headers { - r.Header.Set(keys, headers[keys]) - fmt.Println(r.Header) - } - - if err != nil { - panic(err) - } - - client := &http.Client{} - res, err := client.Do(r) - if err != nil { - panic(err) - } - - defer res.Body.Close() - - response, err := io.ReadAll(res.Body) - if err != nil { - fmt.Println("Error reading HTTP response body:", err) - return "", err - } - - if !(res.StatusCode >= 200 && res.StatusCode <= 299) { - var errors1 Error - if err := json.Unmarshal(response, &errors1); err != nil { - panic("Can not unmarshaled JSON") - } - if strings.Contains(string(response), "ClientId is Invalid") { - return "", errors.New(errMsg("DTG001")) - } else if strings.Contains(string(response), "grant") { - return "", errors.New(errMsg("DTG003")) - } else if strings.Contains(string(response), "redirect") { - return "", errors.New(errMsg("DTG004")) - } else if strings.Contains(string(response), "Authorization Code") { - return "", errors.New(errMsg("DTG005")) - } else if strings.Contains(string(response), "Authorization Header") { - return "", errors.New(errMsg("DTG006")) - } else if strings.Contains(string(response), "quota") { - return "", errors.New(errMsg("DTG007")) - } else if strings.Contains(string(response), "Client credentials are invalid") { - return "", errors.New(errMsg("DTG008")) - } else { - return "", errors.New("{\"response\":{\"errors\":[{\"code\":\"DTG009\",\"message\":\"" + string(response) + "\"}]}}") - } - //panic(res.Status) - } - - var result Post - if err := json.Unmarshal(response, &result); err != nil { - fmt.Println("Can not unmarshaled JSON") - } - - return result.AccessToken, nil -} - -func errMsg(errCode string) string { - - msg := "{\"response\":{\"errors\":[{\"code\":\"" + errCode + "\",\"message\":\"Token generation has encountered an error. Please contact your UPS Relationship Manager.\"}]}}" - return msg -} - -type Post struct { - TokenType string `json:"token_type"` - IssuedAt string `json:"issued_at"` - ClientID string `json:"client_id"` - AccessToken string `json:"access_token"` - ExpiresIn string `json:"expires_in"` - Status string `json:"status"` -} - -type Error struct { - Response struct { - Errors []struct { - Code string `json:"code"` - Message string `json:"message"` - } `json:"errors"` - } `json:"response"` -} diff --git a/token_generation.go b/token_generation.go new file mode 100644 index 0000000..a57a07a --- /dev/null +++ b/token_generation.go @@ -0,0 +1,137 @@ +package ups +// If ups decides to publish this as a go gettable package. +// package github.com/UPS-API/Widgets-SDK + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "strings" + "time" +) + +// oauthHTTPClient is an http oauthHTTPClient used for oauth requests. +var oauthHTTPClient = &http.Client{ + Timeout: time.Second * 30, + Transport: &http.Transport{ + // Set default timeouts + TLSHandshakeTimeout: time.Second * 10, + IdleConnTimeout: time.Second * 30, + ResponseHeaderTimeout: time.Second * 30, + ExpectContinueTimeout: time.Second * 30, + }, +} + +// tokenEndpoint is the endpoint used to generate UPS oauth tokens. +const tokenEndpoint = "https://onlinetools.ups.com/security/v1/oauth/token" + +// GenerateToken requests an Oauth Token from UPS. +// Returns an oauth token or an error. +// additionalHeaders and postData are optional additional data to send in the request. +func GenerateToken( + ctx context.Context, + clientID string, + clientSecret string, + additionalHeaders map[string]string, + postData map[string]string, +) (string, error) { + if clientID == "" || clientSecret == "" { + return "", newTokenGenerationError("DTG002") + } + + body := url.Values{} + body.Set("grant_type", "client_credentials") + body.Set("scope", "public") + + for keys := range postData { + body.Add(keys, postData[keys]) + } + encodedData := body.Encode() + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, tokenEndpoint, strings.NewReader(encodedData)) + if err != nil { + return "", fmt.Errorf("error creating request: %w", err) + } + + req.SetBasicAuth(clientID, clientSecret) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + for keys := range additionalHeaders { + req.Header.Set(keys, additionalHeaders[keys]) + } + + res, err := oauthHTTPClient.Do(req) + if err != nil { + return "", fmt.Errorf("error executing request: %w", err) + } + defer res.Body.Close() + + response, err := io.ReadAll(res.Body) + if err != nil { + return "", fmt.Errorf("error reading http response body: %w", err) + } + + if !(res.StatusCode >= 200 && res.StatusCode <= 299) { + var errors1 oauthTokenError + if err := json.Unmarshal(response, &errors1); err != nil { + return "", fmt.Errorf("error decoding response: %w", err) + } + + respStr := string(response) + + if strings.Contains(respStr, "ClientId is Invalid") { + return "", newTokenGenerationError("DTG001") + } else if strings.Contains(respStr, "grant") { + return "", newTokenGenerationError("DTG003") + } else if strings.Contains(respStr, "redirect") { + return "", newTokenGenerationError("DTG004") + } else if strings.Contains(respStr, "Authorization Code") { + return "", newTokenGenerationError("DTG005") + } else if strings.Contains(respStr, "Authorization Header") { + return "", newTokenGenerationError("DTG006") + } else if strings.Contains(respStr, "quota") { + return "", newTokenGenerationError("DTG007") + } else if strings.Contains(respStr, "Client credentials are invalid") { + return "", newTokenGenerationError("DTG008") + } + + return "", fmt.Errorf("%s: %s", respStr, "DTG009") + } + + var result oauthTokenResponse + + if err := json.Unmarshal(response, &result); err != nil { + return "", fmt.Errorf("error unmarshaling token response: %w", err) + } + + return result.AccessToken, nil +} + +// newTokenGenerationError creates a token generation error mesasge. +// errCode is a UPS error code to append to the end. +func newTokenGenerationError(errCode string) error { + return fmt.Errorf("token generation has encountered an error. Please contact your UPS Relationship Manager: %s", errCode) +} + +// oauthTokenResponse is the token response from UPS. +type oauthTokenResponse struct { + TokenType string `json:"token_type"` + IssuedAt string `json:"issued_at"` + ClientID string `json:"client_id"` + AccessToken string `json:"access_token"` + ExpiresIn string `json:"expires_in"` + Status string `json:"status"` +} + +// oauthTokenError is an error response during token generation from UPS. +type oauthTokenError struct { + Response struct { + Errors []struct { + Code string `json:"code"` + Message string `json:"message"` + } `json:"errors"` + } `json:"response"` +}