Is there support for an asynchronous `PlayServiceCall`?

scala

(Elijah Rippeth) #1

I am trying to develop an OpenID authentication mechanism using existing Play components. I know that, given a play.api.mvc.RequestHeader, I can use play.api.libs.openid.OpenIdClient to authenticate a request. I also know Lagom supports PlayServiceCalls. I came up with this snippet:

import com.lightbend.lagom.scaladsl.api.transport.Forbidden
import com.lightbend.lagom.scaladsl.api.{Service, ServiceCall}
import com.lightbend.lagom.scaladsl.server.PlayServiceCall
import play.api.libs.openid.{OpenIdClient, UserInfo}
import play.api.mvc.EssentialAction

import scala.concurrent.{ExecutionContext, Future}

/**
  * A trait which will provide OpenID authentication when provided with an OpenID client.
  */
trait OpenId { self: Service =>

  /**
    * The client against which verification will be made.
    * @return
    */
  def openIdClient: OpenIdClient

  /**
    * A composable service call which will check whether a user exists given the request
    * headers. In the case the user does not exist, a Forbidden error is returned. In
    * the case that the user does exist, the returned user information from the OpenID client
    * is forwarded to the requested service endpoint call.
    *
    * @param serviceCall the requested service endpoint which requires authentication.
    * @tparam Request the type of request the intended service endpoint accepts.
    * @tparam Response the type of response the intended service endpoint produces.
    */
  def authenticated[Request, Response](
    serviceCall: UserInfo => ServiceCall[Request, Response]
  )(implicit ec: ExecutionContext) =
    PlayServiceCall[Request, Response] { wrapCall =>

      EssentialAction { reqHeader =>
        val res: Future[ServiceCall[Request, Response]] = {
          val user: Future[UserInfo] = openIdClient.verifiedId(reqHeader)
          user.map(info => serviceCall(info)).recover {
            case _: Throwable => throw Forbidden("User must be authenticated to access this service call")
          }
        }
        val wrappedAction = wrapCall(res)
        val accumulator = wrappedAction(reqHeader)
        accumulator.map(identity)
      }
    }
}

However, this doesn’t work becaue res is a Future[ServiceCall[_, _]], but wrapCall expects a ServiceCall[_, _].

Is there a way to make this operate asynchronously like ServerServiceCall.composeAsync?