Skip to content
This repository has been archived by the owner on Apr 2, 2024. It is now read-only.

Commit

Permalink
Updated authentication to allow non signed actions
Browse files Browse the repository at this point in the history
  • Loading branch information
icellan committed Feb 17, 2022
1 parent 7ce7bd3 commit 0dfa880
Show file tree
Hide file tree
Showing 3 changed files with 143 additions and 25 deletions.
57 changes: 35 additions & 22 deletions authentication.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,40 +36,48 @@ func (c *Client) AuthenticateRequest(ctx context.Context, req *http.Request, adm
}
}

// adminRequired will always force checking of a signature
if (requireSigning || adminRequired) && !signingDisabled {
if req.Body == nil {
return req, ErrMissingBody
}
defer func() {
_ = req.Body.Close()
}()
b, err := io.ReadAll(req.Body)
if err != nil {
return req, err
}
if req.Body == nil {
return req, ErrMissingBody
}
defer func() {
_ = req.Body.Close()
}()
b, err := io.ReadAll(req.Body)
if err != nil {
return req, err
}

req.Body = io.NopCloser(bytes.NewReader(b))
req.Body = io.NopCloser(bytes.NewReader(b))

authTime, _ := strconv.Atoi(req.Header.Get(AuthHeaderTime))
authData := &AuthPayload{
AuthHash: req.Header.Get(AuthHeaderHash),
AuthNonce: req.Header.Get(AuthHeaderNonce),
AuthTime: int64(authTime),
BodyContents: string(b),
Signature: req.Header.Get(AuthSignature),
}
authTime, _ := strconv.Atoi(req.Header.Get(AuthHeaderTime))
authData := &AuthPayload{
AuthHash: req.Header.Get(AuthHeaderHash),
AuthNonce: req.Header.Get(AuthHeaderNonce),
AuthTime: int64(authTime),
BodyContents: string(b),
Signature: req.Header.Get(AuthSignature),
}

// adminRequired will always force checking of a signature
if (requireSigning || adminRequired) && !signingDisabled {
if err = c.checkSignature(ctx, xPubOrAccessKey, authData); err != nil {
return req, err
}
req = setOnRequest(req, authSigned, true)
} else {
// check the signature and add to request, but do not fail if incorrect
err = c.checkSignature(ctx, xPubOrAccessKey, authData)
req = setOnRequest(req, authSigned, err == nil)

// Validate that the xPub is an HD key (length, validation)
// NOTE: you can not use an access key if signing is turned off
if _, err := utils.ValidateXPub(xPubOrAccessKey); err != nil {
if _, err = utils.ValidateXPub(xPubOrAccessKey); err != nil {
return req, err
}
}

req = setOnRequest(req, adminRequest, adminRequired)

// Set the data back onto the request
return setOnRequest(setOnRequest(req, xPubKey, xPubOrAccessKey), xPubHashKey, utils.Hash(xPubOrAccessKey)), nil
}
Expand Down Expand Up @@ -228,6 +236,11 @@ func GetXpubFromRequest(req *http.Request) (string, bool) {
return getFromRequest(req, xPubKey)
}

// IsAdminRequest gets the stored xPub from the request if found
func IsAdminRequest(req *http.Request) (bool, bool) {
return getBoolFromRequest(req, adminRequest)
}

// GetXpubHashFromRequest gets the stored xPub hash from the request if found
func GetXpubHashFromRequest(req *http.Request) (string, bool) {
return getFromRequest(req, xPubHashKey)
Expand Down
12 changes: 10 additions & 2 deletions authentication_internal.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,10 @@ type AuthPayload struct {
type paramRequestKey string

const (
xPubKey paramRequestKey = "xpub"
xPubHashKey paramRequestKey = "xpub_hash"
xPubKey paramRequestKey = "xpub"
xPubHashKey paramRequestKey = "xpub_hash"
adminRequest paramRequestKey = "auth_admin"
authSigned paramRequestKey = "auth_signed"
)

// createBodyHash will create the hash of the body, removing any carriage returns
Expand Down Expand Up @@ -116,3 +118,9 @@ func getFromRequest(req *http.Request, key paramRequestKey) (v string, ok bool)
v, ok = req.Context().Value(key).(string)
return
}

// getBoolFromRequest gets the stored bool value from the request if found
func getBoolFromRequest(req *http.Request, key paramRequestKey) (v bool, ok bool) {
v, ok = req.Context().Value(key).(bool)
return
}
99 changes: 98 additions & 1 deletion authentication_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func TestClient_AuthenticateRequest(t *testing.T) {
t.Parallel()

t.Run("valid xpub", func(t *testing.T) {
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "", nil)
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "", bytes.NewReader([]byte(`{}`)))
require.NoError(t, err)
require.NotNil(t, req)

Expand All @@ -60,6 +60,64 @@ func TestClient_AuthenticateRequest(t *testing.T) {
assert.Equal(t, true, ok)
})

t.Run("xpub - valid signature", func(t *testing.T) {
key, err := bitcoin.GenerateHDKey(bitcoin.SecureSeedLength)
require.NoError(t, err)
require.NotNil(t, key)

var req *http.Request
req, err = http.NewRequestWithContext(context.Background(), http.MethodGet, "", bytes.NewReader([]byte(`{}`)))
require.NoError(t, err)
require.NotNil(t, req)

var authData *AuthPayload
authData, err = createSignature(key, `{}`)
require.NoError(t, err)
require.NotNil(t, authData)

err = SetSignature(&req.Header, key, `{}`)
require.NoError(t, err)

_, client, deferMe := CreateTestSQLiteClient(t, false, false)
defer deferMe()

req, err = client.AuthenticateRequest(
context.Background(), req, []string{authData.xPub}, false, true, false,
)
require.NoError(t, err)
require.NotNil(t, req)
assert.Equal(t, true, req.Context().Value(authSigned))
})

t.Run("xpub - valid signature - not required", func(t *testing.T) {
key, err := bitcoin.GenerateHDKey(bitcoin.SecureSeedLength)
require.NoError(t, err)
require.NotNil(t, key)

var req *http.Request
req, err = http.NewRequestWithContext(context.Background(), http.MethodGet, "", bytes.NewReader([]byte(`{}`)))
require.NoError(t, err)
require.NotNil(t, req)

var authData *AuthPayload
authData, err = createSignature(key, `{}`)
require.NoError(t, err)
require.NotNil(t, authData)

err = SetSignature(&req.Header, key, `{}`)
require.NoError(t, err)

_, client, deferMe := CreateTestSQLiteClient(t, false, false)
defer deferMe()

req, err = client.AuthenticateRequest(
context.Background(), req, []string{authData.xPub}, false, false, false,
)
require.NoError(t, err)
require.NotNil(t, req)
assert.Equal(t, true, req.Context().Value(authSigned))
})

t.Run("error - admin required", func(t *testing.T) {
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "", nil)
require.NoError(t, err)
Expand Down Expand Up @@ -468,6 +526,45 @@ func TestGetXpubFromRequest(t *testing.T) {
})
}

// TestIsAdminRequest will test the method IsAdminRequest()
func TestIsAdminRequest(t *testing.T) {
t.Parallel()

t.Run("no value", func(t *testing.T) {
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "", nil)
require.NoError(t, err)
require.NotNil(t, req)

isAdmin, ok := IsAdminRequest(req)
assert.Equal(t, false, ok)
assert.Equal(t, false, isAdmin)
})

t.Run("false value", func(t *testing.T) {
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "", nil)
require.NoError(t, err)
require.NotNil(t, req)

req = setOnRequest(req, adminRequest, false)

isAdmin, ok := IsAdminRequest(req)
assert.Equal(t, true, ok)
assert.Equal(t, false, isAdmin)
})

t.Run("valid value", func(t *testing.T) {
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "", nil)
require.NoError(t, err)
require.NotNil(t, req)

req = setOnRequest(req, adminRequest, true)

isAdmin, ok := IsAdminRequest(req)
assert.Equal(t, true, ok)
assert.Equal(t, true, isAdmin)
})
}

// TestGetXpubHashFromRequest will test the method GetXpubHashFromRequest()
func TestGetXpubHashFromRequest(t *testing.T) {
t.Parallel()
Expand Down

0 comments on commit 0dfa880

Please sign in to comment.