| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185 |
- 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,PasswordChange}
- 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((_) =>
- newTokenForUser(user, body.password)
- ).flatten
- }.flatten
- }
- }
- }
- // TODO: Unit test API
- def changePassword() = AuthorizedAction[Authorization](oauth).async(parse.json)
- { implicit request: AuthInfoRequest[JsValue, Authorization] =>
- val body = request.body.as[PasswordChange]
- oauth.validateUsernamePassword(request.authInfo.user.email, body.oldPassword) flatMap {
- case Some(user) => withCollection(User) { (users) =>
- users.replaceOne(
- equal("_id", request.authInfo.user.userId),
- user.changePassword(body.newPassword)
- ).head().transform {
- case Success(_) => Success(
- newTokenForUser(user, body.newPassword)
- )
- case Failure(e) => Failure(e)
- } flatten
- } flatten
- case None => Future.failed(new UnauthorizedClient("password does not match"))
- }
- }
- def newTokenForUser(user: User, password: String)(implicit request: Request[_]) =
- issueAccessToken(oauth)(Request[Map[String, Seq[String]]](
- request.withHeaders(Headers(
- "Authorization" -> encodeBasicAuth(user.email, password)
- )),
- Map("grant_type" -> Seq(OAuthGrantType.PASSWORD))
- ), ec)
- 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) { (users) =>
- // TODO: Email is a privacy issue. Change to a generic username
- users.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
- }
|