From 436dc3cfb06fd7456b1df92ebed6ff906a5f868e Mon Sep 17 00:00:00 2001 From: Jack Kearney Date: Thu, 17 Oct 2024 13:49:50 -0400 Subject: [PATCH 1/4] Refactor client parameters. Enable setting header --- client.go | 107 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 100 insertions(+), 7 deletions(-) diff --git a/client.go b/client.go index 85e4932..947409b 100644 --- a/client.go +++ b/client.go @@ -2,7 +2,11 @@ package sdk import ( + "fmt" + "net/http" + "github.com/go-openapi/runtime" + httptransport "github.com/go-openapi/runtime/client" "github.com/go-openapi/strfmt" "github.com/pkg/errors" @@ -11,22 +15,111 @@ import ( "github.com/tkhq/go-sdk/pkg/store/local" ) +const DefaultClientVersion = "go-sdk" + +type config struct { + apiKey *apikey.Key + clientVersion string + registry strfmt.Registry + transportConfig *client.TransportConfig +} + +type Optfn func(c *config) error + +// WithClientVersion overrides the client version used for this API client. +func WithClientVersion(clientVersion string) Optfn { + return func(c *config) error { + c.clientVersion = clientVersion + return nil + } +} + +// WithRegistry sets the registry formats used for this API client. +func WithRegistry(registry strfmt.Registry) Optfn { + return func(c *config) error { + c.registry = registry + return nil + } +} + +// WithTransportConfig sets the TransportConfig used for this API client. +func WithTransportConfig(transportConfig client.TransportConfig) Optfn { + return func(c *config) error { + c.transportConfig = &transportConfig + return nil + } +} + +// WithAPIKey sets the API key used for this API client. +// Users would normally use WithAPIKeyName. This offers a lower-level custom API +// key. +func WithAPIKey(apiKey *apikey.Key) Optfn { + return func(c *config) error { + c.apiKey = apiKey + return nil + } +} + +// WithAPIKeyName sets the API key to the key loaded from the local keystore +// with the provided name. +func WithAPIKeyName(keyname string) Optfn { + return func(c *config) error { + apiKey, err := local.New[*apikey.Key]().Load(keyname) + if err != nil { + return errors.Wrap(err, "failed to load API key") + } + c.apiKey = apiKey + return nil + } +} + // New returns a new API Client with the given API key name from the default keystore. -func New(keyname string) (*Client, error) { - apiKey, err := local.New[*apikey.Key, apikey.Metadata]().Load(keyname) - if err != nil { - return nil, errors.Wrap(err, "failed to load API key") +func New(options ...Optfn) (*Client, error) { + c := &config{ + clientVersion: DefaultClientVersion, + transportConfig: client.DefaultTransportConfig(), + } + + for _, o := range options { + o(c) } + fmt.Println(c.transportConfig.Host) + + // create transport and client + transport := httptransport.New( + c.transportConfig.Host, + c.transportConfig.BasePath, + c.transportConfig.Schemes, + ) + transport.Transport = SetClientVersion(transport.Transport, c.clientVersion) return &Client{ - Client: client.NewHTTPClient(nil), - Authenticator: &Authenticator{Key: apiKey}, - APIKey: apiKey, + Client: client.New(transport, c.registry), + Authenticator: &Authenticator{Key: c.apiKey}, + APIKey: c.apiKey, }, nil } +func SetClientVersion(inner http.RoundTripper, clientVersion string) http.RoundTripper { + return &addClientVersion{ + inner: inner, + Version: clientVersion, + } +} + +type addClientVersion struct { + inner http.RoundTripper + Version string +} + +func (acv *addClientVersion) RoundTrip(r *http.Request) (*http.Response, error) { + r.Header.Set("X-Client-Version", acv.Version) + return acv.inner.RoundTrip(r) +} + // NewHTTPClient returns a new base HTTP API client. // Most users will call New() instead. +// Deprecated: Use New(WithRegistry(formats)) instead func NewHTTPClient(formats strfmt.Registry) *client.TurnkeyAPI { return client.NewHTTPClient(formats) } From 1b778e108547bb0d9901b9c076a435ce8d735ad9 Mon Sep 17 00:00:00 2001 From: Jack Kearney Date: Wed, 23 Oct 2024 19:23:38 -0400 Subject: [PATCH 2/4] Syntax fixes --- client.go | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/client.go b/client.go index 947409b..4b9ad5a 100644 --- a/client.go +++ b/client.go @@ -2,7 +2,6 @@ package sdk import ( - "fmt" "net/http" "github.com/go-openapi/runtime" @@ -24,10 +23,11 @@ type config struct { transportConfig *client.TransportConfig } -type Optfn func(c *config) error +// OptionFunc defines a function which sets configuration options for a Client. +type OptionFunc func(c *config) error // WithClientVersion overrides the client version used for this API client. -func WithClientVersion(clientVersion string) Optfn { +func WithClientVersion(clientVersion string) OptionFunc { return func(c *config) error { c.clientVersion = clientVersion return nil @@ -35,7 +35,7 @@ func WithClientVersion(clientVersion string) Optfn { } // WithRegistry sets the registry formats used for this API client. -func WithRegistry(registry strfmt.Registry) Optfn { +func WithRegistry(registry strfmt.Registry) OptionFunc { return func(c *config) error { c.registry = registry return nil @@ -43,7 +43,7 @@ func WithRegistry(registry strfmt.Registry) Optfn { } // WithTransportConfig sets the TransportConfig used for this API client. -func WithTransportConfig(transportConfig client.TransportConfig) Optfn { +func WithTransportConfig(transportConfig client.TransportConfig) OptionFunc { return func(c *config) error { c.transportConfig = &transportConfig return nil @@ -53,7 +53,7 @@ func WithTransportConfig(transportConfig client.TransportConfig) Optfn { // WithAPIKey sets the API key used for this API client. // Users would normally use WithAPIKeyName. This offers a lower-level custom API // key. -func WithAPIKey(apiKey *apikey.Key) Optfn { +func WithAPIKey(apiKey *apikey.Key) OptionFunc { return func(c *config) error { c.apiKey = apiKey return nil @@ -62,7 +62,7 @@ func WithAPIKey(apiKey *apikey.Key) Optfn { // WithAPIKeyName sets the API key to the key loaded from the local keystore // with the provided name. -func WithAPIKeyName(keyname string) Optfn { +func WithAPIKeyName(keyname string) OptionFunc { return func(c *config) error { apiKey, err := local.New[*apikey.Key]().Load(keyname) if err != nil { @@ -74,7 +74,7 @@ func WithAPIKeyName(keyname string) Optfn { } // New returns a new API Client with the given API key name from the default keystore. -func New(options ...Optfn) (*Client, error) { +func New(options ...OptionFunc) (*Client, error) { c := &config{ clientVersion: DefaultClientVersion, transportConfig: client.DefaultTransportConfig(), @@ -83,14 +83,15 @@ func New(options ...Optfn) (*Client, error) { for _, o := range options { o(c) } - fmt.Println(c.transportConfig.Host) - // create transport and client + // Create transport and client transport := httptransport.New( c.transportConfig.Host, c.transportConfig.BasePath, c.transportConfig.Schemes, ) + + // Add client version header transport.Transport = SetClientVersion(transport.Transport, c.clientVersion) return &Client{ From 70c9a546866a5131b0bc9d7d951675484e5e5f83 Mon Sep 17 00:00:00 2001 From: Jack Kearney Date: Thu, 24 Oct 2024 11:29:18 -0400 Subject: [PATCH 3/4] Fixes to examples --- examples/signing/sign_raw_payload/main.go | 2 +- examples/signing/sign_transaction/main.go | 4 ++-- examples/wallets/create_wallet/main.go | 4 ++-- examples/wallets/create_wallet_accounts/main.go | 2 +- examples/whoami/whoami.go | 2 +- pkg/store/local/local.go | 1 - 6 files changed, 7 insertions(+), 8 deletions(-) diff --git a/examples/signing/sign_raw_payload/main.go b/examples/signing/sign_raw_payload/main.go index 446cd8b..785234e 100644 --- a/examples/signing/sign_raw_payload/main.go +++ b/examples/signing/sign_raw_payload/main.go @@ -14,7 +14,7 @@ import ( func main() { // NB: make sure to create and register an API key, first. - client, err := sdk.New("default") + client, err := sdk.New(sdk.WithAPIKeyName("default")) if err != nil { log.Fatal("failed to create new SDK client:", err) } diff --git a/examples/signing/sign_transaction/main.go b/examples/signing/sign_transaction/main.go index 1a4d78a..cd27d5b 100644 --- a/examples/signing/sign_transaction/main.go +++ b/examples/signing/sign_transaction/main.go @@ -13,8 +13,8 @@ import ( ) func main() { - // NB: make sure to create and register an API key, first. - client, err := sdk.New("default") + // NB: make sure to create and register an API key,first. + client, err := sdk.New(sdk.WithAPIKeyName("default")) if err != nil { log.Fatal("failed to create new SDK client:", err) } diff --git a/examples/wallets/create_wallet/main.go b/examples/wallets/create_wallet/main.go index d1a38e9..4a81d2b 100644 --- a/examples/wallets/create_wallet/main.go +++ b/examples/wallets/create_wallet/main.go @@ -13,8 +13,8 @@ import ( ) func main() { - // NB: make sure to create and register an API key, first. - client, err := sdk.New("default") + // NB: make sure to create WithAPIKeyand register an API key, first. + client, err := sdk.New(sdk.WithAPIKeyName("default")) if err != nil { log.Fatal("failed to create new SDK client:", err) } diff --git a/examples/wallets/create_wallet_accounts/main.go b/examples/wallets/create_wallet_accounts/main.go index c9debb4..9dbf400 100644 --- a/examples/wallets/create_wallet_accounts/main.go +++ b/examples/wallets/create_wallet_accounts/main.go @@ -14,7 +14,7 @@ import ( func main() { // NB: make sure to create and register an API key, first. - client, err := sdk.New("default") + client, err := sdk.New(sdk.WithAPIKeyName("default")) if err != nil { log.Fatal("failed to create new SDK client:", err) } diff --git a/examples/whoami/whoami.go b/examples/whoami/whoami.go index e143e0a..210c0a0 100644 --- a/examples/whoami/whoami.go +++ b/examples/whoami/whoami.go @@ -12,7 +12,7 @@ import ( func main() { // NB: make sure to create and register an API key, first. - client, err := sdk.New("") + client, err := sdk.New() if err != nil { log.Fatal("failed to create new SDK client:", err) } diff --git a/pkg/store/local/local.go b/pkg/store/local/local.go index df80cb7..db14c9c 100644 --- a/pkg/store/local/local.go +++ b/pkg/store/local/local.go @@ -123,7 +123,6 @@ func getConfigDir() string { var err error cfgDir, err = os.UserConfigDir() - if err != nil { shouldUseHomeDir = true } From cb38ffc127548bcb54171c4976d831a312678e78 Mon Sep 17 00:00:00 2001 From: Jack Kearney Date: Thu, 24 Oct 2024 13:53:44 -0400 Subject: [PATCH 4/4] Linting fixes --- client.go | 14 ++++++++++++-- examples/signing/sign_transaction/main.go | 2 +- examples/wallets/create_wallet/main.go | 2 +- pkg/apikey/apikey.go | 4 ++-- pkg/apikey/ecdsa.go | 8 ++++++-- pkg/encryptionkey/encryptionkey.go | 9 ++++----- pkg/store/local/local.go | 8 +++++--- pkg/store/local/local_test.go | 19 +++---------------- 8 files changed, 34 insertions(+), 32 deletions(-) diff --git a/client.go b/client.go index 4b9ad5a..96a4058 100644 --- a/client.go +++ b/client.go @@ -30,6 +30,7 @@ type OptionFunc func(c *config) error func WithClientVersion(clientVersion string) OptionFunc { return func(c *config) error { c.clientVersion = clientVersion + return nil } } @@ -38,6 +39,7 @@ func WithClientVersion(clientVersion string) OptionFunc { func WithRegistry(registry strfmt.Registry) OptionFunc { return func(c *config) error { c.registry = registry + return nil } } @@ -46,6 +48,7 @@ func WithRegistry(registry strfmt.Registry) OptionFunc { func WithTransportConfig(transportConfig client.TransportConfig) OptionFunc { return func(c *config) error { c.transportConfig = &transportConfig + return nil } } @@ -56,6 +59,7 @@ func WithTransportConfig(transportConfig client.TransportConfig) OptionFunc { func WithAPIKey(apiKey *apikey.Key) OptionFunc { return func(c *config) error { c.apiKey = apiKey + return nil } } @@ -68,7 +72,9 @@ func WithAPIKeyName(keyname string) OptionFunc { if err != nil { return errors.Wrap(err, "failed to load API key") } + c.apiKey = apiKey + return nil } } @@ -81,7 +87,10 @@ func New(options ...OptionFunc) (*Client, error) { } for _, o := range options { - o(c) + err := o(c) + if err != nil { + return nil, err + } } // Create transport and client @@ -115,12 +124,13 @@ type addClientVersion struct { func (acv *addClientVersion) RoundTrip(r *http.Request) (*http.Response, error) { r.Header.Set("X-Client-Version", acv.Version) + return acv.inner.RoundTrip(r) } // NewHTTPClient returns a new base HTTP API client. // Most users will call New() instead. -// Deprecated: Use New(WithRegistry(formats)) instead +// Deprecated: Use New(WithRegistry(formats)) instead. func NewHTTPClient(formats strfmt.Registry) *client.TurnkeyAPI { return client.NewHTTPClient(formats) } diff --git a/examples/signing/sign_transaction/main.go b/examples/signing/sign_transaction/main.go index cd27d5b..d7c109b 100644 --- a/examples/signing/sign_transaction/main.go +++ b/examples/signing/sign_transaction/main.go @@ -13,7 +13,7 @@ import ( ) func main() { - // NB: make sure to create and register an API key,first. + // NB: make sure to create and register an API key, first. client, err := sdk.New(sdk.WithAPIKeyName("default")) if err != nil { log.Fatal("failed to create new SDK client:", err) diff --git a/examples/wallets/create_wallet/main.go b/examples/wallets/create_wallet/main.go index 4a81d2b..cfe9a71 100644 --- a/examples/wallets/create_wallet/main.go +++ b/examples/wallets/create_wallet/main.go @@ -13,7 +13,7 @@ import ( ) func main() { - // NB: make sure to create WithAPIKeyand register an API key, first. + // NB: make sure to create and register an API key, first. client, err := sdk.New(sdk.WithAPIKeyName("default")) if err != nil { log.Fatal("failed to create new SDK client:", err) diff --git a/pkg/apikey/apikey.go b/pkg/apikey/apikey.go index 83ca4ca..d411273 100644 --- a/pkg/apikey/apikey.go +++ b/pkg/apikey/apikey.go @@ -50,11 +50,11 @@ type APIStamp struct { // New generates a new Turnkey API key. func New(organizationID string, opts ...optionFunc) (*Key, error) { if organizationID == "" { - return nil, fmt.Errorf("please supply a valid Organization UUID") + return nil, errors.New("please supply a valid Organization UUID") } if _, err := uuid.Parse(organizationID); err != nil { - return nil, fmt.Errorf("failed to parse organization ID") + return nil, errors.New("failed to parse organization ID") } apiKey := &Key{ diff --git a/pkg/apikey/ecdsa.go b/pkg/apikey/ecdsa.go index 393e64f..219a998 100644 --- a/pkg/apikey/ecdsa.go +++ b/pkg/apikey/ecdsa.go @@ -29,6 +29,10 @@ func (k *ecdsaKey) sign(msg []byte) (string, error) { return hex.EncodeToString(sigBytes), nil } +// TurnkeyECDSAPublicKeyBytes is the expected number of bytes for a public ECDSA +// key. +const TurnkeyECDSAPublicKeyBytes = 33 + // EncodePrivateECDSAKey encodes an ECDSA private key into the Turnkey format. // For now, "Turnkey format" = raw DER form. func EncodePrivateECDSAKey(privateKey *ecdsa.PrivateKey) string { @@ -81,7 +85,7 @@ func DecodeTurnkeyPublicECDSAKey(encodedPublicKey string, scheme signatureScheme return nil, err } - if len(bytes) != 33 { + if len(bytes) != TurnkeyECDSAPublicKeyBytes { return nil, fmt.Errorf("expected a 33-bytes-long public key (compressed). Got %d bytes", len(bytes)) } @@ -99,7 +103,7 @@ func DecodeTurnkeyPublicECDSAKey(encodedPublicKey string, scheme signatureScheme pubkey, err := dcrec.ParsePubKey(bytes) if err != nil { - return nil, fmt.Errorf("cannot parse bytes into secp256k1 public key") + return nil, errors.New("cannot parse bytes into secp256k1 public key") } x = pubkey.X() diff --git a/pkg/encryptionkey/encryptionkey.go b/pkg/encryptionkey/encryptionkey.go index 47630c2..18cdabe 100644 --- a/pkg/encryptionkey/encryptionkey.go +++ b/pkg/encryptionkey/encryptionkey.go @@ -4,7 +4,6 @@ package encryptionkey import ( "encoding/hex" "encoding/json" - "fmt" "os" "github.com/cloudflare/circl/hpke" @@ -39,19 +38,19 @@ type Key struct { // New generates a new Turnkey encryption key. func New(userID string, organizationID string) (*Key, error) { if userID == "" { - return nil, fmt.Errorf("please supply a valid User UUID") + return nil, errors.New("please supply a valid User UUID") } if _, err := uuid.Parse(userID); err != nil { - return nil, fmt.Errorf("failed to parse user ID") + return nil, errors.New("failed to parse user ID") } if organizationID == "" { - return nil, fmt.Errorf("please supply a valid Organization UUID") + return nil, errors.New("please supply a valid Organization UUID") } if _, err := uuid.Parse(organizationID); err != nil { - return nil, fmt.Errorf("failed to parse organization ID") + return nil, errors.New("failed to parse organization ID") } _, privateKey, err := KemID.Scheme().GenerateKeyPair() diff --git a/pkg/store/local/local.go b/pkg/store/local/local.go index db14c9c..bc5e801 100644 --- a/pkg/store/local/local.go +++ b/pkg/store/local/local.go @@ -26,6 +26,8 @@ const ( publicKeyExtension = "public" privateKeyExtension = "private" metadataExtension = "meta" + fileOwnerRWGroupRAllR = 0o0644 + fileOwnerRW = 0o0600 ) // Store defines an api key Store using the local filesystem. @@ -210,7 +212,7 @@ func (s *Store[T, M]) Store(name string, keypair common.IKey[M]) error { return errors.Errorf("a keypair named %q already exists; exiting", name) } - if err = createKeyFile(s.PublicKeyFile(name), keypair.GetPublicKey(), 0o0644); err != nil { + if err = createKeyFile(s.PublicKeyFile(name), keypair.GetPublicKey(), fileOwnerRWGroupRAllR); err != nil { return errors.Wrap(err, "failed to store public key to file") } @@ -219,11 +221,11 @@ func (s *Store[T, M]) Store(name string, keypair common.IKey[M]) error { privateKeyData = fmt.Sprintf("%s:%s", privateKeyData, curve) } - if err = createKeyFile(s.PrivateKeyFile(name), privateKeyData, 0o0600); err != nil { + if err = createKeyFile(s.PrivateKeyFile(name), privateKeyData, fileOwnerRW); err != nil { return errors.Wrap(err, "failed to store private key to file") } - if err = s.createMetadataFile(s.MetadataFile(name), keypair.GetMetadata(), 0o0600); err != nil { + if err = s.createMetadataFile(s.MetadataFile(name), keypair.GetMetadata(), fileOwnerRW); err != nil { return errors.Wrap(err, "failed to store key metadata") } diff --git a/pkg/store/local/local_test.go b/pkg/store/local/local_test.go index a75b160..e35c113 100644 --- a/pkg/store/local/local_test.go +++ b/pkg/store/local/local_test.go @@ -14,11 +14,7 @@ import ( // MacOSX has $HOME set by default. func TestGetKeyDirPathMacOSX(t *testing.T) { - require.NoError(t, os.Setenv("HOME", "/home/dir")) - - defer func() { - require.NoError(t, os.Unsetenv("HOME")) - }() + t.Setenv("HOME", "/home/dir") // Need to unset this explicitly: the test runner has this set by default! originalValue := os.Getenv("XDG_CONFIG_HOME") @@ -36,17 +32,8 @@ func TestGetKeyDirPathMacOSX(t *testing.T) { // On UNIX, we expect XDG_CONFIG_HOME to be set. // If it's not set, we're back to a MacOSX-like system. func TestGetKeyDirPathUnix(t *testing.T) { - require.NoError(t, os.Setenv("XDG_CONFIG_HOME", "/special/dir")) - - defer func() { - require.NoError(t, os.Unsetenv("XDG_CONFIG_HOME")) - }() - - require.NoError(t, os.Setenv("HOME", "/home/dir")) - - defer func() { - require.NoError(t, os.Unsetenv("HOME")) - }() + t.Setenv("XDG_CONFIG_HOME", "/special/dir") + t.Setenv("HOME", "/home/dir") assert.Equal(t, "/special/dir/turnkey/keys", local.DefaultAPIKeysDir()) assert.Equal(t, "/special/dir/turnkey/encryption-keys", local.DefaultEncryptionKeysDir())