diff --git a/actor-server/actor-activation/src/main/resources/reference.conf b/actor-server/actor-activation/src/main/resources/reference.conf index bba3b7ac6b..4cb08cde93 100644 --- a/actor-server/actor-activation/src/main/resources/reference.conf +++ b/actor-server/actor-activation/src/main/resources/reference.conf @@ -17,6 +17,14 @@ services { } } + magfa { + service : "" + domain : "" + username: "" + password : "" + from: "" + } + actor-activation { uri: "https://gate.actor.im" auth-token: "" diff --git a/actor-server/actor-activation/src/main/scala/im/actor/server/activation/magfa/MagfaProvider.scala b/actor-server/actor-activation/src/main/scala/im/actor/server/activation/magfa/MagfaProvider.scala new file mode 100644 index 0000000000..a10d69fb23 --- /dev/null +++ b/actor-server/actor-activation/src/main/scala/im/actor/server/activation/magfa/MagfaProvider.scala @@ -0,0 +1,60 @@ +package im.actor.server.activation.magfa + +import akka.actor.ActorSystem +import akka.pattern.ask +import akka.util.Timeout +import cats.data.Xor +import im.actor.config.ActorConfig +import im.actor.server.activation.common.ActivationStateActor.{ ForgetSentCode, Send, SendAck } +import im.actor.server.activation.common._ +import im.actor.server.db.DbExtension +import im.actor.server.model.AuthPhoneTransaction +import im.actor.server.persist.auth.AuthTransactionRepo +import im.actor.server.sms._ +import im.actor.util.misc.PhoneNumberUtils.isTestPhone + +import scala.concurrent.Future +import scala.concurrent.duration._ + +private[activation] final class MagfaProvider(implicit system: ActorSystem) extends ActivationProvider with CommonAuthCodes { + + protected val activationConfig = ActivationConfig.load.getOrElse(throw new RuntimeException("Failed to load activation config")) + protected val db = DbExtension(system).db + protected implicit val ec = system.dispatcher + + private val magfaClient = new MagfaClient(ActorConfig.load().getConfig("services.magfa")) + private val smsEngine = new MagfaSmsEngine(magfaClient) + + private implicit val timeout = Timeout(20.seconds) + + private val smsStateActor = system.actorOf(ActivationStateActor.props[Long, SmsCode]( + repeatLimit = activationConfig.repeatLimit, + sendAction = (code: SmsCode) ⇒ smsEngine.sendCode(code.phone, code.code), + id = (code: SmsCode) ⇒ code.phone + ), "magfa-sms-state") + + override def send(txHash: String, code: Code): Future[CodeFailure Xor Unit] = code match { + case s: SmsCode ⇒ + for { + resp ← if (isTestPhone(s.phone)) + Future.successful(Xor.right(())) + else + (smsStateActor ? Send(code)).mapTo[SendAck].map(_.result) + _ ← createAuthCodeIfNeeded(resp, txHash, code.code) + } yield resp + case other ⇒ throw new RuntimeException(s"This provider can't handle code of type: ${other.getClass}") + } + + override def cleanup(txHash: String): Future[Unit] = { + for { + ac ← db.run(AuthTransactionRepo.findChildren(txHash)) + _ = ac match { + case Some(x: AuthPhoneTransaction) ⇒ + smsStateActor ! ForgetSentCode.phone(x.phoneNumber) + case _ ⇒ + } + _ ← deleteAuthCode(txHash) + } yield () + } + +} \ No newline at end of file diff --git a/actor-server/actor-sms/src/main/resources/reference.conf b/actor-server/actor-sms/src/main/resources/reference.conf index 0bee43de44..140982c73b 100644 --- a/actor-server/actor-sms/src/main/resources/reference.conf +++ b/actor-server/actor-sms/src/main/resources/reference.conf @@ -6,4 +6,8 @@ sms { clickatell { } + + magfa { + + } } diff --git a/actor-server/actor-sms/src/main/scala/im/actor/server/sms/MagfaClient.scala b/actor-server/actor-sms/src/main/scala/im/actor/server/sms/MagfaClient.scala new file mode 100644 index 0000000000..9c3ba2677e --- /dev/null +++ b/actor-server/actor-sms/src/main/scala/im/actor/server/sms/MagfaClient.scala @@ -0,0 +1,53 @@ +package im.actor.server.sms + +import java.net.URLEncoder + +import akka.actor.ActorSystem +import com.ning.http.client.Response +import com.typesafe.config.Config +import dispatch.{ Http, url } + +import scala.concurrent.{ ExecutionContext, Future } +import scala.util.Failure + +final class MagfaClient(config: Config)(implicit system: ActorSystem) { + + private lazy val http = new Http() + private val Utf8Encoding = "UTF-8" + system registerOnTermination http.shutdown() + + private implicit val ec: ExecutionContext = system.dispatcher + + private val BaseUrl = config.getString("url") + private val ResourcePath = "/magfaHttpService?" + + def sendSmsCode(phoneNumber: Long, code: String): Future[Unit] = { + postRequest(ResourcePath, Map( + "service" -> config.getString("service"), + "domain" -> config.getString("domain"), + "username" -> config.getString("username"), + "password" -> config.getString("password"), + "from" → config.getString("from"), + "to" → phoneNumber.toString, + "message" → code + )) map { _ ⇒ + system.log.debug("Message sent via magfa") + } + } + + private def postRequest(resourcePath: String, params: Map[String, String]): Future[Response] = { + val body = params.map(p ⇒ s"${p._1}=${URLEncoder.encode(p._2, Utf8Encoding)}").mkString("&") + val request = url(BaseUrl + resourcePath + body) + + http(request).map { resp ⇒ + if (resp.getStatusCode < 199 || resp.getStatusCode > 299) { + throw new RuntimeException(s"Response has code ${resp.getStatusCode}. [${resp.getResponseBody}]") + } else { + resp + } + } andThen { + case Failure(e) ⇒ + system.log.error(e, "Failed to make request to telesign") + } + } +} \ No newline at end of file diff --git a/actor-server/actor-sms/src/main/scala/im/actor/server/sms/MagfaSmsEngine.scala b/actor-server/actor-sms/src/main/scala/im/actor/server/sms/MagfaSmsEngine.scala new file mode 100644 index 0000000000..40ea64c759 --- /dev/null +++ b/actor-server/actor-sms/src/main/scala/im/actor/server/sms/MagfaSmsEngine.scala @@ -0,0 +1,7 @@ +package im.actor.server.sms + +import scala.concurrent.Future + +final class MagfaSmsEngine(magfaSmsClient: MagfaClient) extends AuthSmsEngine { + override def sendCode(phoneNumber: Long, message: String): Future[Unit] = magfaSmsClient.sendSmsCode(phoneNumber, message) +} \ No newline at end of file