feat(user): Add username support for login and implement username checks in repository

This commit is contained in:
Habib Fatkhul Rohman 2025-10-28 22:29:56 +07:00
parent 62a188fcc6
commit 98bdfce83a
4 changed files with 74 additions and 30 deletions

View File

@ -53,7 +53,7 @@ func (r *refreshTokenRepository) FindByToken(ctx context.Context, tx *gorm.DB, t
var refreshToken entities.RefreshToken var refreshToken entities.RefreshToken
if err := tx.WithContext(ctx). if err := tx.WithContext(ctx).
Preload("User"). Preload("User").
Preload("User.UserRoles.Role"). Preload("User.Roles").
Take(&refreshToken).Error; err != nil { Take(&refreshToken).Error; err != nil {
return entities.RefreshToken{}, err return entities.RefreshToken{}, err
} }

View File

@ -130,7 +130,7 @@ type (
} }
UserLoginRequest struct { UserLoginRequest struct {
Email string `json:"email" form:"email" binding:"required"` Login string `json:"login" form:"login" binding:"required"`
Password string `json:"password" form:"password" binding:"required"` Password string `json:"password" form:"password" binding:"required"`
} }

View File

@ -12,7 +12,9 @@ type (
Register(ctx context.Context, tx *gorm.DB, user entities.M_User) (entities.M_User, error) Register(ctx context.Context, tx *gorm.DB, user entities.M_User) (entities.M_User, error)
GetUserById(ctx context.Context, tx *gorm.DB, userId string) (entities.M_User, error) GetUserById(ctx context.Context, tx *gorm.DB, userId string) (entities.M_User, error)
GetUserByEmail(ctx context.Context, tx *gorm.DB, email string) (entities.M_User, error) GetUserByEmail(ctx context.Context, tx *gorm.DB, email string) (entities.M_User, error)
GetUserByUsername(ctx context.Context, tx *gorm.DB, username string) (entities.M_User, error)
CheckEmail(ctx context.Context, tx *gorm.DB, email string) (entities.M_User, bool, error) CheckEmail(ctx context.Context, tx *gorm.DB, email string) (entities.M_User, bool, error)
CheckUsername(ctx context.Context, tx *gorm.DB, username string) (entities.M_User, bool, error)
Update(ctx context.Context, tx *gorm.DB, user entities.M_User) (entities.M_User, error) Update(ctx context.Context, tx *gorm.DB, user entities.M_User) (entities.M_User, error)
Delete(ctx context.Context, tx *gorm.DB, userId string) error Delete(ctx context.Context, tx *gorm.DB, userId string) error
SwitchRole(ctx context.Context, tx *gorm.DB, userId string, roleId string) (entities.M_User, error) SwitchRole(ctx context.Context, tx *gorm.DB, userId string, roleId string) (entities.M_User, error)
@ -23,6 +25,37 @@ type (
} }
) )
// CheckUsername implements UserRepository.
func (r *userRepository) CheckUsername(ctx context.Context, tx *gorm.DB, username string) (entities.M_User, bool, error) {
if tx == nil {
tx = r.db
}
var user entities.M_User
if err := tx.WithContext(ctx).Where("username = ?", username).Take(&user).Error; err != nil {
return entities.M_User{}, false, err
}
return user, true, nil
}
// GetUserByUsername implements UserRepository.
func (r *userRepository) GetUserByUsername(ctx context.Context, tx *gorm.DB, username string) (entities.M_User, error) {
if tx == nil {
tx = r.db
}
var user entities.M_User
if err := tx.WithContext(ctx).
Preload("Roles").
Where("username = ?", username).
Take(&user).Error; err != nil {
return entities.M_User{}, err
}
return user, nil
}
func NewUserRepository(db *gorm.DB) UserRepository { func NewUserRepository(db *gorm.DB) UserRepository {
return &userRepository{ return &userRepository{
db: db, db: db,

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"strings"
"github.com/Caknoooo/go-gin-clean-starter/database/entities" "github.com/Caknoooo/go-gin-clean-starter/database/entities"
authDto "github.com/Caknoooo/go-gin-clean-starter/modules/auth/dto" authDto "github.com/Caknoooo/go-gin-clean-starter/modules/auth/dto"
@ -124,9 +125,17 @@ func (s *userService) GetUserById(ctx context.Context, userId string) (dto.UserR
} }
func (s *userService) Verify(ctx context.Context, req dto.UserLoginRequest) (authDto.TokenResponse, error) { func (s *userService) Verify(ctx context.Context, req dto.UserLoginRequest) (authDto.TokenResponse, error) {
user, err := s.userRepository.GetUserByEmail(ctx, s.db, req.Email) var user entities.M_User
var err error
if strings.Contains(req.Login, "@") {
user, err = s.userRepository.GetUserByEmail(ctx, s.db, req.Login)
} else {
user, err = s.userRepository.GetUserByUsername(ctx, s.db, req.Login)
}
if err != nil { if err != nil {
return authDto.TokenResponse{}, dto.ErrEmailNotFound return authDto.TokenResponse{}, dto.ErrUserNotFound
} }
isValid := utils.CheckPasswordHash(req.Password, user.Password) isValid := utils.CheckPasswordHash(req.Password, user.Password)
@ -134,19 +143,18 @@ func (s *userService) Verify(ctx context.Context, req dto.UserLoginRequest) (aut
fmt.Println("Password validation error:", err) fmt.Println("Password validation error:", err)
return authDto.TokenResponse{}, dto.ErrUserNotFound return authDto.TokenResponse{}, dto.ErrUserNotFound
} }
// Ambil roles dari UserRoles
roles := append([]entities.M_Role{}, user.Roles...) roles := append([]entities.M_Role{}, user.Roles...)
// var roles []dto.UserRolesResponse
// for _, ur := range user.Roles {
// roles = append(roles, dto.UserRolesResponse{
// ID: ur.ID.String(),
// Name: ur.Name,
// })
// }
if len(roles) == 0 { if len(roles) == 0 {
return authDto.TokenResponse{}, errors.New("user has no roles assigned") return authDto.TokenResponse{}, errors.New("user has no roles assigned")
} }
// DeleteByUserID
err = s.refreshTokenRepository.DeleteByUserID(ctx, s.db, user.ID.String())
if err != nil {
return authDto.TokenResponse{}, err
}
accessToken := s.jwtService.GenerateAccessToken(user.ClientID.String(), user.ID.String(), roles[0].ID.String()) accessToken := s.jwtService.GenerateAccessToken(user.ClientID.String(), user.ID.String(), roles[0].ID.String())
refreshTokenString, expiresAt := s.jwtService.GenerateRefreshToken() refreshTokenString, expiresAt := s.jwtService.GenerateRefreshToken()
@ -292,31 +300,34 @@ func (s *userService) RefreshToken(ctx context.Context, req authDto.RefreshToken
} }
roles := append([]entities.M_Role{}, refreshToken.User.Roles...) roles := append([]entities.M_Role{}, refreshToken.User.Roles...)
if len(roles) == 0 { if len(roles) == 0 {
return authDto.TokenResponse{}, errors.New("user has no roles assigned") return authDto.TokenResponse{}, errors.New("1 user has no roles assigned")
} }
accessToken := s.jwtService.GenerateAccessToken(refreshToken.ClientID.String(), refreshToken.UserID.String(), roles[0].ID.String()) accessToken := s.jwtService.GenerateAccessToken(refreshToken.ClientID.String(), refreshToken.UserID.String(), roles[0].ID.String())
newRefreshTokenString, expiresAt := s.jwtService.GenerateRefreshToken() // newRefreshTokenString, expiresAt := s.jwtService.GenerateRefreshToken()
err = s.refreshTokenRepository.DeleteByToken(ctx, s.db, req.RefreshToken) // err = s.refreshTokenRepository.DeleteByToken(ctx, s.db, req.RefreshToken)
if err != nil { // if err != nil {
return authDto.TokenResponse{}, err // return authDto.TokenResponse{}, err
} // }
newRefreshToken := entities.RefreshToken{ // newRefreshToken := entities.RefreshToken{
ID: uuid.New(), // ID: uuid.New(),
UserID: refreshToken.UserID, // UserID: refreshToken.UserID,
Token: newRefreshTokenString, // ClientID: refreshToken.ClientID,
ExpiresAt: expiresAt, // Token: newRefreshTokenString,
} // ExpiresAt: expiresAt,
// }
_, err = s.refreshTokenRepository.Create(ctx, s.db, newRefreshToken) // _, err = s.refreshTokenRepository.Create(ctx, s.db, newRefreshToken)
if err != nil { // if err != nil {
return authDto.TokenResponse{}, err // return authDto.TokenResponse{}, err
} // }
return authDto.TokenResponse{ return authDto.TokenResponse{
AccessToken: accessToken, AccessToken: accessToken,
RefreshToken: newRefreshTokenString, // RefreshToken: newRefreshTokenString,
RefreshToken: req.RefreshToken, // atau kosongkan jika tidak ingin return refresh token sama sekali
}, nil }, nil
} }