~ashkeel/kilovolt-client-go

6c3ac741a6fb6736df306a2fb63cb569bbf91112 — Ash Keel 3 years ago 1a3f0d0 v6.0.0
Sync version to Kilovolt, add authentication
6 files changed, 98 insertions(+), 21 deletions(-)

M README.md
M client.go
M client_test.go
M cmd/kvcli/main.go
M go.mod
M go.sum
M README.md => README.md +3 -2
@@ 8,6 8,7 @@ Depending on what version of kilovolt you need to interface with, you'll need to

| Protocol | Go URL                                               |
| -------- | ---------------------------------------------------- |
| `v3`     | `go get github.com/strimertul/kilovolt-client-go`    |
| `v4`     | `go get github.com/strimertul/kilovolt-client-go/v2` |
| `v6`     | `go get github.com/strimertul/kilovolt-client-go/v6` |
| `v5`     | `go get github.com/strimertul/kilovolt-client-go/v3` |
| `v4`     | `go get github.com/strimertul/kilovolt-client-go/v2` |
| `v3`     | `go get github.com/strimertul/kilovolt-client-go`    |

M client.go => client.go +59 -10
@@ 1,6 1,9 @@
package kvclient

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/base64"
	"errors"
	"fmt"
	"math/rand"


@@ 14,11 17,10 @@ import (
	cmap "github.com/orcaman/concurrent-map"
	"github.com/sirupsen/logrus"

	kv "github.com/strimertul/kilovolt/v5"
	kv "github.com/strimertul/kilovolt/v6"
)

var (
	ErrNotAuthenticated     = errors.New("not authenticated")
	ErrSubscriptionNotFound = errors.New("subscription not found")
	ErrEmptyKey             = errors.New("key empty or unset")
)


@@ 41,8 43,9 @@ type Client struct {
}

type ClientOptions struct {
	Headers http.Header
	Logger  logrus.FieldLogger
	Headers  http.Header
	Password string
	Logger   logrus.FieldLogger
}

func NewClient(endpoint string, options ClientOptions) (*Client, error) {


@@ 62,14 65,60 @@ func NewClient(endpoint string, options ClientOptions) (*Client, error) {
	}

	err := client.ConnectToWebsocket()
	if err != nil {
		return nil, err
	}

	if options.Password != "" {
		err = client.Authenticate(options.Password)
		if err != nil {
			return nil, err
		}
	}

	return client, nil
}

func (s *Client) Authenticate(password string) error {
	res, err := s.makeRequest(kv.Request{
		CmdName: kv.CmdAuthRequest,
	})
	if err != nil {
		return err
	}

	data := res.Data.(map[string]interface{})

	// Decode challenge
	challengeBytes, err := base64.StdEncoding.DecodeString(data["challenge"].(string))
	if err != nil {
		return fmt.Errorf("failed to decode challenge: %w", err)
	}
	saltBytes, err := base64.StdEncoding.DecodeString(data["salt"].(string))
	if err != nil {
		return fmt.Errorf("failed to decode salt: %w", err)
	}

	return client, err
	// Create hash from password and challenge
	hash := hmac.New(sha256.New, append([]byte(password), saltBytes...))
	hash.Write(challengeBytes)
	hashBytes := hash.Sum(nil)

	// Send auth challenge
	_, err = s.makeRequest(kv.Request{
		CmdName: kv.CmdAuthChallenge,
		Data: map[string]interface{}{
			"hash": base64.StdEncoding.EncodeToString(hashBytes),
		},
	})
	return err
}

