Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable optional headers to be passed into the authentication parameters #68

Merged
merged 4 commits into from
Oct 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 111 additions & 7 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
package sdk

import (
"net/http"

"github.com/go-openapi/runtime"
httptransport "github.com/go-openapi/runtime/client"
"github.com/go-openapi/strfmt"
"github.com/pkg/errors"

Expand All @@ -11,22 +14,123 @@ 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
}

// 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) OptionFunc {
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) OptionFunc {
return func(c *config) error {
c.registry = registry

return nil
}
}

// WithTransportConfig sets the TransportConfig used for this API client.
func WithTransportConfig(transportConfig client.TransportConfig) OptionFunc {
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) OptionFunc {
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) OptionFunc {
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 ...OptionFunc) (*Client, error) {
c := &config{
clientVersion: DefaultClientVersion,
transportConfig: client.DefaultTransportConfig(),
}

for _, o := range options {
err := o(c)
if err != nil {
return nil, err
}
}

// 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)
Ulexus marked this conversation as resolved.
Show resolved Hide resolved

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)
}
Expand Down
2 changes: 1 addition & 1 deletion examples/signing/sign_raw_payload/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
2 changes: 1 addition & 1 deletion examples/signing/sign_transaction/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
2 changes: 1 addition & 1 deletion examples/wallets/create_wallet/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
2 changes: 1 addition & 1 deletion examples/wallets/create_wallet_accounts/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
2 changes: 1 addition & 1 deletion examples/whoami/whoami.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/apikey/apikey.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down
8 changes: 6 additions & 2 deletions pkg/apikey/ecdsa.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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))
}

Expand All @@ -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()
Expand Down
9 changes: 4 additions & 5 deletions pkg/encryptionkey/encryptionkey.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ package encryptionkey
import (
"encoding/hex"
"encoding/json"
"fmt"
"os"

"github.com/cloudflare/circl/hpke"
Expand Down Expand Up @@ -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()
Expand Down
9 changes: 5 additions & 4 deletions pkg/store/local/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -123,7 +125,6 @@ func getConfigDir() string {
var err error

cfgDir, err = os.UserConfigDir()

if err != nil {
shouldUseHomeDir = true
}
Expand Down Expand Up @@ -211,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")
}

Expand All @@ -220,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")
}

Expand Down
19 changes: 3 additions & 16 deletions pkg/store/local/local_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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())
Expand Down
Loading