diff --git a/README.md b/README.md index 84c1cf8..8035872 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,30 @@ Authenticated requests must include both API-Key and API-Sign HTTP headers, and Nonce must be an always increasing, unsigned 64-bit integer, for each request that is made with a particular API key. While a simple counter would provide a valid nonce, a more usual method of generating a valid nonce is to use e.g. a UNIX timestamp in milliseconds. +### 2FA + +If two-factor authentication (2FA) is enabled for the API key and action in question, the one time password must be specified in the payload's otp value. + +In order to set OTP in the request, you can use the `ContextWithOtp` function. Internally, OTP value is stored in the context and then used in the request. + + For example: + +```go +package main + +import ( + "context" + + "github.com/jferrl/go-kraken" +) + +func main() { + ctx := context.Background() + + ctxWithOpt := kraken.ContextWithOtp(ctx, "123456") +} +``` + ###  API-Key The "API-Key" header should contain your API key. diff --git a/auth.go b/auth.go index 6b7cb58..119188d 100644 --- a/auth.go +++ b/auth.go @@ -1,7 +1,26 @@ package kraken +import "context" + // Secret represents a Kraken API secret. type Secret []byte // APIKey represents a Kraken API key. type APIKey string + +// Otp defines a single-use password. +// It is used as a second authentication factor. +type Otp string + +type otpKey string + +// OtpFromContext returns the one-time password from the context. +func OtpFromContext(ctx context.Context) Otp { + otp, _ := ctx.Value(otpKey("otp")).(Otp) + return otp +} + +// ContextWithOtp adds a one-time password to the context. +func ContextWithOtp(ctx context.Context, otp Otp) context.Context { + return context.WithValue(ctx, otpKey("otp"), otp) +} diff --git a/auth_test.go b/auth_test.go new file mode 100644 index 0000000..1af2c33 --- /dev/null +++ b/auth_test.go @@ -0,0 +1,35 @@ +package kraken + +import ( + "context" + "testing" +) + +func TestOtpFromContext(t *testing.T) { + type args struct { + ctx context.Context + } + tests := []struct { + name string + args args + want Otp + }{ + { + name: "empty context", + args: args{ctx: context.Background()}, + want: "", + }, + { + name: "context with otp", + args: args{ctx: ContextWithOtp(context.Background(), "123456")}, + want: "123456", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := OtpFromContext(tt.args.ctx); got != tt.want { + t.Errorf("OtpFromContext() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/http.go b/http.go index 1de0c4f..196d2c4 100644 --- a/http.go +++ b/http.go @@ -59,7 +59,11 @@ func (c *Client) newPublicRequest(ctx context.Context, method string, path strin func (c *Client) newPrivateRequest(ctx context.Context, method string, path string, body url.Values) (*http.Request, error) { reqURL := c.buildPrivateURL(path) - body.Set(NonceKey, fmt.Sprintf("%d", time.Now().UnixNano())) + if otp := OtpFromContext(ctx); otp != "" { + body.Set("otp", string(otp)) + } + + body.Set(nonceKey, fmt.Sprintf("%d", time.Now().UnixNano())) req, err := http.NewRequestWithContext(ctx, method, reqURL.String(), strings.NewReader(body.Encode())) if err != nil { diff --git a/sign.go b/sign.go index aed291d..4bd4618 100644 --- a/sign.go +++ b/sign.go @@ -8,10 +8,7 @@ import ( "net/url" ) -// NonceKey is the key used for the nonce value. A nonce simply stands for a Number used ONCE. -// It’s a unique token used to add a layer of security to the request and also to validate the intent -// of a user initiated action. -const NonceKey = "nonce" +const nonceKey = "nonce" // Signer represents a Kraken API signature. type Signer struct { @@ -34,7 +31,7 @@ func NewSigner(s string) *Signer { // Docs: https://www.kraken.com/help/api#general-usage for more information func (s *Signer) Sign(v url.Values, path string) string { sha := sha256.New() - sha.Write([]byte(v.Get(NonceKey) + v.Encode())) + sha.Write([]byte(v.Get(nonceKey) + v.Encode())) mac := hmac.New(sha512.New, s.Secret) mac.Write(append([]byte(path), sha.Sum(nil)...)) diff --git a/trading_test.go b/trading_test.go index 58032d3..8444b6c 100644 --- a/trading_test.go +++ b/trading_test.go @@ -10,7 +10,7 @@ import ( ) func TestTrading_AddOrder(t *testing.T) { - ctx := context.Background() + ctx := ContextWithOtp(context.Background(), "123456") type fields struct { apiMock *httptest.Server