package service import ( "context" "errors" "time" "github.com/golang-jwt/jwt/v5" "github.com/google/uuid" "golang.org/x/crypto/bcrypt" "tm/internal/domain" "tm/internal/infrastructure" "tm/pkg/logger" ) // UserAuthServiceImpl implements the UserAuthService interface type UserAuthServiceImpl struct { userRepo domain.UserRepository config *infrastructure.AuthConfig logger logger.Logger } // NewUserAuthService creates a new panel user authentication service func NewUserAuthService( userRepo domain.UserRepository, config *infrastructure.AuthConfig, logger logger.Logger, ) domain.UserAuthService { return &UserAuthServiceImpl{ userRepo: userRepo, config: config, logger: logger, } } // Login authenticates a panel user and returns tokens func (s *UserAuthServiceImpl) Login(ctx context.Context, req domain.LoginRequest) (*domain.UserAuthResponse, error) { s.logger.Info("Panel user login attempt", map[string]interface{}{ "email": req.Email, }) // Get user by email user, err := s.userRepo.GetByEmail(ctx, req.Email) if err != nil { s.logger.Warn("Panel user login failed - user not found", map[string]interface{}{ "email": req.Email, }) return nil, errors.New("invalid credentials") } // Check if user is active if !user.IsActive { s.logger.Warn("Panel user login failed - account inactive", map[string]interface{}{ "email": req.Email, "user_id": user.ID.String(), }) return nil, errors.New("account is inactive") } // Verify password if !s.verifyPassword(req.Password, user.Password) { s.logger.Warn("Panel user login failed - invalid password", map[string]interface{}{ "email": req.Email, "user_id": user.ID.String(), }) return nil, errors.New("invalid credentials") } // Update last login time user.LastLoginAt = &time.Time{} *user.LastLoginAt = time.Now() if err := s.userRepo.Update(ctx, user); err != nil { s.logger.Warn("Failed to update user last login", map[string]interface{}{ "error": err.Error(), "user_id": user.ID.String(), }) // Don't fail login for this } // Generate tokens accessToken, err := s.generateAccessToken(user) if err != nil { s.logger.Error("Failed to generate access token", map[string]interface{}{ "error": err.Error(), "user_id": user.ID.String(), }) return nil, errors.New("failed to generate access token") } refreshToken, err := s.generateRefreshToken(user) if err != nil { s.logger.Error("Failed to generate refresh token", map[string]interface{}{ "error": err.Error(), "user_id": user.ID.String(), }) return nil, errors.New("failed to generate refresh token") } s.logger.Info("Panel user logged in successfully", map[string]interface{}{ "user_id": user.ID.String(), "email": user.Email, "role": user.Role, "department": user.Department, }) return &domain.UserAuthResponse{ User: user, AccessToken: accessToken, RefreshToken: refreshToken, ExpiresIn: int64(s.config.JWT.AccessTokenDuration.Seconds()), }, nil } // RefreshToken generates new tokens using refresh token func (s *UserAuthServiceImpl) RefreshToken(ctx context.Context, refreshToken string) (*domain.UserAuthResponse, error) { // Parse and validate refresh token token, err := jwt.Parse(refreshToken, func(token *jwt.Token) (interface{}, error) { if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, errors.New("invalid signing method") } return []byte(s.config.JWT.Secret), nil }) if err != nil || !token.Valid { return nil, errors.New("invalid refresh token") } claims, ok := token.Claims.(jwt.MapClaims) if !ok { return nil, errors.New("invalid token claims") } // Verify token type tokenType, ok := claims["type"].(string) if !ok || tokenType != "refresh" { return nil, errors.New("invalid token type") } // Verify user type userType, ok := claims["user_type"].(string) if !ok || userType != "user" { return nil, errors.New("invalid user type") } userIDStr, ok := claims["user_id"].(string) if !ok { return nil, errors.New("invalid user ID in token") } userID, err := uuid.Parse(userIDStr) if err != nil { return nil, errors.New("invalid user ID format") } // Get user from database user, err := s.userRepo.GetByID(ctx, userID) if err != nil { return nil, errors.New("user not found") } if !user.IsActive { return nil, errors.New("account is inactive") } // Generate new tokens accessToken, err := s.generateAccessToken(user) if err != nil { return nil, errors.New("failed to generate access token") } newRefreshToken, err := s.generateRefreshToken(user) if err != nil { return nil, errors.New("failed to generate refresh token") } return &domain.UserAuthResponse{ User: user, AccessToken: accessToken, RefreshToken: newRefreshToken, ExpiresIn: int64(s.config.JWT.AccessTokenDuration.Seconds()), }, nil } // ValidateToken validates a JWT token and returns the user func (s *UserAuthServiceImpl) ValidateToken(ctx context.Context, tokenString string) (*domain.User, error) { token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, errors.New("invalid signing method") } return []byte(s.config.JWT.Secret), nil }) if err != nil || !token.Valid { return nil, errors.New("invalid token") } claims, ok := token.Claims.(jwt.MapClaims) if !ok { return nil, errors.New("invalid token claims") } // Verify token type tokenType, ok := claims["type"].(string) if !ok || tokenType != "access" { return nil, errors.New("invalid token type") } // Verify user type userType, ok := claims["user_type"].(string) if !ok || userType != "user" { return nil, errors.New("invalid user type") } userIDStr, ok := claims["user_id"].(string) if !ok { return nil, errors.New("invalid user ID in token") } userID, err := uuid.Parse(userIDStr) if err != nil { return nil, errors.New("invalid user ID format") } user, err := s.userRepo.GetByID(ctx, userID) if err != nil { return nil, errors.New("user not found") } if !user.IsActive { return nil, errors.New("account is inactive") } return user, nil } // ChangePassword changes panel user password func (s *UserAuthServiceImpl) ChangePassword(ctx context.Context, userID uuid.UUID, oldPassword, newPassword string) error { user, err := s.userRepo.GetByID(ctx, userID) if err != nil { return errors.New("user not found") } // Verify old password if !s.verifyPassword(oldPassword, user.Password) { return errors.New("invalid old password") } // Hash new password hashedPassword, err := s.hashPassword(newPassword) if err != nil { return errors.New("failed to process new password") } // Update password user.Password = hashedPassword if err := s.userRepo.Update(ctx, user); err != nil { s.logger.Error("Failed to update user password", map[string]interface{}{ "error": err.Error(), "user_id": userID.String(), }) return errors.New("failed to update password") } s.logger.Info("Panel user password changed successfully", map[string]interface{}{ "user_id": userID.String(), }) return nil } // CreatePanelUser creates a new panel user (admin only) func (s *UserAuthServiceImpl) CreatePanelUser(ctx context.Context, req domain.CreateUserRequest, createdBy uuid.UUID) (*domain.User, error) { s.logger.Info("Creating new panel user", map[string]interface{}{ "email": req.Email, "role": req.Role, "created_by": createdBy.String(), }) // Check if user already exists existingUser, err := s.userRepo.GetByEmail(ctx, req.Email) if err == nil && existingUser != nil { return nil, errors.New("user already exists") } // Validate role if !s.isValidRole(req.Role) { return nil, errors.New("invalid role") } // Hash password hashedPassword, err := s.hashPassword(req.Password) if err != nil { s.logger.Error("Failed to hash password", map[string]interface{}{ "error": err.Error(), }) return nil, errors.New("failed to process password") } // Create user user := &domain.User{ ID: uuid.New(), Email: req.Email, Password: hashedPassword, FirstName: req.FirstName, LastName: req.LastName, Role: req.Role, Department: req.Department, IsActive: true, Permissions: s.getDefaultPermissions(req.Role), CreatedBy: &createdBy, CreatedAt: time.Now(), UpdatedAt: time.Now(), } // Add custom permissions if provided if len(req.Permissions) > 0 { user.Permissions = append(user.Permissions, req.Permissions...) user.Permissions = s.removeDuplicatePermissions(user.Permissions) } if err := s.userRepo.Create(ctx, user); err != nil { s.logger.Error("Failed to create panel user", map[string]interface{}{ "error": err.Error(), "email": req.Email, "created_by": createdBy.String(), }) return nil, errors.New("failed to create user") } s.logger.Info("Panel user created successfully", map[string]interface{}{ "user_id": user.ID.String(), "email": user.Email, "role": user.Role, "created_by": createdBy.String(), }) // Remove password from response user.Password = "" return user, nil } // UpdateUserPermissions updates panel user permissions (admin only) func (s *UserAuthServiceImpl) UpdateUserPermissions(ctx context.Context, userID uuid.UUID, permissions []string, updatedBy uuid.UUID) error { // Check if user exists _, err := s.userRepo.GetByID(ctx, userID) if err != nil { return errors.New("user not found") } // Validate permissions for _, permission := range permissions { if !s.isValidPermission(permission) { return errors.New("invalid permission: " + permission) } } // Update permissions if err := s.userRepo.UpdatePermissions(ctx, userID, permissions); err != nil { s.logger.Error("Failed to update user permissions", map[string]interface{}{ "error": err.Error(), "user_id": userID.String(), "updated_by": updatedBy.String(), }) return errors.New("failed to update permissions") } s.logger.Info("Panel user permissions updated successfully", map[string]interface{}{ "user_id": userID.String(), "permissions": permissions, "updated_by": updatedBy.String(), }) return nil } // Private helper methods func (s *UserAuthServiceImpl) hashPassword(password string) (string, error) { hashedBytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) if err != nil { return "", err } return string(hashedBytes), nil } func (s *UserAuthServiceImpl) verifyPassword(password, hashedPassword string) bool { err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password)) return err == nil } func (s *UserAuthServiceImpl) generateAccessToken(user *domain.User) (string, error) { claims := jwt.MapClaims{ "user_id": user.ID.String(), "email": user.Email, "role": user.Role, "department": user.Department, "permissions": user.Permissions, "user_type": "user", "exp": time.Now().Add(s.config.JWT.AccessTokenDuration).Unix(), "iat": time.Now().Unix(), "type": "access", } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) return token.SignedString([]byte(s.config.JWT.Secret)) } func (s *UserAuthServiceImpl) generateRefreshToken(user *domain.User) (string, error) { claims := jwt.MapClaims{ "user_id": user.ID.String(), "user_type": "user", "exp": time.Now().Add(s.config.JWT.RefreshTokenDuration).Unix(), "iat": time.Now().Unix(), "type": "refresh", } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) return token.SignedString([]byte(s.config.JWT.Secret)) } func (s *UserAuthServiceImpl) isValidRole(role domain.UserRole) bool { validRoles := []domain.UserRole{ domain.UserRoleAdmin, domain.UserRoleModerator, domain.UserRoleSupport, domain.UserRoleAnalyst, } for _, validRole := range validRoles { if role == validRole { return true } } return false } func (s *UserAuthServiceImpl) getDefaultPermissions(role domain.UserRole) []string { switch role { case domain.UserRoleAdmin: return []string{ "manage_users", "manage_tenders", "manage_companies", "manage_customers", "view_reports", "manage_system", "manage_sources", } case domain.UserRoleModerator: return []string{ "manage_tenders", "manage_companies", "view_customers", "view_reports", "manage_sources", } case domain.UserRoleSupport: return []string{ "view_customers", "view_tenders", "view_companies", "create_tickets", } case domain.UserRoleAnalyst: return []string{ "view_reports", "view_tenders", "view_customers", "view_companies", "export_data", } default: return []string{} } } func (s *UserAuthServiceImpl) isValidPermission(permission string) bool { validPermissions := []string{ "manage_users", "manage_tenders", "manage_companies", "manage_customers", "view_customers", "view_tenders", "view_companies", "view_reports", "manage_system", "manage_sources", "create_tickets", "export_data", } for _, validPermission := range validPermissions { if permission == validPermission { return true } } return false } func (s *UserAuthServiceImpl) removeDuplicatePermissions(permissions []string) []string { seen := make(map[string]bool) result := []string{} for _, permission := range permissions { if !seen[permission] { seen[permission] = true result = append(result, permission) } } return result }