mirror of
https://github.com/serty2005/rmser.git
synced 2026-02-04 19:02:33 -06:00
сложно поддерживать однояйцевых близнецов - desktop и TMA, подготовил к рефакторингу структуры
184 lines
5.5 KiB
Go
184 lines
5.5 KiB
Go
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)
|
||
}
|