Skip to content

Commit

Permalink
⚡️ Added Login service
Browse files Browse the repository at this point in the history
  • Loading branch information
lhbelfanti committed Jan 13, 2025
1 parent 4c9f2b6 commit ce64a20
Show file tree
Hide file tree
Showing 7 changed files with 143 additions and 10 deletions.
3 changes: 3 additions & 0 deletions cmd/api/auth/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ var (
FailedToInsertUserIntoDatabase = errors.New("failed to insert user into database")
MissingUsername = errors.New("missing username")
MissingPassword = errors.New("missing password")
FailedToSelectUserByUsername = errors.New("failed to execute select user by username")
FailedToLoginDueWrongPassword = errors.New("failed to login due wrong password")
FailedToCreateUserSession = errors.New("failed to create user session")
)

const (
Expand Down
39 changes: 39 additions & 0 deletions cmd/api/auth/login.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package auth

import (
"context"
"golang.org/x/crypto/bcrypt"
"time"

"ahbcc/cmd/api/user"
"ahbcc/cmd/api/user/session"
"ahbcc/internal/log"
)

// Login logs the user in
type Login func(ctx context.Context, user user.DTO) (string, time.Time, error)

// MakeLogin creates a new Login function
func MakeLogin(selectUserByUsername user.SelectByUsername, createSessionToken session.CreateToken) Login {
return func(ctx context.Context, user user.DTO) (string, time.Time, error) {
userDAO, err := selectUserByUsername(ctx, user.Username)
if err != nil {
log.Error(ctx, err.Error())
return "", time.Time{}, FailedToSelectUserByUsername
}

err = bcrypt.CompareHashAndPassword([]byte(userDAO.PasswordHash), []byte(user.Password))
if err != nil {
log.Error(ctx, err.Error())
return "", time.Time{}, FailedToLoginDueWrongPassword
}

token, expiresAt, err := createSessionToken(ctx, userDAO.ID)
if err != nil {
log.Error(ctx, err.Error())
return "", time.Time{}, FailedToCreateUserSession
}

return token, expiresAt, nil
}
}
77 changes: 77 additions & 0 deletions cmd/api/auth/login_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package auth_test

import (
"context"
"errors"
"testing"
"time"

"github.com/stretchr/testify/assert"

"ahbcc/cmd/api/auth"
"ahbcc/cmd/api/user"
"ahbcc/cmd/api/user/session"
)

func TestLogin_success(t *testing.T) {
mockUserDAO := user.MockDAO()
mockSelectUserByUsername := user.MockSelectByUsername(mockUserDAO, nil)
mockToken := "abcd"
mockCreatedAt := time.Date(2006, time.January, 1, 0, 0, 0, 0, time.Local)
mockCreateSessionToken := session.MockCreateToken(mockToken, mockCreatedAt, nil)
mockUserDTO := user.MockDTO()

login := auth.MakeLogin(mockSelectUserByUsername, mockCreateSessionToken)

token, createdAt, err := login(context.Background(), mockUserDTO)

assert.Nil(t, err)
assert.Equal(t, mockToken, token)
assert.Equal(t, mockCreatedAt, createdAt)
}

func TestLogin_failsWhenSelectUserByUsernameThrowsError(t *testing.T) {
mockUserDAO := user.MockDAO()
mockSelectUserByUsername := user.MockSelectByUsername(mockUserDAO, errors.New("error while executing SelectByUsername"))
mockToken := "abcd"
mockCreatedAt := time.Date(2006, time.January, 1, 0, 0, 0, 0, time.Local)
mockCreateSessionToken := session.MockCreateToken(mockToken, mockCreatedAt, nil)
mockUserDTO := user.MockDTO()

login := auth.MakeLogin(mockSelectUserByUsername, mockCreateSessionToken)

want := auth.FailedToSelectUserByUsername
_, _, got := login(context.Background(), mockUserDTO)

assert.Equal(t, want, got)
}

func TestLogin_failsWhenCompareHashAndPasswordThrowsError(t *testing.T) {
mockUserDAO := user.MockDAO()
mockSelectUserByUsername := user.MockSelectByUsername(mockUserDAO, nil)
mockCreatedAt := time.Date(2006, time.January, 1, 0, 0, 0, 0, time.Local)
mockCreateSessionToken := session.MockCreateToken("abcd", mockCreatedAt, nil)
mockUserDTO := user.MockDTO()
mockUserDTO.Password = "wrong password"

login := auth.MakeLogin(mockSelectUserByUsername, mockCreateSessionToken)

want := auth.FailedToLoginDueWrongPassword
_, _, got := login(context.Background(), mockUserDTO)

assert.Equal(t, want, got)
}

func TestLogin_failsWhenCreateSessionTokenThrowsError(t *testing.T) {
mockUserDAO := user.MockDAO()
mockSelectUserByUsername := user.MockSelectByUsername(mockUserDAO, nil)
mockCreateSessionToken := session.MockCreateToken("abcd", time.Time{}, errors.New("error while executing CreateSessionToken"))
mockUserDTO := user.MockDTO()

login := auth.MakeLogin(mockSelectUserByUsername, mockCreateSessionToken)

want := auth.FailedToCreateUserSession
_, _, got := login(context.Background(), mockUserDTO)

assert.Equal(t, want, got)
}
8 changes: 4 additions & 4 deletions cmd/api/user/daos.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import "time"

// DAO represents a user
type DAO struct {
ID int `json:"id"`
Username string `json:"username"`
Password string `json:"password_hash"`
CreatedAt time.Time `json:"created_at"`
ID int `json:"id"`
Username string `json:"username"`
PasswordHash string `json:"password_hash"`
CreatedAt time.Time `json:"created_at"`
}
17 changes: 12 additions & 5 deletions cmd/api/user/mocks.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@ func MockExists(userExists bool, err error) Exists {
}
}

