package service import ( "context" "crypto/rand" "encoding/base64" "errors" "fmt" "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) 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 } if err := s.mailer.SendConfirmationEmail(user.Email, confirmationToken); err != nil { return nil, fmt.Errorf("failed to send confirmation email: %w", 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) { userID, err := s.tokenAuth.ValidateRefreshToken(refreshToken) if err != nil { return nil, errors.New("invalid refresh token") } if _, err = s.userRepo.GetByID(ctx, userID); err != nil { return nil, errors.New("user not found") } return s.tokenAuth.GenerateTokens(userID) } func (s *authService) ConfirmEmail(ctx context.Context, token string) error { user, err := s.userRepo.GetByConfirmationToken(ctx, token) if err != nil { return fmt.Errorf("invalid or expired confirmation token") } now := time.Now() user.EmailConfirmedAt = &now user.EmailConfirmationToken = "" return s.userRepo.Update(ctx, user) }