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 }