mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-06-01 23:16:51 +00:00
account: segregate holdings by credentials for future multi-key management (#956)
* exchanges/account: shift credentials to account package and segregate funds to keys * merge: fixes * linter: fix * Update exchanges/account/account.go Co-authored-by: Scott <gloriousCode@users.noreply.github.com> * glorious: nits + protection for string panic * glorious_suggestion: add method for matching keys * linter: fix tests * account: add protected method for credentials minimizing access, display full account details to rpc. * linter: spelling kweeeeeeen * accounts/portfolio: clean/check portfolio code and quickly check balances from change. Add protected method for future matching. * accounts: theres no point in pointerising everything * linter: ok pointerise this then... * exchanges: fix regression add in little notes. * glorious: nits * Update exchanges/account/credentials.go Co-authored-by: Scott <gloriousCode@users.noreply.github.com> * Update exchanges/account/credentials_test.go Co-authored-by: Scott <gloriousCode@users.noreply.github.com> * Update exchanges/account/credentials_test.go Co-authored-by: Scott <gloriousCode@users.noreply.github.com> * glorious: nits * gloriously: fix glorious glorious test gloriously Co-authored-by: Ryan O'Hara-Reid <ryan.oharareid@thrasher.io> Co-authored-by: Scott <gloriousCode@users.noreply.github.com>
This commit is contained in:
@@ -5,28 +5,11 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/common/crypto"
|
||||
"github.com/thrasher-corp/gocryptotrader/config"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/account"
|
||||
"github.com/thrasher-corp/gocryptotrader/log"
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
|
||||
// contextCredential is a string flag for use with context values when setting
|
||||
// credentials internally or via gRPC.
|
||||
type contextCredential string
|
||||
|
||||
const (
|
||||
contextCredentialsFlag contextCredential = "apicredentials"
|
||||
contextSubAccountFlag contextCredential = "subaccountoverride"
|
||||
|
||||
key = "key"
|
||||
secret = "secret"
|
||||
subAccount = "subaccount"
|
||||
clientID = "clientid"
|
||||
oneTimePassword = "otp"
|
||||
_PEMKey = "pemkey"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -39,167 +22,20 @@ var (
|
||||
// undertake an authenticated HTTP request.
|
||||
ErrCredentialsAreEmpty = errors.New("credentials are empty")
|
||||
|
||||
errRequiresAPIKey = errors.New("requires API key but default/empty one set")
|
||||
errRequiresAPISecret = errors.New("requires API secret but default/empty one set")
|
||||
errRequiresAPIPEMKey = errors.New("requires API PEM key but default/empty one set")
|
||||
errRequiresAPIClientID = errors.New("requires API Client ID but default/empty one set")
|
||||
errBase64DecodeFailure = errors.New("base64 decode has failed")
|
||||
errMissingInfo = errors.New("cannot parse meta data missing information in key value pair")
|
||||
errInvalidCredentialMetaDataLength = errors.New("invalid meta data to process credentials")
|
||||
errContextCredentialsFailure = errors.New("context credentials type assertion failure")
|
||||
errMetaDataIsNil = errors.New("meta data is nil")
|
||||
errRequiresAPIKey = errors.New("requires API key but default/empty one set")
|
||||
errRequiresAPISecret = errors.New("requires API secret but default/empty one set")
|
||||
errRequiresAPIPEMKey = errors.New("requires API PEM key but default/empty one set")
|
||||
errRequiresAPIClientID = errors.New("requires API Client ID but default/empty one set")
|
||||
errBase64DecodeFailure = errors.New("base64 decode has failed")
|
||||
errContextCredentialsFailure = errors.New("context credentials type assertion failure")
|
||||
)
|
||||
|
||||
// ParseCredentialsMetadata intercepts and converts credentials metadata to a
|
||||
// static type for authentication processing and protection.
|
||||
func ParseCredentialsMetadata(ctx context.Context, md metadata.MD) (context.Context, error) {
|
||||
if md == nil {
|
||||
return ctx, errMetaDataIsNil
|
||||
}
|
||||
|
||||
credMD, ok := md[string(contextCredentialsFlag)]
|
||||
if !ok || len(credMD) == 0 {
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
if len(credMD) != 1 {
|
||||
return ctx, errInvalidCredentialMetaDataLength
|
||||
}
|
||||
|
||||
segregatedCreds := strings.Split(credMD[0], ",")
|
||||
var ctxCreds Credentials
|
||||
var subAccountHere string
|
||||
for x := range segregatedCreds {
|
||||
keyVals := strings.Split(segregatedCreds[x], ":")
|
||||
if len(keyVals) != 2 {
|
||||
return ctx, fmt.Errorf("%w received %v fields, expected 2 contains: %s",
|
||||
errMissingInfo,
|
||||
len(keyVals),
|
||||
keyVals)
|
||||
}
|
||||
switch keyVals[0] {
|
||||
case key:
|
||||
ctxCreds.Key = keyVals[1]
|
||||
case secret:
|
||||
ctxCreds.Secret = keyVals[1]
|
||||
case subAccount:
|
||||
// Capture sub account as this can override if other values are
|
||||
// not included in metadata.
|
||||
subAccountHere = keyVals[1]
|
||||
case clientID:
|
||||
ctxCreds.ClientID = keyVals[1]
|
||||
case _PEMKey:
|
||||
ctxCreds.PEMKey = keyVals[1]
|
||||
case oneTimePassword:
|
||||
ctxCreds.OneTimePassword = keyVals[1]
|
||||
}
|
||||
}
|
||||
if ctxCreds.IsEmpty() && subAccountHere != "" {
|
||||
// This will override default sub account details if needed.
|
||||
return deploySubAccountOverrideToContext(ctx, subAccountHere), nil
|
||||
}
|
||||
// merge sub account to main context credentials
|
||||
ctxCreds.SubAccount = subAccountHere
|
||||
return DeployCredentialsToContext(ctx, &ctxCreds), nil
|
||||
}
|
||||
|
||||
// Credentials define parameters that allow for an authenticated request.
|
||||
type Credentials struct {
|
||||
Key string
|
||||
Secret string
|
||||
ClientID string
|
||||
PEMKey string
|
||||
SubAccount string
|
||||
OneTimePassword string
|
||||
}
|
||||
|
||||
// DeployCredentialsToContext sets credentials for internal use to context which
|
||||
// can override default credential values.
|
||||
func DeployCredentialsToContext(ctx context.Context, creds *Credentials) context.Context {
|
||||
flag, store := creds.getInternal()
|
||||
return context.WithValue(ctx, flag, store)
|
||||
}
|
||||
|
||||
// deploySubAccountOverrideToContext sets subaccount as override to credentials
|
||||
// as a separate flag.
|
||||
func deploySubAccountOverrideToContext(ctx context.Context, subAccount string) context.Context {
|
||||
return context.WithValue(ctx, contextSubAccountFlag, subAccount)
|
||||
}
|
||||
|
||||
// GetMetaData returns the credentials for metadata context deployment
|
||||
func (c *Credentials) GetMetaData() (flag, values string) {
|
||||
vals := make([]string, 0, 6)
|
||||
if c.Key != "" {
|
||||
vals = append(vals, key+":"+c.Key)
|
||||
}
|
||||
if c.Secret != "" {
|
||||
vals = append(vals, secret+":"+c.Secret)
|
||||
}
|
||||
if c.SubAccount != "" {
|
||||
vals = append(vals, subAccount+":"+c.SubAccount)
|
||||
}
|
||||
if c.ClientID != "" {
|
||||
vals = append(vals, clientID+":"+c.ClientID)
|
||||
}
|
||||
if c.PEMKey != "" {
|
||||
vals = append(vals, _PEMKey+":"+c.PEMKey)
|
||||
}
|
||||
if c.OneTimePassword != "" {
|
||||
vals = append(vals, oneTimePassword+":"+c.OneTimePassword)
|
||||
}
|
||||
return string(contextCredentialsFlag), strings.Join(vals, ",")
|
||||
}
|
||||
|
||||
// IsEmpty return true if the underlying credentials type has not been filled
|
||||
// with at least one item.
|
||||
func (c *Credentials) IsEmpty() bool {
|
||||
return c == nil || c.ClientID == "" &&
|
||||
c.Key == "" &&
|
||||
c.OneTimePassword == "" &&
|
||||
c.PEMKey == "" &&
|
||||
c.Secret == "" &&
|
||||
c.SubAccount == ""
|
||||
}
|
||||
|
||||
// contextCredentialsStore protects the stored credentials for use in a context
|
||||
type contextCredentialsStore struct {
|
||||
creds *Credentials
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// getInternal returns the values for assignment to an internal context
|
||||
func (c *Credentials) getInternal() (contextCredential, *contextCredentialsStore) {
|
||||
if c.IsEmpty() {
|
||||
return "", nil
|
||||
}
|
||||
store := &contextCredentialsStore{}
|
||||
store.Load(c)
|
||||
return contextCredentialsFlag, store
|
||||
}
|
||||
|
||||
// Load stores provided credentials
|
||||
func (c *contextCredentialsStore) Load(creds *Credentials) {
|
||||
// Segregate from external call
|
||||
cpy := *creds
|
||||
c.mu.Lock()
|
||||
c.creds = &cpy
|
||||
c.mu.Unlock()
|
||||
}
|
||||
|
||||
// Get returns the full credentials from the store
|
||||
func (c *contextCredentialsStore) Get() *Credentials {
|
||||
c.mu.RLock()
|
||||
creds := *c.creds
|
||||
c.mu.RUnlock()
|
||||
return &creds
|
||||
}
|
||||
|
||||
// SetKey sets new key for the default credentials
|
||||
func (a *API) SetKey(key string) {
|
||||
a.credMu.Lock()
|
||||
defer a.credMu.Unlock()
|
||||
if a.credentials == nil {
|
||||
a.credentials = &Credentials{}
|
||||
a.credentials = &account.Credentials{}
|
||||
}
|
||||
a.credentials.Key = key
|
||||
}
|
||||
@@ -209,7 +45,7 @@ func (a *API) SetSecret(secret string) {
|
||||
a.credMu.Lock()
|
||||
defer a.credMu.Unlock()
|
||||
if a.credentials == nil {
|
||||
a.credentials = &Credentials{}
|
||||
a.credentials = &account.Credentials{}
|
||||
}
|
||||
a.credentials.Secret = secret
|
||||
}
|
||||
@@ -219,7 +55,7 @@ func (a *API) SetClientID(clientID string) {
|
||||
a.credMu.Lock()
|
||||
defer a.credMu.Unlock()
|
||||
if a.credentials == nil {
|
||||
a.credentials = &Credentials{}
|
||||
a.credentials = &account.Credentials{}
|
||||
}
|
||||
a.credentials.ClientID = clientID
|
||||
}
|
||||
@@ -229,7 +65,7 @@ func (a *API) SetPEMKey(pem string) {
|
||||
a.credMu.Lock()
|
||||
defer a.credMu.Unlock()
|
||||
if a.credentials == nil {
|
||||
a.credentials = &Credentials{}
|
||||
a.credentials = &account.Credentials{}
|
||||
}
|
||||
a.credentials.PEMKey = pem
|
||||
}
|
||||
@@ -239,14 +75,14 @@ func (a *API) SetSubAccount(sub string) {
|
||||
a.credMu.Lock()
|
||||
defer a.credMu.Unlock()
|
||||
if a.credentials == nil {
|
||||
a.credentials = &Credentials{}
|
||||
a.credentials = &account.Credentials{}
|
||||
}
|
||||
a.credentials.SubAccount = sub
|
||||
}
|
||||
|
||||
// CheckCredentials checks to see if the required fields have been set before
|
||||
// sending an authenticated API request
|
||||
func (b *Base) CheckCredentials(creds *Credentials, isContext bool) error {
|
||||
func (b *Base) CheckCredentials(creds *account.Credentials, isContext bool) error {
|
||||
if b.SkipAuthCheck {
|
||||
return nil
|
||||
}
|
||||
@@ -277,7 +113,7 @@ func (b *Base) AreCredentialsValid(ctx context.Context) bool {
|
||||
|
||||
// GetDefaultCredentials returns the exchange.Base api credentials loaded by
|
||||
// config.json
|
||||
func (b *Base) GetDefaultCredentials() *Credentials {
|
||||
func (b *Base) GetDefaultCredentials() *account.Credentials {
|
||||
b.API.credMu.RLock()
|
||||
defer b.API.credMu.RUnlock()
|
||||
if b.API.credentials == nil {
|
||||
@@ -289,12 +125,14 @@ func (b *Base) GetDefaultCredentials() *Credentials {
|
||||
|
||||
// GetCredentials checks and validates current credentials, context credentials
|
||||
// override default credentials, if no credentials found, will return an error.
|
||||
func (b *Base) GetCredentials(ctx context.Context) (*Credentials, error) {
|
||||
value := ctx.Value(contextCredentialsFlag)
|
||||
func (b *Base) GetCredentials(ctx context.Context) (*account.Credentials, error) {
|
||||
value := ctx.Value(account.ContextCredentialsFlag)
|
||||
if value != nil {
|
||||
ctxCredStore, ok := value.(*contextCredentialsStore)
|
||||
ctxCredStore, ok := value.(*account.ContextCredentialsStore)
|
||||
if !ok {
|
||||
return &Credentials{}, errContextCredentialsFailure
|
||||
// NOTE: Return empty credentials on error to limit panic on
|
||||
// websocket handling.
|
||||
return &account.Credentials{}, errContextCredentialsFailure
|
||||
}
|
||||
|
||||
creds := ctxCredStore.Get()
|
||||
@@ -306,9 +144,11 @@ func (b *Base) GetCredentials(ctx context.Context) (*Credentials, error) {
|
||||
|
||||
err := b.CheckCredentials(b.API.credentials, false)
|
||||
if err != nil {
|
||||
return &Credentials{}, err
|
||||
// NOTE: Return empty credentials on error to limit panic on websocket
|
||||
// handling.
|
||||
return &account.Credentials{}, err
|
||||
}
|
||||
subAccountOverride, ok := ctx.Value(contextSubAccountFlag).(string)
|
||||
subAccountOverride, ok := ctx.Value(account.ContextSubAccountFlag).(string)
|
||||
b.API.credMu.RLock()
|
||||
defer b.API.credMu.RUnlock()
|
||||
creds := *b.API.credentials
|
||||
@@ -319,7 +159,7 @@ func (b *Base) GetCredentials(ctx context.Context) (*Credentials, error) {
|
||||
}
|
||||
|
||||
// ValidateAPICredentials validates the exchanges API credentials
|
||||
func (b *Base) ValidateAPICredentials(creds *Credentials) error {
|
||||
func (b *Base) ValidateAPICredentials(creds *account.Credentials) error {
|
||||
b.API.credMu.RLock()
|
||||
defer b.API.credMu.RUnlock()
|
||||
if creds.IsEmpty() {
|
||||
@@ -359,7 +199,7 @@ func (b *Base) SetCredentials(apiKey, apiSecret, clientID, subaccount, pemKey, o
|
||||
b.API.credMu.Lock()
|
||||
defer b.API.credMu.Unlock()
|
||||
if b.API.credentials == nil {
|
||||
b.API.credentials = &Credentials{}
|
||||
b.API.credentials = &account.Credentials{}
|
||||
}
|
||||
b.API.credentials.Key = apiKey
|
||||
b.API.credentials.ClientID = clientID
|
||||
|
||||
Reference in New Issue
Block a user