package auth import ( "crypto/rand" "encoding/hex" "errors" "fmt" "time" "rmser/internal/domain/account" "rmser/internal/transport/ws" "rmser/pkg/logger" "github.com/golang-jwt/jwt/v5" "github.com/google/uuid" "go.uber.org/zap" ) // Claims представляет JWT claims для токена авторизации type Claims struct { UserID uuid.UUID `json:"user_id"` TelegramID int64 `json:"telegram_id"` jwt.RegisteredClaims } // Service представляет сервис авторизации для desktop auth type Service struct { accountRepo account.Repository wsServer *ws.Server secretKey string } // NewService создает новый экземпляр сервиса авторизации func NewService(accountRepo account.Repository, wsServer *ws.Server, secretKey string) *Service { return &Service{ accountRepo: accountRepo, wsServer: wsServer, secretKey: secretKey, } } // InitDesktopAuth генерирует уникальный session_id для desktop авторизации func (s *Service) InitDesktopAuth() (string, error) { sessionID := uuid.New().String() logger.Log.Info("Инициализация desktop авторизации", zap.String("session_id", sessionID), ) return sessionID, nil } // ConfirmDesktopAuth подтверждает авторизацию и отправляет токен через Socket.IO func (s *Service) ConfirmDesktopAuth(sessionID string, telegramID int64) error { // Ищем пользователя по Telegram ID user, err := s.accountRepo.GetUserByTelegramID(telegramID) if err != nil { logger.Log.Error("Пользователь не найден", zap.Int64("telegram_id", telegramID), zap.Error(err), ) return errors.New("пользователь не найден") } // Генерируем JWT токен token, err := s.generateJWTToken(user) if err != nil { logger.Log.Error("Ошибка генерации JWT токена", zap.String("user_id", user.ID.String()), zap.Error(err), ) return err } // Отправляем токен через WebSocket s.wsServer.SendAuthSuccess(sessionID, token, *user) logger.Log.Info("Desktop авторизация подтверждена", zap.String("session_id", sessionID), zap.String("user_id", user.ID.String()), zap.Int64("telegram_id", telegramID), ) return nil } // generateJWTToken генерирует JWT токен для пользователя func (s *Service) generateJWTToken(user *account.User) (string, error) { // Создаем claims с данными пользователя claims := Claims{ UserID: user.ID, TelegramID: user.TelegramID, RegisteredClaims: jwt.RegisteredClaims{ ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)), // Токен действителен 24 часа IssuedAt: jwt.NewNumericDate(time.Now()), NotBefore: jwt.NewNumericDate(time.Now()), }, } // Создаем токен с claims token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) // Подписываем токен секретным ключом tokenString, err := token.SignedString([]byte(s.secretKey)) if err != nil { return "", err } return tokenString, nil } // generateRefreshToken генерирует безопасный refresh токен func (s *Service) generateRefreshToken() (string, error) { b := make([]byte, 32) if _, err := rand.Read(b); err != nil { return "", err } return hex.EncodeToString(b), nil } // CreateWebSession создает новую веб-сессию для пользователя func (s *Service) CreateWebSession(userID uuid.UUID, userAgent, ip string) (*account.Session, error) { token, err := s.generateRefreshToken() if err != nil { logger.Log.Error("failed to generate refresh token", zap.Error(err)) return nil, err } session := &account.Session{ UserID: userID, RefreshToken: token, UserAgent: userAgent, IP: ip, ExpiresAt: time.Now().Add(7 * 24 * time.Hour), } if err := s.accountRepo.CreateSession(session); err != nil { logger.Log.Error("failed to create session", zap.Error(err)) return nil, err } return session, nil } // ValidateAndExtendSession проверяет и продлевает сессию (sliding window) func (s *Service) ValidateAndExtendSession(token string) (*account.User, error) { session, err := s.accountRepo.GetSessionByToken(token) if err != nil { logger.Log.Error("failed to get session", zap.Error(err)) return nil, err } if session == nil { return nil, fmt.Errorf("session not found") } if time.Now().After(session.ExpiresAt) { // Сессия истекла, удаляем её s.accountRepo.DeleteSession(token) return nil, fmt.Errorf("session expired") } // Sliding expiration: обновляем expiresAt на 7 дней от текущего момента newExpiry := time.Now().Add(7 * 24 * time.Hour) if err := s.accountRepo.UpdateSessionExpiry(session.ID, newExpiry); err != nil { logger.Log.Error("failed to update session expiry", zap.Error(err)) // Не критичная ошибка, продолжаем } // Получаем пользователя user, err := s.accountRepo.GetUserByID(session.UserID) if err != nil { logger.Log.Error("failed to get user", zap.Error(err)) return nil, err } return user, nil } // DeleteSession удаляет сессию пользователя func (s *Service) DeleteSession(token string) error { return s.accountRepo.DeleteSession(token) }