UPDATE: Fix bug
All checks were successful
Build and Release / release (push) Successful in 1m8s

This commit is contained in:
2026-04-27 20:31:01 +07:00
parent eb08c16232
commit 17aafacbfd
36 changed files with 842 additions and 447 deletions

View File

@@ -32,14 +32,14 @@ import (
)
type AuthService interface {
Signin(ctx context.Context, dto *request.SignInDto) (*response.AuthResponse, error)
Signup(ctx context.Context, dto *request.SignUpDto) (*response.AuthResponse, error)
Logout(ctx context.Context, userId string) error
ForgotPassword(ctx context.Context, dto *request.ForgotPasswordDto) error
VerifyToken(ctx context.Context, dto *request.VerifyTokenDto) (*response.VerifyTokenResponse, error)
CreateToken(ctx context.Context, dto *request.CreateTokenDto) error
SigninWithGoogle(ctx context.Context, dto *request.SigninWithGoogleDto) (*response.AuthResponse, error)
RefreshToken(ctx context.Context, id string, refreshToken string) (*response.AuthResponse, error)
Signin(ctx context.Context, dto *request.SignInDto) (*response.AuthResponse, *fiber.Error)
Signup(ctx context.Context, dto *request.SignUpDto) (*response.AuthResponse, *fiber.Error)
Logout(ctx context.Context, userId string) *fiber.Error
ForgotPassword(ctx context.Context, dto *request.ForgotPasswordDto) *fiber.Error
VerifyToken(ctx context.Context, dto *request.VerifyTokenDto) (*response.VerifyTokenResponse, *fiber.Error)
CreateToken(ctx context.Context, dto *request.CreateTokenDto) *fiber.Error
SigninWithGoogle(ctx context.Context, dto *request.SigninWithGoogleDto) (*response.AuthResponse, *fiber.Error)
RefreshToken(ctx context.Context, id string, refreshToken string) (*response.AuthResponse, *fiber.Error)
}
type authService struct {
@@ -69,15 +69,15 @@ func NewAuthService(
func (a *authService) genToken(user *models.UserEntity) (*response.AuthResponse, error) {
jwtSecret, err := config.GetConfig("JWT_SECRET")
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, "missing JWT_SECRET in environment")
return nil, err
}
jwtRefreshSecret, err := config.GetConfig("JWT_REFRESH_SECRET")
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, "missing JWT_REFRESH_SECRET in environment")
return nil, err
}
if jwtSecret == "" || jwtRefreshSecret == "" {
return nil, fiber.NewError(fiber.StatusInternalServerError, "missing JWT secrets in environment")
return nil, errors.New("missing JWT secrets in environment")
}
claimsAccess := &response.JWTClaims{
@@ -117,9 +117,9 @@ func (a *authService) genToken(user *models.UserEntity) (*response.AuthResponse,
return &res, nil
}
func (a *authService) Signin(ctx context.Context, dto *request.SignInDto) (*response.AuthResponse, error) {
func (a *authService) Signin(ctx context.Context, dto *request.SignInDto) (*response.AuthResponse, *fiber.Error) {
if !constants.EMAIL_REGEX.MatchString(dto.Email) {
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid email")
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid email format")
}
err := constants.ValidatePassword(dto.Password)
@@ -129,7 +129,7 @@ func (a *authService) Signin(ctx context.Context, dto *request.SignInDto) (*resp
user, err := a.userRepo.GetByEmail(ctx, dto.Email)
if err != nil || user == nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, err.Error())
return nil, fiber.NewError(fiber.StatusUnauthorized, "Invalid email or password")
}
if user.AuthProvider != constants.ProviderTypeLocal.String() && user.PasswordHash == "" {
@@ -137,17 +137,17 @@ func (a *authService) Signin(ctx context.Context, dto *request.SignInDto) (*resp
}
if err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(dto.Password)); err != nil {
return nil, fiber.NewError(fiber.StatusUnauthorized, "Invalid identity or password!")
return nil, fiber.NewError(fiber.StatusUnauthorized, "Invalid email or password")
}
data, err := a.genToken(user)
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, err.Error())
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to generate security tokens")
}
pgID, err := convert.StringToUUID(user.ID)
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, err.Error())
return nil, fiber.NewError(fiber.StatusInternalServerError, "Invalid user ID internal format")
}
err = a.userRepo.UpdateRefreshToken(
ctx,
@@ -160,14 +160,14 @@ func (a *authService) Signin(ctx context.Context, dto *request.SignInDto) (*resp
},
)
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, err.Error())
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to update refresh token")
}
return data, nil
}
func (a *authService) Logout(ctx context.Context, userId string) error {
func (a *authService) Logout(ctx context.Context, userId string) *fiber.Error {
tx, err := a.db.Begin(ctx)
if err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "Failed to start transaction")
@@ -178,11 +178,11 @@ func (a *authService) Logout(ctx context.Context, userId string) error {
pgID, err := convert.StringToUUID(userId)
if err != nil {
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
return fiber.NewError(fiber.StatusBadRequest, "Invalid user ID format")
}
user, err := a.userRepo.GetByID(ctx, pgID)
if err != nil || user == nil {
return fiber.NewError(fiber.StatusInternalServerError, "Invalid user data")
return fiber.NewError(fiber.StatusNotFound, "User not found")
}
err = uRepoTx.UpdateTokenVersion(ctx, sqlc.UpdateTokenVersionParams{
@@ -190,7 +190,7 @@ func (a *authService) Logout(ctx context.Context, userId string) error {
TokenVersion: user.TokenVersion + 1,
})
if err != nil {
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
return fiber.NewError(fiber.StatusInternalServerError, "Failed to revoke sessions")
}
err = uRepoTx.UpdateRefreshToken(ctx, sqlc.UpdateUserRefreshTokenParams{
@@ -201,39 +201,39 @@ func (a *authService) Logout(ctx context.Context, userId string) error {
},
})
if err != nil {
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
return fiber.NewError(fiber.StatusInternalServerError, "Failed to clear refresh token")
}
err = tx.Commit(ctx)
if err != nil {
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
return fiber.NewError(fiber.StatusInternalServerError, "Failed to commit logout")
}
return nil
}
func (a *authService) RefreshToken(ctx context.Context, id string, refreshToken string) (*response.AuthResponse, error) {
func (a *authService) RefreshToken(ctx context.Context, id string, refreshToken string) (*response.AuthResponse, *fiber.Error) {
var pgID pgtype.UUID
err := pgID.Scan(id)
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, err.Error())
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid user ID format")
}
user, err := a.userRepo.GetByID(ctx, pgID)
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, "Invalid user data")
return nil, fiber.NewError(fiber.StatusNotFound, "User not found")
}
if user.RefreshToken != refreshToken {
return nil, fiber.NewError(fiber.StatusUnauthorized, "Invalid refresh token")
return nil, fiber.NewError(fiber.StatusUnauthorized, "Invalid or expired refresh token")
}
roles := models.RolesEntityToRoleConstant(user.Roles)
if slices.Contains(roles, constants.RoleTypeBanned) {
return nil, fiber.NewError(fiber.StatusUnauthorized, "User is banned!")
return nil, fiber.NewError(fiber.StatusUnauthorized, "Your account has been banned")
}
data, err := a.genToken(user)
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, err.Error())
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to generate new tokens")
}
err = a.userRepo.UpdateRefreshToken(
@@ -247,13 +247,13 @@ func (a *authService) RefreshToken(ctx context.Context, id string, refreshToken
},
)
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, err.Error())
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to update refresh token")
}
return data, nil
}
func (a *authService) Signup(ctx context.Context, dto *request.SignUpDto) (*response.AuthResponse, error) {
func (a *authService) Signup(ctx context.Context, dto *request.SignUpDto) (*response.AuthResponse, *fiber.Error) {
tx, err := a.db.Begin(ctx)
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to start transaction")
@@ -264,7 +264,7 @@ func (a *authService) Signup(ctx context.Context, dto *request.SignUpDto) (*resp
rRepoTx := a.roleRepo.WithTx(tx)
if !constants.EMAIL_REGEX.MatchString(dto.Email) {
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid email")
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid email format")
}
err = constants.ValidatePassword(dto.Password)
if err != nil {
@@ -273,24 +273,24 @@ func (a *authService) Signup(ctx context.Context, dto *request.SignUpDto) (*resp
ok, err := a.tokenRepo.CheckVerified(ctx, dto.Email, constants.TokenTypeEmailVerify, dto.TokenID)
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, err.Error())
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to verify registration token")
}
if !ok {
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid or expired token")
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid or expired verification token")
}
user, err := a.userRepo.GetByEmail(ctx, dto.Email)
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return nil, fiber.NewError(fiber.StatusInternalServerError, err.Error())
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to check existing user")
}
if user != nil {
return nil, fiber.NewError(fiber.StatusBadRequest, "User already exists")
return nil, fiber.NewError(fiber.StatusConflict, "Email is already registered")
}
hashed, err := bcrypt.GenerateFromPassword([]byte(dto.Password), bcrypt.DefaultCost)
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, err.Error())
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to hash password")
}
user, err = uRepoTx.UpsertUser(
@@ -305,12 +305,12 @@ func (a *authService) Signup(ctx context.Context, dto *request.SignUpDto) (*resp
},
)
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, err.Error())
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to create user account")
}
userId, err := convert.StringToUUID(user.ID)
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, err.Error())
return nil, fiber.NewError(fiber.StatusInternalServerError, "Invalid user ID internal format")
}
_, err = uRepoTx.CreateProfile(
ctx,
@@ -323,16 +323,16 @@ func (a *authService) Signup(ctx context.Context, dto *request.SignUpDto) (*resp
},
)
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, err.Error())
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to create user profile")
}
role, err := a.roleRepo.GetByName(ctx, constants.RoleTypeUser.String())
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, err.Error())
return nil, fiber.NewError(fiber.StatusInternalServerError, "Default role not found")
}
roleId, err := convert.StringToUUID(role.ID)
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, err.Error())
return nil, fiber.NewError(fiber.StatusInternalServerError, "Invalid role ID internal format")
}
err = rRepoTx.CreateUserRole(
@@ -343,12 +343,12 @@ func (a *authService) Signup(ctx context.Context, dto *request.SignUpDto) (*resp
},
)
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, err.Error())
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to assign user role")
}
data, err := a.genToken(user)
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, err.Error())
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to generate security tokens")
}
err = uRepoTx.UpdateRefreshToken(
@@ -362,40 +362,40 @@ func (a *authService) Signup(ctx context.Context, dto *request.SignUpDto) (*resp
},
)
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, err.Error())
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to update refresh token")
}
err = tx.Commit(ctx)
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, err.Error())
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to commit signup")
}
return data, nil
}
func (a *authService) ForgotPassword(ctx context.Context, dto *request.ForgotPasswordDto) error {
func (a *authService) ForgotPassword(ctx context.Context, dto *request.ForgotPasswordDto) *fiber.Error {
ok, err := a.tokenRepo.CheckVerified(ctx, dto.Email, constants.TokenTypePasswordReset, dto.TokenID)
if err != nil {
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
return fiber.NewError(fiber.StatusInternalServerError, "Failed to verify reset token")
}
if !ok {
return fiber.NewError(fiber.StatusBadRequest, "Invalid or expired token")
return fiber.NewError(fiber.StatusBadRequest, "Invalid or expired reset token")
}
user, err := a.userRepo.GetByEmail(ctx, dto.Email)
if err != nil {
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
return fiber.NewError(fiber.StatusNotFound, "User not found")
}
if user == nil {
return fiber.NewError(fiber.StatusBadRequest, "User not found")
return fiber.NewError(fiber.StatusNotFound, "User not found")
}
hashed, err := bcrypt.GenerateFromPassword([]byte(dto.NewPassword), bcrypt.DefaultCost)
if err != nil {
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
return fiber.NewError(fiber.StatusInternalServerError, "Failed to hash new password")
}
userId, err := convert.StringToUUID(user.ID)
if err != nil {
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
return fiber.NewError(fiber.StatusInternalServerError, "Invalid user ID format")
}
err = a.userRepo.UpdatePassword(ctx, sqlc.UpdateUserPasswordParams{
ID: userId,
@@ -405,12 +405,12 @@ func (a *authService) ForgotPassword(ctx context.Context, dto *request.ForgotPas
},
})
if err != nil {
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update password")
}
return nil
}
func (a *authService) SigninWithGoogle(ctx context.Context, dto *request.SigninWithGoogleDto) (*response.AuthResponse, error) {
func (a *authService) SigninWithGoogle(ctx context.Context, dto *request.SigninWithGoogleDto) (*response.AuthResponse, *fiber.Error) {
tx, err := a.db.Begin(ctx)
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to start transaction")
@@ -422,17 +422,17 @@ func (a *authService) SigninWithGoogle(ctx context.Context, dto *request.SigninW
user, err := a.userRepo.GetByEmail(ctx, dto.Email)
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return nil, fiber.NewError(fiber.StatusInternalServerError, err.Error())
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch user data")
}
if user != nil {
userId, err := convert.StringToUUID(user.ID)
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, err.Error())
return nil, fiber.NewError(fiber.StatusInternalServerError, "Invalid user ID format")
}
data, err := a.genToken(user)
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, err.Error())
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to generate security tokens")
}
err = uRepoTx.UpdateRefreshToken(
ctx,
@@ -445,7 +445,7 @@ func (a *authService) SigninWithGoogle(ctx context.Context, dto *request.SigninW
},
)
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, err.Error())
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to update refresh token")
}
return data, nil
}
@@ -462,11 +462,11 @@ func (a *authService) SigninWithGoogle(ctx context.Context, dto *request.SigninW
},
)
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, err.Error())
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to create user account")
}
userId, err := convert.StringToUUID(user.ID)
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, err.Error())
return nil, fiber.NewError(fiber.StatusInternalServerError, "Invalid user ID format")
}
_, err = uRepoTx.CreateProfile(
ctx,
@@ -483,16 +483,16 @@ func (a *authService) SigninWithGoogle(ctx context.Context, dto *request.SigninW
},
)
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, err.Error())
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to create user profile")
}
role, err := a.roleRepo.GetByName(ctx, constants.RoleTypeUser.String())
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, err.Error())
return nil, fiber.NewError(fiber.StatusInternalServerError, "Default role not found")
}
roleId, err := convert.StringToUUID(role.ID)
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, err.Error())
return nil, fiber.NewError(fiber.StatusInternalServerError, "Invalid role ID format")
}
err = rRepoTx.CreateUserRole(
@@ -503,12 +503,12 @@ func (a *authService) SigninWithGoogle(ctx context.Context, dto *request.SigninW
},
)
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, err.Error())
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to assign user role")
}
data, err := a.genToken(user)
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, err.Error())
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to generate security tokens")
}
err = uRepoTx.UpdateRefreshToken(
ctx,
@@ -521,11 +521,11 @@ func (a *authService) SigninWithGoogle(ctx context.Context, dto *request.SigninW
},
)
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, err.Error())
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to update refresh token")
}
err = tx.Commit(ctx)
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, err.Error())
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to commit Google signin")
}
return data, nil
}
@@ -540,19 +540,19 @@ func (a *authService) GenerateOTP() (string, error) {
return fmt.Sprintf("%06d", otp), nil
}
func (a *authService) CreateToken(ctx context.Context, dto *request.CreateTokenDto) error {
func (a *authService) CreateToken(ctx context.Context, dto *request.CreateTokenDto) *fiber.Error {
ok, err := a.tokenRepo.CheckCooldown(ctx, dto.Email, dto.TokenType)
if err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "Internal Server Error")
return fiber.NewError(fiber.StatusInternalServerError, "Failed to check request cooldown")
}
if ok {
return fiber.NewError(fiber.StatusBadRequest, "Too many requests. Please try again later.")
return fiber.NewError(fiber.StatusTooManyRequests, "Too many requests. Please try again later.")
}
user, err := a.userRepo.GetByEmail(ctx, dto.Email)
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return fiber.NewError(fiber.StatusInternalServerError, "Internal Server Error")
return fiber.NewError(fiber.StatusInternalServerError, "Failed to check user existence")
}
shouldSend := true
@@ -564,7 +564,7 @@ func (a *authService) CreateToken(ctx context.Context, dto *request.CreateTokenD
if shouldSend {
otp, err := a.GenerateOTP()
if err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "Internal Server Error")
return fiber.NewError(fiber.StatusInternalServerError, "Failed to generate OTP")
}
hash := sha256.Sum256([]byte(otp))
hashString := hex.EncodeToString(hash[:])
@@ -575,7 +575,7 @@ func (a *authService) CreateToken(ctx context.Context, dto *request.CreateTokenD
}
err = a.tokenRepo.Create(ctx, token)
if err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "Internal Server Error")
return fiber.NewError(fiber.StatusInternalServerError, "Failed to save verification token")
}
token.Token = otp
@@ -585,7 +585,7 @@ func (a *authService) CreateToken(ctx context.Context, dto *request.CreateTokenD
return nil
}
func (a *authService) VerifyToken(ctx context.Context, dto *request.VerifyTokenDto) (*response.VerifyTokenResponse, error) {
func (a *authService) VerifyToken(ctx context.Context, dto *request.VerifyTokenDto) (*response.VerifyTokenResponse, *fiber.Error) {
genericError := fiber.NewError(fiber.StatusBadRequest, "Invalid or expired token")
token, err := a.tokenRepo.Get(ctx, dto.Email, dto.TokenType)
if err != nil || token == nil {
@@ -607,7 +607,7 @@ func (a *authService) VerifyToken(ctx context.Context, dto *request.VerifyTokenD
user, err := a.userRepo.GetByEmail(ctx, dto.Email)
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return nil, fiber.NewError(fiber.StatusInternalServerError, "Internal Server Error")
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to check user existence")
}
if (dto.TokenType == constants.TokenTypeEmailVerify && user != nil) ||
@@ -618,7 +618,7 @@ func (a *authService) VerifyToken(ctx context.Context, dto *request.VerifyTokenD
tokenId := uuid.New().String()
err = a.tokenRepo.CreateVerified(ctx, dto.Email, dto.TokenType, tokenId)
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, "Internal Server Error")
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to create verified token record")
}
_ = a.tokenRepo.Delete(ctx, dto.Email, dto.TokenType)