package authinfra import ( "context" "crypto/rand" "errors" "fmt" "math/big" "time" "git-molva.ru/Molva/molva-backend/services/api_gateway/internal/cache" "github.com/google/uuid" ) const ( passwordResetOTPTTL = time.Duration(5 * time.Minute) passwordResetTokenTTL = time.Duration(1 * time.Hour) ) type CacheAuthInfraServiceConfig struct { CacheClient cache.Client } type CacheAuthInfraService struct { cacheClient cache.Client } func newCacheAuthInfraService(cfg CacheAuthInfraServiceConfig) *CacheAuthInfraService { return &CacheAuthInfraService{ cacheClient: cfg.CacheClient, } } func (s *CacheAuthInfraService) generatePasswordResetOTP(size int) (string, error) { const digits = "0123456789" otp := make([]byte, size) for i := range size { num, err := rand.Int(rand.Reader, big.NewInt(int64(len(digits)))) if err != nil { return "", err } otp[i] = digits[num.Int64()] } return string(otp), nil } func (s *CacheAuthInfraService) generatePasswordResetToken() string { return uuid.NewString() } func (s *CacheAuthInfraService) CreatePasswordResetOTP( ctx context.Context, request *PasswordResetOTPCreateRequest, ) (*PasswordResetOTPCreateResponse, error) { if request == nil { return nil, fmt.Errorf("%w: request is nil", ErrBadRequest) } if request.Email == "" { return nil, fmt.Errorf("%w: email is required", ErrBadRequest) } otp, err := s.generatePasswordResetOTP(4) if err != nil { return nil, fmt.Errorf("%w: error generating OTP key: %v", ErrInternal, err) } if err := s.cacheClient.Set(ctx, request.Email, cache.PswResetOTPValueType, otp, passwordResetOTPTTL); err != nil { return nil, fmt.Errorf("%w: error setting OTP key to cache: %v", ErrInternal, err) } return &PasswordResetOTPCreateResponse{ OTP: otp, }, nil } func (s *CacheAuthInfraService) ValidatePasswordResetOTP( ctx context.Context, request *ValidatePasswordResetOTPRequest, ) (*ValidatePasswordResetOTPResponse, error) { if request == nil { return nil, fmt.Errorf("%w: request is nil", ErrBadRequest) } if request.Email == "" { return nil, fmt.Errorf("%w: email is required", ErrBadRequest) } if request.OTP == "" { return nil, fmt.Errorf("%w: OTP is required", ErrBadRequest) } val, err := s.cacheClient.Get(ctx, request.Email, cache.PswResetOTPValueType) if err != nil { if errors.Is(err, cache.ErrKeyNotFound) { return nil, ErrNotFound } return nil, fmt.Errorf("%w: error getting OTP key from cache: %w", ErrInternal, err) } if val != request.OTP { return nil, ErrUnauthorized } if err := s.cacheClient.Del(ctx, request.Email, cache.PswResetOTPValueType); err != nil { return nil, fmt.Errorf("%w: error deleting OTP key from cache: %v", ErrInternal, err) } token := s.generatePasswordResetToken() if err := s.cacheClient.Set(ctx, request.Email, cache.PswResetTokenValueType, token, passwordResetTokenTTL); err != nil { return nil, fmt.Errorf("%w: error setting token key to cache: %v", ErrInternal, err) } return &ValidatePasswordResetOTPResponse{ Token: token, }, nil } func (s *CacheAuthInfraService) ValidatePasswordResetToken( ctx context.Context, request *ValidatePasswordResetTokenRequest, ) (*ValidatePasswordResetTokenResponse, error) { if request == nil { return nil, fmt.Errorf("%w: request is nil", ErrBadRequest) } if request.Email == "" { return nil, fmt.Errorf("%w: email is required", ErrBadRequest) } if request.Token == "" { return nil, fmt.Errorf("%w: token is required", ErrBadRequest) } val, err := s.cacheClient.Get(ctx, request.Email, cache.PswResetTokenValueType) if err != nil { if errors.Is(err, cache.ErrKeyNotFound) { return nil, ErrNotFound } return nil, fmt.Errorf("%w: error getting token key from cache: %w", ErrInternal, err) } if val != request.Token { return nil, ErrUnauthorized } if err := s.cacheClient.Del(ctx, request.Email, cache.PswResetTokenValueType); err != nil { return nil, fmt.Errorf("%w: error deleting token key from cache: %v", ErrInternal, err) } return &ValidatePasswordResetTokenResponse{}, nil }