github.com/machinefi/w3bstream@v1.6.5-rc9.0.20240426031326-b8c7c4876e72/pkg/modules/account/account_eth.go (about)

     1  package account
     2  
     3  import (
     4  	"context"
     5  	"github.com/machinefi/w3bstream/pkg/depends/kit/logr"
     6  	"strings"
     7  
     8  	// "github.com/spruceid/siwe-go"
     9  
    10  	confid "github.com/machinefi/w3bstream/pkg/depends/conf/id"
    11  	"github.com/machinefi/w3bstream/pkg/depends/kit/sqlx"
    12  	"github.com/machinefi/w3bstream/pkg/enums"
    13  	"github.com/machinefi/w3bstream/pkg/errors/status"
    14  	"github.com/machinefi/w3bstream/pkg/models"
    15  	"github.com/machinefi/w3bstream/pkg/modules/operator"
    16  	"github.com/machinefi/w3bstream/pkg/types"
    17  	"github.com/spruceid/siwe-go"
    18  )
    19  
    20  func FetchOrCreateAccountByEthAddress(ctx context.Context, address types.EthAddress) (*models.Account, *models.AccountIdentity, error) {
    21  	_, l := logr.Start(ctx, "account.FetchOrCreateAccountByEthAddress")
    22  	defer l.End()
    23  
    24  	d := types.MustMgrDBExecutorFromContext(ctx)
    25  	g := confid.MustSFIDGeneratorFromContext(ctx)
    26  
    27  	var (
    28  		rel    = models.RelAccount{AccountID: g.MustGenSFID()}
    29  		acc    *models.Account
    30  		aci    *models.AccountIdentity
    31  		exists bool
    32  	)
    33  
    34  	err := sqlx.NewTasks(d).With(
    35  		// fetch AccountIdentity
    36  		func(db sqlx.DBExecutor) error {
    37  			aci = &models.AccountIdentity{
    38  				AccountIdentityInfo: models.AccountIdentityInfo{
    39  					Type:       enums.ACCOUNT_IDENTITY_TYPE__ETHADDRESS,
    40  					IdentityID: address.String(),
    41  				},
    42  			}
    43  			err := aci.FetchByTypeAndIdentityID(db)
    44  			if err != nil {
    45  				if sqlx.DBErr(err).IsNotFound() {
    46  					exists = false
    47  					return nil
    48  				} else {
    49  					return status.DatabaseError.StatusErr().WithDesc(err.Error())
    50  				}
    51  			} else {
    52  				exists = true
    53  				rel.AccountID = aci.AccountID
    54  				return nil
    55  			}
    56  		},
    57  		// create or fetch Account
    58  		func(db sqlx.DBExecutor) error {
    59  			acc = &models.Account{RelAccount: rel}
    60  			if exists {
    61  				if err := acc.FetchByAccountID(db); err != nil {
    62  					if sqlx.DBErr(err).IsNotFound() {
    63  						return status.AccountNotFound
    64  					}
    65  					return status.DatabaseError.StatusErr().WithDesc(err.Error())
    66  				}
    67  				if acc.State != enums.ACCOUNT_STATE__ENABLED {
    68  					return status.DisabledAccount
    69  				}
    70  				return nil
    71  			} else {
    72  				acc.Role = enums.ACCOUNT_ROLE__DEVELOPER
    73  				acc.State = enums.ACCOUNT_STATE__ENABLED
    74  				if err := acc.Create(db); err != nil {
    75  					return status.DatabaseError.StatusErr().WithDesc(err.Error())
    76  				}
    77  				return nil
    78  			}
    79  		},
    80  		// create AccountIdentity
    81  		func(db sqlx.DBExecutor) error {
    82  			if exists {
    83  				return nil
    84  			}
    85  			aci.RelAccount = rel
    86  			aci.Source = enums.ACCOUNT_SOURCE__SUBMIT
    87  			if err := aci.Create(db); err != nil {
    88  				if sqlx.DBErr(err).IsConflict() {
    89  					return status.AccountIdentityConflict
    90  				}
    91  				return status.DatabaseError.StatusErr().WithDesc(err.Error())
    92  			}
    93  			return nil
    94  		},
    95  		func(d sqlx.DBExecutor) error {
    96  			if exists {
    97  				return nil
    98  			}
    99  			req := operator.CreateReq{
   100  				Name:       operator.DefaultOperatorName,
   101  				PrivateKey: generateRandomPrivateKey(),
   102  			}
   103  			ctx := types.WithAccount(types.WithMgrDBExecutor(ctx, d), acc)
   104  			_, err := operator.Create(ctx, &req)
   105  			return err
   106  		},
   107  	).Do()
   108  
   109  	if err != nil {
   110  		l.Error(err)
   111  		return nil, nil, err
   112  	}
   113  	return acc, aci, nil
   114  }
   115  
   116  type LoginByEthAddressReq struct {
   117  	Message   string `json:"message"`   // Message siwe serialized message
   118  	Signature string `json:"signature"` // Signature should have '0x' prefix
   119  }
   120  
   121  func ValidateLoginByEthAddress(ctx context.Context, r *LoginByEthAddressReq) (*models.Account, error) {
   122  	_, l := logr.Start(ctx, "account.ValidateLoginByEthAddress")
   123  	defer l.End()
   124  
   125  	msg, err := siwe.ParseMessage(r.Message)
   126  	if err != nil {
   127  		l.Error(err)
   128  		return nil, status.InvalidEthLoginMessage.StatusErr().WithDesc(err.Error())
   129  	}
   130  
   131  	if _, err = msg.Verify(r.Signature, nil, nil, nil); err != nil {
   132  		l.Error(err)
   133  		return nil, status.InvalidEthLoginSignature.StatusErr().WithDesc(err.Error())
   134  	}
   135  
   136  	address := strings.ToLower(msg.GetAddress().String())
   137  
   138  	if lst, ok := types.EthAddressWhiteListFromContext(ctx); ok {
   139  		if !lst.Validate(address) {
   140  			return nil, status.WhiteListForbidden
   141  		}
   142  	}
   143  
   144  	acc, _, err := FetchOrCreateAccountByEthAddress(ctx, types.EthAddress(address))
   145  
   146  	if err != nil {
   147  		l.Error(err)
   148  		return nil, err
   149  	}
   150  	return acc, nil
   151  }