Skip to content

Commit

Permalink
chore: support for 2FA
Browse files Browse the repository at this point in the history
  • Loading branch information
jferrl committed Sep 12, 2023
1 parent d97ed88 commit fa1ec92
Show file tree
Hide file tree
Showing 6 changed files with 86 additions and 7 deletions.
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
19 changes: 19 additions & 0 deletions auth.go
Original file line number Diff line number Diff line change
@@ -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)
}
35 changes: 35 additions & 0 deletions auth_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
})
}
}
6 changes: 5 additions & 1 deletion http.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
7 changes: 2 additions & 5 deletions sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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)...))
Expand Down
2 changes: 1 addition & 1 deletion trading_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit fa1ec92

Please sign in to comment.