func (s *Client) Close() {
func (s *Client) Close() error {
	if s.ws != nil {
		s.ws.Close()
		return s.ws.Close()
	}
	return nil
}

func (s *Client) ConnectToWebsocket() error {


@@ 136,9 185,9 @@ func (s *Client) ConnectToWebsocket() error {
							}
						}
						// Deliver to prefix subscritpions
						for kv := range s.prefixsubs.IterBuffered() {
							if strings.HasPrefix(push.Key, kv.Key) {
								for _, chann := range kv.Val.([]chan KeyValuePair) {
						for pair := range s.prefixsubs.IterBuffered() {
							if strings.HasPrefix(push.Key, pair.Key) {
								for _, chann := range pair.Val.([]chan KeyValuePair) {
									chann <- KeyValuePair{push.Key, push.NewValue}
								}
							}

M client_test.go => client_test.go +31 -2
@@ 9,7 9,7 @@ import (
	"github.com/dgraph-io/badger/v3"
	"github.com/sirupsen/logrus"

	kv "github.com/strimertul/kilovolt/v5"
	kv "github.com/strimertul/kilovolt/v6"
)

func TestCommands(t *testing.T) {


@@ 267,6 267,35 @@ func TestKeyList(t *testing.T) {
	}
}

func TestAuthentication(t *testing.T) {
	log := logrus.New()
	log.Level = logrus.TraceLevel

	// Create hub with password
	const password = "testPassword"
	server, hub := createInMemoryKV(t, log)
	hub.SetOptions(kv.HubOptions{
		Password: password,
	})

	// Create client with password option
	client, err := NewClient(server.URL, ClientOptions{
		Logger:   log,
		Password: password,
	})
	if err != nil {
		t.Fatal("error creating kv client", err.Error())
	}

	// Couple test operations to test if auth actually went correctly
	if err = client.SetKey("test", "testvalue1234"); err != nil {
		t.Fatal("error modifying key", err.Error())
	}
	if _, err = client.GetKey("test"); err != nil {
		t.Fatal("error getting key")
	}
}

func createInMemoryKV(t *testing.T, log logrus.FieldLogger) (*httptest.Server, *kv.Hub) {
	// Open in-memory DB
	options := badger.DefaultOptions("").WithInMemory(true).WithLogger(log)


@@ 276,7 305,7 @@ func createInMemoryKV(t *testing.T, log logrus.FieldLogger) (*httptest.Server, *
	}

	// Create hub with in-mem DB
	hub, err := kv.NewHub(db, log)
	hub, err := kv.NewHub(db, kv.HubOptions{}, log)
	if err != nil {
		t.Fatal("hub initialization failed", err.Error())
	}

M cmd/kvcli/main.go => cmd/kvcli/main.go +1 -1
@@ 7,7 7,7 @@ import (
	"os"
	"strings"

	kvclient "github.com/strimertul/kilovolt-client-go/v3"
	kvclient "github.com/strimertul/kilovolt-client-go/v6"
)

func check(err error) {

M go.mod => go.mod +2 -2
@@ 1,4 1,4 @@
module github.com/strimertul/kilovolt-client-go/v3
module github.com/strimertul/kilovolt-client-go/v6

go 1.16



@@ 8,5 8,5 @@ require (
	github.com/json-iterator/go v1.1.11
	github.com/orcaman/concurrent-map v0.0.0-20210501183033-44dafcb38ecc
	github.com/sirupsen/logrus v1.8.1
	github.com/strimertul/kilovolt/v5 v5.0.1
	github.com/strimertul/kilovolt/v6 v6.0.0
)

M go.sum => go.sum +2 -4
@@ 90,10 90,8 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/strimertul/kilovolt/v4 v4.0.1 h1:81isohdSixVURO2+dZKKZBPw97HJmNN4/BXn6ADFoWM=
github.com/strimertul/kilovolt/v4 v4.0.1/go.mod h1:AO2ZFQtSB+AcjCw0RTkXjbM6XBAjhsXsrRq10BX95kw=
github.com/strimertul/kilovolt/v5 v5.0.1 h1:LHAVqb3SrXiew3loTpYuPdz16Nl8/aTReBYj56xwF7I=
github.com/strimertul/kilovolt/v5 v5.0.1/go.mod h1:HxfnnlEGhY6p+Im9U7pso07HEV+cXEsJH7uFTM7c6uE=
github.com/strimertul/kilovolt/v6 v6.0.0 h1:0vkg3Vc0ploLuCkoF9v30vMPrmTryf26socXoklkYLo=
github.com/strimertul/kilovolt/v6 v6.0.0/go.mod h1:O5Rwg8o66omRP4O3qInBKreW9jILZz2MEq4MuotzAXw=
github.com/twitchyliquid64/golang-asm v0.15.0/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=