159 lines
4.5 KiB
Go
159 lines
4.5 KiB
Go
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
|
||
}
|