Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More accurate errors for user not found #294

Merged
merged 1 commit into from
Jan 31, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 55 additions & 19 deletions storage/mongo/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package mongo
import (
"context"
"encoding/json"
"errors"
"log"
"strings"
"time"
Expand Down Expand Up @@ -73,7 +74,7 @@ type UserStorage struct {

// UserByID returns user by its ID.
func (us *UserStorage) UserByID(id string) (model.User, error) {
hexID, err := primitive.ObjectIDFromHex(id)
_, err := primitive.ObjectIDFromHex(id)
if err != nil {
return model.User{}, err
}
Expand All @@ -82,8 +83,12 @@ func (us *UserStorage) UserByID(id string) (model.User, error) {
defer cancel()

var u model.User
if err := us.coll.FindOne(ctx, bson.M{"_id": hexID.Hex()}).Decode(&u); err != nil {
return model.User{}, model.ErrUserNotFound
if err := us.coll.FindOne(ctx, bson.D{{Key: "_id", Value: id}}).Decode(&u); err != nil {
if errors.Is(err, mongo.ErrNoDocuments) {
return model.User{}, model.ErrUserNotFound
}

return model.User{}, err
}
return u, nil
}
Expand All @@ -99,8 +104,12 @@ func (us *UserStorage) UserByEmail(email string) (model.User, error) {
defer cancel()

var u model.User
if err := us.coll.FindOne(ctx, bson.M{"email": email}).Decode(&u); err != nil {
return model.User{}, model.ErrUserNotFound
if err := us.coll.FindOne(ctx, bson.D{{Key: "email", Value: email}}).Decode(&u); err != nil {
if errors.Is(err, mongo.ErrNoDocuments) {
return model.User{}, model.ErrUserNotFound
}

return model.User{}, err
}
// clear password hash
u.Pswd = ""
Expand All @@ -115,8 +124,12 @@ func (us *UserStorage) UserByFederatedID(provider string, id string) (model.User
defer cancel()

var u model.User
if err := us.coll.FindOne(ctx, bson.M{"federated_ids": sid}).Decode(&u); err != nil {
return model.User{}, model.ErrUserNotFound
if err := us.coll.FindOne(ctx, bson.D{{Key: "federated_ids", Value: sid}}).Decode(&u); err != nil {
if errors.Is(err, mongo.ErrNoDocuments) {
return model.User{}, model.ErrUserNotFound
}

return model.User{}, err
}
// clear password hash
u.Pswd = ""
Expand Down Expand Up @@ -159,8 +172,12 @@ func (us *UserStorage) UserByPhone(phone string) (model.User, error) {
defer cancel()

var u model.User
if err := us.coll.FindOne(ctx, bson.M{"phone": phone}).Decode(&u); err != nil {
return model.User{}, model.ErrUserNotFound
if err := us.coll.FindOne(ctx, bson.D{{Key: "phone", Value: phone}}).Decode(&u); err != nil {
if errors.Is(err, mongo.ErrNoDocuments) {
return model.User{}, model.ErrUserNotFound
}

return model.User{}, err
}
u.Pswd = ""
return u, nil
Expand All @@ -169,14 +186,18 @@ func (us *UserStorage) UserByPhone(phone string) (model.User, error) {
// UserByUsername returns user by name.
func (us *UserStorage) UserByUsername(username string) (model.User, error) {
strictPattern := "^" + strings.ReplaceAll(username, "+", "\\+") + "$"
q := bson.D{primitive.E{Key: "username", Value: primitive.Regex{Pattern: strictPattern, Options: "i"}}}
q := bson.D{{Key: "username", Value: primitive.Regex{Pattern: strictPattern, Options: "i"}}}

ctx, cancel := context.WithTimeout(context.Background(), us.timeout)
defer cancel()

var u model.User
if err := us.coll.FindOne(ctx, q).Decode(&u); err != nil {
return model.User{}, model.ErrUserNotFound
if errors.Is(err, mongo.ErrNoDocuments) {
return model.User{}, model.ErrUserNotFound
}

return model.User{}, err
}

// clear password hash
Expand All @@ -201,6 +222,7 @@ func (us *UserStorage) AddNewUser(user model.User, password string) (model.User,
if isErrDuplication(err) {
return model.User{}, model.ErrorUserExists
}

return model.User{}, err
}
return user, nil
Expand Down Expand Up @@ -236,10 +258,16 @@ func (us *UserStorage) AddUserWithPassword(user model.User, password, role strin
// AddUserWithFederatedID adds new user with social ID.
func (us *UserStorage) AddUserWithFederatedID(user model.User, provider string, federatedID, role string) (model.User, error) {
// If there is no error, it means user already exists.
if _, err := us.UserByFederatedID(provider, federatedID); err == nil {
_, err := us.UserByFederatedID(provider, federatedID)
if err == nil {
return model.User{}, model.ErrorUserExists
}

// unknown error during user existnce check
if err != nil && !errors.Is(err, model.ErrUserNotFound) {
return model.User{}, err
}

user.ID = primitive.NewObjectID().Hex()
user.Active = true
user.AccessRole = role
Expand Down Expand Up @@ -287,7 +315,7 @@ func (us *UserStorage) UpdateUser(userID string, newUser model.User) (model.User

// ResetPassword sets new user's password.
func (us *UserStorage) ResetPassword(id, password string) error {
hexID, err := primitive.ObjectIDFromHex(id)
_, err := primitive.ObjectIDFromHex(id)
if err != nil {
return err
}
Expand All @@ -299,8 +327,16 @@ func (us *UserStorage) ResetPassword(id, password string) error {
defer cancel()

var ud model.User
err = us.coll.FindOneAndUpdate(ctx, bson.M{"_id": hexID.Hex()}, update, opts).Decode(&ud)
return err
err = us.coll.FindOneAndUpdate(ctx, bson.D{{Key: "_id", Value: id}}, update, opts).Decode(&ud)
if err != nil {
if errors.Is(err, mongo.ErrNoDocuments) {
return model.ErrUserNotFound
}

return err
}

return nil
}

// CheckPassword check that password is valid for user id.
Expand All @@ -319,7 +355,7 @@ func (us *UserStorage) CheckPassword(id, password string) error {

// ResetUsername sets new user's username.
func (us *UserStorage) ResetUsername(id, username string) error {
hexID, err := primitive.ObjectIDFromHex(id)
_, err := primitive.ObjectIDFromHex(id)
if err != nil {
return err
}
Expand All @@ -331,21 +367,21 @@ func (us *UserStorage) ResetUsername(id, username string) error {
opts := options.FindOneAndUpdate().SetReturnDocument(options.After)

var ud model.User
err = us.coll.FindOneAndUpdate(ctx, bson.M{"_id": hexID.Hex()}, update, opts).Decode(&ud)
err = us.coll.FindOneAndUpdate(ctx, bson.M{"_id": id}, update, opts).Decode(&ud)
return err
}

// DeleteUser deletes user by id.
func (us *UserStorage) DeleteUser(id string) error {
hexID, err := primitive.ObjectIDFromHex(id)
_, err := primitive.ObjectIDFromHex(id)
if err != nil {
return err
}

ctx, cancel := context.WithTimeout(context.Background(), us.timeout)
defer cancel()

_, err = us.coll.DeleteOne(ctx, bson.M{"_id": hexID.Hex()})
_, err = us.coll.DeleteOne(ctx, bson.M{"_id": id})
return err
}

Expand Down
89 changes: 89 additions & 0 deletions storage/mongo/user_integration_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package mongo_test

import (
"os"
"testing"

"github.com/madappgang/identifo/v2/model"
"github.com/madappgang/identifo/v2/storage/mongo"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.mongodb.org/mongo-driver/bson/primitive"
)

func TestFetchUser(t *testing.T) {
if os.Getenv("IDENTIFO_STORAGE_MONGO_TEST_INGRATION") == "" {
t.SkipNow()
}

connStr := os.Getenv("IDENTIFO_STORAGE_MONGO_CONN")

s, err := mongo.NewUserStorage(model.MongodDatabaseSettings{
ConnectionString: connStr,
DatabaseName: "test_users",
})
require.NoError(t, err)

testUser := model.User{
Username: "test",
Email: "test@examplle.com",
Phone: "+71111111111",
FullName: "test",
Scopes: []string{"test"},
AccessRole: "test",
Active: true,
}

u, err := s.AddUserWithPassword(testUser, "test", "test", false)
require.NoError(t, err)

assert.NotEmpty(t, u.ID)
assert.NotEmpty(t, u.Pswd)

testUser.ID = u.ID
testUser.Pswd = u.Pswd

assert.Equal(t, testUser, u)

t.Run("fetch user by id", func(t *testing.T) {
tu, err := s.UserByID(u.ID)
require.NoError(t, err)
assert.Equal(t, u, tu)
})

t.Run("fetch user by email", func(t *testing.T) {
tu, err := s.UserByEmail(testUser.Email)
require.NoError(t, err)

assert.Empty(t, tu.Pswd)

tu.Pswd = u.Pswd
assert.Equal(t, u, tu)
})

t.Run("fetch user by phone", func(t *testing.T) {
tu, err := s.UserByPhone(testUser.Phone)
require.NoError(t, err)

assert.Empty(t, tu.Pswd)

tu.Pswd = u.Pswd
assert.Equal(t, u, tu)
})

t.Run("user already exists", func(t *testing.T) {
_, err := s.AddUserWithPassword(testUser, "test", "test", false)
require.ErrorIs(t, err, model.ErrorUserExists)
})

t.Run("user not found", func(t *testing.T) {
_, err := s.UserByID(primitive.NewObjectID().Hex())
require.ErrorIs(t, err, model.ErrUserNotFound)

_, err = s.UserByPhone("+71111111112")
require.ErrorIs(t, err, model.ErrUserNotFound)

_, err = s.UserByEmail("noemail@example.com")
require.ErrorIs(t, err, model.ErrUserNotFound)
})
}
16 changes: 16 additions & 0 deletions test/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,19 @@ services:
ports:
- "5001:5001"
entrypoint: ["/usr/local/bin/moto_server", "-H", "0.0.0.0", "-p", "5001"]

store_mongo:
image: mongo:4.2
ports:
- "27017:27017"
environment:
- MONGO_DATA_DIR=/data/mongodb
- MONGO_LOG_DIR=/dev/null
- MONGO_INITDB_ROOT_USERNAME=admin
- MONGO_INITDB_ROOT_PASSWORD=password
healthcheck:
test: echo 'db.runCommand("ping").ok' | mongo mongo:27017/test --quiet
interval: 10s
timeout: 10s
retries: 5
start_period: 40s
3 changes: 3 additions & 0 deletions test/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ export AWS_SECRET_ACCESS_KEY='testing'
export AWS_SECURITY_TOKEN='testing'
export AWS_SESSION_TOKEN='testing'

export IDENTIFO_STORAGE_MONGO_TEST_INGRATION=1
export IDENTIFO_STORAGE_MONGO_CONN="mongodb://admin:password@localhost:27017/billing-local?authSource=admin"

docker-compose up -d

sleep 1
Expand Down