OAuth2Service.scala 6.0 KB

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