diff --git "a/.changes/unreleased/\360\237\216\211 New Product Feature-20241107-081213.yaml" "b/.changes/unreleased/\360\237\216\211 New Product Feature-20241107-081213.yaml" new file mode 100644 index 0000000..b22cc33 --- /dev/null +++ "b/.changes/unreleased/\360\237\216\211 New Product Feature-20241107-081213.yaml" @@ -0,0 +1,3 @@ +kind: "\U0001F389 New Product Feature" +body: Added simple access token caching feature +time: 2024-11-07T08:12:13.07799-08:00 diff --git a/.gitignore b/.gitignore index 47a7dae..8de14e8 100644 --- a/.gitignore +++ b/.gitignore @@ -51,3 +51,5 @@ _artifacts mage_output_file.go dist/ /test_config.json +test.go +.DS_Store diff --git a/vault/vault.go b/vault/vault.go index 622b37f..096b92b 100644 --- a/vault/vault.go +++ b/vault/vault.go @@ -7,7 +7,9 @@ import ( "fmt" "io" "log" + "math" "net/http" + "os" "strings" "time" @@ -58,6 +60,12 @@ type Vault struct { Configuration } +//nolint:tagliatelle // the json is coming from an external API call +type TokenCache struct { + AccessToken string `json:"access_token"` + ExpiresIn int `json:"expires_in"` +} + // New returns a Vault or an error if the Configuration is invalid func New(config Configuration) (*Vault, error) { if config.Provider == auth.CLIENT { @@ -138,12 +146,48 @@ type accessTokenRequest struct { AwsHeaders string `json:"aws_headers,omitempty"` } +//nolint:tagliatelle // the json is coming from an external API call type accessTokenResponse struct { AccessToken string `json:"accessToken"` + ExpiresIn int `json:"expiresIn"` +} + +func (v Vault) setCacheAccessToken(value string, expiresIn int) bool { + percentage := 0.9 + cache := TokenCache{} + cache.AccessToken = value + cache.ExpiresIn = (int(time.Now().Unix()) + expiresIn) - int(math.Floor(float64(expiresIn)*percentage)) + + data, err := json.Marshal(cache) + if err != nil { + return false + } + os.Setenv("SS_AT", string(data)) + return true +} + +func (v Vault) getCacheAccessToken() (string, bool) { + data, ok := os.LookupEnv("SS_AT") + if !ok { + os.Setenv("SS_AT", "") + return "", ok + } + cache := TokenCache{} + if err := json.Unmarshal([]byte(data), &cache); err != nil { + return "", false + } + if time.Now().Unix() < int64(cache.ExpiresIn) { + return cache.AccessToken, true + } + return "", false } // getAccessToken returns access token fetched from DSV. func (v Vault) getAccessToken() (string, error) { + accessToken, found := v.getCacheAccessToken() + if found { + return accessToken, nil + } var rBody accessTokenRequest switch v.Provider { case auth.AWS: @@ -168,7 +212,6 @@ func (v Vault) getAccessToken() (string, error) { request, err := json.Marshal(&rBody) if err != nil { - return "", fmt.Errorf("marshalling token request body: %w", err) } url := v.urlFor("token", "") @@ -181,9 +224,12 @@ func (v Vault) getAccessToken() (string, error) { // TODO: cache the token until it expires. resp := &accessTokenResponse{} if err = json.Unmarshal(response, &resp); err != nil { - return "", fmt.Errorf("unmarshalling token response: %w", err) + return "", fmt.Errorf("unmarshaling token response: %w", err) + } + ok := v.setCacheAccessToken(resp.AccessToken, resp.ExpiresIn) + if !ok { + return "", fmt.Errorf("unable to cache access token") } - return resp.AccessToken, nil }