package service import ( "context" "errors" "fmt" "messenger/internal/models" "messenger/internal/pkg/logger" "messenger/internal/pkg/validator" "messenger/internal/repository" "time" "github.com/golang-jwt/jwt/v5" "golang.org/x/crypto/bcrypt" ) type AuthService struct { userRepo repository.UserRepository profileRepo repository.ProfileRepository jwtSecret []byte jwtExpiry int64 } type Claims struct { UserID int64 `json:"user_id"` Login string `json:"login"` Role string `json:"role"` jwt.RegisteredClaims } func NewAuthService(userRepo repository.UserRepository, profileRepo repository.ProfileRepository, jwtSecret []byte, jwtExpiry int64) *AuthService { return &AuthService{ userRepo: userRepo, profileRepo: profileRepo, jwtSecret: jwtSecret, jwtExpiry: jwtExpiry, } } // Register регистрирует нового пользователя func (s *AuthService) Register(ctx context.Context, login, password string) (*models.User, string, error) { // Валидация входных данных if !validator.ValidateLogin(login) { return nil, "", errors.New("invalid login: must be 3-32 characters, only letters, numbers and underscore") } if !validator.ValidatePassword(password) { return nil, "", errors.New("invalid password: must be at least 6 characters") } // Проверка существования пользователя exists, err := s.userRepo.Exists(ctx, login) if err != nil { logger.Error("Failed to check user existence", "error", err) return nil, "", errors.New("internal server error") } if exists { return nil, "", errors.New("user already exists") } // Хэширование пароля passwordHash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) if err != nil { logger.Error("Failed to hash password", "error", err) return nil, "", errors.New("internal server error") } // Создание пользователя user := &models.User{ Login: login, PasswordHash: string(passwordHash), Role: models.RoleUser, CreatedAt: time.Now(), } if err := s.userRepo.Create(ctx, user); err != nil { logger.Error("Failed to create user", "error", err) return nil, "", errors.New("internal server error") } // Создание профиля profile := &models.Profile{ UserID: user.ID, } if err := s.profileRepo.Create(ctx, profile); err != nil { logger.Error("Failed to create profile", "error", err) // Не фатально, профиль можно создать позже } // Генерация JWT токена token, err := s.generateToken(user) if err != nil { logger.Error("Failed to generate token", "error", err) return nil, "", errors.New("internal server error") } logger.Info("User registered successfully", "user_id", user.ID, "login", user.Login) return user, token, nil } // Login аутентифицирует пользователя func (s *AuthService) Login(ctx context.Context, login, password string) (*models.User, string, error) { // Поиск пользователя user, err := s.userRepo.FindByLogin(ctx, login) if err != nil { logger.Error("Failed to find user", "error", err) return nil, "", errors.New("internal server error") } if user == nil { return nil, "", errors.New("invalid credentials") } // Проверка пароля if err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(password)); err != nil { return nil, "", errors.New("invalid credentials") } // Обновление last_seen go func() { if err := s.userRepo.UpdateLastSeen(context.Background(), user.ID); err != nil { logger.Error("Failed to update last_seen", "error", err) } }() // Генерация JWT токена token, err := s.generateToken(user) if err != nil { logger.Error("Failed to generate token", "error", err) return nil, "", errors.New("internal server error") } logger.Info("User logged in", "user_id", user.ID, "login", user.Login) return user, token, nil } // ValidateToken проверяет валидность JWT токена и возвращает пользователя func (s *AuthService) ValidateToken(tokenString string) (*models.User, error) { claims := &Claims{} token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) { if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) } return s.jwtSecret, nil }) if err != nil { return nil, errors.New("invalid token") } if !token.Valid { return nil, errors.New("invalid token") } // Получаем пользователя из БД (на случай, если роль изменилась) ctx := context.Background() user, err := s.userRepo.FindByID(ctx, claims.UserID) if err != nil { return nil, errors.New("user not found") } if user == nil { return nil, errors.New("user not found") } return user, nil } // generateToken генерирует JWT токен для пользователя func (s *AuthService) generateToken(user *models.User) (string, error) { expirationTime := time.Now().Add(time.Duration(s.jwtExpiry) * time.Hour) claims := &Claims{ UserID: user.ID, Login: user.Login, Role: string(user.Role), RegisteredClaims: jwt.RegisteredClaims{ ExpiresAt: jwt.NewNumericDate(expirationTime), IssuedAt: jwt.NewNumericDate(time.Now()), NotBefore: jwt.NewNumericDate(time.Now()), }, } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) return token.SignedString(s.jwtSecret) } // GetUserByID возвращает пользователя по ID func (s *AuthService) GetUserByID(ctx context.Context, userID int64) (*models.User, error) { return s.userRepo.FindByID(ctx, userID) } // GetUserByLogin возвращает пользователя по логину func (s *AuthService) GetUserByLogin(ctx context.Context, login string) (*models.User, error) { return s.userRepo.FindByLogin(ctx, login) }