OAuth2Service.scala 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. package com.weEat.services
  2. import play.api.Configuration
  3. import codes.reactive.scalatime._
  4. import com.github.t3hnar.bcrypt._
  5. import com.weEat.models.{User,Authorization}
  6. import java.time.Instant
  7. import javax.inject.{Inject,Singleton}
  8. import scala.concurrent.{ExecutionContext,Future}
  9. import scala.util.Success
  10. import scalaoauth2.provider._
  11. import org.mongodb.scala.model.Filters._
  12. import org.mongodb.scala.bson.DefaultBsonTransformers
  13. import org.mongodb.scala.bson.conversions.Bson
  14. import scala.language.implicitConversions
  15. // TOOD: Migrate to SecureSocial OAuth lib
  16. @Singleton
  17. class OAuth2Service @Inject()(db: MongoDBService, config: Configuration)
  18. extends AuthorizationHandler[User]
  19. with ProtectedResourceHandler[Authorization]
  20. with DefaultBsonTransformers {
  21. implicit val ec: ExecutionContext = ExecutionContext.global
  22. import db.withCollection
  23. override def createAccessToken(auth: AuthInfo[User]) = {
  24. println("Create token")
  25. val newAuth = Authorization(auth.user)
  26. withCollection(Authorization) {auths =>
  27. auths.insertOne(newAuth).head().map(_ => newAuth.toToken())
  28. }.flatten
  29. }
  30. val devMode = config.getOptional[Boolean](s"oauth.dev").getOrElse(false)
  31. override def validateClient(
  32. cred: Option[ClientCredential],
  33. requ: AuthorizationRequest
  34. ): Future[Boolean] = {
  35. val cc = cred.getOrElse(throw new UnauthorizedClient("username required"))
  36. requ.grantType match {
  37. case OAuthGrantType.PASSWORD => validateUsernamePassword(
  38. cc.clientId,
  39. cc.clientSecret.getOrElse(throw new AccessDenied("password required"))
  40. )
  41. case OAuthGrantType.REFRESH_TOKEN => validateUsernameRefresh(
  42. cc.clientId,
  43. Authorization.decodeToken(RefreshTokenRequest(requ).refreshToken)
  44. )
  45. case OAuthGrantType.AUTHORIZATION_CODE => throw new UnsupportedGrantType()
  46. case OAuthGrantType.CLIENT_CREDENTIALS => throw new UnsupportedGrantType()
  47. case OAuthGrantType.IMPLICIT => throw new UnsupportedGrantType()
  48. }
  49. }
  50. private def withDevModeCond(bson: Bson) =
  51. if (devMode) bson else and(bson, not(equal("devOnly", true)))
  52. private def validateUsernamePassword(
  53. username: String,
  54. pass: String
  55. ): Future[Boolean] = withCollection(User) {users =>
  56. users.find(withDevModeCond(equal("email", username)))
  57. .first()
  58. .toFutureOption()
  59. .map(res => res.map { x => pass.isBcryptedSafeBounded(x.password)}
  60. .getOrElse(Success(false))
  61. ).transform ({ _.flatten })
  62. }.flatten
  63. private def validateUsernameRefresh(username: String, refresh: Array[Byte]) =
  64. withCollection(Authorization) {auths =>
  65. auths.find(and(
  66. equal("email", username),
  67. equal("refreshToken", refresh)
  68. )).first().toFutureOption().map { _.nonEmpty }
  69. }.flatten
  70. override def findUser(
  71. cred: Option[ClientCredential],
  72. requ: AuthorizationRequest
  73. ): Future[Option[User]] = withCollection(User) {users =>
  74. users.find(withDevModeCond(equal("email", cred.getOrElse(
  75. throw new UnsupportedGrantType("client_id required")
  76. ).clientId))).first().toFutureOption()
  77. }.flatten.map({x => {println(s"findUser: $x"); x}})
  78. /* 2020-07-25: Never re-issue the same authorization token. Always generate a
  79. * new one.
  80. */
  81. override def getStoredAccessToken(auth: AuthInfo[User]) = {
  82. Future.successful(None)
  83. }
  84. private def freshAccessToken(token: Array[Byte]) = and(
  85. gt("created", Instant.now() - Authorization.accessFreshTime),
  86. equal("accessToken", token)
  87. )
  88. override def findAccessToken(token: String): Future[Option[AccessToken]] =
  89. withCollection(Authorization) {auths =>
  90. auths.find(
  91. freshAccessToken(Authorization.decodeToken(token))
  92. ).first().toFutureOption()
  93. .map(_.map(_.toToken()))
  94. }.flatten.map({x => {println(s"findAccessToken: $x"); x}})
  95. override def findAuthInfoByAccessToken(token: AccessToken) =
  96. withCollection(Authorization) {collection =>
  97. collection.find(freshAccessToken(Authorization.decodeToken(token.token)))
  98. .first()
  99. .toFutureOption()
  100. .map(_.map(auth => AuthInfo(auth, Some(auth.email), None, None)))
  101. }.flatten
  102. implicit def optFut2FutOpt[A](
  103. x: Option[Future[A]]
  104. )(implicit ec: ExecutionContext): Future[Option[A]] = x match {
  105. case Some(f) => f.map(Some(_))
  106. case None => Future.successful(None)
  107. }
  108. override def findAuthInfoByRefreshToken(token: String) =
  109. withCollection(Authorization) {auths =>
  110. auths.find(equal("refreshToken", Authorization.decodeToken(token)))
  111. .first()
  112. .toFutureOption()
  113. }.flatten
  114. .map(authOpt => withCollection(User) {users =>
  115. optFut2FutOpt(authOpt.map({ auth => users.find(
  116. withDevModeCond(equal("_id", auth.userId))
  117. ).first()
  118. .toFuture()
  119. .map({user => AuthInfo(user, Some(user.email), None, None)})
  120. }))
  121. }.flatten).flatten
  122. override def refreshAccessToken(
  123. auth: AuthInfo[User],
  124. refreshToken: String
  125. ): Future[AccessToken] = {
  126. val newAuth = Authorization(auth.user)
  127. withCollection(Authorization) {auths =>
  128. auths.replaceOne(
  129. and(
  130. equal("userId", auth.user._id),
  131. equal("refreshToken", Authorization.decodeToken(refreshToken))
  132. ),
  133. newAuth
  134. ).head().map(_ => newAuth.toToken())
  135. }.flatten
  136. }
  137. def revokeAccessToken(
  138. auth: AuthInfo[Authorization],
  139. user: String,
  140. refresh: String
  141. ): Future[Unit] =
  142. if (user != auth.user.email)
  143. Future.failed(
  144. new InvalidClient("Invalid client or client is not authorized")
  145. )
  146. else withCollection(Authorization) {auths =>
  147. auths.deleteOne(and(
  148. equal("userId", auth.user.userId),
  149. equal("refreshToken", Authorization.decodeToken(refresh))
  150. )).head().map { _ => {} }
  151. }.flatten
  152. override def deleteAuthCode(token: String): Future[Unit] =
  153. throw new UnsupportedGrantType("Code grant authorizations are not supported.")
  154. override def findAuthInfoByCode(
  155. code: String
  156. ): Future[Option[AuthInfo[User]]] =
  157. throw new UnsupportedGrantType("Code grant authorizations are not supported.")
  158. }