github.com/qri-io/qri@v0.10.1-0.20220104210721-c771715036cb/remote/registry/regclient/profile.go (about)

     1  package regclient
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"net/http"
     8  	"strings"
     9  
    10  	crypto "github.com/libp2p/go-libp2p-core/crypto"
    11  	"github.com/qri-io/qri/remote/registry"
    12  )
    13  
    14  const proveKeyAPIEndpoint = "/registry/provekey"
    15  
    16  // GetProfile fills in missing fields in p with registry data
    17  func (c Client) GetProfile(p *registry.Profile) error {
    18  	pro, err := c.doJSONProfileReq("GET", p)
    19  	if err != nil {
    20  		return err
    21  	}
    22  	if pro != nil {
    23  		*p = *pro
    24  	}
    25  	return nil
    26  }
    27  
    28  // CreateProfile creates a user profile, associating a public key in the process
    29  func (c *Client) CreateProfile(p *registry.Profile, pk crypto.PrivKey) (*registry.Profile, error) {
    30  	if c == nil {
    31  		return nil, registry.ErrNoRegistry
    32  	}
    33  
    34  	// TODO (b5) - pass full profile
    35  	pro, err := registry.ProfileFromPrivateKey(p, pk)
    36  	if err != nil {
    37  		return nil, err
    38  	}
    39  
    40  	return c.doJSONProfileReq("POST", pro)
    41  }
    42  
    43  // ProveKeyForProfile proves to the registry that the user owns the profile and
    44  // is associating a new keypair
    45  func (c *Client) ProveKeyForProfile(p *registry.Profile) (map[string]string, error) {
    46  	if c == nil {
    47  		return nil, registry.ErrNoRegistry
    48  	}
    49  
    50  	// Ensure all required fields are set
    51  	if p.ProfileID == "" {
    52  		return nil, fmt.Errorf("ProveKeyForProfile: ProfileID required")
    53  	}
    54  	if p.Username == "" {
    55  		return nil, fmt.Errorf("ProveKeyForProfile: Username required")
    56  	}
    57  	if p.Email == "" {
    58  		return nil, fmt.Errorf("ProveKeyForProfile: Email required")
    59  	}
    60  	if p.Password == "" {
    61  		return nil, fmt.Errorf("ProveKeyForProfile: Password required")
    62  	}
    63  	if p.PublicKey == "" {
    64  		return nil, fmt.Errorf("ProveKeyForProfile: PublicKey required")
    65  	}
    66  	if p.Signature == "" {
    67  		return nil, fmt.Errorf("ProveKeyForProfile: Signature required")
    68  	}
    69  
    70  	// Send proof request to the registry
    71  	res := RegistryResponse{}
    72  	err := c.doJSONRegistryRequest("PUT", proveKeyAPIEndpoint, p, &res)
    73  	if err != nil {
    74  		return nil, err
    75  	}
    76  	return res.Data, nil
    77  }
    78  
    79  // UpdateProfile updates some information about the profile in the registry
    80  func (c *Client) UpdateProfile(p *registry.Profile, pk crypto.PrivKey) (*registry.Profile, error) {
    81  	if c == nil {
    82  		return nil, registry.ErrNoRegistry
    83  	}
    84  
    85  	// TODO (b5) - pass full profile
    86  	pro, err := registry.ProfileFromPrivateKey(p, pk)
    87  	if err != nil {
    88  		return nil, err
    89  	}
    90  
    91  	return c.doJSONProfileReq("PUT", pro)
    92  }
    93  
    94  // PutProfile adds a profile to the registry
    95  func (c *Client) PutProfile(p *registry.Profile, privKey crypto.PrivKey) (*registry.Profile, error) {
    96  	if c == nil {
    97  		return nil, registry.ErrNoRegistry
    98  	}
    99  
   100  	p, err := registry.ProfileFromPrivateKey(p, privKey)
   101  	if err != nil {
   102  		return nil, err
   103  	}
   104  
   105  	return c.doJSONProfileReq("POST", p)
   106  }
   107  
   108  // DeleteProfile removes a profile from the registry
   109  func (c *Client) DeleteProfile(p *registry.Profile, privKey crypto.PrivKey) error {
   110  	if c == nil {
   111  		return registry.ErrNoRegistry
   112  	}
   113  
   114  	p, err := registry.ProfileFromPrivateKey(p, privKey)
   115  	if err != nil {
   116  		return err
   117  	}
   118  	_, err = c.doJSONProfileReq("DELETE", p)
   119  	return err
   120  }
   121  
   122  // doJSONProfileReq is a common wrapper for /profile endpoint requests
   123  func (c Client) doJSONProfileReq(method string, p *registry.Profile) (*registry.Profile, error) {
   124  	if c.cfg.Location == "" {
   125  		return nil, ErrNoRegistry
   126  	}
   127  
   128  	data, err := json.Marshal(p)
   129  	if err != nil {
   130  		return nil, err
   131  	}
   132  
   133  	req, err := http.NewRequest(method, fmt.Sprintf("%s/registry/profile", c.cfg.Location), bytes.NewReader(data))
   134  	if err != nil {
   135  		return nil, err
   136  	}
   137  	req.Header.Set("Content-Type", "application/json")
   138  	res, err := c.httpClient.Do(req)
   139  	if err != nil {
   140  		if strings.Contains(err.Error(), "no such host") {
   141  			return nil, ErrNoRegistry
   142  		}
   143  		return nil, err
   144  	}
   145  
   146  	// add response to an envelope
   147  	env := struct {
   148  		Data *registry.Profile
   149  		Meta struct {
   150  			Error  string
   151  			Status string
   152  			Code   int
   153  		}
   154  	}{}
   155  
   156  	if err := json.NewDecoder(res.Body).Decode(&env); err != nil {
   157  		return nil, err
   158  	}
   159  
   160  	if res.StatusCode != http.StatusOK {
   161  		if strings.Contains(env.Meta.Error, "taken") {
   162  			return nil, registry.ErrUsernameTaken
   163  		}
   164  		return nil, fmt.Errorf("registry: %s", env.Meta.Error)
   165  	}
   166  
   167  	// Peername is in the process of being deprecated
   168  	// We want to favor Username, which is what we are
   169  	// using in all our cloud services
   170  	// this ensures any old references to Peername will not
   171  	// be lost
   172  	env.Data.Peername = env.Data.Username
   173  	return env.Data, nil
   174  }
   175  
   176  // RegistryResponse is a generic container for registry requests
   177  // TODO(dustmop): Only used currently for ProveKey
   178  // TODO(arqu): update with common API response object
   179  type RegistryResponse struct {
   180  	Data map[string]string
   181  	Meta struct {
   182  		Error  string
   183  		Status string
   184  		Code   int
   185  	}
   186  }
   187  
   188  // doJSONProfileReq sends a json body to the registry
   189  func (c Client) doJSONRegistryRequest(method, url string, input interface{}, output *RegistryResponse) error {
   190  	if c.cfg.Location == "" {
   191  		return ErrNoRegistry
   192  	}
   193  
   194  	data, err := json.Marshal(input)
   195  	if err != nil {
   196  		return err
   197  	}
   198  
   199  	fullurl := fmt.Sprintf("%s%s", c.cfg.Location, url)
   200  	req, err := http.NewRequest(method, fullurl, bytes.NewBuffer(data))
   201  	if err != nil {
   202  		return err
   203  	}
   204  	req.Header.Set("Content-Type", "application/json")
   205  	res, err := c.httpClient.Do(req)
   206  	if err != nil {
   207  		if strings.Contains(err.Error(), "no such host") {
   208  			return ErrNoRegistry
   209  		}
   210  		return err
   211  	}
   212  
   213  	if err := json.NewDecoder(res.Body).Decode(output); err != nil {
   214  		return err
   215  	}
   216  	if res.StatusCode != http.StatusOK {
   217  		return fmt.Errorf("registry: %s", output.Meta.Error)
   218  	}
   219  	return nil
   220  }