-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for api key auth, add user management CRUD operations (#49)
* Add support for api key auth, add user management CRUD operations * Add test coverage
- Loading branch information
1 parent
3b80652
commit 3c56554
Showing
48 changed files
with
1,299 additions
and
381 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,3 +4,4 @@ __debug_* | |
*secret* | ||
*.pem | ||
*Key.txt | ||
coverage.out |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | ||
}) | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
Oops, something went wrong.