package service import ( "context" "crypto/rand" "encoding/base64" "errors" "fmt" "log" "tailly_back_v2/internal/domain" "tailly_back_v2/internal/repository" "tailly_back_v2/pkg/auth" "time" ) type AuthService interface { Register(ctx context.Context, input RegisterInput) (*domain.User, error) Login(ctx context.Context, email, password string) (*domain.Tokens, error) RefreshTokens(ctx context.Context, refreshToken string) (*domain.Tokens, error) ConfirmEmail(ctx context.Context, token string) (bool, error) } type authService struct { userRepo repository.UserRepository tokenAuth auth.TokenAuth mailer MailService } func NewAuthService(userRepo repository.UserRepository, tokenAuth *auth.TokenAuth, mailer MailService) AuthService { return &authService{ userRepo: userRepo, tokenAuth: *tokenAuth, mailer: mailer, } } type RegisterInput struct { Username string Email string Password string } func (s *authService) Register(ctx context.Context, input RegisterInput) (*domain.User, error) { _, err := s.userRepo.GetByEmail(ctx, input.Email) if err == nil { return nil, errors.New("user with this email already exists") } else if err != repository.ErrUserNotFound { return nil, err } // Генерация токена подтверждения token := make([]byte, 32) if _, err := rand.Read(token); err != nil { return nil, fmt.Errorf("failed to generate confirmation token: %w", err) } confirmationToken := base64.URLEncoding.EncodeToString(token) hashedPassword, err := auth.HashPassword(input.Password) if err != nil { return nil, err } user := &domain.User{ Username: input.Username, Email: input.Email, Password: hashedPassword, EmailConfirmationToken: confirmationToken, CreatedAt: time.Now(), UpdatedAt: time.Now(), } if err := s.userRepo.Create(ctx, user); err != nil { return nil, err } go func() { if err := s.mailer.SendConfirmationEmail(user.Email, confirmationToken); err != nil { log.Printf("Async email send failed: %v", err) } }() return user, nil } func (s *authService) Login(ctx context.Context, email, password string) (*domain.Tokens, error) { user, err := s.userRepo.GetByEmail(ctx, email) if err != nil { if err == repository.ErrUserNotFound { return nil, errors.New("invalid credentials") } return nil, err } if !auth.CheckPasswordHash(password, user.Password) { return nil, errors.New("invalid credentials") } // Проверка подтверждения email (добавьте эту проверку) if user.EmailConfirmedAt == nil { return nil, errors.New("email not confirmed") } tokens, err := s.tokenAuth.GenerateTokens(user.ID) if err != nil { return nil, err } //Получаем информацию об устройстве (если используется) //if deviceInfo, ok := ctx.Value("device_info").(*domain.Device); ok { // // Здесь должна быть проверка isNewDevice и вызов notificationService // // if isNewDevice { ... } //} return tokens, nil } func (s *authService) RefreshTokens(ctx context.Context, refreshToken string) (*domain.Tokens, error) { // Валидируем старый refresh token userID, err := s.tokenAuth.ValidateRefreshToken(refreshToken) if err != nil { log.Printf("Refresh token validation failed: %v", err) return nil, fmt.Errorf("invalid refresh token: %w", err) } // Проверяем существование пользователя user, err := s.userRepo.GetByID(ctx, userID) if err != nil { return nil, fmt.Errorf("user not found: %w", err) } // Генерируем новые токены, используя старый refresh token tokens, err := s.tokenAuth.GenerateTokens(user.ID, refreshToken) if err != nil { return nil, fmt.Errorf("failed to generate tokens: %w", err) } // Обновляем время последней активности пользователя //if err := s.userRepo.UpdateLastActive(ctx, user.ID); err != nil { // log.Printf("Failed to update user last active time: %v", err) //} return tokens, nil } func (s *authService) ConfirmEmail(ctx context.Context, token string) (bool, error) { user, err := s.userRepo.GetByConfirmationToken(ctx, token) if err != nil { return false, nil } now := time.Now() user.EmailConfirmedAt = &now user.EmailConfirmationToken = "" err = s.userRepo.Update(ctx, user) if err != nil { return false, err } return true, nil }