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  }