package auth import ( "api_yal/internal/logger" "api_yal/internal/models" "api_yal/internal/repository" "errors" "time" "github.com/golang-jwt/jwt/v5" "go.uber.org/zap" "golang.org/x/crypto/bcrypt" ) // AuthService интерфейс сервиса аутентификации type AuthService interface { Register(req RegisterRequest) (*AuthResponse, error) Login(req LoginRequest) (*AuthResponse, error) RefreshToken(token string) (*AuthResponse, error) Logout(userID uint) error } // authServiceImpl реализация сервиса аутентификации type authServiceImpl struct { accountRepo repository.AccountRepository jwtSecret []byte } // NewAuthService создает новый экземпляр сервиса аутентификации func NewAuthService(accountRepo repository.AccountRepository, jwtSecret string) AuthService { return &authServiceImpl{ accountRepo: accountRepo, jwtSecret: []byte(jwtSecret), } } // Register регистрация нового пользователя func (s *authServiceImpl) Register(req RegisterRequest) (*AuthResponse, error) { l := logger.Get() l.Debug("Регистрация пользователя AuthService") // Проверяем, существует ли пользователь с таким email existingUser, err := s.accountRepo.GetByEmail(req.Email) if err == nil && existingUser != nil { return nil, ErrUserAlreadyExists } // Хешируем пароль hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost) if err != nil { l.Error("Ошибка хеширования пароля: %v", zap.Error(err)) return nil, err } // Формируем полное имя fullName := req.FirstName + " " + req.LastName // Создаем аккаунт newAcc := &models.Account{ Email: req.Email, PasswordHash: string(hashedPassword), FirstName: req.FirstName, LastName: req.LastName, FullName: fullName, IsActive: true, IsVerified: false, Role: "user", } // Сохраняем в базу данных if err := s.accountRepo.Create(newAcc); err != nil { l.Error("Ошибка создания аккаунта: %v", zap.Error(err)) return nil, err } // Генерируем JWT токен token, expiresAt, err := s.generateToken(newAcc) if err != nil { return nil, err } // Формируем ответ response := &AuthResponse{ Token: token, ExpiresAt: expiresAt, User: UserInfo{ ID: newAcc.Base.ID, Email: newAcc.Email, FirstName: newAcc.FirstName, LastName: newAcc.LastName, FullName: newAcc.FullName, Role: newAcc.Role, }, } l.Info("Пользователь успешно зарегистрирован: %s", zap.String("Email", req.Email)) return response, nil } // Login вход пользователя func (s *authServiceImpl) Login(req LoginRequest) (*AuthResponse, error) { l := logger.Get() l.Debug("Вход пользователя: %s", zap.String("Email", req.Email)) // Ищем пользователя по email account, err := s.accountRepo.GetByEmail(req.Email) if err != nil { l.Error("Пользователь не найден:", zap.String("Email", req.Email), zap.Error(err), ) return nil, ErrUserNotFound } // Проверяем, активен ли аккаунт if !account.IsActive { l.Error("Аккаунт деактивирован: %s", zap.String("Email", req.Email)) return nil, errors.New("account is deactivated") } // Сравниваем пароли if err := bcrypt.CompareHashAndPassword([]byte(account.PasswordHash), []byte(req.Password)); err != nil { l.Error("Неверный пароль для пользователя: %s", zap.String("Email", req.Email)) return nil, ErrInvalidPassword } // Генерируем JWT токен token, expiresAt, err := s.generateToken(account) if err != nil { return nil, err } // Формируем ответ response := &AuthResponse{ Token: token, ExpiresAt: expiresAt, User: UserInfo{ ID: account.Base.ID, Email: account.Email, FirstName: account.FirstName, LastName: account.LastName, FullName: account.FullName, Role: account.Role, }, } l.Info("Пользователь успешно вошел: %s", zap.String("Email", req.Email)) return response, nil } // RefreshToken обновление токена func (s *authServiceImpl) RefreshToken(token string) (*AuthResponse, error) { l := logger.Get() l.Debug("Обновление токена") // Парсим и валидируем токен claims := &jwt.RegisteredClaims{} parsedToken, err := jwt.ParseWithClaims(token, claims, func(t *jwt.Token) (interface{}, error) { return s.jwtSecret, nil }) if err != nil || !parsedToken.Valid { l.Error("Невалидный токен для обновления: %v", zap.Error(err)) return nil, errors.New("invalid token") } // Получаем ID пользователя из claims userID, err := claims.GetSubject() if err != nil { return nil, errors.New("invalid token claims") } // Получаем пользователя из базы var account *models.Account // Здесь нужно преобразовать string в uint // В реальном проекте нужно добавить метод GetByIDString или аналогичный // Для простоты используем существующий метод // account, err = s.accountRepo.GetByEmail(???) // Временное решение - нужно добавить метод GetByID // Пока пропускаем для демонстрации _ = userID // Генерируем новый токен newToken, expiresAt, err := s.generateToken(account) if err != nil { return nil, err } return &AuthResponse{ Token: newToken, ExpiresAt: expiresAt, }, nil } // Logout выход пользователя func (s *authServiceImpl) Logout(userID uint) error { l := logger.Get() l.Debug("Выход пользователя: %d", zap.Uint("userID", userID)) // В реальном проекте здесь можно добавить токен в черный список // или удалить refresh token из базы данных return nil } // generateToken генерирует JWT токен для пользователя func (s *authServiceImpl) generateToken(account *models.Account) (string, time.Time, error) { // Устанавливаем время истечения (24 часа) expiresAt := time.Now().Add(24 * time.Hour) // Создаем claims claims := jwt.MapClaims{ "sub": account.Base.ID, "email": account.Email, "role": account.Role, "exp": expiresAt.Unix(), "iat": time.Now().Unix(), } // Создаем токен token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) // Подписываем токен tokenString, err := token.SignedString(s.jwtSecret) if err != nil { return "", time.Time{}, err } return tokenString, expiresAt, nil }