github.com/vchain-us/vcn@v0.9.11-0.20210921212052-a2484d23c0b3/pkg/api/user.go (about) 1 /* 2 * Copyright (c) 2018-2020 vChain, Inc. All Rights Reserved. 3 * This software is released under GPL3. 4 * The full license information can be found under: 5 * https://www.gnu.org/licenses/gpl-3.0.en.html 6 * 7 */ 8 9 package api 10 11 import ( 12 "bytes" 13 "encoding/json" 14 "fmt" 15 "io" 16 "io/ioutil" 17 "strings" 18 19 "github.com/ethereum/go-ethereum/accounts/abi/bind" 20 "github.com/sirupsen/logrus" 21 "github.com/vchain-us/vcn/internal/errors" 22 "github.com/vchain-us/vcn/pkg/meta" 23 "github.com/vchain-us/vcn/pkg/store" 24 ) 25 26 // User represent a CodeNotary platform user. 27 type User struct { 28 cfg *store.User 29 } 30 31 // NewUser returns a new User instance for the given email. 32 func NewUser(email string) *User { 33 return &User{ 34 cfg: store.Config().UserByMail(email), 35 } 36 } 37 38 // Email returns the User's email, if any, otherwise an empty string. 39 func (u User) Email() string { 40 if u.cfg != nil { 41 return u.cfg.Email 42 } 43 return "" 44 } 45 46 // Authenticate the User against the CodeNotary platform. 47 // If successful the auth token in stored within the User's config and used for subsequent API call. 48 func (u *User) Authenticate(password string, otp string) (err error) { 49 if u == nil || u.Email() == "" { 50 return makeFatal("user not initialized", nil) 51 } 52 53 email := u.Email() 54 55 ok, err := checkUserExists(email) 56 if err != nil { 57 return 58 } 59 if !ok { 60 return fmt.Errorf(`no such user "%s", please create an account at %s`, email, meta.DashboardURL()) 61 } 62 63 token, err := authenticateUser(email, password, otp) 64 if err != nil { 65 return 66 } 67 68 u.cfg.Token = token 69 return nil 70 } 71 72 // ClearAuth deletes the stored authentication token. 73 func (u *User) ClearAuth() { 74 if u != nil && u.cfg != nil { 75 u.cfg.Token = "" 76 } 77 } 78 79 func (u *User) token() string { 80 if u != nil && u.cfg != nil { 81 return u.cfg.Token 82 } 83 return "" 84 } 85 86 // IsAuthenticated returns true if the stored auth token is still valid. 87 func (u User) IsAuthenticated() (bool, error) { 88 if u.cfg == nil || u.cfg.Token == "" { 89 return false, nil 90 } 91 92 return checkToken(u.cfg.Token) 93 } 94 95 // IsExist returns true if the User's was registered on the CodeNotary platform. 96 func (u User) IsExist() (bool, error) { 97 email := u.Email() 98 if email != "" { 99 return checkUserExists(email) 100 } 101 return false, nil 102 } 103 104 // Config returns the User configuration object (see store.User), if any. 105 // It returns nil if the User is not properly initialized. 106 func (u User) Config() *store.User { 107 if u.cfg != nil { 108 return u.cfg 109 } 110 return nil 111 } 112 113 // UserByCfg configures current user with a custom values 114 func (u *User) UserByCfg(cfg *store.User) { 115 u.cfg = cfg 116 } 117 118 func (u User) getWallet() (address, keystore string, offline bool, err error) { 119 authError := new(Error) 120 pagedWalletResponse := new(struct { 121 Content []struct { 122 Address string `json:"address"` 123 KeyStore string `json:"keyStore"` 124 CreatedAt string `json:"createdAt"` 125 Name string `json:"name"` 126 PermissionSyncState string `json:"permissionSyncState"` 127 LevelSyncState string `json:"levelSyncState"` 128 } `json:"content"` 129 }) 130 r, err := newSling(u.token()). 131 Get(meta.APIEndpoint("wallet")). 132 Receive(pagedWalletResponse, authError) 133 logger().WithFields(logrus.Fields{ 134 "response": "HIDDEN", 135 "err": err, 136 "authError": authError, 137 }).Trace("getWallet") 138 if err != nil { 139 return 140 } 141 if r.StatusCode != 200 { 142 err = fmt.Errorf("request failed: %s (%d)", authError.Message, authError.Status) 143 return 144 } 145 146 wallets := pagedWalletResponse.Content 147 for _, wallet := range wallets { 148 if wallet.Address == strings.ToLower(address) { 149 if wallet.PermissionSyncState == "SYNCED" && wallet.LevelSyncState == "SYNCED" { 150 // everything is ok 151 break // currently, the user can have just one wallet 152 } 153 err = fmt.Errorf(errors.AccountNotSynced) 154 return 155 } 156 } 157 158 // currently, the user can have just one wallet 159 if len(wallets) > 0 { 160 address = wallets[0].Address 161 keystore = wallets[0].KeyStore 162 } 163 return 164 } 165 166 // Secret fetches the User's secret and returns an io.Reader for reading it. 167 func (u User) Secret() (reader, id string, offline bool, err error) { 168 id, keystore, offline, err := u.getWallet() 169 170 switch true { 171 case err != nil: 172 // passthru error 173 case id == "": 174 err = fmt.Errorf("no secret found for %s", u.Email()) 175 case offline: 176 // passthru offline 177 case keystore == "": 178 err = fmt.Errorf("no secret found for %s", u.Email()) 179 default: 180 reader = keystore 181 } 182 183 return 184 } 185 186 // SignerID retrives the User's SignerID (the public address derived from the secret) from the platform. 187 func (u User) SignerID() (id string, err error) { 188 id, _, _, err = u.getWallet() 189 if err == nil && id == "" { 190 err = fmt.Errorf("no SignerID found for %s", u.Email()) 191 } 192 return 193 } 194 195 // UploadSecret uploads the User's secret to the platform. 196 func (u User) UploadSecret(secret io.Reader, passphrase string) (err error) { 197 var buf bytes.Buffer 198 tee := io.TeeReader(secret, &buf) 199 transactor, err := bind.NewTransactor(tee, passphrase) 200 if err != nil { 201 return 202 } 203 address := strings.ToLower(transactor.From.Hex()) 204 205 b, err := ioutil.ReadAll(&buf) 206 if err != nil { 207 return err 208 } 209 content := &struct { 210 KeyStore string `json:"keyStore"` 211 }{string(b)} 212 b, err = json.Marshal(content) 213 if err != nil { 214 return 215 } 216 217 authError := new(Error) 218 r, err := newSling(u.token()). 219 Patch(meta.APIEndpoint("wallet")+"?wallet-address="+address). 220 Body(bytes.NewReader(b)). 221 Add("Content-Type", "application/json"). 222 Receive(nil, authError) 223 logger().WithFields(logrus.Fields{ 224 "err": err, 225 "authError": authError, 226 }).Trace("UploadSecret") 227 228 if err != nil { 229 return 230 } 231 if r.StatusCode != 200 { 232 err = fmt.Errorf("request failed: %s (%d)", authError.Message, authError.Status) 233 return 234 } 235 return 236 } 237 238 // RemainingSignOps returns the number of remaining notarizations in the User's account subscription. 239 func (u User) RemainingSignOps() (uint64, error) { 240 response := new(struct { 241 Count uint64 `json:"count"` 242 }) 243 restError := new(Error) 244 r, err := newSling(u.token()). 245 Get(meta.APIEndpoint("artifact/remaining-sign-operations")). 246 Receive(&response, restError) 247 logger().WithFields(logrus.Fields{ 248 "response": response, 249 "err": err, 250 "restError": restError, 251 }).Trace("RemainingSignOps") 252 if err != nil { 253 return 0, err 254 } 255 switch r.StatusCode { 256 case 200: 257 return response.Count, nil 258 } 259 return 0, fmt.Errorf("count remaining sign operations failed: %+v", restError) 260 } 261 262 func (u User) trialExpired() (bool, error) { 263 response := new(struct { 264 TrialExpired bool `json:"trialExpired"` 265 }) 266 restError := new(Error) 267 r, err := newSling(u.token()). 268 Get(publisherEndpoint()). 269 Receive(&response, restError) 270 logger().WithFields(logrus.Fields{ 271 "response": response, 272 "err": err, 273 "restError": restError, 274 }).Trace("trialExpired") 275 if err != nil { 276 return false, err 277 } 278 switch r.StatusCode { 279 case 200: 280 return response.TrialExpired, nil 281 } 282 return false, fmt.Errorf("cannot get user info from platform: %+v", restError) 283 }