Skip to content

Commit

Permalink
feat(SPV-912): handle invalid configuration (#270)
Browse files Browse the repository at this point in the history
  • Loading branch information
chris-4chain authored Oct 3, 2024
1 parent e6d5ab5 commit 30361c8
Show file tree
Hide file tree
Showing 25 changed files with 203 additions and 102 deletions.
3 changes: 2 additions & 1 deletion access_keys_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ func TestAccessKeys(t *testing.T) {
}))
defer server.Close()

client := NewWithAccessKey(server.URL, fixtures.AccessKeyString)
client, err := NewWithAccessKey(server.URL, fixtures.AccessKeyString)
require.NoError(t, err)
require.NotNil(t, client.accessKey)

t.Run("GetAccessKey", func(t *testing.T) {
Expand Down
3 changes: 2 additions & 1 deletion admin_contacts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ func TestAdminContactActions(t *testing.T) {
}))
defer server.Close()

client := NewWithAdminKey(server.URL, fixtures.XPrivString)
client, err := NewWithAdminKey(server.URL, fixtures.XPrivString)
require.NoError(t, err)
require.NotNil(t, client.adminXPriv)

t.Run("AdminGetContacts", func(t *testing.T) {
Expand Down
56 changes: 31 additions & 25 deletions client_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,63 +7,81 @@ import (

bip32 "github.com/bitcoin-sv/go-sdk/compat/bip32"
ec "github.com/bitcoin-sv/go-sdk/primitives/ec"

"github.com/pkg/errors"
)

// configurator is the interface for configuring WalletClient
type configurator interface {
Configure(c *WalletClient)
Configure(c *WalletClient) error
}

// xPrivConf sets the xPrivString field of a WalletClient
type xPrivConf struct {
XPrivString string
}

func (w *xPrivConf) Configure(c *WalletClient) {
func (w *xPrivConf) Configure(c *WalletClient) error {
var err error
if c.xPriv, err = bip32.GenerateHDKeyFromString(w.XPrivString); err != nil {
c.xPriv = nil
return ErrInvalidXpriv.Wrap(err)
}
return nil
}

// xPubConf sets the xPubString on the client
type xPubConf struct {
XPubString string
}

func (w *xPubConf) Configure(c *WalletClient) {
func (w *xPubConf) Configure(c *WalletClient) error {
var err error
if c.xPub, err = bip32.GetHDKeyFromExtendedPublicKey(w.XPubString); err != nil {
c.xPub = nil
return ErrInvalidXpub.Wrap(err)
}

return nil
}

// accessKeyConf sets the accessKeyString on the client
type accessKeyConf struct {
AccessKeyString string
}

func (w *accessKeyConf) Configure(c *WalletClient) {
func (w *accessKeyConf) Configure(c *WalletClient) error {
var err error
if c.accessKey, err = w.initializeAccessKey(); err != nil {
c.accessKey = nil
return err
}
return nil
}

func (w *accessKeyConf) initializeAccessKey() (*ec.PrivateKey, error) {
var errPriv, errPub error
privateKey, errPriv := ec.PrivateKeyFromWif(w.AccessKeyString)
if errPriv != nil {
privateKey, errPub = ec.PrivateKeyFromHex(w.AccessKeyString)
if privateKey == nil {
return nil, ErrInvalidAccessKey.Wrap(errPriv).Wrap(errPub)
}
}

return privateKey, nil
}

// adminKeyConf sets the admin key for creating new xpubs
type adminKeyConf struct {
AdminKeyString string
}

func (w *adminKeyConf) Configure(c *WalletClient) {
func (w *adminKeyConf) Configure(c *WalletClient) error {
var err error
c.adminXPriv, err = bip32.GenerateHDKeyFromString(w.AdminKeyString)
if err != nil {
c.adminXPriv = nil
return ErrInvalidAdminKey.Wrap(err)
}
return nil
}

// httpConf sets the URL and httpConf client of a WalletClient
Expand All @@ -72,13 +90,11 @@ type httpConf struct {
HTTPClient *http.Client
}

func (w *httpConf) Configure(c *WalletClient) {
func (w *httpConf) Configure(c *WalletClient) error {
// Ensure the ServerURL ends with a clean base URL
baseURL, err := validateAndCleanURL(w.ServerURL)
if err != nil {
// Handle the error appropriately
fmt.Println("Invalid URL provided:", err)
return
return ErrInvalidServerURL.Wrap(err)
}

const basePath = "/v1"
Expand All @@ -90,27 +106,17 @@ func (w *httpConf) Configure(c *WalletClient) {
} else {
c.httpClient = http.DefaultClient
}
return nil
}

// signRequest configures whether to sign HTTP requests
type signRequest struct {
Sign bool
}

func (w *signRequest) Configure(c *WalletClient) {
func (w *signRequest) Configure(c *WalletClient) error {
c.signRequest = w.Sign
}

func (w *accessKeyConf) initializeAccessKey() (*ec.PrivateKey, error) {
privateKey, err := ec.PrivateKeyFromWif(w.AccessKeyString)
if err != nil {
privateKey, _ = ec.PrivateKeyFromHex(w.AccessKeyString)
if privateKey == nil {
return nil, errors.New("failed to decode access key")
}
}

return privateKey, nil
return nil
}

// validateAndCleanURL ensures that the provided URL is valid, and strips it down to just the base URL.
Expand Down
3 changes: 2 additions & 1 deletion contacts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ func TestContactActionsRouting(t *testing.T) {
}))
defer server.Close()

client := NewWithAccessKey(server.URL, fixtures.AccessKeyString)
client, err := NewWithAccessKey(server.URL, fixtures.AccessKeyString)
require.NoError(t, err)
require.NotNil(t, client.accessKey)

t.Run("RejectContact", func(t *testing.T) {
Expand Down
3 changes: 2 additions & 1 deletion destinations_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ func TestDestinations(t *testing.T) {
}
}))
defer server.Close()
client := NewWithAccessKey(server.URL, fixtures.AccessKeyString)
client, err := NewWithAccessKey(server.URL, fixtures.AccessKeyString)
require.NoError(t, err)
require.NotNil(t, client.accessKey)

t.Run("GetDestinationByID", func(t *testing.T) {
Expand Down
18 changes: 18 additions & 0 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,24 @@ var ErrAdminKey = models.SPVError{Message: "an admin key must be set to be able
// ErrMissingXpriv is when xpriv is missing
var ErrMissingXpriv = models.SPVError{Message: "xpriv is missing", StatusCode: 401, Code: "error-unauthorized-xpriv-missing"}

// ErrInvalidXpriv is when xpriv is invalid
var ErrInvalidXpriv = models.SPVError{Message: "xpriv is invalid", StatusCode: 401, Code: "error-unauthorized-xpriv-invalid"}

// ErrInvalidXpub is when xpub is invalid
var ErrInvalidXpub = models.SPVError{Message: "xpub is invalid", StatusCode: 401, Code: "error-unauthorized-xpub-invalid"}

// ErrInvalidAccessKey is when access key is invalid
var ErrInvalidAccessKey = models.SPVError{Message: "access key is invalid", StatusCode: 401, Code: "error-unauthorized-access-key-invalid"}

// ErrInvalidAdminKey is when admin key is invalid
var ErrInvalidAdminKey = models.SPVError{Message: "admin key is invalid", StatusCode: 401, Code: "error-unauthorized-admin-key-invalid"}

// ErrInvalidServerURL is when server url is invalid
var ErrInvalidServerURL = models.SPVError{Message: "server url is invalid", StatusCode: 401, Code: "error-unauthorized-server-url-invalid"}

// ErrCreateClient is when client creation fails
var ErrCreateClient = models.SPVError{Message: "failed to create client", StatusCode: 500, Code: "error-create-client-failed"}

// ErrMissingKey is when neither xPriv nor adminXPriv is provided
var ErrMissingKey = models.SPVError{Message: "neither xPriv nor adminXPriv is provided", StatusCode: 404, Code: "error-shared-config-key-missing"}

Expand Down
8 changes: 6 additions & 2 deletions examples/admin_add_user/admin_add_user.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,16 @@ func main() {

server := "http://localhost:3003/v1"

adminClient := walletclient.NewWithAdminKey(server, examples.ExampleAdminKey)
adminClient, err := walletclient.NewWithAdminKey(server, examples.ExampleAdminKey)
if err != nil {
examples.GetFullErrorMessage(err)
os.Exit(1)
}
ctx := context.Background()

metadata := map[string]any{"some_metadata": "example"}

err := adminClient.AdminNewXpub(ctx, examples.ExampleXPub, metadata)
err = adminClient.AdminNewXpub(ctx, examples.ExampleXPub, metadata)
if err != nil {
examples.GetFullErrorMessage(err)
os.Exit(1)
Expand Down
8 changes: 6 additions & 2 deletions examples/admin_remove_user/admin_remove_user.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,14 @@ func main() {

const server = "http://localhost:3003/v1"

adminClient := walletclient.NewWithAdminKey(server, examples.ExampleAdminKey)
adminClient, err := walletclient.NewWithAdminKey(server, examples.ExampleAdminKey)
if err != nil {
examples.GetFullErrorMessage(err)
os.Exit(1)
}
ctx := context.Background()

err := adminClient.AdminDeletePaymail(ctx, examples.ExamplePaymail)
err = adminClient.AdminDeletePaymail(ctx, examples.ExamplePaymail)
if err != nil {
examples.GetFullErrorMessage(err)
os.Exit(1)
Expand Down
6 changes: 5 additions & 1 deletion examples/create_transaction/create_transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@ func main() {

const server = "http://localhost:3003/v1"

client := walletclient.NewWithXPriv(server, examples.ExampleXPriv)
client, err := walletclient.NewWithXPriv(server, examples.ExampleXPriv)
if err != nil {
examples.GetFullErrorMessage(err)
os.Exit(1)
}
ctx := context.Background()

recipient := walletclient.Recipients{To: "alice@example.com", Satoshis: 1}
Expand Down
10 changes: 7 additions & 3 deletions examples/generate_totp/generate_totp.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@ func main() {
const digits = 4
const period = 1200 // 20 minutes

client := walletclient.NewWithXPriv(server, aliceXPriv)
client, err := walletclient.NewWithXPriv(server, aliceXPriv)
if err != nil {
examples.GetFullErrorMessage(err)
os.Exit(1)
}

mockContact := &models.Contact{
PubKey: bobPKI,
Expand All @@ -30,14 +34,14 @@ func main() {

totpCode, err := client.GenerateTotpForContact(mockContact, period, digits)
if err != nil {
fmt.Println(err)
examples.GetFullErrorMessage(err)
os.Exit(1)
}
fmt.Println("TOTP code from Alice to Bob: ", totpCode)

valid, err := client.ValidateTotpForContact(mockContact, totpCode, mockContact.Paymail, period, digits)
if err != nil {
fmt.Println(err)
examples.GetFullErrorMessage(err)
os.Exit(1)
}
fmt.Println("Is TOTP code valid: ", valid)
Expand Down
6 changes: 5 additions & 1 deletion examples/get_balance/get_balance.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@ func main() {

const server = "http://localhost:3003/v1"

client := walletclient.NewWithXPriv(server, examples.ExampleXPriv)
client, err := walletclient.NewWithXPriv(server, examples.ExampleXPriv)
if err != nil {
examples.GetFullErrorMessage(err)
os.Exit(1)
}
ctx := context.Background()

xpubInfo, err := client.GetXPub(ctx)
Expand Down
2 changes: 1 addition & 1 deletion examples/go.mod

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions examples/go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 5 additions & 2 deletions examples/handle_exceptions/handle_exceptions.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,17 @@ func main() {

const server = "http://localhost:3003/v1"

client := walletclient.NewWithXPub(server, examples.ExampleAdminKey)
client, err := walletclient.NewWithXPub(server, examples.ExampleAdminKey)
if err != nil {
examples.GetFullErrorMessage(err)
os.Exit(1)
}
ctx := context.Background()

fmt.Println("Client created")

status, err := client.AdminGetStatus(ctx)
if err != nil {
fmt.Println("Error: ", err)
examples.GetFullErrorMessage(err)
os.Exit(1)
}
Expand Down
6 changes: 5 additions & 1 deletion examples/list_transactions/list_transactions.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@ func main() {

const server = "http://localhost:3003/v1"

client := walletclient.NewWithXPriv(server, examples.ExampleXPriv)
client, err := walletclient.NewWithXPriv(server, examples.ExampleXPriv)
if err != nil {
examples.GetFullErrorMessage(err)
os.Exit(1)
}
ctx := context.Background()

metadata := map[string]any{}
Expand Down
6 changes: 5 additions & 1 deletion examples/send_op_return/send_op_return.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@ func main() {

const server = "http://localhost:3003/v1"

client := walletclient.NewWithXPriv(server, examples.ExampleXPriv)
client, err := walletclient.NewWithXPriv(server, examples.ExampleXPriv)
if err != nil {
examples.GetFullErrorMessage(err)
os.Exit(1)
}
ctx := context.Background()

metadata := map[string]any{}
Expand Down
Loading

0 comments on commit 30361c8

Please sign in to comment.