Files
rmser/internal/services/auth/service.go
SERTY ea1e5bbf6a 0302-добавил куки и сломал десктоп авторизацию.
сложно поддерживать однояйцевых близнецов - desktop и TMA, подготовил к рефакторингу структуры
2026-02-03 09:32:02 +03:00

184 lines
5.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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)
}