tailly_back_v2/internal/service/recovery_service.go
madipo2611 6f5298d420 v0.0.3
2025-05-03 02:37:08 +03:00

134 lines
3.7 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 service
import (
"context"
"crypto/rand"
"encoding/base64"
"errors"
"tailly_back_v2/internal/domain"
"tailly_back_v2/internal/repository"
"time"
)
type RecoveryService interface {
InitiateRecovery(ctx context.Context, email string, newDevice *domain.Device) (string, error)
VerifyRecovery(ctx context.Context, token string) (*domain.Session, error)
AddRecoveryMethod(ctx context.Context, userID int, method *domain.RecoveryMethod) error
GetRecoveryMethods(ctx context.Context, userID int) ([]*domain.RecoveryMethod, error)
}
type recoveryService struct {
recoveryRepo repository.RecoveryRepository
userRepo repository.UserRepository
sessionRepo repository.SessionRepository
deviceRepo repository.DeviceRepository
mailer MailService
}
func (s *recoveryService) AddRecoveryMethod(ctx context.Context, userID int, method *domain.RecoveryMethod) error {
//TODO implement me
panic("implement me")
}
func (s *recoveryService) GetRecoveryMethods(ctx context.Context, userID int) ([]*domain.RecoveryMethod, error) {
//TODO implement me
panic("implement me")
}
func NewRecoveryService(recoveryRepo repository.RecoveryRepository, userRepo repository.UserRepository, sessionRepo repository.SessionRepository, deviceRepo repository.DeviceRepository, mailer MailService) RecoveryService {
return &recoveryService{
recoveryRepo: recoveryRepo,
userRepo: userRepo,
sessionRepo: sessionRepo,
deviceRepo: deviceRepo,
mailer: mailer,
}
}
func (s *recoveryService) InitiateRecovery(ctx context.Context, email string, newDevice *domain.Device) (string, error) {
user, err := s.userRepo.GetByEmail(ctx, email)
if err != nil {
return "", errors.New("user not found")
}
// Проверяем, что у пользователя есть методы восстановления
methods, err := s.recoveryRepo.GetMethods(ctx, user.ID)
if err != nil || len(methods) == 0 {
return "", errors.New("no recovery methods available")
}
// Генерируем токен восстановления
token := make([]byte, 32)
if _, err := rand.Read(token); err != nil {
return "", err
}
tokenStr := base64.URLEncoding.EncodeToString(token)
req := &domain.RecoveryRequest{
UserID: user.ID,
Token: tokenStr,
NewDevice: newDevice,
Status: "pending",
CreatedAt: time.Now(),
ExpiresAt: time.Now().Add(1 * time.Hour),
}
if err := s.recoveryRepo.SaveRequest(ctx, req); err != nil {
return "", err
}
// Отправляем письмо на все методы восстановления
for _, method := range methods {
switch method.MethodType {
case "email":
if err := s.mailer.SendRecoveryEmail(
method.Value,
tokenStr,
newDevice.IPAddress,
newDevice.UserAgent,
); err != nil {
return "", nil
}
// case "phone": отправка SMS
}
}
return tokenStr, nil
}
func (s *recoveryService) VerifyRecovery(ctx context.Context, token string) (*domain.Session, error) {
req, err := s.recoveryRepo.GetRequestByToken(ctx, token)
if err != nil {
return nil, errors.New("invalid recovery token")
}
if time.Now().After(req.ExpiresAt) {
return nil, errors.New("recovery token expired")
}
// Регистрируем новое устройство
if err := s.deviceRepo.Save(ctx, req.NewDevice); err != nil {
return nil, err
}
// Создаем сессию
session := &domain.Session{
UserID: req.UserID,
DeviceID: req.NewDevice.ID,
StartedAt: time.Now(),
}
if err := s.sessionRepo.Save(ctx, session); err != nil {
return nil, err
}
// Помечаем запрос как выполненный
req.Status = "completed"
if err := s.recoveryRepo.UpdateRequest(ctx, req); err != nil {
return nil, err
}
return session, nil
}