Skip to content

Commit

Permalink
Add support for api key auth, add user management CRUD operations (#49)
Browse files Browse the repository at this point in the history
* Add support for api key auth, add user management CRUD operations

* Add test coverage
  • Loading branch information
Luke-Rogerson authored Dec 7, 2023
1 parent 3b80652 commit 3c56554
Show file tree
Hide file tree
Showing 48 changed files with 1,299 additions and 381 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ __debug_*
*secret*
*.pem
*Key.txt
coverage.out
7 changes: 6 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,9 @@ test:

lint:
@echo "Running linter..."
golangci-lint run
golangci-lint run

coverage:
@echo "Generating test coverage report..."
@go test -coverprofile=coverage.out ./...
@go tool cover -html=coverage.out
6 changes: 3 additions & 3 deletions cmd/maker-mock/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
"github.com/shopspring/decimal"
)

const pubKey = "MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEhqhj8rWPzkghzOZTUCOo/sdkE53sU1coVhaYskKGKrgiUF7lsSmxy46i3j8w7E7KMTfYBpCGAFYiWWARa0KQwg=="
const mockApiKey = "abcdef12345"
const depthSize = 5

var HOST = "localhost"
Expand Down Expand Up @@ -71,7 +71,7 @@ func cancelAllOrders() {
return
}

req.Header.Add("X-Public-Key", pubKey)
req.Header.Add("X-API-Key", fmt.Sprintf("Bearer %s", mockApiKey))

resp, err := client.Do(req)
if err != nil {
Expand Down Expand Up @@ -106,7 +106,7 @@ func placeOrder(side string, price, size decimal.Decimal) {
log.Fatalf("error creating request: %v", err)
}

req.Header.Add("X-Public-Key", pubKey)
req.Header.Add("X-API-Key", fmt.Sprintf("Bearer %s", mockApiKey))

res, err := client.Do(req)
//fmt.Printf("res is ------->: %#v\n", res)
Expand Down
9 changes: 8 additions & 1 deletion cmd/order-book/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

"github.com/orbs-network/order-book/data/redisrepo"
"github.com/orbs-network/order-book/service"
"github.com/orbs-network/order-book/serviceuser"
"github.com/orbs-network/order-book/transport/rest"
)

Expand Down Expand Up @@ -50,12 +51,18 @@ func setup() {
log.Fatalf("error creating service: %v", err)
}

userSvc, err := serviceuser.New(repository)
if err != nil {
log.Fatalf("error creating user service: %v", err)
}

router := mux.NewRouter()
handler, err := rest.NewHandler(service, router)
if err != nil {
log.Fatalf("error creating handler: %v", err)
}
handler.Init()

handler.Init(userSvc.GetUserByApiKey)

server := rest.NewHTTPServer(":"+port, handler.Router)
server.StartServer()
Expand Down
29 changes: 29 additions & 0 deletions data/redisrepo/auction_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,5 +60,34 @@ func TestRedisRepository_GetAuction(t *testing.T) {
}

func TestRedisRepository_RemoveAuction(t *testing.T) {
auctionID := uuid.MustParse("a777273e-12de-4acc-a4f8-de7fb5b86e37")

t.Run("should remove auction", func(t *testing.T) {

db, mock := redismock.NewClientMock()

repo := &redisRepository{
client: db,
}

mock.ExpectDel(CreateAuctionKey(auctionID)).SetVal(1)

err := repo.RemoveAuction(ctx, auctionID)

assert.NoError(t, err)
})

t.Run("should return error in case of a Redis error", func(t *testing.T) {
db, mock := redismock.NewClientMock()

repo := &redisRepository{
client: db,
}

mock.ExpectDel(CreateAuctionKey(auctionID)).SetErr(assert.AnError)

err := repo.RemoveAuction(ctx, auctionID)

assert.Equal(t, assert.AnError, err)
})
}
47 changes: 47 additions & 0 deletions data/redisrepo/create_user.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package redisrepo

import (
"context"
"fmt"

"github.com/orbs-network/order-book/models"
"github.com/orbs-network/order-book/utils/logger"
"github.com/orbs-network/order-book/utils/logger/logctx"
)

// CreateUser adds 2 entries to Redis:
// 1. User data indexed by API key
// 2. User data indexed by userId
func (r *redisRepository) CreateUser(ctx context.Context, user models.User) (models.User, error) {

apiKeyKey := CreateUserApiKeyKey(user.ApiKey)
userIdKey := CreateUserIdKey(user.Id)

if exists, err := r.client.Exists(ctx, apiKeyKey, userIdKey).Result(); err != nil {
logctx.Error(ctx, "unexpected error checking if user exists", logger.String("userId", user.Id.String()), logger.Error(err))
return models.User{}, fmt.Errorf("unexpected error checking if user exists: %w", err)
} else if exists > 0 {
logctx.Warn(ctx, "user already exists", logger.String("userId", user.Id.String()))
return models.User{}, models.ErrUserAlreadyExists
}

fields := map[string]interface{}{
"id": user.Id.String(),
"type": user.Type.String(),
"pubKey": user.PubKey,
"apiKey": user.ApiKey,
}

transaction := r.client.TxPipeline()
transaction.HMSet(ctx, apiKeyKey, fields)
transaction.HMSet(ctx, userIdKey, fields)
_, err := transaction.Exec(ctx)

if err != nil {
logctx.Error(ctx, "unexpected error creating user", logger.String("userId", user.Id.String()))
return models.User{}, fmt.Errorf("unexpected error creating user: %w", err)
}

logctx.Info(ctx, "user created", logger.String("userId", user.Id.String()))
return user, nil
}
126 changes: 126 additions & 0 deletions data/redisrepo/create_user_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package redisrepo

