...

Source file src/edge-infra.dev/pkg/edge/api/client/client.go

Documentation: edge-infra.dev/pkg/edge/api/client

     1  package client
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"net/http"
     8  	"net/http/cookiejar"
     9  	"net/url"
    10  	"time"
    11  
    12  	"github.com/shurcooL/graphql"
    13  	"golang.org/x/net/publicsuffix"
    14  
    15  	"edge-infra.dev/pkg/edge/api/graph/model"
    16  )
    17  
    18  const (
    19  	// defaultEndpoint the default Edge API endpoint to use for requests.
    20  	defaultEndpoint = "https://dev1.edge-preprod.dev/api"
    21  	// defaultVersion the default API version to use for requests.
    22  	defaultVersion = "v2"
    23  	// defaultUseragent the default user agent for requests.
    24  	defaultUseragent = "edge-go-client" + "/" + defaultVersion
    25  	// defaultTimeout the default timeout for the http client.
    26  	defaultTimeout = 20 * time.Second
    27  	// userAgentHeader the user-agent header key.
    28  	userAgentHeader = "user-agent"
    29  	// edgeVersionHeader the edge-version header key.
    30  	edgeVersionHeader = "edge-version"
    31  	// authorizationHeader the authorization header key.
    32  	authorizationHeader = "authorization"
    33  	// bearerToken the bearer token prefix for authorization header.
    34  	bearerToken = "bearer"
    35  	// totpToken the totp token prefix for authorization header.
    36  	totpToken = "totp"
    37  )
    38  
    39  type EdgeClient struct {
    40  	// Client is the graphql client for queries and mutations.
    41  	Client *graphql.Client
    42  	// HTTPClient is the http client used for the requests.
    43  	HTTPClient *http.Client
    44  	// jar is a cookiejar that holds http cookies and passes them with every request.
    45  	jar *cookiejar.Jar
    46  	// timeout is the request timeout duration.
    47  	timeout time.Duration
    48  	// userAgent is the user agent for the http request.
    49  	userAgent string
    50  	// version is the corresponding edge version.
    51  	version string
    52  	// sessionExpires is the date and time the authentication session expires
    53  	// it is used to keep track of when to refresh the session.
    54  	sessionExpires time.Time
    55  	// BaseURL is the url to make the request to.
    56  	BaseURL *url.URL
    57  	// loginCredentials contains authetication credentials (username, password and organization).
    58  	loginCredentials *LoginRequest
    59  	// totpToken is the totp authentication token for the http request.
    60  	totpToken string
    61  	// bearerToken is the authentication bearer token for the http request.
    62  	bearerToken string
    63  	// headers is a list of http headers to append to the request.
    64  	headers map[string]string
    65  }
    66  
    67  // New returns a new client to interact with the Edge API.
    68  func New(opts ...Option) (*EdgeClient, error) {
    69  	cl := &EdgeClient{
    70  		headers: make(map[string]string),
    71  	}
    72  	for _, o := range opts {
    73  		o(cl)
    74  	}
    75  	return cl.init()
    76  }
    77  
    78  // init initializes the Edge Client.
    79  func (c *EdgeClient) init() (*EdgeClient, error) {
    80  	if c.jar == nil {
    81  		jar, err := cookiejar.New(&cookiejar.Options{
    82  			PublicSuffixList: publicsuffix.List,
    83  		})
    84  		if err != nil {
    85  			return c, err
    86  		}
    87  		c.jar = jar
    88  	}
    89  	if c.timeout == 0 {
    90  		c.timeout = defaultTimeout
    91  	}
    92  	if c.userAgent == "" {
    93  		c.headers[userAgentHeader] = defaultUseragent
    94  	}
    95  	if c.version == "" {
    96  		c.headers[edgeVersionHeader] = c.version
    97  	}
    98  	if c.HTTPClient == nil {
    99  		c.HTTPClient = &http.Client{
   100  			Jar:     c.jar,
   101  			Timeout: c.timeout,
   102  			Transport: &Transport{
   103  				T:       http.DefaultTransport,
   104  				Headers: c.headers,
   105  			},
   106  		}
   107  	}
   108  	if c.BaseURL == nil {
   109  		baseURL, err := url.Parse(fmt.Sprintf("%s/%s", defaultEndpoint, defaultVersion))
   110  		if err != nil {
   111  			return c, err
   112  		}
   113  		c.BaseURL = baseURL
   114  	}
   115  	if c.Client == nil {
   116  		c.Client = graphql.NewClient(c.BaseURL.String(), c.HTTPClient)
   117  	}
   118  	if c.multipleAuthMethods() {
   119  		return c, errors.New("using multiple authentication methods is not supported")
   120  	}
   121  	if c.hasCredentials() {
   122  		_, err := c.Login(context.Background(), c.loginCredentials)
   123  		if err != nil {
   124  			return c, err
   125  		}
   126  		c.sessionExpires = time.Now()
   127  	}
   128  	return c, nil
   129  }
   130  
   131  // Query calls bff for graphql queries and populate the mutation interface with the response.
   132  func (c EdgeClient) Query(ctx context.Context, query interface{}, variables map[string]interface{}) error {
   133  	if err := c.handleRefresh(ctx); err != nil {
   134  		return err
   135  	}
   136  	return c.Client.Query(ctx, query, variables)
   137  }
   138  
   139  // Mutate calls bff for graphql mutations and populate the mutation interface with the response.
   140  func (c EdgeClient) Mutate(ctx context.Context, mutation interface{}, variables map[string]interface{}) error {
   141  	if err := c.handleRefresh(ctx); err != nil {
   142  		return err
   143  	}
   144  	return c.Client.Mutate(ctx, mutation, variables)
   145  }
   146  
   147  // handleRefresh refreshes the Edge API session.
   148  func (c EdgeClient) handleRefresh(ctx context.Context) error {
   149  	if c.hasCredentials() && !c.sessionExpires.IsZero() { //nolint
   150  		diff := time.Since(c.sessionExpires)
   151  		expires := diff.Minutes()
   152  		if expires >= 13 && expires < 15 {
   153  			_, err := c.SessionRefresh(ctx, model.AuthProviderBsl)
   154  			if err != nil {
   155  				// if an error occurs while refreshing session, then try to login instead
   156  				_, err := c.Login(ctx, c.loginCredentials)
   157  				if err != nil {
   158  					return err
   159  				}
   160  			}
   161  			c.sessionExpires = time.Now()
   162  		}
   163  	}
   164  	return nil
   165  }
   166  
   167  // hasCredentials checks the authentication method being used is username and password.
   168  func (c EdgeClient) hasCredentials() bool {
   169  	return c.loginCredentials != nil && c.loginCredentials.Username != "" && c.loginCredentials.Password != "" && c.loginCredentials.Organization != ""
   170  }
   171  
   172  // multipleAuthMethods checks if multiple authentication methods are being used.
   173  func (c EdgeClient) multipleAuthMethods() bool {
   174  	switch {
   175  	case c.bearerToken != "" && c.totpToken != "" && c.hasCredentials():
   176  		fallthrough
   177  	case c.bearerToken != "" && c.hasCredentials():
   178  		fallthrough
   179  	case c.totpToken != "" && c.hasCredentials():
   180  		fallthrough
   181  	case c.bearerToken != "" && c.totpToken != "":
   182  		return true
   183  	default:
   184  		return false
   185  	}
   186  }
   187  

View as plain text