zee-solution/internal/service/auth_service.go

238 lines
6.9 KiB
Go

package service
import (
"context"
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"errors"
"fmt"
"time"
"zee/internal/resource/role"
"zee/internal/resource/user"
"github.com/golang-jwt/jwt/v5"
"golang.org/x/crypto/bcrypt"
"gorm.io/gorm" // Added gorm import
)
// AuthService xử lý các tác vụ liên quan đến xác thực
type AuthService interface {
Register(ctx context.Context, req RegisterRequest) (*user.User, error)
Login(ctx context.Context, username, password string) (string, string, error)
RefreshToken(refreshToken string) (string, string, error)
ValidateToken(tokenString string) (*Claims, error)
}
type authService struct {
userRepo user.Repository
roleRepo role.Repository
jwtSecret string
jwtExpiration time.Duration
refreshExpires int
}
// Claims định nghĩa các thông tin trong JWT token
type Claims struct {
UserID string `json:"user_id"`
Username string `json:"username"`
Roles []string `json:"roles"`
jwt.RegisteredClaims
}
// NewAuthService tạo mới một AuthService
func NewAuthService(
userRepo user.Repository,
roleRepo role.Repository,
jwtSecret string,
jwtExpiration time.Duration,
) AuthService {
return &authService{
userRepo: userRepo,
roleRepo: roleRepo,
jwtSecret: jwtSecret,
jwtExpiration: jwtExpiration,
refreshExpires: 7 * 24 * 60, // 7 days in minutes
}
}
// Register đăng ký người dùng mới
func (s *authService) Register(ctx context.Context, req RegisterRequest) (*user.User, error) {
// Kiểm tra username đã tồn tại chưa
existingUser, err := s.userRepo.GetByUsername(ctx, req.Username)
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { // Chỉ coi là lỗi nếu không phải RecordNotFound
return nil, fmt.Errorf("error checking username: %w", err)
}
if existingUser != nil { // Nếu existingUser không nil, nghĩa là user đã tồn tại
return nil, errors.New("username already exists")
}
// Kiểm tra email đã tồn tại chưa
existingEmail, err := s.userRepo.GetByEmail(ctx, req.Email)
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { // Chỉ coi là lỗi nếu không phải RecordNotFound
return nil, fmt.Errorf("error checking email: %v", err)
}
if existingEmail != nil {
return nil, errors.New("email already exists")
}
// Mã hóa mật khẩu
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
if err != nil {
return nil, fmt.Errorf("error hashing password: %v", err)
}
// Tạo user mới
newUser := &user.User{
Username: req.Username,
Email: req.Email,
PasswordHash: string(hashedPassword),
FullName: req.FullName,
IsActive: true,
}
// Lưu user vào database
if err := s.userRepo.Create(ctx, newUser); err != nil {
return nil, fmt.Errorf("error creating user: %v", err)
}
// Thêm role mặc định là 'user' cho người dùng mới
userRole, err := s.roleRepo.GetByName(ctx, role.User)
if err != nil {
return nil, fmt.Errorf("error getting user role: %v", err)
}
if userRole == nil {
return nil, errors.New("default user role not found")
}
if err := s.userRepo.AddRole(ctx, newUser.ID, userRole.ID); err != nil {
return nil, fmt.Errorf("error adding role to user: %v", err)
}
// Lấy lại thông tin user với đầy đủ roles
createdUser, err := s.userRepo.GetByID(ctx, newUser.ID)
if err != nil {
return nil, fmt.Errorf("error getting created user: %v", err)
}
return createdUser, nil
}
// Login xác thực đăng nhập và trả về token
func (s *authService) Login(ctx context.Context, username, password string) (string, string, error) {
// Lấy thông tin user
user, err := s.userRepo.GetByUsername(ctx, username)
if err != nil {
return "", "", errors.New("invalid credentials")
}
if user == nil || !user.IsActive {
return "", "", errors.New("invalid credentials")
}
// Kiểm tra mật khẩu
if err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(password)); err != nil {
return "", "", errors.New("invalid credentials")
}
// Tạo access token
accessToken, err := s.generateToken(user)
if err != nil {
return "", "", fmt.Errorf("error generating token: %v", err)
}
// Tạo refresh token
tokenBytes := make([]byte, 32)
if _, err := rand.Read(tokenBytes); err != nil {
return "", "", fmt.Errorf("error generating refresh token: %v", err)
}
// Lưu refresh token vào database (trong thực tế nên lưu vào Redis hoặc database)
// Ở đây chỉ minh họa, nên implement thật kỹ hơn
h := sha256.New()
h.Write(tokenBytes)
tokenID := base64.URLEncoding.EncodeToString(h.Sum(nil))
// TODO: Lưu refresh token vào database với userID và tokenID
_ = tokenID
// Cập nhật thời gian đăng nhập cuối cùng
if err := s.userRepo.UpdateLastLogin(ctx, user.ID); err != nil {
// Log lỗi nhưng không ảnh hưởng đến quá trình đăng nhập
fmt.Printf("Error updating last login: %v\n", err)
}
return accessToken, string(tokenBytes), nil
}
// RefreshToken làm mới access token
func (s *authService) RefreshToken(refreshToken string) (string, string, error) {
// TODO: Kiểm tra refresh token trong database
// Nếu hợp lệ, tạo access token mới và trả về
return "", "", errors.New("not implemented")
}
// ValidateToken xác thực và trả về thông tin từ token
func (s *authService) ValidateToken(tokenString string) (*Claims, error) {
token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
// Kiểm tra signing method
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
return []byte(s.jwtSecret), nil
})
if err != nil {
return nil, err
}
if claims, ok := token.Claims.(*Claims); ok && token.Valid {
return claims, nil
}
return nil, errors.New("invalid token")
}
// generateToken tạo JWT token cho user
func (s *authService) generateToken(user *user.User) (string, error) {
// Lấy danh sách roles
roles := make([]string, len(user.Roles))
for i, r := range user.Roles {
roles[i] = r.Name
}
// Tạo claims
expirationTime := time.Now().Add(s.jwtExpiration)
claims := &Claims{
UserID: user.ID,
Username: user.Username,
Roles: roles,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(expirationTime),
IssuedAt: jwt.NewNumericDate(time.Now()),
NotBefore: jwt.NewNumericDate(time.Now()),
Issuer: "ulflow-zee",
},
}
// Tạo token
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
// Ký token và trả về
tokenString, err := token.SignedString([]byte(s.jwtSecret))
if err != nil {
return "", err
}
return tokenString, nil
}
// RegisterRequest định dạng dữ liệu đăng ký
type RegisterRequest struct {
Username string `json:"username"`
Email string `json:"email"`
Password string `json:"password"`
FullName string `json:"full_name"`
}