package com.weEat.controllers import java.util.Base64 import javax.inject.{Inject,Singleton} import play.api.mvc._ import play.api.libs.json._ import com.weEat.services.OAuth2Service import com.weEat.models.{Authorization,User} import com.weEat.shared.models.UserRegistration import scalaoauth2.provider._ import scala.concurrent.Future import scala.util.{Success,Failure} import org.mongodb.scala.ObservableImplicits import com.weEat.services.MongoDBService import org.mongodb.scala.model.Filters._ import org.bson.types.ObjectId @Singleton class UserController @Inject()( val controllerComponents: ControllerComponents, oauth: OAuth2Service, db: MongoDBService ) extends BaseController with OAuth2Provider with ObservableImplicits with OAuth2ProviderActionBuilders { implicit val ec = scala.concurrent.ExecutionContext.global import db.withCollection override val tokenEndpoint = new TokenEndpoint { override val handlers = Map( //OAuthGrantType.AUTHORIZATION_CODE -> new AuthorizationCode(), OAuthGrantType.REFRESH_TOKEN -> new RefreshToken(), //OAuthGrantType.CLIENT_CREDENTIALS -> new ClientCredentials(), //OAuthGrantType.IMPLICIT -> new Implicit(), OAuthGrantType.PASSWORD -> new Password() ) } def encodeBasicAuth(email: String, pass: String) = s"Basic " + Base64.getEncoder().encodeToString(s"$email:$pass".getBytes()) def decodeBasicAuth(auth: String): Option[(String, String)] = { val format = raw"Basic ([\d\w+/=]*)".r auth match { case format(cred) => { val split = new String(Base64.getDecoder().decode(cred)).split(":", 2) Some((split(0), split(1))) } case _ => None } } def accessToken = Action.async { implicit request: Request[Any] => issueAccessToken(oauth) } def revokeAccessToken() = AuthorizedAction[Authorization](oauth).async(parse.json) { implicit request: AuthInfoRequest[JsValue, Authorization] => val email = (request.body \ "client_id").as[String] val refresh = (request.body \ "refresh_token").as[String] oauth.revokeAccessToken(request.authInfo, email, refresh).transform { case Success(_) => Success( Ok("").withHeaders("Cache-Control" -> "no-store", "Pragma" -> "no-cache") ) case Failure(e: OAuthError) => Success( new Status(e.statusCode)(responseOAuthErrorJson(e)) .withHeaders(responseOAuthErrorHeader(e)) ) case Failure(e) => Failure(e) } } 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])+)\])""" private def isEmail(email: String): Boolean = email.matches(emailRegex) // private def checkEmail(conn: Connection, email: String): Future[Boolean] = { // val emailquery = "SELECT id FROM users WHERE email=? LIMIT 1" // Future { // TryWith(conn.prepareStatement(emailquery)) { st => // st.setString(1, email) // st.executeQuery.next // } // }.transform(_.flatten) // } def validateRegisterRequest(body: UserRegistration) = if (!isEmail(body.email)) Some("Invalid email.") else if (body.password.length < 8) Some("Password too short (Minimum 8 characters).") else None // TODO: Unit test API def registerUser() = Action.async(parse.json) { implicit request: Request[JsValue] => val body = request.body.as[UserRegistration] validateRegisterRequest(body) match { case Some(reason) => Future.successful(BadRequest(reason)) case None => { val user = User(body) withCollection(User) { collection => { collection.insertOne(user).head().map(_ => { issueAccessToken(oauth)(Request[Map[String, Seq[String]]]( request.withHeaders(Headers( "Authorization" -> encodeBasicAuth(user.email, body.password) )), Map("grant_type" -> Seq(OAuthGrantType.PASSWORD)) ), ec)} ).flatten} }.flatten } } } def getName() = AuthorizedAction[Authorization](oauth).async { implicit request: AuthInfoRequest[AnyContent, Authorization] => getUser(request.authInfo.user.userId).map({ case Some(user) => Ok("%s %s".format(user.fname, user.lname)) case None => throw new IllegalStateException("Authorized user does not exist!") }) } // TODO: Unit test API def getUsers() = Action.async { implicit request: Request[AnyContent] => withCollection(User) { collection => collection.find().map(res => res.email) .toFuture() .transform({ case Success(x) => Success(Ok(Json.toJson(x))) case Failure(x) => throw x }) }.flatten } def getUser(id: ObjectId): Future[Option[User]] = withCollection(User) { users => users.find(equal("_id", id)) .first() .toFutureOption() }.flatten def get(id: String) = Action.async { implicit request: Request[AnyContent] => getUser(new ObjectId(id)).map({ case Some(user) => Ok(Json.toJson(user.toShared())) case None => NotFound("No such user with this id") }) } def deleteUser(id: ObjectId) = { withCollection(User) { (collection) => collection.findOneAndDelete(equal("_id", id)) .toFuture() } }.flatten }