// MockSelectByUsername mocks SelectByUsername function
func MockSelectByUsername(userDAO DAO, err error) SelectByUsername {
return func(ctx context.Context, username string) (DAO, error) {
return userDAO, err
}
}

// MockInsert mocks Insert function
func MockInsert(err error) Insert {
return func(ctx context.Context, user DTO) error {
Expand All @@ -30,10 +37,10 @@ func MockDTO() DTO {
// MockDAO mocks user DAO
func MockDAO() DAO {
return DAO{
ID: 1,
Username: "username",
Password: "password",
CreatedAt: time.Date(2006, time.January, 1, 0, 0, 0, 0, time.Local),
ID: 1,
Username: "username",
PasswordHash: "$2b$12$AEscv2yxile/9dNaaRbIPe2x.9ousol85rtBCPu83cS5F9gXPBZQK\n",
CreatedAt: time.Date(2006, time.January, 1, 0, 0, 0, 0, time.Local),
}
}

Expand All @@ -42,7 +49,7 @@ func MockScanUserDAOValues(dao DAO) []any {
return []any{
dao.ID,
dao.Username,
dao.Password,
dao.PasswordHash,
dao.CreatedAt,
}
}
2 changes: 1 addition & 1 deletion cmd/api/user/select.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ func MakeSelectByUsername(db database.Connection) SelectByUsername {
err := db.QueryRow(ctx, query, username).Scan(
&user.ID,
&user.Username,
&user.Password,
&user.PasswordHash,
&user.CreatedAt,
)
if errors.Is(err, pgx.ErrNoRows) {
Expand Down
7 changes: 7 additions & 0 deletions cmd/api/user/session/mocks.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@ func MockInsertUserSession(err error) Insert {
}
}

// MockCreateToken mocks a CreateToken function
func MockCreateToken(token string, expiresAt time.Time, err error) CreateToken {
return func(ctx context.Context, userID int) (string, time.Time, error) {
return token, expiresAt, err
}
}

// MockUserSessionDAO mocks a session DAO
func MockUserSessionDAO() DAO {
return DAO{
Expand Down

0 comments on commit ce64a20

Please sign in to comment.