...

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

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

     1  package client
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"net/http"
     8  
     9  	"github.com/shurcooL/graphql"
    10  
    11  	"edge-infra.dev/pkg/edge/client"
    12  )
    13  
    14  const (
    15  	// Endpoint URL pointing to edge bff
    16  	Endpoint = "api-endpoint"
    17  	// User BSP username with edge credentials
    18  	Username = "username"
    19  	// Password BSP password for username
    20  	Password = "password"
    21  	// Organization BSP organization for username
    22  	Organization = "organization"
    23  	// Timeout is the timeout for bff client
    24  	Timeout = "api-timeout"
    25  )
    26  
    27  // Config represents config for BFF users to connect to BFF
    28  type Config struct {
    29  	Endpoint     string
    30  	Username     string
    31  	Password     string
    32  	Organization string
    33  	TotpToken    string
    34  	BearerToken  string
    35  	Version      string
    36  }
    37  
    38  // Valid returns true when config is valid
    39  func (c Config) Valid() bool {
    40  	return c.Endpoint != "" && (c.hasValidUserPassword() || c.hasValidTopToken() || c.hasValidBearerToken())
    41  }
    42  
    43  func (c Config) hasValidTopToken() bool {
    44  	return c.TotpToken != ""
    45  }
    46  
    47  func (c Config) hasValidBearerToken() bool {
    48  	return c.BearerToken != ""
    49  }
    50  
    51  func (c Config) hasValidUserPassword() bool {
    52  	return c.Username != "" && c.Password != ""
    53  }
    54  
    55  // Client represents the bff client responsible for graphql call
    56  type Client struct {
    57  	config *Config
    58  	*graphql.Client
    59  }
    60  
    61  // Query calls bff for graphql queries and populate the mutation interface with the response
    62  func (c Client) Query(ctx context.Context, query interface{}, variables map[string]interface{}) error {
    63  	return c.Client.Query(ctx, query, variables)
    64  }
    65  
    66  // Mutate calls bff for graphql mutations and populate the mutation interface with the response
    67  func (c Client) Mutate(ctx context.Context, mutation interface{}, variables map[string]interface{}) error {
    68  	return c.Client.Mutate(ctx, mutation, variables)
    69  }
    70  
    71  // New returns a new authenticated graphql client for bff
    72  func New(config *Config) (*Client, error) {
    73  	if config == nil {
    74  		return nil, errors.New("config is required")
    75  	}
    76  
    77  	if !config.Valid() {
    78  		return nil, errors.New("invalid config")
    79  	}
    80  
    81  	var client *http.Client
    82  	var err error
    83  	if config.hasValidTopToken() {
    84  		client = getTotpClient(config)
    85  	} else if config.hasValidBearerToken() {
    86  		client = getBearerClient(config)
    87  	} else if client, err = getUserPasswordClient(config); err != nil {
    88  		return nil, err
    89  	}
    90  
    91  	return &Client{
    92  		config: config,
    93  		Client: graphql.NewClient(config.Endpoint, client),
    94  	}, nil
    95  }
    96  
    97  func getUserPasswordClient(config *Config) (*http.Client, error) {
    98  	token, err := getBearerToken(config.Endpoint, config.Username, config.Password, config.Organization)
    99  	if err != nil {
   100  		return nil, err
   101  	}
   102  	config.BearerToken = token
   103  	return getBearerClient(config), nil
   104  }
   105  
   106  func getBearerClient(config *Config) *http.Client {
   107  	headers := make(map[string]string)
   108  	headers[client.Authorization] = fmt.Sprintf("%s %s", client.BearerToken, config.BearerToken)
   109  	headers[client.EdgeVersion] = config.Version
   110  	return client.NewTokenClient(headers)
   111  }
   112  
   113  func getTotpClient(config *Config) *http.Client {
   114  	headers := make(map[string]string)
   115  	headers[client.Authorization] = fmt.Sprintf("%s %s", client.TotpToken, config.TotpToken)
   116  	headers[client.EdgeVersion] = config.Version
   117  	return client.NewTokenClient(headers)
   118  }
   119  
   120  // getBearerToken retrieves the bearer token from bff
   121  func getBearerToken(url, username, password, organization string) (string, error) {
   122  	client := graphql.NewClient(url, nil)
   123  
   124  	var mutation struct {
   125  		Login struct {
   126  			Token graphql.String
   127  		} `graphql:"login(username: $username, password: $password, organization: $organization)"`
   128  	}
   129  
   130  	variables := map[string]interface{}{
   131  		"username":     graphql.String(username),
   132  		"password":     graphql.String(password),
   133  		"organization": graphql.String(organization),
   134  	}
   135  
   136  	err := client.Mutate(context.Background(), &mutation, variables)
   137  	return string(mutation.Login.Token), err
   138  }
   139  

View as plain text