UserController.scala 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. package com.weEat.controllers
  2. import java.util.Base64
  3. import javax.inject.{Inject,Singleton}
  4. import play.api.mvc._
  5. import play.api.libs.json._
  6. import com.weEat.services.OAuth2Service
  7. import com.weEat.models.{Authorization,User}
  8. import com.weEat.shared.models.{UserRegistration,PasswordChange}
  9. import scalaoauth2.provider._
  10. import scala.concurrent.Future
  11. import scala.util.{Success,Failure}
  12. import org.mongodb.scala.ObservableImplicits
  13. import com.weEat.services.MongoDBService
  14. import org.mongodb.scala.model.Filters._
  15. import org.bson.types.ObjectId
  16. @Singleton
  17. class UserController @Inject()(
  18. val controllerComponents: ControllerComponents,
  19. oauth: OAuth2Service,
  20. db: MongoDBService
  21. ) extends BaseController
  22. with OAuth2Provider
  23. with ObservableImplicits
  24. with OAuth2ProviderActionBuilders {
  25. implicit val ec = scala.concurrent.ExecutionContext.global
  26. import db.withCollection
  27. override val tokenEndpoint = new TokenEndpoint {
  28. override val handlers = Map(
  29. //OAuthGrantType.AUTHORIZATION_CODE -> new AuthorizationCode(),
  30. OAuthGrantType.REFRESH_TOKEN -> new RefreshToken(),
  31. //OAuthGrantType.CLIENT_CREDENTIALS -> new ClientCredentials(),
  32. //OAuthGrantType.IMPLICIT -> new Implicit(),
  33. OAuthGrantType.PASSWORD -> new Password()
  34. )
  35. }
  36. def encodeBasicAuth(email: String, pass: String) =
  37. s"Basic " + Base64.getEncoder().encodeToString(s"$email:$pass".getBytes())
  38. def decodeBasicAuth(auth: String): Option[(String, String)] = {
  39. val format = raw"Basic ([\d\w+/=]*)".r
  40. auth match {
  41. case format(cred) => {
  42. val split = new String(Base64.getDecoder().decode(cred)).split(":", 2)
  43. Some((split(0), split(1)))
  44. }
  45. case _ => None
  46. }
  47. }
  48. def accessToken = Action.async { implicit request: Request[Any] =>
  49. issueAccessToken(oauth)
  50. }
  51. def revokeAccessToken() =
  52. AuthorizedAction[Authorization](oauth).async(parse.json)
  53. { implicit request: AuthInfoRequest[JsValue, Authorization] =>
  54. val email = (request.body \ "client_id").as[String]
  55. val refresh = (request.body \ "refresh_token").as[String]
  56. oauth.revokeAccessToken(request.authInfo, email, refresh).transform {
  57. case Success(_) => Success(
  58. Ok("").withHeaders("Cache-Control" -> "no-store", "Pragma" -> "no-cache")
  59. )
  60. case Failure(e: OAuthError) => Success(
  61. new Status(e.statusCode)(responseOAuthErrorJson(e))
  62. .withHeaders(responseOAuthErrorHeader(e))
  63. )
  64. case Failure(e) => Failure(e)
  65. }
  66. }
  67. private val emailRegex = """(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])"""
  68. private def isEmail(email: String): Boolean = email.matches(emailRegex)
  69. // private def checkEmail(conn: Connection, email: String): Future[Boolean] = {
  70. // val emailquery = "SELECT id FROM users WHERE email=? LIMIT 1"
  71. // Future {
  72. // TryWith(conn.prepareStatement(emailquery)) { st =>
  73. // st.setString(1, email)
  74. // st.executeQuery.next
  75. // }
  76. // }.transform(_.flatten)
  77. // }
  78. def validateRegisterRequest(body: UserRegistration) =
  79. if (!isEmail(body.email))
  80. Some("Invalid email.")
  81. else if (body.password.length < 8)
  82. Some("Password too short (Minimum 8 characters).")
  83. else
  84. None
  85. // TODO: Unit test API
  86. def registerUser() = Action.async(parse.json)
  87. { implicit request: Request[JsValue] =>
  88. val body = request.body.as[UserRegistration]
  89. validateRegisterRequest(body) match {
  90. case Some(reason) => Future.successful(BadRequest(reason))
  91. case None => {
  92. val user = User(body)
  93. withCollection(User) { (collection) =>
  94. collection.insertOne(user).head().map((_) =>
  95. newTokenForUser(user, body.password)
  96. ).flatten
  97. }.flatten
  98. }
  99. }
  100. }
  101. // TODO: Unit test API
  102. def changePassword() = AuthorizedAction[Authorization](oauth).async(parse.json)
  103. { implicit request: AuthInfoRequest[JsValue, Authorization] =>
  104. val body = request.body.as[PasswordChange]
  105. oauth.validateUsernamePassword(request.authInfo.user.email, body.oldPassword) flatMap {
  106. case Some(user) => withCollection(User) { (users) =>
  107. users.replaceOne(
  108. equal("_id", request.authInfo.user.userId),
  109. user.changePassword(body.newPassword)
  110. ).head().transform {
  111. case Success(_) => Success(
  112. newTokenForUser(user, body.newPassword)
  113. )
  114. case Failure(e) => Failure(e)
  115. } flatten
  116. } flatten
  117. case None => Future.failed(new UnauthorizedClient("password does not match"))
  118. }
  119. }
  120. def newTokenForUser(user: User, password: String)(implicit request: Request[_]) =
  121. issueAccessToken(oauth)(Request[Map[String, Seq[String]]](
  122. request.withHeaders(Headers(
  123. "Authorization" -> encodeBasicAuth(user.email, password)
  124. )),
  125. Map("grant_type" -> Seq(OAuthGrantType.PASSWORD))
  126. ), ec)
  127. def getName() = AuthorizedAction[Authorization](oauth).async
  128. { implicit request: AuthInfoRequest[AnyContent, Authorization] =>
  129. getUser(request.authInfo.user.userId).map({
  130. case Some(user) => Ok("%s %s".format(user.fname, user.lname))
  131. case None =>
  132. throw new IllegalStateException("Authorized user does not exist!")
  133. })
  134. }
  135. // TODO: Unit test API
  136. def getUsers() = Action.async
  137. { implicit request: Request[AnyContent] =>
  138. withCollection(User) { (users) =>
  139. // TODO: Email is a privacy issue. Change to a generic username
  140. users.find().map((res) => res.email)
  141. .toFuture()
  142. .transform({
  143. case Success(x) => Success(Ok(Json.toJson(x)))
  144. case Failure(x) => throw x
  145. })
  146. }.flatten
  147. }
  148. def getUser(id: ObjectId): Future[Option[User]] = withCollection(User) { (users) =>
  149. users.find(equal("_id", id))
  150. .first()
  151. .toFutureOption()
  152. }.flatten
  153. def get(id: String) = Action.async { implicit request: Request[AnyContent] =>
  154. getUser(new ObjectId(id)).map({
  155. case Some(user) => Ok(Json.toJson(user.toShared()))
  156. case None => NotFound("No such user with this id")
  157. })
  158. }
  159. def deleteUser(id: ObjectId) = {
  160. withCollection(User) { (collection) =>
  161. collection.findOneAndDelete(equal("_id", id))
  162. .toFuture()
  163. }
  164. }.flatten
  165. }