package services import ( "context" "fmt" "golang.org/x/crypto/bcrypt" "github.com/google/uuid" "github.com/yourusername/victorialogs-manager/internal/models" "github.com/yourusername/victorialogs-manager/internal/repository" "github.com/yourusername/victorialogs-manager/internal/utils" ) // AuthService handles authentication business logic type AuthService struct { userRepo *repository.UserRepository jwtManager *utils.JWTManager } // NewAuthService creates a new auth service func NewAuthService(userRepo *repository.UserRepository, jwtManager *utils.JWTManager) *AuthService { return &AuthService{ userRepo: userRepo, jwtManager: jwtManager, } } // LoginRequest represents a login request type LoginRequest struct { Username string `json:"username" binding:"required"` Password string `json:"password" binding:"required"` } // LoginResponse represents a login response type LoginResponse struct{ AccessToken string `json:"access_token"` RefreshToken string `json:"refresh_token"` User *models.User `json:"user"` } // Login authenticates a user and returns JWT tokens func (s *AuthService) Login(ctx context.Context, req *LoginRequest) (*LoginResponse, error) { // Get user by username user, err := s.userRepo.GetByUsername(ctx, req.Username) if err != nil { return nil, fmt.Errorf("invalid credentials") } // Check if user is active if !user.IsActive { return nil, fmt.Errorf("user account is disabled") } // Verify password err = bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(req.Password)) if err != nil { return nil, fmt.Errorf("invalid credentials") } // Generate access token accessToken, err := s.jwtManager.GenerateAccessToken(user) if err != nil { return nil, fmt.Errorf("failed to generate access token: %w", err) } // Generate refresh token refreshToken, err := s.jwtManager.GenerateRefreshToken(user.ID) if err != nil { return nil, fmt.Errorf("failed to generate refresh token: %w", err) } // Clear password hash from response user.PasswordHash = "" return &LoginResponse{ AccessToken: accessToken, RefreshToken: refreshToken, User: user, }, nil } // RefreshTokenRequest represents a refresh token request type RefreshTokenRequest struct { RefreshToken string `json:"refresh_token" binding:"required"` } // RefreshTokenResponse represents a refresh token response type RefreshTokenResponse struct { AccessToken string `json:"access_token"` RefreshToken string `json:"refresh_token"` } // RefreshToken generates new tokens from a refresh token func (s *AuthService) RefreshToken(ctx context.Context, req *RefreshTokenRequest) (*RefreshTokenResponse, error) { // Validate refresh token userID, err := s.jwtManager.ValidateRefreshToken(req.RefreshToken) if err != nil { return nil, fmt.Errorf("invalid refresh token: %w", err) } // Get user user, err := s.userRepo.GetByID(ctx, userID) if err != nil { return nil, fmt.Errorf("user not found") } // Check if user is active if !user.IsActive { return nil, fmt.Errorf("user account is disabled") } // Generate new access token accessToken, err := s.jwtManager.GenerateAccessToken(user) if err != nil { return nil, fmt.Errorf("failed to generate access token: %w", err) } // Generate new refresh token refreshToken, err := s.jwtManager.GenerateRefreshToken(user.ID) if err != nil { return nil, fmt.Errorf("failed to generate refresh token: %w", err) } return &RefreshTokenResponse{ AccessToken: accessToken, RefreshToken: refreshToken, }, nil } // GetCurrentUser returns the current user from claims func (s *AuthService) GetCurrentUser(ctx context.Context, userID string) (*models.User, error) { user, err := s.userRepo.GetByID(ctx, userID) if err != nil { return nil, fmt.Errorf("user not found") } // Clear password hash user.PasswordHash = "" return user, nil } // CreateUserRequest represents a user creation request type CreateUserRequest struct { Username string `json:"username" binding:"required,min=3,max=50"` Email string `json:"email" binding:"required,email"` Password string `json:"password" binding:"required,min=8"` Role models.Role `json:"role" binding:"required"` } // CreateUser creates a new user (admin only) func (s *AuthService) CreateUser(ctx context.Context, req *CreateUserRequest) (*models.User, error) { // Check if username exists exists, err := s.userRepo.UsernameExists(ctx, req.Username) if err != nil { return nil, fmt.Errorf("failed to check username: %w", err) } if exists { return nil, fmt.Errorf("username already exists") } // Check if email exists exists, err = s.userRepo.EmailExists(ctx, req.Email) if err != nil { return nil, fmt.Errorf("failed to check email: %w", err) } if exists { return nil, fmt.Errorf("email already exists") } // Hash password hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost) if err != nil { return nil, fmt.Errorf("failed to hash password: %w", err) } // Create user user := &models.User{ ID: uuid.New().String(), Username: req.Username, Email: req.Email, PasswordHash: string(hashedPassword), Role: req.Role, IsActive: true, } err = s.userRepo.Create(ctx, user) if err != nil { return nil, fmt.Errorf("failed to create user: %w", err) } // Clear password hash user.PasswordHash = "" return user, nil } // ChangePasswordRequest represents a password change request type ChangePasswordRequest struct { OldPassword string `json:"old_password" binding:"required"` NewPassword string `json:"new_password" binding:"required,min=8"` } // ChangePassword changes a user's password func (s *AuthService) ChangePassword(ctx context.Context, userID string, req *ChangePasswordRequest) error { // Get user user, err := s.userRepo.GetByID(ctx, userID) if err != nil { return fmt.Errorf("user not found") } // Verify old password err = bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(req.OldPassword)) if err != nil { return fmt.Errorf("invalid old password") } // Hash new password hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.NewPassword), bcrypt.DefaultCost) if err != nil { return fmt.Errorf("failed to hash password: %w", err) } // Update password err = s.userRepo.UpdatePassword(ctx, userID, string(hashedPassword)) if err != nil { return fmt.Errorf("failed to update password: %w", err) } return nil }