All Downloads are FREE. Search and download functionalities are using the official Maven repository.

im.actor.server.activation.ActivationContext.scala Maven / Gradle / Ivy

The newest version!
package im.actor.server.activation

import akka.actor.ActorSystem
import akka.http.scaladsl.util.FastFuture
import cats.data.Xor
import im.actor.server.activation.common._
import im.actor.server.db.DbExtension
import im.actor.server.model.{ AuthEmailTransaction, AuthPhoneTransaction, AuthTransactionBase, ExpirableCode }
import im.actor.util.cache.CacheHelpers

import scala.concurrent.Future

final class ActivationContext(implicit system: ActorSystem) extends CodeGen {

  import system.dispatcher
  import ActivationProviders._
  import CacheHelpers._

  private val db = DbExtension(system).db
  private val providers = getProviders()

  require(providers exists { case (k, _) ⇒ k != Internal }, "Should be at least one external activation provider")

  private val optInternalProvider = providers.get(Internal)
  private val optSmsProvider = providers.get(Sms)
  private val optCallProvider = providers.get(Call)
  private val optSmtpProvider = providers.get(Smtp)

  private val MaxCacheSize = 1000L
  private implicit val codesCache = createCache[String, Code](MaxCacheSize)

  /**
   * We don't care about result of sending internal code.
   * But we do care about sending code via external provider.
   * We also don't show "Try to send code later" warning to end users.
   */
  def send(txHash: String, codeTemplate: Code): Future[CodeFailure Xor Unit] = {
    val code = getCachedOrElsePut(txHash, generateCode(codeTemplate))
    (for {
      _ ← trySend(optInternalProvider, txHash, code, logFailure = false)
      result ← code match {
        case s: SmsCode   ⇒ trySend(optSmsProvider, txHash, s)
        case e: EmailCode ⇒ trySend(optSmtpProvider, txHash, e)
        case c: CallCode  ⇒ trySend(optCallProvider, txHash, c)
      }
    } yield result) map {
      case Xor.Left(BadRequest(message)) ⇒
        system.log.warning("Bad request. Message: {}. Tx hash: {}, code: {}", message, txHash, code)
        Xor.Right(())
      case error @ Xor.Left(SendFailure(message)) ⇒
        system.log.error("Send failure. Message: {}. Tx hash: {}, code: {}", message, txHash, code)
        error
      case result: Xor.Right[_] ⇒ result
    }
  }

  /**
   * If internal code validates - we are fine.
   * Otherwise - validate code sent via external provider.
   */
  def validate(tx: AuthTransactionBase with ExpirableCode, code: String): Future[ValidationResponse] =
    for {
      internalResp ← tryValidate(optInternalProvider, tx.transactionHash, code, logFailure = false)
      result ← if (internalResp == Validated) {
        FastFuture.successful(internalResp)
      } else {
        for {
          resp ← tx match {
            case _: AuthEmailTransaction ⇒ tryValidate(optSmtpProvider, tx.transactionHash, code)
            case _: AuthPhoneTransaction ⇒ tryValidate(optSmsProvider, tx.transactionHash, code)
          }
        } yield resp
      }
    } yield result

  /**
   * It is required to cleanup both internal and external provider.
   */
  def cleanup(tx: AuthTransactionBase with ExpirableCode): Future[Unit] =
    for {
      _ ← tryCleanup(optInternalProvider, tx.transactionHash)
      _ ← for {
        resp ← tx match {
          case _: AuthEmailTransaction ⇒ tryCleanup(optSmtpProvider, tx.transactionHash)
          case _: AuthPhoneTransaction ⇒ tryCleanup(optSmsProvider, tx.transactionHash)
        }
      } yield ()
      _ = codesCache.invalidate(tx.transactionHash)
    } yield ()

  private def trySend(optProvider: Option[ActivationProvider], txHash: String, code: Code, logFailure: Boolean = true): Future[CodeFailure Xor Unit] =
    optProvider map (_.send(txHash, code)) getOrElse {
      if (logFailure) { system.log.error(s"No provider found to handle code of type {}", code.getClass) }
      FastFuture.successful(Xor.left(SendFailure(s"No provider found to handle code of type ${code.getClass}")))
    }

  private def tryValidate(optProvider: Option[ActivationProvider], txHash: String, code: String, logFailure: Boolean = true): Future[ValidationResponse] =
    optProvider map (_.validate(txHash, code)) getOrElse {
      if (logFailure) { system.log.error(s"No provider found to validate code") }
      FastFuture.successful(InternalError)
    }

  private def tryCleanup(optProvider: Option[ActivationProvider], txHash: String): Future[Unit] =
    optProvider map (_.cleanup(txHash)) getOrElse FastFuture.successful(())
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy