github.com/qri-io/qri@v0.10.1-0.20220104210721-c771715036cb/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/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  	// TODO(arqu): convert to lib/http/HTTPClient
   139  	res, err := HTTPClient.Do(req)
   140  	if err != nil {
   141  		if strings.Contains(err.Error(), "no such host") {
   142  			return nil, ErrNoRegistry
   143  		}
   144  		return nil, err
   145  	}
   146  
   147  	// add response to an envelope
   148  	env := struct {
   149  		Data *registry.Profile
   150  		Meta struct {
   151  			Error  string
   152  			Status string
   153  			Code   int
   154  		}
   155  	}{}
   156  
   157  	if err := json.NewDecoder(res.Body).Decode(&env); err != nil {
   158  		return nil, err
   159  	}
   160  
   161  	if res.StatusCode != http.StatusOK {
   162  		if strings.Contains(env.Meta.Error, "taken") {
   163  			return nil, registry.ErrUsernameTaken
   164  		}
   165  		return nil, fmt.Errorf("registry: %s", env.Meta.Error)
   166  	}
   167  
   168  	// Peername is in the process of being deprecated
   169  	// We want to favor Username, which is what we are
   170  	// using in all our cloud services
   171  	// this ensures any old references to Peername will not
   172  	// be lost
   173  	env.Data.Peername = env.Data.Username
   174  	return env.Data, nil
   175  }
   176  
   177  // RegistryResponse is a generic container for registry requests
   178  // TODO(dustmop): Only used currently for ProveKey
   179  // TODO(arqu): update with common API response object
   180  type RegistryResponse struct {
   181  	Data map[string]string
   182  	Meta struct {
   183  		Error  string
   184  		Status string
   185  		Code   int
   186  	}
   187  }
   188  
   189  // doJSONProfileReq sends a json body to the registry
   190  func (c Client) doJSONRegistryRequest(method, url string, input interface{}, output *RegistryResponse) error {
   191  	if c.cfg.Location == "" {
   192  		return ErrNoRegistry
   193  	}
   194  
   195  	data, err := json.Marshal(input)
   196  	if err != nil {
   197  		return err
   198  	}
   199  
   200  	fullurl := fmt.Sprintf("%s%s", c.cfg.Location, url)
   201  	req, err := http.NewRequest(method, fullurl, bytes.NewBuffer(data))
   202  	if err != nil {
   203  		return err
   204  	}
   205  	req.Header.Set("Content-Type", "application/json")
   206  	// TODO(arqu): convert to lib/http/HTTPClient
   207  	res, err := HTTPClient.Do(req)
   208  	if err != nil {
   209  		if strings.Contains(err.Error(), "no such host") {
   210  			return ErrNoRegistry
   211  		}
   212  		return err
   213  	}
   214  
   215  	if err := json.NewDecoder(res.Body).Decode(output); err != nil {
   216  		return err
   217  	}
   218  	if res.StatusCode != http.StatusOK {
   219  		return fmt.Errorf("registry: %s", output.Meta.Error)
   220  	}
   221  	return nil
   222  }