Files
cloud-services/pkg/auth/user_consent_token.go

141 lines
3.8 KiB
Go

package auth
import (
"encoding/base64"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"sync"
"time"
"github.com/fiskerinc/cloud-services/pkg/httpclient"
"github.com/fiskerinc/cloud-services/pkg/jwt"
"github.com/fiskerinc/cloud-services/pkg/logger"
"github.com/fiskerinc/cloud-services/pkg/utils/envtool"
"github.com/pkg/errors"
)
var cognitoUserConsentClientID = envtool.GetEnv("COGNITO_USER_CONSENT_CLIENT_ID", "REPLACE_ME")
var cognitoUserConsentClientSecret = envtool.GetEnv("COGNITO_USER_CONSENT_CLIENT_SECRET", "REPLACE_ME")
var cognitoUserConsentAuthURL = envtool.GetEnv("COGNITO_USER_CONSENT_AUTH_URL", "REPLACE_ME")
var CognitoUserConsentJWT CognitoJWT
type CognitoJWT struct {
token string
expiration *time.Time
mu sync.RWMutex
}
func (c *CognitoJWT) GetUserConsentToken() (string, error) {
c.mu.Lock()
defer c.mu.Unlock()
// check for an unexpired token. if it is there, return it
if c.token != "" && time.Now().Before(*c.expiration) {
return c.token, nil
}
// make a request to Cognito to get the token
authResponse, err := RequestUserConsentToken()
if err != nil {
return "", err
}
// cache the token and expire time
c.token = authResponse.AccessToken.JWTToken
c.expiration = &authResponse.ExpireTime
return c.token, nil
}
func RequestUserConsentToken() (AuthResponse, error) {
var resp AuthTokens
var tokens AuthResponse
tokenReq, err := getUserConsentTokenRequest()
if err != nil {
return tokens, err
}
tokenRes, err := httpclient.Do(tokenReq)
if err != nil {
return tokens, err
}
defer tokenRes.Body.Close()
err = json.NewDecoder(tokenRes.Body).Decode(&resp)
if err != nil {
logger.Error().Err(err).Send()
}
if len(resp.Error) > 0 {
return tokens, errors.New(resp.Error)
}
return getAuthResponse(&resp), nil
}
// getUserConsentTokenRequest returns http request to exchange code for a user consent token
func getUserConsentTokenRequest() (*http.Request, error) {
basicAuth := base64.StdEncoding.EncodeToString([]byte(strings.Join([]string{cognitoUserConsentClientID, ":", cognitoUserConsentClientSecret}, "")))
body := url.Values{
"client_id": {cognitoUserConsentClientID},
"grant_type": {"client_credentials"},
}
r, err := http.NewRequest(http.MethodPost, getUserConsentTokenURL(), strings.NewReader(body.Encode()))
if err != nil {
return nil, errors.WithStack(err)
}
r.Header.Add("Authorization", fmt.Sprintf("Basic %s", basicAuth))
r.Header.Add("Content-type", "application/x-www-form-urlencoded")
return r, nil
}
// GetAuthResponse converts AuthTokens response from Cognito to include decoded payload
func getAuthResponse(data *AuthTokens) AuthResponse {
var result AuthResponse
result.AccessToken.JWTToken = data.AcessToken
result.IDToken.JWTToken = data.IDToken
result.RefreshToken.Token = data.RefreshToken
// calculate the expire time of the token, leaving off one minute of wiggle room
result.ExpireTime = time.Now().Add(time.Duration(data.ExpiresIn)*time.Second - time.Minute)
return result
}
func getUserConsentTokenURL() string {
return cognitoUserConsentAuthURL + "/oauth2/token"
}
// AuthTokens json response for auth tokens
type AuthTokens struct {
AcessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token,omitempty"`
IDToken string `json:"id_token,omitempty"`
TokenType string `json:"token_type"`
ExpiresIn int64 `json:"expires_in"`
Error string `json:"error,omitempty"`
}
// JWTResponse json response
type JWTResponse struct {
JWTToken string `json:"jwtToken"`
}
// AuthResponse json response
type AuthResponse struct {
AccessToken JWTResponse `json:"accessToken,omitempty"`
IDToken JWTResponse `json:"idToken,omitempty"`
RefreshToken jwt.AuthToken `json:"refreshToken,omitempty"`
ExpireTime time.Time `json:"expire_time"`
}