134 lines
3.7 KiB
Go
134 lines
3.7 KiB
Go
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
|
||
}
|