import (
"testing"

"github.com/go-redis/redismock/v9"
"github.com/orbs-network/order-book/mocks"
"github.com/orbs-network/order-book/models"
"github.com/stretchr/testify/assert"
)

func TestRedisRepository_CreateUser(t *testing.T) {

fields := map[string]interface{}{
"id": mocks.UserId.String(),
"type": mocks.UserType.String(),
"pubKey": mocks.PubKey,
"apiKey": mocks.ApiKey,
}

t.Run("should successfully create user and return user instance on success", func(t *testing.T) {
db, mock := redismock.NewClientMock()

repo := &redisRepository{
client: db,
}

userApiKey := CreateUserApiKeyKey(mocks.ApiKey)
userIdKey := CreateUserIdKey(mocks.UserId)

mock.ExpectExists(userApiKey, userIdKey).SetVal(0)
mock.ExpectTxPipeline()
mock.ExpectHMSet(userApiKey, fields).SetVal(true)
mock.ExpectHMSet(userIdKey, fields).SetVal(true)
mock.ExpectTxPipelineExec()

user, err := repo.CreateUser(ctx, models.User{
Id: mocks.UserId,
PubKey: mocks.PubKey,
Type: mocks.UserType,
ApiKey: mocks.ApiKey,
})

assert.Equal(t, models.User{
Id: mocks.UserId,
PubKey: mocks.PubKey,
Type: mocks.UserType,
ApiKey: mocks.ApiKey,
}, user)
assert.NoError(t, err)

})

t.Run("should return `ErrUserAlreadyExists` error if user already exists", func(t *testing.T) {
db, mock := redismock.NewClientMock()

repo := &redisRepository{
client: db,
}

userApiKey := CreateUserApiKeyKey(mocks.ApiKey)
userIdKey := CreateUserIdKey(mocks.UserId)

mock.ExpectExists(userApiKey, userIdKey).SetVal(1)

user, err := repo.CreateUser(ctx, models.User{
Id: mocks.UserId,
PubKey: mocks.PubKey,
Type: mocks.UserType,
ApiKey: mocks.ApiKey,
})

assert.Equal(t, models.User{}, user)
assert.ErrorIs(t, err, models.ErrUserAlreadyExists)
})

t.Run("should return error on unexpected exists error", func(t *testing.T) {
db, mock := redismock.NewClientMock()

repo := &redisRepository{
client: db,
}

userApiKey := CreateUserApiKeyKey(mocks.ApiKey)
userIdKey := CreateUserIdKey(mocks.UserId)

mock.ExpectExists(userApiKey, userIdKey).SetErr(assert.AnError)

user, err := repo.CreateUser(ctx, models.User{
Id: mocks.UserId,
PubKey: mocks.PubKey,
Type: mocks.UserType,
ApiKey: mocks.ApiKey,
})

assert.Equal(t, models.User{}, user)
assert.ErrorContains(t, err, "unexpected error checking if user exists")
})

t.Run("should return error on unexpected create user error", func(t *testing.T) {
db, mock := redismock.NewClientMock()

repo := &redisRepository{
client: db,
}

userApiKey := CreateUserApiKeyKey(mocks.ApiKey)
userIdKey := CreateUserIdKey(mocks.UserId)

mock.ExpectExists(userApiKey, userIdKey).SetVal(0)
mock.ExpectTxPipeline()
mock.ExpectHMSet(userApiKey, fields).SetErr(assert.AnError)
mock.ExpectTxPipelineExec()

user, err := repo.CreateUser(ctx, models.User{
Id: mocks.UserId,
PubKey: mocks.PubKey,
Type: mocks.UserType,
ApiKey: mocks.ApiKey,
})

assert.Equal(t, models.User{}, user)
assert.ErrorContains(t, err, "unexpected error creating user")
})

}
54 changes: 54 additions & 0 deletions data/redisrepo/get_user_by_api_key.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package redisrepo

import (
"context"
"fmt"

"github.com/google/uuid"
"github.com/orbs-network/order-book/models"
"github.com/orbs-network/order-book/utils/logger"
"github.com/orbs-network/order-book/utils/logger/logctx"
)

// GetUserByApiKey returns a user by their apiKey
func (r *redisRepository) GetUserByApiKey(ctx context.Context, apiKey string) (*models.User, error) {

key := CreateUserApiKeyKey(apiKey)

fields, err := r.client.HGetAll(ctx, key).Result()
if err != nil {
logctx.Error(ctx, "unexpected error getting user by api key", logger.Error(err))
return nil, fmt.Errorf("unexpected error getting user by api key: %w", err)
}

if len(fields) == 0 {
logctx.Warn(ctx, "user not found by api key")
return nil, models.ErrUserNotFound
}

userId, err := uuid.Parse(fields["id"])
if err != nil {
logctx.Error(ctx, "unexpected error parsing user id", logger.Error(err), logger.String("userId", fields["id"]))
return nil, fmt.Errorf("unexpected error parsing user id: %w", err)
}

userType, err := models.StrToUserType(fields["type"])
if err != nil {
logctx.Error(ctx, "unexpected error parsing user type", logger.Error(err), logger.String("userId", userId.String()), logger.String("type", fields["type"]))
return nil, fmt.Errorf("unexpected error parsing user type: %w", err)
}

if fields["apiKey"] != apiKey {
logctx.Error(ctx, "api key mismatch", logger.String("userId", userId.String()))
return nil, fmt.Errorf("api key mismatch")
}

logctx.Info(ctx, "user found", logger.String("userId", userId.String()))

return &models.User{
Id: userId,
PubKey: fields["pubKey"],
Type: userType,
ApiKey: fields["apiKey"],
}, nil
}
Loading

0 comments on commit 3c56554

Please sign in to comment.