Skip to content

Commit

Permalink
feat(prove): Prove a new keypair for an account, set original profileID
Browse files Browse the repository at this point in the history
  • Loading branch information
dustmop committed Feb 5, 2021
1 parent a41140a commit 6effbea
Show file tree
Hide file tree
Showing 2 changed files with 125 additions and 12 deletions.
51 changes: 42 additions & 9 deletions lib/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package lib

import (
"context"
"encoding/base64"
"fmt"

"github.com/qri-io/qri/base"
"github.com/qri-io/qri/config"
Expand Down Expand Up @@ -46,27 +48,58 @@ func (m RegistryClientMethods) CreateProfile(p *RegistryProfile, ok *bool) (err
return m.updateConfig(pro)
}

// ProveProfileKey asserts to a registry that this user has control of a
// specified private key
// ProveProfileKey sends proof to the registry that this user has control of a
// specified private key, and modifies the user's config in order to reconcile
// it with any already existing identity the registry knows about
func (m RegistryClientMethods) ProveProfileKey(p *RegistryProfile, ok *bool) error {
if m.inst.rpc != nil {
return checkRPCError(m.inst.rpc.Call("RegistryClientMethods.CreateProfile", p, ok))
}

ctx := context.TODO()
ctx := context.Background()

// For signing the outgoing message
// TODO(arqu): this should take the profile PK instead of active PK once multi tenancy is supported
pro, err := m.inst.registry.ProveProfileKey(p, m.inst.repo.PrivateKey(ctx))
privKey := m.inst.repo.PrivateKey(ctx)

// Get public key to send to server
pro, err := m.inst.repo.Profile(ctx)
if err != nil {
return err
}
pubkeybytes, err := pro.PubKey.Bytes()
if err != nil {
return err
}

log.Debugf("prove profile response: %v", pro)
*p = *pro
p.ProfileID = pro.ID.String()
p.PublicKey = base64.StdEncoding.EncodeToString(pubkeybytes)
// TODO(dustmop): Expand the signature to sign more than just the username
sigbytes, err := privKey.Sign([]byte(p.Username))
p.Signature = base64.StdEncoding.EncodeToString(sigbytes)

return m.updateConfig(pro)
// Send proof to the registry
res, err := m.inst.registry.ProveKeyForProfile(p)
if err != nil {
return err
}
log.Debugf("prove profile response: %v", res)

// Convert the profile to a configuration, assign the registry provided profileID
cfg := m.configFromProfile(p)
if profileID, ok := res["ProfileID"]; ok {
cfg.Profile.ID = profileID
} else {
return fmt.Errorf("prove: server response invalid, did not have profileID")
}
// TODO(dustmop): Also get logbook

// Save the modified config
return m.inst.ChangeConfig(cfg)
}

func (m RegistryClientMethods) configChanges(pro *registry.Profile) *config.Config {
// Construct a config with the same values as the profile
func (m RegistryClientMethods) configFromProfile(pro *registry.Profile) *config.Config {
cfg := m.inst.cfg.Copy()
cfg.Profile.Peername = pro.Username
cfg.Profile.Created = pro.Created
Expand All @@ -83,7 +116,7 @@ func (m RegistryClientMethods) configChanges(pro *registry.Profile) *config.Conf

func (m RegistryClientMethods) updateConfig(pro *registry.Profile) error {
ctx := context.TODO()
cfg := m.configChanges(pro)
cfg := m.configFromProfile(pro)

// TODO (b5) - this should be automatically done by m.inst.ChangeConfig
repoPro, err := profile.NewProfile(cfg.Profile)
Expand Down
86 changes: 83 additions & 3 deletions registry/regclient/profile.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,44 @@ func (c *Client) CreateProfile(p *registry.Profile, pk crypto.PrivKey) (*registr
return c.doJSONProfileReq("POST", pro)
}

// ProveProfileKey associates a public key with a profile by proving this user
// can sign messages with the private key
func (c *Client) ProveProfileKey(p *registry.Profile, pk crypto.PrivKey) (*registry.Profile, error) {
// ProveKeyForProfile proves to the registry that the user owns the profile and
// is associating a new keypair
func (c *Client) ProveKeyForProfile(p *registry.Profile) (map[string]string, error) {
if c == nil {
return nil, registry.ErrNoRegistry
}

// Ensure all required fields are set
if p.ProfileID == "" {
return nil, fmt.Errorf("ProveKeyForProfile: ProfileID required")
}
if p.Username == "" {
return nil, fmt.Errorf("ProveKeyForProfile: Username required")
}
if p.Email == "" {
return nil, fmt.Errorf("ProveKeyForProfile: Email required")
}
if p.Password == "" {
return nil, fmt.Errorf("ProveKeyForProfile: Password required")
}
if p.PublicKey == "" {
return nil, fmt.Errorf("ProveKeyForProfile: PublicKey required")
}
if p.Signature == "" {
return nil, fmt.Errorf("ProveKeyForProfile: Signature required")
}

// Send proof request to the registry
res := RegistryResponse{}
err := c.doJSONRegistryRequest("PUT", "/registry/provekey", p, &res)
if err != nil {
return nil, err
}
return res.Data, nil
}

// UpdateProfile updates some information about the profile in the registry
func (c *Client) UpdateProfile(p *registry.Profile, pk crypto.PrivKey) (*registry.Profile, error) {
if c == nil {
return nil, registry.ErrNoRegistry
}
Expand Down Expand Up @@ -135,3 +170,48 @@ func (c Client) doJSONProfileReq(method string, p *registry.Profile) (*registry.
env.Data.Peername = env.Data.Username
return env.Data, nil
}

// RegistryResponse is a generic container for registry requests
// TODO(dustmop): Only used currently for ProveKey
type RegistryResponse struct {
Data map[string]string
Meta struct {
Error string
Status string
Code int
}
}

// doJSONProfileReq sends a json body to the registry
func (c Client) doJSONRegistryRequest(method, url string, input interface{}, output *RegistryResponse) error {
if c.cfg.Location == "" {
return ErrNoRegistry
}

data, err := json.Marshal(input)
if err != nil {
return err
}

fullurl := fmt.Sprintf("%s%s", c.cfg.Location, url)
req, err := http.NewRequest(method, fullurl, bytes.NewBuffer(data))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
res, err := c.httpClient.Do(req)
if err != nil {
if strings.Contains(err.Error(), "no such host") {
return ErrNoRegistry
}
return err
}

if err := json.NewDecoder(res.Body).Decode(output); err != nil {
return err
}
if res.StatusCode != http.StatusOK {
return fmt.Errorf("registry: %s", output.Meta.Error)
}
return nil
}

0 comments on commit 6effbea

Please sign in to comment.